@kici-dev/compiler 0.1.25 → 0.1.27

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 (96) hide show
  1. package/dist/cli-banner.d.ts +6 -3
  2. package/dist/cli-banner.js +13 -5
  3. package/dist/cli.js +66 -43
  4. package/dist/commands/check-mode.d.ts +1 -1
  5. package/dist/commands/compile.js +3 -3
  6. package/dist/commands/index.d.ts +5 -1
  7. package/dist/commands/index.js +6 -3
  8. package/dist/commands/init.js +1 -1
  9. package/dist/commands/local-trust-root.d.ts +6 -0
  10. package/dist/commands/local-trust-root.js +52 -0
  11. package/dist/commands/local.d.ts +39 -0
  12. package/dist/commands/local.js +137 -0
  13. package/dist/commands/login.d.ts +2 -0
  14. package/dist/commands/login.js +40 -0
  15. package/dist/commands/logout.js +7 -0
  16. package/dist/commands/preview.js +1 -1
  17. package/dist/commands/run-banner.d.ts +31 -0
  18. package/dist/commands/run-banner.js +25 -0
  19. package/dist/commands/run-routed.d.ts +53 -0
  20. package/dist/commands/run-routed.js +176 -0
  21. package/dist/commands/run.d.ts +0 -17
  22. package/dist/commands/run.js +7 -50
  23. package/dist/commands/secrets-list.d.ts +2 -2
  24. package/dist/commands/secrets-list.js +4 -4
  25. package/dist/commands/types.d.ts +3 -3
  26. package/dist/commands/types.js +4 -4
  27. package/dist/execution/executor.js +7 -7
  28. package/dist/generators/secrets-dts.d.ts +9 -9
  29. package/dist/generators/secrets-dts.js +12 -12
  30. package/dist/llm-context/llms-architecture.txt +2 -2
  31. package/dist/llm-context/llms-cli.txt +674 -340
  32. package/dist/llm-context/llms-features.txt +334 -336
  33. package/dist/llm-context/llms-full.txt +1207 -892
  34. package/dist/llm-context/llms-getting-started.txt +7 -15
  35. package/dist/llm-context/llms-patterns.txt +5 -23
  36. package/dist/llm-context/llms-providers.txt +10 -1
  37. package/dist/llm-context/llms-sdk.txt +12 -12
  38. package/dist/llm-context/llms.txt +5 -5
  39. package/dist/local-executor/secret-loader.d.ts +1 -1
  40. package/dist/local-plane/orchestrator-process.d.ts +65 -0
  41. package/dist/local-plane/orchestrator-process.js +115 -0
  42. package/dist/local-plane/paths.d.ts +39 -0
  43. package/dist/local-plane/paths.js +54 -0
  44. package/dist/local-plane/plane-manager.d.ts +151 -0
  45. package/dist/local-plane/plane-manager.js +340 -0
  46. package/dist/local-plane/plane-seed.d.ts +76 -0
  47. package/dist/local-plane/plane-seed.js +59 -0
  48. package/dist/local-plane/plane-trigger.d.ts +62 -0
  49. package/dist/local-plane/plane-trigger.js +97 -0
  50. package/dist/local-plane/platform-attach.d.ts +53 -0
  51. package/dist/local-plane/platform-attach.js +111 -0
  52. package/dist/local-plane/postgres.d.ts +44 -0
  53. package/dist/local-plane/postgres.js +165 -0
  54. package/dist/local-plane/resolve-plane.d.ts +37 -0
  55. package/dist/local-plane/resolve-plane.js +76 -0
  56. package/dist/local-plane/run-follow.d.ts +41 -0
  57. package/dist/local-plane/run-follow.js +104 -0
  58. package/dist/local-plane/scaler-config.d.ts +54 -0
  59. package/dist/local-plane/scaler-config.js +114 -0
  60. package/dist/local-plane/secret-seed.d.ts +59 -0
  61. package/dist/local-plane/secret-seed.js +112 -0
  62. package/dist/local-plane/source-provider.d.ts +43 -0
  63. package/dist/local-plane/source-provider.js +165 -0
  64. package/dist/local-plane/trusted-routing.d.ts +32 -0
  65. package/dist/local-plane/trusted-routing.js +47 -0
  66. package/dist/lockfile/generator.js +7 -7
  67. package/dist/remote/dashboard-client.d.ts +7 -7
  68. package/dist/remote/dashboard-client.js +7 -7
  69. package/dist/remote/platform-client.d.ts +1 -1
  70. package/dist/remote/secret-loader.d.ts +18 -0
  71. package/dist/{local-executor → remote}/secret-loader.js +4 -4
  72. package/dist/remote/secret-upload.d.ts +1 -1
  73. package/dist/remote/secret-upload.js +2 -2
  74. package/dist/remote/uploader.d.ts +1 -1
  75. package/dist/remote/uploader.js +2 -2
  76. package/dist/templates/agents-md.d.ts +1 -1
  77. package/dist/templates/agents-md.js +2 -2
  78. package/dist/templates/package-json.js +1 -1
  79. package/dist/test-runner/job-executor.js +1 -1
  80. package/dist/test-runner/runs-on-display.d.ts +9 -0
  81. package/dist/{local-executor → test-runner}/runs-on-display.js +1 -1
  82. package/dist/test-runner/step-context.d.ts +1 -1
  83. package/dist/test-runner/step-context.js +2 -2
  84. package/dist/types.d.ts +6 -6
  85. package/package.json +7 -4
  86. package/sbom.spdx.json +9884 -1459
  87. package/dist/local-executor/dag-scheduler.js +0 -183
  88. package/dist/local-executor/index.js +0 -378
  89. package/dist/local-executor/job-runner.js +0 -411
  90. package/dist/local-executor/materializer.js +0 -132
  91. package/dist/local-executor/output-streamer.js +0 -168
  92. package/dist/local-executor/payload-generator.js +0 -138
  93. package/dist/local-executor/picker.js +0 -109
  94. package/dist/local-executor/to-event-payload.js +0 -21
  95. package/dist/local-executor/types.js +0 -2
  96. package/dist/local-executor/workflow-lock.js +0 -0
