@kici-dev/compiler 0.1.21 → 0.1.23
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.js +25 -7
- package/dist/commands/compile.d.ts +6 -0
- package/dist/commands/compile.js +6 -3
- package/dist/commands/docs.d.ts +8 -8
- package/dist/commands/docs.js +35 -16
- package/dist/commands/held-run-client.d.ts +7 -2
- package/dist/commands/held-run-client.js +9 -3
- package/dist/commands/held-run-resolve.d.ts +5 -0
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +15 -7
- package/dist/commands/org.js +2 -2
- package/dist/commands/run-hold-watch.d.ts +57 -0
- package/dist/commands/run-hold-watch.js +87 -0
- package/dist/commands/run.d.ts +23 -0
- package/dist/commands/run.js +150 -17
- package/dist/commands/test.d.ts +14 -0
- package/dist/commands/types.d.ts +2 -0
- package/dist/commands/types.js +1 -1
- package/dist/fixtures/describe-event.d.ts +6 -0
- package/dist/fixtures/describe-event.js +18 -0
- package/dist/fixtures/picker.d.ts +19 -0
- package/dist/fixtures/picker.js +64 -0
- package/dist/llm-context/llms-architecture.txt +1440 -0
- package/dist/llm-context/llms-cli.txt +2386 -0
- package/dist/llm-context/llms-features.txt +2389 -0
- package/dist/llm-context/llms-full.txt +1304 -349
- package/dist/llm-context/llms-getting-started.txt +519 -0
- package/dist/llm-context/llms-patterns.txt +1324 -0
- package/dist/llm-context/llms-providers.txt +805 -0
- package/dist/llm-context/llms-sdk.txt +3725 -0
- package/dist/llm-context/llms.txt +15 -1
- package/dist/local-executor/index.js +40 -3
- package/dist/local-executor/job-runner.d.ts +2 -0
- package/dist/local-executor/job-runner.js +37 -4
- package/dist/local-executor/types.d.ts +2 -0
- package/dist/lockfile/generator.js +46 -20
- package/dist/remote/config.d.ts +2 -0
- package/dist/remote/config.js +1 -0
- package/dist/remote/platform-client.d.ts +12 -1
- package/dist/remote/uploader.js +1 -0
- package/dist/templates/package-json.js +1 -1
- package/dist/test-runner/rule-evaluator.d.ts +1 -1
- package/dist/test-runner/rule-evaluator.js +2 -1
- package/dist/test-runner/step-context.d.ts +1 -1
- package/dist/test-runner/step-context.js +8 -2
- package/dist/types.d.ts +15 -6
- package/package.json +4 -4
- package/sbom.spdx.json +35 -35
|
@@ -4,6 +4,18 @@
|
|
|
4
4
|
|
|
5
5
|
The full markdown bundle of every page indexed here is available at https://docs.kici.dev/llms-full.txt.
|
|
6
6
|
|
|
7
|
+
## Bundles
|
|
8
|
+
|
|
9
|
+
Each bundle below is a self-contained markdown file for one authoring task. Fetch only the one your task needs instead of the full bundle:
|
|
10
|
+
|
|
11
|
+
- [getting-started](https://docs.kici.dev/llms-getting-started.txt) (25 KB) — Install the SDK, write your first workflow, compile and test locally
|
|
12
|
+
- [patterns](https://docs.kici.dev/llms-patterns.txt) (49 KB) — Copy-paste workflow recipes: triggers, conditionals, matrix, scheduling, integrations
|
|
13
|
+
- [sdk](https://docs.kici.dev/llms-sdk.txt) (185 KB) — Authoring API: workflow/job/step factories, triggers, rules, matrix, runtime, caching
|
|
14
|
+
- [cli](https://docs.kici.dev/llms-cli.txt) (117 KB) — Running the CLI: compile, test, run local/remote, auth, hooks, lock-file drift
|
|
15
|
+
- [features](https://docs.kici.dev/llms-features.txt) (116 KB) — Workflow features: concurrency, environments, secrets, approvals, provenance, events
|
|
16
|
+
- [providers](https://docs.kici.dev/llms-providers.txt) (36 KB) — Connecting sources: GitHub App, universal-git (Forgejo/Gitea/GitLab), local file://
|
|
17
|
+
- [architecture](https://docs.kici.dev/llms-architecture.txt) (88 KB) — How the runtime works: three-tier relay model, data flows, configuration
|
|
18
|
+
|
|
7
19
|
## Getting started
|
|
8
20
|
|
|
9
21
|
- [User guide](https://docs.kici.dev/user/): Writing and testing CI/CD workflows in TypeScript
|
|
@@ -14,6 +26,7 @@ The full markdown bundle of every page indexed here is available at https://docs
|
|
|
14
26
|
|
|
15
27
|
- [Basic workflow patterns](https://docs.kici.dev/user/patterns/basic/): Basic CI, PR-only / push-only filters, multiple triggers, manual-only workflows
|
|
16
28
|
- [Conditionals & matrix patterns](https://docs.kici.dev/user/patterns/conditionals-matrix/): Conditional execution with rules, matrix builds (static + dynamic), dynamic job generation
|
|
29
|
+
- [Host restart & wait-for-alive](https://docs.kici.dev/user/patterns/host-restart/): Reboot the host a workflow runs on and continue after it comes back
|
|
17
30
|
- [Integration patterns](https://docs.kici.dev/user/patterns/integrations/): Workflow chaining, generic webhooks, Stripe, self-hosted git forges, plain GitHub repos
|
|
18
31
|
- [Pattern reference](https://docs.kici.dev/user/patterns/reference/): Step context, examples repository, GitHub check run output — cross-cutting reference for all patterns
|
|
19
32
|
- [Scheduling & event patterns](https://docs.kici.dev/user/patterns/scheduling-and-events/): Nightly cron, workflow-complete-triggered deploys, custom event chaining
|
|
@@ -44,7 +57,7 @@ The full markdown bundle of every page indexed here is available at https://docs
|
|
|
44
57
|
## Workflow features
|
|
45
58
|
|
|
46
59
|
- [Account and sign-in](https://docs.kici.dev/user/account-and-login/): How your KiCI account relates to sign-in methods, and how to change the way you sign in.
|
|
47
|
-
- [Approval gates](https://docs.kici.dev/user/approvals/): Pause a workflow for human sign-off at step, job, or workflow granularity with
|
|
60
|
+
- [Approval gates](https://docs.kici.dev/user/approvals/): Pause a workflow for human sign-off at step, job, or workflow granularity with approval
|
|
48
61
|
- [Concurrency groups](https://docs.kici.dev/user/concurrency/): Control parallel execution with auto-cancel and queue modes
|
|
49
62
|
- [Dashboard](https://docs.kici.dev/user/dashboard/): Web UI for monitoring workflow runs, managing sources, secrets, and organization settings.
|
|
50
63
|
- [Dynamic values](https://docs.kici.dev/user/dynamic-values/)
|
|
@@ -65,5 +78,6 @@ The full markdown bundle of every page indexed here is available at https://docs
|
|
|
65
78
|
|
|
66
79
|
## Architecture overview
|
|
67
80
|
|
|
81
|
+
- [Configuration architecture](https://docs.kici.dev/architecture/configuration/): Config resolution chain, DB schema, encryption, hot-reload, cluster sync
|
|
68
82
|
- [Data flows](https://docs.kici.dev/architecture/data-flows/): End-to-end data flows through the KiCI three-tier architecture
|
|
69
83
|
- [Architecture overview](https://docs.kici.dev/architecture/overview/): Three-tier relay model, package structure, and component responsibilities
|
|
@@ -15,7 +15,7 @@ import path from "node:path";
|
|
|
15
15
|
import pc from "picocolors";
|
|
16
16
|
import { writeFile } from "node:fs/promises";
|
|
17
17
|
import { logger } from "@kici-dev/core";
|
|
18
|
-
import { CheckMode, CheckStepOutcome, matchAllWorkflows } from "@kici-dev/engine";
|
|
18
|
+
import { CheckMode, CheckStepOutcome, coerceDispatchInputs, matchAllWorkflows, parseInputPairs } from "@kici-dev/engine";
|
|
19
19
|
import os from "node:os";
|
|
20
20
|
//#region src/local-executor/index.ts
|
|
21
21
|
/**
|
|
@@ -136,8 +136,37 @@ async function runOneMatchedWorkflow(workflow, ctx) {
|
|
|
136
136
|
* The actual DAG-execution body for one workflow. Split out so
|
|
137
137
|
* {@link runOneMatchedWorkflow} can wrap it in a lock acquire/release.
|
|
138
138
|
*/
|
|
139
|
+
/**
|
|
140
|
+
* Resolve the dispatch-input descriptor declared on a workflow's `dispatch()`
|
|
141
|
+
* trigger(s) from the in-memory trigger objects, merged across triggers.
|
|
142
|
+
* Returns undefined when none declared.
|
|
143
|
+
*/
|
|
144
|
+
function dispatchInputsDescriptorForWorkflow(workflow) {
|
|
145
|
+
const merged = {};
|
|
146
|
+
let found = false;
|
|
147
|
+
for (const trigger of transformTriggers(workflow.on)) if (trigger._type === "dispatch" && trigger.inputs) {
|
|
148
|
+
Object.assign(merged, trigger.inputs);
|
|
149
|
+
found = true;
|
|
150
|
+
}
|
|
151
|
+
return found ? merged : void 0;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Coerce + default the operator's `--input` pairs against the workflow's
|
|
155
|
+
* dispatch descriptor for a local run. The local executor is authoritative for
|
|
156
|
+
* `run local` (no orchestrator), so defaults are applied here exactly once.
|
|
157
|
+
* Returns the resolved values, or throws when input is invalid.
|
|
158
|
+
*/
|
|
159
|
+
function resolveLocalDispatchInputs(workflow, options) {
|
|
160
|
+
const descriptor = dispatchInputsDescriptorForWorkflow(workflow);
|
|
161
|
+
const raw = parseInputPairs(options.inputs ?? []);
|
|
162
|
+
if (!descriptor) return raw;
|
|
163
|
+
const r = coerceDispatchInputs(raw, descriptor);
|
|
164
|
+
if ("error" in r) throw r.error;
|
|
165
|
+
return r.values;
|
|
166
|
+
}
|
|
139
167
|
async function runWorkflowBody(workflow, ctx, options, secrets, kiciDir, concurrency, failFast) {
|
|
140
168
|
const { event } = ctx;
|
|
169
|
+
const dispatchInputs = resolveLocalDispatchInputs(workflow, options);
|
|
141
170
|
const resolvedJobs = await resolveJobs(workflow, event);
|
|
142
171
|
let dagNodes = resolvedJobs.map((r) => ({
|
|
143
172
|
name: r.expandedName,
|
|
@@ -170,7 +199,8 @@ async function runWorkflowBody(workflow, ctx, options, secrets, kiciDir, concurr
|
|
|
170
199
|
execDir: ctx.execDir,
|
|
171
200
|
jobOutputsMap,
|
|
172
201
|
signal,
|
|
173
|
-
checkMode: options.checkMode
|
|
202
|
+
checkMode: options.checkMode,
|
|
203
|
+
dispatchInputs
|
|
174
204
|
});
|
|
175
205
|
},
|
|
176
206
|
isSuccess: (result) => result.status === "success" || result.status === "skipped"
|
|
@@ -223,7 +253,8 @@ async function executeLocal(options) {
|
|
|
223
253
|
if (!await compileCommand({
|
|
224
254
|
kiciDir,
|
|
225
255
|
check: false,
|
|
226
|
-
verbose: options.debug ?? false
|
|
256
|
+
verbose: options.debug ?? false,
|
|
257
|
+
quiet: Boolean(options.json || options.quiet)
|
|
227
258
|
})) {
|
|
228
259
|
process.exitCode = 2;
|
|
229
260
|
return false;
|
|
@@ -268,6 +299,12 @@ async function executeLocal(options) {
|
|
|
268
299
|
return true;
|
|
269
300
|
}
|
|
270
301
|
}
|
|
302
|
+
for (const workflow of matchedWorkflows) try {
|
|
303
|
+
resolveLocalDispatchInputs(workflow, options);
|
|
304
|
+
} catch (err) {
|
|
305
|
+
logger.error(pc.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
306
|
+
process.exit(2);
|
|
307
|
+
}
|
|
271
308
|
const isQuiet = Boolean(options.quiet || options.json);
|
|
272
309
|
const repoRoot = path.dirname(kiciDir);
|
|
273
310
|
let materialized = null;
|
|
@@ -27,6 +27,8 @@ export interface JobExecutionContext {
|
|
|
27
27
|
signal: AbortSignal;
|
|
28
28
|
/** Run mode for idempotent steps. Defaults to `apply` when unset. */
|
|
29
29
|
checkMode?: CheckMode;
|
|
30
|
+
/** Resolved (coerced + defaulted) workflow-dispatch inputs for `ctx.dispatchInputs`. */
|
|
31
|
+
dispatchInputs?: Readonly<Record<string, string | number | boolean | null>>;
|
|
30
32
|
}
|
|
31
33
|
/**
|
|
32
34
|
* Resolve all jobs in a workflow, expanding matrix jobs and evaluating dynamic jobs.
|
|
@@ -8,6 +8,7 @@ import { toEventPayload } from "./to-event-payload.js";
|
|
|
8
8
|
import { pathToFileURL } from "node:url";
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
import { applyIncludeExclude, expandMatrix, isDynamicJobFn, setJobOutputsMap, setStepOutputsMap, setStepRefMap } from "@kici-dev/sdk";
|
|
11
|
+
import { computeBackoffDelay } from "@kici-dev/core";
|
|
11
12
|
import { CheckMode, CheckStepOutcome, formatMatrixSuffix } from "@kici-dev/engine";
|
|
12
13
|
import { runIdempotentStep } from "@kici-dev/core/idempotency";
|
|
13
14
|
//#region src/local-executor/job-runner.ts
|
|
@@ -91,6 +92,33 @@ async function runLocalStepWithCheckMode(step, ctx, checkMode) {
|
|
|
91
92
|
};
|
|
92
93
|
}
|
|
93
94
|
/**
|
|
95
|
+
* Run a local step through its retry policy. An attempt is one full
|
|
96
|
+
* `runLocalStepWithCheckMode` call (which throws on failure). A thrown attempt
|
|
97
|
+
* is retried while attempts remain AND `retryIf(err)` is true; backoff sleeps
|
|
98
|
+
* between attempts. Mirrors the agent step loop so `kici run local` retries
|
|
99
|
+
* identically to a remote run.
|
|
100
|
+
*/
|
|
101
|
+
async function runLocalStepWithRetry(step, ctx, checkMode, log) {
|
|
102
|
+
const retry = step.retry;
|
|
103
|
+
const max = retry?.maxAttempts ?? 1;
|
|
104
|
+
let lastErr;
|
|
105
|
+
for (let n = 1; n <= max; n++) try {
|
|
106
|
+
return await runLocalStepWithCheckMode(step, ctx, checkMode);
|
|
107
|
+
} catch (err) {
|
|
108
|
+
lastErr = err;
|
|
109
|
+
if (!(n < max && (retry?.retryIf?.(err) ?? true))) break;
|
|
110
|
+
const delay = computeBackoffDelay(n, {
|
|
111
|
+
maxAttempts: max,
|
|
112
|
+
delayMs: retry.delayMs,
|
|
113
|
+
backoff: retry.backoff,
|
|
114
|
+
maxDelayMs: retry.maxDelayMs
|
|
115
|
+
});
|
|
116
|
+
log(`Step '${step.name}' attempt ${n}/${max} failed: ${err instanceof Error ? err.message : String(err)}; retrying in ${delay}ms`);
|
|
117
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
118
|
+
}
|
|
119
|
+
throw lastErr;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
94
122
|
* Maintain the base-name `{ byMatrix, merged }` envelope as each matrix child
|
|
95
123
|
* completes. `merged` is rebuilt last-write-wins in suffix order so the result
|
|
96
124
|
* is deterministic regardless of child completion order — matching the remote
|
|
@@ -145,7 +173,12 @@ async function resolveJobs(workflow, event) {
|
|
|
145
173
|
query: () => Promise.resolve([]),
|
|
146
174
|
get: () => Promise.resolve(null)
|
|
147
175
|
},
|
|
148
|
-
oidc: { token: () => Promise.reject(/* @__PURE__ */ new Error("ctx.kici.oidc.token() is not available during local execution")) }
|
|
176
|
+
oidc: { token: () => Promise.reject(/* @__PURE__ */ new Error("ctx.kici.oidc.token() is not available during local execution")) },
|
|
177
|
+
host: { requestReboot: () => Promise.reject(/* @__PURE__ */ new Error("ctx.kici.host.requestReboot() is not available during local execution")) },
|
|
178
|
+
bootstrap: {
|
|
179
|
+
ensureInitRunner: () => Promise.reject(/* @__PURE__ */ new Error("ctx.kici.bootstrap.ensureInitRunner() is not available during local execution")),
|
|
180
|
+
preBootSend: () => Promise.reject(/* @__PURE__ */ new Error("ctx.kici.bootstrap.preBootSend() is not available during local execution"))
|
|
181
|
+
}
|
|
149
182
|
}
|
|
150
183
|
});
|
|
151
184
|
for (const genJob of generatedJobs) {
|
|
@@ -256,7 +289,7 @@ async function executeResolvedJobInner(resolvedJob, context, startTime) {
|
|
|
256
289
|
const sdkSetters = await resolveSdkSetters(context.kiciDir);
|
|
257
290
|
let ruleResults;
|
|
258
291
|
if (job.rules && job.rules.length > 0) {
|
|
259
|
-
const ruleContext = createRuleContext(context.event, context.event.changedFiles);
|
|
292
|
+
const ruleContext = createRuleContext(context.event, context.event.changedFiles, context.dispatchInputs ?? {});
|
|
260
293
|
const ruleEval = await evaluateRulesWithFormatting(job.rules, ruleContext, expandedName);
|
|
261
294
|
ruleResults = ruleEval.results;
|
|
262
295
|
if (!ruleEval.allPassed) return {
|
|
@@ -281,7 +314,7 @@ async function executeResolvedJobInner(resolvedJob, context, startTime) {
|
|
|
281
314
|
const stepCtx = createStepContext({ name: context.workflowName }, {
|
|
282
315
|
name: expandedName,
|
|
283
316
|
runsOn: localRunsOnString(job.runsOn)
|
|
284
|
-
}, repoRoot, void 0, hasMatrix ? matrixValues : void 0, context.secrets, void 0, context.event.payload, context.event.provider);
|
|
317
|
+
}, repoRoot, void 0, hasMatrix ? matrixValues : void 0, context.secrets, void 0, context.event.payload, context.event.provider, context.dispatchInputs ?? {});
|
|
285
318
|
const stepResults = [];
|
|
286
319
|
let stepCounter = 0;
|
|
287
320
|
for (const stepOrFn of job.steps) {
|
|
@@ -312,7 +345,7 @@ async function executeResolvedJobInner(resolvedJob, context, startTime) {
|
|
|
312
345
|
const stepStart = Date.now();
|
|
313
346
|
try {
|
|
314
347
|
const checkMode = context.checkMode ?? CheckMode.enum.apply;
|
|
315
|
-
const phase = await
|
|
348
|
+
const phase = await runLocalStepWithRetry(normalizedStep, stepCtx, checkMode, (line) => formatter.logJobLine(expandedName, line));
|
|
316
349
|
const outputs = phase.outputs;
|
|
317
350
|
const stepDuration = Date.now() - stepStart;
|
|
318
351
|
formatter.logStepComplete(expandedName, normalizedStep.name, stepDuration);
|
|
@@ -27,6 +27,8 @@ export interface RunLocalOptions {
|
|
|
27
27
|
container?: boolean;
|
|
28
28
|
/** --env KEY=VALUE overrides (repeatable) */
|
|
29
29
|
env?: string[];
|
|
30
|
+
/** --input KEY=VALUE typed workflow-dispatch inputs (repeatable) */
|
|
31
|
+
inputs?: string[];
|
|
30
32
|
/** --quiet: minimal output */
|
|
31
33
|
quiet?: boolean;
|
|
32
34
|
/** --json: JSON output format */
|
|
@@ -5,11 +5,11 @@ import { resolveHashFiles } from "./hash-files.js";
|
|
|
5
5
|
import { analyzePurity } from "./purity-analyzer.js";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { readFileSync } from "node:fs";
|
|
8
|
-
import { getDynamicJobGroup, getDynamicJobNeeds, isDynamicFunction, isDynamicGroupRef, isDynamicJobFn, isStaticArray, isStaticObject,
|
|
8
|
+
import { getDynamicJobGroup, getDynamicJobNeeds, isDynamicFunction, isDynamicGroupRef, isDynamicJobFn, isStaticArray, isStaticObject, normalizeApproval, normalizeCacheSpecs } from "@kici-dev/sdk";
|
|
9
9
|
import { sha256 } from "@kici-dev/core";
|
|
10
10
|
import { PackageManager, detectPackageManagerSync, detectYarnFlavorSync } from "@kici-dev/core/package-manager";
|
|
11
|
-
import { validateResourceRequest } from "@kici-dev/engine";
|
|
12
|
-
import { normalizeRunsOnAllToMatchers, normalizeRunsOnToMatchers } from "@kici-dev/engine/labels/compile";
|
|
11
|
+
import { extractInputsDescriptorMap, resolveWhenToRunOn, validateResourceRequest } from "@kici-dev/engine";
|
|
12
|
+
import { normalizeRunsOnAllToMatchers, normalizeRunsOnToMatchers, runsOnPickFromInput } from "@kici-dev/engine/labels/compile";
|
|
13
13
|
import { execSync } from "node:child_process";
|
|
14
14
|
//#region src/lockfile/generator.ts
|
|
15
15
|
/**
|
|
@@ -158,7 +158,7 @@ function transformWorkflow(workflow, sourceFile, exportRef, bundleSource, gitRoo
|
|
|
158
158
|
...workflow.concurrency.max !== void 0 && { max: workflow.concurrency.max }
|
|
159
159
|
} },
|
|
160
160
|
...workflow.timeout !== void 0 && { timeout: workflow.timeout },
|
|
161
|
-
...workflow.
|
|
161
|
+
...workflow.approval !== void 0 && { approval: (assertNonStepApprovalScope(workflow.approval, "workflow"), toLockApproval(workflow.approval)) }
|
|
162
162
|
};
|
|
163
163
|
}
|
|
164
164
|
/**
|
|
@@ -234,7 +234,8 @@ function toLockDispatch(t) {
|
|
|
234
234
|
return {
|
|
235
235
|
_type: "dispatch",
|
|
236
236
|
types: t.types,
|
|
237
|
-
...reposField(t)
|
|
237
|
+
...reposField(t),
|
|
238
|
+
...t.inputs && { inputs: extractInputsDescriptorMap(t.inputs) }
|
|
238
239
|
};
|
|
239
240
|
}
|
|
240
241
|
function toLockCreate(t) {
|
|
@@ -458,7 +459,8 @@ function normalizeRunsOnForLock(runsOn, jobName) {
|
|
|
458
459
|
const { include, exclude } = normalizeRunsOnToMatchers(runsOn, `job '${jobName}' runsOn`);
|
|
459
460
|
return {
|
|
460
461
|
runsOn: include,
|
|
461
|
-
...exclude.length > 0 ? { excludeLabels: exclude } : {}
|
|
462
|
+
...exclude.length > 0 ? { excludeLabels: exclude } : {},
|
|
463
|
+
runsOnPick: runsOnPickFromInput(runsOn)
|
|
462
464
|
};
|
|
463
465
|
}
|
|
464
466
|
/**
|
|
@@ -540,6 +542,7 @@ function transformJob(job, configPath, index, gitRoot, uuidToName) {
|
|
|
540
542
|
...job.runsOn !== void 0 ? normalizeRunsOnForLock(job.runsOn, job.name) : {},
|
|
541
543
|
...job.runsOnAll !== void 0 && { runsOnAll: normalizeRunsOnAllToMatchers(job.runsOnAll, `job '${job.name}' runsOnAll`) },
|
|
542
544
|
...job.onUnreachable !== void 0 && { onUnreachable: job.onUnreachable },
|
|
545
|
+
...job.includeUninitialized !== void 0 && { includeUninitialized: job.includeUninitialized },
|
|
543
546
|
...job.maxParallel !== void 0 && { maxParallel: job.maxParallel },
|
|
544
547
|
...job.failFast !== void 0 && { failFast: job.failFast },
|
|
545
548
|
...resolveNeedsForLock(job.needs, uuidToName),
|
|
@@ -565,12 +568,13 @@ function transformJob(job, configPath, index, gitRoot, uuidToName) {
|
|
|
565
568
|
...job.timeout !== void 0 && { timeout: job.timeout },
|
|
566
569
|
...job.resources !== void 0 && { resources: job.resources },
|
|
567
570
|
...job.init !== void 0 && { init: job.init },
|
|
568
|
-
...job.
|
|
571
|
+
...job.approval !== void 0 && { approval: (assertNonStepApprovalScope(job.approval, "job"), toLockApproval(job.approval)) }
|
|
569
572
|
};
|
|
570
573
|
}
|
|
571
574
|
/**
|
|
572
575
|
* Resolve needs to lock file format.
|
|
573
|
-
* Handles strings, Job objects, DynamicGroupRef, and object forms with
|
|
576
|
+
* Handles strings, Job objects, DynamicGroupRef, and object forms with a `when`
|
|
577
|
+
* run condition (normalized to a `runOn` status-set via the engine helper).
|
|
574
578
|
* Uses the UUID-to-renamed-name mapping so that references to id-less jobs
|
|
575
579
|
* resolve to their lock file names (job-N) instead of the original UUIDs.
|
|
576
580
|
*/
|
|
@@ -582,26 +586,26 @@ function resolveNeedsForLock(needs, uuidToName) {
|
|
|
582
586
|
else if (isDynamicGroupRef(need)) {
|
|
583
587
|
resolvedNeeds.push({
|
|
584
588
|
group: need.group,
|
|
585
|
-
|
|
589
|
+
runOn: resolveWhenToRunOn(need.when)
|
|
586
590
|
});
|
|
587
591
|
groups.push(need.group);
|
|
592
|
+
} else if ("_tag" in need && need._tag === "Job") {
|
|
593
|
+
const name = need.name;
|
|
594
|
+
resolvedNeeds.push(uuidToName?.get(name) ?? name);
|
|
588
595
|
} else if ("group" in need && typeof need.group === "string") {
|
|
589
596
|
const g = need;
|
|
590
597
|
resolvedNeeds.push({
|
|
591
598
|
group: g.group,
|
|
592
|
-
|
|
599
|
+
runOn: resolveWhenToRunOn(g.when)
|
|
593
600
|
});
|
|
594
601
|
groups.push(g.group);
|
|
595
|
-
} else
|
|
602
|
+
} else {
|
|
596
603
|
const n = need;
|
|
597
604
|
const name = uuidToName?.get(n.name) ?? n.name;
|
|
598
605
|
resolvedNeeds.push({
|
|
599
606
|
name,
|
|
600
|
-
|
|
607
|
+
runOn: resolveWhenToRunOn(n.when)
|
|
601
608
|
});
|
|
602
|
-
} else {
|
|
603
|
-
const name = need.name;
|
|
604
|
-
resolvedNeeds.push(uuidToName?.get(name) ?? name);
|
|
605
609
|
}
|
|
606
610
|
return {
|
|
607
611
|
needs: resolvedNeeds,
|
|
@@ -613,15 +617,31 @@ function resolveNeedsForLock(needs, uuidToName) {
|
|
|
613
617
|
* Assigns counter-based IDs to unnamed steps (bare functions and id-less steps).
|
|
614
618
|
* Counter only increments for unnamed entries; named steps keep their names.
|
|
615
619
|
*/
|
|
616
|
-
/** Map an SDK `
|
|
617
|
-
function toLockApproval(
|
|
618
|
-
const n =
|
|
620
|
+
/** Map an SDK `approval` to the normalized lock `approval` block. */
|
|
621
|
+
function toLockApproval(c) {
|
|
622
|
+
const n = normalizeApproval(c);
|
|
619
623
|
return {
|
|
620
624
|
clauses: n.clauses,
|
|
621
625
|
...n.reason !== void 0 && { reason: n.reason },
|
|
622
|
-
...n.timeoutSeconds !== void 0 && { timeoutSeconds: n.timeoutSeconds }
|
|
626
|
+
...n.timeoutSeconds !== void 0 && { timeoutSeconds: n.timeoutSeconds },
|
|
627
|
+
when: n.when
|
|
623
628
|
};
|
|
624
629
|
}
|
|
630
|
+
/**
|
|
631
|
+
* Validate an approval config at job/workflow scope: `when: 'drift'` is a
|
|
632
|
+
* step-scope-only gate (it fires between a step's check and run), so it is a
|
|
633
|
+
* compile error anywhere else.
|
|
634
|
+
*/
|
|
635
|
+
function assertNonStepApprovalScope(c, scope) {
|
|
636
|
+
if (normalizeApproval(c).when === "drift") throw new Error(`approval.when "drift" is only valid on steps (found at ${scope} scope)`);
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Validate a step's approval config: `when: 'drift'` fires between the step's
|
|
640
|
+
* check and run, so it requires a `check` facet. A compile error otherwise.
|
|
641
|
+
*/
|
|
642
|
+
function assertStepApprovalCheckFacet(step) {
|
|
643
|
+
if (step.approval !== void 0 && normalizeApproval(step.approval).when === "drift" && step.check === void 0) throw new Error(`step '${step.name || "(unnamed)"}': approval.when "drift" requires a check facet`);
|
|
644
|
+
}
|
|
625
645
|
function transformSteps(steps, gitRoot) {
|
|
626
646
|
let stepCounter = 0;
|
|
627
647
|
return steps.map((stepOrFn) => {
|
|
@@ -638,6 +658,12 @@ function transformSteps(steps, gitRoot) {
|
|
|
638
658
|
hasOutputs: !!step.outputs && Object.keys(step.outputs).length > 0,
|
|
639
659
|
...step.continueOnError !== void 0 && { continueOnError: step.continueOnError },
|
|
640
660
|
...step.timeout !== void 0 && { timeout: step.timeout },
|
|
661
|
+
...step.retry !== void 0 && { retry: {
|
|
662
|
+
maxAttempts: step.retry.maxAttempts,
|
|
663
|
+
delayMs: step.retry.delayMs,
|
|
664
|
+
backoff: step.retry.backoff,
|
|
665
|
+
maxDelayMs: step.retry.maxDelayMs
|
|
666
|
+
} },
|
|
641
667
|
...step.cache !== void 0 && { cache: normalizeCacheSpecs(step.cache) },
|
|
642
668
|
...step._sourceLocation && { sourceLocation: {
|
|
643
669
|
file: makeRelativePath(step._sourceLocation.file, gitRoot),
|
|
@@ -652,7 +678,7 @@ function transformSteps(steps, gitRoot) {
|
|
|
652
678
|
...step.cleanup !== void 0 && { hasCleanup: true },
|
|
653
679
|
...step.check !== void 0 && { hasCheck: true },
|
|
654
680
|
...step.whenInSync !== void 0 && { hasWhenInSync: true },
|
|
655
|
-
...step.
|
|
681
|
+
...step.approval !== void 0 && { approval: (assertStepApprovalCheckFacet(step), toLockApproval(step.approval)) }
|
|
656
682
|
};
|
|
657
683
|
});
|
|
658
684
|
}
|
package/dist/remote/config.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export interface GlobalConfig {
|
|
|
9
9
|
endpoint?: string;
|
|
10
10
|
/** Platform relay URL */
|
|
11
11
|
platformEndpoint?: string;
|
|
12
|
+
/** OIDC issuer URL the PAT was minted against (provenance; from OAuth login) */
|
|
13
|
+
oidcIssuer?: string;
|
|
12
14
|
/** Routing key for webhook source identification (e.g., 'github:42') */
|
|
13
15
|
routingKey?: string;
|
|
14
16
|
/** Personal access token (from OAuth login) */
|
package/dist/remote/config.js
CHANGED
|
@@ -14,6 +14,7 @@ function sanitizeConfig(raw) {
|
|
|
14
14
|
if (typeof obj.token === "string") config.token = obj.token;
|
|
15
15
|
if (typeof obj.endpoint === "string") config.endpoint = obj.endpoint;
|
|
16
16
|
if (typeof obj.platformEndpoint === "string") config.platformEndpoint = obj.platformEndpoint;
|
|
17
|
+
if (typeof obj.oidcIssuer === "string") config.oidcIssuer = obj.oidcIssuer;
|
|
17
18
|
if (typeof obj.routingKey === "string") config.routingKey = obj.routingKey;
|
|
18
19
|
if (typeof obj.pat === "string") config.pat = obj.pat;
|
|
19
20
|
if (typeof obj.patId === "string") config.patId = obj.patId;
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* The overlay tarball does NOT flow through here: `initUpload` returns an
|
|
11
11
|
* external presigned URL the CLI PUTs to directly (data plane).
|
|
12
12
|
*/
|
|
13
|
-
import type { CheckMode } from '@kici-dev/engine';
|
|
13
|
+
import type { CheckMode, HostTargetSelector } from '@kici-dev/engine';
|
|
14
14
|
export declare class AuthenticationError extends Error {
|
|
15
15
|
constructor(message?: string);
|
|
16
16
|
}
|
|
@@ -77,6 +77,17 @@ export interface PlatformTriggerInput {
|
|
|
77
77
|
* agent step loop in the requested mode. Omitted means `apply`.
|
|
78
78
|
*/
|
|
79
79
|
checkMode?: CheckMode;
|
|
80
|
+
/**
|
|
81
|
+
* Host narrowing from `kici run --target`. The Platform relays it verbatim to
|
|
82
|
+
* the orchestrator, which intersects each runsOnAll roster with it.
|
|
83
|
+
*/
|
|
84
|
+
target?: HostTargetSelector;
|
|
85
|
+
/**
|
|
86
|
+
* Raw operator-supplied `kici run --input KEY=VALUE` pairs (not defaulted /
|
|
87
|
+
* coerced). The Platform relays them verbatim; the orchestrator validates,
|
|
88
|
+
* coerces, and applies defaults authoritatively against the lock descriptor.
|
|
89
|
+
*/
|
|
90
|
+
dispatchInputs?: Record<string, string>;
|
|
80
91
|
}
|
|
81
92
|
export interface PlatformTriggerResponse {
|
|
82
93
|
runId: string;
|
package/dist/remote/uploader.js
CHANGED
|
@@ -232,6 +232,7 @@ function getSizeWarning(compressedSize) {
|
|
|
232
232
|
*/
|
|
233
233
|
async function uploadTarball(opts) {
|
|
234
234
|
const { tarballPath, signedUrl, orchestratorPublicKey, onProgress } = opts;
|
|
235
|
+
if (!signedUrl) throw new Error("The orchestrator did not return an upload URL, so the overlay cannot be uploaded. This usually means the orchestrator has no object storage configured for remote runs. Ask your orchestrator operator to enable cache storage (KICI_STORAGE_TYPE=s3 or filesystem).");
|
|
235
236
|
const { encryptedPath, cliPublicKey } = await encryptTarball(tarballPath, orchestratorPublicKey);
|
|
236
237
|
const encryptedData = await fs.readFile(encryptedPath);
|
|
237
238
|
const encryptedSize = encryptedData.length;
|
|
@@ -3,7 +3,7 @@ import type { RuleEvaluationResult } from '@kici-dev/sdk';
|
|
|
3
3
|
/**
|
|
4
4
|
* Create RuleContext for rule evaluation.
|
|
5
5
|
*/
|
|
6
|
-
export declare function createRuleContext(event: EventPayload, changedFiles?: string[]): RuleContext;
|
|
6
|
+
export declare function createRuleContext(event: EventPayload, changedFiles?: string[], dispatchInputs?: Readonly<Record<string, string | number | boolean | null>>): RuleContext;
|
|
7
7
|
/**
|
|
8
8
|
* Evaluate rules with formatting output.
|
|
9
9
|
* Wraps the SDK's evaluateRules() with a callback that logs each rule result.
|
|
@@ -8,11 +8,12 @@ initZx();
|
|
|
8
8
|
/**
|
|
9
9
|
* Create RuleContext for rule evaluation.
|
|
10
10
|
*/
|
|
11
|
-
function createRuleContext(event, changedFiles = []) {
|
|
11
|
+
function createRuleContext(event, changedFiles = [], dispatchInputs = {}) {
|
|
12
12
|
return {
|
|
13
13
|
event,
|
|
14
14
|
changedFiles,
|
|
15
15
|
env: { ...process.env },
|
|
16
|
+
dispatchInputs,
|
|
16
17
|
$
|
|
17
18
|
};
|
|
18
19
|
}
|
|
@@ -11,5 +11,5 @@ import type { StepContext, WorkflowInfo, JobInfo, MatrixValues } from '@kici-dev
|
|
|
11
11
|
export declare function createStepContext(workflowInfo: WorkflowInfo, jobInfo: JobInfo, repoRoot: string, inputs?: Record<string, unknown>, matrix?: MatrixValues, testSecrets?: {
|
|
12
12
|
flat: Record<string, string>;
|
|
13
13
|
contexts: Record<string, Record<string, string>>;
|
|
14
|
-
}, environment?: string, rawPayload?: Record<string, unknown>, provider?: string): StepContext;
|
|
14
|
+
}, environment?: string, rawPayload?: Record<string, unknown>, provider?: string, dispatchInputs?: Readonly<Record<string, string | number | boolean | null>>): StepContext;
|
|
15
15
|
//# sourceMappingURL=step-context.d.ts.map
|
|
@@ -42,7 +42,7 @@ function createTestLogger(jobName) {
|
|
|
42
42
|
* `ctx.$` would inherit `process.cwd()` — i.e. wherever the user invoked
|
|
43
43
|
* `kici` — which silently breaks any step that uses relative paths.
|
|
44
44
|
*/
|
|
45
|
-
function createStepContext(workflowInfo, jobInfo, repoRoot, inputs = {}, matrix, testSecrets, environment, rawPayload, provider) {
|
|
45
|
+
function createStepContext(workflowInfo, jobInfo, repoRoot, inputs = {}, matrix, testSecrets, environment, rawPayload, provider, dispatchInputs = {}) {
|
|
46
46
|
const flat = testSecrets?.flat ?? {};
|
|
47
47
|
const namespacedSecrets = testSecrets?.contexts ?? {};
|
|
48
48
|
const mergedFlat = { ...flat };
|
|
@@ -110,6 +110,7 @@ function createStepContext(workflowInfo, jobInfo, repoRoot, inputs = {}, matrix,
|
|
|
110
110
|
process.env.PATH = updated;
|
|
111
111
|
},
|
|
112
112
|
inputs,
|
|
113
|
+
dispatchInputs,
|
|
113
114
|
workflow: workflowInfo,
|
|
114
115
|
job: jobInfo,
|
|
115
116
|
matrix,
|
|
@@ -137,7 +138,12 @@ function createStepContext(workflowInfo, jobInfo, repoRoot, inputs = {}, matrix,
|
|
|
137
138
|
query: () => Promise.resolve([]),
|
|
138
139
|
get: () => Promise.resolve(null)
|
|
139
140
|
},
|
|
140
|
-
oidc: { token: () => Promise.reject(/* @__PURE__ */ new Error("ctx.kici.oidc.token() is not available in the local test runner")) }
|
|
141
|
+
oidc: { token: () => Promise.reject(/* @__PURE__ */ new Error("ctx.kici.oidc.token() is not available in the local test runner")) },
|
|
142
|
+
host: { requestReboot: () => Promise.reject(/* @__PURE__ */ new Error("ctx.kici.host.requestReboot() is not available in the local test runner")) },
|
|
143
|
+
bootstrap: {
|
|
144
|
+
ensureInitRunner: () => Promise.reject(/* @__PURE__ */ new Error("ctx.kici.bootstrap.ensureInitRunner() is not available in the local test runner")),
|
|
145
|
+
preBootSend: () => Promise.reject(/* @__PURE__ */ new Error("ctx.kici.bootstrap.preBootSend() is not available in the local test runner"))
|
|
146
|
+
}
|
|
141
147
|
},
|
|
142
148
|
cache: {
|
|
143
149
|
restore: async () => ({ hit: false }),
|
package/dist/types.d.ts
CHANGED
|
@@ -12,18 +12,23 @@
|
|
|
12
12
|
* v15 adds per-job init config(s).
|
|
13
13
|
* v17 widens per-job init to typed presets ('mise' / { mise }) and 'auto' detection.
|
|
14
14
|
*/
|
|
15
|
-
import type { ResourceRequest, ApproverClause, RunsOnAllPredicate, OnUnreachableMode, LabelMatcher } from '@kici-dev/engine';
|
|
15
|
+
import type { ResourceRequest, ApproverClause, RunsOnAllPredicate, OnUnreachableMode, LabelMatcher, ExecutionJobStatus, InputsDescriptorMap } from '@kici-dev/engine';
|
|
16
16
|
/**
|
|
17
17
|
* Normalized approval config carried in the lock file. Mirrors the engine
|
|
18
|
-
* `LockApproval` type. Produced by the compiler from an SDK `
|
|
18
|
+
* `LockApproval` type. Produced by the compiler from an SDK `approval` config.
|
|
19
19
|
*/
|
|
20
20
|
export interface LockApproval {
|
|
21
21
|
readonly clauses: ApproverClause[];
|
|
22
22
|
readonly reason?: string;
|
|
23
23
|
readonly timeoutSeconds?: number;
|
|
24
|
+
/**
|
|
25
|
+
* When the gate fires. `always` (default) gates before the element; `drift`
|
|
26
|
+
* gates between a step's check and run on detected drift (step scope only).
|
|
27
|
+
*/
|
|
28
|
+
readonly when: 'always' | 'drift';
|
|
24
29
|
}
|
|
25
30
|
/** Schema version - re-exported from engine as single source of truth */
|
|
26
|
-
export declare const SCHEMA_VERSION:
|
|
31
|
+
export declare const SCHEMA_VERSION: 26;
|
|
27
32
|
/**
|
|
28
33
|
* Source file reference with meaningful path.
|
|
29
34
|
* Format: file is relative path from git root, export uses hash syntax.
|
|
@@ -122,6 +127,8 @@ export interface LockDispatchTrigger {
|
|
|
122
127
|
readonly _type: 'dispatch';
|
|
123
128
|
readonly types: readonly string[];
|
|
124
129
|
readonly repos?: readonly LockBranchPattern[];
|
|
130
|
+
/** Typed dispatch-input descriptors (from `dispatch({ inputs })`). */
|
|
131
|
+
readonly inputs?: InputsDescriptorMap;
|
|
125
132
|
}
|
|
126
133
|
/**
|
|
127
134
|
* Create trigger in lock file.
|
|
@@ -367,15 +374,15 @@ export declare function isLockInlineValue(value: unknown): value is LockInlineVa
|
|
|
367
374
|
* (e.g., `kici:role:builder`, `kici:role:init-runner`) are injected by the orchestrator
|
|
368
375
|
* for internal job types (build/init) and are not user-settable.
|
|
369
376
|
*/
|
|
370
|
-
/** Needs entry with per-edge
|
|
377
|
+
/** Needs entry with per-edge run-on status-set (mirrors engine NeedsEntry). */
|
|
371
378
|
export interface LockNeedsEntry {
|
|
372
379
|
readonly name: string;
|
|
373
|
-
readonly
|
|
380
|
+
readonly runOn: ExecutionJobStatus[];
|
|
374
381
|
}
|
|
375
382
|
/** Needs group entry for dynamic group dependencies (mirrors engine NeedsGroupEntry). */
|
|
376
383
|
export interface LockNeedsGroupEntry {
|
|
377
384
|
readonly group: string;
|
|
378
|
-
readonly
|
|
385
|
+
readonly runOn: ExecutionJobStatus[];
|
|
379
386
|
}
|
|
380
387
|
export interface LockJob {
|
|
381
388
|
readonly _type: 'static';
|
|
@@ -387,6 +394,8 @@ export interface LockJob {
|
|
|
387
394
|
readonly runsOnAll?: RunsOnAllPredicate;
|
|
388
395
|
/** Failure policy for unreachable durable hosts in a runsOnAll fan-out. */
|
|
389
396
|
readonly onUnreachable?: OnUnreachableMode;
|
|
397
|
+
/** Widen runsOnAll to declared-but-un-agented hosts (init-runner bring-up per fresh box). */
|
|
398
|
+
readonly includeUninitialized?: boolean;
|
|
390
399
|
/** Fan-out concurrency width (sliding window; 1 = serial). Applies to matrix and runsOnAll. */
|
|
391
400
|
readonly maxParallel?: number;
|
|
392
401
|
/** Halt the fan-out on first child failure, skipping the held remainder. Default false. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kici-dev/compiler",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.23",
|
|
4
4
|
"description": "Compiler and CLI for KiCI workflows. Compiles `.kici/workflows/*.ts` to a `kici.lock.json` file consumed by the orchestrator and agents, and runs workflows locally or against a remote orchestrator.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ci",
|
|
@@ -63,11 +63,11 @@
|
|
|
63
63
|
"yaml": "^2.9.0",
|
|
64
64
|
"zod": "^4.4.3",
|
|
65
65
|
"zx": "^8.8.5",
|
|
66
|
-
"@kici-dev/core": "0.1.
|
|
67
|
-
"@kici-dev/engine": "0.1.
|
|
66
|
+
"@kici-dev/core": "0.1.23",
|
|
67
|
+
"@kici-dev/engine": "0.1.23"
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
70
|
-
"@kici-dev/sdk": "0.1.
|
|
70
|
+
"@kici-dev/sdk": "0.1.23"
|
|
71
71
|
},
|
|
72
72
|
"devDependencies": {
|
|
73
73
|
"@types/proper-lockfile": "^4.1.4"
|