@visulima/task-runner 1.0.0-alpha.6 → 1.0.0-alpha.8

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 +34 -0
  2. package/dist/index.d.ts +2507 -49
  3. package/dist/index.js +9 -7
  4. package/dist/packem_shared/{TaskOrchestrator-rf45vW5c.js → TaskOrchestrator-UCMHCx8c.js} +68 -2
  5. package/dist/packem_shared/{buildForwardDependencyMap-DLPgKEto.js → buildForwardDependencyMap-0BJFMMPv.js} +1 -2
  6. package/dist/packem_shared/{computeTaskHash-DYqfrDGq.js → computeTaskHash-B5APHW7e.js} +1 -1
  7. package/dist/packem_shared/{createTaskGraph-B7nH0kY_.js → createTaskGraph-B5YrfAMx.js} +6 -2
  8. package/dist/packem_shared/{defaultTaskRunner-Cp7jCmIl.js → defaultTaskRunner-DzR0ld8F.js} +36 -4
  9. package/dist/packem_shared/expandTokensInString-C03AGAjh.js +46 -0
  10. package/dist/packem_shared/{extractPackageName-BllKetnz.js → extractPackageName-CbVNW-dr.js} +1 -2
  11. package/dist/packem_shared/getCurrentBranch-DsKPDoVj.js +153 -0
  12. package/dist/packem_shared/{parseCommands-D-IgF8Zh.js → parseCommands-CJ16ohOB.js} +7 -2
  13. package/index.js +556 -723
  14. package/package.json +14 -14
  15. package/dist/affected.d.ts +0 -82
  16. package/dist/archive.d.ts +0 -38
  17. package/dist/cache.d.ts +0 -138
  18. package/dist/chrome-trace.d.ts +0 -53
  19. package/dist/command-parser/expand-arguments.d.ts +0 -11
  20. package/dist/command-parser/expand-shortcut.d.ts +0 -15
  21. package/dist/command-parser/expand-wildcard.d.ts +0 -13
  22. package/dist/command-parser/index.d.ts +0 -18
  23. package/dist/command-parser/strip-quotes.d.ts +0 -6
  24. package/dist/concurrent-fallback.d.ts +0 -16
  25. package/dist/concurrent.d.ts +0 -23
  26. package/dist/default-task-runner.d.ts +0 -44
  27. package/dist/detect-shell.d.ts +0 -19
  28. package/dist/file-access-tracker.d.ts +0 -59
  29. package/dist/fingerprint.d.ts +0 -54
  30. package/dist/flow-controllers/index.d.ts +0 -7
  31. package/dist/flow-controllers/input-handler.d.ts +0 -44
  32. package/dist/flow-controllers/log-timings.d.ts +0 -18
  33. package/dist/flow-controllers/restart-process.d.ts +0 -21
  34. package/dist/flow-controllers/teardown.d.ts +0 -22
  35. package/dist/framework-inference.d.ts +0 -35
  36. package/dist/graph-visualizer.d.ts +0 -74
  37. package/dist/incremental-hasher.d.ts +0 -76
  38. package/dist/life-cycle.d.ts +0 -38
  39. package/dist/lockfile-hasher.d.ts +0 -73
  40. package/dist/log-reporter.d.ts +0 -34
  41. package/dist/native-binding.d.ts +0 -106
  42. package/dist/output-resolver.d.ts +0 -20
  43. package/dist/project-constraints.d.ts +0 -9
  44. package/dist/remote-cache.d.ts +0 -100
  45. package/dist/run-summary.d.ts +0 -111
  46. package/dist/task-graph-utils.d.ts +0 -39
  47. package/dist/task-graph.d.ts +0 -22
  48. package/dist/task-hasher.d.ts +0 -104
  49. package/dist/task-orchestrator.d.ts +0 -38
  50. package/dist/task-scheduler.d.ts +0 -41
  51. package/dist/terminal-buffer.d.ts +0 -29
  52. package/dist/tracked-executor.d.ts +0 -46
  53. package/dist/types.d.ts +0 -757
  54. package/dist/utils.d.ts +0 -39
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
- export { buildForwardDependencyMap, buildReverseDependencyMap, expandAffected, filterAffectedTasks, getAffectedProjects, getChangedFiles } from './packem_shared/buildForwardDependencyMap-DLPgKEto.js';
1
+ export { buildForwardDependencyMap, buildReverseDependencyMap, expandAffected, filterAffectedTasks, getAffectedProjects, getChangedFiles } from './packem_shared/buildForwardDependencyMap-0BJFMMPv.js';
2
2
  export { Cache, DEFAULT_CACHE_DIRECTORY_NAME, formatCacheSize, parseCacheSize } from './packem_shared/Cache-CWaX_c8U.js';
