@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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import "../rolldown-runtime-ClRpJifh.js";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import fs from "node:fs";
|
|
3
4
|
import os from "node:os";
|
|
4
5
|
//#region src/local-plane/paths.ts
|
|
5
6
|
/**
|
|
@@ -11,10 +12,30 @@ const PLANE_STAMP_VERSION = 3;
|
|
|
11
12
|
/**
|
|
12
13
|
* Root directory of the local dev plane's state, following the same
|
|
13
14
|
* `KICI_CONFIG_DIR` → `~/.kici` convention the rest of the CLI uses.
|
|
15
|
+
*
|
|
16
|
+
* RESOLVED THROUGH SYMLINKS, deliberately. The plane is a singleton on fixed
|
|
17
|
+
* ports, so a caller may reach it through a config dir that only symlinks
|
|
18
|
+
* `local` at the durable one — `pnpm deploy:stg` does exactly that, to run
|
|
19
|
+
* against a throwaway config dir carrying no credentials while still reusing
|
|
20
|
+
* the warm plane.
|
|
21
|
+
*
|
|
22
|
+
* Without resolving, the plane's Postgres is started with a data directory
|
|
23
|
+
* addressed through that ephemeral path and keeps it open. When the caller
|
|
24
|
+
* removes its temp dir, Postgres PANICs — `could not open file
|
|
25
|
+
* "<tmp>/local/pgdata/global/pg_control"` — and shuts the whole plane down,
|
|
26
|
+
* taking every later phase with it. Resolving first means Postgres only ever
|
|
27
|
+
* sees the durable path, so a caller's temp dir can come and go beneath it.
|
|
28
|
+
*
|
|
29
|
+
* A path that does not exist yet resolves to itself: a fresh plane creates it.
|
|
14
30
|
*/
|
|
15
31
|
function planeRoot() {
|
|
16
32
|
const base = process.env.KICI_CONFIG_DIR ?? path.join(os.homedir(), ".kici");
|
|
17
|
-
|
|
33
|
+
const root = path.join(base, "local");
|
|
34
|
+
try {
|
|
35
|
+
return fs.realpathSync(root);
|
|
36
|
+
} catch {
|
|
37
|
+
return root;
|
|
38
|
+
}
|
|
18
39
|
}
|
|
19
40
|
/**
|
|
20
41
|
* Absolute paths for every artefact the plane persists under its state root.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* `TS_CONST_PINS` in hack/containers-lock.ts (identity
|
|
4
4
|
* docker.io/library/postgres+alpine), so a lock bump rewrites it in place.
|
|
5
5
|
*/
|
|
6
|
-
export declare const PLANE_PG_IMAGE = "docker.io/library/postgres:18.
|
|
6
|
+
export declare const PLANE_PG_IMAGE = "docker.io/library/postgres:18.6-alpine";
|
|
7
7
|
/** Name of the fallback podman Postgres container. */
|
|
8
8
|
export declare const PLANE_PG_CONTAINER = "kici-local-postgres";
|
|
9
9
|
export type PlanePgHandle = {
|
|
@@ -11,13 +11,6 @@ export type PlanePgHandle = {
|
|
|
11
11
|
kind: 'embedded' | 'podman';
|
|
12
12
|
stop(): Promise<void>;
|
|
13
13
|
};
|
|
14
|
-
declare let readyPoller: typeof defaultReadyPoller;
|
|
15
|
-
/** Test seam: override the podman readiness poller. */
|
|
16
|
-
export declare function __setReadyPollerForTest(fn: typeof readyPoller): void;
|
|
17
|
-
declare let embeddedDaemon: typeof defaultEmbeddedDaemon;
|
|
18
|
-
/** Test seam: override the embedded postmaster daemonizer. */
|
|
19
|
-
export declare function __setEmbeddedDaemonForTest(fn: typeof embeddedDaemon): void;
|
|
20
|
-
declare function defaultReadyPoller(_port: number): Promise<boolean>;
|
|
21
14
|
/**
|
|
22
15
|
* Whether this plane's embedded postmaster is already serving `port`.
|
|
23
16
|
*
|
|
@@ -31,13 +24,6 @@ declare function defaultReadyPoller(_port: number): Promise<boolean>;
|
|
|
31
24
|
* one this plane can reuse.
|
|
32
25
|
*/
|
|
33
26
|
export declare function embeddedClusterIsServing(port: number): Promise<boolean>;
|
|
34
|
-
/**
|
|
35
|
-
* Start a detached embedded postmaster via `pg_ctl` so it survives the exit of
|
|
36
|
-
* this CLI process (embedded-postgres's in-process server is killed by its own
|
|
37
|
-
* exit hook, so it cannot back a warm plane). A cluster already serving this
|
|
38
|
-
* plane's port is reused as-is.
|
|
39
|
-
*/
|
|
40
|
-
declare function defaultEmbeddedDaemon(port: number): Promise<void>;
|
|
41
27
|
/**
|
|
42
28
|
* Stop the plane's Postgres by backend kind. Handle-independent so a separate
|
|
43
29
|
* CLI invocation (`kici local down`) can tear down what `up` started.
|
|
@@ -51,6 +37,7 @@ export declare function stopPlanePostgres(kind: 'embedded' | 'podman'): Promise<
|
|
|
51
37
|
*/
|
|
52
38
|
export declare function startPlanePostgres(opts?: {
|
|
53
39
|
forcePodman?: boolean;
|
|
40
|
+
readyPoller?: (port: number) => Promise<boolean>;
|
|
41
|
+
embeddedDaemon?: (port: number) => Promise<void>;
|
|
54
42
|
}): Promise<PlanePgHandle>;
|
|
55
|
-
export {};
|
|
56
43
|
//# sourceMappingURL=postgres.d.ts.map
|
|
@@ -13,19 +13,9 @@ import EmbeddedPostgres from "embedded-postgres";
|
|
|
13
13
|
* `TS_CONST_PINS` in hack/containers-lock.ts (identity
|
|
14
14
|
* docker.io/library/postgres+alpine), so a lock bump rewrites it in place.
|
|
15
15
|
*/
|
|
16
|
-
const PLANE_PG_IMAGE = "docker.io/library/postgres:18.
|
|
16
|
+
const PLANE_PG_IMAGE = "docker.io/library/postgres:18.6-alpine";
|
|
17
17
|
/** Name of the fallback podman Postgres container. */
|
|
18
18
|
const PLANE_PG_CONTAINER = "kici-local-postgres";
|
|
19
|
-
let readyPoller = defaultReadyPoller;
|
|
20
|
-
/** Test seam: override the podman readiness poller. */
|
|
21
|
-
function __setReadyPollerForTest(fn) {
|
|
22
|
-
readyPoller = fn;
|
|
23
|
-
}
|
|
24
|
-
let embeddedDaemon = defaultEmbeddedDaemon;
|
|
25
|
-
/** Test seam: override the embedded postmaster daemonizer. */
|
|
26
|
-
function __setEmbeddedDaemonForTest(fn) {
|
|
27
|
-
embeddedDaemon = fn;
|
|
28
|
-
}
|
|
29
19
|
async function defaultReadyPoller(_port) {
|
|
30
20
|
for (let i = 0; i < 60; i++) try {
|
|
31
21
|
await $`podman exec ${PLANE_PG_CONTAINER} pg_isready -U kici`.quiet();
|
|
@@ -56,7 +46,8 @@ function resolvePgCtl() {
|
|
|
56
46
|
"win32:x64": "@embedded-postgres/windows-x64"
|
|
57
47
|
}[`${platform}:${arch}`];
|
|
58
48
|
if (!pkg) throw new Error(`unsupported platform for embedded Postgres: ${platform}/${arch}`);
|
|
59
|
-
const
|
|
49
|
+
const require = createRequire(import.meta.url);
|
|
50
|
+
const entry = createRequire(require.resolve("embedded-postgres")).resolve(pkg);
|
|
60
51
|
const binName = platform === "win32" ? "pg_ctl.exe" : "pg_ctl";
|
|
61
52
|
return path.resolve(path.dirname(entry), "..", "native", "bin", binName);
|
|
62
53
|
}
|
|
@@ -119,13 +110,15 @@ async function embeddedClusterIsServing(port) {
|
|
|
119
110
|
async function defaultEmbeddedDaemon(port) {
|
|
120
111
|
const { pgData, logFile } = planePaths();
|
|
121
112
|
if (await embeddedClusterIsServing(port)) return;
|
|
122
|
-
|
|
113
|
+
const pgCtl = resolvePgCtl();
|
|
114
|
+
await $`${pgCtl} -D ${pgData} -o ${`-p ${port}`} -l ${`${logFile}.pg`} -w start`.quiet();
|
|
123
115
|
}
|
|
124
116
|
/** Stop the detached embedded postmaster (handle-independent, reads the data dir). */
|
|
125
117
|
async function stopEmbeddedDaemon() {
|
|
126
118
|
const { pgData } = planePaths();
|
|
127
119
|
if (!fs.existsSync(path.join(pgData, "postmaster.pid"))) return;
|
|
128
|
-
|
|
120
|
+
const pgCtl = resolvePgCtl();
|
|
121
|
+
await $`${pgCtl} -D ${pgData} stop -m fast`.quiet().catch(() => {});
|
|
129
122
|
}
|
|
130
123
|
/**
|
|
131
124
|
* Stop the plane's Postgres by backend kind. Handle-independent so a separate
|
|
@@ -142,6 +135,8 @@ async function stopPlanePostgres(kind) {
|
|
|
142
135
|
* (or when forced via `forcePodman` / `KICI_LOCAL_PG_MODE=podman`).
|
|
143
136
|
*/
|
|
144
137
|
async function startPlanePostgres(opts = {}) {
|
|
138
|
+
const readyPoller = opts.readyPoller ?? defaultReadyPoller;
|
|
139
|
+
const embeddedDaemon = opts.embeddedDaemon ?? defaultEmbeddedDaemon;
|
|
145
140
|
const { postgres: port } = planePorts();
|
|
146
141
|
const url = `postgres://kici:kici@127.0.0.1:${port}/kici_local`;
|
|
147
142
|
if (!(opts.forcePodman || process.env.KICI_LOCAL_PG_MODE === "podman")) try {
|
|
@@ -182,6 +177,6 @@ async function startPlanePostgres(opts = {}) {
|
|
|
182
177
|
};
|
|
183
178
|
}
|
|
184
179
|
//#endregion
|
|
185
|
-
export { PLANE_PG_CONTAINER, PLANE_PG_IMAGE,
|
|
180
|
+
export { PLANE_PG_CONTAINER, PLANE_PG_IMAGE, embeddedClusterIsServing, startPlanePostgres, stopPlanePostgres };
|
|
186
181
|
|
|
187
182
|
//# sourceMappingURL=postgres.js.map
|
|
@@ -36,6 +36,18 @@ export declare function detectGitRoot(): string;
|
|
|
36
36
|
* For yarn the prefix also carries the flavor (`yarn-classic` / `yarn-berry`),
|
|
37
37
|
* so a classic-layout dep-cache tarball is never restored into a berry install.
|
|
38
38
|
*
|
|
39
|
+
* The detected manager only orders the search; it never restricts it. Detection
|
|
40
|
+
* falls back to the AMBIENT `npm_config_user_agent` when a directory carries no
|
|
41
|
+
* signal of its own, so a repo whose root holds neither a `package.json` nor a
|
|
42
|
+
* lockfile is reported as whatever manager happened to invoke the compiler. A
|
|
43
|
+
* repo compiled under pnpm that keeps an npm `.kici/package-lock.json` was
|
|
44
|
+
* therefore searched for `pnpm-lock.yaml` only, found none, and got no hash at
|
|
45
|
+
* all — which silently disables the orchestrator's dependency cache for it, so
|
|
46
|
+
* every agent installs from the registry and an agent with no registry route
|
|
47
|
+
* cannot run the job at all. Searching every manager's candidates after the
|
|
48
|
+
* detected one's keeps the detected manager authoritative where it has real
|
|
49
|
+
* evidence, and still finds the lockfile that is actually on disk.
|
|
50
|
+
*
|
|
39
51
|
* @param gitRoot - Absolute path to git repository root
|
|
40
52
|
* @returns Hex SHA-256 hash string, or null if no lockfile is found
|
|
41
53
|
*/
|
|
@@ -5,13 +5,13 @@ import "../errors/index.js";
|
|
|
5
5
|
import { BREAKING_FLOOR as BREAKING_FLOOR$1, SCHEMA_VERSION as SCHEMA_VERSION$1 } from "../types.js";
|
|
6
6
|
import { computeContentHash } from "./hasher.js";
|
|
7
7
|
import { resolveHashFiles } from "./hash-files.js";
|
|
8
|
-
import { analyzePurity } from "./purity-analyzer.js";
|
|
9
8
|
import path from "node:path";
|
|
10
9
|
import { readFileSync } from "node:fs";
|
|
11
10
|
import { getDynamicJobGroup, getDynamicJobNeeds, isDynamicFunction, isDynamicGroupRef, isDynamicJobFn, isParallelGroup, isStaticArray, isStaticObject, normalizeApproval, normalizeCacheSpecs } from "@kici-dev/sdk";
|
|
12
11
|
import { sha256 } from "@kici-dev/core";
|
|
13
12
|
import { PackageManager, detectPackageManagerSync, detectYarnFlavorSync } from "@kici-dev/core/package-manager";
|
|
14
|
-
import { assertScheduleInputsSatisfiable, extractInputsDescriptorMap, resolveWhenToRunOn, validateResourceRequest } from "@kici-dev/engine";
|
|
13
|
+
import { assertScheduleInputsSatisfiable, extractInputsDescriptorMap, resolveContentFormat, resolveWhenToRunOn, validateResourceRequest } from "@kici-dev/engine";
|
|
14
|
+
import { assertSafeRegex } from "@kici-dev/engine/safe-regex";
|
|
15
15
|
import { normalizeRunsOnAllToMatchers, normalizeRunsOnToMatchers, runsOnPickFromInput } from "@kici-dev/engine/labels/compile";
|
|
16
16
|
import { execSync } from "node:child_process";
|
|
17
17
|
//#region src/lockfile/generator.ts
|
|
@@ -66,16 +66,39 @@ function detectGitRoot() {
|
|
|
66
66
|
* For yarn the prefix also carries the flavor (`yarn-classic` / `yarn-berry`),
|
|
67
67
|
* so a classic-layout dep-cache tarball is never restored into a berry install.
|
|
68
68
|
*
|
|
69
|
+
* The detected manager only orders the search; it never restricts it. Detection
|
|
70
|
+
* falls back to the AMBIENT `npm_config_user_agent` when a directory carries no
|
|
71
|
+
* signal of its own, so a repo whose root holds neither a `package.json` nor a
|
|
72
|
+
* lockfile is reported as whatever manager happened to invoke the compiler. A
|
|
73
|
+
* repo compiled under pnpm that keeps an npm `.kici/package-lock.json` was
|
|
74
|
+
* therefore searched for `pnpm-lock.yaml` only, found none, and got no hash at
|
|
75
|
+
* all — which silently disables the orchestrator's dependency cache for it, so
|
|
76
|
+
* every agent installs from the registry and an agent with no registry route
|
|
77
|
+
* cannot run the job at all. Searching every manager's candidates after the
|
|
78
|
+
* detected one's keeps the detected manager authoritative where it has real
|
|
79
|
+
* evidence, and still finds the lockfile that is actually on disk.
|
|
80
|
+
*
|
|
69
81
|
* @param gitRoot - Absolute path to git repository root
|
|
70
82
|
* @returns Hex SHA-256 hash string, or null if no lockfile is found
|
|
71
83
|
*/
|
|
72
84
|
function computeLockfileHash(gitRoot) {
|
|
73
85
|
const pm = detectPackageManagerSync(gitRoot) === PackageManager.Npm ? detectPackageManagerSync(path.join(gitRoot, ".kici")) : detectPackageManagerSync(gitRoot);
|
|
74
|
-
const
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
86
|
+
const byManager = (m) => m === PackageManager.Pnpm ? [path.join(gitRoot, "pnpm-lock.yaml"), path.join(gitRoot, ".kici", "pnpm-lock.yaml")] : m === PackageManager.Yarn ? [path.join(gitRoot, "yarn.lock"), path.join(gitRoot, ".kici", "yarn.lock")] : [path.join(gitRoot, ".kici", "package-lock.json")];
|
|
87
|
+
const order = [pm, ...[
|
|
88
|
+
PackageManager.Npm,
|
|
89
|
+
PackageManager.Pnpm,
|
|
90
|
+
PackageManager.Yarn
|
|
91
|
+
].filter((m) => m !== pm)];
|
|
92
|
+
for (const manager of order) for (const lockfilePath of byManager(manager)) {
|
|
93
|
+
let content;
|
|
94
|
+
try {
|
|
95
|
+
content = readFileSync(lockfilePath, "utf-8");
|
|
96
|
+
} catch {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const prefix = manager === PackageManager.Yarn ? `${manager}-${detectYarnFlavorSync(path.dirname(lockfilePath))}` : manager;
|
|
100
|
+
return sha256(`${prefix}\n${content}`);
|
|
101
|
+
}
|
|
79
102
|
return null;
|
|
80
103
|
}
|
|
81
104
|
/**
|
|
@@ -112,16 +135,17 @@ function generateLockFile(workflowsWithSource) {
|
|
|
112
135
|
const workflows = workflowsWithSource.map(({ workflow, source, bundleSource }) => {
|
|
113
136
|
return transformWorkflow(workflow, path.relative(gitRoot, source.file).replaceAll("\\", "/"), formatExportRef(source), bundleSource, gitRoot);
|
|
114
137
|
});
|
|
138
|
+
const contentHash = sha256(JSON.stringify({
|
|
139
|
+
schemaVersion: SCHEMA_VERSION$1,
|
|
140
|
+
minReaderVersion: BREAKING_FLOOR$1,
|
|
141
|
+
source: topLevelSource,
|
|
142
|
+
workflows
|
|
143
|
+
}));
|
|
115
144
|
return {
|
|
116
145
|
schemaVersion: SCHEMA_VERSION$1,
|
|
117
146
|
minReaderVersion: BREAKING_FLOOR$1,
|
|
118
147
|
source: topLevelSource,
|
|
119
|
-
contentHash
|
|
120
|
-
schemaVersion: SCHEMA_VERSION$1,
|
|
121
|
-
minReaderVersion: BREAKING_FLOOR$1,
|
|
122
|
-
source: topLevelSource,
|
|
123
|
-
workflows
|
|
124
|
-
})),
|
|
148
|
+
contentHash,
|
|
125
149
|
...lockfileHash && { lockfileHash },
|
|
126
150
|
workflows
|
|
127
151
|
};
|
|
@@ -178,7 +202,8 @@ function transformWorkflow(workflow, sourceFile, exportRef, bundleSource, gitRoo
|
|
|
178
202
|
...workflow.concurrency.max !== void 0 && { max: workflow.concurrency.max }
|
|
179
203
|
} },
|
|
180
204
|
...workflow.timeout !== void 0 && { timeout: workflow.timeout },
|
|
181
|
-
...workflow.approval !== void 0 && { approval: (assertNonStepApprovalScope(workflow.approval, "workflow", locationForWorkflow(sourceFile)), toLockApproval(workflow.approval)) }
|
|
205
|
+
...workflow.approval !== void 0 && { approval: (assertNonStepApprovalScope(workflow.approval, "workflow", locationForWorkflow(sourceFile)), toLockApproval(workflow.approval)) },
|
|
206
|
+
...typeof workflow.filter === "function" && { hasFilter: true }
|
|
182
207
|
};
|
|
183
208
|
}
|
|
184
209
|
/**
|
|
@@ -189,6 +214,116 @@ function transformWorkflow(workflow, sourceFile, exportRef, bundleSource, gitRoo
|
|
|
189
214
|
function reposField(trigger) {
|
|
190
215
|
return trigger.repos && trigger.repos.length > 0 ? { repos: transformBranchPatterns(trigger.repos) } : {};
|
|
191
216
|
}
|
|
217
|
+
/** Normalize a scalar-or-array SDK value to an array. */
|
|
218
|
+
function toArray(v) {
|
|
219
|
+
if (v === void 0) return [];
|
|
220
|
+
return Array.isArray(v) ? v : [v];
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Normalize one regex entry to the lock's `/pattern/flags` form, rejecting an
|
|
224
|
+
* invalid or ReDoS-prone pattern at compile time so a catastrophic pattern fails
|
|
225
|
+
* `kici compile` with author feedback instead of reaching the orchestrator.
|
|
226
|
+
*/
|
|
227
|
+
function serializeRegexEntry(entry, ctx) {
|
|
228
|
+
const source = entry instanceof RegExp ? `/${entry.source}/${entry.flags}` : entry;
|
|
229
|
+
const wrapped = /^\/(.+)\/([gimsuy]*)$/.exec(source);
|
|
230
|
+
const pattern = wrapped ? wrapped[1] : source;
|
|
231
|
+
const flags = wrapped ? wrapped[2] : "";
|
|
232
|
+
let re;
|
|
233
|
+
try {
|
|
234
|
+
re = new RegExp(pattern, flags);
|
|
235
|
+
} catch (err) {
|
|
236
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
237
|
+
throw compilerError("E123", `${ctx}: invalid regex — ${reason}`);
|
|
238
|
+
}
|
|
239
|
+
try {
|
|
240
|
+
assertSafeRegex(re.source, re.flags, ctx);
|
|
241
|
+
} catch (err) {
|
|
242
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
243
|
+
throw compilerError("E123", `${ctx}: ${reason}`);
|
|
244
|
+
}
|
|
245
|
+
return `/${re.source}/${re.flags}`;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Serialize a {@link TextMatch} to its lock form: every key normalized to a flat
|
|
249
|
+
* array, every regex to `/pattern/flags`, every degenerate shape rejected.
|
|
250
|
+
*
|
|
251
|
+
* `ctx` is a human label woven into any thrown error (e.g. `push trigger
|
|
252
|
+
* 'commitMessage'`, `requires 'Dockerfile'`).
|
|
253
|
+
*/
|
|
254
|
+
function serializeTextMatch(m, ctx) {
|
|
255
|
+
const contains = toArray(m.contains);
|
|
256
|
+
const notContains = toArray(m.notContains);
|
|
257
|
+
const matches = toArray(m.matches);
|
|
258
|
+
const notMatches = toArray(m.notMatches);
|
|
259
|
+
if (contains.length === 0 && notContains.length === 0 && matches.length === 0 && notMatches.length === 0) throw compilerError("E122", `${ctx}: no query key (contains/notContains/matches/notMatches) — nothing to check`);
|
|
260
|
+
for (const needle of [...contains, ...notContains]) if (needle === "") throw compilerError("E122", `${ctx}: an empty needle matches every text — remove it`);
|
|
261
|
+
if (m.ignoreCase !== void 0 && contains.length === 0 && notContains.length === 0) throw compilerError("E122", `${ctx}: 'ignoreCase' affects only contains/notContains — a regex carries its own flags`);
|
|
262
|
+
return {
|
|
263
|
+
...contains.length > 0 && { contains: [...contains] },
|
|
264
|
+
...notContains.length > 0 && { notContains: [...notContains] },
|
|
265
|
+
...matches.length > 0 && { matches: matches.map((e) => serializeRegexEntry(e, ctx)) },
|
|
266
|
+
...notMatches.length > 0 && { notMatches: notMatches.map((e) => serializeRegexEntry(e, ctx)) },
|
|
267
|
+
...m.ignoreCase !== void 0 && { ignoreCase: m.ignoreCase }
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
/** Emit the optional `commitMessage` field for a git-event lock trigger. */
|
|
271
|
+
function commitMessageField(commitMessage, triggerLabel) {
|
|
272
|
+
if (commitMessage === void 0) return {};
|
|
273
|
+
return { commitMessage: serializeTextMatch(commitMessage, `${triggerLabel} 'commitMessage'`) };
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Serialize one SDK content requirement into its lock form, validating and
|
|
277
|
+
* resolving `format: 'auto'` by extension at compile time. A malformed filter
|
|
278
|
+
* throws (fails `kici compile`) rather than reaching the orchestrator.
|
|
279
|
+
*/
|
|
280
|
+
function serializeOneRequirement(req) {
|
|
281
|
+
const hasJsonQuery = req.exists !== void 0 && req.exists.length > 0 || req.match !== void 0 && Object.keys(req.match).length > 0 || req.not !== void 0 && Object.keys(req.not).length > 0;
|
|
282
|
+
const textKeys = {
|
|
283
|
+
...req.contains !== void 0 && { contains: req.contains },
|
|
284
|
+
...req.notContains !== void 0 && { notContains: req.notContains },
|
|
285
|
+
...req.matches !== void 0 && { matches: req.matches },
|
|
286
|
+
...req.notMatches !== void 0 && { notMatches: req.notMatches },
|
|
287
|
+
...req.ignoreCase !== void 0 && { ignoreCase: req.ignoreCase }
|
|
288
|
+
};
|
|
289
|
+
const hasTextQuery = req.contains !== void 0 || req.notContains !== void 0 || req.matches !== void 0 || req.notMatches !== void 0;
|
|
290
|
+
const hasAnyQuery = hasJsonQuery || hasTextQuery || req.ignoreCase !== void 0;
|
|
291
|
+
if (req.absent) {
|
|
292
|
+
if (hasAnyQuery) throw compilerError("E118", `requires '${req.file}': 'absent' is mutually exclusive with query keys (exists/match/not/contains/notContains/matches/notMatches)`);
|
|
293
|
+
return {
|
|
294
|
+
file: req.file,
|
|
295
|
+
absent: true
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
const format = resolveContentFormat(req.file, req.format);
|
|
299
|
+
if (format === "text" && hasJsonQuery) throw compilerError("E119", `requires '${req.file}': text format cannot carry a json/yaml query key (exists/match/not) — name a .json/.yaml file or set format: 'json' | 'yaml'`);
|
|
300
|
+
if (format !== "text" && (hasTextQuery || req.ignoreCase !== void 0)) throw compilerError("E119", `requires '${req.file}': '${format}' format cannot carry a raw-text key (contains/notContains/matches/notMatches/ignoreCase) — use format: 'text'`);
|
|
301
|
+
if (!hasAnyQuery) {
|
|
302
|
+
if (req.format !== void 0) throw compilerError("E122", `requires '${req.file}': format '${req.format}' declared with no query key — nothing to check`);
|
|
303
|
+
return { file: req.file };
|
|
304
|
+
}
|
|
305
|
+
const text = hasTextQuery ? serializeTextMatch(textKeys, `requires '${req.file}'`) : {};
|
|
306
|
+
return {
|
|
307
|
+
file: req.file,
|
|
308
|
+
format,
|
|
309
|
+
...req.exists !== void 0 && { exists: req.exists },
|
|
310
|
+
...req.match !== void 0 && { match: req.match },
|
|
311
|
+
...req.not !== void 0 && { not: req.not },
|
|
312
|
+
...text
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Serialize a trigger's `requires` list, or `undefined` when empty (so the lock
|
|
317
|
+
* field stays absent). Each entry is validated + format-resolved at compile time.
|
|
318
|
+
*/
|
|
319
|
+
function serializeRequires(requires) {
|
|
320
|
+
if (!requires || requires.length === 0) return void 0;
|
|
321
|
+
return requires.map(serializeOneRequirement);
|
|
322
|
+
}
|
|
323
|
+
function requiresField(requires) {
|
|
324
|
+
const serialized = serializeRequires(requires);
|
|
325
|
+
return serialized ? { requires: serialized } : {};
|
|
326
|
+
}
|
|
192
327
|
function toLockPr(t) {
|
|
193
328
|
return {
|
|
194
329
|
_type: "pr",
|
|
@@ -196,19 +331,27 @@ function toLockPr(t) {
|
|
|
196
331
|
targetBranches: transformBranchPatterns(t.targetBranches),
|
|
197
332
|
sourceBranches: transformBranchPatterns(t.sourceBranches),
|
|
198
333
|
paths: t.paths,
|
|
199
|
-
...reposField(t)
|
|
334
|
+
...reposField(t),
|
|
335
|
+
...requiresField(t.requires),
|
|
336
|
+
...commitMessageField(t.commitMessage, "pr trigger")
|
|
200
337
|
};
|
|
201
338
|
}
|
|
202
339
|
function toLockPushAndTag(t) {
|
|
340
|
+
const requires = requiresField(t.requires);
|
|
341
|
+
const commitMessage = commitMessageField(t.commitMessage, "push trigger");
|
|
203
342
|
const results = [{
|
|
204
343
|
_type: "push",
|
|
205
344
|
branches: transformBranchPatterns(t.branches),
|
|
206
345
|
paths: t.paths,
|
|
207
|
-
...reposField(t)
|
|
346
|
+
...reposField(t),
|
|
347
|
+
...requires,
|
|
348
|
+
...commitMessage
|
|
208
349
|
}];
|
|
209
350
|
if (t.tags.length > 0) results.push({
|
|
210
351
|
_type: "tag",
|
|
211
|
-
patterns: transformBranchPatterns(t.tags)
|
|
352
|
+
patterns: transformBranchPatterns(t.tags),
|
|
353
|
+
...requires,
|
|
354
|
+
...commitMessage
|
|
212
355
|
});
|
|
213
356
|
return results;
|
|
214
357
|
}
|
|
@@ -216,7 +359,9 @@ function toLockTag(t) {
|
|
|
216
359
|
return {
|
|
217
360
|
_type: "tag",
|
|
218
361
|
patterns: transformBranchPatterns(t.patterns),
|
|
219
|
-
...reposField(t)
|
|
362
|
+
...reposField(t),
|
|
363
|
+
...requiresField(t.requires),
|
|
364
|
+
...commitMessageField(t.commitMessage, "tag trigger")
|
|
220
365
|
};
|
|
221
366
|
}
|
|
222
367
|
function toLockComment(t) {
|
|
@@ -518,25 +663,15 @@ function validateRunsOn(runsOn, jobName, location) {
|
|
|
518
663
|
}
|
|
519
664
|
/**
|
|
520
665
|
* Transform one context reference (static name or function) into a lock
|
|
521
|
-
* `{ value, dynamic }` entry. A function element
|
|
522
|
-
*
|
|
523
|
-
*
|
|
666
|
+
* `{ value, dynamic }` entry. A function element carries only the `dynamic`
|
|
667
|
+
* flag (the agent resolves it in the init round); a static name carries its
|
|
668
|
+
* literal value.
|
|
524
669
|
*/
|
|
525
670
|
function transformContextRef(ref) {
|
|
526
|
-
if (typeof ref === "function") {
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
_type: "inline",
|
|
531
|
-
expression: fnSource
|
|
532
|
-
},
|
|
533
|
-
dynamic: true
|
|
534
|
-
};
|
|
535
|
-
return {
|
|
536
|
-
value: "",
|
|
537
|
-
dynamic: true
|
|
538
|
-
};
|
|
539
|
-
}
|
|
671
|
+
if (typeof ref === "function") return {
|
|
672
|
+
value: "",
|
|
673
|
+
dynamic: true
|
|
674
|
+
};
|
|
540
675
|
return {
|
|
541
676
|
value: ref,
|
|
542
677
|
dynamic: false
|
|
@@ -551,7 +686,7 @@ function transformJob(job, configPath, index, gitRoot, uuidToName) {
|
|
|
551
686
|
location: jobLocation,
|
|
552
687
|
suggestion: "Set exactly one of runsOn (single agent) or runsOnAll (fan-out to every matching agent)."
|
|
553
688
|
});
|
|
554
|
-
if (job.runsOn === void 0 && job.runsOnAll === void 0) throw compilerError("E109", `job '${job.name}': one of runsOn or runsOnAll is required`, {
|
|
689
|
+
if (job.invoke === void 0 && job.runsOn === void 0 && job.runsOnAll === void 0) throw compilerError("E109", `job '${job.name}': one of runsOn or runsOnAll is required`, {
|
|
555
690
|
location: jobLocation,
|
|
556
691
|
suggestion: "Add runsOn: \"kici:os:linux\" (or another agent label) to the job."
|
|
557
692
|
});
|
|
@@ -567,15 +702,8 @@ function transformJob(job, configPath, index, gitRoot, uuidToName) {
|
|
|
567
702
|
if (contextRefs !== void 0 && contextRefs.length > 0) contextFields.contexts = contextRefs.map((ref) => transformContextRef(ref));
|
|
568
703
|
const envFields = {};
|
|
569
704
|
if (job.env !== void 0) {
|
|
570
|
-
if (typeof job.env === "function")
|
|
571
|
-
|
|
572
|
-
const purity = analyzePurity(fnSource);
|
|
573
|
-
envFields.dynamicEnv = true;
|
|
574
|
-
if (purity.pure) envFields.env = {
|
|
575
|
-
_type: "inline",
|
|
576
|
-
expression: fnSource
|
|
577
|
-
};
|
|
578
|
-
} else if (typeof job.env === "object") envFields.env = { ...job.env };
|
|
705
|
+
if (typeof job.env === "function") envFields.dynamicEnv = true;
|
|
706
|
+
else if (typeof job.env === "object") envFields.env = { ...job.env };
|
|
579
707
|
}
|
|
580
708
|
if (job.resources !== void 0) try {
|
|
581
709
|
validateResourceRequest(job.resources);
|
|
@@ -588,15 +716,8 @@ function transformJob(job, configPath, index, gitRoot, uuidToName) {
|
|
|
588
716
|
}
|
|
589
717
|
const concurrencyFields = {};
|
|
590
718
|
if (job.concurrencyGroup !== void 0) {
|
|
591
|
-
if (typeof job.concurrencyGroup === "function")
|
|
592
|
-
|
|
593
|
-
const purity = analyzePurity(fnSource);
|
|
594
|
-
concurrencyFields.dynamicConcurrencyGroup = true;
|
|
595
|
-
if (purity.pure) concurrencyFields.concurrencyGroup = {
|
|
596
|
-
_type: "inline",
|
|
597
|
-
expression: fnSource
|
|
598
|
-
};
|
|
599
|
-
} else if (typeof job.concurrencyGroup === "string") concurrencyFields.concurrencyGroup = job.concurrencyGroup;
|
|
719
|
+
if (typeof job.concurrencyGroup === "function") concurrencyFields.dynamicConcurrencyGroup = true;
|
|
720
|
+
else if (typeof job.concurrencyGroup === "string") concurrencyFields.concurrencyGroup = job.concurrencyGroup;
|
|
600
721
|
}
|
|
601
722
|
return {
|
|
602
723
|
_type: "static",
|
|
@@ -605,8 +726,15 @@ function transformJob(job, configPath, index, gitRoot, uuidToName) {
|
|
|
605
726
|
...job.runsOnAll !== void 0 && { runsOnAll: normalizeRunsOnAllToMatchers(job.runsOnAll, `job '${job.name}' runsOnAll`) },
|
|
606
727
|
...job.onUnreachable !== void 0 && { onUnreachable: job.onUnreachable },
|
|
607
728
|
...job.includeUninitialized !== void 0 && { includeUninitialized: job.includeUninitialized },
|
|
729
|
+
...job.gitCredentials !== void 0 && { gitCredentials: job.gitCredentials },
|
|
608
730
|
...job.maxParallel !== void 0 && { maxParallel: job.maxParallel },
|
|
609
731
|
...job.failFast !== void 0 && { failFast: job.failFast },
|
|
732
|
+
...job.invoke !== void 0 && { invoke: {
|
|
733
|
+
event: job.invoke.event,
|
|
734
|
+
scope: job.invoke.scope,
|
|
735
|
+
...job.invoke.payload !== void 0 && { payload: job.invoke.payload },
|
|
736
|
+
...job.invoke.optional === true && { optional: true }
|
|
737
|
+
} },
|
|
610
738
|
...resolveNeedsForLock(job.needs, uuidToName),
|
|
611
739
|
steps: transformSteps(job.steps, gitRoot, jobLocation),
|
|
612
740
|
matrix: job.matrix ? transformMatrix(job.matrix, job.name, configPath) : void 0,
|
package/dist/lockfile/index.d.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
1
|
export { generateLockFile, serializeLockFile, detectGitRoot, computeLockfileHash, schemaWindowWarning, } from './generator.js';
|
|
2
2
|
export { computeContentHash, COMPILE_SCHEMA_VERSION } from './hasher.js';
|
|
3
|
-
export { DynamicValueField, analyzeJobPurity, collectWorkflowPurityWarnings, } from './purity-diagnostics.js';
|
|
4
|
-
export type { JobPurityWarning } from './purity-diagnostics.js';
|
|
5
3
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/lockfile/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import "../rolldown-runtime-ClRpJifh.js";
|
|
2
2
|
import { COMPILE_SCHEMA_VERSION, computeContentHash } from "./hasher.js";
|
|
3
3
|
import { computeLockfileHash, detectGitRoot, generateLockFile, schemaWindowWarning, serializeLockFile } from "./generator.js";
|
|
4
|
-
|
|
5
|
-
export { COMPILE_SCHEMA_VERSION, DynamicValueField, analyzeJobPurity, collectWorkflowPurityWarnings, computeContentHash, computeLockfileHash, detectGitRoot, generateLockFile, schemaWindowWarning, serializeLockFile };
|
|
4
|
+
export { COMPILE_SCHEMA_VERSION, computeContentHash, computeLockfileHash, detectGitRoot, generateLockFile, schemaWindowWarning, serializeLockFile };
|
package/dist/postinstall.js
CHANGED
|
@@ -26,7 +26,8 @@ async function detectDevelopmentMode() {
|
|
|
26
26
|
if (process.env.KICI_DEV === "true") return true;
|
|
27
27
|
try {
|
|
28
28
|
const rootDir = process.env.INIT_CWD || process.cwd();
|
|
29
|
-
const
|
|
29
|
+
const rootPkgPath = path.join(rootDir, "package.json");
|
|
30
|
+
const content = await readFile(rootPkgPath, "utf-8");
|
|
30
31
|
return JSON.parse(content).kici?.development === true;
|
|
31
32
|
} catch {
|
|
32
33
|
return false;
|
package/dist/remote/config.d.ts
CHANGED
|
@@ -35,22 +35,9 @@ export interface GlobalConfig {
|
|
|
35
35
|
*
|
|
36
36
|
* Resolution order:
|
|
37
37
|
* 1. `KICI_CONFIG_DIR`, when set to a non-empty value.
|
|
38
|
-
* 2.
|
|
39
|
-
* machine's `~/.kici/config` names a real endpoint and carries a live
|
|
40
|
-
* PAT, so a test process that reads it authenticates against whatever
|
|
41
|
-
* that config points at — in practice, production. Every test that needs
|
|
42
|
-
* a config must name its own isolated directory.
|
|
43
|
-
* 3. `~/.kici`.
|
|
38
|
+
* 2. `~/.kici`.
|
|
44
39
|
*
|
|
45
|
-
* `
|
|
46
|
-
* `hack/lib/vitest-isolation.ts`. It is deliberately a KiCI-owned name rather
|
|
47
|
-
* than the runner's `VITEST`: `kici` is a compat-protected CLI, and `VITEST`
|
|
48
|
-
* is a third-party marker that propagates into spawned children, so keying off
|
|
49
|
-
* it would break a customer whose vitest test shells out to `kici`.
|
|
50
|
-
*
|
|
51
|
-
* `env` is injectable so this module's own tests can exercise every branch.
|
|
52
|
-
* It is NOT an opt-out: production call sites pass no argument and inherit the
|
|
53
|
-
* guard.
|
|
40
|
+
* `env` is injectable so this module's own tests can exercise both branches.
|
|
54
41
|
*/
|
|
55
42
|
export declare function getConfigDir(env?: NodeJS.ProcessEnv): string;
|
|
56
43
|
/**
|
package/dist/remote/config.js
CHANGED
|
@@ -33,26 +33,12 @@ function sanitizeConfig(raw) {
|
|
|
33
33
|
*
|
|
34
34
|
* Resolution order:
|
|
35
35
|
* 1. `KICI_CONFIG_DIR`, when set to a non-empty value.
|
|
36
|
-
* 2.
|
|
37
|
-
* machine's `~/.kici/config` names a real endpoint and carries a live
|
|
38
|
-
* PAT, so a test process that reads it authenticates against whatever
|
|
39
|
-
* that config points at — in practice, production. Every test that needs
|
|
40
|
-
* a config must name its own isolated directory.
|
|
41
|
-
* 3. `~/.kici`.
|
|
36
|
+
* 2. `~/.kici`.
|
|
42
37
|
*
|
|
43
|
-
* `
|
|
44
|
-
* `hack/lib/vitest-isolation.ts`. It is deliberately a KiCI-owned name rather
|
|
45
|
-
* than the runner's `VITEST`: `kici` is a compat-protected CLI, and `VITEST`
|
|
46
|
-
* is a third-party marker that propagates into spawned children, so keying off
|
|
47
|
-
* it would break a customer whose vitest test shells out to `kici`.
|
|
48
|
-
*
|
|
49
|
-
* `env` is injectable so this module's own tests can exercise every branch.
|
|
50
|
-
* It is NOT an opt-out: production call sites pass no argument and inherit the
|
|
51
|
-
* guard.
|
|
38
|
+
* `env` is injectable so this module's own tests can exercise both branches.
|
|
52
39
|
*/
|
|
53
40
|
function getConfigDir(env = process.env) {
|
|
54
41
|
if (env.KICI_CONFIG_DIR) return env.KICI_CONFIG_DIR;
|
|
55
|
-
if (env.KICI_TEST_ISOLATION) throw new Error("Refusing to read the ambient ~/.kici config from a test process. Set KICI_CONFIG_DIR to an isolated directory for this run — the developer machine config may point at production.");
|
|
56
42
|
return path.join(os.homedir(), ".kici");
|
|
57
43
|
}
|
|
58
44
|
/**
|