@kici-dev/agent 0.1.17 → 0.1.18
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/execution/dep-installer.d.ts +9 -4
- package/dist/execution/dep-packer.d.ts +8 -5
- package/dist/execution/sandbox/ipc-protocol.d.ts +23 -0
- package/dist/execution/validate-kici-deps.d.ts +7 -4
- package/dist/execution/workflow-loader.d.ts +5 -1
- package/dist/execution/yarnrc-berry-config.d.ts +23 -0
- package/dist/index.js +217 -28
- package/dist/server.js +275 -79
- package/dist/workflow-runner.js +225 -34
- package/package.json +13 -12
- package/sbom.spdx.json +2551 -6049
package/dist/server.js
CHANGED
|
@@ -15,7 +15,7 @@ import { LOGGER_ENV_VARS, defineEnv, validateUnknownKiciVars } from "@kici-dev/s
|
|
|
15
15
|
import { ALLOWED_SYSTEM_VARS, CacheRunEventType, CacheStepType, ExecutionJobStatus, ExecutionStepStatus, InitFailureCategory, KNOWN_ROLES, PROTOCOL_VERSION, SANDBOX_DEFAULT_VARS, WS_CLOSE_AGENT_AUTH_FAILED, WS_MAX_PAYLOAD_BYTES, applyIncludeExclude, deriveOsArchLabels, expandMatrix, heartbeatSchema, hostLabel, mergeAutoLabels, orchestratorToAgentMessageSchema, resolveRoleLabels, validateNoReservedLabels } from "@kici-dev/engine";
|
|
16
16
|
import { execFile, execFileSync, execSync, fork, spawn } from "node:child_process";
|
|
17
17
|
import WebSocket from "ws";
|
|
18
|
-
import
|
|
18
|
+
import { ZipArchive } from "archiver";
|
|
19
19
|
import fs, { existsSync } from "node:fs";
|
|
20
20
|
import path, { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
21
21
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -24,13 +24,15 @@ import { format, promisify } from "node:util";
|
|
|
24
24
|
import { gcStaleTmpDirs } from "@kici-dev/core/tmp-gc";
|
|
25
25
|
import fs$1, { access, lstat, mkdir, mkdtemp, readFile, readdir, realpath, rm, unlink, writeFile } from "node:fs/promises";
|
|
26
26
|
import Docker from "dockerode";
|
|
27
|
-
import { buildKiciApi, isDynamicGroupRef, isDynamicJobFn, isStaticArray, isStaticObject } from "@kici-dev/sdk";
|
|
27
|
+
import { buildKiciApi, buildNeedsContext, isDynamicGroupRef, isDynamicJobFn, isStaticArray, isStaticObject } from "@kici-dev/sdk";
|
|
28
28
|
import { c, x } from "tar";
|
|
29
29
|
import https from "node:https";
|
|
30
30
|
import http from "node:http";
|
|
31
31
|
import { pipeline } from "node:stream/promises";
|
|
32
32
|
import { createGunzip } from "node:zlib";
|
|
33
|
-
import {
|
|
33
|
+
import { normalizeRunsOnToMatchers } from "@kici-dev/engine/labels/compile";
|
|
34
|
+
import { PNPM_IGNORE_BUILD_GATE_ARG, PackageManager, YarnFlavor, detectPackageManagerFromManifests, detectYarnFlavor } from "@kici-dev/shared/package-manager";
|
|
35
|
+
import { parse, stringify } from "yaml";
|
|
34
36
|
import { createInterface } from "node:readline";
|
|
35
37
|
var __defProp = Object.defineProperty;
|
|
36
38
|
var __esmMin = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
@@ -189,7 +191,7 @@ function agentClientConnectionOptions(config) {
|
|
|
189
191
|
* lean subset of the orchestrator's createDebugBundle.
|
|
190
192
|
*/
|
|
191
193
|
async function buildAgentMiniBundle(opts) {
|
|
192
|
-
const archive =
|
|
194
|
+
const archive = new ZipArchive({ zlib: { level: 6 } });
|
|
193
195
|
const chunks = [];
|
|
194
196
|
archive.on("data", (d) => chunks.push(d));
|
|
195
197
|
const done = new Promise((resolve, reject) => {
|
|
@@ -1297,14 +1299,14 @@ var init_console_capture = __esmMin((() => {
|
|
|
1297
1299
|
init_console_capture();
|
|
1298
1300
|
function safe(name, fallback = "unknown") {
|
|
1299
1301
|
switch (name) {
|
|
1300
|
-
case "version": return "0.1.
|
|
1301
|
-
case "buildCommit": return "
|
|
1302
|
-
case "sdkVersion": return "0.1.
|
|
1303
|
-
case "sdkBundleHash": return "
|
|
1304
|
-
case "sharedVersion": return "0.1.
|
|
1305
|
-
case "sharedBundleHash": return "
|
|
1306
|
-
case "engineVersion": return "0.1.
|
|
1307
|
-
case "engineBundleHash": return "
|
|
1302
|
+
case "version": return "0.1.18";
|
|
1303
|
+
case "buildCommit": return "d8cff38bb";
|
|
1304
|
+
case "sdkVersion": return "0.1.18";
|
|
1305
|
+
case "sdkBundleHash": return "8308089347c304e41b457d3867b17bbff11d6b5cd9706b6823e7abdbd849f33f";
|
|
1306
|
+
case "sharedVersion": return "0.1.18";
|
|
1307
|
+
case "sharedBundleHash": return "5f2c220f24d166b0f13d0620a2e80d19689ac8b683a12154daef44e938fd46a7";
|
|
1308
|
+
case "engineVersion": return "0.1.18";
|
|
1309
|
+
case "engineBundleHash": return "79ce14640d1798eaaa7cb3aa6c4bc325da3bff1e9f4716936e19614eabb1d858";
|
|
1308
1310
|
default: return fallback;
|
|
1309
1311
|
}
|
|
1310
1312
|
}
|
|
@@ -1949,18 +1951,20 @@ function extractSteps(workflow, jobName) {
|
|
|
1949
1951
|
* A sibling mismatch logs a warning; a missing target job throws a clear
|
|
1950
1952
|
* determinism error.
|
|
1951
1953
|
*/
|
|
1952
|
-
async function extractStepsFromDynamicJob(workflow, dynamicIndex, jobName, event, env, apiTransport, expectedJobNames) {
|
|
1954
|
+
async function extractStepsFromDynamicJob(workflow, dynamicIndex, jobName, event, env, apiTransport, expectedJobNames, upstreamSnapshot, declaredNeeds) {
|
|
1953
1955
|
const dynamicFn = extractDynamicJobFn(workflow, dynamicIndex);
|
|
1954
1956
|
const { $ } = await import("zx");
|
|
1955
1957
|
const { createLogger } = await import("@kici-dev/shared");
|
|
1956
|
-
const { buildKiciApi } = await import("@kici-dev/sdk");
|
|
1958
|
+
const { buildKiciApi, buildNeedsContext } = await import("@kici-dev/sdk");
|
|
1957
1959
|
const log = createLogger({ prefix: `dynamic-job-fn:${workflow.name}` });
|
|
1958
1960
|
const kici = buildKiciApi(apiTransport ?? (() => Promise.reject(/* @__PURE__ */ new Error("Agent API not available during re-evaluation"))));
|
|
1961
|
+
const needs = upstreamSnapshot ? buildNeedsContext(upstreamSnapshot, declaredNeeds ?? []) : void 0;
|
|
1959
1962
|
const generatedJobs = await dynamicFn({
|
|
1960
1963
|
$,
|
|
1961
1964
|
ctx: {
|
|
1962
1965
|
workflow: { name: workflow.name },
|
|
1963
|
-
event
|
|
1966
|
+
event,
|
|
1967
|
+
...needs && { needs }
|
|
1964
1968
|
},
|
|
1965
1969
|
log,
|
|
1966
1970
|
env,
|
|
@@ -1988,8 +1992,8 @@ async function extractStepsFromDynamicJob(workflow, dynamicIndex, jobName, event
|
|
|
1988
1992
|
}
|
|
1989
1993
|
var AGENT_SDK_VERSION, AGENT_SDK_BUNDLE_HASH, hookRegistered;
|
|
1990
1994
|
var init_workflow_loader = __esmMin((() => {
|
|
1991
|
-
AGENT_SDK_VERSION = "0.1.
|
|
1992
|
-
AGENT_SDK_BUNDLE_HASH = "
|
|
1995
|
+
AGENT_SDK_VERSION = "0.1.18";
|
|
1996
|
+
AGENT_SDK_BUNDLE_HASH = "8308089347c304e41b457d3867b17bbff11d6b5cd9706b6823e7abdbd849f33f";
|
|
1993
1997
|
hookRegistered = false;
|
|
1994
1998
|
}));
|
|
1995
1999
|
//#endregion
|
|
@@ -2496,7 +2500,7 @@ async function serializeJobsToLock(jobs, ctx, staticNames, allowedGroups) {
|
|
|
2496
2500
|
return result;
|
|
2497
2501
|
}
|
|
2498
2502
|
async function serializeJob(job, generatedNames, ctx, staticNames, allowedGroups) {
|
|
2499
|
-
const { runsOn, excludeLabels } =
|
|
2503
|
+
const { include: runsOn, exclude: excludeLabels } = normalizeRunsOnToMatchers(job.runsOn, `generated job '${job.name}' runsOn`);
|
|
2500
2504
|
let resolvedEnvironment;
|
|
2501
2505
|
if (typeof job.environment === "function") {
|
|
2502
2506
|
const value = await withTimeout(() => job.environment(ctx.event), DYNAMIC_FIELD_TIMEOUT_MS, `dynamic environment for generated job '${job.name}'`);
|
|
@@ -2519,7 +2523,7 @@ async function serializeJob(job, generatedNames, ctx, staticNames, allowedGroups
|
|
|
2519
2523
|
_type: "static",
|
|
2520
2524
|
name: job.name,
|
|
2521
2525
|
runsOn,
|
|
2522
|
-
...excludeLabels ? { excludeLabels } : {},
|
|
2526
|
+
...excludeLabels.length > 0 ? { excludeLabels } : {},
|
|
2523
2527
|
needs: resolvedNeeds,
|
|
2524
2528
|
...dependsOnGroups.length > 0 ? { dependsOnGroups } : {},
|
|
2525
2529
|
steps: serializeSteps(job.steps),
|
|
@@ -2533,20 +2537,6 @@ async function serializeJob(job, generatedNames, ctx, staticNames, allowedGroups
|
|
|
2533
2537
|
};
|
|
2534
2538
|
}
|
|
2535
2539
|
/**
|
|
2536
|
-
* Normalize the polymorphic RunsOn type into the lock file format.
|
|
2537
|
-
*/
|
|
2538
|
-
function normalizeRunsOn(runsOn) {
|
|
2539
|
-
if (typeof runsOn === "string") return { runsOn };
|
|
2540
|
-
if (Array.isArray(runsOn)) return { runsOn };
|
|
2541
|
-
const selector = runsOn;
|
|
2542
|
-
const labels = typeof selector.labels === "string" ? [selector.labels] : selector.labels;
|
|
2543
|
-
const exclude = selector.exclude ? typeof selector.exclude === "string" ? [selector.exclude] : selector.exclude : void 0;
|
|
2544
|
-
return {
|
|
2545
|
-
runsOn: labels,
|
|
2546
|
-
...exclude ? { excludeLabels: exclude } : {}
|
|
2547
|
-
};
|
|
2548
|
-
}
|
|
2549
|
-
/**
|
|
2550
2540
|
* Resolve needs references. Jobs can reference other jobs by name (string),
|
|
2551
2541
|
* Job object reference, DynamicGroupRef, or NeedsEntry/NeedsGroupEntry objects.
|
|
2552
2542
|
* Validates against generatedNames union staticNames union allowedGroups.
|
|
@@ -2632,7 +2622,7 @@ async function serializeMatrix(matrix, jobName, runsOn, ctx) {
|
|
|
2632
2622
|
workflow: { name: ctx.workflowName },
|
|
2633
2623
|
job: {
|
|
2634
2624
|
name: jobName,
|
|
2635
|
-
runsOn:
|
|
2625
|
+
runsOn: runsOn.map((m) => m.kind === "exact" ? m.value : `/${m.source}/${m.flags}`)
|
|
2636
2626
|
}
|
|
2637
2627
|
},
|
|
2638
2628
|
log: ctx.log,
|
|
@@ -3059,7 +3049,7 @@ function noopResult() {
|
|
|
3059
3049
|
};
|
|
3060
3050
|
}
|
|
3061
3051
|
/** Build the synthesized env-var name for registry index `i`. */
|
|
3062
|
-
function tokenEnvName(jobIdShort, index) {
|
|
3052
|
+
function tokenEnvName$1(jobIdShort, index) {
|
|
3063
3053
|
return `KICI_NPM_TOKEN_${jobIdShort}_${index}`;
|
|
3064
3054
|
}
|
|
3065
3055
|
/** Render the agent-managed block of `.npmrc` lines. */
|
|
@@ -3068,7 +3058,7 @@ function renderAgentLines(registries, jobIdShort) {
|
|
|
3068
3058
|
const lines = [];
|
|
3069
3059
|
for (let i = 0; i < registries.length; i++) {
|
|
3070
3060
|
const reg = registries[i];
|
|
3071
|
-
const envVar = tokenEnvName(jobIdShort, i);
|
|
3061
|
+
const envVar = tokenEnvName$1(jobIdShort, i);
|
|
3072
3062
|
const authKey = reg.url.replace(/^https?:/, "");
|
|
3073
3063
|
if (reg.scope) lines.push(`${reg.scope}:registry=${reg.url}`);
|
|
3074
3064
|
else lines.push(`registry=${reg.url}`);
|
|
@@ -3100,7 +3090,7 @@ async function applyNpmRegistryConfig(args) {
|
|
|
3100
3090
|
const tokenEnv = {};
|
|
3101
3091
|
const tokensForRedaction = [];
|
|
3102
3092
|
for (let i = 0; i < registries.length; i++) {
|
|
3103
|
-
tokenEnv[tokenEnvName(args.jobIdShort, i)] = registries[i].token;
|
|
3093
|
+
tokenEnv[tokenEnvName$1(args.jobIdShort, i)] = registries[i].token;
|
|
3104
3094
|
tokensForRedaction.push(registries[i].token);
|
|
3105
3095
|
}
|
|
3106
3096
|
for (const value of Object.values(installEnvSecrets)) if (value) tokensForRedaction.push(value);
|
|
@@ -3138,6 +3128,113 @@ function redactNpmOutput(input, tokens) {
|
|
|
3138
3128
|
}
|
|
3139
3129
|
var init_npm_registry_config = __esmMin((() => {}));
|
|
3140
3130
|
//#endregion
|
|
3131
|
+
//#region src/execution/yarnrc-berry-config.ts
|
|
3132
|
+
/**
|
|
3133
|
+
* Apply yarn-berry registry auth + a forced `nodeLinker: node-modules` to a
|
|
3134
|
+
* workflow's `.kici/.yarnrc.yml` for the lifetime of one `yarn install`, then
|
|
3135
|
+
* restore the file on cleanup. The berry analog of `npm-registry-config.ts`:
|
|
3136
|
+
* berry reads `.yarnrc.yml` (not `.npmrc`), so the auth block uses berry's
|
|
3137
|
+
* `npmRegistryServer` / `npmScopes` / `npmAuthToken` keys with `${VAR}`
|
|
3138
|
+
* env-var interpolation. Token bytes never reach disk — each registry token is
|
|
3139
|
+
* exposed as a job-scoped env var and the on-disk value is the `${VAR}`
|
|
3140
|
+
* reference.
|
|
3141
|
+
*
|
|
3142
|
+
* `nodeLinker: node-modules` makes berry lay down a real `node_modules` tree
|
|
3143
|
+
* (no PnP `.pnp.cjs`), so the agent's packer / restore / sibling-walk /
|
|
3144
|
+
* workflow-loader work unchanged. `enableScripts: false` (when a private
|
|
3145
|
+
* registry is configured) keeps dependency lifecycle scripts from seeing the
|
|
3146
|
+
* synthesized token env vars — the same security model as npm/pnpm/classic
|
|
3147
|
+
* `--ignore-scripts`.
|
|
3148
|
+
*
|
|
3149
|
+
* Reuses the same `ApplyNpmRegistryConfigArgs` / `ApplyNpmRegistryConfigResult`
|
|
3150
|
+
* shapes as the npm overlay so `dep-installer` can pick either by flavor.
|
|
3151
|
+
*/
|
|
3152
|
+
/** Build the synthesized env-var name for registry index `i`. */
|
|
3153
|
+
function tokenEnvName(jobIdShort, index) {
|
|
3154
|
+
return `KICI_NPM_TOKEN_${jobIdShort}_${index}`;
|
|
3155
|
+
}
|
|
3156
|
+
/** Read + parse an existing `.yarnrc.yml`, or `{}` when absent/empty. */
|
|
3157
|
+
async function readOriginalYarnrc(path) {
|
|
3158
|
+
try {
|
|
3159
|
+
const raw = await readFile(path, "utf8");
|
|
3160
|
+
return {
|
|
3161
|
+
raw,
|
|
3162
|
+
doc: parse(raw) ?? {}
|
|
3163
|
+
};
|
|
3164
|
+
} catch (err) {
|
|
3165
|
+
if (err.code === "ENOENT") return {
|
|
3166
|
+
raw: null,
|
|
3167
|
+
doc: {}
|
|
3168
|
+
};
|
|
3169
|
+
throw err;
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
function buildRegistryBlock(envVar, url, alwaysAuth) {
|
|
3173
|
+
return {
|
|
3174
|
+
npmRegistryServer: url,
|
|
3175
|
+
npmAuthToken: `\${${envVar}}`,
|
|
3176
|
+
...alwaysAuth ? { npmAlwaysAuth: true } : {}
|
|
3177
|
+
};
|
|
3178
|
+
}
|
|
3179
|
+
async function applyYarnrcBerryConfig(args) {
|
|
3180
|
+
const registries = args.npmRegistries ?? [];
|
|
3181
|
+
const installEnvSecrets = args.installEnvSecrets ?? {};
|
|
3182
|
+
const hasPrivateRegistry = registries.length > 0 || Object.keys(installEnvSecrets).length > 0;
|
|
3183
|
+
const yarnrcPath = join(args.kiciDir, ".yarnrc.yml");
|
|
3184
|
+
const { raw: original, doc } = await readOriginalYarnrc(yarnrcPath);
|
|
3185
|
+
const cacheFolder = await mkdtemp(join(tmpdir(), "kici-yarn-berry-cache-"));
|
|
3186
|
+
const merged = {
|
|
3187
|
+
...doc,
|
|
3188
|
+
nodeLinker: "node-modules",
|
|
3189
|
+
enableGlobalCache: false,
|
|
3190
|
+
cacheFolder
|
|
3191
|
+
};
|
|
3192
|
+
const tokenEnv = {};
|
|
3193
|
+
const tokensForRedaction = [];
|
|
3194
|
+
if (hasPrivateRegistry) {
|
|
3195
|
+
merged.enableScripts = false;
|
|
3196
|
+
const npmScopes = { ...doc.npmScopes ?? {} };
|
|
3197
|
+
for (let i = 0; i < registries.length; i++) {
|
|
3198
|
+
const reg = registries[i];
|
|
3199
|
+
const envVar = tokenEnvName(args.jobIdShort, i);
|
|
3200
|
+
tokenEnv[envVar] = reg.token;
|
|
3201
|
+
tokensForRedaction.push(reg.token);
|
|
3202
|
+
const block = buildRegistryBlock(envVar, reg.url, reg.alwaysAuth);
|
|
3203
|
+
if (reg.scope) npmScopes[reg.scope] = block;
|
|
3204
|
+
else {
|
|
3205
|
+
merged.npmRegistryServer = reg.url;
|
|
3206
|
+
merged.npmAuthToken = block.npmAuthToken;
|
|
3207
|
+
if (reg.alwaysAuth) merged.npmAlwaysAuth = true;
|
|
3208
|
+
}
|
|
3209
|
+
}
|
|
3210
|
+
if (Object.keys(npmScopes).length > 0) merged.npmScopes = npmScopes;
|
|
3211
|
+
for (const value of Object.values(installEnvSecrets)) if (value) tokensForRedaction.push(value);
|
|
3212
|
+
}
|
|
3213
|
+
await writeFile(yarnrcPath, stringify(merged), {
|
|
3214
|
+
encoding: "utf8",
|
|
3215
|
+
mode: 384
|
|
3216
|
+
});
|
|
3217
|
+
const cleanup = async () => {
|
|
3218
|
+
try {
|
|
3219
|
+
if (original === null) await unlink(yarnrcPath).catch(() => {});
|
|
3220
|
+
else await writeFile(yarnrcPath, original, { encoding: "utf8" });
|
|
3221
|
+
} catch {}
|
|
3222
|
+
await rm(cacheFolder, {
|
|
3223
|
+
recursive: true,
|
|
3224
|
+
force: true
|
|
3225
|
+
}).catch(() => {});
|
|
3226
|
+
};
|
|
3227
|
+
return {
|
|
3228
|
+
extraEnv: {
|
|
3229
|
+
...installEnvSecrets,
|
|
3230
|
+
...tokenEnv
|
|
3231
|
+
},
|
|
3232
|
+
tokensForRedaction,
|
|
3233
|
+
cleanup
|
|
3234
|
+
};
|
|
3235
|
+
}
|
|
3236
|
+
var init_yarnrc_berry_config = __esmMin((() => {}));
|
|
3237
|
+
//#endregion
|
|
3141
3238
|
//#region src/execution/validate-kici-deps.ts
|
|
3142
3239
|
/**
|
|
3143
3240
|
* Pre-install validation for `.kici/` dependency specifiers.
|
|
@@ -3158,8 +3255,10 @@ var init_npm_registry_config = __esmMin((() => {}));
|
|
|
3158
3255
|
* - yarn classic (v1) has no `workspace:` protocol and no `portal:` — it links
|
|
3159
3256
|
* in-repo siblings by version range, not by a local specifier — so both are
|
|
3160
3257
|
* rejected with guidance; `file:`/`link:` are allowed when the path stays
|
|
3161
|
-
* inside the clone, rejected when it escapes.
|
|
3162
|
-
*
|
|
3258
|
+
* inside the clone, rejected when it escapes.
|
|
3259
|
+
* - yarn berry (v2+) resolves `workspace:` against the repo-root package.json
|
|
3260
|
+
* `workspaces` field and `portal:`/`file:`/`link:` against inside-repo paths,
|
|
3261
|
+
* so those are allowed when present/inside the clone and rejected otherwise.
|
|
3163
3262
|
*
|
|
3164
3263
|
* This module performs that classification so unresolvable specifiers fail
|
|
3165
3264
|
* fast with guidance rather than a cryptic install error.
|
|
@@ -3219,6 +3318,17 @@ async function fileExists$1(target) {
|
|
|
3219
3318
|
return false;
|
|
3220
3319
|
}
|
|
3221
3320
|
}
|
|
3321
|
+
/** Whether the repo-root package.json declares a non-empty `workspaces` array. */
|
|
3322
|
+
async function rootHasWorkspaces(repoRoot) {
|
|
3323
|
+
try {
|
|
3324
|
+
const ws = JSON.parse(await readFile(join(repoRoot, "package.json"), "utf-8")).workspaces;
|
|
3325
|
+
if (Array.isArray(ws)) return ws.length > 0;
|
|
3326
|
+
if (ws && typeof ws === "object" && Array.isArray(ws.packages)) return ws.packages.length > 0;
|
|
3327
|
+
return false;
|
|
3328
|
+
} catch {
|
|
3329
|
+
return false;
|
|
3330
|
+
}
|
|
3331
|
+
}
|
|
3222
3332
|
/** Resolve a `file:`/`link:`/`portal:` spec to an absolute path under kiciDir. */
|
|
3223
3333
|
function resolveLocalPath(kiciDir, dep) {
|
|
3224
3334
|
const rawPath = dep.spec.slice(dep.protocol.length);
|
|
@@ -3233,8 +3343,20 @@ function isInsideRepo(repoRoot, target) {
|
|
|
3233
3343
|
* Classify each local-protocol dependency for the detected package manager and
|
|
3234
3344
|
* return the ones that are unresolvable in the agent's single-clone model.
|
|
3235
3345
|
*/
|
|
3236
|
-
async function findUnresolvableDeps(deps, packageManager, kiciDir, repoRoot) {
|
|
3346
|
+
async function findUnresolvableDeps(deps, packageManager, kiciDir, repoRoot, yarnFlavor) {
|
|
3237
3347
|
if (packageManager === PackageManager.Npm) return [...deps];
|
|
3348
|
+
if (packageManager === PackageManager.Yarn && yarnFlavor === YarnFlavor.Berry) {
|
|
3349
|
+
const hasWorkspaces = await rootHasWorkspaces(repoRoot);
|
|
3350
|
+
const unresolvable = [];
|
|
3351
|
+
for (const dep of deps) {
|
|
3352
|
+
if (dep.protocol === "workspace:") {
|
|
3353
|
+
if (!hasWorkspaces) unresolvable.push(dep);
|
|
3354
|
+
continue;
|
|
3355
|
+
}
|
|
3356
|
+
if (!isInsideRepo(repoRoot, resolveLocalPath(kiciDir, dep))) unresolvable.push(dep);
|
|
3357
|
+
}
|
|
3358
|
+
return unresolvable;
|
|
3359
|
+
}
|
|
3238
3360
|
if (packageManager === PackageManager.Yarn) {
|
|
3239
3361
|
const unresolvable = [];
|
|
3240
3362
|
for (const dep of deps) {
|
|
@@ -3258,10 +3380,11 @@ async function findUnresolvableDeps(deps, packageManager, kiciDir, repoRoot) {
|
|
|
3258
3380
|
return unresolvable;
|
|
3259
3381
|
}
|
|
3260
3382
|
/** Build the actionable error for unresolvable local-protocol dependencies. */
|
|
3261
|
-
function formatUnresolvableDepError(offenders, packageManager) {
|
|
3383
|
+
function formatUnresolvableDepError(offenders, packageManager, yarnFlavor) {
|
|
3262
3384
|
const list = offenders.map((o) => `${o.name}: ${o.spec}`).join(", ");
|
|
3263
3385
|
if (packageManager === PackageManager.Npm) return `These .kici/ dependencies use local-protocol specifiers npm cannot resolve from a registry: ${list}. npm has no workspace protocol — pin a published version, publish the package to your registry, or use pnpm so an in-repo workspace sibling can be resolved.`;
|
|
3264
|
-
if (packageManager === PackageManager.Yarn) return `These .kici/ dependencies
|
|
3386
|
+
if (packageManager === PackageManager.Yarn && yarnFlavor === YarnFlavor.Berry) return `These .kici/ dependencies cannot be resolved by yarn berry from the cloned repository: ${list}. A workspace: dependency requires a "workspaces" array in the repo-root package.json, and file:/link:/portal: paths must stay inside this repository.`;
|
|
3387
|
+
if (packageManager === PackageManager.Yarn) return `These .kici/ dependencies use specifiers yarn classic cannot resolve: ${list}. yarn classic has no workspace: or portal: protocol — reference an in-repo sibling by a version range (yarn links matching workspace members), use pnpm, or keep file:/link: paths inside this repository. (yarn berry support requires a yarn@2+ packageManager field or a .yarnrc.yml.)`;
|
|
3265
3388
|
return `These .kici/ dependencies point outside the cloned repository, which the agent never has: ${list}. A workspace: dependency requires a pnpm-workspace.yaml at the repo root, and file:/link:/portal: paths must stay inside this repository.`;
|
|
3266
3389
|
}
|
|
3267
3390
|
/**
|
|
@@ -3275,9 +3398,10 @@ async function assertResolvableDeps(args) {
|
|
|
3275
3398
|
if (!pkg) return;
|
|
3276
3399
|
const localDeps = findLocalProtocolDeps(pkg);
|
|
3277
3400
|
if (localDeps.length === 0) return;
|
|
3278
|
-
const
|
|
3401
|
+
const flavor = args.yarnFlavor ?? YarnFlavor.Classic;
|
|
3402
|
+
const offenders = await findUnresolvableDeps(localDeps, args.packageManager, args.kiciDir, args.repoRoot, flavor);
|
|
3279
3403
|
if (offenders.length === 0) return;
|
|
3280
|
-
throw new Error(formatUnresolvableDepError(offenders, args.packageManager));
|
|
3404
|
+
throw new Error(formatUnresolvableDepError(offenders, args.packageManager, flavor));
|
|
3281
3405
|
}
|
|
3282
3406
|
var LOCAL_PROTOCOLS, DEP_FIELDS;
|
|
3283
3407
|
var init_validate_kici_deps = __esmMin((() => {
|
|
@@ -3402,10 +3526,13 @@ var init_workspace_siblings = __esmMin((() => {}));
|
|
|
3402
3526
|
* presence of `.kici/package.json` signals that deps should be installed. npm
|
|
3403
3527
|
* is the default and ships with every Node.js install; pnpm is used when the
|
|
3404
3528
|
* repo is a pnpm workspace so a `.kici/` member can resolve in-repo
|
|
3405
|
-
* `workspace:` siblings. yarn classic (v1)
|
|
3406
|
-
*
|
|
3407
|
-
*
|
|
3408
|
-
*
|
|
3529
|
+
* `workspace:` siblings. yarn is supported in both flavors: classic (v1) reads
|
|
3530
|
+
* `.kici/.npmrc` for registry auth and links version-range workspace siblings;
|
|
3531
|
+
* berry (v2+) reads a synthesized `.kici/.yarnrc.yml` for auth, runs with a
|
|
3532
|
+
* forced `nodeLinker: node-modules` (so the resulting tree matches classic/npm
|
|
3533
|
+
* and the runner's plain node resolution holds), and resolves
|
|
3534
|
+
* `workspace:`/`portal:` siblings. Either flavor links the sibling but does not
|
|
3535
|
+
* build it, so the agent builds the in-repo closure after install.
|
|
3409
3536
|
*
|
|
3410
3537
|
* Security: the install runs with an isolated per-invocation cache/store
|
|
3411
3538
|
* directory to prevent cache poisoning across build jobs — a malicious
|
|
@@ -3426,6 +3553,15 @@ async function detectKiciPackageManager(repoRoot, kiciDir) {
|
|
|
3426
3553
|
return await detectPackageManagerFromManifests(repoRoot) ?? await detectPackageManagerFromManifests(kiciDir) ?? PackageManager.Npm;
|
|
3427
3554
|
}
|
|
3428
3555
|
/**
|
|
3556
|
+
* Detect the yarn flavor (classic vs berry) for the cloned repo. Mirrors
|
|
3557
|
+
* `detectKiciPackageManager`: probe the repo root first, then `.kici/` for a
|
|
3558
|
+
* standalone project. Only called when the detected manager is `Yarn`.
|
|
3559
|
+
*/
|
|
3560
|
+
async function detectKiciYarnFlavor(repoRoot, kiciDir) {
|
|
3561
|
+
if (await detectYarnFlavor(repoRoot) === YarnFlavor.Berry) return YarnFlavor.Berry;
|
|
3562
|
+
return detectYarnFlavor(kiciDir);
|
|
3563
|
+
}
|
|
3564
|
+
/**
|
|
3429
3565
|
* Install `.kici/` dependencies inline with the repo's package manager.
|
|
3430
3566
|
*
|
|
3431
3567
|
* Falls back to this when the dep cache is unavailable or a download fails.
|
|
@@ -3444,19 +3580,28 @@ async function detectKiciPackageManager(repoRoot, kiciDir) {
|
|
|
3444
3580
|
async function installDeps(kiciDir, opts = {}) {
|
|
3445
3581
|
const repoRoot = opts.repoRoot ?? dirname(kiciDir);
|
|
3446
3582
|
const packageManager = await detectKiciPackageManager(repoRoot, kiciDir);
|
|
3583
|
+
const yarnFlavor = packageManager === PackageManager.Yarn ? await detectKiciYarnFlavor(repoRoot, kiciDir) : YarnFlavor.Classic;
|
|
3447
3584
|
logger$6.info("Installing deps inline", {
|
|
3448
3585
|
packageManager,
|
|
3586
|
+
yarnFlavor,
|
|
3449
3587
|
dir: kiciDir
|
|
3450
3588
|
});
|
|
3451
|
-
process.stderr.write(`[dep-installer:trace] starting install: pm=${packageManager}, cwd=${kiciDir}\n`);
|
|
3589
|
+
process.stderr.write(`[dep-installer:trace] starting install: pm=${packageManager}, flavor=${yarnFlavor}, cwd=${kiciDir}\n`);
|
|
3452
3590
|
await assertResolvableDeps({
|
|
3453
3591
|
kiciDir,
|
|
3454
3592
|
repoRoot,
|
|
3455
|
-
packageManager
|
|
3593
|
+
packageManager,
|
|
3594
|
+
yarnFlavor
|
|
3456
3595
|
});
|
|
3457
3596
|
const startTime = Date.now();
|
|
3458
3597
|
const hasPrivateRegistry = (opts.npmRegistries?.length ?? 0) > 0 || (opts.installEnvSecrets ? Object.keys(opts.installEnvSecrets).length > 0 : false);
|
|
3459
|
-
const
|
|
3598
|
+
const isBerry = packageManager === PackageManager.Yarn && yarnFlavor === YarnFlavor.Berry;
|
|
3599
|
+
const registryConfig = isBerry ? await applyYarnrcBerryConfig({
|
|
3600
|
+
kiciDir,
|
|
3601
|
+
npmRegistries: opts.npmRegistries,
|
|
3602
|
+
installEnvSecrets: opts.installEnvSecrets,
|
|
3603
|
+
jobIdShort: opts.jobIdShort ?? "00000000"
|
|
3604
|
+
}) : await applyNpmRegistryConfig({
|
|
3460
3605
|
kiciDir,
|
|
3461
3606
|
npmRegistries: opts.npmRegistries,
|
|
3462
3607
|
installEnvSecrets: opts.installEnvSecrets,
|
|
@@ -3468,6 +3613,10 @@ async function installDeps(kiciDir, opts = {}) {
|
|
|
3468
3613
|
hasPrivateRegistry,
|
|
3469
3614
|
registryConfig
|
|
3470
3615
|
});
|
|
3616
|
+
else if (isBerry) await runYarnBerryInstall({
|
|
3617
|
+
kiciDir,
|
|
3618
|
+
registryConfig
|
|
3619
|
+
});
|
|
3471
3620
|
else if (packageManager === PackageManager.Yarn) await runYarnInstall({
|
|
3472
3621
|
kiciDir,
|
|
3473
3622
|
hasPrivateRegistry,
|
|
@@ -3487,7 +3636,7 @@ async function installDeps(kiciDir, opts = {}) {
|
|
|
3487
3636
|
await registryConfig.cleanup();
|
|
3488
3637
|
}
|
|
3489
3638
|
if (packageManager === PackageManager.Pnpm && await kiciHasLocalProtocolDeps(kiciDir)) await buildWorkspaceClosure(repoRoot);
|
|
3490
|
-
if (packageManager === PackageManager.Yarn) await buildYarnWorkspaceClosure(repoRoot, kiciDir);
|
|
3639
|
+
if (packageManager === PackageManager.Yarn) await buildYarnWorkspaceClosure(repoRoot, kiciDir, yarnFlavor);
|
|
3491
3640
|
const durationMs = Date.now() - startTime;
|
|
3492
3641
|
process.stderr.write(`[dep-installer:trace] install complete: ${durationMs}ms\n`);
|
|
3493
3642
|
logger$6.info("Deps installed inline", {
|
|
@@ -3616,6 +3765,35 @@ async function runYarnInstall(args) {
|
|
|
3616
3765
|
}).catch(() => {});
|
|
3617
3766
|
}
|
|
3618
3767
|
}
|
|
3768
|
+
/** Pure: argv for a berry `yarn install`. Cache + linker live in .yarnrc.yml. */
|
|
3769
|
+
function buildYarnBerryInstallArgs() {
|
|
3770
|
+
return ["install"];
|
|
3771
|
+
}
|
|
3772
|
+
/**
|
|
3773
|
+
* Run a berry `yarn install` from `.kici/`. The synthesized `.kici/.yarnrc.yml`
|
|
3774
|
+
* (applied by `applyYarnrcBerryConfig`) forces `nodeLinker: node-modules`, an
|
|
3775
|
+
* isolated `cacheFolder`, and — when a private registry is configured —
|
|
3776
|
+
* `enableScripts: false` + `npmScopes`/`npmRegistryServer` auth. corepack
|
|
3777
|
+
* provisions the repo-pinned berry version; `COREPACK_ENABLE_DOWNLOAD_PROMPT=0`
|
|
3778
|
+
* makes that non-interactive. Not `--immutable` (resolved URLs in the lockfile
|
|
3779
|
+
* may point at a different registry than the synthesized config).
|
|
3780
|
+
*/
|
|
3781
|
+
async function runYarnBerryInstall(args) {
|
|
3782
|
+
await assertYarnAvailable();
|
|
3783
|
+
const { nodeDir } = resolveNpm();
|
|
3784
|
+
const env = {
|
|
3785
|
+
...envWithNodeOnPath(args.registryConfig.extraEnv, nodeDir),
|
|
3786
|
+
COREPACK_ENABLE_DOWNLOAD_PROMPT: "0"
|
|
3787
|
+
};
|
|
3788
|
+
const argv = buildYarnBerryInstallArgs();
|
|
3789
|
+
process.stderr.write(`[dep-installer:trace] running: yarn ${argv.join(" ")} (berry)\n`);
|
|
3790
|
+
await execFileAsync("yarn", argv, {
|
|
3791
|
+
cwd: args.kiciDir,
|
|
3792
|
+
env,
|
|
3793
|
+
timeout: INSTALL_TIMEOUT_MS,
|
|
3794
|
+
maxBuffer: INSTALL_MAX_BUFFER
|
|
3795
|
+
});
|
|
3796
|
+
}
|
|
3619
3797
|
/** Throw an actionable error when the repo needs yarn but it is not installed. */
|
|
3620
3798
|
async function assertYarnAvailable() {
|
|
3621
3799
|
try {
|
|
@@ -3635,7 +3813,7 @@ async function assertYarnAvailable() {
|
|
|
3635
3813
|
* Deep cross-sibling build chains may build out of strict topological order —
|
|
3636
3814
|
* real `.kici` closures are shallow.
|
|
3637
3815
|
*/
|
|
3638
|
-
async function buildYarnWorkspaceClosure(repoRoot, kiciDir) {
|
|
3816
|
+
async function buildYarnWorkspaceClosure(repoRoot, kiciDir, yarnFlavor) {
|
|
3639
3817
|
const siblings = await collectInRepoSiblings(repoRoot, kiciDir, resolveYarnNodeModulesRoot(repoRoot, kiciDir));
|
|
3640
3818
|
if (siblings.length === 0) return;
|
|
3641
3819
|
const { nodeDir } = resolveNpm();
|
|
@@ -3643,15 +3821,16 @@ async function buildYarnWorkspaceClosure(repoRoot, kiciDir) {
|
|
|
3643
3821
|
for (const rel of [...siblings].reverse()) {
|
|
3644
3822
|
const sibDir = join(repoRoot, rel);
|
|
3645
3823
|
if (!await siblingHasBuildScript(sibDir)) continue;
|
|
3646
|
-
|
|
3824
|
+
const [argv, cwd] = yarnFlavor === YarnFlavor.Berry ? [["run", "build"], sibDir] : [[
|
|
3825
|
+
"--cwd",
|
|
3826
|
+
sibDir,
|
|
3827
|
+
"run",
|
|
3828
|
+
"build"
|
|
3829
|
+
], repoRoot];
|
|
3830
|
+
process.stderr.write(`[dep-installer:trace] building yarn sibling (${yarnFlavor}): yarn ${argv.join(" ")} @ ${cwd}\n`);
|
|
3647
3831
|
try {
|
|
3648
|
-
await execFileAsync("yarn",
|
|
3649
|
-
|
|
3650
|
-
sibDir,
|
|
3651
|
-
"run",
|
|
3652
|
-
"build"
|
|
3653
|
-
], {
|
|
3654
|
-
cwd: repoRoot,
|
|
3832
|
+
await execFileAsync("yarn", argv, {
|
|
3833
|
+
cwd,
|
|
3655
3834
|
env,
|
|
3656
3835
|
timeout: INSTALL_TIMEOUT_MS,
|
|
3657
3836
|
maxBuffer: INSTALL_MAX_BUFFER
|
|
@@ -3729,6 +3908,7 @@ function logSubprocessStreams(e, tokens) {
|
|
|
3729
3908
|
var logger$6, execFileAsync, INSTALL_TIMEOUT_MS, INSTALL_MAX_BUFFER;
|
|
3730
3909
|
var init_dep_installer = __esmMin((() => {
|
|
3731
3910
|
init_npm_registry_config();
|
|
3911
|
+
init_yarnrc_berry_config();
|
|
3732
3912
|
init_validate_kici_deps();
|
|
3733
3913
|
init_workspace_siblings();
|
|
3734
3914
|
logger$6 = createLogger({ prefix: "dep-installer" });
|
|
@@ -3753,11 +3933,14 @@ var init_dep_installer = __esmMin((() => {
|
|
|
3753
3933
|
* symlinks into the root store and into sibling dirs that live outside
|
|
3754
3934
|
* `.kici/`, so packing `.kici/node_modules` alone would capture dangling
|
|
3755
3935
|
* links — the store and siblings must travel together.
|
|
3756
|
-
* - yarn classic: the resolved node_modules root (standalone `.kici`
|
|
3757
|
-
* `.kici/node_modules`; hoisted workspace member → the repo-root
|
|
3758
|
-
* `node_modules`) plus the in-repo
|
|
3759
|
-
*
|
|
3760
|
-
*
|
|
3936
|
+
* - yarn (classic + berry): the resolved node_modules root (standalone `.kici`
|
|
3937
|
+
* → `.kici/node_modules`; hoisted workspace member → the repo-root
|
|
3938
|
+
* `node_modules`) plus the in-repo sibling package directories `.kici` depends
|
|
3939
|
+
* on (with their built output), whose symlinks would dangle otherwise. Berry
|
|
3940
|
+
* runs with a forced `nodeLinker: node-modules`, so its tree has the same
|
|
3941
|
+
* node_modules shape as classic and is packed identically (the flavor only
|
|
3942
|
+
* changes how siblings are referenced — version range vs `workspace:`/`portal:`
|
|
3943
|
+
* — not the packed layout).
|
|
3761
3944
|
*
|
|
3762
3945
|
* Uses tar.gz (Node.js built-in zlib, no external binary) in portable mode to
|
|
3763
3946
|
* strip user/group info for cross-machine consistency; symlinks are preserved
|
|
@@ -3805,10 +3988,11 @@ async function packNodeModules(kiciDir) {
|
|
|
3805
3988
|
/**
|
|
3806
3989
|
* Compute the repo-root-relative tar entries for the dependency closure. npm
|
|
3807
3990
|
* needs only `.kici/node_modules`. pnpm additionally needs the root store and
|
|
3808
|
-
* the in-repo workspace siblings `.kici` resolves. yarn classic packs
|
|
3809
|
-
* resolved node_modules root (standalone → `.kici/node_modules`; hoisted
|
|
3810
|
-
* workspace member → root `node_modules`) plus the in-repo
|
|
3811
|
-
*
|
|
3991
|
+
* the in-repo workspace siblings `.kici` resolves. yarn (classic + berry) packs
|
|
3992
|
+
* the resolved node_modules root (standalone → `.kici/node_modules`; hoisted
|
|
3993
|
+
* workspace member → root `node_modules`) plus the in-repo siblings, which it
|
|
3994
|
+
* links but does not place in the store. Berry's forced node-modules linker
|
|
3995
|
+
* gives it the same packed shape as classic.
|
|
3812
3996
|
*/
|
|
3813
3997
|
async function closureEntries(workDir, kiciDir, packageManager) {
|
|
3814
3998
|
if (packageManager === PackageManager.Pnpm) {
|
|
@@ -3988,6 +4172,8 @@ function buildRequest(dispatch, workDir) {
|
|
|
3988
4172
|
jobName: jobConfig.baseJobName ?? jobConfig.name ?? "",
|
|
3989
4173
|
runsOn: jobConfig.runsOn ?? "",
|
|
3990
4174
|
matrixValues: jobConfig.matrixValues,
|
|
4175
|
+
host: jobConfig.host,
|
|
4176
|
+
agent: jobConfig.agent,
|
|
3991
4177
|
secrets: dispatch.secrets,
|
|
3992
4178
|
namespacedSecrets: dispatch.namespacedSecrets,
|
|
3993
4179
|
sourceFile: jobConfig.source?.file,
|
|
@@ -5241,6 +5427,14 @@ function determineExecutionMode(jobConfig, agentConfig) {
|
|
|
5241
5427
|
if (agentConfig.scalerManaged) return "firecracker";
|
|
5242
5428
|
return "bare-metal";
|
|
5243
5429
|
}
|
|
5430
|
+
/**
|
|
5431
|
+
* Build the result-aware `ctx.needs` for a dynamic eval from its frozen upstream
|
|
5432
|
+
* snapshot. Returns undefined for an event-only generator (no snapshot).
|
|
5433
|
+
*/
|
|
5434
|
+
function buildEvalNeedsContext(config) {
|
|
5435
|
+
if (!config.resultAware || !config.upstreamSnapshot) return void 0;
|
|
5436
|
+
return buildNeedsContext(config.upstreamSnapshot, config.declaredNeeds ?? []);
|
|
5437
|
+
}
|
|
5244
5438
|
var logger$2, JobRunner$1;
|
|
5245
5439
|
var init_job_runner = __esmMin((() => {
|
|
5246
5440
|
init_git_clone();
|
|
@@ -5988,11 +6182,13 @@ var init_job_runner = __esmMin((() => {
|
|
|
5988
6182
|
const { extractDynamicJobFn } = await Promise.resolve().then(() => (init_workflow_loader(), workflow_loader_exports));
|
|
5989
6183
|
const dynamicFn = extractDynamicJobFn(extractWorkflow(module, config.workflowName), config.source.index);
|
|
5990
6184
|
evalLog(`Evaluating DynamicJobFn (index ${config.source.index}, timeout ${timeoutMs}ms)`);
|
|
6185
|
+
const needs = buildEvalNeedsContext(config);
|
|
5991
6186
|
const context = {
|
|
5992
6187
|
$: scopedDollar,
|
|
5993
6188
|
ctx: {
|
|
5994
6189
|
workflow: { name: config.workflowName },
|
|
5995
|
-
event: config.event
|
|
6190
|
+
event: config.event,
|
|
6191
|
+
...needs && { needs }
|
|
5996
6192
|
},
|
|
5997
6193
|
log: dynamicJobLogger,
|
|
5998
6194
|
env: process.env,
|
|
@@ -6209,14 +6405,14 @@ var init_job_runner = __esmMin((() => {
|
|
|
6209
6405
|
*/
|
|
6210
6406
|
init_console_capture();
|
|
6211
6407
|
init_npm_resolver();
|
|
6212
|
-
const AGENT_VERSION = "0.1.
|
|
6213
|
-
const BUILD_COMMIT = "
|
|
6214
|
-
const SDK_VERSION = "0.1.
|
|
6215
|
-
const SDK_BUNDLE_HASH = "
|
|
6216
|
-
const SHARED_VERSION = "0.1.
|
|
6217
|
-
const SHARED_BUNDLE_HASH = "
|
|
6218
|
-
const ENGINE_VERSION = "0.1.
|
|
6219
|
-
const ENGINE_BUNDLE_HASH = "
|
|
6408
|
+
const AGENT_VERSION = "0.1.18";
|
|
6409
|
+
const BUILD_COMMIT = "d8cff38bb";
|
|
6410
|
+
const SDK_VERSION = "0.1.18";
|
|
6411
|
+
const SDK_BUNDLE_HASH = "8308089347c304e41b457d3867b17bbff11d6b5cd9706b6823e7abdbd849f33f";
|
|
6412
|
+
const SHARED_VERSION = "0.1.18";
|
|
6413
|
+
const SHARED_BUNDLE_HASH = "5f2c220f24d166b0f13d0620a2e80d19689ac8b683a12154daef44e938fd46a7";
|
|
6414
|
+
const ENGINE_VERSION = "0.1.18";
|
|
6415
|
+
const ENGINE_BUNDLE_HASH = "79ce14640d1798eaaa7cb3aa6c4bc325da3bff1e9f4716936e19614eabb1d858";
|
|
6220
6416
|
initTelemetry({
|
|
6221
6417
|
serviceName: "kici-agent",
|
|
6222
6418
|
otlpEndpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT
|