3
3
  export { toChromeTrace, writeChromeTrace } from './packem_shared/toChromeTrace-B2tZoJ-7.js';
4
- export { parseCommands } from './packem_shared/parseCommands-D-IgF8Zh.js';
4
+ export { parseCommands } from './packem_shared/parseCommands-CJ16ohOB.js';
5
5
  export { runConcurrently } from './packem_shared/runConcurrently-CmfC4r-f.js';
6
6
  export { runConcurrentFallback } from './packem_shared/runConcurrentFallback-BTmgGV1H.js';
7
- export { defaultTaskRunner } from './packem_shared/defaultTaskRunner-Cp7jCmIl.js';
7
+ export { defaultTaskRunner } from './packem_shared/defaultTaskRunner-DzR0ld8F.js';
8
8
  export { detectScriptShell } from './packem_shared/detectScriptShell-CR-xXKA4.js';
9
9
  export { FileAccessTracker, generatePreloadScript } from './packem_shared/FileAccessTracker-CQ5Ot7Hd.js';
10
10
  export { FingerprintManager } from './packem_shared/FingerprintManager-CV7U4f4f.js';
@@ -12,24 +12,26 @@ export { detectFrameworks, getFrameworkEnvVariables, inferFrameworkEnvPatterns }
12
12
  export { projectGraphToDot, toGraphAscii, toGraphHtml, toGraphJson, toGraphvizDot } from './packem_shared/projectGraphToDot-DU1lSe-c.js';
13
13
  export { IncrementalFileHasher } from './packem_shared/IncrementalFileHasher-BRS76-mb.js';
14
14
  export { CompositeLifeCycle, ConsoleLifeCycle, EmptyLifeCycle } from './packem_shared/CompositeLifeCycle-CSVbRC_5.js';
15
- export { LockfileHasher, extractPackageName, parseNpmLockfile, parsePnpmLockfile, parseYarnLockfile } from './packem_shared/extractPackageName-BllKetnz.js';
15
+ export { LockfileHasher, extractPackageName, parseNpmLockfile, parsePnpmLockfile, parseYarnLockfile } from './packem_shared/extractPackageName-CbVNW-dr.js';
16
16
  export { LogReporter, createLogReporter } from './packem_shared/LogReporter-BDt52HLu.js';
17
17
  export { isNativeAvailable, loadNativeBindings } from './packem_shared/isNativeAvailable-BpD28A6Z.js';
18
18
  export { resolveOutputs } from './packem_shared/resolveOutputs-n6MCKoTe.js';
19
19
  export { enforceProjectConstraints } from './packem_shared/enforceProjectConstraints-C5Jp_C3u.js';
20
20
  export { RemoteCache } from './packem_shared/RemoteCache-DSU3lc87.js';
21
21
  export { generateRunSummary, getLastRunSummaryPath, readLastRunSummary, writeLastRunSummary, writeRunSummary } from './packem_shared/generateRunSummary-BE1jnQ3H.js';
22
- export { createTaskGraph, getTaskId, parseTaskId } from './packem_shared/createTaskGraph-B7nH0kY_.js';
22
+ export { createTaskGraph, getTaskId, parseTaskId } from './packem_shared/createTaskGraph-B5YrfAMx.js';
23
23
  export { findCycle, findCycles, getDependentTasks, getLeafTasks, getTransitiveDependencies, makeAcyclic, reverseTaskGraph, walkTaskGraph } from './packem_shared/findCycle-DefgNYhg.js';
