@kici-dev/sdk 0.1.27 → 0.2.0
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/api-types.d.ts +28 -0
- package/dist/api-types.js +3 -1
- package/dist/approval.d.ts +3 -8
- package/dist/approval.js +9 -0
- package/dist/artifacts-types.d.ts +45 -0
- package/dist/artifacts-types.js +3 -0
- package/dist/context.d.ts +65 -15
- package/dist/events/define-event.d.ts +11 -3
- package/dist/events/define-event.js +14 -4
- package/dist/events/emit-typing.test-d.d.ts +2 -0
- package/dist/events/emit-typing.test-d.js +36 -0
- package/dist/events/event-payloads.d.ts +4 -0
- package/dist/events/index.d.ts +1 -1
- package/dist/events/index.js +2 -2
- package/dist/fleet/agent-version-converge.d.ts +23 -0
- package/dist/fleet/agent-version-converge.js +58 -0
- package/dist/index.d.ts +9 -3
- package/dist/index.js +6 -2
- package/dist/job-outputs.test-d.d.ts +2 -0
- package/dist/job-outputs.test-d.js +164 -0
- package/dist/job.d.ts +71 -9
- package/dist/job.js +14 -0
- package/dist/matrix/expand.d.ts +1 -1
- package/dist/matrix/expand.js +2 -2
- package/dist/needs-context.d.ts +13 -6
- package/dist/outputs.d.ts +19 -4
- package/dist/outputs.js +21 -7
- package/dist/parallel.d.ts +1 -1
- package/dist/parallel.js +1 -1
- package/dist/provenance-types.d.ts +3 -3
- package/dist/rules/context.d.ts +33 -0
- package/dist/rules/context.js +51 -0
- package/dist/rules/evaluator.d.ts +10 -0
- package/dist/rules/evaluator.js +9 -1
- package/dist/rules/index.d.ts +2 -0
- package/dist/rules/index.js +2 -1
- package/dist/rules/types.d.ts +11 -1
- package/dist/step.d.ts +6 -6
- package/dist/step.js +6 -3
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +3 -0
- package/dist/testing/step-context.d.ts +56 -0
- package/dist/testing/step-context.js +259 -0
- package/dist/triggers/index.d.ts +2 -1
- package/dist/triggers/index.js +2 -1
- package/dist/triggers/types.d.ts +23 -1
- package/dist/triggers/workflows-failed-batch.d.ts +20 -0
- package/dist/triggers/workflows-failed-batch.js +25 -0
- package/dist/types.d.ts +142 -9
- package/dist/validation/dag.js +11 -3
- package/package.json +10 -5
- package/sbom.spdx.json +192 -192
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import "./rolldown-runtime-ClRpJifh.js";
|
|
2
|
+
import { isHostJobOutputs, isMatrixJobOutputs } from "./context.js";
|
|
3
|
+
import { dynamicGroup } from "./dynamic-group.js";
|
|
4
|
+
import { step } from "./step.js";
|
|
5
|
+
import { job } from "./job.js";
|
|
6
|
+
import { workflow } from "./workflow.js";
|
|
7
|
+
import { describe, expectTypeOf, it } from "vitest";
|
|
8
|
+
//#region src/job-outputs.test-d.ts
|
|
9
|
+
describe("Job<TOutputs> — merged-steps inference", () => {
|
|
10
|
+
it("infers a nested output map keyed by step name for a multi-step job", () => {
|
|
11
|
+
const j = job("ci", {
|
|
12
|
+
runsOn: "kici:os:linux",
|
|
13
|
+
steps: [step("build", { run: async () => ({ version: "1.0" }) }), step("test", { run: async () => ({ passed: true }) })]
|
|
14
|
+
});
|
|
15
|
+
expectTypeOf(j.result.build.version).toEqualTypeOf();
|
|
16
|
+
expectTypeOf(j.result.test.passed).toEqualTypeOf();
|
|
17
|
+
});
|
|
18
|
+
it("rejects reading an unknown output field of a declared step", () => {
|
|
19
|
+
job("ci", {
|
|
20
|
+
runsOn: "kici:os:linux",
|
|
21
|
+
steps: [step("build", { run: async () => ({ version: "1.0" }) })]
|
|
22
|
+
}).result.build.nope;
|
|
23
|
+
});
|
|
24
|
+
it("rejects reading an undeclared step name", () => {
|
|
25
|
+
job("ci", {
|
|
26
|
+
runsOn: "kici:os:linux",
|
|
27
|
+
steps: [step("build", { run: async () => ({ version: "1.0" }) })]
|
|
28
|
+
}).result.other;
|
|
29
|
+
});
|
|
30
|
+
it("infers a flat output shape for the run: shorthand", () => {
|
|
31
|
+
const flat = job("flat", {
|
|
32
|
+
runsOn: "kici:os:linux",
|
|
33
|
+
run: async () => ({ url: "x" })
|
|
34
|
+
});
|
|
35
|
+
expectTypeOf(flat.result.url).toEqualTypeOf();
|
|
36
|
+
flat.result.nope;
|
|
37
|
+
});
|
|
38
|
+
it("honours an explicit output-type override for a dynamically-shaped job", () => {
|
|
39
|
+
expectTypeOf(job("dyn", {
|
|
40
|
+
runsOn: "kici:os:linux",
|
|
41
|
+
steps: [step(async () => ({}))]
|
|
42
|
+
}).result.n).toEqualTypeOf();
|
|
43
|
+
});
|
|
44
|
+
it("omits id-less steps and falls back to the loose shape", () => {
|
|
45
|
+
expectTypeOf(job("idless", {
|
|
46
|
+
runsOn: "kici:os:linux",
|
|
47
|
+
steps: [step(async () => ({ x: 1 }))]
|
|
48
|
+
}).result.anything).toEqualTypeOf();
|
|
49
|
+
});
|
|
50
|
+
it("keeps a typed job assignable to the bare Job type", () => {
|
|
51
|
+
expectTypeOf(job("ci", {
|
|
52
|
+
runsOn: "kici:os:linux",
|
|
53
|
+
steps: [step("build", { run: async () => ({ version: "1.0" }) })]
|
|
54
|
+
})).toEqualTypeOf();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
describe("Job<void> — void run-shorthand assignability", () => {
|
|
58
|
+
it("makes a void run-shorthand job assignable to JobOrFactory (inline)", () => {
|
|
59
|
+
workflow("wf", { jobs: [job("setup", {
|
|
60
|
+
runsOn: "kici:os:linux",
|
|
61
|
+
run: async ({ $ }) => {
|
|
62
|
+
await $`echo hi`;
|
|
63
|
+
}
|
|
64
|
+
})] });
|
|
65
|
+
expectTypeOf().toMatchTypeOf();
|
|
66
|
+
});
|
|
67
|
+
it("makes a void run-shorthand job assignable to JobOrFactory (const-extracted)", () => {
|
|
68
|
+
const setup = job("setup", {
|
|
69
|
+
runsOn: "kici:os:linux",
|
|
70
|
+
run: async ({ $ }) => {
|
|
71
|
+
await $`echo hi`;
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
expectTypeOf(setup).toMatchTypeOf();
|
|
75
|
+
workflow("wf", { jobs: [setup] });
|
|
76
|
+
});
|
|
77
|
+
it("resolves result to never for a void run-shorthand job", () => {
|
|
78
|
+
expectTypeOf(job("setup", {
|
|
79
|
+
runsOn: "kici:os:linux",
|
|
80
|
+
run: async () => {}
|
|
81
|
+
}).result).toEqualTypeOf();
|
|
82
|
+
});
|
|
83
|
+
it("leaves a value-returning run job untouched (regression guard)", () => {
|
|
84
|
+
const flat = job("flat", {
|
|
85
|
+
runsOn: "kici:os:linux",
|
|
86
|
+
run: async () => ({ url: "x" })
|
|
87
|
+
});
|
|
88
|
+
expectTypeOf(flat.result.url).toEqualTypeOf();
|
|
89
|
+
expectTypeOf(flat).toMatchTypeOf();
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
describe("typed ctx.needs from job references", () => {
|
|
93
|
+
const ci = job("ci", {
|
|
94
|
+
runsOn: "kici:os:linux",
|
|
95
|
+
steps: [step("build", { run: async () => ({ version: "1.0" }) }), step("test", { run: async () => ({ passed: true }) })]
|
|
96
|
+
});
|
|
97
|
+
it("threads a referenced job’s inferred outputs into ctx.needs", () => {
|
|
98
|
+
job("deploy", {
|
|
99
|
+
runsOn: "kici:os:linux",
|
|
100
|
+
needs: [ci],
|
|
101
|
+
run: async (ctx) => {
|
|
102
|
+
expectTypeOf(ctx.needs.ci.result.build.version).toEqualTypeOf();
|
|
103
|
+
expectTypeOf(ctx.needs.ci.result.test.passed).toEqualTypeOf();
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
it("rejects an undeclared need and an unknown field through a need", () => {
|
|
108
|
+
job("deploy2", {
|
|
109
|
+
runsOn: "kici:os:linux",
|
|
110
|
+
needs: [ci],
|
|
111
|
+
run: async (ctx) => {
|
|
112
|
+
ctx.needs.other;
|
|
113
|
+
ctx.needs.ci.result.build.nope;
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
it("keeps string-form needs loose", () => {
|
|
118
|
+
job("loose", {
|
|
119
|
+
runsOn: "kici:os:linux",
|
|
120
|
+
needs: ["ci"],
|
|
121
|
+
run: async (ctx) => {
|
|
122
|
+
expectTypeOf(ctx.needs.ci.result).toEqualTypeOf();
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
it("falls back to the loose open map when a group need is mixed in (no regression)", () => {
|
|
127
|
+
job("mixed", {
|
|
128
|
+
runsOn: "kici:os:linux",
|
|
129
|
+
needs: [ci, dynamicGroup("shards")],
|
|
130
|
+
run: async (ctx) => {
|
|
131
|
+
expectTypeOf(ctx.needs.ci).not.toBeNever();
|
|
132
|
+
expectTypeOf(ctx.needs.shards).not.toBeNever();
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
describe("envelope-generic jobOutputs for typed refs", () => {
|
|
138
|
+
const ci = job("ci", {
|
|
139
|
+
runsOn: "kici:os:linux",
|
|
140
|
+
steps: [step("build", { run: async () => ({ version: "1.0" }) })]
|
|
141
|
+
});
|
|
142
|
+
const ctx = null;
|
|
143
|
+
it("types a job-ref jobOutputs as the plain | matrix | host union of the inferred shape", () => {
|
|
144
|
+
expectTypeOf(ctx.jobOutputs(ci)).toEqualTypeOf();
|
|
145
|
+
});
|
|
146
|
+
it("narrows to the typed matrix envelope via isMatrixJobOutputs", () => {
|
|
147
|
+
const out = ctx.jobOutputs(ci);
|
|
148
|
+
if (isMatrixJobOutputs(out)) {
|
|
149
|
+
expectTypeOf(out.byMatrix).toEqualTypeOf();
|
|
150
|
+
expectTypeOf(out.merged.build.version).toEqualTypeOf();
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
it("narrows to the typed host envelope via isHostJobOutputs", () => {
|
|
154
|
+
const out = ctx.jobOutputs(ci);
|
|
155
|
+
if (isHostJobOutputs(out)) expectTypeOf(out.byHost).toEqualTypeOf();
|
|
156
|
+
});
|
|
157
|
+
it("keeps a string-name ref on the loose union", () => {
|
|
158
|
+
expectTypeOf(ctx.jobOutputs({ name: "ci" })).toEqualTypeOf();
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
//#endregion
|
|
162
|
+
export {};
|
|
163
|
+
|
|
164
|
+
//# sourceMappingURL=job-outputs.test-d.js.map
|
package/dist/job.d.ts
CHANGED
|
@@ -1,31 +1,93 @@
|
|
|
1
|
-
import type { Job, JobOptions } from './types.js';
|
|
1
|
+
import type { Job, JobOptions, StepInput, InferJobOutputsFromSteps, StepContextWithNeeds } from './types.js';
|
|
2
|
+
import type { StepContext } from './context.js';
|
|
2
3
|
/**
|
|
3
|
-
* Create a job with an explicit name.
|
|
4
|
+
* Create a job with an explicit name; run shorthand with typed `ctx.needs`.
|
|
5
|
+
*
|
|
6
|
+
* When the job declares `needs: [jobRef, …]`, the `run:` function's `ctx.needs`
|
|
7
|
+
* is typed from the tuple — job references thread their inferred outputs, so
|
|
8
|
+
* `ctx.needs.<job>.result.<field>` is checked (string / `{ name }` entries stay
|
|
9
|
+
* loose). Outputs are inferred flat from the run function's return type.
|
|
10
|
+
*/
|
|
11
|
+
export declare function job<TName extends string, const TNeeds extends readonly unknown[], TRun extends (ctx: StepContextWithNeeds<TNeeds>) => Promise<any>>(name: TName, options: Omit<JobOptions, 'needs' | 'run' | 'steps'> & {
|
|
12
|
+
needs: TNeeds;
|
|
13
|
+
run: TRun;
|
|
14
|
+
steps?: undefined;
|
|
15
|
+
}): Job<Awaited<ReturnType<TRun>>, TName>;
|
|
16
|
+
/**
|
|
17
|
+
* Create a job with an explicit name; outputs inferred from the run shorthand.
|
|
18
|
+
*
|
|
19
|
+
* The `run:` shorthand infers a **flat** output shape from the run function's
|
|
20
|
+
* return type (`job.result.<field>`), matching the runtime's single-step
|
|
21
|
+
* flattening.
|
|
22
|
+
*/
|
|
23
|
+
export declare function job<TName extends string, TRun extends (ctx: StepContext) => Promise<any>>(name: TName, options: JobOptions & {
|
|
24
|
+
run: TRun;
|
|
25
|
+
steps?: undefined;
|
|
26
|
+
}): Job<Awaited<ReturnType<TRun>>, TName>;
|
|
27
|
+
/**
|
|
28
|
+
* Create a job with an explicit name; outputs inferred from the steps tuple.
|
|
29
|
+
*
|
|
30
|
+
* A `steps:` job infers a **nested** output shape keyed by step name
|
|
31
|
+
* (`job.result.<step>.<field>`) via {@link InferJobOutputsFromSteps} — name
|
|
32
|
+
* your steps to get typed cross-job reads (id-less steps contribute nothing).
|
|
4
33
|
*
|
|
5
34
|
* @example
|
|
6
35
|
* const build = job('build', {
|
|
7
|
-
* runsOn: 'linux',
|
|
36
|
+
* runsOn: 'kici:os:linux',
|
|
8
37
|
* steps: [checkout, install, compile],
|
|
9
38
|
* });
|
|
39
|
+
*/
|
|
40
|
+
export declare function job<TName extends string, const TSteps extends readonly StepInput[]>(name: TName, options: JobOptions & {
|
|
41
|
+
steps: TSteps;
|
|
42
|
+
run?: undefined;
|
|
43
|
+
}): Job<InferJobOutputsFromSteps<TSteps>, TName>;
|
|
44
|
+
/**
|
|
45
|
+
* Create a job with an explicit name and an explicit output-type override.
|
|
46
|
+
*
|
|
47
|
+
* Pass the output shape as a type argument (`job<MyOutputs>('name', {...})`) for
|
|
48
|
+
* dynamically-shaped jobs the inference can't reproduce. With no type argument
|
|
49
|
+
* (and no `run` / `steps` to infer from) it defaults to the loose
|
|
50
|
+
* `Record<string, unknown>`, so a bare job keeps compiling.
|
|
10
51
|
*
|
|
11
52
|
* @example
|
|
12
|
-
* // With rules and description
|
|
13
53
|
* const build = job('build', {
|
|
14
|
-
* runsOn: 'linux',
|
|
54
|
+
* runsOn: 'kici:os:linux',
|
|
15
55
|
* steps: [checkout, install, compile],
|
|
16
56
|
* rules: [rule('env: CI')],
|
|
17
57
|
* description: 'Build the project',
|
|
18
58
|
* });
|
|
19
59
|
*/
|
|
20
|
-
export declare function job(name:
|
|
60
|
+
export declare function job<TOutputs = Record<string, unknown>, TName extends string = string>(name: TName, options: JobOptions): Job<TOutputs, TName>;
|
|
61
|
+
/**
|
|
62
|
+
* Create a job with auto-generated ID; run shorthand with typed `ctx.needs`.
|
|
63
|
+
*/
|
|
64
|
+
export declare function job<const TNeeds extends readonly unknown[], TRun extends (ctx: StepContextWithNeeds<TNeeds>) => Promise<any>>(options: Omit<JobOptions, 'needs' | 'run' | 'steps'> & {
|
|
65
|
+
needs: TNeeds;
|
|
66
|
+
run: TRun;
|
|
67
|
+
steps?: undefined;
|
|
68
|
+
}): Job<Awaited<ReturnType<TRun>>>;
|
|
21
69
|
/**
|
|
22
|
-
* Create a job with auto-generated ID.
|
|
70
|
+
* Create a job with auto-generated ID; outputs inferred from the run shorthand.
|
|
71
|
+
*/
|
|
72
|
+
export declare function job<TRun extends (ctx: StepContext) => Promise<any>>(options: JobOptions & {
|
|
73
|
+
run: TRun;
|
|
74
|
+
steps?: undefined;
|
|
75
|
+
}): Job<Awaited<ReturnType<TRun>>>;
|
|
76
|
+
/**
|
|
77
|
+
* Create a job with auto-generated ID; outputs inferred from the steps tuple.
|
|
23
78
|
*
|
|
24
79
|
* @example
|
|
25
80
|
* const build = job({
|
|
26
|
-
* runsOn: 'linux',
|
|
81
|
+
* runsOn: 'kici:os:linux',
|
|
27
82
|
* steps: [checkout, install],
|
|
28
83
|
* });
|
|
29
84
|
*/
|
|
30
|
-
export declare function job(options: JobOptions
|
|
85
|
+
export declare function job<const TSteps extends readonly StepInput[]>(options: JobOptions & {
|
|
86
|
+
steps: TSteps;
|
|
87
|
+
run?: undefined;
|
|
88
|
+
}): Job<InferJobOutputsFromSteps<TSteps>>;
|
|
89
|
+
/**
|
|
90
|
+
* Create a job with auto-generated ID and an explicit output-type override.
|
|
91
|
+
*/
|
|
92
|
+
export declare function job<TOutputs = Record<string, unknown>>(options: JobOptions): Job<TOutputs>;
|
|
31
93
|
//# sourceMappingURL=job.d.ts.map
|
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 { isKnownCapability } from "@kici-dev/engine";
|
|
3
4
|
import { randomUUID } from "node:crypto";
|
|
4
5
|
//#region src/job.ts
|
|
5
6
|
/** A typed preset is a `'mise'` string or a `{ mise }` object — neither carries `run`. */
|
|
@@ -21,6 +22,17 @@ function validateInit(init, jobName) {
|
|
|
21
22
|
});
|
|
22
23
|
}
|
|
23
24
|
/**
|
|
25
|
+
* Validate a job's `sandbox` escape-hatch request. An unknown capability name
|
|
26
|
+
* is a typo that would never match the operator allow-list, so it is rejected
|
|
27
|
+
* at author time (security is still enforced deny-by-default at dispatch — this
|
|
28
|
+
* is a correctness/UX guard). Capability form is normalized by the dispatch
|
|
29
|
+
* resolver, so both `NET_ADMIN` and `CAP_NET_ADMIN` are accepted here.
|
|
30
|
+
*/
|
|
31
|
+
function validateSandbox(sandbox, jobName) {
|
|
32
|
+
if (sandbox === void 0) return;
|
|
33
|
+
for (const cap of sandbox.capabilities ?? []) if (!isKnownCapability(cap)) throw new Error(`job('${jobName}'): unknown Linux capability '${cap}' in sandbox.capabilities`);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
24
36
|
* Implementation of job() factory.
|
|
25
37
|
*/
|
|
26
38
|
function job(nameOrOptions, maybeOptions) {
|
|
@@ -32,6 +44,7 @@ function job(nameOrOptions, maybeOptions) {
|
|
|
32
44
|
steps = [options.run];
|
|
33
45
|
}
|
|
34
46
|
validateInit(options.init, name);
|
|
47
|
+
validateSandbox(options.sandbox, name);
|
|
35
48
|
const firstContext = options.contexts?.[0];
|
|
36
49
|
const concurrencyGroup = options.concurrencyGroup ?? (typeof firstContext === "string" ? firstContext : void 0);
|
|
37
50
|
if (options.context !== void 0 && options.contexts !== void 0) throw new Error(`job('${name}'): context and contexts are mutually exclusive — use one`);
|
|
@@ -57,6 +70,7 @@ function job(nameOrOptions, maybeOptions) {
|
|
|
57
70
|
exclude: options.exclude,
|
|
58
71
|
checkout: options.checkout,
|
|
59
72
|
container: options.container,
|
|
73
|
+
...options.sandbox !== void 0 && { sandbox: options.sandbox },
|
|
60
74
|
context: options.context,
|
|
61
75
|
contexts: options.contexts,
|
|
62
76
|
env: options.env,
|
package/dist/matrix/expand.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { expandMatrix, applyIncludeExclude } from '@kici-dev/engine';
|
|
2
2
|
//# sourceMappingURL=expand.d.ts.map
|
package/dist/matrix/expand.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import "../rolldown-runtime-ClRpJifh.js";
|
|
2
|
-
import { applyIncludeExclude, expandMatrix
|
|
3
|
-
export { applyIncludeExclude, expandMatrix
|
|
2
|
+
import { applyIncludeExclude, expandMatrix } from "@kici-dev/engine";
|
|
3
|
+
export { applyIncludeExclude, expandMatrix };
|
package/dist/needs-context.d.ts
CHANGED
|
@@ -15,17 +15,24 @@ export interface UpstreamSnapshot {
|
|
|
15
15
|
groups: Record<string, string[]>;
|
|
16
16
|
statuses?: Record<string, ExecutionJobStatus>;
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* A single-job need entry: the upstream's outputs proxy plus its terminal
|
|
20
|
+
* status. `T` is the upstream's inferred output shape (defaults to the loose
|
|
21
|
+
* `Record<string, unknown>`); a typed `needs: [jobRef]` threads the reference's
|
|
22
|
+
* outputs here so `ctx.needs.<job>.result.<field>` is checked.
|
|
23
|
+
*/
|
|
24
|
+
export interface SingleNeedEntry<T = Record<string, unknown>> {
|
|
25
|
+
result: OutputProxy<T>;
|
|
26
|
+
status: ExecutionJobStatus;
|
|
27
|
+
}
|
|
18
28
|
/** One entry in the array exposed for a `dynamicGroup(...)` need. */
|
|
19
|
-
export interface GroupNeedEntry {
|
|
29
|
+
export interface GroupNeedEntry<T = Record<string, unknown>> {
|
|
20
30
|
name: string;
|
|
21
|
-
result: OutputProxy<
|
|
31
|
+
result: OutputProxy<T>;
|
|
22
32
|
status: ExecutionJobStatus;
|
|
23
33
|
}
|
|
24
34
|
/** A single-job need exposes `{ result, status }`; a group need exposes an ordered array. */
|
|
25
|
-
export type NeedEntry =
|
|
26
|
-
result: OutputProxy<any>;
|
|
27
|
-
status: ExecutionJobStatus;
|
|
28
|
-
} | GroupNeedEntry[];
|
|
35
|
+
export type NeedEntry<T = Record<string, unknown>> = SingleNeedEntry<T> | GroupNeedEntry[];
|
|
29
36
|
/** The resolved `ctx.needs` map keyed by job name or group name. */
|
|
30
37
|
export type NeedsContext = Record<string, NeedEntry>;
|
|
31
38
|
/**
|
package/dist/outputs.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { OutputProxy } from './types.js';
|
|
1
|
+
import type { OutputProxy, SourceLocation } from './types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Shared mutable map that steps write their outputs to and proxies read from.
|
|
4
4
|
* Keys are step/job names, values are the outputs records.
|
|
@@ -42,14 +42,29 @@ export declare function getJobOutputsMap(): OutputsMap;
|
|
|
42
42
|
* Get the current step ref map.
|
|
43
43
|
*/
|
|
44
44
|
export declare function getStepRefMap(): StepRefMap;
|
|
45
|
+
/**
|
|
46
|
+
* Minimal shape the step output proxy needs from a step: a name that may be
|
|
47
|
+
* assigned after the proxy is created (id-less steps are named `step-N` when the
|
|
48
|
+
* job's steps are enumerated at execution start), plus the optional source
|
|
49
|
+
* location for a helpful error when the name is still unresolved.
|
|
50
|
+
*/
|
|
51
|
+
export interface StepNameRef {
|
|
52
|
+
readonly name: string;
|
|
53
|
+
readonly _sourceLocation?: SourceLocation;
|
|
54
|
+
}
|
|
45
55
|
/**
|
|
46
56
|
* Create a Proxy over step outputs that resolves property access lazily
|
|
47
57
|
* against the module-global step outputs map.
|
|
48
58
|
*
|
|
49
|
-
*
|
|
59
|
+
* The proxy reads `stepRef.name` at ACCESS time rather than capturing the name
|
|
60
|
+
* string at creation. Id-less steps have `name === ''` when the proxy is built;
|
|
61
|
+
* the runner assigns `step-N` onto the same step object before any run function
|
|
62
|
+
* executes, so `.result` resolves under the final name by the time it is used.
|
|
63
|
+
*
|
|
64
|
+
* @param stepRef - The step whose (possibly later-assigned) name identifies its outputs
|
|
50
65
|
* @returns A Proxy that resolves property access at runtime
|
|
51
66
|
*/
|
|
52
|
-
export declare function createStepOutputProxy<T>(
|
|
67
|
+
export declare function createStepOutputProxy<T>(stepRef: StepNameRef): OutputProxy<T>;
|
|
53
68
|
/**
|
|
54
69
|
* Create a Proxy over job outputs that resolves property access lazily
|
|
55
70
|
* against the module-global job outputs map.
|
|
@@ -60,7 +75,7 @@ export declare function createStepOutputProxy<T>(stepName: string): OutputProxy<
|
|
|
60
75
|
* @param jobName - The name of the job whose outputs to proxy
|
|
61
76
|
* @returns A Proxy that resolves property access at runtime
|
|
62
77
|
*/
|
|
63
|
-
export declare function createJobOutputProxy(jobName: string): OutputProxy<
|
|
78
|
+
export declare function createJobOutputProxy<TOutputs = any>(jobName: string): OutputProxy<TOutputs>;
|
|
64
79
|
/**
|
|
65
80
|
* Output proxy bound to a specific outputs object (not the module-global map).
|
|
66
81
|
*
|
package/dist/outputs.js
CHANGED
|
@@ -66,30 +66,44 @@ function getStepRefMap() {
|
|
|
66
66
|
* Create a Proxy over step outputs that resolves property access lazily
|
|
67
67
|
* against the module-global step outputs map.
|
|
68
68
|
*
|
|
69
|
-
*
|
|
69
|
+
* The proxy reads `stepRef.name` at ACCESS time rather than capturing the name
|
|
70
|
+
* string at creation. Id-less steps have `name === ''` when the proxy is built;
|
|
71
|
+
* the runner assigns `step-N` onto the same step object before any run function
|
|
72
|
+
* executes, so `.result` resolves under the final name by the time it is used.
|
|
73
|
+
*
|
|
74
|
+
* @param stepRef - The step whose (possibly later-assigned) name identifies its outputs
|
|
70
75
|
* @returns A Proxy that resolves property access at runtime
|
|
71
76
|
*/
|
|
72
|
-
function createStepOutputProxy(
|
|
77
|
+
function createStepOutputProxy(stepRef) {
|
|
78
|
+
const unresolvedError = () => {
|
|
79
|
+
const loc = stepRef._sourceLocation;
|
|
80
|
+
const at = loc ? ` (step defined at ${loc.file}:${loc.line})` : "";
|
|
81
|
+
return /* @__PURE__ */ new Error(`This step has no name yet; .result is only available inside another step's run function${at}. Name the step to reference its outputs before execution.`);
|
|
82
|
+
};
|
|
73
83
|
return new Proxy({}, {
|
|
74
84
|
get(_target, prop, receiver) {
|
|
75
85
|
if (typeof prop === "symbol") return Reflect.get(_target, prop, receiver);
|
|
76
86
|
if (WELL_KNOWN_STRING_PROPS.has(prop)) return Reflect.get(_target, prop, receiver);
|
|
77
|
-
|
|
78
|
-
|
|
87
|
+
if (!stepRef.name) throw unresolvedError();
|
|
88
|
+
const outputs = _stepOutputsMap.get(stepRef.name);
|
|
89
|
+
if (!outputs) throw new Error(`Step '${stepRef.name}' has not produced outputs yet`);
|
|
79
90
|
return outputs[prop];
|
|
80
91
|
},
|
|
81
92
|
ownKeys() {
|
|
82
|
-
|
|
93
|
+
if (!stepRef.name) return [];
|
|
94
|
+
const outputs = _stepOutputsMap.get(stepRef.name);
|
|
83
95
|
if (!outputs) return [];
|
|
84
96
|
return Reflect.ownKeys(outputs);
|
|
85
97
|
},
|
|
86
98
|
has(_target, prop) {
|
|
87
|
-
|
|
99
|
+
if (!stepRef.name) return false;
|
|
100
|
+
const outputs = _stepOutputsMap.get(stepRef.name);
|
|
88
101
|
if (!outputs) return false;
|
|
89
102
|
return prop in outputs;
|
|
90
103
|
},
|
|
91
104
|
getOwnPropertyDescriptor(_target, prop) {
|
|
92
|
-
|
|
105
|
+
if (!stepRef.name) return void 0;
|
|
106
|
+
const outputs = _stepOutputsMap.get(stepRef.name);
|
|
93
107
|
if (!outputs) return void 0;
|
|
94
108
|
if (prop in outputs) return {
|
|
95
109
|
configurable: true,
|
package/dist/parallel.d.ts
CHANGED
|
@@ -32,7 +32,7 @@ export declare function isParallelGroup(x: unknown): x is ParallelGroup;
|
|
|
32
32
|
/**
|
|
33
33
|
* Flatten a job's `steps` into a flat list where each parallel group's children
|
|
34
34
|
* are inlined in array order (the group wrapper is dropped). Used by single-
|
|
35
|
-
* process consumers (the
|
|
35
|
+
* process consumers (the test runner and dry-run preview) that surface each
|
|
36
36
|
* child as its own step but do not run a concurrent scheduler; the concurrent
|
|
37
37
|
* agent path expands groups into observable concurrent tasks instead.
|
|
38
38
|
*/
|
package/dist/parallel.js
CHANGED
|
@@ -21,7 +21,7 @@ function isParallelGroup(x) {
|
|
|
21
21
|
/**
|
|
22
22
|
* Flatten a job's `steps` into a flat list where each parallel group's children
|
|
23
23
|
* are inlined in array order (the group wrapper is dropped). Used by single-
|
|
24
|
-
* process consumers (the
|
|
24
|
+
* process consumers (the test runner and dry-run preview) that surface each
|
|
25
25
|
* child as its own step but do not run a concurrent scheduler; the concurrent
|
|
26
26
|
* agent path expands groups into observable concurrent tasks instead.
|
|
27
27
|
*/
|
|
@@ -33,9 +33,9 @@ export type AttestProvenanceResult = {
|
|
|
33
33
|
bundleMediaType: string;
|
|
34
34
|
} | {
|
|
35
35
|
/**
|
|
36
|
-
* The
|
|
37
|
-
* deferred: the statement was frozen + signed at build time and
|
|
38
|
-
* later (automatically
|
|
36
|
+
* The identity-token mint failed transiently, so only the identity token
|
|
37
|
+
* is deferred: the statement was frozen + signed at build time and the
|
|
38
|
+
* token is minted later (automatically when the mint recovers, or via
|
|
39
39
|
* `kici-admin attestations retry`). The job still completes successfully.
|
|
40
40
|
*/
|
|
41
41
|
deferred: true;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ChangedFilesStatus } from '@kici-dev/engine';
|
|
2
|
+
import type { EventPayload } from '../events/event-payloads.js';
|
|
3
|
+
import type { FanoutPosition } from '../fanout-context.js';
|
|
4
|
+
import type { RuleContext } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Thrown when a rule reads `ctx.changedFiles` but the diff is not available
|
|
7
|
+
* (`changedFilesStatus !== 'fetched'`). `evaluateRules` re-throws this rather
|
|
8
|
+
* than folding it into a `passed=false` skip, so the job fails loudly instead
|
|
9
|
+
* of silently mis-evaluating a path-based gate.
|
|
10
|
+
*/
|
|
11
|
+
export declare class ChangedFilesUnavailableError extends Error {
|
|
12
|
+
readonly changedFilesStatus: ChangedFilesStatus;
|
|
13
|
+
readonly eventType?: string;
|
|
14
|
+
constructor(status: ChangedFilesStatus, eventType?: string);
|
|
15
|
+
}
|
|
16
|
+
/** Input for {@link createRuleContext}. */
|
|
17
|
+
export interface CreateRuleContextInput {
|
|
18
|
+
event: EventPayload | Record<string, unknown>;
|
|
19
|
+
changedFiles?: string[];
|
|
20
|
+
/** Defaults to `'fetched'` — a caller that passes a real list needs no status. */
|
|
21
|
+
changedFilesStatus?: ChangedFilesStatus;
|
|
22
|
+
env?: Record<string, string | undefined>;
|
|
23
|
+
dispatchInputs?: Readonly<Record<string, string | number | boolean | null>>;
|
|
24
|
+
fanout?: FanoutPosition;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Build a RuleContext. `changedFiles` is exposed as a getter: it returns the
|
|
28
|
+
* list when `changedFilesStatus === 'fetched'`, otherwise it throws
|
|
29
|
+
* `ChangedFilesUnavailableError`. This is the single construction site for a
|
|
30
|
+
* rule context across the agent, the compiler test-runner, and tests.
|
|
31
|
+
*/
|
|
32
|
+
export declare function createRuleContext(input: CreateRuleContextInput): RuleContext;
|
|
33
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import "../rolldown-runtime-ClRpJifh.js";
|
|
2
|
+
import { $ } from "zx";
|
|
3
|
+
//#region src/rules/context.ts
|
|
4
|
+
/**
|
|
5
|
+
* Thrown when a rule reads `ctx.changedFiles` but the diff is not available
|
|
6
|
+
* (`changedFilesStatus !== 'fetched'`). `evaluateRules` re-throws this rather
|
|
7
|
+
* than folding it into a `passed=false` skip, so the job fails loudly instead
|
|
8
|
+
* of silently mis-evaluating a path-based gate.
|
|
9
|
+
*/
|
|
10
|
+
var ChangedFilesUnavailableError = class extends Error {
|
|
11
|
+
changedFilesStatus;
|
|
12
|
+
eventType;
|
|
13
|
+
constructor(status, eventType) {
|
|
14
|
+
super(`ctx.changedFiles is not available (status: ${status}` + (eventType ? `, event: ${eventType}` : "") + "). Changed files are only defined for push / pull_request events with a computable diff. Guard with ctx.changedFilesStatus before accessing, e.g. `if (ctx.changedFilesStatus !== 'fetched') return true`.");
|
|
15
|
+
this.name = "ChangedFilesUnavailableError";
|
|
16
|
+
this.changedFilesStatus = status;
|
|
17
|
+
this.eventType = eventType;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Build a RuleContext. `changedFiles` is exposed as a getter: it returns the
|
|
22
|
+
* list when `changedFilesStatus === 'fetched'`, otherwise it throws
|
|
23
|
+
* `ChangedFilesUnavailableError`. This is the single construction site for a
|
|
24
|
+
* rule context across the agent, the compiler test-runner, and tests.
|
|
25
|
+
*/
|
|
26
|
+
function createRuleContext(input) {
|
|
27
|
+
const status = input.changedFilesStatus ?? "fetched";
|
|
28
|
+
const files = input.changedFiles ?? [];
|
|
29
|
+
const eventType = typeof input.event.type === "string" ? input.event.type : void 0;
|
|
30
|
+
const base = {
|
|
31
|
+
event: input.event,
|
|
32
|
+
changedFilesStatus: status,
|
|
33
|
+
env: input.env ?? {},
|
|
34
|
+
dispatchInputs: input.dispatchInputs ?? {},
|
|
35
|
+
...input.fanout && { fanout: input.fanout },
|
|
36
|
+
$
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(base, "changedFiles", {
|
|
39
|
+
enumerable: true,
|
|
40
|
+
configurable: true,
|
|
41
|
+
get() {
|
|
42
|
+
if (status !== "fetched") throw new ChangedFilesUnavailableError(status, eventType);
|
|
43
|
+
return files;
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
return base;
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
export { ChangedFilesUnavailableError, createRuleContext };
|
|
50
|
+
|
|
51
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -6,6 +6,16 @@ import type { Rule, RuleContext, RuleResult } from './types.js';
|
|
|
6
6
|
export interface RuleEvaluationResult {
|
|
7
7
|
allPassed: boolean;
|
|
8
8
|
results: RuleResult[];
|
|
9
|
+
/**
|
|
10
|
+
* Set iff a rule's check() threw — an evaluation failure, NOT a clean
|
|
11
|
+
* `return false`. Consumers fail-hard on this (surface the error + fail the
|
|
12
|
+
* job/step) instead of treating a crashed gate as a silent skip, which would
|
|
13
|
+
* be a false green. A clean false leaves this undefined.
|
|
14
|
+
*/
|
|
15
|
+
evaluationError?: {
|
|
16
|
+
label: string;
|
|
17
|
+
message: string;
|
|
18
|
+
};
|
|
9
19
|
}
|
|
10
20
|
/**
|
|
11
21
|
* Evaluate rules sequentially with fail-fast behavior.
|
package/dist/rules/evaluator.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import "../rolldown-runtime-ClRpJifh.js";
|
|
2
|
+
import { ChangedFilesUnavailableError } from "./context.js";
|
|
2
3
|
import { toErrorMessage } from "@kici-dev/core";
|
|
3
4
|
//#region src/rules/evaluator.ts
|
|
4
5
|
/**
|
|
@@ -17,6 +18,7 @@ import { toErrorMessage } from "@kici-dev/core";
|
|
|
17
18
|
async function evaluateRules(rules, context, _label, onRuleResult) {
|
|
18
19
|
const results = [];
|
|
19
20
|
let allPassed = true;
|
|
21
|
+
let evaluationError;
|
|
20
22
|
for (const rule of rules) {
|
|
21
23
|
const startTime = Date.now();
|
|
22
24
|
let passed = false;
|
|
@@ -24,8 +26,13 @@ async function evaluateRules(rules, context, _label, onRuleResult) {
|
|
|
24
26
|
try {
|
|
25
27
|
passed = await rule.check(context);
|
|
26
28
|
} catch (e) {
|
|
29
|
+
if (e instanceof ChangedFilesUnavailableError) throw e;
|
|
27
30
|
passed = false;
|
|
28
31
|
error = toErrorMessage(e);
|
|
32
|
+
evaluationError = {
|
|
33
|
+
label: rule.label,
|
|
34
|
+
message: error
|
|
35
|
+
};
|
|
29
36
|
}
|
|
30
37
|
const durationMs = Date.now() - startTime;
|
|
31
38
|
const result = {
|
|
@@ -43,7 +50,8 @@ async function evaluateRules(rules, context, _label, onRuleResult) {
|
|
|
43
50
|
}
|
|
44
51
|
return {
|
|
45
52
|
allPassed,
|
|
46
|
-
results
|
|
53
|
+
results,
|
|
54
|
+
evaluationError
|
|
47
55
|
};
|
|
48
56
|
}
|
|
49
57
|
//#endregion
|
package/dist/rules/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { rule, skip, onlyOnFirstHost, onlyOnLastHost, onlyOnFanoutIndex } from './rule.js';
|
|
2
2
|
export { evaluateRules, type RuleEvaluationResult } from './evaluator.js';
|
|
3
|
+
export { createRuleContext, ChangedFilesUnavailableError } from './context.js';
|
|
4
|
+
export type { CreateRuleContextInput } from './context.js';
|
|
3
5
|
export type { Rule, RuleCheckFn, RuleContext, RuleResult, EventPayload } from './types.js';
|
|
4
6
|
export { isEventType } from '../events/event-payloads.js';
|
|
5
7
|
export type { EventBase, PullRequestEventPayload, PushEventPayload, TagEventPayload, CommentEventPayload, ReviewEventPayload, ReviewCommentEventPayload, ReleaseEventPayload, DispatchEventPayload, CreateEventPayload, DeleteEventPayload, StatusEventPayload, WorkflowRunEventPayload, ForkEventPayload, StarEventPayload, WatchEventPayload, WebhookEventPayload, KiciEventPayload, WorkflowCompleteEventPayload, JobCompleteEventPayload, GenericWebhookEventPayload, ScheduleEventPayload, LifecycleEventPayload, GitHubRepository, GitHubUser, GitHubPullRequest, GitHubCommit, GitHubComment, GitHubReview, GitHubRelease, } from '../events/event-payloads.js';
|
package/dist/rules/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import "../rolldown-runtime-ClRpJifh.js";
|
|
2
2
|
import { onlyOnFanoutIndex, onlyOnFirstHost, onlyOnLastHost, rule, skip } from "./rule.js";
|
|
3
|
+
import { ChangedFilesUnavailableError, createRuleContext } from "./context.js";
|
|
3
4
|
import { evaluateRules } from "./evaluator.js";
|
|
4
5
|
import { isEventType } from "../events/event-payloads.js";
|
|
5
|
-
export { evaluateRules, isEventType, onlyOnFanoutIndex, onlyOnFirstHost, onlyOnLastHost, rule, skip };
|
|
6
|
+
export { ChangedFilesUnavailableError, createRuleContext, evaluateRules, isEventType, onlyOnFanoutIndex, onlyOnFirstHost, onlyOnLastHost, rule, skip };
|