@kici-dev/agent 0.1.10 → 0.1.11
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 +30 -22
- package/dist/execution/dep-packer.d.ts +20 -13
- package/dist/execution/dep-restore.d.ts +9 -5
- package/dist/execution/validate-kici-deps.d.ts +65 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +522 -2
- package/dist/server.js +511 -138
- package/dist/workflow-runner.js +363 -103
- package/package.json +4 -4
- package/sbom.spdx.json +48 -48
package/dist/server.js
CHANGED
|
@@ -12,14 +12,14 @@ import { RingBuffer, createHealthRoutes, createLogger, createMeter, createMetric
|
|
|
12
12
|
import { z } from "zod";
|
|
13
13
|
import { LOGGER_ENV_VARS, defineEnv, validateUnknownKiciVars } from "@kici-dev/shared/env";
|
|
14
14
|
import { ALLOWED_SYSTEM_VARS, ExecutionJobStatus, ExecutionStepStatus, KNOWN_ROLES, PROTOCOL_VERSION, SANDBOX_DEFAULT_VARS, WS_CLOSE_AGENT_AUTH_FAILED, WS_MAX_PAYLOAD_BYTES, deriveOsArchLabels, heartbeatSchema, hostLabel, mergeAutoLabels, orchestratorToAgentMessageSchema, resolveRoleLabels, validateNoReservedLabels } from "@kici-dev/engine";
|
|
15
|
-
import { execFileSync, execSync, fork, spawn } from "node:child_process";
|
|
15
|
+
import { execFile, execFileSync, execSync, fork, spawn } from "node:child_process";
|
|
16
16
|
import WebSocket from "ws";
|
|
17
17
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
18
|
-
import { format } from "node:util";
|
|
18
|
+
import { format, promisify } from "node:util";
|
|
19
19
|
import { existsSync } from "node:fs";
|
|
20
|
-
import path, { dirname, join } from "node:path";
|
|
20
|
+
import path, { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
21
21
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
22
|
-
import fs, { access, mkdir, mkdtemp, readFile, rm, unlink, writeFile } from "node:fs/promises";
|
|
22
|
+
import fs, { access, lstat, mkdir, mkdtemp, readFile, readdir, realpath, rm, unlink, writeFile } from "node:fs/promises";
|
|
23
23
|
import Docker from "dockerode";
|
|
24
24
|
import { buildKiciApi, isDynamicGroupRef, isDynamicJobFn, isStaticArray, isStaticObject } from "@kici-dev/sdk";
|
|
25
25
|
import { c, x } from "tar";
|
|
@@ -27,6 +27,7 @@ import https from "node:https";
|
|
|
27
27
|
import http from "node:http";
|
|
28
28
|
import { pipeline } from "node:stream/promises";
|
|
29
29
|
import { createGunzip } from "node:zlib";
|
|
30
|
+
import { PackageManager, detectPackageManagerFromManifests } from "@kici-dev/shared/package-manager";
|
|
30
31
|
import { createInterface } from "node:readline";
|
|
31
32
|
var __defProp = Object.defineProperty;
|
|
32
33
|
var __esmMin = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
@@ -974,14 +975,14 @@ var init_console_capture = __esmMin((() => {
|
|
|
974
975
|
init_console_capture();
|
|
975
976
|
function safe(name, fallback = "unknown") {
|
|
976
977
|
switch (name) {
|
|
977
|
-
case "version": return "0.1.
|
|
978
|
-
case "buildCommit": return "
|
|
979
|
-
case "sdkVersion": return "0.1.
|
|
978
|
+
case "version": return "0.1.11";
|
|
979
|
+
case "buildCommit": return "b9e5aed32";
|
|
980
|
+
case "sdkVersion": return "0.1.11";
|
|
980
981
|
case "sdkBundleHash": return "675aafe4de03e677785ef2794d8f34951b14e2bd1e6f0ea825953d6c2abfe128";
|
|
981
|
-
case "sharedVersion": return "0.1.
|
|
982
|
+
case "sharedVersion": return "0.1.11";
|
|
982
983
|
case "sharedBundleHash": return "36a23a454fd75ec35f258d39f254543ec0451c6b543fe3618ce427e6e6369aae";
|
|
983
|
-
case "engineVersion": return "0.1.
|
|
984
|
-
case "engineBundleHash": return "
|
|
984
|
+
case "engineVersion": return "0.1.11";
|
|
985
|
+
case "engineBundleHash": return "4ab08e63bcb8558b1191c5d3ab8d58b71950b77d911af56c9955ae1d787c4302";
|
|
985
986
|
default: return fallback;
|
|
986
987
|
}
|
|
987
988
|
}
|
|
@@ -1628,7 +1629,7 @@ async function extractStepsFromDynamicJob(workflow, dynamicIndex, jobName, event
|
|
|
1628
1629
|
}
|
|
1629
1630
|
var AGENT_SDK_VERSION, AGENT_SDK_BUNDLE_HASH, hookRegistered;
|
|
1630
1631
|
var init_workflow_loader = __esmMin((() => {
|
|
1631
|
-
AGENT_SDK_VERSION = "0.1.
|
|
1632
|
+
AGENT_SDK_VERSION = "0.1.11";
|
|
1632
1633
|
AGENT_SDK_BUNDLE_HASH = "675aafe4de03e677785ef2794d8f34951b14e2bd1e6f0ea825953d6c2abfe128";
|
|
1633
1634
|
hookRegistered = false;
|
|
1634
1635
|
}));
|
|
@@ -1746,8 +1747,8 @@ async function streamFetchAndExtract(url, targetDir) {
|
|
|
1746
1747
|
* We sidestep the race entirely by extracting each attempt into a unique
|
|
1747
1748
|
* scratch dir under `.kici/`. Failed attempts leave orphan scratch dirs whose
|
|
1748
1749
|
* draining writes are harmless — the next attempt does not touch them. On
|
|
1749
|
-
* success
|
|
1750
|
-
* (atomic on the same filesystem), then best-effort
|
|
1750
|
+
* success `moveScratchIntoRepo` renames the extracted entries into place
|
|
1751
|
+
* (atomic on the same filesystem), then best-effort cleans the scratch dir.
|
|
1751
1752
|
*
|
|
1752
1753
|
* Scratch dirs land inside the customer's cloned working tree, so the clone
|
|
1753
1754
|
* phase registers `SCRATCH_DIR_GIT_EXCLUDE_GLOB` in `.git/info/exclude` to
|
|
@@ -1783,13 +1784,58 @@ function resolveOrchestratorUrl(url) {
|
|
|
1783
1784
|
}
|
|
1784
1785
|
}
|
|
1785
1786
|
/**
|
|
1786
|
-
*
|
|
1787
|
+
* Move a fully-extracted scratch tree into the cloned repo. The dep tarball is
|
|
1788
|
+
* packed repo-root-relative, so the scratch holds repo-root entries:
|
|
1789
|
+
* `.kici/node_modules` for every manager, plus (for pnpm) the root
|
|
1790
|
+
* `node_modules/.pnpm` store and in-repo workspace sibling dirs. `.kici/` itself
|
|
1791
|
+
* already exists in the work tree (cloned or source-restored), so its children
|
|
1792
|
+
* are moved individually; every other top-level entry is moved wholesale.
|
|
1787
1793
|
*
|
|
1788
|
-
*
|
|
1789
|
-
*
|
|
1790
|
-
*
|
|
1794
|
+
* On a cache-hit execution agent the destinations do not pre-exist (source
|
|
1795
|
+
* restore excludes node_modules and never carries sibling dirs), so the renames
|
|
1796
|
+
* have nothing to race; the defensive `rm` covers re-runs.
|
|
1797
|
+
*/
|
|
1798
|
+
async function moveScratchIntoRepo(scratchDir, workDir) {
|
|
1799
|
+
for (const child of await fs.readdir(scratchDir)) if (child === ".kici") {
|
|
1800
|
+
const kiciScratch = join(scratchDir, ".kici");
|
|
1801
|
+
for (const sub of await fs.readdir(kiciScratch)) await moveInto(join(kiciScratch, sub), join(workDir, ".kici", sub));
|
|
1802
|
+
} else await moveInto(join(scratchDir, child), join(workDir, child));
|
|
1803
|
+
}
|
|
1804
|
+
/** Move `src` to `dest`, creating the parent and clearing any stale dest. */
|
|
1805
|
+
async function moveInto(src, dest) {
|
|
1806
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
1807
|
+
await fs.rm(dest, {
|
|
1808
|
+
recursive: true,
|
|
1809
|
+
force: true
|
|
1810
|
+
});
|
|
1811
|
+
await fs.rename(src, dest);
|
|
1812
|
+
}
|
|
1813
|
+
/** Best-effort cleanup of a settled scratch dir; logs and continues on failure. */
|
|
1814
|
+
async function cleanupScratch(scratchDir) {
|
|
1815
|
+
try {
|
|
1816
|
+
await fs.rm(scratchDir, {
|
|
1817
|
+
recursive: true,
|
|
1818
|
+
force: true
|
|
1819
|
+
});
|
|
1820
|
+
} catch (cleanupErr) {
|
|
1821
|
+
logger$8.warn("Scratch dir cleanup failed (orphan left behind)", {
|
|
1822
|
+
scratchDir,
|
|
1823
|
+
error: cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr)
|
|
1824
|
+
});
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
/**
|
|
1828
|
+
* Restore dependencies from a cached tarball into the cloned repo.
|
|
1791
1829
|
*
|
|
1792
|
-
*
|
|
1830
|
+
* The tarball is packed repo-root-relative (see `dep-packer.ts`): every manager
|
|
1831
|
+
* carries `.kici/node_modules`; pnpm additionally carries the root
|
|
1832
|
+
* `node_modules/.pnpm` store and the in-repo workspace siblings `.kici` resolves.
|
|
1833
|
+
* Restore extracts into a scratch dir, then moves each entry into place — one
|
|
1834
|
+
* code path for all managers.
|
|
1835
|
+
*
|
|
1836
|
+
* For HTTP/HTTPS URLs: a streaming pipeline (response -> hash -> gunzip -> tar)
|
|
1837
|
+
* with a 5-minute timeout and up to 2 retries avoids buffering whole tarballs.
|
|
1838
|
+
* For file:// URLs: a buffer-based approach (local, no streaming benefit).
|
|
1793
1839
|
*
|
|
1794
1840
|
* @param workDir - Root directory of the cloned repository
|
|
1795
1841
|
* @param depsUrl - URL to the dependency tarball (http://, https://, or file://)
|
|
@@ -1806,11 +1852,14 @@ async function restoreDeps(workDir, depsUrl, depsHash) {
|
|
|
1806
1852
|
const actualHash = computeHash(data);
|
|
1807
1853
|
if (actualHash !== depsHash) throw new Error(`Dep tarball hash mismatch: expected ${depsHash}, got ${actualHash}`);
|
|
1808
1854
|
}
|
|
1809
|
-
|
|
1855
|
+
const scratchDir = join(kiciDir, `${SCRATCH_DIR_BASENAME_PREFIX}${process.pid}-file-${Date.now()}`);
|
|
1856
|
+
await extractTarball(data, scratchDir);
|
|
1857
|
+
await moveScratchIntoRepo(scratchDir, workDir);
|
|
1858
|
+
await cleanupScratch(scratchDir);
|
|
1810
1859
|
const sizeMB = (data.length / (1024 * 1024)).toFixed(2);
|
|
1811
1860
|
logger$8.info("Dependencies restored from cache (file)", {
|
|
1812
1861
|
sizeMB,
|
|
1813
|
-
targetDir:
|
|
1862
|
+
targetDir: workDir
|
|
1814
1863
|
});
|
|
1815
1864
|
return;
|
|
1816
1865
|
}
|
|
@@ -1824,19 +1873,9 @@ async function restoreDeps(workDir, depsUrl, depsHash) {
|
|
|
1824
1873
|
try {
|
|
1825
1874
|
const { scratchDir, hash } = await extractIntoScratch(depsUrl, kiciDir, attempt);
|
|
1826
1875
|
if (depsHash && hash !== depsHash) throw new Error(`Dep tarball hash mismatch: expected ${depsHash}, got ${hash}`);
|
|
1827
|
-
await
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
recursive: true,
|
|
1831
|
-
force: true
|
|
1832
|
-
});
|
|
1833
|
-
} catch (cleanupErr) {
|
|
1834
|
-
logger$8.warn("Scratch dir cleanup failed (orphan left behind)", {
|
|
1835
|
-
scratchDir,
|
|
1836
|
-
error: cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr)
|
|
1837
|
-
});
|
|
1838
|
-
}
|
|
1839
|
-
logger$8.info("Dependencies restored from cache (stream)", { targetDir: kiciDir });
|
|
1876
|
+
await moveScratchIntoRepo(scratchDir, workDir);
|
|
1877
|
+
await cleanupScratch(scratchDir);
|
|
1878
|
+
logger$8.info("Dependencies restored from cache (stream)", { targetDir: workDir });
|
|
1840
1879
|
return;
|
|
1841
1880
|
} catch (err) {
|
|
1842
1881
|
lastError = err instanceof Error ? err : new Error(String(err));
|
|
@@ -2704,46 +2743,208 @@ function redactNpmOutput(input, tokens) {
|
|
|
2704
2743
|
}
|
|
2705
2744
|
var init_npm_registry_config = __esmMin((() => {}));
|
|
2706
2745
|
//#endregion
|
|
2746
|
+
//#region src/execution/validate-kici-deps.ts
|
|
2747
|
+
/**
|
|
2748
|
+
* Pre-install validation for `.kici/` dependency specifiers.
|
|
2749
|
+
*
|
|
2750
|
+
* The agent clones a single source repository and installs its `.kici/`
|
|
2751
|
+
* dependencies with the repo's package manager. Local-protocol specifiers —
|
|
2752
|
+
* `workspace:`, `file:`, `link:`, `portal:` — resolve a dependency against
|
|
2753
|
+
* another package on the same filesystem rather than a registry. Whether they
|
|
2754
|
+
* are resolvable depends on the manager and the layout:
|
|
2755
|
+
*
|
|
2756
|
+
* - npm has no `workspace:` protocol and cannot resolve any of these from a
|
|
2757
|
+
* registry, so they are rejected up front with an actionable message
|
|
2758
|
+
* instead of the raw `EUNSUPPORTEDPROTOCOL` npm would emit.
|
|
2759
|
+
* - pnpm resolves `workspace:` against the repo's pnpm workspace (the agent
|
|
2760
|
+
* clones the whole repo, so an in-repo sibling is present), and resolves
|
|
2761
|
+
* `file:`/`link:`/`portal:` against a path — allowed when that path stays
|
|
2762
|
+
* inside the cloned repo, rejected when it escapes the clone.
|
|
2763
|
+
*
|
|
2764
|
+
* This module performs that classification so unresolvable specifiers fail
|
|
2765
|
+
* fast with guidance rather than a cryptic install error.
|
|
2766
|
+
*/
|
|
2767
|
+
/**
|
|
2768
|
+
* Scan a parsed `.kici/package.json` for dependency specifiers that use a
|
|
2769
|
+
* local protocol (`workspace:`/`file:`/`link:`/`portal:`). Returns one entry
|
|
2770
|
+
* per dependency, in field order. Returns an empty array when there are none.
|
|
2771
|
+
*/
|
|
2772
|
+
function findLocalProtocolDeps(pkg) {
|
|
2773
|
+
const found = [];
|
|
2774
|
+
for (const field of DEP_FIELDS) {
|
|
2775
|
+
const deps = pkg[field];
|
|
2776
|
+
if (!deps || typeof deps !== "object") continue;
|
|
2777
|
+
for (const [name, spec] of Object.entries(deps)) {
|
|
2778
|
+
if (typeof spec !== "string") continue;
|
|
2779
|
+
const protocol = LOCAL_PROTOCOLS.find((proto) => spec.startsWith(proto));
|
|
2780
|
+
if (protocol) found.push({
|
|
2781
|
+
name,
|
|
2782
|
+
spec,
|
|
2783
|
+
protocol
|
|
2784
|
+
});
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
return found;
|
|
2788
|
+
}
|
|
2789
|
+
/** Parse `.kici/package.json`, returning `null` when it is missing or invalid. */
|
|
2790
|
+
async function readKiciPackageJson(kiciDir) {
|
|
2791
|
+
let raw;
|
|
2792
|
+
try {
|
|
2793
|
+
raw = await readFile(join(kiciDir, "package.json"), "utf-8");
|
|
2794
|
+
} catch {
|
|
2795
|
+
return null;
|
|
2796
|
+
}
|
|
2797
|
+
try {
|
|
2798
|
+
return JSON.parse(raw);
|
|
2799
|
+
} catch {
|
|
2800
|
+
return null;
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
/**
|
|
2804
|
+
* Whether `.kici/package.json` declares any local-protocol dependency. Used to
|
|
2805
|
+
* decide whether the agent must build the in-repo workspace dependency closure
|
|
2806
|
+
* after a pnpm install (so a `workspace:` sibling's build output exists before
|
|
2807
|
+
* the workflow that imports it loads).
|
|
2808
|
+
*/
|
|
2809
|
+
async function kiciHasLocalProtocolDeps(kiciDir) {
|
|
2810
|
+
const pkg = await readKiciPackageJson(kiciDir);
|
|
2811
|
+
if (!pkg) return false;
|
|
2812
|
+
return findLocalProtocolDeps(pkg).length > 0;
|
|
2813
|
+
}
|
|
2814
|
+
async function fileExists$1(target) {
|
|
2815
|
+
try {
|
|
2816
|
+
await access(target);
|
|
2817
|
+
return true;
|
|
2818
|
+
} catch {
|
|
2819
|
+
return false;
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
/** Resolve a `file:`/`link:`/`portal:` spec to an absolute path under kiciDir. */
|
|
2823
|
+
function resolveLocalPath(kiciDir, dep) {
|
|
2824
|
+
const rawPath = dep.spec.slice(dep.protocol.length);
|
|
2825
|
+
return isAbsolute(rawPath) ? resolve(rawPath) : resolve(kiciDir, rawPath);
|
|
2826
|
+
}
|
|
2827
|
+
/** Whether `target` is `repoRoot` itself or a path inside it. */
|
|
2828
|
+
function isInsideRepo(repoRoot, target) {
|
|
2829
|
+
const rel = relative(repoRoot, target);
|
|
2830
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
2831
|
+
}
|
|
2832
|
+
/**
|
|
2833
|
+
* Classify each local-protocol dependency for the detected package manager and
|
|
2834
|
+
* return the ones that are unresolvable in the agent's single-clone model.
|
|
2835
|
+
*/
|
|
2836
|
+
async function findUnresolvableDeps(deps, packageManager, kiciDir, repoRoot) {
|
|
2837
|
+
if (packageManager === PackageManager.Npm) return [...deps];
|
|
2838
|
+
const hasWorkspaceFile = await fileExists$1(join(repoRoot, "pnpm-workspace.yaml"));
|
|
2839
|
+
const unresolvable = [];
|
|
2840
|
+
for (const dep of deps) {
|
|
2841
|
+
if (dep.protocol === "workspace:") {
|
|
2842
|
+
if (!hasWorkspaceFile) unresolvable.push(dep);
|
|
2843
|
+
continue;
|
|
2844
|
+
}
|
|
2845
|
+
if (!isInsideRepo(repoRoot, resolveLocalPath(kiciDir, dep))) unresolvable.push(dep);
|
|
2846
|
+
}
|
|
2847
|
+
return unresolvable;
|
|
2848
|
+
}
|
|
2849
|
+
/** Build the actionable error for unresolvable local-protocol dependencies. */
|
|
2850
|
+
function formatUnresolvableDepError(offenders, packageManager) {
|
|
2851
|
+
const list = offenders.map((o) => `${o.name}: ${o.spec}`).join(", ");
|
|
2852
|
+
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.`;
|
|
2853
|
+
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.`;
|
|
2854
|
+
}
|
|
2855
|
+
/**
|
|
2856
|
+
* Throw an actionable error when `.kici/package.json` declares a local-protocol
|
|
2857
|
+
* dependency the detected package manager cannot resolve from the single cloned
|
|
2858
|
+
* repository. A missing or unparseable package.json is left for the install to
|
|
2859
|
+
* report.
|
|
2860
|
+
*/
|
|
2861
|
+
async function assertResolvableDeps(args) {
|
|
2862
|
+
const pkg = await readKiciPackageJson(args.kiciDir);
|
|
2863
|
+
if (!pkg) return;
|
|
2864
|
+
const localDeps = findLocalProtocolDeps(pkg);
|
|
2865
|
+
if (localDeps.length === 0) return;
|
|
2866
|
+
const offenders = await findUnresolvableDeps(localDeps, args.packageManager, args.kiciDir, args.repoRoot);
|
|
2867
|
+
if (offenders.length === 0) return;
|
|
2868
|
+
throw new Error(formatUnresolvableDepError(offenders, args.packageManager));
|
|
2869
|
+
}
|
|
2870
|
+
var LOCAL_PROTOCOLS, DEP_FIELDS;
|
|
2871
|
+
var init_validate_kici_deps = __esmMin((() => {
|
|
2872
|
+
LOCAL_PROTOCOLS = [
|
|
2873
|
+
"workspace:",
|
|
2874
|
+
"file:",
|
|
2875
|
+
"link:",
|
|
2876
|
+
"portal:"
|
|
2877
|
+
];
|
|
2878
|
+
DEP_FIELDS = [
|
|
2879
|
+
"dependencies",
|
|
2880
|
+
"devDependencies",
|
|
2881
|
+
"optionalDependencies",
|
|
2882
|
+
"peerDependencies"
|
|
2883
|
+
];
|
|
2884
|
+
}));
|
|
2885
|
+
//#endregion
|
|
2707
2886
|
//#region src/execution/dep-installer.ts
|
|
2708
2887
|
/**
|
|
2709
2888
|
* Inline dependency installation for graceful degradation.
|
|
2710
2889
|
*
|
|
2711
|
-
* When dep cache is unavailable or download fails, the agent
|
|
2712
|
-
*
|
|
2890
|
+
* When the dep cache is unavailable or a download fails, the agent installs
|
|
2891
|
+
* `.kici/` dependencies directly with the repository's package manager.
|
|
2713
2892
|
*
|
|
2714
|
-
*
|
|
2715
|
-
*
|
|
2893
|
+
* The package manager is detected from the cloned repo (npm / pnpm); the
|
|
2894
|
+
* presence of `.kici/package.json` signals that deps should be installed. npm
|
|
2895
|
+
* is the default and ships with every Node.js install; pnpm is used when the
|
|
2896
|
+
* repo is a pnpm workspace so a `.kici/` member can resolve in-repo
|
|
2897
|
+
* `workspace:` siblings. yarn is detected but not yet supported and is
|
|
2898
|
+
* rejected with an actionable error.
|
|
2716
2899
|
*
|
|
2717
|
-
* Security:
|
|
2718
|
-
* cache poisoning across build jobs
|
|
2719
|
-
* taint the cache used by subsequent builds.
|
|
2720
|
-
*
|
|
2721
|
-
* `--ignore-scripts` whenever a private
|
|
2900
|
+
* Security: the install runs with an isolated per-invocation cache/store
|
|
2901
|
+
* directory to prevent cache poisoning across build jobs — a malicious
|
|
2902
|
+
* package.json in one repo cannot taint the cache used by subsequent builds.
|
|
2903
|
+
* The same pressure rules out letting lifecycle scripts see synthesized auth
|
|
2904
|
+
* env vars — the install runs with `--ignore-scripts` whenever a private
|
|
2905
|
+
* registry is configured.
|
|
2722
2906
|
*/
|
|
2723
2907
|
/**
|
|
2724
|
-
*
|
|
2725
|
-
*
|
|
2726
|
-
*
|
|
2908
|
+
* Detect the package manager for the cloned repo from its committed manifests.
|
|
2909
|
+
* A pnpm workspace's `packageManager` field + `pnpm-lock.yaml` live at the repo
|
|
2910
|
+
* root, so check there first; fall back to `.kici/` for a standalone
|
|
2911
|
+
* (non-workspace) project that carries its own lockfile; default to npm when
|
|
2912
|
+
* neither carries a signal. Uses the manifests-only detector so the agent's own
|
|
2913
|
+
* launch env (`npm_config_user_agent`) never leaks into the decision.
|
|
2914
|
+
*/
|
|
2915
|
+
async function detectKiciPackageManager(repoRoot, kiciDir) {
|
|
2916
|
+
return await detectPackageManagerFromManifests(repoRoot) ?? await detectPackageManagerFromManifests(kiciDir) ?? PackageManager.Npm;
|
|
2917
|
+
}
|
|
2918
|
+
/**
|
|
2919
|
+
* Install `.kici/` dependencies inline with the repo's package manager.
|
|
2727
2920
|
*
|
|
2728
|
-
*
|
|
2729
|
-
*
|
|
2921
|
+
* Falls back to this when the dep cache is unavailable or a download fails.
|
|
2922
|
+
* The install runs with an isolated cache/store directory (created in
|
|
2923
|
+
* `os.tmpdir()`) to prevent cache poisoning between build jobs; the directory
|
|
2924
|
+
* is removed after installation.
|
|
2730
2925
|
*
|
|
2731
|
-
* If `opts.npmRegistries` / `opts.installEnvSecrets` is provided,
|
|
2732
|
-
*
|
|
2733
|
-
* the
|
|
2734
|
-
*
|
|
2735
|
-
* synthesized token env vars.
|
|
2926
|
+
* If `opts.npmRegistries` / `opts.installEnvSecrets` is provided, a job-scoped
|
|
2927
|
+
* `.kici/.npmrc` overlay is synthesized for the install, restored in `finally`,
|
|
2928
|
+
* and the install runs with `--ignore-scripts` so lifecycle scripts in a
|
|
2929
|
+
* committed `package.json` cannot exfiltrate the synthesized token env vars.
|
|
2736
2930
|
*
|
|
2737
|
-
* @param kiciDir - Path to the
|
|
2738
|
-
* @param opts - Optional registry / installEnv configuration.
|
|
2739
|
-
* behavior is identical to the pre-private-registry version.
|
|
2931
|
+
* @param kiciDir - Path to the `.kici/` directory containing package.json.
|
|
2932
|
+
* @param opts - Optional registry / installEnv / repoRoot configuration.
|
|
2740
2933
|
*/
|
|
2741
2934
|
async function installDeps(kiciDir, opts = {}) {
|
|
2935
|
+
const repoRoot = opts.repoRoot ?? dirname(kiciDir);
|
|
2936
|
+
const packageManager = await detectKiciPackageManager(repoRoot, kiciDir);
|
|
2742
2937
|
logger$5.info("Installing deps inline", {
|
|
2743
|
-
packageManager
|
|
2938
|
+
packageManager,
|
|
2744
2939
|
dir: kiciDir
|
|
2745
2940
|
});
|
|
2746
|
-
process.stderr.write(`[dep-installer:trace] starting install: pm
|
|
2941
|
+
process.stderr.write(`[dep-installer:trace] starting install: pm=${packageManager}, cwd=${kiciDir}\n`);
|
|
2942
|
+
if (packageManager === PackageManager.Yarn) throw new Error("This repository uses yarn, which the KiCI agent does not yet support for .kici/ dependency installation. Use npm or pnpm for the .kici/ project, or open a feature request for yarn support.");
|
|
2943
|
+
await assertResolvableDeps({
|
|
2944
|
+
kiciDir,
|
|
2945
|
+
repoRoot,
|
|
2946
|
+
packageManager
|
|
2947
|
+
});
|
|
2747
2948
|
const startTime = Date.now();
|
|
2748
2949
|
const hasPrivateRegistry = (opts.npmRegistries?.length ?? 0) > 0 || (opts.installEnvSecrets ? Object.keys(opts.installEnvSecrets).length > 0 : false);
|
|
2749
2950
|
const registryConfig = await applyNpmRegistryConfig({
|
|
@@ -2753,125 +2954,297 @@ async function installDeps(kiciDir, opts = {}) {
|
|
|
2753
2954
|
jobIdShort: opts.jobIdShort ?? "00000000"
|
|
2754
2955
|
});
|
|
2755
2956
|
try {
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
const { promisify } = await import("node:util");
|
|
2767
|
-
const execFileAsync = promisify(execFile);
|
|
2768
|
-
const buildArgs = (...prefix) => {
|
|
2769
|
-
const args = [
|
|
2770
|
-
...prefix,
|
|
2771
|
-
npmCmd,
|
|
2772
|
-
"--cache",
|
|
2773
|
-
cacheDir,
|
|
2774
|
-
"--no-audit",
|
|
2775
|
-
"--no-fund"
|
|
2776
|
-
];
|
|
2777
|
-
if (hasPrivateRegistry) args.push("--ignore-scripts");
|
|
2778
|
-
return args;
|
|
2779
|
-
};
|
|
2780
|
-
try {
|
|
2781
|
-
if (npmCliPath) {
|
|
2782
|
-
process.stderr.write(`[dep-installer:trace] running: ${nodeExe} ${npmCliPath} ${buildArgs().join(" ")}\n`);
|
|
2783
|
-
await execFileAsync(nodeExe, buildArgs(npmCliPath), {
|
|
2784
|
-
cwd: kiciDir,
|
|
2785
|
-
env: envWithNode,
|
|
2786
|
-
timeout: 6e5,
|
|
2787
|
-
maxBuffer: 128 * 1024 * 1024
|
|
2788
|
-
});
|
|
2789
|
-
} else {
|
|
2790
|
-
process.stderr.write(`[dep-installer:trace] running: npm ${buildArgs().join(" ")}\n`);
|
|
2791
|
-
await execFileAsync("npm", buildArgs(), {
|
|
2792
|
-
cwd: kiciDir,
|
|
2793
|
-
env: envWithNode,
|
|
2794
|
-
timeout: 6e5,
|
|
2795
|
-
maxBuffer: 128 * 1024 * 1024
|
|
2796
|
-
});
|
|
2797
|
-
}
|
|
2798
|
-
} finally {
|
|
2799
|
-
await rm(cacheDir, {
|
|
2800
|
-
recursive: true,
|
|
2801
|
-
force: true
|
|
2802
|
-
}).catch(() => {});
|
|
2803
|
-
}
|
|
2957
|
+
if (packageManager === PackageManager.Pnpm) await runPnpmInstall({
|
|
2958
|
+
kiciDir,
|
|
2959
|
+
hasPrivateRegistry,
|
|
2960
|
+
registryConfig
|
|
2961
|
+
});
|
|
2962
|
+
else await runNpmInstall({
|
|
2963
|
+
kiciDir,
|
|
2964
|
+
hasPrivateRegistry,
|
|
2965
|
+
registryConfig
|
|
2966
|
+
});
|
|
2804
2967
|
} catch (e) {
|
|
2805
|
-
const msg = toErrorMessage(e);
|
|
2806
2968
|
const tokens = registryConfig.tokensForRedaction;
|
|
2807
|
-
process.stderr.write(`[dep-installer:trace] INSTALL FAILED: ${redactNpmOutput(
|
|
2808
|
-
|
|
2809
|
-
if (e && typeof e === "object" && "stderr" in e) process.stderr.write(`[dep-installer:trace] stderr: ${redactNpmOutput(String(e.stderr), tokens).slice(0, 500)}\n`);
|
|
2969
|
+
process.stderr.write(`[dep-installer:trace] INSTALL FAILED: ${redactNpmOutput(toErrorMessage(e), tokens)}\n`);
|
|
2970
|
+
logSubprocessStreams(e, tokens);
|
|
2810
2971
|
throw e;
|
|
2811
2972
|
} finally {
|
|
2812
2973
|
await registryConfig.cleanup();
|
|
2813
2974
|
}
|
|
2975
|
+
if (packageManager === PackageManager.Pnpm && await kiciHasLocalProtocolDeps(kiciDir)) await buildWorkspaceClosure(repoRoot);
|
|
2814
2976
|
const durationMs = Date.now() - startTime;
|
|
2815
2977
|
process.stderr.write(`[dep-installer:trace] install complete: ${durationMs}ms\n`);
|
|
2816
2978
|
logger$5.info("Deps installed inline", {
|
|
2817
|
-
packageManager
|
|
2979
|
+
packageManager,
|
|
2818
2980
|
durationMs
|
|
2819
2981
|
});
|
|
2820
2982
|
}
|
|
2821
|
-
|
|
2983
|
+
/** Build the Node binary directory onto PATH so spawned tools find `node`. */
|
|
2984
|
+
function envWithNodeOnPath(extraEnv, nodeDir) {
|
|
2985
|
+
const { NODE_ENV: _NODE_ENV, ...restEnv } = process.env;
|
|
2986
|
+
return {
|
|
2987
|
+
...restEnv,
|
|
2988
|
+
...extraEnv,
|
|
2989
|
+
PATH: `${nodeDir}${process.platform === "win32" ? ";" : ":"}${process.env.PATH ?? ""}`
|
|
2990
|
+
};
|
|
2991
|
+
}
|
|
2992
|
+
/** Run `npm install` in `.kici/` with an isolated cache directory. */
|
|
2993
|
+
async function runNpmInstall(args) {
|
|
2994
|
+
const { npmCliPath, nodeExe, nodeDir } = resolveNpm();
|
|
2995
|
+
const cacheDir = await mkdtemp(join(tmpdir(), "kici-npm-cache-"));
|
|
2996
|
+
const env = envWithNodeOnPath(args.registryConfig.extraEnv, nodeDir);
|
|
2997
|
+
const buildArgs = (...prefix) => {
|
|
2998
|
+
const a = [
|
|
2999
|
+
...prefix,
|
|
3000
|
+
"install",
|
|
3001
|
+
"--cache",
|
|
3002
|
+
cacheDir,
|
|
3003
|
+
"--no-audit",
|
|
3004
|
+
"--no-fund"
|
|
3005
|
+
];
|
|
3006
|
+
if (args.hasPrivateRegistry) a.push("--ignore-scripts");
|
|
3007
|
+
return a;
|
|
3008
|
+
};
|
|
3009
|
+
try {
|
|
3010
|
+
const bin = npmCliPath ? nodeExe : "npm";
|
|
3011
|
+
const argv = npmCliPath ? buildArgs(npmCliPath) : buildArgs();
|
|
3012
|
+
process.stderr.write(`[dep-installer:trace] running: ${bin} ${argv.join(" ")}\n`);
|
|
3013
|
+
await execFileAsync(bin, argv, {
|
|
3014
|
+
cwd: args.kiciDir,
|
|
3015
|
+
env,
|
|
3016
|
+
timeout: INSTALL_TIMEOUT_MS,
|
|
3017
|
+
maxBuffer: INSTALL_MAX_BUFFER
|
|
3018
|
+
});
|
|
3019
|
+
} finally {
|
|
3020
|
+
await rm(cacheDir, {
|
|
3021
|
+
recursive: true,
|
|
3022
|
+
force: true
|
|
3023
|
+
}).catch(() => {});
|
|
3024
|
+
}
|
|
3025
|
+
}
|
|
3026
|
+
/**
|
|
3027
|
+
* Run `pnpm install` from `.kici/`. pnpm walks up to the workspace root, so a
|
|
3028
|
+
* `workspace:` sibling in the same cloned repo resolves. Uses an isolated store
|
|
3029
|
+
* (`--config.store-dir`) for cross-job isolation, `package-import-method=copy`
|
|
3030
|
+
* so the on-disk store is a self-contained tree of real files (a later dep
|
|
3031
|
+
* cache tars it), and disables interactive purge prompts + the side-effects
|
|
3032
|
+
* cache for deterministic, non-interactive runs.
|
|
3033
|
+
*/
|
|
3034
|
+
async function runPnpmInstall(args) {
|
|
3035
|
+
await assertPnpmAvailable();
|
|
3036
|
+
const { nodeDir } = resolveNpm();
|
|
3037
|
+
const storeDir = await mkdtemp(join(tmpdir(), "kici-pnpm-store-"));
|
|
3038
|
+
const env = envWithNodeOnPath(args.registryConfig.extraEnv, nodeDir);
|
|
3039
|
+
const argv = [
|
|
3040
|
+
"install",
|
|
3041
|
+
`--config.store-dir=${storeDir}`,
|
|
3042
|
+
"--config.package-import-method=copy",
|
|
3043
|
+
"--config.confirm-modules-purge=false",
|
|
3044
|
+
"--config.side-effects-cache=false"
|
|
3045
|
+
];
|
|
3046
|
+
if (args.hasPrivateRegistry) argv.push("--ignore-scripts");
|
|
3047
|
+
try {
|
|
3048
|
+
process.stderr.write(`[dep-installer:trace] running: pnpm ${argv.join(" ")}\n`);
|
|
3049
|
+
await execFileAsync("pnpm", argv, {
|
|
3050
|
+
cwd: args.kiciDir,
|
|
3051
|
+
env,
|
|
3052
|
+
timeout: INSTALL_TIMEOUT_MS,
|
|
3053
|
+
maxBuffer: INSTALL_MAX_BUFFER
|
|
3054
|
+
});
|
|
3055
|
+
} finally {
|
|
3056
|
+
await rm(storeDir, {
|
|
3057
|
+
recursive: true,
|
|
3058
|
+
force: true
|
|
3059
|
+
}).catch(() => {});
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
3062
|
+
/**
|
|
3063
|
+
* Build the in-repo dependency closure of the `.kici/` package so a
|
|
3064
|
+
* `workspace:` sibling's build output exists before the workflow that imports
|
|
3065
|
+
* it loads. `--filter "{.kici}^..."` selects only `.kici`'s dependencies (not
|
|
3066
|
+
* `.kici` itself); `--if-present` skips siblings without a build script. Runs
|
|
3067
|
+
* with a clean env (no synthesized registry tokens).
|
|
3068
|
+
*/
|
|
3069
|
+
async function buildWorkspaceClosure(repoRoot) {
|
|
3070
|
+
const { nodeDir } = resolveNpm();
|
|
3071
|
+
const env = envWithNodeOnPath({}, nodeDir);
|
|
3072
|
+
const argv = [
|
|
3073
|
+
"--filter",
|
|
3074
|
+
"{.kici}^...",
|
|
3075
|
+
"run",
|
|
3076
|
+
"build",
|
|
3077
|
+
"--if-present"
|
|
3078
|
+
];
|
|
3079
|
+
process.stderr.write(`[dep-installer:trace] building workspace closure: pnpm ${argv.join(" ")}\n`);
|
|
3080
|
+
await execFileAsync("pnpm", argv, {
|
|
3081
|
+
cwd: repoRoot,
|
|
3082
|
+
env,
|
|
3083
|
+
timeout: INSTALL_TIMEOUT_MS,
|
|
3084
|
+
maxBuffer: INSTALL_MAX_BUFFER
|
|
3085
|
+
});
|
|
3086
|
+
}
|
|
3087
|
+
/** Throw an actionable error when the repo needs pnpm but it is not installed. */
|
|
3088
|
+
async function assertPnpmAvailable() {
|
|
3089
|
+
try {
|
|
3090
|
+
await execFileAsync("pnpm", ["--version"], { timeout: 3e4 });
|
|
3091
|
+
} catch (e) {
|
|
3092
|
+
throw new Error(`This repository is a pnpm workspace, but pnpm is not available on this agent. Install pnpm (e.g. \`corepack enable\`) or run on a container/ Firecracker agent that bundles it. (${toErrorMessage(e)})`);
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
/** Trace the redacted stdout/stderr of a failed install subprocess. */
|
|
3096
|
+
function logSubprocessStreams(e, tokens) {
|
|
3097
|
+
if (e && typeof e === "object" && "stdout" in e) process.stderr.write(`[dep-installer:trace] stdout: ${redactNpmOutput(String(e.stdout), tokens).slice(0, 500)}\n`);
|
|
3098
|
+
if (e && typeof e === "object" && "stderr" in e) process.stderr.write(`[dep-installer:trace] stderr: ${redactNpmOutput(String(e.stderr), tokens).slice(0, 500)}\n`);
|
|
3099
|
+
}
|
|
3100
|
+
var logger$5, execFileAsync, INSTALL_TIMEOUT_MS, INSTALL_MAX_BUFFER;
|
|
2822
3101
|
var init_dep_installer = __esmMin((() => {
|
|
2823
3102
|
init_npm_resolver();
|
|
2824
3103
|
init_npm_registry_config();
|
|
3104
|
+
init_validate_kici_deps();
|
|
2825
3105
|
logger$5 = createLogger({ prefix: "dep-installer" });
|
|
3106
|
+
execFileAsync = promisify(execFile);
|
|
3107
|
+
INSTALL_TIMEOUT_MS = 6e5;
|
|
3108
|
+
INSTALL_MAX_BUFFER = 128 * 1024 * 1024;
|
|
2826
3109
|
}));
|
|
2827
3110
|
//#endregion
|
|
2828
3111
|
//#region src/execution/dep-packer.ts
|
|
2829
3112
|
/**
|
|
2830
|
-
*
|
|
3113
|
+
* Dependency tarball creation for build agents.
|
|
3114
|
+
*
|
|
3115
|
+
* After installing dependencies the build agent packs the dependency closure
|
|
3116
|
+
* into a gzip tarball for upload to the dep cache. The closure is packed
|
|
3117
|
+
* **repo-root-relative** (cwd = the clone root) so restore is a single layout
|
|
3118
|
+
* regardless of package manager:
|
|
2831
3119
|
*
|
|
2832
|
-
*
|
|
2833
|
-
*
|
|
3120
|
+
* - npm / yarn: just `.kici/node_modules`.
|
|
3121
|
+
* - pnpm: `.kici/node_modules` plus the repo-root `node_modules/.pnpm` virtual
|
|
3122
|
+
* store and the in-repo `workspace:` sibling package directories `.kici`
|
|
3123
|
+
* depends on (with their built output). pnpm lays `.kici/node_modules` out as
|
|
3124
|
+
* symlinks into the root store and into sibling dirs that live outside
|
|
3125
|
+
* `.kici/`, so packing `.kici/node_modules` alone would capture dangling
|
|
3126
|
+
* links — the store and siblings must travel together.
|
|
2834
3127
|
*
|
|
2835
|
-
* Uses tar.gz
|
|
2836
|
-
*
|
|
3128
|
+
* Uses tar.gz (Node.js built-in zlib, no external binary) in portable mode to
|
|
3129
|
+
* strip user/group info for cross-machine consistency; symlinks are preserved
|
|
3130
|
+
* as symlinks so the pnpm link graph restores intact.
|
|
2837
3131
|
*/
|
|
2838
3132
|
/**
|
|
2839
|
-
* Pack
|
|
3133
|
+
* Pack the dependency closure into a gzip tarball and compute its SHA-256 hash.
|
|
2840
3134
|
*
|
|
2841
|
-
*
|
|
2842
|
-
*
|
|
2843
|
-
*
|
|
2844
|
-
*
|
|
2845
|
-
* @param kiciDir - Path to the .kici/ directory containing node_modules/
|
|
2846
|
-
* @returns Object with tarball Buffer and SHA-256 hash string
|
|
2847
|
-
* @throws Error if node_modules/ does not exist in kiciDir
|
|
3135
|
+
* @param kiciDir - Path to the `.kici/` directory containing node_modules/.
|
|
3136
|
+
* @returns Object with the tarball Buffer and its SHA-256 hash string.
|
|
3137
|
+
* @throws Error if `.kici/node_modules` does not exist.
|
|
2848
3138
|
*/
|
|
2849
3139
|
async function packNodeModules(kiciDir) {
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
3140
|
+
if (!existsSync(join(kiciDir, "node_modules"))) throw new Error(`node_modules not found at ${join(kiciDir, "node_modules")}`);
|
|
3141
|
+
const workDir = dirname(kiciDir);
|
|
3142
|
+
const packageManager = await detectPackageManagerFromManifests(workDir) ?? await detectPackageManagerFromManifests(kiciDir) ?? PackageManager.Npm;
|
|
3143
|
+
const entries = await closureEntries(workDir, kiciDir, packageManager);
|
|
3144
|
+
logger$4.info("Packing dependency closure into tarball", {
|
|
3145
|
+
dir: workDir,
|
|
3146
|
+
packageManager,
|
|
3147
|
+
entries
|
|
3148
|
+
});
|
|
2853
3149
|
const startTime = Date.now();
|
|
2854
3150
|
const stream = c({
|
|
2855
3151
|
gzip: true,
|
|
2856
|
-
cwd:
|
|
3152
|
+
cwd: workDir,
|
|
2857
3153
|
portable: true
|
|
2858
|
-
},
|
|
3154
|
+
}, entries);
|
|
2859
3155
|
const chunks = [];
|
|
2860
3156
|
for await (const chunk of stream) chunks.push(Buffer.from(chunk));
|
|
2861
3157
|
const tarball = Buffer.concat(chunks);
|
|
2862
3158
|
const hash = sha256(tarball);
|
|
2863
3159
|
const sizeMB = (tarball.length / (1024 * 1024)).toFixed(2);
|
|
2864
|
-
|
|
2865
|
-
logger$4.info("Node_modules packed", {
|
|
3160
|
+
logger$4.info("Dependency closure packed", {
|
|
2866
3161
|
sizeMB,
|
|
2867
3162
|
hash: hash.slice(0, 12),
|
|
2868
|
-
durationMs
|
|
3163
|
+
durationMs: Date.now() - startTime
|
|
2869
3164
|
});
|
|
2870
3165
|
return {
|
|
2871
3166
|
tarball,
|
|
2872
3167
|
hash
|
|
2873
3168
|
};
|
|
2874
3169
|
}
|
|
3170
|
+
/**
|
|
3171
|
+
* Compute the repo-root-relative tar entries for the dependency closure. npm /
|
|
3172
|
+
* yarn need only `.kici/node_modules`; pnpm additionally needs the root store
|
|
3173
|
+
* and the in-repo workspace siblings `.kici` resolves.
|
|
3174
|
+
*/
|
|
3175
|
+
async function closureEntries(workDir, kiciDir, packageManager) {
|
|
3176
|
+
const kiciNodeModules = relative(workDir, join(kiciDir, "node_modules"));
|
|
3177
|
+
if (packageManager !== PackageManager.Pnpm) return [kiciNodeModules];
|
|
3178
|
+
const entries = [kiciNodeModules];
|
|
3179
|
+
if (existsSync(join(workDir, "node_modules", ".pnpm"))) entries.push(join("node_modules", ".pnpm"));
|
|
3180
|
+
for (const sibling of await collectInRepoSiblings(workDir, kiciDir)) entries.push(sibling);
|
|
3181
|
+
return entries;
|
|
3182
|
+
}
|
|
3183
|
+
/**
|
|
3184
|
+
* Walk `.kici`'s `node_modules` (and transitively each in-repo sibling's
|
|
3185
|
+
* `node_modules`) collecting the repo-relative directories of `workspace:`
|
|
3186
|
+
* siblings — package dirs that live inside the clone but outside `.kici/` and
|
|
3187
|
+
* outside the root `node_modules/` store. Returns each dir once.
|
|
3188
|
+
*/
|
|
3189
|
+
async function collectInRepoSiblings(workDir, kiciDir) {
|
|
3190
|
+
const repoRoot = resolve(workDir);
|
|
3191
|
+
const kiciResolved = resolve(kiciDir);
|
|
3192
|
+
const rootNodeModules = resolve(join(workDir, "node_modules"));
|
|
3193
|
+
const found = /* @__PURE__ */ new Set();
|
|
3194
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3195
|
+
const queue = [join(kiciDir, "node_modules")];
|
|
3196
|
+
while (queue.length > 0) {
|
|
3197
|
+
const nmDir = queue.shift();
|
|
3198
|
+
const real = await realpath(nmDir).catch(() => null);
|
|
3199
|
+
if (!real || visited.has(real)) continue;
|
|
3200
|
+
visited.add(real);
|
|
3201
|
+
for (const target of await resolveNodeModulesLinks(nmDir)) {
|
|
3202
|
+
if (!isInside(repoRoot, target)) continue;
|
|
3203
|
+
if (isInside(kiciResolved, target) || isInside(rootNodeModules, target)) continue;
|
|
3204
|
+
const rel = relative(workDir, target);
|
|
3205
|
+
if (!found.has(rel)) {
|
|
3206
|
+
found.add(rel);
|
|
3207
|
+
queue.push(join(target, "node_modules"));
|
|
3208
|
+
}
|
|
3209
|
+
}
|
|
3210
|
+
}
|
|
3211
|
+
return [...found];
|
|
3212
|
+
}
|
|
3213
|
+
/** Resolve every package symlink target under a `node_modules` dir (descending one level into `@scope` dirs). */
|
|
3214
|
+
async function resolveNodeModulesLinks(nmDir) {
|
|
3215
|
+
const targets = [];
|
|
3216
|
+
for (const entry of await readdir(nmDir).catch(() => [])) {
|
|
3217
|
+
if (entry.startsWith(".")) continue;
|
|
3218
|
+
const entryPath = join(nmDir, entry);
|
|
3219
|
+
if (entry.startsWith("@")) {
|
|
3220
|
+
for (const scoped of await readdir(entryPath).catch(() => [])) {
|
|
3221
|
+
const target = await resolveIfSymlink(join(entryPath, scoped));
|
|
3222
|
+
if (target) targets.push(target);
|
|
3223
|
+
}
|
|
3224
|
+
continue;
|
|
3225
|
+
}
|
|
3226
|
+
const target = await resolveIfSymlink(entryPath);
|
|
3227
|
+
if (target) targets.push(target);
|
|
3228
|
+
}
|
|
3229
|
+
return targets;
|
|
3230
|
+
}
|
|
3231
|
+
/** Return the real path of `p` if it is a symlink, else null. */
|
|
3232
|
+
async function resolveIfSymlink(p) {
|
|
3233
|
+
try {
|
|
3234
|
+
if (!(await lstat(p)).isSymbolicLink()) return null;
|
|
3235
|
+
return await realpath(p);
|
|
3236
|
+
} catch {
|
|
3237
|
+
return null;
|
|
3238
|
+
}
|
|
3239
|
+
}
|
|
3240
|
+
/** Whether `target` is `root` itself or a path inside it. */
|
|
3241
|
+
function isInside(root, target) {
|
|
3242
|
+
const rel = relative(root, target);
|
|
3243
|
+
return rel === "" || !rel.startsWith("..") && !rel.startsWith(`..${sep}`) && !isAbsoluteRel(rel);
|
|
3244
|
+
}
|
|
3245
|
+
function isAbsoluteRel(rel) {
|
|
3246
|
+
return rel.length > 1 && rel[1] === ":";
|
|
3247
|
+
}
|
|
2875
3248
|
var logger$4;
|
|
2876
3249
|
var init_dep_packer = __esmMin((() => {
|
|
2877
3250
|
logger$4 = createLogger({ prefix: "dep-packer" });
|
|
@@ -5053,14 +5426,14 @@ var init_job_runner = __esmMin((() => {
|
|
|
5053
5426
|
*/
|
|
5054
5427
|
init_console_capture();
|
|
5055
5428
|
init_npm_resolver();
|
|
5056
|
-
const AGENT_VERSION = "0.1.
|
|
5057
|
-
const BUILD_COMMIT = "
|
|
5058
|
-
const SDK_VERSION = "0.1.
|
|
5429
|
+
const AGENT_VERSION = "0.1.11";
|
|
5430
|
+
const BUILD_COMMIT = "b9e5aed32";
|
|
5431
|
+
const SDK_VERSION = "0.1.11";
|
|
5059
5432
|
const SDK_BUNDLE_HASH = "675aafe4de03e677785ef2794d8f34951b14e2bd1e6f0ea825953d6c2abfe128";
|
|
5060
|
-
const SHARED_VERSION = "0.1.
|
|
5433
|
+
const SHARED_VERSION = "0.1.11";
|
|
5061
5434
|
const SHARED_BUNDLE_HASH = "36a23a454fd75ec35f258d39f254543ec0451c6b543fe3618ce427e6e6369aae";
|
|
5062
|
-
const ENGINE_VERSION = "0.1.
|
|
5063
|
-
const ENGINE_BUNDLE_HASH = "
|
|
5435
|
+
const ENGINE_VERSION = "0.1.11";
|
|
5436
|
+
const ENGINE_BUNDLE_HASH = "4ab08e63bcb8558b1191c5d3ab8d58b71950b77d911af56c9955ae1d787c4302";
|
|
5064
5437
|
initTelemetry({
|
|
5065
5438
|
serviceName: "kici-agent",
|
|
5066
5439
|
otlpEndpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT
|