24
- export { InProcessTaskHasher, computeTaskHash } from './packem_shared/computeTaskHash-DYqfrDGq.js';
25
- export { TaskOrchestrator } from './packem_shared/TaskOrchestrator-rf45vW5c.js';
24
+ export { InProcessTaskHasher, computeTaskHash } from './packem_shared/computeTaskHash-B5APHW7e.js';
25
+ export { TaskOrchestrator } from './packem_shared/TaskOrchestrator-UCMHCx8c.js';
26
26
  export { TaskScheduler, parsePartition } from './packem_shared/parsePartition-BfLbHGAx.js';
27
27
  export { TerminalBuffer } from './packem_shared/TerminalBuffer-qVJvbRQZ.js';
28
28
  export { TrackedTaskExecutor } from './packem_shared/TrackedTaskExecutor-CFPpQfXF.js';
29
29
  export { c as collectFiles, a as createFailureResult, h as hashFile, b as hashStrings, r as readPackageDeps, d as resolveTaskCwd, s as sortObjectKeys, u as uniqueId } from './packem_shared/utils-zO0ZRgtf.js';
30
+ export { evaluateWhen, explainWhen, getCurrentBranch, resetBranchCache } from './packem_shared/getCurrentBranch-DsKPDoVj.js';
30
31
  export { createInputHandler } from './packem_shared/createInputHandler-DTfePcTG.js';
31
32
  export { expandArguments } from './packem_shared/expandArguments-0AwD2BIA.js';
32
33
  export { expandShortcut } from './packem_shared/expandShortcut-BVG05ee4.js';
34
+ export { expandTokens, expandTokensInString } from './packem_shared/expandTokensInString-C03AGAjh.js';
33
35
  export { expandWildcard } from './packem_shared/expandWildcard-B0xN_knq.js';
34
36
  export { formatTimingTable, logTimings } from './packem_shared/formatTimingTable-3qtCM552.js';
35
37
  export { runTeardown } from './packem_shared/runTeardown-BAezH79J.js';
@@ -2,8 +2,9 @@ import { d as resolveTaskCwd, a as createFailureResult, e as createXxh3Hasher }
2
2
  import { resolve, join } from '@visulima/path';
3
3
  import { FingerprintManager } from './FingerprintManager-CV7U4f4f.js';
4
4
  import { generateRunSummary, writeLastRunSummary, writeRunSummary } from './generateRunSummary-BE1jnQ3H.js';
5
- import { computeTaskHash } from './computeTaskHash-DYqfrDGq.js';
5
+ import { computeTaskHash } from './computeTaskHash-B5APHW7e.js';
6
6
  import { TrackedTaskExecutor } from './TrackedTaskExecutor-CFPpQfXF.js';
7
+ import { getCurrentBranch, evaluateWhen, explainWhen } from './getCurrentBranch-DsKPDoVj.js';
7
8
 
