@kici-dev/compiler 0.1.26 → 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.
- package/dist/cli-banner.d.ts +6 -3
- package/dist/cli-banner.js +13 -5
- package/dist/cli.js +66 -43
- package/dist/commands/check-mode.d.ts +1 -1
- package/dist/commands/compile.js +3 -3
- package/dist/commands/index.d.ts +5 -1
- package/dist/commands/index.js +6 -3
- package/dist/commands/init.js +1 -1
- package/dist/commands/local-trust-root.d.ts +6 -0
- package/dist/commands/local-trust-root.js +52 -0
- package/dist/commands/local.d.ts +39 -0
- package/dist/commands/local.js +137 -0
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +40 -0
- package/dist/commands/logout.js +7 -0
- package/dist/commands/preview.js +1 -1
- package/dist/commands/run-banner.d.ts +31 -0
- package/dist/commands/run-banner.js +25 -0
- package/dist/commands/run-routed.d.ts +53 -0
- package/dist/commands/run-routed.js +176 -0
- package/dist/commands/run.d.ts +0 -17
- package/dist/commands/run.js +7 -50
- package/dist/commands/secrets-list.d.ts +2 -2
- package/dist/commands/secrets-list.js +4 -4
- package/dist/commands/types.d.ts +3 -3
- package/dist/commands/types.js +4 -4
- package/dist/execution/executor.js +7 -7
- package/dist/generators/secrets-dts.d.ts +9 -9
- package/dist/generators/secrets-dts.js +12 -12
- package/dist/llm-context/llms-architecture.txt +2 -2
- package/dist/llm-context/llms-cli.txt +674 -340
- package/dist/llm-context/llms-features.txt +334 -336
- package/dist/llm-context/llms-full.txt +1207 -892
- package/dist/llm-context/llms-getting-started.txt +7 -15
- package/dist/llm-context/llms-patterns.txt +5 -23
- package/dist/llm-context/llms-providers.txt +10 -1
- package/dist/llm-context/llms-sdk.txt +12 -12
- package/dist/llm-context/llms.txt +5 -5
- package/dist/local-executor/secret-loader.d.ts +1 -1
- package/dist/local-plane/orchestrator-process.d.ts +65 -0
- package/dist/local-plane/orchestrator-process.js +115 -0
- package/dist/local-plane/paths.d.ts +39 -0
- package/dist/local-plane/paths.js +54 -0
- package/dist/local-plane/plane-manager.d.ts +151 -0
- package/dist/local-plane/plane-manager.js +340 -0
- package/dist/local-plane/plane-seed.d.ts +76 -0
- package/dist/local-plane/plane-seed.js +59 -0
- package/dist/local-plane/plane-trigger.d.ts +62 -0
- package/dist/local-plane/plane-trigger.js +97 -0
- package/dist/local-plane/platform-attach.d.ts +53 -0
- package/dist/local-plane/platform-attach.js +111 -0
- package/dist/local-plane/postgres.d.ts +44 -0
- package/dist/local-plane/postgres.js +165 -0
- package/dist/local-plane/resolve-plane.d.ts +37 -0
- package/dist/local-plane/resolve-plane.js +76 -0
- package/dist/local-plane/run-follow.d.ts +41 -0
- package/dist/local-plane/run-follow.js +104 -0
- package/dist/local-plane/scaler-config.d.ts +54 -0
- package/dist/local-plane/scaler-config.js +114 -0
- package/dist/local-plane/secret-seed.d.ts +59 -0
- package/dist/local-plane/secret-seed.js +112 -0
- package/dist/local-plane/source-provider.d.ts +43 -0
- package/dist/local-plane/source-provider.js +165 -0
- package/dist/local-plane/trusted-routing.d.ts +32 -0
- package/dist/local-plane/trusted-routing.js +47 -0
- package/dist/lockfile/generator.js +7 -7
- package/dist/remote/dashboard-client.d.ts +7 -7
- package/dist/remote/dashboard-client.js +7 -7
- package/dist/remote/platform-client.d.ts +1 -1
- package/dist/remote/secret-loader.d.ts +18 -0
- package/dist/{local-executor → remote}/secret-loader.js +4 -4
- package/dist/remote/secret-upload.d.ts +1 -1
- package/dist/remote/secret-upload.js +2 -2
- package/dist/remote/uploader.d.ts +1 -1
- package/dist/remote/uploader.js +2 -2
- package/dist/templates/agents-md.d.ts +1 -1
- package/dist/templates/agents-md.js +2 -2
- package/dist/templates/package-json.js +1 -1
- package/dist/test-runner/job-executor.js +1 -1
- package/dist/test-runner/runs-on-display.d.ts +9 -0
- package/dist/{local-executor → test-runner}/runs-on-display.js +1 -1
- package/dist/test-runner/step-context.d.ts +1 -1
- package/dist/test-runner/step-context.js +2 -2
- package/dist/types.d.ts +6 -6
- package/package.json +7 -4
- package/sbom.spdx.json +9884 -1459
- package/dist/local-executor/dag-scheduler.js +0 -183
- package/dist/local-executor/index.js +0 -378
- package/dist/local-executor/job-runner.js +0 -411
- package/dist/local-executor/materializer.js +0 -132
- package/dist/local-executor/output-streamer.js +0 -168
- package/dist/local-executor/payload-generator.js +0 -138
- package/dist/local-executor/picker.js +0 -109
- package/dist/local-executor/to-event-payload.js +0 -21
- package/dist/local-executor/types.js +0 -2
- 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
|