@kici-dev/compiler 0.4.0 → 0.6.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/cli.js +37 -7
- package/dist/commands/approve.d.ts +12 -0
- package/dist/commands/approve.js +5 -2
- package/dist/commands/compile.js +5 -15
- package/dist/commands/doctor.js +2 -2
- package/dist/commands/endpoints.js +4 -6
- package/dist/commands/held-run-client.d.ts +21 -1
- package/dist/commands/held-run-client.js +34 -15
- package/dist/commands/hook.js +22 -20
- package/dist/commands/index.d.ts +2 -0
- package/dist/commands/index.js +2 -1
- package/dist/commands/init.d.ts +9 -2
- package/dist/commands/init.js +43 -16
- package/dist/commands/login.js +1 -1
- package/dist/commands/orchestrators.js +3 -2
- package/dist/commands/preview.js +1 -8
- package/dist/commands/reject.d.ts +12 -0
- package/dist/commands/reject.js +5 -2
- package/dist/commands/report/collect.d.ts +82 -0
- package/dist/commands/report/collect.js +234 -0
- package/dist/commands/report/identity.d.ts +48 -0
- package/dist/commands/report/identity.js +49 -0
- package/dist/commands/report/index.d.ts +63 -0
- package/dist/commands/report/index.js +119 -0
- package/dist/commands/report/upload.d.ts +38 -0
- package/dist/commands/report/upload.js +64 -0
- package/dist/commands/run-hold-watch.js +2 -2
- package/dist/commands/run.js +6 -3
- package/dist/commands/runs/show.js +80 -1
- package/dist/commands/types.js +51 -9
- package/dist/errors/formatter.d.ts +2 -4
- package/dist/errors/formatter.js +1 -3
- package/dist/errors/index.d.ts +1 -1
- package/dist/errors/index.js +2 -2
- package/dist/execution/sdk-alias.js +4 -2
- package/dist/fixtures/compiler.js +2 -1
- package/dist/format.js +3 -3
- package/dist/generators/secrets-dts.d.ts +8 -3
- package/dist/generators/secrets-dts.js +3 -3
- package/dist/hooks/installer.js +2 -1
- package/dist/llm-context/llms-architecture.txt +59 -23
- package/dist/llm-context/llms-cli.txt +168 -34
- package/dist/llm-context/llms-features-execution.txt +2017 -0
- package/dist/llm-context/llms-features.txt +96 -1100
- package/dist/llm-context/llms-full.txt +3131 -1407
- package/dist/llm-context/llms-getting-started.txt +164 -31
- package/dist/llm-context/llms-patterns.txt +186 -7
- package/dist/llm-context/llms-providers.txt +15 -33
- package/dist/llm-context/llms-sdk-runtime.txt +62 -40
- package/dist/llm-context/llms-sdk.txt +284 -58
- package/dist/llm-context/llms.txt +23 -15
- package/dist/local-plane/paths.d.ts +15 -0
- package/dist/local-plane/paths.js +22 -1
- package/dist/local-plane/plane-manager.js +2 -2
- package/dist/local-plane/port-holder.js +1 -1
- package/dist/local-plane/postgres.d.ts +3 -16
- package/dist/local-plane/postgres.js +10 -15
- package/dist/lockfile/generator.d.ts +12 -0
- package/dist/lockfile/generator.js +182 -54
- package/dist/lockfile/index.d.ts +0 -2
- package/dist/lockfile/index.js +1 -2
- package/dist/postinstall.js +2 -1
- package/dist/remote/config.d.ts +2 -15
- package/dist/remote/config.js +2 -16
- package/dist/remote/dashboard-client.d.ts +39 -0
- package/dist/remote/dashboard-client.js +41 -0
- package/dist/remote/oauth.js +7 -5
- package/dist/remote/uploader.js +2 -2
- package/dist/templates/package-json.js +1 -1
- package/dist/test-runner/dry-run.d.ts +1 -2
- package/dist/test-runner/dry-run.js +5 -20
- package/dist/test-runner/git-detector.js +2 -1
- package/dist/test-runner/job-executor.js +2 -1
- package/dist/test-runner/payload-builder.js +11 -17
- package/dist/types.d.ts +64 -10
- package/dist/types.js +7 -1
- package/dist/validation/validator.js +63 -6
- package/package.json +16 -11
- package/sbom.spdx.json +957 -900
- package/dist/lockfile/purity-analyzer.d.ts +0 -25
- package/dist/lockfile/purity-analyzer.js +0 -204
- package/dist/lockfile/purity-diagnostics.d.ts +0 -31
- package/dist/lockfile/purity-diagnostics.js +0 -52
|
@@ -37,6 +37,18 @@ export declare const webhookActivitySchema: z.ZodObject<{
|
|
|
37
37
|
orchestratorUnavailable: z.ZodBoolean;
|
|
38
38
|
}, z.core.$strip>;
|
|
39
39
|
export type WebhookActivity = z.infer<typeof webhookActivitySchema>;
|
|
40
|
+
declare const issueReportListSchema: z.ZodObject<{
|
|
41
|
+
reports: z.ZodArray<z.ZodObject<{
|
|
42
|
+
ref: z.ZodString;
|
|
43
|
+
bundleId: z.ZodString;
|
|
44
|
+
byteSize: z.ZodNumber;
|
|
45
|
+
status: z.ZodString;
|
|
46
|
+
createdAt: z.ZodString;
|
|
47
|
+
userId: z.ZodString;
|
|
48
|
+
message: z.ZodNullable<z.ZodString>;
|
|
49
|
+
}, z.core.$strip>>;
|
|
50
|
+
}, z.core.$strip>;
|
|
51
|
+
export type IssueReportListResponse = z.infer<typeof issueReportListSchema>;
|
|
40
52
|
/** Result of {@link DashboardClient.listRegistrations}. */
|
|
41
53
|
export interface RegistrationsListResult {
|
|
42
54
|
registrations: RegistrationItem[];
|
|
@@ -104,6 +116,33 @@ export declare class DashboardClient {
|
|
|
104
116
|
* relays it without re-signing, so the artifact bytes never transit Platform.
|
|
105
117
|
*/
|
|
106
118
|
listArtifacts(runId: string): Promise<ArtifactsListResult>;
|
|
119
|
+
/**
|
|
120
|
+
* Reserve an issue-report upload slot.
|
|
121
|
+
*
|
|
122
|
+
* Returns a presigned PUT the caller uploads the bundle bytes to directly —
|
|
123
|
+
* the Platform never proxies them — plus the reference id the customer
|
|
124
|
+
* quotes to support.
|
|
125
|
+
*/
|
|
126
|
+
createIssueReport(body: {
|
|
127
|
+
bundleId: string;
|
|
128
|
+
byteSize: number;
|
|
129
|
+
sha256: string;
|
|
130
|
+
message?: string;
|
|
131
|
+
email?: string;
|
|
132
|
+
}): Promise<{
|
|
133
|
+
ref: string;
|
|
134
|
+
uploadUrl: string;
|
|
135
|
+
}>;
|
|
136
|
+
/** Tell the Platform the bytes landed, so it can verify and mark the report received. */
|
|
137
|
+
confirmIssueReport(ref: string): Promise<{
|
|
138
|
+
ref: string;
|
|
139
|
+
status: string;
|
|
140
|
+
}>;
|
|
141
|
+
listIssueReports(): Promise<IssueReportListResponse>;
|
|
142
|
+
withdrawIssueReport(ref: string): Promise<{
|
|
143
|
+
ref: string;
|
|
144
|
+
deleted: boolean;
|
|
145
|
+
}>;
|
|
107
146
|
rerunRun(runId: string): Promise<{
|
|
108
147
|
newRunId: string;
|
|
109
148
|
}>;
|
|
@@ -31,6 +31,27 @@ const webhookActivitySchema = z.object({
|
|
|
31
31
|
lockfileMissing: z.number().optional(),
|
|
32
32
|
orchestratorUnavailable: z.boolean()
|
|
33
33
|
});
|
|
34
|
+
const issueReportCreateSchema = z.object({
|
|
35
|
+
ref: z.string(),
|
|
36
|
+
uploadUrl: z.string()
|
|
37
|
+
});
|
|
38
|
+
const issueReportConfirmSchema = z.object({
|
|
39
|
+
ref: z.string(),
|
|
40
|
+
status: z.string()
|
|
41
|
+
});
|
|
42
|
+
const issueReportDeleteSchema = z.object({
|
|
43
|
+
ref: z.string(),
|
|
44
|
+
deleted: z.boolean()
|
|
45
|
+
});
|
|
46
|
+
const issueReportListSchema = z.object({ reports: z.array(z.object({
|
|
47
|
+
ref: z.string(),
|
|
48
|
+
bundleId: z.string(),
|
|
49
|
+
byteSize: z.number(),
|
|
50
|
+
status: z.string(),
|
|
51
|
+
createdAt: z.string(),
|
|
52
|
+
userId: z.string(),
|
|
53
|
+
message: z.string().nullable()
|
|
54
|
+
})) });
|
|
34
55
|
const rerunResponseSchema = z.object({ newRunId: z.string() });
|
|
35
56
|
const cancelResponseSchema = z.object({
|
|
36
57
|
cancelledJobs: z.number().optional(),
|
|
@@ -176,6 +197,26 @@ var DashboardClient = class DashboardClient {
|
|
|
176
197
|
async listArtifacts(runId) {
|
|
177
198
|
return artifactsListResultSchema.parse(await this.getJson(`/runs/${runId}/artifacts`));
|
|
178
199
|
}
|
|
200
|
+
/**
|
|
201
|
+
* Reserve an issue-report upload slot.
|
|
202
|
+
*
|
|
203
|
+
* Returns a presigned PUT the caller uploads the bundle bytes to directly —
|
|
204
|
+
* the Platform never proxies them — plus the reference id the customer
|
|
205
|
+
* quotes to support.
|
|
206
|
+
*/
|
|
207
|
+
async createIssueReport(body) {
|
|
208
|
+
return issueReportCreateSchema.parse(await this.postJson("/issue-reports", body));
|
|
209
|
+
}
|
|
210
|
+
/** Tell the Platform the bytes landed, so it can verify and mark the report received. */
|
|
211
|
+
async confirmIssueReport(ref) {
|
|
212
|
+
return issueReportConfirmSchema.parse(await this.postJson(`/issue-reports/${encodeURIComponent(ref)}/confirm`));
|
|
213
|
+
}
|
|
214
|
+
async listIssueReports() {
|
|
215
|
+
return issueReportListSchema.parse(await this.getJson("/issue-reports"));
|
|
216
|
+
}
|
|
217
|
+
async withdrawIssueReport(ref) {
|
|
218
|
+
return issueReportDeleteSchema.parse(await this.deleteJson(`/issue-reports/${encodeURIComponent(ref)}`));
|
|
219
|
+
}
|
|
179
220
|
async rerunRun(runId) {
|
|
180
221
|
return rerunResponseSchema.parse(await this.postJson(`/runs/${runId}/rerun`));
|
|
181
222
|
}
|
package/dist/remote/oauth.js
CHANGED
|
@@ -106,7 +106,7 @@ function describeListenFailure(err, listenPort) {
|
|
|
106
106
|
* @returns OIDC access token
|
|
107
107
|
*/
|
|
108
108
|
async function pkceFlow(opts) {
|
|
109
|
-
const { issuer, clientId, nudgeAfterMs = 9e4, timeoutMs =
|
|
109
|
+
const { issuer, clientId, nudgeAfterMs = 9e4, timeoutMs = 3e5 } = opts;
|
|
110
110
|
let listenPort = 0;
|
|
111
111
|
const callbackPortEnv = process.env.KICI_CALLBACK_PORT;
|
|
112
112
|
if (callbackPortEnv) {
|
|
@@ -211,10 +211,12 @@ async function pkceFlow(opts) {
|
|
|
211
211
|
if (browserCmd === "none") {
|
|
212
212
|
console.log(`KICI_AUTH_URL=${authUrl.toString()}`);
|
|
213
213
|
console.log(pc.cyan(` Waiting for browser callback on port ${actualPort}...`));
|
|
214
|
-
} else if (browserCmd)
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
214
|
+
} else if (browserCmd) {
|
|
215
|
+
const cmd = browserCmd.replace("{url}", authUrl.toString());
|
|
216
|
+
exec(cmd, (err) => {
|
|
217
|
+
if (err) console.log(pc.yellow(` Custom browser command failed: ${err.message}\n Please visit:\n ${authUrl.toString()}`));
|
|
218
|
+
});
|
|
219
|
+
} else {
|
|
218
220
|
console.log(pc.cyan(" Opening browser for authentication..."));
|
|
219
221
|
console.log(pc.dim(` If it does not open, visit:\n ${authUrl.toString()}`));
|
|
220
222
|
open(authUrl.toString()).catch(() => {
|
package/dist/remote/uploader.js
CHANGED
|
@@ -9,9 +9,9 @@ import { makeTempDir } from "@kici-dev/core/tmp";
|
|
|
9
9
|
import picomatch from "picomatch";
|
|
10
10
|
//#region src/remote/uploader.ts
|
|
11
11
|
/** Size threshold for warning (50MB) */
|
|
12
|
-
const SIZE_WARN_THRESHOLD =
|
|
12
|
+
const SIZE_WARN_THRESHOLD = 52428800;
|
|
13
13
|
/** Size threshold for hard error (500MB) */
|
|
14
|
-
const SIZE_ERROR_THRESHOLD =
|
|
14
|
+
const SIZE_ERROR_THRESHOLD = 524288e3;
|
|
15
15
|
/** Maximum upload retry attempts */
|
|
16
16
|
const MAX_RETRIES = 3;
|
|
17
17
|
/**
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { LockWorkflow } from '../types.js';
|
|
2
|
-
import type { JobPurityWarning } from '../lockfile/purity-diagnostics.js';
|
|
3
2
|
import type { WorkflowDecision } from '@kici-dev/engine';
|
|
4
3
|
interface DryRunOptions {
|
|
5
4
|
workflow?: string;
|
|
@@ -8,6 +7,6 @@ interface DryRunOptions {
|
|
|
8
7
|
/**
|
|
9
8
|
* Display dry-run output showing what would execute.
|
|
10
9
|
*/
|
|
11
|
-
export declare function displayDryRun(workflows: readonly LockWorkflow[], decisions: WorkflowDecision[], options: DryRunOptions
|
|
10
|
+
export declare function displayDryRun(workflows: readonly LockWorkflow[], decisions: WorkflowDecision[], options: DryRunOptions): void;
|
|
12
11
|
export {};
|
|
13
12
|
//# sourceMappingURL=dry-run.d.ts.map
|
|
@@ -3,26 +3,11 @@ import pc from "picocolors";
|
|
|
3
3
|
import { logger } from "@kici-dev/core";
|
|
4
4
|
//#region src/test-runner/dry-run.ts
|
|
5
5
|
/**
|
|
6
|
-
* Print the injected `__init__` job line for each impure dynamic value on a job,
|
|
7
|
-
* so the ~5-10s init-job cost is visible before the first run. Returns the number
|
|
8
|
-
* of lines rendered so the caller can count the jobs it actually surfaced.
|
|
9
|
-
*/
|
|
10
|
-
function renderJobInitWarnings(purityWarnings, workflowName, jobName) {
|
|
11
|
-
let rendered = 0;
|
|
12
|
-
for (const w of purityWarnings) {
|
|
13
|
-
if (w.workflowName !== workflowName || w.jobName !== jobName) continue;
|
|
14
|
-
logger.info(pc.yellow(` ⚠ __init__ job required (~5-10s): ${w.field} is not pure — ${w.reason}`));
|
|
15
|
-
rendered++;
|
|
16
|
-
}
|
|
17
|
-
return rendered;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
6
|
* Display dry-run output showing what would execute.
|
|
21
7
|
*/
|
|
22
|
-
function displayDryRun(workflows, decisions, options
|
|
8
|
+
function displayDryRun(workflows, decisions, options) {
|
|
23
9
|
logger.info(pc.bold("\n🔍 DRY RUN - No commands will be executed\n"));
|
|
24
10
|
const matchedWorkflows = decisions.filter((d) => d.matched);
|
|
25
|
-
const injectedInitJobs = /* @__PURE__ */ new Set();
|
|
26
11
|
if (matchedWorkflows.length === 0) {
|
|
27
12
|
logger.info(pc.yellow("No workflows matched the event.\n"));
|
|
28
13
|
displayDecisionSummary(decisions);
|
|
@@ -44,11 +29,12 @@ function displayDryRun(workflows, decisions, options, purityWarnings = []) {
|
|
|
44
29
|
logger.info(pc.gray(` runs-on: ${job.runsOn}`));
|
|
45
30
|
if (job.needs.length > 0) logger.info(pc.gray(` needs: ${job.needs.join(", ")}`));
|
|
46
31
|
if (job.rules && job.rules.length > 0) logger.info(pc.gray(` rules: ${job.rules.map((r) => r.label).join(", ")}`));
|
|
47
|
-
if (job.matrix)
|
|
48
|
-
|
|
32
|
+
if (job.matrix) {
|
|
33
|
+
if (job.matrix._type === "static" && job.matrix.values) logger.info(pc.gray(` matrix: ${JSON.stringify(job.matrix.values)}`));
|
|
34
|
+
else logger.info(pc.gray(` matrix: [dynamic]`));
|
|
35
|
+
}
|
|
49
36
|
logger.info(pc.gray(` steps (${job.steps.length}):`));
|
|
50
37
|
for (const step of job.steps) logger.info(pc.gray(` - ${step.name}`));
|
|
51
|
-
if (renderJobInitWarnings(purityWarnings, decision.workflowName, job.name) > 0) injectedInitJobs.add(`${decision.workflowName} ${job.name}`);
|
|
52
38
|
}
|
|
53
39
|
const dynamicJobs = workflow.jobs.filter((j) => j._type === "dynamic");
|
|
54
40
|
if (dynamicJobs.length > 0) {
|
|
@@ -57,7 +43,6 @@ function displayDryRun(workflows, decisions, options, purityWarnings = []) {
|
|
|
57
43
|
}
|
|
58
44
|
logger.info("");
|
|
59
45
|
}
|
|
60
|
-
if (injectedInitJobs.size > 0) logger.info(pc.yellow(`⚠ ${injectedInitJobs.size} __init__ job(s) will be injected for impure dynamic values (~5-10s each).`));
|
|
61
46
|
displayDecisionSummary(decisions);
|
|
62
47
|
logger.info(pc.green("✓ Dry run complete\n"));
|
|
63
48
|
}
|
|
@@ -12,7 +12,8 @@ import { readFile } from "node:fs/promises";
|
|
|
12
12
|
*/
|
|
13
13
|
async function detectRepoFromGit(cwd = process.cwd()) {
|
|
14
14
|
try {
|
|
15
|
-
const
|
|
15
|
+
const gitConfigPath = path.join(cwd, ".git", "config");
|
|
16
|
+
const remoteMatch = (await readFile(gitConfigPath, "utf-8")).match(/\[remote "origin"\][^[]*url\s*=\s*(.+)/m);
|
|
16
17
|
if (!remoteMatch) return null;
|
|
17
18
|
const match = remoteMatch[1].trim().match(/github\.com[:/]([^/]+)\/([^/\s]+?)(\.git)?$/);
|
|
18
19
|
if (!match) return null;
|
|
@@ -18,7 +18,8 @@ import { logger } from "@kici-dev/core";
|
|
|
18
18
|
*/
|
|
19
19
|
async function resolveSdkSetters(kiciDir) {
|
|
20
20
|
if (kiciDir) try {
|
|
21
|
-
const
|
|
21
|
+
const sdkPath = path.join(kiciDir, "node_modules", "@kici-dev", "sdk", "dist", "index.js");
|
|
22
|
+
const sdk = await import(pathToFileURL(sdkPath).href);
|
|
22
23
|
return {
|
|
23
24
|
setStepOutputsMap: sdk.setStepOutputsMap,
|
|
24
25
|
setStepRefMap: sdk.setStepRefMap,
|
|
@@ -9,8 +9,10 @@ import { readFile } from "node:fs/promises";
|
|
|
9
9
|
*/
|
|
10
10
|
function deepMerge(base, overrides) {
|
|
11
11
|
const result = { ...base };
|
|
12
|
-
for (const [key, value] of Object.entries(overrides)) if (value !== void 0 && value !== null)
|
|
13
|
-
|
|
12
|
+
for (const [key, value] of Object.entries(overrides)) if (value !== void 0 && value !== null) {
|
|
13
|
+
if (typeof value === "object" && !Array.isArray(value) && result[key] && typeof result[key] === "object") result[key] = deepMerge(result[key], value);
|
|
14
|
+
else result[key] = value;
|
|
15
|
+
}
|
|
14
16
|
return result;
|
|
15
17
|
}
|
|
16
18
|
/**
|
|
@@ -51,9 +53,7 @@ async function buildEventPayload(eventArg, options) {
|
|
|
51
53
|
case "review_comment":
|
|
52
54
|
overrides.pull_request = { head: { sha: options.sha } };
|
|
53
55
|
break;
|
|
54
|
-
case "workflow_run":
|
|
55
|
-
overrides.workflow_run = { head_sha: options.sha };
|
|
56
|
-
break;
|
|
56
|
+
case "workflow_run": overrides.workflow_run = { head_sha: options.sha };
|
|
57
57
|
}
|
|
58
58
|
if (options.branch) switch (eventType.type) {
|
|
59
59
|
case "push":
|
|
@@ -77,12 +77,10 @@ async function buildEventPayload(eventArg, options) {
|
|
|
77
77
|
case "delete":
|
|
78
78
|
overrides.ref = options.branch;
|
|
79
79
|
break;
|
|
80
|
-
case "workflow_run":
|
|
81
|
-
overrides.workflow_run
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
};
|
|
85
|
-
break;
|
|
80
|
+
case "workflow_run": overrides.workflow_run = {
|
|
81
|
+
...overrides.workflow_run ?? {},
|
|
82
|
+
head_branch: options.branch
|
|
83
|
+
};
|
|
86
84
|
}
|
|
87
85
|
if (options.pr && (eventType.type === "pull_request" || eventType.type === "review" || eventType.type === "review_comment")) {
|
|
88
86
|
overrides.number = options.pr;
|
|
@@ -137,9 +135,7 @@ async function buildEventPayload(eventArg, options) {
|
|
|
137
135
|
break;
|
|
138
136
|
case "fork":
|
|
139
137
|
case "star":
|
|
140
|
-
case "watch":
|
|
141
|
-
targetBranch = defaultBranch;
|
|
142
|
-
break;
|
|
138
|
+
case "watch": targetBranch = defaultBranch;
|
|
143
139
|
}
|
|
144
140
|
return {
|
|
145
141
|
type: eventType.type,
|
|
@@ -206,9 +202,7 @@ async function buildInternalEventPayload(eventType, options) {
|
|
|
206
202
|
timezone: eventType.timezone ?? "UTC"
|
|
207
203
|
};
|
|
208
204
|
break;
|
|
209
|
-
case "lifecycle":
|
|
210
|
-
payload = { lifecycleEvent: eventType.lifecycleEvent };
|
|
211
|
-
break;
|
|
205
|
+
case "lifecycle": payload = { lifecycleEvent: eventType.lifecycleEvent };
|
|
212
206
|
}
|
|
213
207
|
return {
|
|
214
208
|
type: eventType.type,
|
package/dist/types.d.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
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, ExecutionJobStatus, InputsDescriptorMap } from '@kici-dev/engine';
|
|
15
|
+
import type { ResourceRequest, ApproverClause, RunsOnAllPredicate, OnUnreachableMode, LabelMatcher, ExecutionJobStatus, InputsDescriptorMap, LockContentRequirement } from '@kici-dev/engine';
|
|
16
16
|
/**
|
|
17
17
|
* Normalized approval config carried in the lock file. Mirrors the engine
|
|
18
18
|
* `LockApproval` type. Produced by the compiler from an SDK `approval` config.
|
|
@@ -28,7 +28,7 @@ export interface LockApproval {
|
|
|
28
28
|
readonly when: 'always' | 'drift';
|
|
29
29
|
}
|
|
30
30
|
/** Schema version - re-exported from engine as single source of truth */
|
|
31
|
-
export declare const SCHEMA_VERSION:
|
|
31
|
+
export declare const SCHEMA_VERSION: 39;
|
|
32
32
|
/** Lock compatibility-window floor - re-exported from engine as single source of truth */
|
|
33
33
|
export declare const BREAKING_FLOOR: 30;
|
|
34
34
|
/**
|
|
@@ -58,6 +58,8 @@ export interface LockPrTrigger {
|
|
|
58
58
|
readonly sourceBranches: readonly LockBranchPattern[];
|
|
59
59
|
readonly paths: readonly string[];
|
|
60
60
|
readonly repos?: readonly LockBranchPattern[];
|
|
61
|
+
/** Declarative static content filter over source files at the event ref (AND-ed). */
|
|
62
|
+
readonly requires?: readonly LockContentRequirement[];
|
|
61
63
|
}
|
|
62
64
|
/**
|
|
63
65
|
* Push trigger in lock file.
|
|
@@ -68,6 +70,8 @@ export interface LockPushTrigger {
|
|
|
68
70
|
readonly branches: readonly LockBranchPattern[];
|
|
69
71
|
readonly paths: readonly string[];
|
|
70
72
|
readonly repos?: readonly LockBranchPattern[];
|
|
73
|
+
/** Declarative static content filter over source files at the event ref (AND-ed). */
|
|
74
|
+
readonly requires?: readonly LockContentRequirement[];
|
|
71
75
|
}
|
|
72
76
|
/**
|
|
73
77
|
* Tag trigger in lock file.
|
|
@@ -77,6 +81,8 @@ export interface LockTagTrigger {
|
|
|
77
81
|
readonly _type: 'tag';
|
|
78
82
|
readonly patterns: readonly LockBranchPattern[];
|
|
79
83
|
readonly repos?: readonly LockBranchPattern[];
|
|
84
|
+
/** Declarative static content filter over source files at the event ref (AND-ed). */
|
|
85
|
+
readonly requires?: readonly LockContentRequirement[];
|
|
80
86
|
}
|
|
81
87
|
/**
|
|
82
88
|
* Comment trigger in lock file.
|
|
@@ -389,17 +395,27 @@ export type LockStepEntry = LockStep | LockParallelStep;
|
|
|
389
395
|
/** Type guard distinguishing a parallel group from an ordinary lock step. */
|
|
390
396
|
export declare function isLockParallelStep(entry: LockStepEntry): entry is LockParallelStep;
|
|
391
397
|
/**
|
|
392
|
-
*
|
|
393
|
-
*
|
|
394
|
-
*
|
|
395
|
-
*
|
|
396
|
-
*
|
|
398
|
+
* Serialized inline expression for a dynamic env/context/concurrencyGroup
|
|
399
|
+
* field, shaped as `{ _type: 'inline', expression: '(event) => ...' }`
|
|
400
|
+
* alongside the existing 'static' and 'dynamic' discriminants.
|
|
401
|
+
*
|
|
402
|
+
* @deprecated Schema v11 inline expressions are no longer evaluated in the
|
|
403
|
+
* orchestrator. Dynamic env/context/concurrencyGroup fields are resolved on the
|
|
404
|
+
* eval agent's init-runner. The compiler no longer emits this type; readers keep
|
|
405
|
+
* recognizing it only to defer an old lock's field to the init round. Removed at
|
|
406
|
+
* the next major (v1.0.0).
|
|
397
407
|
*/
|
|
398
408
|
export interface LockInlineValue {
|
|
399
409
|
readonly _type: 'inline';
|
|
400
410
|
readonly expression: string;
|
|
401
411
|
}
|
|
402
|
-
/**
|
|
412
|
+
/**
|
|
413
|
+
* Type guard for inline expression values.
|
|
414
|
+
*
|
|
415
|
+
* @deprecated See {@link LockInlineValue}. Retained only so a reader can
|
|
416
|
+
* recognize an old lock's inline field and defer it to the eval agent's
|
|
417
|
+
* init-runner. Removed at the next major (v1.0.0).
|
|
418
|
+
*/
|
|
403
419
|
export declare function isLockInlineValue(value: unknown): value is LockInlineValue;
|
|
404
420
|
/**
|
|
405
421
|
* Static job in lock file.
|
|
@@ -448,10 +464,40 @@ export interface LockJob {
|
|
|
448
464
|
readonly checkout?: boolean;
|
|
449
465
|
/** Declarative cache specs (normalized to an array). Restored before steps / saved after the job. */
|
|
450
466
|
readonly cache?: readonly import('@kici-dev/sdk').CacheSpec[];
|
|
451
|
-
/**
|
|
467
|
+
/**
|
|
468
|
+
* Container for job execution. All steps run inside it.
|
|
469
|
+
*
|
|
470
|
+
* A bare image string, or an object naming exactly one image source: a
|
|
471
|
+
* finalized `image`, or a `dockerfile` the agent builds from the cloned tree
|
|
472
|
+
* before the job starts.
|
|
473
|
+
*
|
|
474
|
+
* Kept in step with `LockJob.container` in `@kici-dev/engine` and
|
|
475
|
+
* `ContainerConfig` in `@kici-dev/sdk` — three mirrors of one shape, and a
|
|
476
|
+
* field added to one has to be added to all three or the generator stops
|
|
477
|
+
* typechecking.
|
|
478
|
+
*/
|
|
452
479
|
readonly container?: string | {
|
|
453
|
-
image
|
|
480
|
+
/** Finalized image to pull. Exactly one of `image` / `dockerfile` is set. */
|
|
481
|
+
image?: string;
|
|
482
|
+
/** Repo-relative Dockerfile the agent builds before the job runs. */
|
|
483
|
+
dockerfile?: string;
|
|
484
|
+
/** Repo-relative build context. Defaults to the repository root. */
|
|
485
|
+
context?: string;
|
|
486
|
+
/** Build stage to stop at. */
|
|
487
|
+
target?: string;
|
|
488
|
+
/** Build arguments. Plain strings — never secret references. */
|
|
489
|
+
args?: Record<string, string>;
|
|
454
490
|
env?: Record<string, string>;
|
|
491
|
+
/** Private-registry credentials for pulling the image (flattened Sourced pairs). */
|
|
492
|
+
auth?: {
|
|
493
|
+
username?: string;
|
|
494
|
+
usernameSecret?: string;
|
|
495
|
+
usernameValue?: string;
|
|
496
|
+
tokenSecret?: string;
|
|
497
|
+
tokenValue?: string;
|
|
498
|
+
/** Registry host. Required when `dockerfile` is set. */
|
|
499
|
+
registry?: string;
|
|
500
|
+
};
|
|
455
501
|
};
|
|
456
502
|
/**
|
|
457
503
|
* Bound contexts in merge order. Each entry is a static name or inline
|
|
@@ -589,6 +635,13 @@ export interface LockWorkflow {
|
|
|
589
635
|
readonly timeout?: number;
|
|
590
636
|
/** Normalized approval gate; when set the whole run is held before any job dispatches. */
|
|
591
637
|
readonly approval?: LockApproval;
|
|
638
|
+
/**
|
|
639
|
+
* True when the workflow declares a `filter` predicate. A bare flag, not a
|
|
640
|
+
* source reference: `LockWorkflow.source` already identifies the module and
|
|
641
|
+
* export, so the eval agent loads it and reads `.filter` off the workflow
|
|
642
|
+
* object. Mirrors the `dynamicEnv` / `dynamicConcurrencyGroup` convention.
|
|
643
|
+
*/
|
|
644
|
+
readonly hasFilter?: boolean;
|
|
592
645
|
}
|
|
593
646
|
/**
|
|
594
647
|
* Complete lock file structure.
|
|
@@ -602,6 +655,7 @@ export interface LockWorkflow {
|
|
|
602
655
|
* v8 adds runsOn polymorphic type (string | string[] | selector) and excludeLabels.
|
|
603
656
|
* v11 adds LockInlineValue type for pure function inline evaluation.
|
|
604
657
|
* v13 adds job-level and workflow-level timeout.
|
|
658
|
+
* v34 adds LockWorkflow.hasFilter (workflow-level pre-dispatch filter predicate).
|
|
605
659
|
*/
|
|
606
660
|
export interface LockFile {
|
|
607
661
|
readonly schemaVersion: typeof SCHEMA_VERSION;
|
package/dist/types.js
CHANGED
|
@@ -23,7 +23,13 @@ const BREAKING_FLOOR = BREAKING_FLOOR$1;
|
|
|
23
23
|
function isLockParallelStep(entry) {
|
|
24
24
|
return entry.kind === "parallel";
|
|
25
25
|
}
|
|
26
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Type guard for inline expression values.
|
|
28
|
+
*
|
|
29
|
+
* @deprecated See {@link LockInlineValue}. Retained only so a reader can
|
|
30
|
+
* recognize an old lock's inline field and defer it to the eval agent's
|
|
31
|
+
* init-runner. Removed at the next major (v1.0.0).
|
|
32
|
+
*/
|
|
27
33
|
function isLockInlineValue(value) {
|
|
28
34
|
return typeof value === "object" && value !== null && value._type === "inline";
|
|
29
35
|
}
|
|
@@ -75,6 +75,65 @@ function validateWorkflow(workflow, workflowFile) {
|
|
|
75
75
|
const matrixErrors = validateStaticMatrix(job, workflow.name, workflowFile);
|
|
76
76
|
errors.push(...matrixErrors);
|
|
77
77
|
}
|
|
78
|
+
for (const job of staticJobs) errors.push(...validateInvokeJob(job, jobLoc(job.name)));
|
|
79
|
+
errors.push(...validateGlobalApproval(workflow, staticJobs, workflowFile));
|
|
80
|
+
return errors;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Validate an invoke-gate job. A gate is mutually exclusive with step code and
|
|
84
|
+
* must name a non-empty event. Defense-in-depth: the SDK `job()` factory already
|
|
85
|
+
* enforces these, but a generated job could reach the compiler carrying them.
|
|
86
|
+
*/
|
|
87
|
+
function validateInvokeJob(job, location) {
|
|
88
|
+
if (!job.invoke) return [];
|
|
89
|
+
const errors = [];
|
|
90
|
+
if ((job.steps?.length ?? 0) > 0) errors.push(compilerError("E125", `job '${job.name}': 'invoke' cannot be combined with 'steps'`, {
|
|
91
|
+
location,
|
|
92
|
+
suggestion: "An invoke gate never runs steps — remove `steps`/`run`, or drop `invoke`."
|
|
93
|
+
}));
|
|
94
|
+
if (typeof job.invoke.event !== "string" || job.invoke.event.trim().length === 0) errors.push(compilerError("E126", `job '${job.name}': invoke.event must be a non-empty event name`, {
|
|
95
|
+
location,
|
|
96
|
+
suggestion: "Pass a kici event name to invokeSource(), e.g. invokeSource(\"myorg.repo-tests\")."
|
|
97
|
+
}));
|
|
98
|
+
return errors;
|
|
99
|
+
}
|
|
100
|
+
/** True when any trigger carries `repos:`, which is what makes a workflow global. */
|
|
101
|
+
function isGlobalWorkflow(workflow) {
|
|
102
|
+
return (Array.isArray(workflow.on) ? workflow.on : workflow.on ? [workflow.on] : []).some((trigger) => {
|
|
103
|
+
const repos = trigger.repos;
|
|
104
|
+
return Array.isArray(repos) && repos.length > 0;
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Refuse an `approval` gate on an organization-wide workflow.
|
|
109
|
+
*
|
|
110
|
+
* The approval hold is applied by the per-repository dispatch path. The global
|
|
111
|
+
* path builds its job inputs and dispatches them without ever consulting the
|
|
112
|
+
* gate, so an `approval` declared on a global workflow is **silently ignored**:
|
|
113
|
+
* the job runs immediately and the author believes a human had to release it.
|
|
114
|
+
*
|
|
115
|
+
* Failing the compile rather than warning is the right direction for a gate an
|
|
116
|
+
* author is relying on. A silently-ignored approval is not a cosmetic problem —
|
|
117
|
+
* it is a security control the workflow claims to have and does not.
|
|
118
|
+
*
|
|
119
|
+
* Only static jobs and the workflow level are reachable here; a generated job
|
|
120
|
+
* carrying `approval` is caught at dispatch, which is the only place it exists.
|
|
121
|
+
*/
|
|
122
|
+
function validateGlobalApproval(workflow, staticJobs, workflowFile) {
|
|
123
|
+
if (!isGlobalWorkflow(workflow)) return [];
|
|
124
|
+
const errors = [];
|
|
125
|
+
const suggestion = "Approval gates are supported on per-repository workflows only. Drop `approval`, or move the gated jobs into a workflow whose triggers carry no `repos:`.";
|
|
126
|
+
if (workflow.approval) errors.push(compilerError("E124", `Workflow "${workflow.name}" declares \`approval\` and is organization-wide (a trigger carries \`repos:\`). The approval would never be enforced.`, {
|
|
127
|
+
location: locationForWorkflow(workflowFile),
|
|
128
|
+
suggestion
|
|
129
|
+
}));
|
|
130
|
+
for (const job of staticJobs) {
|
|
131
|
+
if (!job.approval) continue;
|
|
132
|
+
errors.push(compilerError("E124", `Job "${job.name}" in organization-wide workflow "${workflow.name}" declares \`approval\`. The approval would never be enforced.`, {
|
|
133
|
+
location: locationForJob(job, workflowFile),
|
|
134
|
+
suggestion
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
78
137
|
return errors;
|
|
79
138
|
}
|
|
80
139
|
/**
|
|
@@ -112,12 +171,10 @@ function validateJobDag(jobs, workflowName, jobLoc, dynamicGroupNames) {
|
|
|
112
171
|
suggestion: "Remove the self-dependency"
|
|
113
172
|
}));
|
|
114
173
|
break;
|
|
115
|
-
case "missing-dependency":
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}));
|
|
120
|
-
break;
|
|
174
|
+
case "missing-dependency": errors.push(compilerError("E101", `Job "${result.nodeId}" in workflow "${workflowName}" depends on non-existent job "${result.missingDep}"`, {
|
|
175
|
+
location: dagNodeLocation(result.nodeId, jobLoc),
|
|
176
|
+
suggestion: `Create a job named "${result.missingDep}" or fix the dependency name`
|
|
177
|
+
}));
|
|
121
178
|
}
|
|
122
179
|
return errors;
|
|
123
180
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kici-dev/compiler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
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",
|
|
@@ -46,28 +46,33 @@
|
|
|
46
46
|
"./cli": "./dist/cli.js"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@inquirer/prompts": "^8.
|
|
49
|
+
"@inquirer/prompts": "^8.7.0",
|
|
50
|
+
"archiver": "^8.0.0",
|
|
50
51
|
"chokidar": "^5.0.0",
|
|
51
52
|
"commander": "^15.0.0",
|
|
52
53
|
"embedded-postgres": "18.4.0-beta.17",
|
|
53
54
|
"fast-glob": "^3.3.3",
|
|
54
|
-
"open": "
|
|
55
|
+
"open": "11.0.0",
|
|
55
56
|
"picocolors": "^1.1.1",
|
|
56
|
-
"picomatch": "^4.0.
|
|
57
|
-
"tar": "^7.5.
|
|
57
|
+
"picomatch": "^4.0.7",
|
|
58
|
+
"tar": "^7.5.22",
|
|
58
59
|
"typescript": "^6.0.3",
|
|
59
|
-
"ws": "^8.21.
|
|
60
|
+
"ws": "^8.21.3",
|
|
60
61
|
"wsl-utils": "^0.3.0",
|
|
61
62
|
"yaml": "^2.9.0",
|
|
62
63
|
"zod": "^4.4.3",
|
|
63
64
|
"zx": "^8.8.5",
|
|
64
|
-
"@kici-dev/agent": "0.
|
|
65
|
-
"@kici-dev/core": "0.
|
|
66
|
-
"@kici-dev/engine": "0.
|
|
67
|
-
"@kici-dev/orchestrator": "0.
|
|
65
|
+
"@kici-dev/agent": "0.6.0",
|
|
66
|
+
"@kici-dev/core": "0.6.0",
|
|
67
|
+
"@kici-dev/engine": "0.6.0",
|
|
68
|
+
"@kici-dev/orchestrator": "0.6.0"
|
|
69
|
+
},
|
|
70
|
+
"devDependencies": {
|
|
71
|
+
"@types/archiver": "^8.0.0",
|
|
72
|
+
"jszip": "^3.10.1"
|
|
68
73
|
},
|
|
69
74
|
"peerDependencies": {
|
|
70
|
-
"@kici-dev/sdk": "0.
|
|
75
|
+
"@kici-dev/sdk": "0.6.0"
|
|
71
76
|
},
|
|
72
77
|
"scripts": {
|
|
73
78
|
"build": "node ../../scripts/build-ts.mjs && tsgo --emitDeclarationOnly",
|