zyrk 0.4.9 → 0.5.1

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.
Files changed (41) hide show
  1. package/README.md +11 -7
  2. package/VERSION +1 -1
  3. package/dist/commands/deploy.d.ts.map +1 -1
  4. package/dist/commands/deploy.js +347 -18
  5. package/dist/commands/maintenance.d.ts.map +1 -1
  6. package/dist/commands/maintenance.js +22 -2
  7. package/dist/commands/native-export.d.ts +2 -0
  8. package/dist/commands/native-export.d.ts.map +1 -0
  9. package/dist/commands/native-export.js +192 -0
  10. package/dist/fetch/package.d.ts.map +1 -1
  11. package/dist/fetch/package.js +2 -1
  12. package/dist/fetch/registry.d.ts +5 -0
  13. package/dist/fetch/registry.d.ts.map +1 -1
  14. package/dist/fetch/registry.js +84 -10
  15. package/dist/install/import-npm-workspace.d.ts +1 -1
  16. package/dist/install/import-npm-workspace.d.ts.map +1 -1
  17. package/dist/install/import-npm-workspace.js +1 -1
  18. package/dist/install/import-npm.d.ts +1 -1
  19. package/dist/install/import-npm.d.ts.map +1 -1
  20. package/dist/install/import-npm.js +3 -2
  21. package/dist/install/registry-policy.d.ts +9 -0
  22. package/dist/install/registry-policy.d.ts.map +1 -0
  23. package/dist/install/registry-policy.js +30 -0
  24. package/dist/install/registry.d.ts.map +1 -1
  25. package/dist/install/registry.js +18 -13
  26. package/dist/install/run.d.ts.map +1 -1
  27. package/dist/install/run.js +11 -3
  28. package/dist/install/workspace-graph.d.ts.map +1 -1
  29. package/dist/install/workspace-graph.js +4 -3
  30. package/dist/install/workspace-install.d.ts.map +1 -1
  31. package/dist/install/workspace-install.js +12 -3
  32. package/dist/install/workspace-lock.d.ts +1 -1
  33. package/dist/install/workspace-lock.d.ts.map +1 -1
  34. package/dist/install/workspace-lock.js +5 -1
  35. package/dist/lockfile/fingerprints.d.ts +1 -0
  36. package/dist/lockfile/fingerprints.d.ts.map +1 -1
  37. package/dist/lockfile/fingerprints.js +3 -0
  38. package/dist/resolve/resolver.d.ts +1 -0
  39. package/dist/resolve/resolver.d.ts.map +1 -1
  40. package/dist/resolve/resolver.js +2 -2
  41. package/package.json +1 -1
package/README.md CHANGED
@@ -116,8 +116,10 @@ Default registry and authentication settings are loaded from user/project
116
116
  token placeholders are supported. Credentials are restricted to their configured
117
117
  scheme, host, port, and path; redirects cannot expand that scope. Use
118
118
  `--registry=https://your-registry.example/` on install for an explicit registry.
119
- Mixed scoped routing and disabled TLS verification are rejected before public
120
- lookup. Git sources, non-workspace local sources, and overrides remain unsupported;
119
+ Scoped registries route metadata, exact sources, imports and tool commands by
120
+ the real package name, including aliases. Advisory requests are partitioned by
121
+ registry; private names never fall back to a public endpoint. Disabled TLS
122
+ verification is rejected before lookup. Git sources, non-workspace local sources, and overrides remain unsupported;
121
123
  they fail explicitly. Optional dependencies incompatible with the selected platform
122
124
  are omitted; missing packages, integrity failures, and other optional failures
123
125
  remain errors. Platform selection is recorded in the lock. macOS, Windows, and
@@ -144,8 +146,7 @@ and repair have workspace fixtures. Builds that reach live workspace sources are
144
146
  blocked before execution. Existing npm v2/v3 workspace `package-lock.json` selections can be adopted with
145
147
  `zyrk import --from npm`, preserving exact versions, aliases and live members.
146
148
  Adoption validates the complete graph before project changes; npm shrinkwrap
147
- precedence and unsupported bundled/external links fail explicitly. Portable
148
- workspace export still needs its adapter. Recursive execution supports
149
+ precedence and unsupported bundled/external links fail explicitly. Workspace-root production exports include live member closures and assets. Recursive execution supports
149
150
  member scripts; filters are exact names or paths, not arbitrary glob expressions.
150
151
 
151
152
  ### Run a one-off tool
@@ -233,9 +234,12 @@ node ./dist-runtime/dist/server.js
233
234
 
234
235
  The exporter validates that links stay inside the output and excludes the host
235
236
  store. Generic export supports shared production graphs and relocatable private
