@visulima/task-runner 1.0.0-alpha.3 → 1.0.0-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +193 -51
  3. package/dist/affected.d.ts +37 -3
  4. package/dist/cache.d.ts +8 -1
  5. package/dist/command-parser/expand-arguments.d.ts +11 -0
  6. package/dist/command-parser/expand-shortcut.d.ts +15 -0
  7. package/dist/command-parser/expand-wildcard.d.ts +13 -0
  8. package/dist/command-parser/index.d.ts +18 -0
  9. package/dist/command-parser/strip-quotes.d.ts +6 -0
  10. package/dist/concurrent-fallback.d.ts +16 -0
  11. package/dist/concurrent.d.ts +23 -0
  12. package/dist/detect-shell.d.ts +19 -0
  13. package/dist/flow-controllers/index.d.ts +7 -0
  14. package/dist/flow-controllers/input-handler.d.ts +44 -0
  15. package/dist/flow-controllers/log-timings.d.ts +18 -0
  16. package/dist/flow-controllers/restart-process.d.ts +21 -0
  17. package/dist/flow-controllers/teardown.d.ts +22 -0
  18. package/dist/index.d.ts +14 -4
  19. package/dist/index.js +26 -12
  20. package/dist/native-binding.d.ts +44 -2
  21. package/dist/packem_shared/{Cache-IYpTYVUC.js → Cache-iAjRMV2d.js} +5 -5
  22. package/dist/packem_shared/{FingerprintManager-D6Y0erg-.js → FingerprintManager-Cu-ta9ee.js} +0 -1
  23. package/dist/packem_shared/{IncrementalFileHasher-Ds3J6dgb.js → IncrementalFileHasher-Cm_kJY5V.js} +1 -1
  24. package/dist/packem_shared/{TaskOrchestrator-BvYs3ONw.js → TaskOrchestrator-lLn-PH1m.js} +2 -5
  25. package/dist/packem_shared/TerminalBuffer-CnPyFgPB.js +266 -0
  26. package/dist/packem_shared/{filterAffectedTasks-I-18zPg6.js → buildForwardDependencyMap-0BJFMMPv.js} +61 -21
  27. package/dist/packem_shared/{computeTaskHash-BoCnnvIJ.js → computeTaskHash-B2SVZqgp.js} +1 -2
  28. package/dist/packem_shared/createInputHandler-DTfePcTG.js +37 -0
  29. package/dist/packem_shared/{defaultTaskRunner-CrW4v5Ye.js → defaultTaskRunner-BdFTifsh.js} +6 -7
  30. package/dist/packem_shared/detectScriptShell-CR-xXKA4.js +53 -0
  31. package/dist/packem_shared/enforceProjectConstraints-C5Jp_C3u.js +111 -0
  32. package/dist/packem_shared/expandArguments-0AwD2BIA.js +26 -0
  33. package/dist/packem_shared/expandShortcut-BVG05ee4.js +23 -0
  34. package/dist/packem_shared/expandWildcard-B0xN_knq.js +107 -0
  35. package/dist/packem_shared/{findCycle-DF4_BRdO.js → findCycle-DefgNYhg.js} +1 -1
  36. package/dist/packem_shared/formatTimingTable-3qtCM552.js +46 -0
  37. package/dist/packem_shared/isNativeAvailable-BpD28A6Z.js +44 -0
  38. package/dist/packem_shared/parseCommands-D-IgF8Zh.js +26 -0
  39. package/dist/packem_shared/{TaskScheduler-CJilHDta.js → parsePartition-C4-P5RjK.js} +44 -1
  40. package/dist/packem_shared/{projectGraphToDot-VdTjHcVp.js → projectGraphToDot-C8uYeaPo.js} +20 -3
  41. package/dist/packem_shared/runConcurrentFallback-CGHz_f-Q.js +371 -0
  42. package/dist/packem_shared/runConcurrently-qrkWyzXW.js +67 -0
  43. package/dist/packem_shared/runTeardown-BAezH79J.js +49 -0
  44. package/dist/packem_shared/stripQuotes-Cey-zwFf.js +9 -0
  45. package/dist/packem_shared/withRestart-BREjRJa4.js +49 -0
  46. package/dist/project-constraints.d.ts +9 -0
  47. package/dist/task-scheduler.d.ts +23 -0
  48. package/dist/terminal-buffer.d.ts +29 -0
  49. package/dist/types.d.ts +239 -1
  50. package/index.js +769 -0
  51. package/package.json +14 -13
  52. package/binding.js +0 -204
  53. package/dist/packem_shared/isNativeAvailable-BWhnZ4ES.js +0 -19
  54. package/dist/packem_shared/{RemoteCache-BDqrnDEi.js → RemoteCache-BFceSe4a.js} +1 -1