@@ -1,183 +0,0 @@
1
- import "../rolldown-runtime-ClRpJifh.js";
2
- //#region src/local-executor/dag-scheduler.ts
3
- /**
4
- * Detect cycles using Kahn's algorithm.
5
- * Returns true if a cycle exists.
6
- */
7
- function detectCycle(nodes) {
8
- const inDegree = /* @__PURE__ */ new Map();
9
- for (const n of nodes) {
10
- if (!inDegree.has(n.name)) inDegree.set(n.name, 0);
11
- for (const dep of n.needs) {
12
- inDegree.set(n.name, (inDegree.get(n.name) ?? 0) + 1);
13
- if (!inDegree.has(dep)) inDegree.set(dep, 0);
14
- }
15
- }
16
- const queue = [];
17
- for (const [name, deg] of inDegree) if (deg === 0) queue.push(name);
18
- let processed = 0;
19
- while (queue.length > 0) {
20
- const name = queue.shift();
21
- processed++;
22
- for (const n of nodes) if (n.needs.includes(name)) {
23
- const newDeg = inDegree.get(n.name) - 1;
24
- inDegree.set(n.name, newDeg);
25
- if (newDeg === 0) queue.push(n.name);
26
- }
27
- }
28
- return processed < nodes.length;
29
- }
30
- /**
31
- * Execute a DAG of nodes with bounded concurrency and fail-fast support.
32
- *
33
- * Algorithm:
34
- * 1. Detect cycles
35
- * 2. Initialize ready queue with zero-dependency nodes
36
- * 3. Event loop: launch ready nodes up to concurrency limit,
37
- * await first completion, update state, repeat
38
- * 4. On failure: if failFast, abort all running and skip pending;
39
- * if keep-going, only skip transitive dependents
40
- */
41
- async function executeDag(nodes, callbacks, options) {
42
- if (nodes.length === 0) return {
43
- results: /* @__PURE__ */ new Map(),
44
- skipped: [],
45
- cancelled: [],
46
- status: "success"
47
- };
48
- if (detectCycle(nodes)) throw new Error("Circular dependency detected in job graph");
49
- const nodeMap = new Map(nodes.map((n) => [n.name, n]));
50
- const results = /* @__PURE__ */ new Map();
51
- const skipped = [];
52
- const cancelled = [];
53
- const failedNames = /* @__PURE__ */ new Set();
54
- const pending = new Set(nodes.map((n) => n.name));
55
- const running = /* @__PURE__ */ new Map();
56
- let hasFailure = false;
57
- let abortAll = false;
58
- /**
59
- * Check if a node is ready to execute:
60
- * all dependencies completed successfully.
61
- */
62
- function isReady(name) {
63
- return nodeMap.get(name).needs.every((dep) => results.has(dep) && callbacks.isSuccess(results.get(dep)));
64
- }
65
- /**
66
- * Check if a node should be skipped because a dependency failed.
67
- */
68
- function shouldSkip(name) {
69
- return nodeMap.get(name).needs.some((dep) => failedNames.has(dep) || skipped.includes(dep) || cancelled.includes(dep));
70
- }
71
- while (pending.size > 0 || running.size > 0) {
72
- if (abortAll) {
73
- for (const name of pending) skipped.push(name);
74
- pending.clear();
75
- if (running.size > 0) {
76
- const entries = [...running.entries()];
77
- const settled = await Promise.allSettled(entries.map(([, v]) => v.promise));
78
- for (let i = 0; i < entries.length; i++) {
79
- const [name] = entries[i];
80
- const s = settled[i];
81
- if (s.status === "fulfilled") {
82
- results.set(name, s.value);
83
- if (!callbacks.isSuccess(s.value)) cancelled.push(name);
84
- } else cancelled.push(name);
85
- running.delete(name);
86
- }
87
- }
88
- break;
89
- }
90
- let skipChanged = true;
91
- while (skipChanged) {
92
- skipChanged = false;
93
- const toSkip = [];
94
- for (const name of pending) if (shouldSkip(name)) toSkip.push(name);
95
- for (const name of toSkip) {
96
- pending.delete(name);
97
- skipped.push(name);
98
- skipChanged = true;
99
- }
100
- }
101
- const readyNodes = [];
102
- for (const name of pending) {
103
- if (running.size + readyNodes.length >= options.maxConcurrency) break;
104
- if (isReady(name)) readyNodes.push(name);
105
- }
106
- for (const name of readyNodes) {
107
- pending.delete(name);
108
- const controller = new AbortController();
109
- const promise = callbacks.execute(name, controller.signal);
110
- running.set(name, {
111
- promise,
112
- controller
113
- });
114
- }
115
- if (running.size === 0) break;
116
- const wrappedPromises = [...running.entries()].map(async ([name, { promise }]) => {
117
- return {
118
- name,
119
- result: await promise
120
- };
121
- });
122
- for (const p of wrappedPromises) p.catch(() => {});
123
- const { name: completedName, result: completedResult } = await Promise.race(wrappedPromises);
124
- running.delete(completedName);
125
- results.set(completedName, completedResult);
126
- if (!callbacks.isSuccess(completedResult)) {
127
- hasFailure = true;
128
- failedNames.add(completedName);
129
- if (options.failFast) {
130
- for (const [, { controller }] of running) controller.abort();
131
- abortAll = true;
132
- }
133
- }
134
- }
135
- return {
136
- results,
137
- skipped,
138
- cancelled,
139
- status: hasFailure || skipped.length > 0 || cancelled.length > 0 ? "failure" : "success"
140
- };
141
- }
142
- /**
143
- * Given a list of DAG nodes and a target job name, return the target
144
- * plus all transitive dependencies in topological order.
145
- */
146
- function resolveJobFilter(nodes, targetName) {
147
- const nodeMap = new Map(nodes.map((n) => [n.name, n]));
148
- if (!nodeMap.has(targetName)) return [];
149
- const needed = /* @__PURE__ */ new Set();
150
- const queue = [targetName];
151
- while (queue.length > 0) {
152
- const name = queue.shift();
153
- if (needed.has(name)) continue;
154
- needed.add(name);
155
- const node = nodeMap.get(name);
156
- if (node) {
157
- for (const dep of node.needs) if (!needed.has(dep)) queue.push(dep);
158
- }
159
- }
160
- const subset = nodes.filter((n) => needed.has(n.name));
161
- const inDegree = /* @__PURE__ */ new Map();
162
- for (const n of subset) {
163
- if (!inDegree.has(n.name)) inDegree.set(n.name, 0);
164
- for (const dep of n.needs) if (needed.has(dep)) inDegree.set(n.name, (inDegree.get(n.name) ?? 0) + 1);
165
- }
166
- const result = [];
167
- const topoQueue = [];
168
- for (const [name, deg] of inDegree) if (deg === 0) topoQueue.push(name);
169
- while (topoQueue.length > 0) {
170
- const name = topoQueue.shift();
171
- result.push(nodeMap.get(name));
172
- for (const n of subset) if (n.needs.includes(name) && needed.has(n.name)) {
173
- const newDeg = inDegree.get(n.name) - 1;
174
- inDegree.set(n.name, newDeg);
175
- if (newDeg === 0) topoQueue.push(n.name);
176
- }
177
- }
178
- return result;
179
- }
180
- //#endregion
181
- export { executeDag, resolveJobFilter };
182
-
183
- //# sourceMappingURL=dag-scheduler.js.map
@@ -1,378 +0,0 @@
1
- import "../rolldown-runtime-ClRpJifh.js";
2
- import { discoverWorkflows, resolveKiciDir } from "../execution/executor.js";
3
- import "../execution/index.js";
4
- import { transformTriggers } from "../lockfile/generator.js";
5
- import { loadLocalSecrets } from "./secret-loader.js";
6
- import { executeDag, resolveJobFilter } from "./dag-scheduler.js";
7
- import { compileCommand } from "../commands/compile.js";
8
- import { generateEventPayload } from "./payload-generator.js";
9
- import { executeResolvedJob, resolveJobs } from "./job-runner.js";
10
- import { displayLocalSummary, formatLocalJsonResult, formatLocalJunitResult } from "./output-streamer.js";
11
- import { PickerCancelledError, runPicker } from "./picker.js";
12
- import { gcStaleRunCheckouts, materializeCheckout } from "./materializer.js";
13
- import { ConcurrencyKeyEvaluationError, acquireWorkflowLock } from "./workflow-lock.js";
14
- import path from "node:path";
15
- import pc from "picocolors";
16
- import { writeFile } from "node:fs/promises";
17
- import { flattenStepInputs } from "@kici-dev/sdk";
18
- import { logger } from "@kici-dev/core";
19
- import { CheckMode, CheckStepOutcome, coerceDispatchInputs, matchAllWorkflows, parseInputPairs } from "@kici-dev/engine";
20
- import os from "node:os";
21
- //#region src/local-executor/index.ts
22
- /**
23
- * Public API: executeLocal() orchestrating compile, match, resolve, schedule, report.
24
- *
25
- * Pipeline:
26
- * 1. Compile workflows from .kici/ sources
27
- * 2. Load workflows (discover runtime modules)
28
- * 3. Load secrets from local files
29
- * 4. Build event payload from git state + CLI overrides
30
- * 5. Match triggers to find active workflows
31
- * 6. Apply --workflow and --job filters
32
- * 7. Resolve jobs (matrix expansion, dynamic evaluation)
33
- * 8. Execute via DAG scheduler with concurrency control
34
- * 9. Return success/failure
35
- */
36
- /**
37
- * Convert SDK workflows to lock-file-like structure for trigger matching.
38
- * Simplified version of the one in test.ts -- only needs trigger info.
39
- */
40
- function workflowsToLockFormat(workflows) {
41
- return workflows.map((w) => ({
42
- name: w.name,
43
- contentHash: "",
44
- compileSchemaVersion: 0,
45
- triggers: transformTriggers(w.on),
46
- jobs: w.jobs.map((j, i) => {
47
- if (typeof j === "function") return {
48
- _type: "dynamic",
49
- source: {
50
- file: "",
51
- index: i
52
- }
53
- };
54
- return {
55
- _type: "static",
56
- name: j.name,
57
- runsOn: j.runsOn,
58
- needs: j.needs?.map((n) => typeof n === "string" ? n : n.name) ?? [],
59
- steps: flattenStepInputs(j.steps).map((s) => {
60
- if (typeof s === "function") return {
61
- name: "",
62
- hasOutputs: false
63
- };
64
- return {
65
- name: s.name,
66
- hasOutputs: !!s.outputs
67
- };
68
- })
69
- };
70
- }),
71
- rules: w.rules?.map((r, i) => ({
72
- _type: "dynamic",
73
- label: r.label,
74
- source: {
75
- file: "",
76
- index: i
77
- }
78
- }))
79
- }));
80
- }
81
- /**
82
- * Derive `groupCtx.branch` for the workflow concurrency `group` callback.
83
- * Mirrors the agent's semantics (`request.branch ?? request.ref`) so a
84
- * `group({ branch })` function works identically locally and remotely.
85
- */
86
- function deriveBranchForGroupCtx(event) {
87
- if (event.targetBranch) return event.targetBranch;
88
- if (event.sourceBranch) return event.sourceBranch;
89
- const ref = event.payload?.ref;
90
- if (typeof ref === "string") {
91
- const m = /^refs\/heads\/(.+)$/.exec(ref);
92
- if (m) return m[1];
93
- }
94
- return "";
95
- }
96
- /**
97
- * Execute one matched workflow under its concurrency lock (if any).
98
- *
99
- * Returns the result and a flag indicating whether it should count towards
100
- * `allSucceeded`. Returns `null` when the workflow was filtered out (e.g. by
101
- * `--job` removing every node).
102
- */
103
- async function runOneMatchedWorkflow(workflow, ctx) {
104
- const { options, isQuiet, event, secrets, kiciDir, concurrency, failFast } = ctx;
105
- if (!isQuiet) logger.info(`\n${pc.bold("Running workflow:")} ${workflow.name}\n`);
106
- let lockHandle = null;
107
- try {
108
- lockHandle = await acquireWorkflowLock({
109
- workflowName: workflow.name,
110
- workflow,
111
- event,
112
- branch: deriveBranchForGroupCtx(event),
113
- debug: options.debug
114
- });
115
- } catch (err) {
116
- if (err instanceof ConcurrencyKeyEvaluationError) {
117
- logger.error(pc.red(`Concurrency group evaluation failed for workflow "${workflow.name}": ${err.cause?.message ?? err.message}`));
118
- return {
119
- result: {
120
- name: workflow.name,
121
- status: "failure",
122
- jobs: [],
123
- durationMs: 0
124
- },
125
- succeeded: false
126
- };
127
- }
128
- throw err;
129
- }
130
- try {
131
- return await runWorkflowBody(workflow, ctx, options, secrets, kiciDir, concurrency, failFast);
132
- } finally {
133
- await lockHandle?.release();
134
- }
135
- }
136
- /**
137
- * The actual DAG-execution body for one workflow. Split out so
138
- * {@link runOneMatchedWorkflow} can wrap it in a lock acquire/release.
139
- */
140
- /**
141
- * Resolve the dispatch-input descriptor declared on a workflow's `dispatch()`
142
- * trigger(s) from the in-memory trigger objects, merged across triggers.
143
- * Returns undefined when none declared.
144
- */
145
- function dispatchInputsDescriptorForWorkflow(workflow) {
146
- const merged = {};
147
- let found = false;
148
- for (const trigger of transformTriggers(workflow.on)) if (trigger._type === "dispatch" && trigger.inputs) {
149
- Object.assign(merged, trigger.inputs);
150
- found = true;
151
- }
152
- return found ? merged : void 0;
153
- }
154
- /**
155
- * Coerce + default the operator's `--input` pairs against the workflow's
156
- * dispatch descriptor for a local run. The local executor is authoritative for
157
- * `run local` (no orchestrator), so defaults are applied here exactly once.
158
- * Returns the resolved values, or throws when input is invalid.
159
- */
160
- function resolveLocalDispatchInputs(workflow, options) {
161
- const descriptor = dispatchInputsDescriptorForWorkflow(workflow);
162
- const raw = parseInputPairs(options.inputs ?? []);
163
- if (!descriptor) return raw;
164
- const r = coerceDispatchInputs(raw, descriptor);
165
- if ("error" in r) throw r.error;
166
- return r.values;
167
- }
168
- async function runWorkflowBody(workflow, ctx, options, secrets, kiciDir, concurrency, failFast) {
169
- const { event } = ctx;
170
- const dispatchInputs = resolveLocalDispatchInputs(workflow, options);
171
- const resolvedJobs = await resolveJobs(workflow, event);
172
- let dagNodes = resolvedJobs.map((r) => ({
173
- name: r.expandedName,
174
- needs: r.resolvedNeeds
175
- }));
176
- if (options.job) {
177
- dagNodes = resolveJobFilter(dagNodes, options.job);
178
- if (dagNodes.length === 0) return null;
179
- }
180
- const resolvedJobMap = new Map(resolvedJobs.map((r) => [r.expandedName, r]));
181
- const dagNodeNames = new Set(dagNodes.map((n) => n.name));
182
- const filteredResolvedJobs = resolvedJobs.filter((r) => dagNodeNames.has(r.expandedName));
183
- const jobOutputsMap = /* @__PURE__ */ new Map();
184
- const startTime = Date.now();
185
- const dagResult = await executeDag(dagNodes, {
186
- execute: async (name, signal) => {
187
- const resolved = resolvedJobMap.get(name);
188
- if (!resolved) return {
189
- name,
190
- status: "failure",
191
- durationMs: 0,
192
- steps: [],
193
- error: /* @__PURE__ */ new Error(`Job "${name}" not found in resolved jobs`)
194
- };
195
- return executeResolvedJob(resolved, {
196
- workflowName: workflow.name,
197
- event,
198
- secrets,
199
- kiciDir,
200
- execDir: ctx.execDir,
201
- jobOutputsMap,
202
- signal,
203
- checkMode: options.checkMode,
204
- dispatchInputs
205
- });
206
- },
207
- isSuccess: (result) => result.status === "success" || result.status === "skipped"
208
- }, {
209
- maxConcurrency: concurrency,
210
- failFast
211
- });
212
- const durationMs = Date.now() - startTime;
213
- const jobResults = [];
214
- for (const rj of filteredResolvedJobs) {
215
- const result = dagResult.results.get(rj.expandedName);
216
- if (result) jobResults.push({
217
- ...result,
218
- matrixValues: Object.keys(rj.matrixValues).length > 0 ? rj.matrixValues : void 0
219
- });
220
- else if (dagResult.skipped.includes(rj.expandedName)) jobResults.push({
221
- name: rj.expandedName,
222
- status: "skipped",
223
- durationMs: 0,
224
- steps: [],
225
- matrixValues: Object.keys(rj.matrixValues).length > 0 ? rj.matrixValues : void 0
226
- });
227
- else if (dagResult.cancelled.includes(rj.expandedName)) jobResults.push({
228
- name: rj.expandedName,
229
- status: "cancelled",
230
- durationMs: 0,
231
- steps: [],
232
- cancelled: true,
233
- matrixValues: Object.keys(rj.matrixValues).length > 0 ? rj.matrixValues : void 0
234
- });
235
- }
236
- return {
237
- result: {
238
- name: workflow.name,
239
- status: dagResult.status === "success" ? "success" : "failure",
240
- jobs: jobResults,
241
- durationMs
242
- },
243
- succeeded: dagResult.status === "success"
244
- };
245
- }
246
- /**
247
- * Execute workflows locally -- the full compile-match-resolve-execute pipeline.
248
- *
249
- * @param options - CLI options from `kici run local`
250
- * @returns true if all workflows succeeded, false otherwise
251
- */
252
- async function executeLocal(options) {
253
- const kiciDir = resolveKiciDir(options.kiciDir);
254
- if (!await compileCommand({
255
- kiciDir,
256
- check: false,
257
- verbose: options.debug ?? false,
258
- quiet: Boolean(options.json || options.quiet)
259
- })) {
260
- process.exitCode = 2;
261
- return false;
262
- }
263
- const { workflows: workflowsWithSource } = await discoverWorkflows(kiciDir);
264
- const workflows = workflowsWithSource.map((w) => w.workflow);
265
- if (workflows.length === 0) {
266
- logger.info(pc.yellow("No workflows found"));
267
- return true;
268
- }
269
- if (options.pick) try {
270
- const picked = await runPicker(workflows, { filterEvent: options.event });
271
- options = {
272
- ...options,
273
- event: picked.event,
274
- workflow: picked.workflow
275
- };
276
- } catch (err) {
277
- if (err instanceof PickerCancelledError) {
278
- logger.info(pc.yellow(err.message));
279
- return true;
280
- }
281
- throw err;
282
- }
283
- if (!options.event) {
284
- logger.error(pc.red("Missing event argument. Pass an event (e.g. \"push\") or use --pick to select one."));
285
- process.exitCode = 2;
286
- return false;
287
- }
288
- const secrets = await loadLocalSecrets(kiciDir, options.env);
289
- const event = await generateEventPayload(options.event, options);
290
- const decisions = matchAllWorkflows(workflowsToLockFormat(workflows), event);
291
- let matchedWorkflows = workflows.filter((_, i) => decisions[i]?.matched);
292
- if (matchedWorkflows.length === 0) {
293
- logger.info(pc.gray("No workflows matched the event"));
294
- return true;
295
- }
296
- if (options.workflow) {
297
- matchedWorkflows = matchedWorkflows.filter((w) => w.name === options.workflow);
298
- if (matchedWorkflows.length === 0) {
299
- logger.info(pc.yellow(`No workflow named "${options.workflow}" matched the event`));
300
- return true;
301
- }
302
- }
303
- for (const workflow of matchedWorkflows) try {
304
- resolveLocalDispatchInputs(workflow, options);
305
- } catch (err) {
306
- logger.error(pc.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
307
- process.exit(2);
308
- }
309
- const isQuiet = Boolean(options.quiet || options.json);
310
- const repoRoot = path.dirname(kiciDir);
311
- let materialized = null;
312
- let execDir = repoRoot;
313
- if (!options.inPlace) {
314
- await gcStaleRunCheckouts(process.env.KICI_RUN_DIR ?? os.tmpdir());
315
- materialized = await materializeCheckout(repoRoot, { runDir: process.env.KICI_RUN_DIR });
316
- execDir = materialized.path;
317
- logger.info(pc.gray(`running in ${execDir}`));
318
- }
319
- const concurrency = options.concurrency ?? getDefaultConcurrency();
320
- const failFast = !options.keepGoing;
321
- let allSucceeded = true;
322
- const workflowResults = [];
323
- const ctx = {
324
- options,
325
- isQuiet,
326
- event,
327
- secrets,
328
- kiciDir,
329
- execDir,
330
- concurrency,
331
- failFast
332
- };
333
- try {
334
- for (const workflow of matchedWorkflows) {
335
- const outcome = await runOneMatchedWorkflow(workflow, ctx);
336
- if (outcome === null) continue;
337
- workflowResults.push(outcome.result);
338
- if (!outcome.succeeded) allSucceeded = false;
339
- }
340
- if (options.json) console.log(formatLocalJsonResult(workflowResults));
341
- else if (options.junit) {
342
- const junitXml = formatLocalJunitResult(workflowResults);
343
- await writeFile(options.junit, junitXml);
344
- if (!isQuiet) logger.info(pc.green(`JUnit XML written to ${options.junit}`));
345
- }
346
- if (!isQuiet) displayLocalSummary(workflowResults);
347
- if (options.checkMode === CheckMode.enum["check-fail-on-drift"] && hasDrift(workflowResults)) {
348
- if (!isQuiet) logger.info(pc.yellow("Drift detected in check mode (--fail-on-drift): exiting with code 2"));
349
- if (materialized && !options.keep) await materialized.cleanup();
350
- process.exit(2);
351
- }
352
- return allSucceeded;
353
- } finally {
354
- if (materialized) if (allSucceeded && !options.keep) await materialized.cleanup();
355
- else logger.info(pc.gray(`kept isolated checkout at ${execDir}`));
356
- }
357
- }
358
- /**
359
- * True when any step across all workflows reported drift (`dry-run` outcome) —
360
- * the signal `check-fail-on-drift` uses to exit non-zero.
361
- */
362
- function hasDrift(results) {
363
- return results.some((wf) => wf.jobs.some((job) => (job.steps ?? []).some((s) => s.checkOutcome === CheckStepOutcome.enum["dry-run"])));
364
- }
365
- /**
366
- * Get default concurrency based on available CPU parallelism.
367
- */
368
- function getDefaultConcurrency() {
369
- try {
370
- return os.availableParallelism();
371
- } catch {
372
- return os.cpus().length || 4;
373
- }
374
- }
375
- //#endregion
376
- export { executeLocal };
377
-
378
- //# sourceMappingURL=index.js.map