236
- JavaScript outputs, including selected patches and generated files. Native binaries,
237
- host-path-dependent outputs, workspace export, and arbitrary shell startup adapters
238
- remain unsupported. Exports validate active generation/profile metadata and reject
237
+ JavaScript outputs, including selected patches and generated files. Workspace-root exports include live production members, peer contexts, cycles
238
+ and assets. A restricted Linux x64 ELF64 N-API copy recipe supports dependency-free
239
+ addons using `napi_create_int32` and `napi_set_named_property` with matching
240
+ Node/ABI/build profiles. Native bytes are copied unchanged after loader validation.
241
+ Other native formats/API imports, external libraries, loader paths, host-path-dependent
242
+ outputs and arbitrary shell startup adapters remain unsupported. Exports validate active generation/profile metadata and reject
239
243
  credentials or escaping links. Structural validation does not prove application health. Run an
240
244
  application smoke test from the exported directory with host-store access removed.
241
245
 
package/VERSION CHANGED
@@ -1 +1 @@
1
- v0.4.9
1
+ v0.5.1
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AAyDA,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC7B,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,MAAM,EAAE;QAAE,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;IAC1C,MAAM,EAAE;QAAE,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;IAC1C,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AAwEF,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,MAAM,CAAC,CA8YjB"}
1
+ {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AAoEA,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC7B,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,MAAM,EAAE;QAAE,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;IAC1C,MAAM,EAAE;QAAE,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;IAC1C,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AAwEF,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,MAAM,CAAC,CAqZjB"}
@@ -1,7 +1,12 @@
1
- /** Self-contained pure-package deployment and explicit local Docker commands. */
1
+ import { installedRegistryConfig } from "../install/registry-policy.js";
2
+ /** Self-contained runtime deployment and explicit local Docker commands. */
2
3
  import { constants } from "node:fs";
3
4
  import { open, copyFile, chmod, lstat, mkdir, mkdtemp, readFile, readdir, readlink, realpath, rename, rm, symlink, writeFile, } from "node:fs/promises";
4
5
  import * as path from "node:path";
6
+ import { discoverWorkspaces, findWorkspaceRoot, } from "../install/workspaces.js";
7
+ import { readWorkspaceLock } from "../install/workspace-lock.js";
8
+ import { validateNativeExport } from "./native-export.js";
9
+ import { workspaceRegistryOccurrences, workspaceContextId, } from "../install/workspace-graph.js";
5
10
  import { findProjectBoundary, resolveStoreRoot } from "../config/paths.js";
6
11
  import { ZyrkError } from "../errors.js";
7
12
  import { lockedGraph } from "../install/lock.js";
@@ -81,7 +86,8 @@ function output(options, result, human) {
81
86
  : human + "\n");
82
87
  }