8
9
  const hashFingerprint = (fingerprint) => {
9
10
  const hash = createXxh3Hasher();
@@ -54,6 +55,8 @@ class TaskOrchestrator {
54
55
  #taskGraph;
55
56
  #results = /* @__PURE__ */ new Map();
56
57
  #startTime;
58
+ #alwaysTasks;
59
+ #whenContext;
57
60
  /** Tracks in-flight task promises so the execution loop can await them */
58
61
  #inFlightTasks = /* @__PURE__ */ new Map();
59
62
  /** Deferred that gets resolved whenever a task completes, waking the loop */
@@ -78,6 +81,10 @@ class TaskOrchestrator {
78
81
  this.#summarize = options.summarize ?? false;
79
82
  this.#taskGraph = options.taskGraph ?? void 0;
80
83
  this.#startTime = Date.now();
84
+ this.#alwaysTasks = options.alwaysTasks ?? [];
85
+ this.#whenContext = options.whenContext ?? {
86
+ branch: getCurrentBranch(options.workspaceRoot)
87
+ };
81
88
  if (this.#autoFingerprint) {
82
89
  this.#fingerprintManager = new FingerprintManager(options.workspaceRoot);
83
90
  this.#trackedExecutor = new TrackedTaskExecutor(options.workspaceRoot);
@@ -96,6 +103,9 @@ class TaskOrchestrator {
96
103
  process.on("SIGTERM", signalHandler);
97
104
  try {
98
105
  await this.#executionLoop();
106
+ if (this.#alwaysTasks.length > 0 && !this.#aborted) {
107
+ await this.#runAlwaysTasks();
108
+ }
99
109
  } finally {
100
110
  process.removeListener("SIGINT", signalHandler);
101
111
  process.removeListener("SIGTERM", signalHandler);
@@ -110,6 +120,40 @@ class TaskOrchestrator {
110
120
  }
111
121
  return this.#results;
112
122
  }
123
+ /**
124
+ * Runs the configured `always: true` tasks sequentially after the
125
+ * main task graph completes. Each task's `when` clause is still
126
+ * honoured. Failures are recorded but never propagate — the whole
127
+ * point of an always-task is to fire regardless of upstream state.
128
+ *
129
+ * Always-tasks deliberately bypass `#processTask` /
130
+ * `#processTaskWithFingerprint`, which means **no cache lookup
131
+ * and no fingerprint check**. Cleanup / teardown / notification
132
+ * tasks are expected to run every invocation; serving them from
133
+ * cache would defeat the purpose.
134
+ */
135
+ async #runAlwaysTasks() {
136
+ for (const task of this.#alwaysTasks) {
137
+ this.#lifeCycle.scheduleTask?.(task);
138
+ this.#lifeCycle.startTasks?.([task]);
139
+ let result;
140
+ if (this.#shouldSkipForWhen(task)) {
141
+ result = this.#whenSkipResult(task);
142
+ } else {
143
+ const startTime = Date.now();
144
+ try {
145
+ result = await this.#executeTask(task, startTime);
146
+ } catch (error) {
147
+ result = createFailureResult(task, error, startTime);
148
+ this.#results.set(task.id, result);
149
+ }
150
+ }
151
+ this.#lifeCycle.endTasks?.([result]);
152
+ if (result.terminalOutput) {
153
+ this.#lifeCycle.printTaskTerminalOutput?.(result.task, result.status, result.terminalOutput);
154
+ }
155
+ }
156
+ }
113
157
  async #executionLoop() {
114
158
  while (!this.#scheduler.isComplete() && !this.#aborted) {
115
159
  const batch = this.#scheduler.getNextBatch();
@@ -130,7 +174,8 @@ class TaskOrchestrator {
130
174
  }
131
175
  this.#lifeCycle.startTasks?.(batch);
132
176
  for (const task of batch) {
133
- const taskPromise = (this.#autoFingerprint ? this.#processTaskWithFingerprint(task) : this.#processTask(task)).catch((error) => {
177
+ const startPromise = this.#shouldSkipForWhen(task) ? Promise.resolve(this.#whenSkipResult(task)) : this.#autoFingerprint ? this.#processTaskWithFingerprint(task) : this.#processTask(task);
178
+ const taskPromise = startPromise.catch((error) => {
134
179
  const errorResult = createFailureResult(task, error, Date.now());
135
180
  this.#results.set(task.id, errorResult);
136
181
  return errorResult;
@@ -413,6 +458,27 @@ class TaskOrchestrator {
413
458
  this.#results.set(task.id, result);
414
459
  return result;
415
460
  }
461
+ #shouldSkipForWhen(task) {
462
+ if (!task.when) {
463
+ return false;
464
+ }
465
+ return !evaluateWhen(task.when, this.#whenContext);
466
+ }
467
+ #whenSkipResult(task) {
468
+ const reason = explainWhen(task.when, this.#whenContext);
469
+ const startTime = Date.now();
470
+ this.#lifeCycle.printWhenSkip?.(task, reason);
471
+ const result = {
472
+ code: 0,
473
+ endTime: startTime,
474
+ startTime,
475
+ status: "skipped",
476
+ task,
477
+ terminalOutput: reason ? `Skipped: ${reason}` : "Skipped by when clause"
478
+ };
479
+ this.#results.set(task.id, result);
480
+ return result;
481
+ }
416
482
  }
417
483
 
418
484
  export { TaskOrchestrator };
@@ -33,8 +33,7 @@ const findProjectForFile = (filePath, projects) => {
33
33
  let bestLength = 0;
34
34
  for (const [name, config] of Object.entries(projects)) {
35
35
  const { root } = config;
36
- if ((filePath.startsWith(`${root}/`) || filePath === root) && // Prefer the most specific (longest) match
37
- root.length > bestLength) {
36
+ if ((filePath.startsWith(`${root}/`) || filePath === root) && root.length > bestLength) {
38
37
  bestMatch = name;
39
38
  bestLength = root.length;
40
39
  }
@@ -27,7 +27,7 @@ const {
27
27
  import { b as hashStrings, s as sortObjectKeys, c as collectFiles, x as xxh3Hash, e as createXxh3Hasher } from './utils-zO0ZRgtf.js';
28
28
  import { join, resolve, relative } from '@visulima/path';
29
29
  import { getFrameworkEnvVariables } from './detectFrameworks-CeFzKE6J.js';
30
- import { LockfileHasher } from './extractPackageName-BllKetnz.js';
30
+ import { LockfileHasher } from './extractPackageName-CbVNW-dr.js';
31
31
  import { loadNativeBindings } from './isNativeAvailable-BpD28A6Z.js';
32
32
 
33
33
  const DEFAULT_GLOBAL_INPUTS = ["package-lock.json", "pnpm-lock.yaml", "yarn.lock", "tsconfig.base.json", "tsconfig.json", ".env"];
@@ -38,13 +38,15 @@ const getSameProjectTask = (projectName, targetName, overrides, workspace, targe
38
38
  };
39
39
  return [
40
40
  {
41
+ always: project.targets?.[targetName]?.always ?? targetDefaults?.[targetName]?.always,
41
42
  cache: project.targets?.[targetName]?.cache ?? targetDefaults?.[targetName]?.cache,
42
43
  id: getTaskId(target),
43
44
  outputs: getTaskOutputs(projectName, targetName, workspace, targetDefaults),
44
45
  overrides,
45
46
  parallelism: project.targets?.[targetName]?.parallelism ?? targetDefaults?.[targetName]?.parallelism,
46
47
  projectRoot: project.root,
47
- target
48
+ target,
49
+ when: project.targets?.[targetName]?.when ?? targetDefaults?.[targetName]?.when
48
50
  }
49
51
  ];
50
52
  };
@@ -63,13 +65,15 @@ const getDependencyProjectTasks = (projectName, targetName, overrides, workspace
63
65
  target: targetName
64
66
  };
65
67
  tasks.push({
68
+ always: depProject.targets?.[targetName]?.always ?? targetDefaults?.[targetName]?.always,
66
69
  cache: depProject.targets?.[targetName]?.cache ?? targetDefaults?.[targetName]?.cache,
67
70
  id: getTaskId(target),
68
71
  outputs: getTaskOutputs(dep.target, targetName, workspace, targetDefaults),
69
72
  overrides,
70
73
  parallelism: depProject.targets?.[targetName]?.parallelism ?? targetDefaults?.[targetName]?.parallelism,
71
74
  projectRoot: depProject.root,
72
- target
75
+ target,
76
+ when: depProject.targets?.[targetName]?.when ?? targetDefaults?.[targetName]?.when
73
77
  });
74
78
  }
75
79
  }
@@ -3,10 +3,40 @@ import { Cache } from './Cache-CWaX_c8U.js';
3
3
  import { inferFrameworkEnvPatterns } from './detectFrameworks-CeFzKE6J.js';
4
4
  import { IncrementalFileHasher } from './IncrementalFileHasher-BRS76-mb.js';
5
5
  import { RemoteCache } from './RemoteCache-DSU3lc87.js';
6
- import { InProcessTaskHasher } from './computeTaskHash-DYqfrDGq.js';
7
- import { TaskOrchestrator } from './TaskOrchestrator-rf45vW5c.js';
6
+ import { InProcessTaskHasher } from './computeTaskHash-B5APHW7e.js';
7
+ import { TaskOrchestrator } from './TaskOrchestrator-UCMHCx8c.js';
8
8
  import { TaskScheduler } from './parsePartition-BfLbHGAx.js';
9
9
 
10
+ const partitionAlwaysTasks = (taskGraph) => {
11
+ const alwaysTasks = [];
12
+ const remaining = {};
13
+ for (const [id, task] of Object.entries(taskGraph.tasks)) {
14
+ if (task.always) {
15
+ alwaysTasks.push(task);
16
+ } else {
17
+ remaining[id] = task;
18
+ }
19
+ }
20
+ if (alwaysTasks.length === 0) {
21
+ return { alwaysTasks: [], graph: taskGraph };
22
+ }
23
+ const alwaysIds = new Set(alwaysTasks.map((t) => t.id));
24
+ const dependencies = {};
25
+ for (const [id, deps] of Object.entries(taskGraph.dependencies)) {
26
+ if (alwaysIds.has(id)) {
27
+ continue;
28
+ }
29
+ dependencies[id] = deps.filter((dep) => !alwaysIds.has(dep));
30
+ }
31
+ return {
32
+ alwaysTasks,
33
+ graph: {
34
+ dependencies,
35
+ roots: taskGraph.roots.filter((id) => !alwaysIds.has(id)),
36
+ tasks: remaining
37
+ }
38
+ };
39
+ };
10
40
  const computeGlobalEnvNamespace = (globalEnv) => {
11
41
  if (!globalEnv || globalEnv.length === 0) {
12
42
  return void 0;
@@ -56,8 +86,9 @@ const defaultTaskRunner = async (_tasks, options, context) => {
56
86
  targetDefaults: options.targetDefaults,
57
87
  workspaceRoot
58
88
  });
89
+ const { alwaysTasks, graph: scheduledGraph } = partitionAlwaysTasks(taskGraph);
59
90
  const maxParallel = resolveParallel(options.parallel);
60
- const scheduler = new TaskScheduler(taskGraph, projectGraph, maxParallel);
91
+ const scheduler = new TaskScheduler(scheduledGraph, projectGraph, maxParallel);
61
92
  const resolveCommand = (task) => {
62
93
  const project = projectGraph.nodes[task.target.project];
63
94
  const targetConfig = project?.data.targets?.[task.target.target];
@@ -71,6 +102,7 @@ const defaultTaskRunner = async (_tasks, options, context) => {
71
102
  fingerprintEnvPatterns = [.../* @__PURE__ */ new Set([...fingerprintEnvPatterns, ...inferredPatterns])];
72
103
  }
73
104
  const orchestrator = new TaskOrchestrator({
105
+ alwaysTasks,
74
106
  autoFingerprint: options.autoFingerprint,
75
107
  cache,
76
108
  cacheDiagnostics: options.cacheDiagnostics,
@@ -84,7 +116,7 @@ const defaultTaskRunner = async (_tasks, options, context) => {
84
116
  skipCache: options.skipNxCache,
85
117
  summarize: options.summarize,
86
118
  taskExecutor,
87
- taskGraph,
119
+ taskGraph: scheduledGraph,
88
120
  taskHasher,
89
121
  untrackedEnvVars: options.untrackedEnvVars,
90
122
  workspaceRoot
@@ -0,0 +1,46 @@
1
+ const TOKEN_PATHS = {
2
+ "affected.files": "affectedFiles",
3
+ changed_files: "affectedFiles"
4
+ };
5
+ const TOKEN_REGEX = /\\?\$\{\s*([\w.]+)\s*(?:\|\s*flag\s+(["'])(.*?)\2\s*)?\}/g;
6
+ const shellQuote = (value) => `'${value.replaceAll("'", String.raw`'\''`)}'`;
7
+ const rewriteForProjectRoot = (filePath, projectRoot) => {
8
+ if (filePath === projectRoot) {
9
+ return ".";
10
+ }
11
+ const prefix = `${projectRoot}/`;
12
+ if (filePath.startsWith(prefix)) {
13
+ return filePath.slice(prefix.length);
14
+ }
15
+ return void 0;
16
+ };
17
+ const expandTokensInString = (command, context) => command.replaceAll(TOKEN_REGEX, (match, name, _quote, flagValue) => {
18
+ if (match.startsWith("\\")) {
19
+ return match.slice(1);
20
+ }
21
+ const sourceKey = TOKEN_PATHS[name];
22
+ if (!sourceKey) {
23
+ return match;
24
+ }
25
+ const rawFiles = context[sourceKey] ?? [];
26
+ const files = context.projectRoot ? rawFiles.map((f) => rewriteForProjectRoot(f, context.projectRoot)).filter((f) => f !== void 0) : rawFiles;
27
+ if (files.length === 0) {
28
+ return "";
29
+ }
30
+ if (flagValue !== void 0 && flagValue.length > 0) {
31
+ return files.map((file) => `${flagValue} ${shellQuote(file)}`).join(" ");
32
+ }
33
+ return files.map(shellQuote).join(" ");
34
+ });
35
+ const expandTokens = (config, context) => {
36
+ if (!config.command.includes("${")) {
37
+ return config;
38
+ }
39
+ const command = expandTokensInString(config.command, context);
40
+ if (command === config.command) {
41
+ return config;
42
+ }
43
+ return { ...config, command };
44
+ };
45
+
46
+ export { expandTokens, expandTokensInString };
@@ -44,8 +44,7 @@ const parseNpmLockfile = (content) => {
44
44
  continue;
45
45
  }
46
46
  const name = extractPackageName(path);
47
- if (name && // Use the first (top-level) occurrence
48
- !versions.has(name)) {
47
+ if (name && !versions.has(name)) {
49
48
  versions.set(name, entry.version);
50
49
  }
51
50
  }
@@ -0,0 +1,153 @@
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
+ const normalisePlatform = (value) => value === "windows" ? "win32" : value;
25
+ const matchPlatform = (clause, current) => {
26
+ const list = Array.isArray(clause) ? clause : [clause];
27
+ return list.some((p) => normalisePlatform(p) === normalisePlatform(current));
28
+ };
29
+ const matchBranch = (clause, current) => {
30
+ if (current === "") {
31
+ return false;
32
+ }
33
+ const list = Array.isArray(clause) ? clause : [clause];
34
+ return list.includes(current);
35
+ };
36
+ const matchSingleEnv = (matcher, env) => {
37
+ if (typeof matcher === "string") {
38
+ const value2 = env[matcher];
39
+ return value2 !== void 0 && value2 !== "";
40
+ }
41
+ const value = env[matcher.name];
42
+ if (matcher.equals !== void 0) {
43
+ return value === matcher.equals;
44
+ }
45
+ if (matcher.exists !== void 0) {
46
+ const present = value !== void 0 && value !== "";
47
+ return matcher.exists ? present : !present;
48
+ }
49
+ return value !== void 0 && value !== "";
50
+ };
51
+ const matchEnv = (clause, env) => {
52
+ const list = Array.isArray(clause) ? clause : [clause];
53
+ return list.some((m) => matchSingleEnv(m, env));
54
+ };
55
+ const detectCi = (env) => {
56
+ const value = env.CI;
57
+ return value !== void 0 && value !== "" && value !== "false" && value !== "0";
58
+ };
59
+ const branchCache = /* @__PURE__ */ new Map();
60
+ const getCurrentBranch = (cwd) => {
61
+ const cached = branchCache.get(cwd);
62
+ if (cached !== void 0) {
63
+ return cached;
64
+ }
65
+ try {
66
+ const out = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd, stdio: ["ignore", "pipe", "ignore"] });
67
+ let branch = out.toString("utf8").trim();
68
+ if (branch === "HEAD") {
69
+ branch = "";
70
+ }
71
+ branchCache.set(cwd, branch);
72
+ return branch;
73
+ } catch {
74
+ branchCache.set(cwd, "");
75
+ return "";
76
+ }
77
+ };
78
+ const resetBranchCache = () => {
79
+ branchCache.clear();
80
+ };
81
+ const evaluateWhen = (when, context = {}) => {
82
+ if (!when) {
83
+ return true;
84
+ }
85
+ const env = context.env ?? process.env;
86
+ const currentPlatform = context.platform ?? process.platform;
87
+ const branch = context.branch ?? "";
88
+ const ci = context.ci ?? detectCi(env);
89
+ if (when.os !== void 0 && !matchPlatform(when.os, currentPlatform)) {
90
+ return false;
91
+ }
92
+ if (when.env !== void 0 && !matchEnv(when.env, env)) {
93
+ return false;
94
+ }
95
+ if (when.branch !== void 0 && !matchBranch(when.branch, branch)) {
96
+ return false;
97
+ }
98
+ if (when.ci !== void 0 && when.ci !== ci) {
99
+ return false;
100
+ }
101
+ if (when.not) {
102
+ if (when.not.os !== void 0 && matchPlatform(when.not.os, currentPlatform)) {
103
+ return false;
104
+ }
105
+ if (when.not.env !== void 0 && matchEnv(when.not.env, env)) {
106
+ return false;
107
+ }
108
+ if (when.not.branch !== void 0 && matchBranch(when.not.branch, branch)) {
109
+ return false;
110
+ }
111
+ if (when.not.ci !== void 0 && when.not.ci === ci) {
112
+ return false;
113
+ }
114
+ }
115
+ return true;
116
+ };
117
+ const explainWhen = (when, context = {}) => {
118
+ if (!when || evaluateWhen(when, context)) {
119
+ return "";
120
+ }
121
+ const env = context.env ?? process.env;
122
+ const currentPlatform = context.platform ?? process.platform;
123
+ const branch = context.branch ?? "";
124
+ const ci = context.ci ?? detectCi(env);
125
+ const reasons = [];
126
+ if (when.os !== void 0 && !matchPlatform(when.os, currentPlatform)) {
127
+ reasons.push(`os=${currentPlatform} does not match ${JSON.stringify(when.os)}`);
128
+ }
129
+ if (when.env !== void 0 && !matchEnv(when.env, env)) {
130
+ reasons.push(`env clause did not match`);
131
+ }
132
+ if (when.branch !== void 0 && !matchBranch(when.branch, branch)) {
133
+ reasons.push(`branch=${branch || "(unknown)"} does not match ${JSON.stringify(when.branch)}`);
134
+ }
135
+ if (when.ci !== void 0 && when.ci !== ci) {
136
+ reasons.push(`ci=${ci} does not match required ci=${when.ci}`);
137
+ }
138
+ if (when.not?.os !== void 0 && matchPlatform(when.not.os, currentPlatform)) {
139
+ reasons.push(`os=${currentPlatform} matches excluded ${JSON.stringify(when.not.os)}`);
140
+ }
141
+ if (when.not?.env !== void 0 && matchEnv(when.not.env, env)) {
142
+ reasons.push(`env clause matches excluded matcher`);
143
+ }
144
+ if (when.not?.branch !== void 0 && matchBranch(when.not.branch, branch)) {
145
+ reasons.push(`branch=${branch} matches excluded ${JSON.stringify(when.not.branch)}`);
146
+ }
147
+ if (when.not?.ci !== void 0 && when.not.ci === ci) {
148
+ reasons.push(`ci=${ci} matches excluded ci=${when.not.ci}`);
149
+ }
150
+ return reasons.join("; ");
151
+ };
152
+
153
+ export { evaluateWhen, explainWhen, getCurrentBranch, resetBranchCache };
@@ -1,10 +1,12 @@
1
1
  import { expandArguments } from './expandArguments-0AwD2BIA.js';
2
2
  import { expandShortcut } from './expandShortcut-BVG05ee4.js';
3
+ import { expandTokens } from './expandTokensInString-C03AGAjh.js';
4
+ export { expandTokensInString } from './expandTokensInString-C03AGAjh.js';
3
5
  import { expandWildcard } from './expandWildcard-B0xN_knq.js';
4
6
  import { stripQuotes } from './stripQuotes-Cey-zwFf.js';
5
7
 
6
8
  const parseCommands = (inputs, options = {}) => {
7
- const { additionalArguments = [] } = options;
9
+ const { additionalArguments = [], tokens } = options;
8
10
  let configs = inputs.map((input) => {
9
11
  if (typeof input === "string") {
10
12
  return { command: input };
@@ -17,10 +19,13 @@ const parseCommands = (inputs, options = {}) => {
17
19
  const result = expandWildcard(config);
18
20
  return Array.isArray(result) ? result : [result];
19
21
  });
22
+ if (tokens) {
23
+ configs = configs.map((config) => expandTokens(config, tokens));
24
+ }
20
25
  if (additionalArguments.length > 0) {
21
26
  configs = configs.map((config) => expandArguments(config, additionalArguments));
22
27
  }
23
28
  return configs;
24
29
  };
25
30
 
26
- export { expandArguments, expandShortcut, expandWildcard, parseCommands, stripQuotes };
31
+ export { expandArguments, expandShortcut, expandTokens, expandWildcard, parseCommands, stripQuotes };