fix: address review findings — CJS build (tsup), workflowCost signature, bottlenecks empty-graph test
- C1(critical): Replace tsc build with tsup for dual ESM + CJS output - W2(warning): Change workflowCost to accept TaskGraph instead of TaskGraphInner - S1(suggestion): Add test for bottlenecks empty-graph early return - S2(suggestion): Document dangling-reference detection is unreachable via public API
This commit is contained in:
389
package-lock.json
generated
389
package-lock.json
generated
@@ -20,6 +20,7 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.0",
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
"tsup": "^8.5.1",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.1.0"
|
||||
},
|
||||
@@ -1206,6 +1207,19 @@
|
||||
"integrity": "sha512-UYvAq/XCA7xoh1juWDYsq3W0WywOB+pz8cgVnE1b45ZfdMhBvHDrgmSFG3jXeZSr2tMTYLGHFHON+ekG05Jebg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.16.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
|
||||
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
|
||||
@@ -1232,6 +1246,13 @@
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/any-promise": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
|
||||
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/assertion-error": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
|
||||
@@ -1284,6 +1305,22 @@
|
||||
"node": "18 || 20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/bundle-require": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz",
|
||||
"integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"load-tsconfig": "^0.2.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"esbuild": ">=0.18"
|
||||
}
|
||||
},
|
||||
"node_modules/cac": {
|
||||
"version": "6.7.14",
|
||||
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
|
||||
@@ -1321,6 +1358,22 @@
|
||||
"node": ">= 16"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"readdirp": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.16.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
@@ -1341,6 +1394,33 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
|
||||
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/confbox": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
|
||||
"integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/consola": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz",
|
||||
"integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
@@ -1494,6 +1574,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/fix-dts-default-cjs-exports": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz",
|
||||
"integrity": "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"magic-string": "^0.30.17",
|
||||
"mlly": "^1.7.4",
|
||||
"rollup": "^4.34.8"
|
||||
}
|
||||
},
|
||||
"node_modules/foreground-child": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
|
||||
@@ -1795,6 +1887,16 @@
|
||||
"@pkgjs/parseargs": "^0.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/joycon": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz",
|
||||
"integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz",
|
||||
@@ -1802,6 +1904,36 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lilconfig": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
|
||||
"integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antonk52"
|
||||
}
|
||||
},
|
||||
"node_modules/lines-and-columns": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/load-tsconfig": {
|
||||
"version": "0.2.5",
|
||||
"resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz",
|
||||
"integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/loupe": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz",
|
||||
@@ -1880,6 +2012,19 @@
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/mlly": {
|
||||
"version": "1.8.2",
|
||||
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz",
|
||||
"integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"acorn": "^8.16.0",
|
||||
"pathe": "^2.0.3",
|
||||
"pkg-types": "^1.3.1",
|
||||
"ufo": "^1.6.3"
|
||||
}
|
||||
},
|
||||
"node_modules/mnemonist": {
|
||||
"version": "0.39.8",
|
||||
"resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.39.8.tgz",
|
||||
@@ -1896,6 +2041,18 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mz": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
||||
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"any-promise": "^1.0.0",
|
||||
"object-assign": "^4.0.1",
|
||||
"thenify-all": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.11",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||
@@ -1915,6 +2072,16 @@
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/obliterator": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.5.tgz",
|
||||
@@ -2001,6 +2168,28 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/pirates": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
|
||||
"integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/pkg-types": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz",
|
||||
"integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"confbox": "^0.1.8",
|
||||
"mlly": "^1.7.4",
|
||||
"pathe": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.12",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz",
|
||||
@@ -2030,6 +2219,73 @@
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-load-config": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
|
||||
"integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lilconfig": "^3.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"jiti": ">=1.21.0",
|
||||
"postcss": ">=8.0.9",
|
||||
"tsx": "^4.8.1",
|
||||
"yaml": "^2.4.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"jiti": {
|
||||
"optional": true
|
||||
},
|
||||
"postcss": {
|
||||
"optional": true
|
||||
},
|
||||
"tsx": {
|
||||
"optional": true
|
||||
},
|
||||
"yaml": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14.18.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-from": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
|
||||
"integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.60.2",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz",
|
||||
@@ -2131,6 +2387,16 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.7.6",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
|
||||
"integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
@@ -2272,6 +2538,29 @@
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/sucrase": {
|
||||
"version": "3.35.1",
|
||||
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
|
||||
"integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.2",
|
||||
"commander": "^4.0.0",
|
||||
"lines-and-columns": "^1.1.6",
|
||||
"mz": "^2.7.0",
|
||||
"pirates": "^4.0.1",
|
||||
"tinyglobby": "^0.2.11",
|
||||
"ts-interface-checker": "^0.1.9"
|
||||
},
|
||||
"bin": {
|
||||
"sucrase": "bin/sucrase",
|
||||
"sucrase-node": "bin/sucrase-node"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
@@ -2300,6 +2589,29 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/thenify": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
||||
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"any-promise": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/thenify-all": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
|
||||
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"thenify": ">= 3.1.0 < 4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/tinybench": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
|
||||
@@ -2361,6 +2673,76 @@
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tree-kill": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
|
||||
"integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"tree-kill": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-interface-checker": {
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
|
||||
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/tsup": {
|
||||
"version": "8.5.1",
|
||||
"resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.1.tgz",
|
||||
"integrity": "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bundle-require": "^5.1.0",
|
||||
"cac": "^6.7.14",
|
||||
"chokidar": "^4.0.3",
|
||||
"consola": "^3.4.0",
|
||||
"debug": "^4.4.0",
|
||||
"esbuild": "^0.27.0",
|
||||
"fix-dts-default-cjs-exports": "^1.0.0",
|
||||
"joycon": "^3.1.1",
|
||||
"picocolors": "^1.1.1",
|
||||
"postcss-load-config": "^6.0.1",
|
||||
"resolve-from": "^5.0.0",
|
||||
"rollup": "^4.34.8",
|
||||
"source-map": "^0.7.6",
|
||||
"sucrase": "^3.35.0",
|
||||
"tinyexec": "^0.3.2",
|
||||
"tinyglobby": "^0.2.11",
|
||||
"tree-kill": "^1.2.2"
|
||||
},
|
||||
"bin": {
|
||||
"tsup": "dist/cli-default.js",
|
||||
"tsup-node": "dist/cli-node.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@microsoft/api-extractor": "^7.36.0",
|
||||
"@swc/core": "^1",
|
||||
"postcss": "^8.4.12",
|
||||
"typescript": ">=4.5.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@microsoft/api-extractor": {
|
||||
"optional": true
|
||||
},
|
||||
"@swc/core": {
|
||||
"optional": true
|
||||
},
|
||||
"postcss": {
|
||||
"optional": true
|
||||
},
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
@@ -2375,6 +2757,13 @@
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/ufo": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz",
|
||||
"integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"build": "tsup",
|
||||
"build:tsc": "tsc",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"test:coverage": "vitest run --coverage",
|
||||
@@ -49,6 +50,7 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.0",
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
"tsup": "^8.5.1",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.1.0"
|
||||
},
|
||||
|
||||
@@ -5,6 +5,7 @@ import type {
|
||||
WorkflowCostResult,
|
||||
} from "../schema/results.js";
|
||||
import type { TaskGraphInner } from "../graph/construction.js";
|
||||
import { TaskGraph } from "../graph/index.js";
|
||||
import { topologicalOrder } from "../graph/queries.js";
|
||||
import { resolveDefaults } from "./defaults.js";
|
||||
|
||||
@@ -149,21 +150,22 @@ export function computeEffectiveP(
|
||||
* **remain in the propagation chain** with p=1.0. Removing completed tasks from
|
||||
* propagation would worsen downstream probability estimates.
|
||||
*
|
||||
* @param graph - The graphology graph instance
|
||||
* @param graph - The TaskGraph instance
|
||||
* @param options - Optional configuration for the analysis
|
||||
* @returns WorkflowCostResult with per-task entries and aggregate totals
|
||||
* @throws {CircularDependencyError} If the graph contains cycles
|
||||
*/
|
||||
export function workflowCost(
|
||||
graph: TaskGraphInner,
|
||||
graph: TaskGraph,
|
||||
options?: WorkflowCostOptions,
|
||||
): WorkflowCostResult {
|
||||
const raw = graph.raw;
|
||||
const propagationMode = options?.propagationMode ?? "dag-propagate";
|
||||
const defaultQualityRetention = options?.defaultQualityRetention ?? 0.9;
|
||||
const includeCompleted = options?.includeCompleted ?? false;
|
||||
|
||||
// Get topological order — throws CircularDependencyError if cyclic
|
||||
const topoOrder = topologicalOrder(graph);
|
||||
const topoOrder = topologicalOrder(raw);
|
||||
|
||||
// Map of task IDs → their actual success probability for downstream propagation
|
||||
const upstreamSuccessProbs = new Map<string, number>();
|
||||
@@ -172,23 +174,19 @@ export function workflowCost(
|
||||
const taskEntries: WorkflowCostResult["tasks"] = [];
|
||||
|
||||
for (const taskId of topoOrder) {
|
||||
const nodeAttrs = graph.getNodeAttributes(taskId);
|
||||
const nodeAttrs = raw.getNodeAttributes(taskId);
|
||||
const resolved = resolveDefaults(nodeAttrs);
|
||||
const pIntrinsic = resolved.successProbability;
|
||||
|
||||
// Determine the probability to propagate downstream for this task
|
||||
let propagationP: number;
|
||||
let pEffective: number;
|
||||
|
||||
// Completed tasks propagate with p=1.0 when includeCompleted is false
|
||||
const isCompleted = nodeAttrs.status === "completed";
|
||||
|
||||
if (isCompleted && !includeCompleted) {
|
||||
// Completed + excluded: propagate p=1.0, compute pEffective normally but
|
||||
// for propagation purposes the task is a guaranteed success
|
||||
pEffective = computeEffectiveP(
|
||||
taskId,
|
||||
graph,
|
||||
raw,
|
||||
upstreamSuccessProbs,
|
||||
defaultQualityRetention,
|
||||
propagationMode,
|
||||
@@ -196,10 +194,9 @@ export function workflowCost(
|
||||
);
|
||||
propagationP = 1.0;
|
||||
} else {
|
||||
// Normal task: compute pEffective and use it for downstream propagation
|
||||
pEffective = computeEffectiveP(
|
||||
taskId,
|
||||
graph,
|
||||
raw,
|
||||
upstreamSuccessProbs,
|
||||
defaultQualityRetention,
|
||||
propagationMode,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
id: analysis/critical-path
|
||||
name: Implement criticalPath and weightedCriticalPath functions
|
||||
status: pending
|
||||
status: completed
|
||||
depends_on:
|
||||
- graph/construction
|
||||
- graph/queries
|
||||
@@ -33,7 +33,22 @@ Implement `criticalPath` and `weightedCriticalPath` as standalone functions. `cr
|
||||
|
||||
## Notes
|
||||
|
||||
> To be filled by implementation agent
|
||||
Implementation uses topological order + dynamic programming (longest path in DAG).
|
||||
Both functions delegate to a shared `computeLongestPath` helper that:
|
||||
1. Gets topological order (throws CircularDependencyError via `graph.topologicalOrder()`)
|
||||
2. Initializes source nodes with their weight
|
||||
3. Relaxes edges in topological order (DP: dist[v] = max(dist[u] + weight(v)))
|
||||
4. Backtracks from the node with maximum distance to reconstruct the path
|
||||
|
||||
`criticalPath` uses `weightFn = () => 1` (unweighted).
|
||||
`weightedCriticalPath` accepts a custom weight function on `(taskId, attrs)`.
|
||||
|
||||
## Summary
|
||||
|
||||
Implemented `criticalPath` and `weightedCriticalPath` as standalone functions using topological-order DP.
|
||||
- Modified: `src/analysis/critical-path.ts` (full implementation, 161 lines)
|
||||
- Modified: `test/analysis.test.ts` (20 tests covering all acceptance criteria)
|
||||
- Tests: 20, all passing (462 total passing)
|
||||
|
||||
## Summary
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
id: api/public-exports
|
||||
name: Wire up public API surface in src/index.ts
|
||||
status: pending
|
||||
status: completed
|
||||
depends_on:
|
||||
- graph/taskgraph-class
|
||||
- graph/construction
|
||||
@@ -54,4 +54,10 @@ Wire up `src/index.ts` to re-export the full public API surface. This is the mai
|
||||
|
||||
## Summary
|
||||
|
||||
> To be filled on completion
|
||||
Implemented the public API surface in `src/index.ts` using selective named re-exports instead of wildcard `export *`, ensuring no internal implementation details leak through.
|
||||
|
||||
- Modified: `src/index.ts` — rewrote with selective named exports for all public API items
|
||||
- Modified: `src/schema/task.ts` — removed internal `Nullable` re-export (kept import for internal use)
|
||||
- Modified: `src/schema/index.ts` — switched to `export *` (kept as barrel; public API filtering is in src/index.ts)
|
||||
- Modified: `test/schema.test.ts` — removed test for `Nullable` re-export from task.ts (no longer exported)
|
||||
- Tests: 590, all passing
|
||||
@@ -213,6 +213,12 @@ describe('bottlenecks', () => {
|
||||
expect(typeof bottlenecks).toBe('function');
|
||||
});
|
||||
|
||||
it('returns empty array for empty graph', () => {
|
||||
const tg = new TaskGraph();
|
||||
const result = bottlenecks(tg);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('returns array of { taskId, score } objects', () => {
|
||||
const tg = TaskGraph.fromTasks([
|
||||
{ id: 'A', name: 'Task A', dependsOn: [] },
|
||||
|
||||
@@ -601,7 +601,7 @@ describe("workflowCost", () => {
|
||||
{ id: "B", name: "Implementation", dependsOn: ["A"], risk: "medium", scope: "broad", impact: "component" },
|
||||
]);
|
||||
|
||||
const result = workflowCost(graph.raw);
|
||||
const result = workflowCost(graph);
|
||||
|
||||
expect(result.propagationMode).toBe("dag-propagate");
|
||||
|
||||
@@ -632,7 +632,7 @@ describe("workflowCost", () => {
|
||||
{ id: "B", name: "Implementation", dependsOn: ["A"], risk: "medium", scope: "broad", impact: "component" },
|
||||
]);
|
||||
|
||||
const result = workflowCost(graph.raw, { propagationMode: "independent" });
|
||||
const result = workflowCost(graph, { propagationMode: "independent" });
|
||||
|
||||
expect(result.propagationMode).toBe("independent");
|
||||
|
||||
@@ -655,8 +655,8 @@ describe("workflowCost", () => {
|
||||
{ id: "C", name: "Review", dependsOn: ["B"], risk: "low", scope: "narrow", impact: "isolated" },
|
||||
]);
|
||||
|
||||
const dagResult = workflowCost(graph.raw, { propagationMode: "dag-propagate" });
|
||||
const indepResult = workflowCost(graph.raw, { propagationMode: "independent" });
|
||||
const dagResult = workflowCost(graph, { propagationMode: "dag-propagate" });
|
||||
const indepResult = workflowCost(graph, { propagationMode: "independent" });
|
||||
|
||||
// In dag-propagate, every task that has parents should have pEffective < pIntrinsic
|
||||
// (assuming qualityRetention < 1.0)
|
||||
@@ -695,7 +695,7 @@ describe("workflowCost", () => {
|
||||
{ id: "C", name: "Task C", dependsOn: ["B"], risk: "medium", scope: "narrow", impact: "isolated" },
|
||||
]);
|
||||
|
||||
const result = workflowCost(graph.raw, { propagationMode: "dag-propagate" });
|
||||
const result = workflowCost(graph, { propagationMode: "dag-propagate" });
|
||||
|
||||
const taskA = result.tasks.find(t => t.taskId === "A")!;
|
||||
const taskB = result.tasks.find(t => t.taskId === "B")!;
|
||||
@@ -727,7 +727,7 @@ describe("workflowCost", () => {
|
||||
|
||||
it("diamond graph: convergence multiplies inherited quality factors", () => {
|
||||
const graph = createDiamondGraph();
|
||||
const result = workflowCost(graph.raw);
|
||||
const result = workflowCost(graph);
|
||||
|
||||
const taskA = result.tasks.find(t => t.taskId === "A")!;
|
||||
const taskB = result.tasks.find(t => t.taskId === "B")!;
|
||||
@@ -760,7 +760,7 @@ describe("workflowCost", () => {
|
||||
{ id: "B", name: "Task B", dependsOn: ["A"], risk: "medium", scope: "narrow", impact: "isolated" },
|
||||
]);
|
||||
|
||||
const result = workflowCost(graph.raw, { includeCompleted: false });
|
||||
const result = workflowCost(graph, { includeCompleted: false });
|
||||
|
||||
// A should not appear in the task list
|
||||
expect(result.tasks.find(t => t.taskId === "A")).toBeUndefined();
|
||||
@@ -780,7 +780,7 @@ describe("workflowCost", () => {
|
||||
{ id: "B", name: "Task B", dependsOn: ["A"], risk: "medium", scope: "narrow", impact: "isolated" },
|
||||
]);
|
||||
|
||||
const result = workflowCost(graph.raw); // default includeCompleted=false
|
||||
const result = workflowCost(graph); // default includeCompleted=false
|
||||
|
||||
// A should NOT appear in the task list (default behavior)
|
||||
expect(result.tasks.find(t => t.taskId === "A")).toBeUndefined();
|
||||
@@ -798,7 +798,7 @@ describe("workflowCost", () => {
|
||||
{ id: "B", name: "Task B", dependsOn: ["A"], risk: "medium", scope: "narrow", impact: "isolated" },
|
||||
]);
|
||||
|
||||
const result = workflowCost(graph.raw, { includeCompleted: true });
|
||||
const result = workflowCost(graph, { includeCompleted: true });
|
||||
|
||||
// A should appear in the task list when explicitly included
|
||||
const taskA = result.tasks.find(t => t.taskId === "A")!;
|
||||
@@ -827,7 +827,7 @@ describe("workflowCost", () => {
|
||||
{ id: "C", name: "Task C", dependsOn: ["B"], risk: "medium", scope: "narrow", impact: "isolated" },
|
||||
]);
|
||||
|
||||
const result = workflowCost(graph.raw, { includeCompleted: false });
|
||||
const result = workflowCost(graph, { includeCompleted: false });
|
||||
|
||||
// A should not be in results
|
||||
expect(result.tasks.find(t => t.taskId === "A")).toBeUndefined();
|
||||
@@ -852,7 +852,7 @@ describe("workflowCost", () => {
|
||||
{ id: "B", name: "Task B", dependsOn: [], risk: "medium", scope: "narrow", impact: "isolated", status: "blocked" },
|
||||
]);
|
||||
|
||||
const result = workflowCost(graph.raw, { includeCompleted: false });
|
||||
const result = workflowCost(graph, { includeCompleted: false });
|
||||
|
||||
// Both failed and blocked tasks should be included
|
||||
expect(result.tasks.find(t => t.taskId === "A")).toBeDefined();
|
||||
@@ -861,12 +861,12 @@ describe("workflowCost", () => {
|
||||
|
||||
it("throws CircularDependencyError for cyclic graph", () => {
|
||||
const graph = createCyclicGraph();
|
||||
expect(() => workflowCost(graph.raw)).toThrow(CircularDependencyError);
|
||||
expect(() => workflowCost(graph)).toThrow(CircularDependencyError);
|
||||
});
|
||||
|
||||
it("handles empty graph", () => {
|
||||
const graph = new TaskGraph();
|
||||
const result = workflowCost(graph.raw);
|
||||
const result = workflowCost(graph);
|
||||
|
||||
expect(result.tasks).toEqual([]);
|
||||
expect(result.totalEv).toBe(0);
|
||||
@@ -879,7 +879,7 @@ describe("workflowCost", () => {
|
||||
{ id: "A", name: "Task A", dependsOn: [], risk: "medium", scope: "narrow", impact: "isolated" },
|
||||
]);
|
||||
|
||||
const result = workflowCost(graph.raw);
|
||||
const result = workflowCost(graph);
|
||||
|
||||
expect(result.tasks).toHaveLength(1);
|
||||
expect(result.tasks[0]!.taskId).toBe("A");
|
||||
@@ -896,7 +896,7 @@ describe("workflowCost", () => {
|
||||
tg.raw.addEdgeWithKey("A->B", "A", "B", {});
|
||||
|
||||
// With defaultQualityRetention = 1.0, should behave like independent model
|
||||
const result = workflowCost(tg.raw, { defaultQualityRetention: 1.0 });
|
||||
const result = workflowCost(tg, { defaultQualityRetention: 1.0 });
|
||||
|
||||
const taskB = result.tasks.find(t => t.taskId === "B")!;
|
||||
// inheritedQuality = parentP + (1-parentP) * 1.0 = 1.0
|
||||
@@ -916,7 +916,7 @@ describe("workflowCost", () => {
|
||||
]
|
||||
);
|
||||
|
||||
const result = workflowCost(graph.raw); // default qualityRetention=0.9
|
||||
const result = workflowCost(graph); // default qualityRetention=0.9
|
||||
const taskB = result.tasks.find(t => t.taskId === "B")!;
|
||||
|
||||
// Per-edge qualityRetention = 0.5 overrides default 0.9
|
||||
@@ -932,7 +932,7 @@ describe("workflowCost", () => {
|
||||
{ id: "C", name: "Task C", dependsOn: ["B"], risk: "medium", scope: "narrow", impact: "isolated" },
|
||||
]);
|
||||
|
||||
const result = workflowCost(graph.raw, { limit: 2 });
|
||||
const result = workflowCost(graph, { limit: 2 });
|
||||
|
||||
expect(result.tasks).toHaveLength(2);
|
||||
// limit only affects the result list, not propagation
|
||||
@@ -946,7 +946,7 @@ describe("workflowCost", () => {
|
||||
{ id: "B", name: "Task B", dependsOn: ["A"], risk: "medium", scope: "narrow", impact: "isolated" },
|
||||
]);
|
||||
|
||||
const result = workflowCost(graph.raw);
|
||||
const result = workflowCost(graph);
|
||||
for (const task of result.tasks) {
|
||||
expect(typeof task.pIntrinsic).toBe("number");
|
||||
expect(typeof task.pEffective).toBe("number");
|
||||
@@ -965,7 +965,7 @@ describe("workflowCost", () => {
|
||||
{ id: "B", name: "Task B", dependsOn: [], risk: "medium", scope: "narrow", impact: "isolated" },
|
||||
]);
|
||||
|
||||
const result = workflowCost(graph.raw, { propagationMode: "independent" });
|
||||
const result = workflowCost(graph, { propagationMode: "independent" });
|
||||
|
||||
// Two independent tasks with medium risk, narrow scope, isolated impact
|
||||
// p=0.80, scopeCost=2.0, impactWeight=1.0
|
||||
@@ -981,7 +981,7 @@ describe("workflowCost", () => {
|
||||
{ id: "A", name: "Task A", dependsOn: [] },
|
||||
]);
|
||||
|
||||
const result = workflowCost(graph.raw);
|
||||
const result = workflowCost(graph);
|
||||
|
||||
const taskA = result.tasks[0]!;
|
||||
// defaults: risk=medium (p=0.80), scope=narrow (costEstimate=2.0), impact=isolated (weight=1.0)
|
||||
@@ -998,7 +998,7 @@ describe("workflowCost", () => {
|
||||
{ id: "C", name: "Task C", dependsOn: ["B"], risk: "low", scope: "narrow", impact: "isolated" },
|
||||
]);
|
||||
|
||||
const result = workflowCost(graph.raw, { propagationMode: "independent" });
|
||||
const result = workflowCost(graph, { propagationMode: "independent" });
|
||||
|
||||
for (const task of result.tasks) {
|
||||
expect(task.pEffective).toBeCloseTo(task.pIntrinsic);
|
||||
@@ -1013,8 +1013,8 @@ describe("workflowCost", () => {
|
||||
{ id: "implementation", name: "Implementation", dependsOn: ["planning"], risk: "medium", scope: "broad", impact: "component" },
|
||||
]);
|
||||
|
||||
const dagResult = workflowCost(graph.raw, { propagationMode: "dag-propagate" });
|
||||
const indepResult = workflowCost(graph.raw, { propagationMode: "independent" });
|
||||
const dagResult = workflowCost(graph, { propagationMode: "dag-propagate" });
|
||||
const indepResult = workflowCost(graph, { propagationMode: "independent" });
|
||||
|
||||
const dagImpl = dagResult.tasks.find(t => t.taskId === "implementation")!;
|
||||
const indepImpl = indepResult.tasks.find(t => t.taskId === "implementation")!;
|
||||
@@ -1039,8 +1039,8 @@ describe("workflowCost", () => {
|
||||
{ id: "B", name: "Task B", dependsOn: [], risk: "medium", scope: "narrow", impact: "isolated" },
|
||||
]);
|
||||
|
||||
const dagResult = workflowCost(graph.raw, { propagationMode: "dag-propagate" });
|
||||
const indepResult = workflowCost(graph.raw, { propagationMode: "independent" });
|
||||
const dagResult = workflowCost(graph, { propagationMode: "dag-propagate" });
|
||||
const indepResult = workflowCost(graph, { propagationMode: "independent" });
|
||||
|
||||
// No dependencies → no propagation → same result
|
||||
expect(dagResult.tasks[0]!.pEffective).toBeCloseTo(indepResult.tasks[0]!.pEffective);
|
||||
@@ -1055,13 +1055,13 @@ describe("workflowCost", () => {
|
||||
describe("workflowCost cycle detection", () => {
|
||||
it("throws CircularDependencyError when graph has cycles", () => {
|
||||
const graph = createCyclicGraph();
|
||||
expect(() => workflowCost(graph.raw)).toThrow(CircularDependencyError);
|
||||
expect(() => workflowCost(graph)).toThrow(CircularDependencyError);
|
||||
});
|
||||
|
||||
it("CircularDependencyError contains cycle information", () => {
|
||||
const graph = createCyclicGraph();
|
||||
try {
|
||||
workflowCost(graph.raw);
|
||||
workflowCost(graph);
|
||||
expect.fail("Should have thrown CircularDependencyError");
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(CircularDependencyError);
|
||||
|
||||
@@ -420,6 +420,11 @@ describe('validateGraph', () => {
|
||||
expect(danglingErrors).toHaveLength(0);
|
||||
});
|
||||
|
||||
// Note: dangling-reference detection (lines 78-93 in validation.ts) is unreachable
|
||||
// through the public API because graphology's mergeEdge auto-creates missing nodes
|
||||
// and addEdgeWithKey rejects non-existent source/target. The code is a defensive
|
||||
// guard for direct raw graph mutation that bypasses TaskGraph invariants.
|
||||
|
||||
it('detects multiple independent cycles', () => {
|
||||
// Create a graph with two independent cycles
|
||||
const data: TaskGraphSerialized = {
|
||||
|
||||
10
tsup.config.ts
Normal file
10
tsup.config.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { defineConfig } from 'tsup';
|
||||
|
||||
export default defineConfig({
|
||||
entry: ['src/index.ts'],
|
||||
format: ['esm', 'cjs'],
|
||||
dts: true,
|
||||
sourcemap: true,
|
||||
clean: true,
|
||||
target: 'es2022',
|
||||
});
|
||||
Reference in New Issue
Block a user