83
88
  export async function runDeployCommand(options) {
84
- const boundary = await findProjectBoundary(options.cwd);
89
+ const boundary = (await findWorkspaceRoot(options.cwd)) ??
90
+ (await findProjectBoundary(options.cwd));
85
91
  if (!boundary)
86
92
  throw error("ZYRK_MANIFEST_INVALID", "Cannot find a project to deploy.");
87
93
  const project = await realpath(boundary);
@@ -125,7 +131,7 @@ export async function runDeployCommand(options) {
125
131
  const rawLock = await readJson(path.join(project, "package-lock.json"));
126
132
  if (manifest.workspaces !== undefined ||
127
133
  (object(rawLock.zyrk) && rawLock.zyrk.version === 2))
128
- throw error("ZYRK_DEPLOY_NONRELOCATABLE", "Workspace runtime exports require a validated live-importer deployment adapter.");
134
+ return await exportWorkspace(options, project, manifest, state, out, parent, files, entry);
129
135
  const doc = validateLockDoc(rawLock, "project package-lock.json");
130
136
  const resolved = lockedGraph(doc);
131
137
  const privateGraph = state.linker === "private";
@@ -136,16 +142,14 @@ export async function runDeployCommand(options) {
136
142
  manifestFingerprint(manifest))
137
143
  throw error("ZYRK_LOCK_MISMATCH", "Deployment manifest, lock, and installed generation disagree.");
138
144
  if (resolved.nodes.length) {
139
- const origins = new Set(resolved.nodes.map((node) => node.origin));
140
- if (origins.size !== 1)
141
- throw error("ZYRK_DEPLOY_NONRELOCATABLE", "Mixed-origin deployment requires a validated registry adapter.");
145
+ const registryConfig = installedRegistryConfig(state, resolved.nodes);
142
146
  const patches = await loadProjectPatches(project);
143
147
  const policy = object(manifest.zyrk) ? manifest.zyrk : {};
144
148
  assertFrozenCompatible(doc, {
145
149
  manifest: manifestFingerprint(manifest),
146
150
  scripts: scriptPolicyFingerprint(policy.scripts),
147
151
  config: configFingerprint({
148
- registry: [...origins][0],
152
+ ...registryConfig,
149
153
  linker: "shared",
150
154
  platform: currentPlatform(),
151
155
  ...(Array.isArray(policy.mutablePackages)
@@ -168,6 +172,8 @@ export async function runDeployCommand(options) {
168
172
  root: resolveStoreRoot({ env: options.env }).root,
169
173
  });
170
174
  store.requireInitialized();
175
+ if (within(store.root, out))
176
+ throw error("ZYRK_DEPLOY_ESCAPE", "Deployment output must be outside the managed store.");
171
177
  const barrier = await acquireLock(path.join(store.paths.locksDir, "liveness.lock"), { name: "portable deployment retention" });
172
178
  try {
173
179
  const installedKeys = new Set(Array.isArray(state.contexts)
@@ -261,9 +267,7 @@ export async function runDeployCommand(options) {
261
267
  await symlink(e.target, target);
262
268
  }
263
269
  else {
264
- if (e.path.endsWith(".node"))
265
- throw error("ZYRK_DEPLOY_NONRELOCATABLE", "Native addon runtime compatibility requires a validated deployment adapter.");
266
- await copyFile(path.join(sourceDir, e.path), target);
270
+ await scanOrCopyPrivateFile(path.join(sourceDir, e.path), target, [project, store.root], privateBudget);
267
271
  await chmod(target, e.mode === "exec" ? 0o755 : 0o644);
268
272
  }
269
273
  }
@@ -311,8 +315,7 @@ export async function runDeployCommand(options) {
311
315
  const entryStat = await lstat(path.join(stage, entry)).catch(() => null);
312
316
  if (!entryStat?.isFile())
313
317
  throw error("ZYRK_DEPLOY_NONRELOCATABLE", "The selected runtime Node entry is missing.");
314
- if (privateGraph)
315
- await scanPrivateExport(stage, [project, store.root], privateBudget);
318
+ await scanPrivateExport(stage, [project, store.root], privateBudget);
316
319
  await validateLinks(stage, stage);
317
320
  await writeFile(path.join(stage, "zyrk-deploy.json"), JSON.stringify({
318
321
  schemaVersion: 1,
@@ -473,7 +476,7 @@ async function docker(options, project, manifest) {
473
476
  buildScript: build ?? null,
474
477
  port: Number(portText),
475
478
  }, null, 2) + "\n",
476
- "ZYRK_DOCKER.md": `Review Dockerfile and .dockerignore before building. Runtime files: ${files.join(", ")}. Runtime entry: node ${entry}. Build script: ${build ?? "none"}. Container port: ${portText}.\n\nThe pinned CLI tarball must remain at its declared path. Dependencies are fetched with an optional BuildKit npmrc secret, then installed, built, and exported without networking or registry secrets. No host node_modules or store enters the runtime image. Adapt build inputs and credential exclusions to your project; generated files do not prove application health. Relocatable JavaScript output from ready private generations is supported, including patches and approved build output. Native outputs, embedded host paths, live workspace exports, and arbitrary shell start commands require a validated adapter and are rejected by this generic exporter.\n\nBuild: zyrk docker build --tag your-app:local\nRun: zyrk docker run --tag your-app:local --port ${portText}\nSupply private registry credentials to Docker BuildKit directly with docker build --secret id=npmrc,src=<credential-file> when required; zyrk never embeds them.\n`,
479
+ "ZYRK_DOCKER.md": `Review Dockerfile and .dockerignore before building. Runtime files: ${files.join(", ")}. Runtime entry: node ${entry}. Build script: ${build ?? "none"}. Container port: ${portText}.\n\nThe pinned CLI tarball must remain at its declared path. Dependencies are fetched with an optional BuildKit npmrc secret, then installed, built, and exported without networking or registry secrets. No host node_modules or store enters the runtime image. Adapt build inputs and credential exclusions to your project; generated files do not prove application health. Relocatable JavaScript output from ready private generations is supported, including patches and approved build output. Live workspace runtime closures and a restricted Linux ELF64 N-API native copy recipe are supported. The native v1 recipe is limited to Linux x64 ELF64 addons importing napi_create_int32 and napi_set_named_property (plus validated weak toolchain hooks), with exact installed Node/ABI/profile checks. It copies bytes unchanged and runs no package code during export. Native addons with external libraries, loader search paths, unknown formats or API imports, embedded absolute paths, and arbitrary shell start commands require another validated adapter and are rejected by this exporter. Workspace member payloads include their live files and assets except node_modules; credential filenames and links outside each source root are rejected.\n\nBuild: zyrk docker build --tag your-app:local\nRun: zyrk docker run --tag your-app:local --port ${portText}\nSupply private registry credentials to Docker BuildKit directly with docker build --secret id=npmrc,src=<credential-file> when required; zyrk never embeds them.\n`,
477
480
  };
478
481
  for (const name of Object.keys(contents))
479
482
  if (await lstat(path.join(project, name)).catch(() => null))
@@ -667,8 +670,23 @@ async function scanOrCopyPrivateFile(source, target, forbidden, budget) {
667
670
  const metadata = await input.stat();
668
671
  if (!metadata.isFile())
669
672
  throw error("ZYRK_DEPLOY_ESCAPE", "Private payload contains a special filesystem entry.");
670
- if (/\.node$/i.test(source))
671
- throw error("ZYRK_DEPLOY_NONRELOCATABLE", "Native dependency outputs require a validated export adapter.");
673
+ let nativeSnapshot;
674
+ if (/\.node$/i.test(source)) {
675
+ if (metadata.size > 64 * 1024 * 1024)
676
+ throw error("ZYRK_DEPLOY_NONRELOCATABLE", "Native addon exceeds the validated export size budget.");
677
+ const bytes = Buffer.alloc(metadata.size);
678
+ let offset = 0;
679
+ while (offset < bytes.length) {
680
+ const read = await input.read(bytes, offset, bytes.length - offset, offset);
681
+ if (!read.bytesRead)
682
+ throw error("ZYRK_DEPLOY_NONRELOCATABLE", "Native addon changed during export validation.");
683
+ offset += read.bytesRead;
684
+ }
685
+ validateNativeExport(bytes);
686
+ // Scan and copy these exact validated bytes. Private source files remain
687
+ // mutable; reopening or rereading them would discard the validation.
688
+ nativeSnapshot = bytes;
689
+ }
672
690
  if (target !== undefined)
673
691
  output = await open(target, "wx", metadata.mode & 0o111 ? 0o755 : 0o644);
674
692
  const needles = [
@@ -684,16 +702,23 @@ async function scanOrCopyPrivateFile(source, target, forbidden, budget) {
684
702
  let fileBytes = 0;
685
703
  const chunk = Buffer.allocUnsafe(1024 * 1024);
686
704
  for (;;) {
687
- const { bytesRead } = await input.read(chunk, 0, chunk.length, null);
705
+ let bytes;
706
+ if (nativeSnapshot) {
707
+ bytes = nativeSnapshot.subarray(fileBytes, fileBytes + chunk.length);
708
+ }
709
+ else {
710
+ const read = await input.read(chunk, 0, chunk.length, null);
711
+ bytes = chunk.subarray(0, read.bytesRead);
712
+ }
713
+ const bytesRead = bytes.length;
688
714
  if (!bytesRead)
689
715
  break;
690
- const bytes = chunk.subarray(0, bytesRead);
691
716
  fileBytes += bytesRead;
692
717
  budget.bytes += bytesRead;
693
718
  checkPrivateBudget(budget);
694
719
  if (fileBytes > 512 * 1024 * 1024)
695
720
  throw error("ZYRK_DEPLOY_NONRELOCATABLE", "A private generated file exceeds the deployment byte budget.");
696
- if (first && nativeMagic(bytes))
721
+ if (first && nativeMagic(bytes) && !/\.node$/i.test(source))
697
722
  throw error("ZYRK_DEPLOY_NONRELOCATABLE", "Native dependency outputs require a validated export adapter.");
698
723
  first = false;
699
724
  const inspect = Buffer.concat([previous, bytes]);
@@ -759,3 +784,307 @@ async function scanPrivateExport(root, forbidden, budget) {
759
784
  await scanOrCopyPrivateFile(file, undefined, forbidden, budget);
760
785
  }
761
786
  }
787
+ /** Export the selected importer closure; live files are copied, never entered into CAS. */
788
+ async function exportWorkspace(options, project, manifest, state, out, parent, files, entry) {
789
+ const workspace = await discoverWorkspaces(project);
790
+ const locked = await readWorkspaceLock(project, workspace);
791
+ if (!locked ||
792
+ locked.doc.zyrk?.manifest !== workspace.manifestFingerprint ||
793
+ state.lockDigest !== sha512Hex(canonicalJson(locked.doc)))
794
+ throw error("ZYRK_LOCK_MISMATCH", "Workspace deployment manifests, lock and installed generation disagree.");
795
+ if (state.readiness !== "ready")
796
+ throw error("ZYRK_SCRIPT_BLOCKED", "Cannot export an unready workspace generation.");
797
+ if (state.platform !== process.platform || state.arch !== process.arch)
798
+ throw error("ZYRK_PLATFORM_UNSUPPORTED", "Workspace export requires the recorded target platform and architecture.");
799
+ const policy = object(manifest.zyrk) ? manifest.zyrk : {};
800
+ if (locked.doc.zyrk.scripts !== scriptPolicyFingerprint(policy.scripts))
801
+ throw error("ZYRK_LOCK_MISMATCH", "Workspace deployment build policy changed after installation.");
802
+ const registryNodesForPolicy = locked.graph.nodes.filter((n) => n.kind === "registry");
803
+ if (registryNodesForPolicy.length || state.registryConfig !== undefined) {
804
+ const registryConfig = installedRegistryConfig(state, registryNodesForPolicy);
805
+ const patches = await loadProjectPatches(project);
806
+ const expected = configFingerprint({
807
+ ...registryConfig,
808
+ linker: "shared",
809
+ platform: currentPlatform(),
810
+ ...(Array.isArray(policy.mutablePackages)
811
+ ? { mutablePackages: policy.mutablePackages }
812
+ : {}),
813
+ ...(patches.length
814
+ ? {
815
+ patches: sha512Hex(canonicalJson(patches.map((p) => ({
816
+ sourceId: p.sourceId,
817
+ digest: p.digest,
818
+ })))),
819
+ }
820
+ : {}),
821
+ });
822
+ if (locked.doc.zyrk.config !== expected)
823
+ throw error("ZYRK_LOCK_MISMATCH", "Workspace deployment configuration or patches changed after installation.");
824
+ }
825
+ const store = await Store.open({
826
+ root: resolveStoreRoot({ env: options.env }).root,
827
+ });
828
+ store.requireInitialized();
829
+ if (within(store.root, out))
830
+ throw error("ZYRK_DEPLOY_ESCAPE", "Deployment output must be outside the managed store.");
831
+ const barrier = await acquireLock(path.join(store.paths.locksDir, "liveness.lock"), { name: "workspace deployment retention" });
832
+ try {
833
+ const graph = locked.graph, byKey = new Map(graph.nodes.map((n) => [n.key, n]));
834
+ const selected = new Set();
835
+ const visit = (key) => {
836
+ if (selected.has(key))
837
+ return;
838
+ const n = byKey.get(key);
839
+ if (!n)
840
+ throw error("ZYRK_OFFLINE_MISSING", "Workspace runtime edge is missing.");
841
+ selected.add(key);
842
+ for (const e of n.edges)
843
+ if (!(n.kind === "local" && e.kind === "dev"))
844
+ visit(e.target);
845
+ };
846
+ const rootKey = graph.importers["."];
847
+ if (!rootKey)
848
+ throw error("ZYRK_LOCK_MISMATCH", "Workspace root importer is missing.");
849
+ visit(rootKey);
850
+ const directories = new Map();
851
+ for (const key of selected) {
852
+ const n = byKey.get(key);
853
+ if (n.kind === "local")
854
+ directories.set(key, workspace.importers.find((i) => i.id === n.importer).directory);
855
+ }
856
+ // Resolve recorded projection edges rather than enumerating arbitrary node_modules entries.
857
+ const queue = [...directories.keys()], checked = new Set();
858
+ while (queue.length) {
859
+ const key = queue.shift();
860
+ if (checked.has(key))
861
+ continue;
862
+ checked.add(key);
863
+ const n = byKey.get(key), directory = directories.get(key);
864
+ for (const e of n.edges) {
865
+ if (!selected.has(e.target) || (n.kind === "local" && e.kind === "dev"))
866
+ continue;
867
+ const slot = n.kind === "local" || e.name === n.name
868
+ ? path.join(directory, "node_modules", e.name)
869
+ : path.join(directory, n.name.startsWith("@") ? "../.." : "..", e.name);
870
+ const actual = await realpath(slot).catch(() => null), meta = await lstat(slot).catch(() => null);
871
+ if (!actual ||
872
+ !meta?.isSymbolicLink() ||
873
+ (directories.has(e.target) && directories.get(e.target) !== actual))
874
+ throw error("ZYRK_DEPLOY_ESCAPE", "Workspace dependency projection disagrees with its recorded context.");
875
+ directories.set(e.target, actual);
876
+ queue.push(e.target);
877
+ }
878
+ }
879
+ for (const [key, source] of directories)
880
+ if (key !== rootKey && (within(source, out) || within(out, source)))
881
+ throw error("ZYRK_DEPLOY_ESCAPE", "Deployment output overlaps a selected workspace runtime source.");
882
+ const registryNodes = workspaceRegistryOccurrences(graph);
883
+ const registryKeys = new Set(registryNodes.map((n) => n.key));
884
+ const privateKeys = new Set();
885
+ for (let changed = true; changed;) {
886
+ changed = false;
887
+ for (const n of registryNodes)
888
+ if (!privateKeys.has(n.key) &&
889
+ n.edges.some((e) => !registryKeys.has(e.target) || privateKeys.has(e.target))) {
890
+ privateKeys.add(n.key);
891
+ changed = true;
892
+ }
893
+ }
894
+ const sourceGraph = createGraphDoc(registryNodes.map((n) => ({
895
+ ...n,
896
+ deps: n.edges.filter((e) => registryKeys.has(e.target)),
897
+ })));
898
+ const pureGraph = createGraphDoc(registryNodes
899
+ .filter((n) => !privateKeys.has(n.key))
900
+ .map((n) => ({ ...n, deps: n.edges })));
901
+ const sharedDirectories = selectRealizationDirs(store, pureGraph);
902
+ const productionGraph = {
903
+ ...graph,
904
+ nodes: graph.nodes.map((n) => ({
905
+ ...n,
906
+ edges: n.edges.filter((e) => !(n.kind === "local" && e.kind === "dev")),
907
+ })),
908
+ };
909
+ const db = openStoreDb(store.paths.dbFile);
910
+ const registered = new Set();
911
+ try {
912
+ if (typeof state.generationId !== "string" ||
913
+ !/^gen1_[a-z2-7]+$/.test(state.generationId) ||
914
+ state.projectId !== `project1_${sha512Hex(project).slice(0, 32)}` ||
915
+ state.storeRoot !== store.root ||
916
+ state.workspaceRoot !== project)
917
+ throw error("ZYRK_STORE_CORRUPT", "Workspace generation identity is invalid.");
918
+ const generation = db
919
+ .prepare("SELECT readiness,state,graph_digest FROM generations WHERE generation_id=? AND project_id=?")
920
+ .get(state.generationId, state.projectId);
921
+ if (generation?.readiness !== "ready" ||
922
+ generation.state !== "active" ||
923
+ generation.graph_digest !== state.graphDigest)
924
+ throw error("ZYRK_STORE_CORRUPT", "Workspace deployment requires its active ready generation.");
925
+ const rows = db
926
+ .prepare("SELECT ri.path,ri.instance_id FROM generation_instances gi JOIN realization_instances ri ON gi.instance_id=ri.instance_id WHERE gi.generation_id=? AND ri.verification_state='verified'")
927
+ .all(state.generationId);
928
+ if (!Array.isArray(state.instances) ||
929
+ rows.length !== state.instances.length ||
930
+ rows.some((row) => !state.instances.includes(row.instance_id)))
931
+ throw error("ZYRK_STORE_CORRUPT", "Workspace instance metadata disagrees with its selected generation.");
932
+ rows.forEach((row) => registered.add(row.path));
933
+ }
934
+ finally {
935
+ db.close();
936
+ }
937
+ if (state.linker === "private" && !object(state.buildProfile))
938
+ throw error("ZYRK_DEPLOY_NONRELOCATABLE", "Workspace private generation has no supported build profile.");
939
+ const nativeProfile = state.buildProfile;
940
+ if (object(nativeProfile) &&
941
+ (typeof nativeProfile.candidate !== "string" ||
942
+ !/^[0-9a-f]{128}$/.test(nativeProfile.candidate) ||
943
+ state.privateDir !==
944
+ path.join(project, ".zyrk-local/generations", String(state.generationId))))
945
+ throw error("ZYRK_STORE_CORRUPT", "Workspace private generation profile or stable directory is invalid.");
946
+ if (object(nativeProfile) &&
947
+ (nativeProfile.node !== process.version ||
948
+ nativeProfile.abi !== process.versions.modules ||
949
+ nativeProfile.platform !== process.platform ||
950
+ nativeProfile.arch !== process.arch))
951
+ throw error("ZYRK_PLATFORM_UNSUPPORTED", "Workspace private artifacts require their exact recorded Node ABI and profile.");
952
+ for (const key of selected) {
953
+ const n = byKey.get(key), directory = directories.get(key);
954
+ if (!directory)
955
+ throw error("ZYRK_OFFLINE_MISSING", "A required workspace runtime package is not installed.");
956
+ if (n.kind === "registry") {
957
+ const context = n.name.startsWith("@")
958
+ ? path.resolve(directory, "../../..")
959
+ : path.resolve(directory, "../..");
960
+ if (!registered.has(context) ||
961
+ (!within(store.paths.realizationsDir, context) &&
962
+ !within(path.join(project, ".zyrk-local/generations", String(state.generationId)), context)))
963
+ throw error("ZYRK_DEPLOY_ESCAPE", "Workspace registry dependency is outside its registered realization.");
964
+ if (within(store.paths.realizationsDir, context)) {
965
+ if (privateKeys.has(key) ||
966
+ packageDirIn(sharedDirectories.get(key), n.name) !== directory)
967
+ throw error("ZYRK_DEPLOY_ESCAPE", "Shared workspace registry dependency selects an unexpected context.");
968
+ await verifyRealization({
969
+ store,
970
+ graph: pureGraph,
971
+ rootKey: key,
972
+ contextId: contextIdFor(pureGraph, key),
973
+ realizationDirs: sharedDirectories,
974
+ });
975
+ }
976
+ else {
977
+ const expectedContexts = privateKeys.has(key)
978
+ ? [
979
+ workspaceContextId(graph, key, String(state.projectId)),
980
+ workspaceContextId(productionGraph, key, String(state.projectId)),
981
+ ]
982
+ : [contextIdFor(sourceGraph, key)];
983
+ if (!expectedContexts.some((id) => path.join(project, ".zyrk-local/generations", String(state.generationId), id) === context))
984
+ throw error("ZYRK_DEPLOY_ESCAPE", "Private workspace dependency selects an unexpected live context.");
985
+ }
986
+ const m = await readJson(path.join(directory, "package.json"));
987
+ if (m.name !== n.name || m.version !== n.version)
988
+ throw error("ZYRK_STORE_CORRUPT", "Workspace runtime package identity disagrees with its lock.");
989
+ }
990
+ }
991
+ const stage = await mkdtemp(path.join(parent, ".zyrk-deploy-"));
992
+ try {
993
+ const budget = { entries: 0, bytes: 0, started: Date.now() }, targets = new Map([[rootKey, stage]]);
994
+ for (const key of selected)
995
+ if (key !== rootKey)
996
+ targets.set(key, packageDirIn(path.join(stage, "node_modules/.zyrk/portable", sha512Hex(key).slice(0, 40)), byKey.get(key).name));
997
+ for (const file of files)
998
+ await copyApplication(path.join(project, file), path.join(stage, file), project);
999
+ for (const key of selected) {
1000
+ const n = byKey.get(key), target = targets.get(key), source = directories.get(key);
1001
+ if (key !== rootKey) {
1002
+ const skip = new Set();
1003
+ if (n.kind === "local")
1004
+ skip.add("node_modules");
1005
+ else
1006
+ for (const e of n.edges)
1007
+ if (e.name === n.name)
1008
+ skip.add(path.join("node_modules", ...e.name.split("/")));
1009
+ await copyPrivatePayload(source, target, source, target, skip, [project, store.root], budget);
1010
+ }
1011
+ const sourceManifest = n.kind === "local"
1012
+ ? workspace.importers.find((i) => i.id === n.importer).manifest
1013
+ : await readJson(path.join(source, "package.json"));
1014
+ const exported = { ...sourceManifest };
1015
+ delete exported.devDependencies;
1016
+ delete exported.zyrk;
1017
+ delete exported.packageManager;
1018
+ delete exported.workspaces;
1019
+ await writeFile(path.join(target, "package.json"), JSON.stringify(exported, null, 2) + "\n");
1020
+ }
1021
+ for (const key of selected) {
1022
+ const n = byKey.get(key);
1023
+ for (const e of n.edges)
1024
+ if (selected.has(e.target) &&
1025
+ !(n.kind === "local" && e.kind === "dev")) {
1026
+ await relativeLink(path.join(targets.get(key), "node_modules", e.name), targets.get(e.target));
1027
+ const child = byKey.get(e.target);
1028
+ const rawBin = child.kind === "registry"
1029
+ ? child.bin
1030
+ : workspace.importers.find((i) => i.id === child.importer)
1031
+ .manifest.bin;
1032
+ const bins = typeof rawBin === "string"
1033
+ ? { [child.name.split("/").at(-1)]: rawBin }
1034
+ : object(rawBin)
1035
+ ? rawBin
1036
+ : {};
1037
+ for (const [name, relative] of Object.entries(bins)) {
1038
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(name) ||
1039
+ typeof relative !== "string")
1040
+ throw error("ZYRK_DEPLOY_ESCAPE", "Workspace executable declaration is unsafe.");
1041
+ const pkg = targets.get(e.target), file = path.resolve(pkg, relative);
1042
+ if (!within(pkg, file))
1043
+ throw error("ZYRK_DEPLOY_ESCAPE", "Workspace executable escapes its runtime package.");
1044
+ const metadata = await lstat(file).catch(() => null);
1045
+ if (!metadata?.isFile())
1046
+ throw error("ZYRK_DEPLOY_NONRELOCATABLE", "A required workspace executable is missing.");
1047
+ const binDir = path.join(targets.get(key), "node_modules/.bin");
1048
+ await mkdir(binDir, { recursive: true });
1049
+ if (await lstat(path.join(binDir, name)).catch(() => null))
1050
+ throw error("ZYRK_DEPLOY_NONRELOCATABLE", "Workspace runtime executable names conflict.");
1051
+ if (metadata.mode & 0o111)
1052
+ await relativeLink(path.join(binDir, name), file);
1053
+ else
1054
+ await writeFile(path.join(binDir, name), '#!/bin/sh\nexec node "$(dirname "$0")"/' +
1055
+ shellQuote(path.relative(binDir, file).split(path.sep).join("/")) +
1056
+ ' "$@"\n', { mode: 0o755 });
1057
+ }
1058
+ }
1059
+ }
1060
+ if (!(await lstat(path.join(stage, entry)).catch(() => null))?.isFile())
1061
+ throw error("ZYRK_DEPLOY_NONRELOCATABLE", "The selected runtime Node entry is missing.");
1062
+ await scanPrivateExport(stage, [project, store.root], budget);
1063
+ await validateLinks(stage, stage);
1064
+ await writeFile(path.join(stage, "zyrk-deploy.json"), JSON.stringify({
1065
+ schemaVersion: 1,
1066
+ platform: process.platform,
1067
+ arch: process.arch,
1068
+ node: process.version,
1069
+ entry: ["node", entry],
1070
+ requirements: ["Node.js " + process.versions.node.split(".")[0]],
1071
+ packages: selected.size - 1,
1072
+ files,
1073
+ validation: "structural; application smoke test not run",
1074
+ }, null, 2) + "\n");
1075
+ await rename(stage, out);
1076
+ output(options, {
1077
+ packages: selected.size - 1,
1078
+ entry: ["node", entry],
1079
+ validation: "structural",
1080
+ }, `Exported ${selected.size - 1} workspace runtime packages. Start with: node ${entry}. Structural validation passed; no application smoke test ran.`);
1081
+ return 0;
1082
+ }
1083
+ finally {
1084
+ await rm(stage, { recursive: true, force: true });
1085
+ }
1086
+ }
1087
+ finally {
1088
+ await barrier.release();
1089
+ }
1090
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"maintenance.d.ts","sourceRoot":"","sources":["../../src/commands/maintenance.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAkB,KAAK,aAAa,EAAE,MAAM,mBAAmB,CAAC;AASvE,QAAA,MAAM,UAAU,YAAI,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,CAAU,CAAC;AAC5E,KAAK,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAC5C,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,UAAU,GAAG,UAAU,GAAG,aAAa,CAAC;IAChD,UAAU,EAAE,KAAK,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,QAAQ,CAAC;QACnB,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,EAAE,MAAM,CAAC;KACb,CAAC,CAAC;IACH,iBAAiB,EAAE,OAAO,CAAC;IAC3B,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,CAAC;AAUF,wBAAsB,qBAAqB,CAAC,OAAO,EAAE;IACnD,OAAO,EAAE,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAC;IACtC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,cAAc,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB,GAAG,OAAO,CAAC,aAAa,GAAG,WAAW,CAAC,CAwOvC"}
1
+ {"version":3,"file":"maintenance.d.ts","sourceRoot":"","sources":["../../src/commands/maintenance.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAkB,KAAK,aAAa,EAAE,MAAM,mBAAmB,CAAC;AASvE,QAAA,MAAM,UAAU,YAAI,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,CAAU,CAAC;AAC5E,KAAK,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAC5C,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,UAAU,GAAG,UAAU,GAAG,aAAa,CAAC;IAChD,UAAU,EAAE,KAAK,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,QAAQ,CAAC;QACnB,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,EAAE,MAAM,CAAC;KACb,CAAC,CAAC;IACH,iBAAiB,EAAE,OAAO,CAAC;IAC3B,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,CAAC;AAUF,wBAAsB,qBAAqB,CAAC,OAAO,EAAE;IACnD,OAAO,EAAE,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAC;IACtC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,cAAc,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB,GAAG,OAAO,CAAC,aAAa,GAAG,WAAW,CAAC,CAgQvC"}
@@ -126,6 +126,7 @@ export async function runMaintenanceCommand(options) {
126
126
  scripts: scriptPolicyFingerprint(object(manifest.zyrk) ? manifest.zyrk.scripts : undefined),
127
127
  config: configFingerprint({
128
128
  registry: options.registry.origin,
129
+ registries: options.registry.scopedRegistries,
129
130
  linker: "shared",
130
131
  platform: currentPlatform(),
131
132
  ...(object(manifest.zyrk) && Array.isArray(manifest.zyrk.mutablePackages)
@@ -146,7 +147,7 @@ export async function runMaintenanceCommand(options) {
146
147
  : lockedGraph(lock.doc).nodes;
147
148
  const packages = Object.create(null);
148
149
  for (const node of graphNodes) {
149
- if (node.origin !== options.registry.origin)
150
+ if (node.origin !== options.registry.originFor(node.name))
150
151
  throw new ZyrkError("ZYRK_POLICY_DENIED", "Cannot audit a graph from a different registry origin.", {
151
152
  remedy: "Select the registry recorded by the lockfile before sending dependency names.",
152
153
  });
@@ -158,7 +159,26 @@ export async function runMaintenanceCommand(options) {
158
159
  return base;
159
160
  let response;
160
161
  try {
161
- response = await options.registry.auditAdvisories(packages);
162
+ const groups = new Map();
163
+ for (const [name, versions] of Object.entries(packages)) {
164
+ const client = options.registry.forPackage(name);
165
+ let selected = groups.get(client);
166
+ if (!selected) {
167
+ selected = Object.create(null);
168
+ groups.set(client, selected);
169
+ }
170
+ selected[name] = versions;
171
+ }
172
+ const replies = await Promise.all([...groups].map(async ([client, selected]) => {
173
+ const reply = await client.auditAdvisories(selected);
174
+ if (!object(reply) ||
175
+ Object.keys(reply).some((name) => !Object.hasOwn(selected, name)))
176
+ return null;
177
+ return reply;
178
+ }));
179
+ response = replies.some((reply) => reply === null)
180
+ ? null
181
+ : Object.assign({}, ...replies);
162
182
  }
163
183
  catch {
164
184
  return {
@@ -0,0 +1,2 @@
1
+ export declare function validateNativeExport(bytes: Buffer): void;
2
+ //# sourceMappingURL=native-export.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native-export.d.ts","sourceRoot":"","sources":["../../src/commands/native-export.ts"],"names":[],"mappings":"AAYA,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CA4MxD"}