@kici-dev/sdk 0.5.0 → 0.6.1
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/agent-cloud-init.d.ts +66 -0
- package/dist/agent-cloud-init.js +128 -0
- package/dist/api-types.d.ts +26 -0
- package/dist/api-types.js +20 -1
- package/dist/context.d.ts +21 -0
- package/dist/filter.test-d.d.ts +2 -0
- package/dist/filter.test-d.js +30 -0
- package/dist/git-types.d.ts +162 -0
- package/dist/git-types.js +32 -0
- package/dist/idempotent.js +3 -2
- package/dist/index.d.ts +8 -1
- package/dist/index.js +8 -4
- package/dist/invoke.d.ts +29 -0
- package/dist/invoke.js +27 -0
- package/dist/job-outputs.test-d.js +32 -18
- package/dist/job.js +93 -1
- package/dist/needs-context.d.ts +31 -2
- package/dist/needs-context.js +3 -1
- package/dist/testing/step-context.js +3 -1
- package/dist/types.d.ts +72 -2
- package/package.json +4 -3
- package/sbom.spdx.json +40 -35
package/dist/invoke.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import "./rolldown-runtime-ClRpJifh.js";
|
|
2
|
+
import { reservedEventNamePrefix } from "@kici-dev/engine";
|
|
3
|
+
//#region src/invoke.ts
|
|
4
|
+
/**
|
|
5
|
+
* Invoke the source repo's opt-in workflows and gate on them.
|
|
6
|
+
*
|
|
7
|
+
* Targets exactly `ctx.sourceRepo`, so a global workflow can hand control back to
|
|
8
|
+
* the repo whose event triggered it without any org-wide fan-out. Subscribers opt
|
|
9
|
+
* in with `kiciEvent({ name })`. Required by default: if no workflow subscribes,
|
|
10
|
+
* the gate fails unless `optional: true` is set.
|
|
11
|
+
*/
|
|
12
|
+
function invokeSource(event, opts) {
|
|
13
|
+
if (typeof event !== "string" || event.trim().length === 0) throw new Error("invokeSource: event name must be a non-empty string");
|
|
14
|
+
const reservedPrefix = reservedEventNamePrefix(event);
|
|
15
|
+
if (reservedPrefix) throw new Error(`invokeSource: event name prefix "${reservedPrefix}" is reserved for KiCI internal events (got "${event}")`);
|
|
16
|
+
return Object.freeze({
|
|
17
|
+
_tag: "InvokeSource",
|
|
18
|
+
event,
|
|
19
|
+
scope: "source",
|
|
20
|
+
...opts?.payload !== void 0 && { payload: Object.freeze({ ...opts.payload }) },
|
|
21
|
+
...opts?.optional === true && { optional: true }
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
export { invokeSource };
|
|
26
|
+
|
|
27
|
+
//# sourceMappingURL=invoke.js.map
|
|
@@ -1,30 +1,34 @@
|
|
|
1
1
|
import "./rolldown-runtime-ClRpJifh.js";
|
|
2
2
|
import { isHostJobOutputs, isMatrixJobOutputs } from "./context.js";
|
|
3
3
|
import { dynamicGroup } from "./dynamic-group.js";
|
|
4
|
-
import { step } from "./step.js";
|
|
5
|
-
import { job } from "./job.js";
|
|
6
4
|
import { workflow } from "./workflow.js";
|
|
5
|
+
import { job } from "./job.js";
|
|
6
|
+
import { step } from "./step.js";
|
|
7
7
|
import { describe, expectTypeOf, it } from "vitest";
|
|
8
8
|
//#region src/job-outputs.test-d.ts
|
|
9
9
|
describe("Job<TOutputs> — merged-steps inference", () => {
|
|
10
10
|
it("infers a nested output map keyed by step name for a multi-step job", () => {
|
|
11
|
+
const build = step("build", { run: async () => ({ version: "1.0" }) });
|
|
12
|
+
const test_ = step("test", { run: async () => ({ passed: true }) });
|
|
11
13
|
const j = job("ci", {
|
|
12
14
|
runsOn: "kici:os:linux",
|
|
13
|
-
steps: [
|
|
15
|
+
steps: [build, test_]
|
|
14
16
|
});
|
|
15
17
|
expectTypeOf(j.result.build.version).toEqualTypeOf();
|
|
16
18
|
expectTypeOf(j.result.test.passed).toEqualTypeOf();
|
|
17
19
|
});
|
|
18
20
|
it("rejects reading an unknown output field of a declared step", () => {
|
|
21
|
+
const build = step("build", { run: async () => ({ version: "1.0" }) });
|
|
19
22
|
job("ci", {
|
|
20
23
|
runsOn: "kici:os:linux",
|
|
21
|
-
steps: [
|
|
24
|
+
steps: [build]
|
|
22
25
|
}).result.build.nope;
|
|
23
26
|
});
|
|
24
27
|
it("rejects reading an undeclared step name", () => {
|
|
28
|
+
const build = step("build", { run: async () => ({ version: "1.0" }) });
|
|
25
29
|
job("ci", {
|
|
26
30
|
runsOn: "kici:os:linux",
|
|
27
|
-
steps: [
|
|
31
|
+
steps: [build]
|
|
28
32
|
}).result.other;
|
|
29
33
|
});
|
|
30
34
|
it("infers a flat output shape for the run: shorthand", () => {
|
|
@@ -36,22 +40,26 @@ describe("Job<TOutputs> — merged-steps inference", () => {
|
|
|
36
40
|
flat.result.nope;
|
|
37
41
|
});
|
|
38
42
|
it("honours an explicit output-type override for a dynamically-shaped job", () => {
|
|
39
|
-
|
|
43
|
+
const dyn = job("dyn", {
|
|
40
44
|
runsOn: "kici:os:linux",
|
|
41
45
|
steps: [step(async () => ({}))]
|
|
42
|
-
})
|
|
46
|
+
});
|
|
47
|
+
expectTypeOf(dyn.result.n).toEqualTypeOf();
|
|
43
48
|
});
|
|
44
49
|
it("omits id-less steps and falls back to the loose shape", () => {
|
|
45
|
-
|
|
50
|
+
const j = job("idless", {
|
|
46
51
|
runsOn: "kici:os:linux",
|
|
47
52
|
steps: [step(async () => ({ x: 1 }))]
|
|
48
|
-
})
|
|
53
|
+
});
|
|
54
|
+
expectTypeOf(j.result.anything).toEqualTypeOf();
|
|
49
55
|
});
|
|
50
56
|
it("keeps a typed job assignable to the bare Job type", () => {
|
|
51
|
-
|
|
57
|
+
const build = step("build", { run: async () => ({ version: "1.0" }) });
|
|
58
|
+
const anyJob = job("ci", {
|
|
52
59
|
runsOn: "kici:os:linux",
|
|
53
|
-
steps: [
|
|
54
|
-
})
|
|
60
|
+
steps: [build]
|
|
61
|
+
});
|
|
62
|
+
expectTypeOf(anyJob).toEqualTypeOf();
|
|
55
63
|
});
|
|
56
64
|
});
|
|
57
65
|
describe("Job<void> — void run-shorthand assignability", () => {
|
|
@@ -75,10 +83,11 @@ describe("Job<void> — void run-shorthand assignability", () => {
|
|
|
75
83
|
workflow("wf", { jobs: [setup] });
|
|
76
84
|
});
|
|
77
85
|
it("resolves result to never for a void run-shorthand job", () => {
|
|
78
|
-
|
|
86
|
+
const setup = job("setup", {
|
|
79
87
|
runsOn: "kici:os:linux",
|
|
80
88
|
run: async () => {}
|
|
81
|
-
})
|
|
89
|
+
});
|
|
90
|
+
expectTypeOf(setup.result).toEqualTypeOf();
|
|
82
91
|
});
|
|
83
92
|
it("leaves a value-returning run job untouched (regression guard)", () => {
|
|
84
93
|
const flat = job("flat", {
|
|
@@ -90,9 +99,11 @@ describe("Job<void> — void run-shorthand assignability", () => {
|
|
|
90
99
|
});
|
|
91
100
|
});
|
|
92
101
|
describe("typed ctx.needs from job references", () => {
|
|
102
|
+
const build = step("build", { run: async () => ({ version: "1.0" }) });
|
|
103
|
+
const test_ = step("test", { run: async () => ({ passed: true }) });
|
|
93
104
|
const ci = job("ci", {
|
|
94
105
|
runsOn: "kici:os:linux",
|
|
95
|
-
steps: [
|
|
106
|
+
steps: [build, test_]
|
|
96
107
|
});
|
|
97
108
|
it("threads a referenced job’s inferred outputs into ctx.needs", () => {
|
|
98
109
|
job("deploy", {
|
|
@@ -135,13 +146,15 @@ describe("typed ctx.needs from job references", () => {
|
|
|
135
146
|
});
|
|
136
147
|
});
|
|
137
148
|
describe("envelope-generic jobOutputs for typed refs", () => {
|
|
149
|
+
const build = step("build", { run: async () => ({ version: "1.0" }) });
|
|
138
150
|
const ci = job("ci", {
|
|
139
151
|
runsOn: "kici:os:linux",
|
|
140
|
-
steps: [
|
|
152
|
+
steps: [build]
|
|
141
153
|
});
|
|
142
154
|
const ctx = null;
|
|
143
155
|
it("types a job-ref jobOutputs as the plain | matrix | host union of the inferred shape", () => {
|
|
144
|
-
|
|
156
|
+
const out = ctx.jobOutputs(ci);
|
|
157
|
+
expectTypeOf(out).toEqualTypeOf();
|
|
145
158
|
});
|
|
146
159
|
it("narrows to the typed matrix envelope via isMatrixJobOutputs", () => {
|
|
147
160
|
const out = ctx.jobOutputs(ci);
|
|
@@ -155,7 +168,8 @@ describe("envelope-generic jobOutputs for typed refs", () => {
|
|
|
155
168
|
if (isHostJobOutputs(out)) expectTypeOf(out.byHost).toEqualTypeOf();
|
|
156
169
|
});
|
|
157
170
|
it("keeps a string-name ref on the loose union", () => {
|
|
158
|
-
|
|
171
|
+
const out = ctx.jobOutputs({ name: "ci" });
|
|
172
|
+
expectTypeOf(out).toEqualTypeOf();
|
|
159
173
|
});
|
|
160
174
|
});
|
|
161
175
|
//#endregion
|
package/dist/job.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import "./rolldown-runtime-ClRpJifh.js";
|
|
2
2
|
import { createJobOutputProxy } from "./outputs.js";
|
|
3
|
+
import { assertSecretName } from "./git-types.js";
|
|
3
4
|
import { isKnownCapability } from "@kici-dev/engine";
|
|
4
5
|
import { randomUUID } from "node:crypto";
|
|
5
6
|
//#region src/job.ts
|
|
@@ -38,6 +39,7 @@ function validateSandbox(sandbox, jobName) {
|
|
|
38
39
|
function job(nameOrOptions, maybeOptions) {
|
|
39
40
|
const name = typeof nameOrOptions === "string" ? nameOrOptions : randomUUID();
|
|
40
41
|
const options = typeof nameOrOptions === "string" ? maybeOptions : nameOrOptions;
|
|
42
|
+
if (options.invoke && ((options.steps?.length ?? 0) > 0 || "run" in options)) throw new Error(`job('${name}'): 'invoke' is mutually exclusive with 'steps'/'run'`);
|
|
41
43
|
let steps = options.steps ?? [];
|
|
42
44
|
if (options.run) {
|
|
43
45
|
if (options.steps && options.steps.length > 0) throw new Error("job() cannot have both \"run\" and \"steps\" -- use one or the other");
|
|
@@ -49,7 +51,10 @@ function job(nameOrOptions, maybeOptions) {
|
|
|
49
51
|
const concurrencyGroup = options.concurrencyGroup ?? (typeof firstContext === "string" ? firstContext : void 0);
|
|
50
52
|
if (options.context !== void 0 && options.contexts !== void 0) throw new Error(`job('${name}'): context and contexts are mutually exclusive — use one`);
|
|
51
53
|
if (options.runsOn !== void 0 && options.runsOnAll !== void 0) throw new Error(`job('${name}'): runsOn and runsOnAll are mutually exclusive`);
|
|
52
|
-
if (options.
|
|
54
|
+
if (options.container !== void 0) validateContainerImageSource(name, options.container);
|
|
55
|
+
if (options.container && typeof options.container === "object" && options.container.auth) validateContainerAuth(name, options.container.auth);
|
|
56
|
+
if (options.gitCredentials) validateGitCredentials(name, options.gitCredentials);
|
|
57
|
+
if (options.invoke === void 0 && options.runsOn === void 0 && options.runsOnAll === void 0) throw new Error(`job('${name}'): one of runsOn or runsOnAll is required`);
|
|
53
58
|
if (options.onUnreachable !== void 0 && options.runsOnAll === void 0) console.warn(`[kici] job('${name}'): onUnreachable is ignored without runsOnAll`);
|
|
54
59
|
if (options.includeUninitialized !== void 0 && options.runsOnAll === void 0) console.warn(`[kici] job('${name}'): includeUninitialized is ignored without runsOnAll`);
|
|
55
60
|
return {
|
|
@@ -61,6 +66,7 @@ function job(nameOrOptions, maybeOptions) {
|
|
|
61
66
|
...options.includeUninitialized !== void 0 && { includeUninitialized: options.includeUninitialized },
|
|
62
67
|
...options.maxParallel !== void 0 && { maxParallel: options.maxParallel },
|
|
63
68
|
...options.failFast !== void 0 && { failFast: options.failFast },
|
|
69
|
+
...options.invoke !== void 0 && { invoke: options.invoke },
|
|
64
70
|
steps,
|
|
65
71
|
needs: options.needs,
|
|
66
72
|
rules: options.rules,
|
|
@@ -69,6 +75,7 @@ function job(nameOrOptions, maybeOptions) {
|
|
|
69
75
|
include: options.include,
|
|
70
76
|
exclude: options.exclude,
|
|
71
77
|
checkout: options.checkout,
|
|
78
|
+
...options.gitCredentials && { gitCredentials: options.gitCredentials },
|
|
72
79
|
container: options.container,
|
|
73
80
|
...options.sandbox !== void 0 && { sandbox: options.sandbox },
|
|
74
81
|
context: options.context,
|
|
@@ -90,6 +97,91 @@ function job(nameOrOptions, maybeOptions) {
|
|
|
90
97
|
result: createJobOutputProxy(name)
|
|
91
98
|
};
|
|
92
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Validate a job's named git credentials at definition time.
|
|
102
|
+
*
|
|
103
|
+
* Every `*Secret` field is a qualified `<context>:<secret-name>` reference into
|
|
104
|
+
* the secrets backend — the same form `workflow()` enforces for
|
|
105
|
+
* `registries[].tokenSecret`. Catching a pasted credential here matters because
|
|
106
|
+
* the alternative is committing it to a git repository and discovering it later
|
|
107
|
+
* as a confusing auth failure.
|
|
108
|
+
*/
|
|
109
|
+
/**
|
|
110
|
+
* Validate a job's container registry auth at definition time.
|
|
111
|
+
*
|
|
112
|
+
* Same contract as `gitCredentials`: every `*Secret` field is a qualified
|
|
113
|
+
* `<context>:<secret-name>` reference, and a pasted credential is refused
|
|
114
|
+
* rather than committed to a git repository. The `*Value` half is material
|
|
115
|
+
* supplied at run time and is deliberately not checked for shape.
|
|
116
|
+
*/
|
|
117
|
+
/**
|
|
118
|
+
* Is `p` a repo-relative path that stays inside the repository?
|
|
119
|
+
*
|
|
120
|
+
* Rejects an absolute path and any path whose `..` segments walk out of the
|
|
121
|
+
* tree. Deliberately string-only: the workflow is authored on one machine and
|
|
122
|
+
* the path is resolved on another, so there is no filesystem here to consult.
|
|
123
|
+
*/
|
|
124
|
+
function isInsideRepo(p) {
|
|
125
|
+
if (p.startsWith("/") || /^[A-Za-z]:[\\/]/.test(p)) return false;
|
|
126
|
+
let depth = 0;
|
|
127
|
+
for (const part of p.split(/[\\/]+/)) {
|
|
128
|
+
if (part === "" || part === ".") continue;
|
|
129
|
+
if (part === "..") {
|
|
130
|
+
depth -= 1;
|
|
131
|
+
if (depth < 0) return false;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
depth += 1;
|
|
135
|
+
}
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* A container names exactly one image source: a finalized `image`, or a
|
|
140
|
+
* `dockerfile` the agent builds before the job runs.
|
|
141
|
+
*
|
|
142
|
+
* Refused at definition time rather than at dispatch, because the author is the
|
|
143
|
+
* only one who can fix it and the workflow file is where they are looking. The
|
|
144
|
+
* agent re-checks the paths against the real workdir — a lock file is repo
|
|
145
|
+
* content and is not trusted — but that failure reaches a run log, not an
|
|
146
|
+
* editor.
|
|
147
|
+
*/
|
|
148
|
+
function validateContainerImageSource(name, container) {
|
|
149
|
+
if (typeof container === "string") return;
|
|
150
|
+
const hasImage = typeof container.image === "string" && container.image.length > 0;
|
|
151
|
+
const hasDockerfile = typeof container.dockerfile === "string" && container.dockerfile.length > 0;
|
|
152
|
+
if (hasImage === hasDockerfile) throw new Error(`job('${name}'): exactly one of container.image or container.dockerfile is required`);
|
|
153
|
+
if (!hasDockerfile) {
|
|
154
|
+
for (const field of [
|
|
155
|
+
"context",
|
|
156
|
+
"target",
|
|
157
|
+
"args"
|
|
158
|
+
]) if (container[field] !== void 0) throw new Error(`job('${name}'): container.${field} applies only to container.dockerfile`);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (!isInsideRepo(container.dockerfile)) throw new Error(`job('${name}'): container.dockerfile must stay inside the repository (got: ${container.dockerfile})`);
|
|
162
|
+
if (container.context !== void 0 && !isInsideRepo(container.context)) throw new Error(`job('${name}'): container.context must stay inside the repository (got: ${container.context})`);
|
|
163
|
+
if (container.auth && typeof container.auth.registry !== "string") throw new Error(`job('${name}'): container.auth.registry is required with container.dockerfile — the base image is named inside the Dockerfile, so the registry cannot be derived`);
|
|
164
|
+
}
|
|
165
|
+
function validateContainerAuth(name, auth) {
|
|
166
|
+
const bag = auth;
|
|
167
|
+
for (const [field, value] of Object.entries(bag)) {
|
|
168
|
+
if (!field.endsWith("Secret") || typeof value !== "string") continue;
|
|
169
|
+
assertSecretName(value, field, "container registry credential");
|
|
170
|
+
const idx = value.indexOf(":");
|
|
171
|
+
if (idx <= 0 || idx >= value.length - 1 || value.slice(idx + 1).includes(":")) throw new Error(`job('${name}'): container.auth.${field} must use qualified <context>:<secret-name> syntax (got: ${value})`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function validateGitCredentials(name, credentials) {
|
|
175
|
+
for (const [alias, ref] of Object.entries(credentials)) {
|
|
176
|
+
const bag = ref;
|
|
177
|
+
for (const [field, value] of Object.entries(bag)) {
|
|
178
|
+
if (!field.endsWith("Secret") || typeof value !== "string") continue;
|
|
179
|
+
assertSecretName(value, `${alias}.${field}`);
|
|
180
|
+
const idx = value.indexOf(":");
|
|
181
|
+
if (idx <= 0 || idx >= value.length - 1 || value.slice(idx + 1).includes(":")) throw new Error(`job('${name}'): gitCredentials.${alias}.${field} must use qualified <context>:<secret-name> syntax (got: ${value})`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
93
185
|
//#endregion
|
|
94
186
|
export { job };
|
|
95
187
|
|
package/dist/needs-context.d.ts
CHANGED
|
@@ -14,6 +14,23 @@ export interface UpstreamSnapshot {
|
|
|
14
14
|
jobs: Record<string, Record<string, unknown>>;
|
|
15
15
|
groups: Record<string, string[]>;
|
|
16
16
|
statuses?: Record<string, ExecutionJobStatus>;
|
|
17
|
+
/**
|
|
18
|
+
* Maps an invoke-gate job name to its ordered per-run results, one entry per
|
|
19
|
+
* run the gate triggered. Present only when the upstream is an invoke gate.
|
|
20
|
+
*/
|
|
21
|
+
invokeResults?: Record<string, InvokeResult[]>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* One invoked-run result exposed on an invoke-gate upstream's
|
|
25
|
+
* `ctx.needs[gate].result` array — one entry per run the gate triggered.
|
|
26
|
+
* `outputs` carries the invoked run's non-secret declared outputs.
|
|
27
|
+
*/
|
|
28
|
+
export interface InvokeResult {
|
|
29
|
+
readonly repo: string;
|
|
30
|
+
readonly workflow: string;
|
|
31
|
+
readonly runId: string;
|
|
32
|
+
readonly status: string;
|
|
33
|
+
readonly outputs: Readonly<Record<string, unknown>>;
|
|
17
34
|
}
|
|
18
35
|
/**
|
|
19
36
|
* A single-job need entry: the upstream's outputs proxy plus its terminal
|
|
@@ -31,8 +48,20 @@ export interface GroupNeedEntry<T = Record<string, unknown>> {
|
|
|
31
48
|
result: OutputProxy<T>;
|
|
32
49
|
status: ExecutionJobStatus;
|
|
33
50
|
}
|
|
34
|
-
/**
|
|
35
|
-
|
|
51
|
+
/**
|
|
52
|
+
* An invoke-gate need entry: `ctx.needs['<gate>'].result` is an ordered array of
|
|
53
|
+
* {@link InvokeResult}, one per run the gate triggered.
|
|
54
|
+
*/
|
|
55
|
+
export interface InvokeNeedEntry {
|
|
56
|
+
result: readonly InvokeResult[];
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A single-job need exposes `{ result, status }`; a group need exposes an
|
|
60
|
+
* ordered array of `{ name, result, status }`; an invoke-gate need exposes
|
|
61
|
+
* `{ result }` — an ordered array of {@link InvokeResult} on `.result`, one per
|
|
62
|
+
* triggered run.
|
|
63
|
+
*/
|
|
64
|
+
export type NeedEntry<T = Record<string, unknown>> = SingleNeedEntry<T> | GroupNeedEntry[] | InvokeNeedEntry;
|
|
36
65
|
/** The resolved `ctx.needs` map keyed by job name or group name. */
|
|
37
66
|
export type NeedsContext = Record<string, NeedEntry>;
|
|
38
67
|
/**
|
package/dist/needs-context.js
CHANGED
|
@@ -34,7 +34,9 @@ function buildNeedsContext(snapshot, declaredNeeds) {
|
|
|
34
34
|
const out = {};
|
|
35
35
|
for (const need of declaredNeeds) {
|
|
36
36
|
const { kind, key } = needKey(need);
|
|
37
|
-
|
|
37
|
+
const invokeResults = snapshot.invokeResults?.[key];
|
|
38
|
+
if (kind === "job" && invokeResults !== void 0) out[key] = { result: invokeResults };
|
|
39
|
+
else if (kind === "group") out[key] = (snapshot.groups[key] ?? []).map((name) => ({
|
|
38
40
|
name,
|
|
39
41
|
result: createSnapshotOutputProxy(name, snapshot.jobs[name]),
|
|
40
42
|
status: statusFor(snapshot, name)
|
|
@@ -218,13 +218,15 @@ function createTestStepContext(options = {}) {
|
|
|
218
218
|
get: () => Promise.resolve(null)
|
|
219
219
|
},
|
|
220
220
|
oidc: { token: () => Promise.reject(/* @__PURE__ */ new Error("ctx.kici.oidc.token() is not available in a test step context")) },
|
|
221
|
+
git: { github: { getToken: () => Promise.reject(/* @__PURE__ */ new Error("ctx.kici.git.github.getToken() is not available in a test step context")) } },
|
|
221
222
|
host: { requestReboot: () => Promise.reject(/* @__PURE__ */ new Error("ctx.kici.host.requestReboot() is not available in a test step context")) },
|
|
222
223
|
bootstrap: {
|
|
223
224
|
ensureInitRunner: () => Promise.reject(/* @__PURE__ */ new Error("ctx.kici.bootstrap.ensureInitRunner() is not available in a test step context")),
|
|
224
225
|
preBootSend: () => Promise.reject(/* @__PURE__ */ new Error("ctx.kici.bootstrap.preBootSend() is not available in a test step context")),
|
|
225
226
|
agentVersionStatus: () => Promise.reject(/* @__PURE__ */ new Error("ctx.kici.bootstrap.agentVersionStatus() is not available in a test step context")),
|
|
226
227
|
restageAgent: () => Promise.reject(/* @__PURE__ */ new Error("ctx.kici.bootstrap.restageAgent() is not available in a test step context"))
|
|
227
|
-
}
|
|
228
|
+
},
|
|
229
|
+
scaler: { claimAgentCredentials: () => Promise.reject(/* @__PURE__ */ new Error("ctx.kici.scaler.claimAgentCredentials() is not available in a test step context")) }
|
|
228
230
|
},
|
|
229
231
|
cache: {
|
|
230
232
|
restore: async () => ({ hit: false }),
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { z } from 'zod';
|
|
2
2
|
import type { $ as Shell } from 'zx';
|
|
3
3
|
import type { RetryBackoff } from '@kici-dev/core';
|
|
4
|
+
import type { GitCredentialMap, ContainerRegistryAuth } from './git-types.js';
|
|
4
5
|
import type { ResourceRequest, RunsOnAllInput, OnUnreachableMode, NeedsWhen, ExecutionJobStatus } from '@kici-dev/engine';
|
|
5
6
|
import type { StepContext, Logger, RepoInfo } from './context.js';
|
|
6
7
|
import type { FilterFn } from './filter.js';
|
|
@@ -406,10 +407,43 @@ export { DYNAMIC_JOB_NEEDS_TAG };
|
|
|
406
407
|
* When set, all steps run inside the specified container.
|
|
407
408
|
*/
|
|
408
409
|
export interface ContainerConfig {
|
|
409
|
-
/**
|
|
410
|
-
|
|
410
|
+
/**
|
|
411
|
+
* Docker image name (e.g., 'node:20-alpine'). Mutually exclusive with
|
|
412
|
+
* `dockerfile`; exactly one of the two is required.
|
|
413
|
+
*/
|
|
414
|
+
image?: string;
|
|
415
|
+
/**
|
|
416
|
+
* Repo-relative path to a Dockerfile the agent builds before the job runs.
|
|
417
|
+
* Mutually exclusive with `image`.
|
|
418
|
+
*
|
|
419
|
+
* The build runs on the agent host, NOT inside the job's hardened sandbox.
|
|
420
|
+
* See the container-jobs documentation for the trust model and the
|
|
421
|
+
* untrusted-ref rule.
|
|
422
|
+
*/
|
|
423
|
+
dockerfile?: string;
|
|
424
|
+
/**
|
|
425
|
+
* Repo-relative build context. Defaults to the repository root.
|
|
426
|
+
* Meaningful only with `dockerfile`.
|
|
427
|
+
*/
|
|
428
|
+
context?: string;
|
|
429
|
+
/** Build stage to stop at (`--target`). Meaningful only with `dockerfile`. */
|
|
430
|
+
target?: string;
|
|
431
|
+
/**
|
|
432
|
+
* Build arguments. Meaningful only with `dockerfile`.
|
|
433
|
+
*
|
|
434
|
+
* Plain strings only, deliberately: a build argument is recorded in the
|
|
435
|
+
* image's history, so a secret placed here is readable by anyone who can
|
|
436
|
+
* inspect the image. Pass a secret to a step instead.
|
|
437
|
+
*/
|
|
438
|
+
args?: Record<string, string>;
|
|
411
439
|
/** Additional environment variables for the container */
|
|
412
440
|
env?: Record<string, string>;
|
|
441
|
+
/**
|
|
442
|
+
* Private-registry credentials. With `image` they pull that image; with
|
|
443
|
+
* `dockerfile` they pull the Dockerfile's own `FROM` base — and must name
|
|
444
|
+
* the registry, because there is no image reference to derive it from.
|
|
445
|
+
*/
|
|
446
|
+
auth?: ContainerRegistryAuth;
|
|
413
447
|
}
|
|
414
448
|
/**
|
|
415
449
|
* Per-job sandbox escape hatch for container-sandbox jobs (a job with a
|
|
@@ -574,6 +608,13 @@ export interface Job<TOutputs = Record<string, unknown>, TName extends string =
|
|
|
574
608
|
readonly maxParallel?: number;
|
|
575
609
|
/** Halt the fan-out on first child failure, skipping the remainder. Default `false`. */
|
|
576
610
|
readonly failFast?: boolean;
|
|
611
|
+
/**
|
|
612
|
+
* Invoke the source repo's opt-in workflows and gate on them. Mutually
|
|
613
|
+
* exclusive with `steps` / `run` — an invoke job never runs step code on an
|
|
614
|
+
* agent; it fans out into one proxy node per triggered run. Built by
|
|
615
|
+
* `invokeSource()`.
|
|
616
|
+
*/
|
|
617
|
+
readonly invoke?: import('./invoke.js').InvokeConfig;
|
|
577
618
|
readonly steps: readonly StepInput[];
|
|
578
619
|
readonly needs?: ReadonlyArray<Job | string | DynamicGroupRef | {
|
|
579
620
|
name: string;
|
|
@@ -594,6 +635,17 @@ export interface Job<TOutputs = Record<string, unknown>, TName extends string =
|
|
|
594
635
|
readonly exclude?: MatrixExclude[];
|
|
595
636
|
/** When false, agent skips git clone (default: true). Useful for deploy/notify jobs. */
|
|
596
637
|
readonly checkout?: boolean;
|
|
638
|
+
/**
|
|
639
|
+
* Named git credentials available to every step in this job.
|
|
640
|
+
*
|
|
641
|
+
* Values are **secret names**, resolved from the secrets backend — never
|
|
642
|
+
* secret material. `default` is used whenever a call names no credential;
|
|
643
|
+
* any other key is referenced by name, e.g.
|
|
644
|
+
* `kici.git.clone({ repo, credential: 'forge' })`.
|
|
645
|
+
*
|
|
646
|
+
* Omit entirely and the source credential applies, which is all a read needs.
|
|
647
|
+
*/
|
|
648
|
+
readonly gitCredentials?: GitCredentialMap;
|
|
597
649
|
/** Docker image for job execution. All steps run inside the container. */
|
|
598
650
|
readonly container?: string | ContainerConfig;
|
|
599
651
|
/** Per-job sandbox escape hatch (container jobs only); granted within the operator allow-list. */
|
|
@@ -709,6 +761,13 @@ export interface JobOptions {
|
|
|
709
761
|
* of sibling outcomes). Applies to both matrix and `runsOnAll` fan-out.
|
|
710
762
|
*/
|
|
711
763
|
failFast?: boolean;
|
|
764
|
+
/**
|
|
765
|
+
* Invoke the source repo's opt-in workflows and gate on them. Mutually
|
|
766
|
+
* exclusive with `steps` / `run` — an invoke job never runs step code on an
|
|
767
|
+
* agent; it fans out into one proxy node per triggered run. Built by
|
|
768
|
+
* `invokeSource()`.
|
|
769
|
+
*/
|
|
770
|
+
invoke?: import('./invoke.js').InvokeConfig;
|
|
712
771
|
/**
|
|
713
772
|
* Steps to execute in this job. Accepts Step objects and bare async functions.
|
|
714
773
|
* Mutually exclusive with `run`.
|
|
@@ -749,6 +808,17 @@ export interface JobOptions {
|
|
|
749
808
|
exclude?: MatrixExclude[];
|
|
750
809
|
/** When false, agent skips git clone (default: true). Useful for deploy/notify jobs. */
|
|
751
810
|
checkout?: boolean;
|
|
811
|
+
/**
|
|
812
|
+
* Named git credentials available to every step in this job.
|
|
813
|
+
*
|
|
814
|
+
* Values are **secret names**, resolved from the secrets backend — never
|
|
815
|
+
* secret material. `default` is used whenever a call names no credential;
|
|
816
|
+
* any other key is referenced by name, e.g.
|
|
817
|
+
* `kici.git.clone({ repo, credential: 'forge' })`.
|
|
818
|
+
*
|
|
819
|
+
* Omit entirely and the source credential applies, which is all a read needs.
|
|
820
|
+
*/
|
|
821
|
+
gitCredentials?: GitCredentialMap;
|
|
752
822
|
/**
|
|
753
823
|
* Docker image for job execution.
|
|
754
824
|
* Simple string form for image name, object form for additional config.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kici-dev/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "TypeScript SDK for defining KiCI workflows. Import into `.kici/workflows/*.ts` to declare workflows, jobs, steps, triggers, rules, and matrix configurations.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ci",
|
|
@@ -52,10 +52,11 @@
|
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"micromatch": "^4.0.8",
|
|
55
|
+
"yaml": "^2.9.0",
|
|
55
56
|
"zod": "^4.4.3",
|
|
56
57
|
"zx": "^8.8.5",
|
|
57
|
-
"@kici-dev/core": "0.
|
|
58
|
-
"@kici-dev/engine": "0.
|
|
58
|
+
"@kici-dev/core": "0.6.1",
|
|
59
|
+
"@kici-dev/engine": "0.6.1"
|
|
59
60
|
},
|
|
60
61
|
"devDependencies": {
|
|
61
62
|
"@types/micromatch": "^4.0.10"
|