@@ -22,8 +22,10 @@ const {
22
22
  } = __cjs_getBuiltinModule("node:child_process");
23
23
 
24
24
  const validateGitRef = (ref) => {
25
- if (!/^[a-zA-Z0-9._\-/~^@{}]+$/.test(ref)) {
26
- throw new Error(`Invalid git ref: "${ref}". Only alphanumeric characters, dots, dashes, underscores, slashes, tildes, carets, and @ are allowed.`);
25
+ if (!/^[\w./~^@{}][\w.\-/~^@{}]*$/.test(ref)) {
26
+ throw new Error(
27
+ `Invalid git ref: "${ref}". Refs must start with an alphanumeric character or one of _ . / ~ ^ @ { } and may only contain letters, digits, dots, dashes, underscores, slashes, tildes, carets, @, and braces.`
28
+ );
27
29
  }
28
30
  };
29
31
  const findProjectForFile = (filePath, projects) => {
@@ -38,34 +40,69 @@ const findProjectForFile = (filePath, projects) => {
38
40
  }
39
41
  return bestMatch;
40
42
  };
41
- const expandAffected = (affectedProjects, projectGraph) => {
42
- const reverseDependencies = /* @__PURE__ */ new Map();
43
+ const buildReverseDependencyMap = (projectGraph) => {
44
+ const map = /* @__PURE__ */ new Map();
43
45
  for (const [project, dependencies] of Object.entries(projectGraph.dependencies)) {
44
46
  for (const dependency of dependencies) {
45
- let set = reverseDependencies.get(dependency.target);
47
+ let set = map.get(dependency.target);
46
48
  if (!set) {
47
49
  set = /* @__PURE__ */ new Set();
48
- reverseDependencies.set(dependency.target, set);
50
+ map.set(dependency.target, set);
49
51
  }
50
52
  set.add(project);
51
53
  }
52
54
  }
53
- const queue = [...affectedProjects];
55
+ return map;
56
+ };
57
+ const buildForwardDependencyMap = (projectGraph) => {
58
+ const map = /* @__PURE__ */ new Map();
59
+ for (const [project, dependencies] of Object.entries(projectGraph.dependencies)) {
60
+ const set = /* @__PURE__ */ new Set();
61
+ for (const dependency of dependencies) {
62
+ set.add(dependency.target);
63
+ }
64
+ if (set.size > 0) {
65
+ map.set(project, set);
66
+ }
67
+ }
68
+ return map;
69
+ };
70
+ const expandInDirection = (affected, seeds, adjacency, scope) => {
71
+ const added = /* @__PURE__ */ new Set();
72
+ const visited = new Set(seeds);
73
+ const queue = [...seeds];
54
74
  while (queue.length > 0) {
55
75
  const project = queue.shift();
56
- if (project === void 0) {
76
+ const neighbors = adjacency.get(project);
77
+ if (!neighbors) {
57
78
  continue;
58
79
  }
59
- const dependents = reverseDependencies.get(project);
60
- if (dependents) {
61
- for (const dependent of dependents) {
62
- if (!affectedProjects.has(dependent)) {
63
- affectedProjects.add(dependent);
64
- queue.push(dependent);
80
+ for (const neighbor of neighbors) {
81
+ if (!visited.has(neighbor)) {
82
+ visited.add(neighbor);
83
+ affected.add(neighbor);
84
+ added.add(neighbor);
85
+ if (scope === "deep") {
86
+ queue.push(neighbor);
65
87
  }
66
88
  }
67
89
  }
68
90
  }
91
+ return added;
92
+ };
93
+ const expandAffected = (changedProjects, projectGraph, options) => {
94
+ const affected = new Set(changedProjects);
95
+ let downstream = /* @__PURE__ */ new Set();
96
+ let upstream = /* @__PURE__ */ new Set();
97
+ if (options.downstream !== "none") {
98
+ const reverseDeps = buildReverseDependencyMap(projectGraph);
99
+ downstream = expandInDirection(affected, changedProjects, reverseDeps, options.downstream);
100
+ }
101
+ if (options.upstream !== "none") {
102
+ const forwardDeps = buildForwardDependencyMap(projectGraph);
103
+ upstream = expandInDirection(affected, changedProjects, forwardDeps, options.upstream);
104
+ }
105
+ return { affected, downstream, upstream };
69
106
  };
70
107
  const getMergeBase = (workspaceRoot, base, head) => new Promise((resolve, reject) => {
71
108
  execFile("git", ["merge-base", base, head], { cwd: workspaceRoot }, (error, stdout) => {
@@ -103,7 +140,7 @@ const getChangedFiles = async (workspaceRoot, base, head) => {
103
140
  }
104
141
  };
105
142
  const getAffectedProjects = async (options) => {
106
- const { base = "main", head = "HEAD", projectGraph, projects, workspaceRoot } = options;
143
+ const { base = "main", downstream = "deep", head = "HEAD", projectGraph, projects, upstream = "none", workspaceRoot } = options;
107
144
  const changedFiles = await getChangedFiles(workspaceRoot, base, head);
108
145
  const changedProjects = /* @__PURE__ */ new Set();
109
146
  for (const file of changedFiles) {
@@ -114,16 +151,19 @@ const getAffectedProjects = async (options) => {
114
151
  return {
115
152
  affectedProjects: Object.keys(projects),
116
153
  changedFiles,
117
- changedProjects: Object.keys(projects)
154
+ changedProjects: [...changedProjects],
155
+ downstreamProjects: [],
156
+ upstreamProjects: []
118
157
  };
119
158
  }
120
159
  }
121
- const affectedProjects = new Set(changedProjects);
122
- expandAffected(affectedProjects, projectGraph);
160
+ const result = expandAffected(changedProjects, projectGraph, { downstream, upstream });
123
161
  return {
124
- affectedProjects: [...affectedProjects],
162
+ affectedProjects: [...result.affected],
125
163
  changedFiles,
126
- changedProjects: [...changedProjects]
164
+ changedProjects: [...changedProjects],
165
+ downstreamProjects: [...result.downstream],
166
+ upstreamProjects: [...result.upstream]
127
167
  };
128
168
  };
129
169
  const filterAffectedTasks = (taskIds, affectedProjects) => taskIds.filter((taskId) => {
@@ -132,4 +172,4 @@ const filterAffectedTasks = (taskIds, affectedProjects) => taskIds.filter((taskI
132
172
  return affectedProjects.has(project);
133
173
  });
134
174
 
135
- export { filterAffectedTasks, getAffectedProjects, getChangedFiles };
175
+ export { buildForwardDependencyMap, buildReverseDependencyMap, expandAffected, filterAffectedTasks, getAffectedProjects, getChangedFiles };
@@ -27,7 +27,7 @@ import { b as hashStrings, s as sortObjectKeys, c as collectFiles, x as xxh3Hash
27
27
  import { join, resolve, relative } from '@visulima/path';
28
28
  import { getFrameworkEnvVariables } from './detectFrameworks-CeFzKE6J.js';
29
29
  import { LockfileHasher } from './extractPackageName-CbVNW-dr.js';
30
- import { loadNativeBindings } from './isNativeAvailable-BWhnZ4ES.js';
30
+ import { loadNativeBindings } from './isNativeAvailable-BpD28A6Z.js';
31
31
 
32
32
  const DEFAULT_GLOBAL_INPUTS = ["package-lock.json", "pnpm-lock.yaml", "yarn.lock", "tsconfig.base.json", "tsconfig.json", ".env"];
33
33
  const IGNORED_DIRS = /* @__PURE__ */ new Set([".git", "coverage", "dist", "node_modules"]);
@@ -104,7 +104,6 @@ class InProcessTaskHasher {
104
104
  this.#lockfileHasher = this.#smartLockfileHashing ? new LockfileHasher(options.workspaceRoot) : void 0;
105
105
  this.#frameworkInference = options.frameworkInference ?? false;
106
106
  }
107
- // eslint-disable-next-line sonarjs/cognitive-complexity
108
107
  async hashTask(task) {
109
108
  const commandHash = this.#hashCommand(task);
110
109
  const nodes = {};
@@ -0,0 +1,37 @@
1
+ const INPUT_PREFIX_REGEX = /^(\S+?):(.+)/s;
2
+ const createInputHandler = (commands, options = {}) => {
3
+ const { defaultTarget = 0, inputStream = process.stdin, pauseOnFinish = true } = options;
4
+ const byIndex = /* @__PURE__ */ new Map();
5
+ const byName = /* @__PURE__ */ new Map();
6
+ for (const cmd of commands) {
7
+ byIndex.set(cmd.index, cmd);
8
+ if (cmd.name) {
9
+ byName.set(cmd.name, cmd);
10
+ }
11
+ }
12
+ const onData = (data) => {
13
+ const input = data.toString();
14
+ const match = INPUT_PREFIX_REGEX.exec(input);
15
+ if (match) {
16
+ const [, target, rest] = match;
17
+ const targetCmd = byName.get(target) ?? byIndex.get(Number(target));
18
+ if (targetCmd) {
19
+ targetCmd.stdin.write(rest);
20
+ return;
21
+ }
22
+ }
23
+ const defaultCmd = byIndex.get(defaultTarget);
24
+ if (defaultCmd) {
25
+ defaultCmd.stdin.write(input);
26
+ }
27
+ };
28
+ inputStream.on("data", onData);
29
+ return () => {
30
+ inputStream.removeListener("data", onData);
31
+ if (pauseOnFinish && typeof inputStream.pause === "function") {
32
+ inputStream.pause();
33
+ }
34
+ };
35
+ };
36
+
37
+ export { createInputHandler };
@@ -1,10 +1,9 @@
1
- import { Cache } from './Cache-IYpTYVUC.js';
1
+ import { Cache } from './Cache-iAjRMV2d.js';
2
2
  import { inferFrameworkEnvPatterns } from './detectFrameworks-CeFzKE6J.js';
3
- import { EmptyLifeCycle } from './CompositeLifeCycle-7AtYw1dv.js';
4
- import { RemoteCache } from './RemoteCache-BDqrnDEi.js';
5
- import { InProcessTaskHasher } from './computeTaskHash-BoCnnvIJ.js';
6
- import { TaskOrchestrator } from './TaskOrchestrator-BvYs3ONw.js';
7
- import { TaskScheduler } from './TaskScheduler-CJilHDta.js';
3
+ import { RemoteCache } from './RemoteCache-BFceSe4a.js';
4
+ import { InProcessTaskHasher } from './computeTaskHash-B2SVZqgp.js';
5
+ import { TaskOrchestrator } from './TaskOrchestrator-lLn-PH1m.js';
6
+ import { TaskScheduler } from './parsePartition-C4-P5RjK.js';
8
7
 
9
8
  const resolveParallel = (parallel) => {
10
9
  if (typeof parallel === "number") {
@@ -16,7 +15,7 @@ const resolveParallel = (parallel) => {
16
15
  return 3;
17
16
  };
18
17
  const defaultTaskRunner = async (_tasks, options, context) => {
19
- const { lifeCycle = new EmptyLifeCycle(), projectGraph, taskExecutor, taskGraph, workspaceRoot } = context;
18
+ const { lifeCycle, projectGraph, taskExecutor, taskGraph, workspaceRoot } = context;
20
19
  const cache = new Cache({
21
20
  cacheDirectory: options.cacheDirectory,
22
21
  maxCacheAge: options.maxCacheAge,
@@ -0,0 +1,53 @@
1
+ import { createRequire as __cjs_createRequire } from "node:module";
2
+
3
+ const __cjs_require = __cjs_createRequire(import.meta.url);
4
+
5
+ const __cjs_getProcess = typeof globalThis !== "undefined" && typeof globalThis.process !== "undefined" ? globalThis.process : process;
6
+
7
+ const __cjs_getBuiltinModule = (module) => {
8
+ // Check if we're in Node.js and version supports getBuiltinModule
9
+ if (typeof __cjs_getProcess !== "undefined" && __cjs_getProcess.versions && __cjs_getProcess.versions.node) {
10
+ const [major, minor] = __cjs_getProcess.versions.node.split(".").map(Number);
11
+ // Node.js 20.16.0+ and 22.3.0+
12
+ if (major > 22 || (major === 22 && minor >= 3) || (major === 20 && minor >= 16)) {
13
+ return __cjs_getProcess.getBuiltinModule(module);
14
+ }
15
+ }
16
+ // Fallback to createRequire
17
+ return __cjs_require(module);
18
+ };
19
+
20
+ const {
21
+ execFileSync
22
+ } = __cjs_getBuiltinModule("node:child_process");
23
+
24
+ let cachedShellPath;
25
+ const detectScriptShell = () => {
26
+ if (cachedShellPath !== void 0) {
27
+ return cachedShellPath ?? void 0;
28
+ }
29
+ const envShell = process.env["npm_config_script_shell"];
30
+ if (envShell) {
31
+ cachedShellPath = envShell;
32
+ return envShell;
33
+ }
34
+ try {
35
+ const result = execFileSync("npm", ["config", "get", "script-shell"], {
36
+ encoding: "utf8",
37
+ stdio: ["ignore", "pipe", "ignore"],
38
+ timeout: 5e3
39
+ }).trim();
40
+ if (result && result !== "undefined" && result !== "") {
41
+ cachedShellPath = result;
42
+ return result;
43
+ }
44
+ } catch {
45
+ }
46
+ cachedShellPath = null;
47
+ return void 0;
48
+ };
49
+ const resetShellCache = () => {
50
+ cachedShellPath = void 0;
51
+ };
52
+
53
+ export { detectScriptShell, resetShellCache };
@@ -0,0 +1,111 @@
1
+ const LAYER_ORDER = ["configuration", "library", "scaffolding", "tool", "automation", "application"];
2
+ const layerIndex = (layer) => {
3
+ if (!layer) {
4
+ return void 0;
5
+ }
6
+ const idx = LAYER_ORDER.indexOf(layer);
7
+ return idx === -1 ? void 0 : idx;
8
+ };
9
+ const enforceProjectConstraints = (projectGraph, constraints) => {
10
+ const violations = [];
11
+ const { dependencyKindRules, enforceLayerRelationships, tagRelationships, typeBoundaries } = constraints;
12
+ const hasTagRules = tagRelationships && Object.keys(tagRelationships).length > 0;
13
+ const hasTypeBoundaries = typeBoundaries !== void 0;
14
+ const hasKindRules = dependencyKindRules !== void 0;
15
+ const hasLayerRules = enforceLayerRelationships === true;
16
+ if (!hasTagRules && !hasTypeBoundaries && !hasKindRules && !hasLayerRules) {
17
+ return violations;
18
+ }
19
+ const enforceAppBoundary = typeBoundaries?.enforceApplicationBoundary !== false;
20
+ const allowedDepTypes = typeBoundaries?.allowedDependencyTypes;
21
+ for (const [projectName, dependencies] of Object.entries(projectGraph.dependencies)) {
22
+ const sourceNode = projectGraph.nodes[projectName];
23
+ if (!sourceNode) {
24
+ continue;
25
+ }
26
+ const sourceTags = sourceNode.data.tags ?? [];
27
+ const sourceType = sourceNode.type;
28
+ for (const dep of dependencies) {
29
+ const depNode = projectGraph.nodes[dep.target];
30
+ if (!depNode) {
31
+ continue;
32
+ }
33
+ const depTags = depNode.data.tags ?? [];
34
+ const depType = depNode.type;
35
+ let appBoundaryViolated = false;
36
+ if (hasTypeBoundaries && enforceAppBoundary && depType === "application") {
37
+ appBoundaryViolated = true;
38
+ violations.push({
39
+ dependencyProject: dep.target,
40
+ message: `Project "${projectName}" depends on "${dep.target}", which is an application. Applications should not be depended upon by other projects.`,
41
+ rule: "type-boundary",
42
+ sourceProject: projectName
43
+ });
44
+ }
45
+ if (allowedDepTypes && !appBoundaryViolated) {
46
+ const allowed = allowedDepTypes[sourceType];
47
+ if (allowed && !allowed.includes(depType)) {
48
+ violations.push({
49
+ dependencyProject: dep.target,
50
+ message: `Project "${projectName}" (type: ${sourceType}) depends on "${dep.target}" (type: ${depType}). Allowed dependency types for "${sourceType}" are: ${allowed.join(", ")}.`,
51
+ rule: "type-boundary",
52
+ sourceProject: projectName
53
+ });
54
+ }
55
+ }
56
+ if (hasTagRules && tagRelationships) {
57
+ for (const sourceTag of sourceTags) {
58
+ const requiredTags = tagRelationships[sourceTag];
59
+ if (!requiredTags || requiredTags.length === 0) {
60
+ continue;
61
+ }
62
+ const hasRequiredTag = depTags.some((tag) => requiredTags.includes(tag));
63
+ if (!hasRequiredTag) {
64
+ violations.push({
65
+ dependencyProject: dep.target,
66
+ message: `Project "${projectName}" (tag: ${sourceTag}) depends on "${dep.target}", which doesn't have any of the required tags: ${requiredTags.join(", ")}. ${depTags.length > 0 ? `"${dep.target}" has tags: ${depTags.join(", ")}.` : `"${dep.target}" has no tags.`}`,
67
+ rule: "tag-relationship",
68
+ sourceProject: projectName
69
+ });
70
+ }
71
+ }
72
+ }
73
+ if (hasKindRules && dependencyKindRules) {
74
+ if (dependencyKindRules.noProductionDependencyOnApplication && dep.type === "static" && depType === "application") {
75
+ violations.push({
76
+ dependencyProject: dep.target,
77
+ message: `Project "${projectName}" has a production dependency on "${dep.target}", which is an application. Production dependencies on applications are not allowed. Use devDependencies instead if needed for testing.`,
78
+ rule: "dependency-kind",
79
+ sourceProject: projectName
80
+ });
81
+ }
82
+ if (dependencyKindRules.noDevDependencyOnProductionDep && dep.type === "devDependency" && sourceType === "library") {
83
+ const hasProductionDep = dependencies.some((other) => other.target === dep.target && other.type === "static");
84
+ if (hasProductionDep) {
85
+ violations.push({
86
+ dependencyProject: dep.target,
87
+ message: `Project "${projectName}" has "${dep.target}" in both dependencies and devDependencies. This is redundant — remove it from devDependencies.`,
88
+ rule: "dependency-kind",
89
+ sourceProject: projectName
90
+ });
91
+ }
92
+ }
93
+ }
94
+ if (hasLayerRules) {
95
+ const sourceLayer = layerIndex(sourceNode.data.layer);
96
+ const depLayer = layerIndex(depNode.data.layer);
97
+ if (sourceLayer !== void 0 && depLayer !== void 0 && depLayer > sourceLayer) {
98
+ violations.push({
99
+ dependencyProject: dep.target,
100
+ message: `Project "${projectName}" (layer: ${sourceNode.data.layer}) depends on "${dep.target}" (layer: ${depNode.data.layer}). A "${sourceNode.data.layer}" project may only depend on projects at the same or lower layer. Hierarchy: ${LAYER_ORDER.join(" < ")}.`,
101
+ rule: "layer-relationship",
102
+ sourceProject: projectName
103
+ });
104
+ }
105
+ }
106
+ }
107
+ }
108
+ return violations;
109
+ };
110
+
111
+ export { enforceProjectConstraints };
@@ -0,0 +1,26 @@
1
+ const PLACEHOLDER_REGEX = /\\?\{([@*]|[1-9]\d*)\}/g;
2
+ const shellQuote = (s) => `'${s.replaceAll("'", String.raw`'\''`)}'`;
3
+ const expandArguments = (config, additionalArguments) => {
4
+ if (additionalArguments.length === 0) {
5
+ return config;
6
+ }
7
+ const command = config.command.replaceAll(PLACEHOLDER_REGEX, (match, target) => {
8
+ if (match.startsWith("\\")) {
9
+ return match.slice(1);
10
+ }
11
+ const index = Number(target);
12
+ if (!Number.isNaN(index) && index <= additionalArguments.length) {
13
+ return shellQuote(additionalArguments[index - 1]);
14
+ }
15
+ if (target === "@") {
16
+ return additionalArguments.map(shellQuote).join(" ");
17
+ }
18
+ if (target === "*") {
19
+ return shellQuote(additionalArguments.join(" "));
20
+ }
21
+ return "";
22
+ });
23
+ return { ...config, command };
24
+ };
25
+
26
+ export { expandArguments };
@@ -0,0 +1,23 @@
1
+ const SHORTCUT_REGEX = /^(npm|yarn|pnpm|bun|node|deno):(\S+)(.*)/;
2
+ const expandShortcut = (config) => {
3
+ const match = SHORTCUT_REGEX.exec(config.command);
4
+ if (!match) {
5
+ return config;
6
+ }
7
+ const [, prefix, script, args] = match;
8
+ let runPrefix;
9
+ if (prefix === "node") {
10
+ runPrefix = "node --run";
11
+ } else if (prefix === "deno") {
12
+ runPrefix = "deno task";
13
+ } else {
14
+ runPrefix = `${prefix} run`;
15
+ }
16
+ return {
17
+ ...config,
18
+ command: `${runPrefix} ${script}${args}`,
19
+ name: config.name ?? script
20
+ };
21
+ };
22
+
23
+ export { expandShortcut };
@@ -0,0 +1,107 @@
1
+ import { createRequire as __cjs_createRequire } from "node:module";
2
+
3
+ const __cjs_require = __cjs_createRequire(import.meta.url);
4
+
5
+ const __cjs_getProcess = typeof globalThis !== "undefined" && typeof globalThis.process !== "undefined" ? globalThis.process : process;
6
+
7
+ const __cjs_getBuiltinModule = (module) => {
8
+ // Check if we're in Node.js and version supports getBuiltinModule
9
+ if (typeof __cjs_getProcess !== "undefined" && __cjs_getProcess.versions && __cjs_getProcess.versions.node) {
10
+ const [major, minor] = __cjs_getProcess.versions.node.split(".").map(Number);
11
+ // Node.js 20.16.0+ and 22.3.0+
12
+ if (major > 22 || (major === 22 && minor >= 3) || (major === 20 && minor >= 16)) {
13
+ return __cjs_getProcess.getBuiltinModule(module);
14
+ }
15
+ }
16
+ // Fallback to createRequire
17
+ return __cjs_require(module);
18
+ };
19
+
20
+ const {
21
+ existsSync,
22
+ readFileSync
23
+ } = __cjs_getBuiltinModule("node:fs");
24
+ const {
25
+ join
26
+ } = __cjs_getBuiltinModule("node:path");
27
+
28
+ const RUN_COMMAND_REGEX = /(?:npm|yarn|pnpm|bun) run|node --run|deno task/;
29
+ const readPackageScripts = (cwd) => {
30
+ try {
31
+ const raw = readFileSync(join(cwd, "package.json"), "utf8");
32
+ const pkg = JSON.parse(raw);
33
+ return pkg.scripts ?? {};
34
+ } catch {
35
+ return {};
36
+ }
37
+ };
38
+ const readDenoTasks = (cwd) => {
39
+ const tasks = {};
40
+ for (const filename of ["deno.json", "deno.jsonc"]) {
41
+ const filepath = join(cwd, filename);
42
+ if (existsSync(filepath)) {
43
+ try {
44
+ let raw = readFileSync(filepath, "utf8");
45
+ if (filename.endsWith("c")) {
46
+ raw = raw.replaceAll(/"(?:[^"\\]|\\.)*"|\/\/[^\n]*/g, (match) => match.startsWith('"') ? match : "");
47
+ }
48
+ const config = JSON.parse(raw);
49
+ if (config.tasks) {
50
+ Object.assign(tasks, config.tasks);
51
+ }
52
+ } catch {
53
+ }
54
+ break;
55
+ }
56
+ }
57
+ const pkgScripts = readPackageScripts(cwd);
58
+ return { ...pkgScripts, ...tasks };
59
+ };
60
+ const escapeRegExp = (s) => s.replaceAll(/[$()*+.?[\\\]^{|}]/g, String.raw`\$&`);
61
+ const expandWildcard = (config) => {
62
+ const { command } = config;
63
+ const runMatch = RUN_COMMAND_REGEX.exec(command);
64
+ if (!runMatch) {
65
+ return config;
66
+ }
67
+ const afterRun = command.slice(runMatch.index + runMatch[0].length).trim();
68
+ const scriptPattern = afterRun.split(/\s/)[0] ?? "";
69
+ if (!scriptPattern.includes("*")) {
70
+ return config;
71
+ }
72
+ const cwd = config.cwd ?? process.cwd();
73
+ const isDeno = runMatch[0] === "deno task";
74
+ const scripts = isDeno ? readDenoTasks(cwd) : readPackageScripts(cwd);
75
+ const scriptNames = Object.keys(scripts);
76
+ const parts = scriptPattern.split("*");
77
+ const regexString = parts.map(escapeRegExp).join("(.+)");
78
+ const wildcardRegex = new RegExp(`^${regexString}$`);
79
+ const omitMatch = /!\(([^)]+)\)/.exec(scriptPattern);
80
+ let omitRegex;
81
+ if (omitMatch) {
82
+ omitRegex = new RegExp(omitMatch[1]);
83
+ }
84
+ const matching = scriptNames.filter((name) => {
85
+ if (!wildcardRegex.test(name)) {
86
+ return false;
87
+ }
88
+ if (omitRegex?.test(name)) {
89
+ return false;
90
+ }
91
+ return true;
92
+ });
93
+ if (matching.length === 0) {
94
+ return config;
95
+ }
96
+ const remainingArgs = afterRun.slice(scriptPattern.length);
97
+ const runPrefix = command.slice(0, runMatch.index + runMatch[0].length);
98
+ return matching.map((scriptName) => {
99
+ return {
100
+ ...config,
101
+ command: `${runPrefix} ${scriptName}${remainingArgs}`,
102
+ name: config.name ?? scriptName
103
+ };
104
+ });
105
+ };
106
+
107
+ export { expandWildcard };
@@ -8,7 +8,7 @@ const findCycle = (taskGraph) => {
8
8
  }
9
9
  const stack = [taskId];
10
10
  while (stack.length > 0) {
11
- const current = stack[stack.length - 1];
11
+ const current = stack.at(-1);
12
12
  if (!visited.has(current)) {
13
13
  visited.add(current);
14
14
  inStack.add(current);
@@ -0,0 +1,46 @@
1
+ const formatDuration = (ms) => {
2
+ if (ms < 1e3) {
3
+ return `${Math.round(ms)}ms`;
4
+ }
5
+ if (ms < 6e4) {
6
+ return `${(ms / 1e3).toFixed(1)}s`;
7
+ }
8
+ const minutes = Math.floor(ms / 6e4);
9
+ const seconds = (ms % 6e4 / 1e3).toFixed(1);
10
+ return `${minutes}m ${seconds}s`;
11
+ };
12
+ const formatTimingTable = (closeEvents) => {
13
+ if (closeEvents.length === 0) {
14
+ return "";
15
+ }
16
+ const sorted = closeEvents.toSorted((a, b) => b.durationMs - a.durationMs);
17
+ const nameWidth = Math.max(4, ...sorted.map((e) => (e.name ?? String(e.index)).length));
18
+ const durationWidth = Math.max(8, ...sorted.map((e) => formatDuration(e.durationMs).length));
19
+ const codeWidth = Math.max(4, ...sorted.map((e) => String(e.exitCode).length));
20
+ const header = ["name".padEnd(nameWidth), "duration".padEnd(durationWidth), "code".padEnd(codeWidth), "killed", "command"].join(" │ ");
21
+ const separator = ["─".repeat(nameWidth), "─".repeat(durationWidth), "─".repeat(codeWidth), "─".repeat(6), "─".repeat(20)].join(
22
+ "─┼─"
23
+ );
24
+ const rows = sorted.map((event) => {
25
+ const name = (event.name ?? String(event.index)).padEnd(nameWidth);
26
+ const duration = formatDuration(event.durationMs).padEnd(durationWidth);
27
+ const code = String(event.exitCode).padEnd(codeWidth);
28
+ const killed = (event.killed ? "yes" : "no").padEnd(6);
29
+ const command = event.command.length > 40 ? `${event.command.slice(0, 39)}…` : event.command;
30
+ return [name, duration, code, killed, command].join(" │ ");
31
+ });
32
+ return [header, separator, ...rows].join("\n");
33
+ };
34
+ const logTimings = (closeEvents, output = process.stdout) => {
35
+ if (closeEvents.length === 0) {
36
+ return;
37
+ }
38
+ const table = formatTimingTable(closeEvents);
39
+ output.write(
40
+ "\n── Timing Summary ───────────────────────────────────\n\n"
41
+ );
42
+ output.write(table);
43
+ output.write("\n\n");
44
+ };
45
+
46
+ export { formatTimingTable, logTimings };
@@ -0,0 +1,44 @@
1
+ import { createRequire as __cjs_createRequire } from "node:module";
2
+
3
+ const __cjs_require = __cjs_createRequire(import.meta.url);
4
+
5
+ const __cjs_getProcess = typeof globalThis !== "undefined" && typeof globalThis.process !== "undefined" ? globalThis.process : process;
6
+
7
+ const __cjs_getBuiltinModule = (module) => {
8
+ // Check if we're in Node.js and version supports getBuiltinModule
9
+ if (typeof __cjs_getProcess !== "undefined" && __cjs_getProcess.versions && __cjs_getProcess.versions.node) {
10
+ const [major, minor] = __cjs_getProcess.versions.node.split(".").map(Number);
11
+ // Node.js 20.16.0+ and 22.3.0+
12
+ if (major > 22 || (major === 22 && minor >= 3) || (major === 20 && minor >= 16)) {
13
+ return __cjs_getProcess.getBuiltinModule(module);
14
+ }
15
+ }
16
+ // Fallback to createRequire
17
+ return __cjs_require(module);
18
+ };
19
+
20
+ const {
21
+ createRequire
22
+ } = __cjs_getBuiltinModule("node:module");
23
+
24
+ let nativeBindings;
25
+ let loadAttempted = false;
26
+ const esmRequire = createRequire(import.meta.url);
27
+ const loadNativeBindings = () => {
28
+ if (loadAttempted) {
29
+ return nativeBindings;
30
+ }
31
+ loadAttempted = true;
32
+ try {
33
+ const loaded = esmRequire("../index.js");
34
+ if (typeof loaded.hashCommand === "function" && typeof loaded.hashFile === "function" && typeof loaded.runConcurrent === "function") {
35
+ nativeBindings = loaded;
36
+ }
37
+ } catch {
38
+ nativeBindings = void 0;
39
+ }
40
+ return nativeBindings;
41
+ };
42
+ const isNativeAvailable = () => loadNativeBindings() !== void 0;
43
+
44
+ export { isNativeAvailable, loadNativeBindings };
@@ -0,0 +1,26 @@
1
+ import { expandArguments } from './expandArguments-0AwD2BIA.js';
2
+ import { expandShortcut } from './expandShortcut-BVG05ee4.js';
3
+ import { expandWildcard } from './expandWildcard-B0xN_knq.js';
4
+ import { stripQuotes } from './stripQuotes-Cey-zwFf.js';
5
+
6
+ const parseCommands = (inputs, options = {}) => {
7
+ const { additionalArguments = [] } = options;
8
+ let configs = inputs.map((input) => {
9
+ if (typeof input === "string") {
10
+ return { command: input };
11
+ }
12
+ return { ...input };
13
+ });
14
+ configs = configs.map(stripQuotes);
15
+ configs = configs.map(expandShortcut);
16
+ configs = configs.flatMap((config) => {
17
+ const result = expandWildcard(config);
18
+ return Array.isArray(result) ? result : [result];
19
+ });
20
+ if (additionalArguments.length > 0) {
21
+ configs = configs.map((config) => expandArguments(config, additionalArguments));
22
+ }
23
+ return configs;
24
+ };
25
+
26
+ export { expandArguments, expandShortcut, expandWildcard, parseCommands, stripQuotes };