zyrk 0.3.7 → 0.3.9
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/VERSION +1 -1
- package/bin/zyrk.js +1 -1
- package/dist/cli/main.d.ts +54 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/main.js +473 -0
- package/dist/cli/npm-delegate.d.ts +38 -0
- package/dist/cli/npm-delegate.d.ts.map +1 -0
- package/{src/cli/npm-delegate.ts → dist/cli/npm-delegate.js} +26 -55
- package/dist/cli/parse.d.ts +40 -0
- package/dist/cli/parse.d.ts.map +1 -0
- package/dist/cli/parse.js +78 -0
- package/dist/commands/dependencies.d.ts +12 -0
- package/dist/commands/dependencies.d.ts.map +1 -0
- package/dist/commands/dependencies.js +118 -0
- package/dist/commands/deploy.d.ts +15 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +506 -0
- package/dist/commands/maintenance.d.ts +27 -0
- package/dist/commands/maintenance.d.ts.map +1 -0
- package/dist/commands/maintenance.js +198 -0
- package/dist/commands/patch.d.ts +38 -0
- package/dist/commands/patch.d.ts.map +1 -0
- package/dist/commands/patch.js +336 -0
- package/dist/commands/project.d.ts +15 -0
- package/dist/commands/project.d.ts.map +1 -0
- package/dist/commands/project.js +396 -0
- package/dist/commands/store.d.ts +10 -0
- package/dist/commands/store.d.ts.map +1 -0
- package/dist/commands/store.js +551 -0
- package/dist/config/paths.d.ts +37 -0
- package/dist/config/paths.d.ts.map +1 -0
- package/dist/config/paths.js +66 -0
- package/dist/dlx/create.d.ts +23 -0
- package/dist/dlx/create.d.ts.map +1 -0
- package/dist/dlx/create.js +59 -0
- package/dist/dlx/run.d.ts +54 -0
- package/dist/dlx/run.d.ts.map +1 -0
- package/dist/dlx/run.js +274 -0
- package/dist/errors.d.ts +68 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +111 -0
- package/dist/extract/tarball.d.ts +82 -0
- package/dist/extract/tarball.d.ts.map +1 -0
- package/dist/extract/tarball.js +645 -0
- package/dist/fetch/all.d.ts +48 -0
- package/dist/fetch/all.d.ts.map +1 -0
- package/dist/fetch/all.js +127 -0
- package/dist/fetch/package.d.ts +45 -0
- package/dist/fetch/package.d.ts.map +1 -0
- package/dist/fetch/package.js +124 -0
- package/dist/fetch/registry.d.ts +109 -0
- package/dist/fetch/registry.d.ts.map +1 -0
- package/dist/fetch/registry.js +418 -0
- package/dist/install/import-npm.d.ts +4 -0
- package/dist/install/import-npm.d.ts.map +1 -0
- package/dist/install/import-npm.js +147 -0
- package/dist/install/lock.d.ts +8 -0
- package/dist/install/lock.d.ts.map +1 -0
- package/dist/install/lock.js +146 -0
- package/dist/install/metadata.d.ts +19 -0
- package/dist/install/metadata.d.ts.map +1 -0
- package/dist/install/metadata.js +79 -0
- package/dist/install/registry.d.ts +8 -0
- package/dist/install/registry.d.ts.map +1 -0
- package/dist/install/registry.js +59 -0
- package/dist/install/run.d.ts +32 -0
- package/dist/install/run.d.ts.map +1 -0
- package/dist/install/run.js +545 -0
- package/dist/lockfile/fingerprints.d.ts +35 -0
- package/dist/lockfile/fingerprints.d.ts.map +1 -0
- package/dist/lockfile/fingerprints.js +56 -0
- package/dist/lockfile/read.d.ts +27 -0
- package/dist/lockfile/read.d.ts.map +1 -0
- package/dist/lockfile/read.js +126 -0
- package/dist/lockfile/types.d.ts +74 -0
- package/dist/lockfile/types.d.ts.map +1 -0
- package/dist/lockfile/types.js +17 -0
- package/dist/lockfile/write.d.ts +39 -0
- package/dist/lockfile/write.d.ts.map +1 -0
- package/dist/lockfile/write.js +180 -0
- package/dist/log.d.ts +21 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +79 -0
- package/dist/project/projection.d.ts +50 -0
- package/dist/project/projection.d.ts.map +1 -0
- package/dist/project/projection.js +276 -0
- package/dist/resolve/resolver.d.ts +75 -0
- package/dist/resolve/resolver.d.ts.map +1 -0
- package/dist/resolve/resolver.js +533 -0
- package/dist/resolve/spec.d.ts +26 -0
- package/dist/resolve/spec.d.ts.map +1 -0
- package/dist/resolve/spec.js +87 -0
- package/dist/store/db.d.ts +49 -0
- package/dist/store/db.d.ts.map +1 -0
- package/{src/store/db.ts → dist/store/db.js} +106 -206
- package/dist/store/graph.d.ts +60 -0
- package/dist/store/graph.d.ts.map +1 -0
- package/dist/store/graph.js +115 -0
- package/dist/store/layout.d.ts +71 -0
- package/dist/store/layout.d.ts.map +1 -0
- package/dist/store/layout.js +282 -0
- package/dist/store/manifests.d.ts +78 -0
- package/dist/store/manifests.d.ts.map +1 -0
- package/dist/store/manifests.js +238 -0
- package/dist/store/objects.d.ts +56 -0
- package/dist/store/objects.d.ts.map +1 -0
- package/dist/store/objects.js +157 -0
- package/dist/store/realize.d.ts +38 -0
- package/dist/store/realize.d.ts.map +1 -0
- package/dist/store/realize.js +227 -0
- package/dist/store/source.d.ts +42 -0
- package/dist/store/source.d.ts.map +1 -0
- package/dist/store/source.js +169 -0
- package/dist/store/txn.d.ts +37 -0
- package/dist/store/txn.d.ts.map +1 -0
- package/dist/store/txn.js +97 -0
- package/dist/store/verify.d.ts +40 -0
- package/dist/store/verify.d.ts.map +1 -0
- package/dist/store/verify.js +396 -0
- package/dist/util/fsx.d.ts +54 -0
- package/dist/util/fsx.d.ts.map +1 -0
- package/dist/util/fsx.js +259 -0
- package/dist/util/hash.d.ts +48 -0
- package/dist/util/hash.d.ts.map +1 -0
- package/dist/util/hash.js +80 -0
- package/dist/util/ids.d.ts +19 -0
- package/dist/util/ids.d.ts.map +1 -0
- package/dist/util/ids.js +38 -0
- package/dist/util/integrity.d.ts +97 -0
- package/dist/util/integrity.d.ts.map +1 -0
- package/dist/util/integrity.js +192 -0
- package/dist/util/json.d.ts +14 -0
- package/dist/util/json.d.ts.map +1 -0
- package/dist/util/json.js +78 -0
- package/dist/util/locks.d.ts +40 -0
- package/dist/util/locks.d.ts.map +1 -0
- package/dist/util/locks.js +186 -0
- package/dist/util/proc.d.ts +32 -0
- package/dist/util/proc.d.ts.map +1 -0
- package/dist/util/proc.js +84 -0
- package/dist/version.d.ts +27 -0
- package/dist/version.d.ts.map +1 -0
- package/{src/version.ts → dist/version.js} +0 -8
- package/package.json +5 -3
- package/src/cli/main.ts +0 -468
- package/src/cli/parse.ts +0 -106
- package/src/commands/dependencies.ts +0 -154
- package/src/commands/deploy.ts +0 -777
- package/src/commands/maintenance.ts +0 -260
- package/src/commands/patch.ts +0 -408
- package/src/commands/project.ts +0 -598
- package/src/commands/store.ts +0 -689
- package/src/config/paths.ts +0 -119
- package/src/dlx/create.ts +0 -69
- package/src/dlx/run.ts +0 -389
- package/src/errors.ts +0 -142
- package/src/extract/tarball.ts +0 -868
- package/src/fetch/all.ts +0 -206
- package/src/fetch/package.ts +0 -181
- package/src/fetch/registry.ts +0 -615
- package/src/install/import-npm.ts +0 -177
- package/src/install/lock.ts +0 -176
- package/src/install/metadata.ts +0 -122
- package/src/install/registry.ts +0 -88
- package/src/install/run.ts +0 -760
- package/src/lockfile/fingerprints.ts +0 -74
- package/src/lockfile/read.ts +0 -198
- package/src/lockfile/types.ts +0 -75
- package/src/lockfile/write.ts +0 -257
- package/src/log.ts +0 -100
- package/src/project/projection.ts +0 -390
- package/src/resolve/resolver.ts +0 -757
- package/src/resolve/spec.ts +0 -131
- package/src/store/graph.ts +0 -175
- package/src/store/layout.ts +0 -431
- package/src/store/manifests.ts +0 -347
- package/src/store/objects.ts +0 -229
- package/src/store/realize.ts +0 -322
- package/src/store/source.ts +0 -249
- package/src/store/txn.ts +0 -118
- package/src/store/verify.ts +0 -433
- package/src/types/vendor.d.ts +0 -120
- package/src/util/fsx.ts +0 -289
- package/src/util/hash.ts +0 -117
- package/src/util/ids.ts +0 -42
- package/src/util/integrity.ts +0 -269
- package/src/util/json.ts +0 -89
- package/src/util/locks.ts +0 -266
- package/src/util/proc.ts +0 -108
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project scaffolding: `zyrk create <name>[@spec] [args...]`.
|
|
3
|
+
*
|
|
4
|
+
* The initializer mapping mirrors npm exactly (verified against npm's own
|
|
5
|
+
* `init` command): `foo` runs `create-foo`, `@scope/bar` runs
|
|
6
|
+
* `@scope/create-bar`, and a bare `@scope` runs `@scope/create`, each with
|
|
7
|
+
* the requested version or `*` when none was given. An already-prefixed
|
|
8
|
+
* `create-foo` is used as-is. Execution is the dlx flow, so temporary
|
|
9
|
+
* environments, trust policy, and cleanup all behave identically.
|
|
10
|
+
*/
|
|
11
|
+
import npa from "npm-package-arg";
|
|
12
|
+
import { ZyrkError } from "../errors.js";
|
|
13
|
+
import { runDlx } from "./run.js";
|
|
14
|
+
/**
|
|
15
|
+
* Map an initializer to its starter package specifier. Throws ZYRK_USAGE
|
|
16
|
+
* for non-registry inputs (Git/hosted initializers are deferred along with
|
|
17
|
+
* every other non-registry protocol).
|
|
18
|
+
*/
|
|
19
|
+
export function starterSpecFor(input) {
|
|
20
|
+
const bareScope = /^@([^/]+?)(@([^/]+))?$/.exec(input);
|
|
21
|
+
if (bareScope !== null) {
|
|
22
|
+
const scope = bareScope[1] ?? "";
|
|
23
|
+
const version = bareScope[3];
|
|
24
|
+
if (scope === "")
|
|
25
|
+
throw usage(input);
|
|
26
|
+
return version === undefined
|
|
27
|
+
? `@${scope}/create`
|
|
28
|
+
: `@${scope}/create@${version}`;
|
|
29
|
+
}
|
|
30
|
+
let parsed;
|
|
31
|
+
try {
|
|
32
|
+
parsed = npa(input);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
throw usage(input, error);
|
|
36
|
+
}
|
|
37
|
+
if (typeof parsed.name !== "string" ||
|
|
38
|
+
parsed.name === "" ||
|
|
39
|
+
typeof parsed.rawSpec !== "string" ||
|
|
40
|
+
parsed.registry !== true) {
|
|
41
|
+
throw usage(input);
|
|
42
|
+
}
|
|
43
|
+
const unscoped = parsed.name.replace(/^@[^/]+\//, "");
|
|
44
|
+
const packageName = unscoped.startsWith("create-")
|
|
45
|
+
? parsed.name
|
|
46
|
+
: parsed.name.replace(/^(@[^/]+\/)?/, "$1create-");
|
|
47
|
+
return `${packageName}@${parsed.rawSpec}`;
|
|
48
|
+
}
|
|
49
|
+
function usage(input, cause) {
|
|
50
|
+
return new ZyrkError("ZYRK_USAGE", `unrecognized initializer: ${input}.`, {
|
|
51
|
+
detail: { input },
|
|
52
|
+
remedy: "use a registry package name with an optional @version, @range, or @tag",
|
|
53
|
+
...(cause === undefined ? {} : { cause }),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
export async function runCreate(options) {
|
|
57
|
+
const { input, ...rest } = options;
|
|
58
|
+
return runDlx({ ...rest, spec: starterSpecFor(input) });
|
|
59
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Temporary tool environments: `zyrk dlx <spec> [args...]` (SPEC 22.4).
|
|
3
|
+
*
|
|
4
|
+
* A tool specifier resolves to an exact package identity; its closure is
|
|
5
|
+
* fetched, verified, and realized through the shared store (reused across
|
|
6
|
+
* runs by exact graph identity, never a mutable `latest`); the tool binary
|
|
7
|
+
* runs with inherited stdio in an isolated task directory outside the
|
|
8
|
+
* store; the directory is removed afterwards. The running process is the
|
|
9
|
+
* lease: nothing else can collect the environment mid-run, and cleanup
|
|
10
|
+
* never touches a running tool.
|
|
11
|
+
*
|
|
12
|
+
* Trust policy is honored strictly: dependency lifecycle scripts are denied
|
|
13
|
+
* by default, and no script runner exists yet, so any tool closure
|
|
14
|
+
* declaring install-time scripts fails with ZYRK_SCRIPT_BLOCKED instead of
|
|
15
|
+
* running anything.
|
|
16
|
+
*/
|
|
17
|
+
import type { RegistryClient } from "../fetch/registry.ts";
|
|
18
|
+
import type { Logger } from "../log.ts";
|
|
19
|
+
import type { Store } from "../store/layout.ts";
|
|
20
|
+
export type DlxOptions = {
|
|
21
|
+
store: Store;
|
|
22
|
+
registry: RegistryClient;
|
|
23
|
+
/** Tool specifier: name[@version|range|tag], possibly an npm alias. */
|
|
24
|
+
spec: string;
|
|
25
|
+
/** Arguments forwarded to the tool binary. */
|
|
26
|
+
args?: readonly string[];
|
|
27
|
+
/** Working directory for the tool (default: current directory). */
|
|
28
|
+
cwd?: string;
|
|
29
|
+
env?: NodeJS.ProcessEnv;
|
|
30
|
+
/** Explicit bin name; otherwise auto-selected (single or package-named). */
|
|
31
|
+
binName?: string;
|
|
32
|
+
/** Capture stdio instead of inheriting it (used by tests). */
|
|
33
|
+
capture?: boolean;
|
|
34
|
+
logger?: Logger;
|
|
35
|
+
};
|
|
36
|
+
export type DlxResult = {
|
|
37
|
+
exitCode: number;
|
|
38
|
+
key: string;
|
|
39
|
+
name: string;
|
|
40
|
+
version: string;
|
|
41
|
+
/** True when every needed realization already existed. */
|
|
42
|
+
reused: boolean;
|
|
43
|
+
stdout: string;
|
|
44
|
+
stderr: string;
|
|
45
|
+
/** Removed temp environment path (assertable, always deleted). */
|
|
46
|
+
envDir: string;
|
|
47
|
+
};
|
|
48
|
+
/** Split a tool specifier into its edge name and version part. */
|
|
49
|
+
export declare function splitDlxSpec(spec: string): {
|
|
50
|
+
name: string;
|
|
51
|
+
versionSpec: string;
|
|
52
|
+
};
|
|
53
|
+
export declare function runDlx(options: DlxOptions): Promise<DlxResult>;
|
|
54
|
+
//# sourceMappingURL=run.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/dlx/run.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AASH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAGxC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAUhD,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,cAAc,CAAC;IACzB,uEAAuE;IACvE,IAAI,EAAE,MAAM,CAAC;IACb,8CAA8C;IAC9C,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,mEAAmE;IACnE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8DAA8D;IAC9D,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,0DAA0D;IAC1D,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,kEAAkE;AAClE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB,CAqBA;AAED,wBAAsB,MAAM,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAuGpE"}
|
package/dist/dlx/run.js
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Temporary tool environments: `zyrk dlx <spec> [args...]` (SPEC 22.4).
|
|
3
|
+
*
|
|
4
|
+
* A tool specifier resolves to an exact package identity; its closure is
|
|
5
|
+
* fetched, verified, and realized through the shared store (reused across
|
|
6
|
+
* runs by exact graph identity, never a mutable `latest`); the tool binary
|
|
7
|
+
* runs with inherited stdio in an isolated task directory outside the
|
|
8
|
+
* store; the directory is removed afterwards. The running process is the
|
|
9
|
+
* lease: nothing else can collect the environment mid-run, and cleanup
|
|
10
|
+
* never touches a running tool.
|
|
11
|
+
*
|
|
12
|
+
* Trust policy is honored strictly: dependency lifecycle scripts are denied
|
|
13
|
+
* by default, and no script runner exists yet, so any tool closure
|
|
14
|
+
* declaring install-time scripts fails with ZYRK_SCRIPT_BLOCKED instead of
|
|
15
|
+
* running anything.
|
|
16
|
+
*/
|
|
17
|
+
import { spawn } from "node:child_process";
|
|
18
|
+
import { lstat, mkdtemp, readFile } from "node:fs/promises";
|
|
19
|
+
import npa from "npm-package-arg";
|
|
20
|
+
import * as os from "node:os";
|
|
21
|
+
import * as path from "node:path";
|
|
22
|
+
import { ZyrkError } from "../errors.js";
|
|
23
|
+
import { fetchSources } from "../fetch/all.js";
|
|
24
|
+
import { installProjection } from "../project/projection.js";
|
|
25
|
+
import { createGraphDoc } from "../store/graph.js";
|
|
26
|
+
import { packageDirIn, realizeGraph } from "../store/realize.js";
|
|
27
|
+
import { resolveRequirements, } from "../resolve/resolver.js";
|
|
28
|
+
import { Transaction } from "../store/txn.js";
|
|
29
|
+
import { rmrfNoFollow } from "../util/fsx.js";
|
|
30
|
+
import { closeCode } from "../util/proc.js";
|
|
31
|
+
/** Split a tool specifier into its edge name and version part. */
|
|
32
|
+
export function splitDlxSpec(spec) {
|
|
33
|
+
const invalid = (cause) => new ZyrkError("ZYRK_RESOLUTION_FAILED", `invalid tool specifier "${spec}"`, {
|
|
34
|
+
detail: { spec },
|
|
35
|
+
remedy: "use a package name with an optional @version, @range, or @tag",
|
|
36
|
+
...(cause === undefined ? {} : { cause }),
|
|
37
|
+
});
|
|
38
|
+
let parsed;
|
|
39
|
+
try {
|
|
40
|
+
parsed = npa(spec);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
throw invalid(error);
|
|
44
|
+
}
|
|
45
|
+
if (typeof parsed.name !== "string" || parsed.name === "")
|
|
46
|
+
throw invalid();
|
|
47
|
+
if (spec === parsed.name)
|
|
48
|
+
return { name: parsed.name, versionSpec: "" };
|
|
49
|
+
if (!spec.startsWith(`${parsed.name}@`))
|
|
50
|
+
throw invalid();
|
|
51
|
+
return { name: parsed.name, versionSpec: spec.slice(parsed.name.length + 1) };
|
|
52
|
+
}
|
|
53
|
+
export async function runDlx(options) {
|
|
54
|
+
const { store, registry } = options;
|
|
55
|
+
const args = options.args ?? [];
|
|
56
|
+
const cwd = options.cwd ?? process.cwd();
|
|
57
|
+
const env = options.env ?? process.env;
|
|
58
|
+
const split = splitDlxSpec(options.spec);
|
|
59
|
+
const resolved = await resolveRequirements({
|
|
60
|
+
provider: registry,
|
|
61
|
+
requirements: [{ name: split.name, spec: split.versionSpec }],
|
|
62
|
+
});
|
|
63
|
+
const root = resolved.roots.find((entry) => entry.name === split.name);
|
|
64
|
+
if (root === undefined)
|
|
65
|
+
throw internal(`resolution lost the root for "${options.spec}"`);
|
|
66
|
+
const byKey = new Map(resolved.nodes.map((node) => [node.key, node]));
|
|
67
|
+
const tool = byKey.get(root.target);
|
|
68
|
+
if (tool === undefined)
|
|
69
|
+
throw internal(`resolution lost node ${root.target}`);
|
|
70
|
+
const txn = await Transaction.begin(store, "dlx");
|
|
71
|
+
try {
|
|
72
|
+
await txn.setState("fetching");
|
|
73
|
+
const fetched = await fetchSources({
|
|
74
|
+
store,
|
|
75
|
+
registry,
|
|
76
|
+
txn,
|
|
77
|
+
requests: resolved.nodes.map((node) => ({
|
|
78
|
+
key: node.key,
|
|
79
|
+
name: node.name,
|
|
80
|
+
version: node.version,
|
|
81
|
+
})),
|
|
82
|
+
...(options.logger === undefined ? {} : { logger: options.logger }),
|
|
83
|
+
});
|
|
84
|
+
for (const node of resolved.nodes) {
|
|
85
|
+
const committed = fetched.get(node.key);
|
|
86
|
+
if (committed === undefined)
|
|
87
|
+
throw internal(`fetch lost ${node.key}`);
|
|
88
|
+
if (committed.sourceId !== node.sourceId) {
|
|
89
|
+
throw new ZyrkError("ZYRK_INTERNAL", `fetch skew for ${node.name}@${node.version}: resolved and committed identities disagree`, {
|
|
90
|
+
detail: { name: node.name, version: node.version },
|
|
91
|
+
remedy: "retry the operation; registries must serve immutable versioned content",
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
await txn.setState("sources-committed");
|
|
96
|
+
const graph = createGraphDoc(resolved.nodes.map((node) => ({
|
|
97
|
+
key: node.key,
|
|
98
|
+
name: node.name,
|
|
99
|
+
version: node.version,
|
|
100
|
+
sourceId: node.sourceId,
|
|
101
|
+
deps: node.edges.map((edge) => ({
|
|
102
|
+
name: edge.name,
|
|
103
|
+
target: edge.target,
|
|
104
|
+
})),
|
|
105
|
+
})));
|
|
106
|
+
const realized = await realizeGraph({
|
|
107
|
+
store,
|
|
108
|
+
graph,
|
|
109
|
+
...(options.logger === undefined ? {} : { logger: options.logger }),
|
|
110
|
+
});
|
|
111
|
+
await txn.setState("realizations-ready");
|
|
112
|
+
const toolRealization = realized.realizationDirByKey.get(tool.key);
|
|
113
|
+
if (toolRealization === undefined)
|
|
114
|
+
throw internal(`missing realization for ${tool.key}`);
|
|
115
|
+
const toolPkgDir = packageDirIn(toolRealization, tool.name);
|
|
116
|
+
await assertNoInstallScripts(realized.packageDirByKey, byKey);
|
|
117
|
+
const binKey = selectBin(tool, options.binName);
|
|
118
|
+
const binTarget = tool.bin[binKey];
|
|
119
|
+
if (binTarget === undefined)
|
|
120
|
+
throw internal(`selected bin ${binKey} vanished`);
|
|
121
|
+
const script = await resolveBinScript(toolPkgDir, tool, binKey, binTarget);
|
|
122
|
+
const envDir = await mkdtemp(path.join(os.tmpdir(), "zyrk-dlx-"));
|
|
123
|
+
try {
|
|
124
|
+
await installProjection(envDir, [
|
|
125
|
+
{ name: tool.name, linkTarget: toolPkgDir },
|
|
126
|
+
]);
|
|
127
|
+
return {
|
|
128
|
+
...(await runTool(script, args, cwd, env, options.capture === true)),
|
|
129
|
+
key: tool.key,
|
|
130
|
+
name: tool.name,
|
|
131
|
+
version: tool.version,
|
|
132
|
+
reused: realized.created.length === 0,
|
|
133
|
+
envDir,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
await rmrfNoFollow(envDir).catch((error) => {
|
|
138
|
+
options.logger?.warn(`temporary tool environment at ${envDir} was not removed: ${String(error)}`);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
await txn.finish().catch(() => { });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
/** Deny-by-default script policy: any install-time script blocks the run. */
|
|
148
|
+
async function assertNoInstallScripts(packageDirByKey, byKey) {
|
|
149
|
+
for (const [key, pkgDir] of packageDirByKey) {
|
|
150
|
+
const node = byKey.get(key);
|
|
151
|
+
const label = node === undefined ? key : `${node.name}@${node.version}`;
|
|
152
|
+
let raw;
|
|
153
|
+
try {
|
|
154
|
+
raw = JSON.parse(await readFile(path.join(pkgDir, "package.json"), "utf8"));
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
throw new ZyrkError("ZYRK_RESOLUTION_FAILED", `tool closure package ${label} has no readable package.json`, {
|
|
158
|
+
detail: { package: label },
|
|
159
|
+
remedy: "tools must ship a readable package manifest",
|
|
160
|
+
cause: error,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
const scripts = raw.scripts;
|
|
164
|
+
const table = typeof scripts === "object" && scripts !== null && !Array.isArray(scripts)
|
|
165
|
+
? scripts
|
|
166
|
+
: {};
|
|
167
|
+
for (const stage of ["preinstall", "install", "postinstall"]) {
|
|
168
|
+
const script = table[stage];
|
|
169
|
+
if (typeof script === "string" && script.trim() !== "") {
|
|
170
|
+
throw new ZyrkError("ZYRK_SCRIPT_BLOCKED", `tool ${label} declares install-time script "${stage}"`, {
|
|
171
|
+
detail: { package: label, script: stage },
|
|
172
|
+
remedy: "dependency lifecycle scripts are denied by default; remove the scripted dependency or approve it explicitly once script review is available",
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function selectBin(tool, binName) {
|
|
179
|
+
const label = `${tool.name}@${tool.version}`;
|
|
180
|
+
if (binName !== undefined) {
|
|
181
|
+
if (tool.bin[binName] === undefined) {
|
|
182
|
+
throw new ZyrkError("ZYRK_RESOLUTION_FAILED", `tool ${label} has no bin entry "${binName}"`, {
|
|
183
|
+
detail: {
|
|
184
|
+
package: label,
|
|
185
|
+
requested: binName,
|
|
186
|
+
available: Object.keys(tool.bin),
|
|
187
|
+
},
|
|
188
|
+
remedy: "pick one of the available bin names",
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
return binName;
|
|
192
|
+
}
|
|
193
|
+
const names = Object.keys(tool.bin);
|
|
194
|
+
if (names.length === 0) {
|
|
195
|
+
throw new ZyrkError("ZYRK_RESOLUTION_FAILED", `tool ${label} declares no executable bin entry`, {
|
|
196
|
+
detail: { package: label },
|
|
197
|
+
remedy: "tools run through their declared bin entry; pick a package that declares one",
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
const only = names[0];
|
|
201
|
+
if (names.length === 1 && only !== undefined)
|
|
202
|
+
return only;
|
|
203
|
+
const short = tool.name.startsWith("@")
|
|
204
|
+
? (tool.name.split("/")[1] ?? tool.name)
|
|
205
|
+
: tool.name;
|
|
206
|
+
if (tool.bin[short] !== undefined)
|
|
207
|
+
return short;
|
|
208
|
+
throw new ZyrkError("ZYRK_RESOLUTION_FAILED", `tool ${label} declares several bins (${names.join(", ")}); select one explicitly`, {
|
|
209
|
+
detail: { package: label, available: names },
|
|
210
|
+
remedy: "rerun with an explicit bin name",
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
async function resolveBinScript(pkgDir, tool, binKey, target) {
|
|
214
|
+
const label = `${tool.name}@${tool.version}`;
|
|
215
|
+
const script = path.resolve(pkgDir, target);
|
|
216
|
+
const relative = path.relative(pkgDir, script);
|
|
217
|
+
if (relative === "" ||
|
|
218
|
+
relative.startsWith("..") ||
|
|
219
|
+
path.isAbsolute(relative)) {
|
|
220
|
+
throw new ZyrkError("ZYRK_RESOLUTION_FAILED", `tool ${label} bin "${binKey}" escapes the package`, {
|
|
221
|
+
detail: { package: label, bin: binKey },
|
|
222
|
+
remedy: "bin targets must stay inside the package directory",
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
let stat;
|
|
226
|
+
try {
|
|
227
|
+
stat = await lstat(script);
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
throw new ZyrkError("ZYRK_RESOLUTION_FAILED", `tool ${label} bin "${binKey}" is missing from the package`, {
|
|
231
|
+
detail: { package: label, bin: binKey },
|
|
232
|
+
remedy: "the registry archive does not match its manifest; report the package",
|
|
233
|
+
cause: error,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
if (!stat.isFile() && !stat.isSymbolicLink()) {
|
|
237
|
+
throw new ZyrkError("ZYRK_RESOLUTION_FAILED", `tool ${label} bin "${binKey}" is not a file`, {
|
|
238
|
+
detail: { package: label, bin: binKey },
|
|
239
|
+
remedy: "bin entries must point at executable files",
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
return script;
|
|
243
|
+
}
|
|
244
|
+
async function runTool(script, args, cwd, env, capture) {
|
|
245
|
+
return new Promise((resolve, reject) => {
|
|
246
|
+
const child = spawn(process.execPath, [script, ...args], {
|
|
247
|
+
cwd,
|
|
248
|
+
env,
|
|
249
|
+
stdio: capture ? "pipe" : "inherit",
|
|
250
|
+
});
|
|
251
|
+
let stdout = "";
|
|
252
|
+
let stderr = "";
|
|
253
|
+
if (capture) {
|
|
254
|
+
child.stdout?.on("data", (chunk) => {
|
|
255
|
+
stdout += chunk.toString();
|
|
256
|
+
});
|
|
257
|
+
child.stderr?.on("data", (chunk) => {
|
|
258
|
+
stderr += chunk.toString();
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
child.on("error", (error) => {
|
|
262
|
+
reject(new ZyrkError("ZYRK_INTERNAL", "failed to launch the tool process", {
|
|
263
|
+
cause: error,
|
|
264
|
+
remedy: "verify the Node.js executable is usable",
|
|
265
|
+
}));
|
|
266
|
+
});
|
|
267
|
+
child.on("close", (code, signal) => {
|
|
268
|
+
resolve({ exitCode: closeCode(code, signal), stdout, stderr });
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
function internal(message) {
|
|
273
|
+
return new ZyrkError("ZYRK_INTERNAL", message);
|
|
274
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stable error codes and the one error type the CLI turns into output.
|
|
3
|
+
*
|
|
4
|
+
* The table in SPEC 29.1 is the contract. Codes beginning with ZYRK_ that are
|
|
5
|
+
* not listed there are internal extensions; they follow the same rules:
|
|
6
|
+
* name the failed action, name the subject, and carry a remedy.
|
|
7
|
+
*/
|
|
8
|
+
export declare const SPEC_ERROR_CODES: readonly ["ZYRK_MANIFEST_INVALID", "ZYRK_LOCK_MISMATCH", "ZYRK_RESOLUTION_FAILED", "ZYRK_PEER_CONFLICT", "ZYRK_INTEGRITY_FAILED", "ZYRK_SIGNATURE_INVALID", "ZYRK_EXTRACTION_UNSAFE", "ZYRK_SCRIPT_BLOCKED", "ZYRK_BUILD_FAILED", "ZYRK_WORKSPACE_BUILD_CONTEXT_UNSUPPORTED", "ZYRK_OFFLINE_MISSING", "ZYRK_LINK_UNSUPPORTED", "ZYRK_STORE_CORRUPT", "ZYRK_PROJECT_LOCKED", "ZYRK_DISK_FULL", "ZYRK_DEPLOY_ESCAPE", "ZYRK_DEPLOY_NONRELOCATABLE", "ZYRK_PLATFORM_UNSUPPORTED", "ZYRK_REGISTRY_AUTH", "ZYRK_POLICY_DENIED",
|
|
9
|
+
/** Global installation requested through zyrk; added by SPEC v1.2 §29.1. */
|
|
10
|
+
"ZYRK_GLOBAL_INSTALL_UNSUPPORTED"];
|
|
11
|
+
export type SpecErrorCode = (typeof SPEC_ERROR_CODES)[number];
|
|
12
|
+
/** Internal extensions. Keep this list short and justified. */
|
|
13
|
+
export declare const INTERNAL_ERROR_CODES: readonly [
|
|
14
|
+
/** Bad invocation: unknown command, contradictory flags, missing argument. */
|
|
15
|
+
"ZYRK_USAGE",
|
|
16
|
+
/** A documented command or mode that has not been implemented yet. */
|
|
17
|
+
"ZYRK_NOT_IMPLEMENTED",
|
|
18
|
+
/** npm executable missing for an explicitly delegated command (D-3). */
|
|
19
|
+
"ZYRK_NPM_UNAVAILABLE",
|
|
20
|
+
/** Store root fails ownership/permission safety checks. */
|
|
21
|
+
"ZYRK_STORE_UNSAFE",
|
|
22
|
+
/** Store format marker missing or newer/older than this CLI understands. */
|
|
23
|
+
"ZYRK_STORE_FORMAT_UNSUPPORTED",
|
|
24
|
+
/**
|
|
25
|
+
* A fetched artifact was refused before use: download exceeded the
|
|
26
|
+
* configured size bound, or a bounded retry budget was exhausted on a
|
|
27
|
+
* refusal-worthy response. Distinct from ZYRK_INTEGRITY_FAILED: nothing
|
|
28
|
+
* was verified or accepted.
|
|
29
|
+
*/
|
|
30
|
+
"ZYRK_FETCH_REFUSED",
|
|
31
|
+
/**
|
|
32
|
+
* Transport failure reaching the registry (DNS, TLS, connect, timeout,
|
|
33
|
+
* HTTP 5xx) after bounded retries. Retryable later; not an integrity or
|
|
34
|
+
* resolution verdict.
|
|
35
|
+
*/
|
|
36
|
+
"ZYRK_FETCH_FAILED",
|
|
37
|
+
/** Unexpected internal failure; always a bug. */
|
|
38
|
+
"ZYRK_INTERNAL"];
|
|
39
|
+
export type InternalErrorCode = (typeof INTERNAL_ERROR_CODES)[number];
|
|
40
|
+
export type ErrorCode = SpecErrorCode | InternalErrorCode;
|
|
41
|
+
export type ZyrkErrorOptions = {
|
|
42
|
+
/** Structured facts about the failure. Never include credentials. */
|
|
43
|
+
detail?: Record<string, unknown>;
|
|
44
|
+
/** One actionable line: what the user can do next. */
|
|
45
|
+
remedy?: string;
|
|
46
|
+
cause?: unknown;
|
|
47
|
+
};
|
|
48
|
+
export declare class ZyrkError extends Error {
|
|
49
|
+
readonly code: ErrorCode;
|
|
50
|
+
readonly detail: Record<string, unknown> | undefined;
|
|
51
|
+
readonly remedy: string | undefined;
|
|
52
|
+
constructor(code: ErrorCode, message: string, options?: ZyrkErrorOptions);
|
|
53
|
+
}
|
|
54
|
+
export declare function isZyrkError(error: unknown): error is ZyrkError;
|
|
55
|
+
/**
|
|
56
|
+
* Wrap an unknown thrown value. Node system errors get their code preserved
|
|
57
|
+
* in `detail.errno` so disk-full and permission failures stay diagnosable
|
|
58
|
+
* without dumping the environment.
|
|
59
|
+
*/
|
|
60
|
+
export declare function toZyrkError(error: unknown, fallbackMessage: string): ZyrkError;
|
|
61
|
+
/**
|
|
62
|
+
* Process exit status. SPEC 15.4 requires nonzero for integrity failure,
|
|
63
|
+
* unresolved required dependencies, blocked required builds, and failed
|
|
64
|
+
* required builds; 1 covers all of those. Usage errors use 2 so scripts can
|
|
65
|
+
* tell "you called it wrong" from "the operation failed".
|
|
66
|
+
*/
|
|
67
|
+
export declare function exitCodeFor(error: ZyrkError): number;
|
|
68
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,eAAO,MAAM,gBAAgB,YAC3B,uBAAuB,EACvB,oBAAoB,EACpB,wBAAwB,EACxB,oBAAoB,EACpB,uBAAuB,EACvB,wBAAwB,EACxB,wBAAwB,EACxB,qBAAqB,EACrB,mBAAmB,EACnB,0CAA0C,EAC1C,sBAAsB,EACtB,uBAAuB,EACvB,oBAAoB,EACpB,qBAAqB,EACrB,gBAAgB,EAChB,oBAAoB,EACpB,4BAA4B,EAC5B,2BAA2B,EAC3B,oBAAoB,EACpB,oBAAoB;AACpB,4EAA4E;AAC5E,iCAAiC,CACzB,CAAC;AAEX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE9D,+DAA+D;AAC/D,eAAO,MAAM,oBAAoB;AAC/B,8EAA8E;AAC9E,YAAY;AACZ,sEAAsE;AACtE,sBAAsB;AACtB,wEAAwE;AACxE,sBAAsB;AACtB,2DAA2D;AAC3D,mBAAmB;AACnB,4EAA4E;AAC5E,+BAA+B;AAC/B;;;;;GAKG;AACH,oBAAoB;AACpB;;;;GAIG;AACH,mBAAmB;AACnB,iDAAiD;AACjD,eAAe,CACP,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtE,MAAM,MAAM,SAAS,GAAG,aAAa,GAAG,iBAAiB,CAAC;AAE1D,MAAM,MAAM,gBAAgB,GAAG;IAC7B,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,qBAAa,SAAU,SAAQ,KAAK;IAClC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IACrD,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAEpC,YACE,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,gBAAqB,EAU/B;CACF;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,SAAS,CAE9D;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,OAAO,EACd,eAAe,EAAE,MAAM,GACtB,SAAS,CAsBX;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAEpD"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stable error codes and the one error type the CLI turns into output.
|
|
3
|
+
*
|
|
4
|
+
* The table in SPEC 29.1 is the contract. Codes beginning with ZYRK_ that are
|
|
5
|
+
* not listed there are internal extensions; they follow the same rules:
|
|
6
|
+
* name the failed action, name the subject, and carry a remedy.
|
|
7
|
+
*/
|
|
8
|
+
export const SPEC_ERROR_CODES = [
|
|
9
|
+
"ZYRK_MANIFEST_INVALID",
|
|
10
|
+
"ZYRK_LOCK_MISMATCH",
|
|
11
|
+
"ZYRK_RESOLUTION_FAILED",
|
|
12
|
+
"ZYRK_PEER_CONFLICT",
|
|
13
|
+
"ZYRK_INTEGRITY_FAILED",
|
|
14
|
+
"ZYRK_SIGNATURE_INVALID",
|
|
15
|
+
"ZYRK_EXTRACTION_UNSAFE",
|
|
16
|
+
"ZYRK_SCRIPT_BLOCKED",
|
|
17
|
+
"ZYRK_BUILD_FAILED",
|
|
18
|
+
"ZYRK_WORKSPACE_BUILD_CONTEXT_UNSUPPORTED",
|
|
19
|
+
"ZYRK_OFFLINE_MISSING",
|
|
20
|
+
"ZYRK_LINK_UNSUPPORTED",
|
|
21
|
+
"ZYRK_STORE_CORRUPT",
|
|
22
|
+
"ZYRK_PROJECT_LOCKED",
|
|
23
|
+
"ZYRK_DISK_FULL",
|
|
24
|
+
"ZYRK_DEPLOY_ESCAPE",
|
|
25
|
+
"ZYRK_DEPLOY_NONRELOCATABLE",
|
|
26
|
+
"ZYRK_PLATFORM_UNSUPPORTED",
|
|
27
|
+
"ZYRK_REGISTRY_AUTH",
|
|
28
|
+
"ZYRK_POLICY_DENIED",
|
|
29
|
+
/** Global installation requested through zyrk; added by SPEC v1.2 §29.1. */
|
|
30
|
+
"ZYRK_GLOBAL_INSTALL_UNSUPPORTED",
|
|
31
|
+
];
|
|
32
|
+
/** Internal extensions. Keep this list short and justified. */
|
|
33
|
+
export const INTERNAL_ERROR_CODES = [
|
|
34
|
+
/** Bad invocation: unknown command, contradictory flags, missing argument. */
|
|
35
|
+
"ZYRK_USAGE",
|
|
36
|
+
/** A documented command or mode that has not been implemented yet. */
|
|
37
|
+
"ZYRK_NOT_IMPLEMENTED",
|
|
38
|
+
/** npm executable missing for an explicitly delegated command (D-3). */
|
|
39
|
+
"ZYRK_NPM_UNAVAILABLE",
|
|
40
|
+
/** Store root fails ownership/permission safety checks. */
|
|
41
|
+
"ZYRK_STORE_UNSAFE",
|
|
42
|
+
/** Store format marker missing or newer/older than this CLI understands. */
|
|
43
|
+
"ZYRK_STORE_FORMAT_UNSUPPORTED",
|
|
44
|
+
/**
|
|
45
|
+
* A fetched artifact was refused before use: download exceeded the
|
|
46
|
+
* configured size bound, or a bounded retry budget was exhausted on a
|
|
47
|
+
* refusal-worthy response. Distinct from ZYRK_INTEGRITY_FAILED: nothing
|
|
48
|
+
* was verified or accepted.
|
|
49
|
+
*/
|
|
50
|
+
"ZYRK_FETCH_REFUSED",
|
|
51
|
+
/**
|
|
52
|
+
* Transport failure reaching the registry (DNS, TLS, connect, timeout,
|
|
53
|
+
* HTTP 5xx) after bounded retries. Retryable later; not an integrity or
|
|
54
|
+
* resolution verdict.
|
|
55
|
+
*/
|
|
56
|
+
"ZYRK_FETCH_FAILED",
|
|
57
|
+
/** Unexpected internal failure; always a bug. */
|
|
58
|
+
"ZYRK_INTERNAL",
|
|
59
|
+
];
|
|
60
|
+
export class ZyrkError extends Error {
|
|
61
|
+
code;
|
|
62
|
+
detail;
|
|
63
|
+
remedy;
|
|
64
|
+
constructor(code, message, options = {}) {
|
|
65
|
+
super(message, options.cause === undefined ? undefined : { cause: options.cause });
|
|
66
|
+
this.name = "ZyrkError";
|
|
67
|
+
this.code = code;
|
|
68
|
+
this.detail = options.detail;
|
|
69
|
+
this.remedy = options.remedy;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export function isZyrkError(error) {
|
|
73
|
+
return error instanceof ZyrkError;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Wrap an unknown thrown value. Node system errors get their code preserved
|
|
77
|
+
* in `detail.errno` so disk-full and permission failures stay diagnosable
|
|
78
|
+
* without dumping the environment.
|
|
79
|
+
*/
|
|
80
|
+
export function toZyrkError(error, fallbackMessage) {
|
|
81
|
+
if (isZyrkError(error))
|
|
82
|
+
return error;
|
|
83
|
+
const detail = {};
|
|
84
|
+
let message = fallbackMessage;
|
|
85
|
+
if (error instanceof Error) {
|
|
86
|
+
message = error.message === "" ? fallbackMessage : error.message;
|
|
87
|
+
const errno = error.code;
|
|
88
|
+
if (typeof errno === "string") {
|
|
89
|
+
detail.errno = errno;
|
|
90
|
+
if (errno === "ENOSPC") {
|
|
91
|
+
return new ZyrkError("ZYRK_DISK_FULL", message, {
|
|
92
|
+
detail,
|
|
93
|
+
cause: error,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
if (errno === "EACCES" || errno === "EPERM") {
|
|
97
|
+
detail.errno = errno;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return new ZyrkError("ZYRK_INTERNAL", message, { detail, cause: error });
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Process exit status. SPEC 15.4 requires nonzero for integrity failure,
|
|
105
|
+
* unresolved required dependencies, blocked required builds, and failed
|
|
106
|
+
* required builds; 1 covers all of those. Usage errors use 2 so scripts can
|
|
107
|
+
* tell "you called it wrong" from "the operation failed".
|
|
108
|
+
*/
|
|
109
|
+
export function exitCodeFor(error) {
|
|
110
|
+
return error.code === "ZYRK_USAGE" ? 2 : 1;
|
|
111
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { type ModeClass } from "../util/hash.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Safe archive extraction (SPEC 13.4).
|
|
4
|
+
*
|
|
5
|
+
* Two passes over the archive: the first validates every entry (paths,
|
|
6
|
+
* collisions, limits, supported types) and decides how a single common root
|
|
7
|
+
* directory, if any, is stripped; the second writes entries under the
|
|
8
|
+
* transaction's staging directory. The validation pass writes nothing.
|
|
9
|
+
*
|
|
10
|
+
* Rules enforced here:
|
|
11
|
+
* - absolute paths, drive letters, backslashes, NUL bytes, and `..`
|
|
12
|
+
* traversal are rejected;
|
|
13
|
+
* - case-folding collisions are rejected; Windows-reserved names and
|
|
14
|
+
* invalid characters are detected and reported (deploy targets re-check);
|
|
15
|
+
* - only regular files, directories, symlinks, and validated hard links are
|
|
16
|
+
* accepted;
|
|
17
|
+
* - symlink and hard-link targets must stay inside the package root;
|
|
18
|
+
* - setuid/setgid/sticky bits and archive ownership are never applied;
|
|
19
|
+
* - files are created exclusively (`wx`) and never written through a
|
|
20
|
+
* previously created symlink;
|
|
21
|
+
* - entry count, total bytes, and path lengths are bounded.
|
|
22
|
+
*/
|
|
23
|
+
export type ExtractionLimits = {
|
|
24
|
+
maxEntries: number;
|
|
25
|
+
maxTotalBytes: number;
|
|
26
|
+
maxPathLength: number;
|
|
27
|
+
maxComponentLength: number;
|
|
28
|
+
maxSymlinkTargetLength: number;
|
|
29
|
+
};
|
|
30
|
+
export declare const DEFAULT_EXTRACTION_LIMITS: ExtractionLimits;
|
|
31
|
+
export type ExtractedFileEntry = {
|
|
32
|
+
path: string;
|
|
33
|
+
type: "file";
|
|
34
|
+
mode: ModeClass;
|
|
35
|
+
size: number;
|
|
36
|
+
innerDigest: string;
|
|
37
|
+
/** Absolute path inside the staging tree, for the commit step. */
|
|
38
|
+
stagingPath: string;
|
|
39
|
+
};
|
|
40
|
+
export type ExtractedDirEntry = {
|
|
41
|
+
path: string;
|
|
42
|
+
type: "dir";
|
|
43
|
+
};
|
|
44
|
+
export type ExtractedSymlinkEntry = {
|
|
45
|
+
path: string;
|
|
46
|
+
type: "symlink";
|
|
47
|
+
target: string;
|
|
48
|
+
};
|
|
49
|
+
export type ExtractedEntry = ExtractedFileEntry | ExtractedDirEntry | ExtractedSymlinkEntry;
|
|
50
|
+
export type ExtractionResult = {
|
|
51
|
+
entries: ExtractedEntry[];
|
|
52
|
+
totals: {
|
|
53
|
+
files: number;
|
|
54
|
+
dirs: number;
|
|
55
|
+
symlinks: number;
|
|
56
|
+
logicalBytes: number;
|
|
57
|
+
};
|
|
58
|
+
/** Deterministic, sorted outcome codes; stored in the source manifest. */
|
|
59
|
+
outcomes: string[];
|
|
60
|
+
/** The stripped common root directory, if one was found. */
|
|
61
|
+
commonRoot: string | null;
|
|
62
|
+
/** Non-fatal observations for CLI warnings. */
|
|
63
|
+
observations: string[];
|
|
64
|
+
};
|
|
65
|
+
export declare function extractTarball(options: {
|
|
66
|
+
tarballPath: string;
|
|
67
|
+
destDir: string;
|
|
68
|
+
limits?: Partial<ExtractionLimits>;
|
|
69
|
+
}): Promise<ExtractionResult>;
|
|
70
|
+
export type PathCheck = {
|
|
71
|
+
normalized: string;
|
|
72
|
+
reserved: boolean;
|
|
73
|
+
invalidWindows: boolean;
|
|
74
|
+
};
|
|
75
|
+
/** Normalize and validate an archive path; anything that can escape fails. */
|
|
76
|
+
export declare function normalizeArchivePath(rawInput: string, limits: ExtractionLimits): PathCheck;
|
|
77
|
+
export declare function chaseSymlinkChain(linkPath: string, target: string, symlinks: Map<string, string>): {
|
|
78
|
+
finalPath: string;
|
|
79
|
+
escaped: boolean;
|
|
80
|
+
cyclic: boolean;
|
|
81
|
+
};
|
|
82
|
+
//# sourceMappingURL=tarball.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tarball.d.ts","sourceRoot":"","sources":["../../src/extract/tarball.ts"],"names":[],"mappings":"AAaA,OAAO,EAAuB,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAGtE;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sBAAsB,EAAE,MAAM,CAAC;CAChC,CAAC;AAEF,eAAO,MAAM,yBAAyB,EAAE,gBAMvC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,kEAAkE;IAClE,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC;AAE9D,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,cAAc,GACxB,kBAAkB,GAAG,iBAAiB,GAAG,qBAAqB,CAAC;AAEjE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,MAAM,EAAE;QACN,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,0EAA0E;IAC1E,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,4DAA4D;IAC5D,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,+CAA+C;IAC/C,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB,CAAC;AAeF,wBAAsB,cAAc,CAAC,OAAO,EAAE;IAC5C,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;CACpC,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAyW5B;AAMD,MAAM,MAAM,SAAS,GAAG;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,8EAA8E;AAC9E,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,gBAAgB,GACvB,SAAS,CAkDX;AAmND,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC5B;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CA2B1D"}
|