zyrk 0.2.3 → 0.3.7

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/README.md CHANGED
@@ -1,16 +1,15 @@
1
1
  # zyrk
2
2
 
3
- [![CI](https://github.com/kingjethro999/nexu/actions/workflows/ci.yml/badge.svg)](https://github.com/kingjethro999/nexu/actions/workflows/ci.yml)
3
+ [![CI](https://github.com/kingjethro999/zyrk/actions/workflows/ci.yml/badge.svg)](https://github.com/kingjethro999/zyrk/actions/workflows/ci.yml)
4
4
  [![Node.js](https://img.shields.io/badge/Node.js-24%2B-339933?logo=node.js&logoColor=white)](https://nodejs.org/)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
6
6
 
7
7
  An npm-compatible package manager built around **one verified package store per
8
8
  OS user**, with dependency-aware package instances linked into projects.
9
9
 
10
- **Pre-release and under active construction.** The current CLI can fetch and run
11
- one-off tools with `dlx` and `create`. Ordinary project installation, dependency
12
- editing, garbage collection, repair, and deployment are still being built. Check
13
- `zyrk --help` for the implemented command surface.
10
+ **Pre-release and under active construction.** `zyrk install` installs project
11
+ dependencies through verified shared storage; `dlx` and `create` run one-off
12
+ tools. Check `zyrk --help` and the limitations below before adopting it.
14
13
 
15
14
  ## Why zyrk exists
16
15
 
@@ -30,8 +29,13 @@ be reported only after reproducible measurements and compatibility tests.
30
29
 
31
30
  ## Try the current CLI
32
31
 
33
- Once the `zyrk` package is published, install it with `npm install -g zyrk`,
34
- then run `zyrk --help`.
32
+ Install from npm:
33
+
34
+ ```bash
35
+ npm install -g zyrk
36
+ zyrk --help
37
+ zyrk --version
38
+ ```
35
39
 
36
40
  Requires **Node.js 24 or newer**. The source runs directly with Node's native
37
41
  TypeScript support; no compilation step or native toolchain is required to
@@ -40,17 +44,65 @@ install zyrk itself.
40
44
  From a checkout:
41
45
 
42
46
  ```bash
43
- git clone https://github.com/kingjethro999/nexu.git zyrk
47
+ git clone https://github.com/kingjethro999/zyrk.git zyrk
44
48
  cd zyrk
45
49
  npm ci
46
50
  node bin/zyrk.js --help
47
51
  node bin/zyrk.js --version
48
52
  ```
49
53
 
50
- For an npm-distributed build, use the exact package name listed on its npm page
51
- and check that its repository points to `kingjethro999/nexu`. The executable is
52
- `zyrk`. The existing [npm publishing action](.github/workflows/npm-publish.yml)
53
- uses the repository’s `NPM_TOKEN` secret when a GitHub release is created.
54
+ ### One-line install (shell)
55
+
56
+ Linux / macOS:
57
+
58
+ ```bash
59
+ curl -fsSL https://raw.githubusercontent.com/kingjethro999/zyrk/main/install.sh | sh
60
+ ```
61
+
62
+ Windows (PowerShell):
63
+
64
+ ```powershell
65
+ irm https://raw.githubusercontent.com/kingjethro999/zyrk/main/install.ps1 | iex
66
+ ```
67
+
68
+ ### Install project dependencies
69
+
70
+ ```bash
71
+ zyrk install # dependencies + devDependencies
72
+ zyrk install react # add and save a registry package
73
+ zyrk install -D typescript # save a development dependency
74
+ zyrk i --frozen-lockfile # require an unchanged compatible lock
75
+ zyrk install --offline --frozen-lockfile
76
+ zyrk install --omit=dev # project production roots only
77
+ zyrk install --lockfile-only # write selections without installing
78
+ ```
79
+
80
+ zyrk resolves exact versions, verifies archive integrity before extraction, and
81
+ creates dependency-aware realizations in the per-user store. Project
82
+ `node_modules` contains links to those realizations. Two projects selecting the
83
+ same dependency context reuse the same files; incompatible versions and peer
84
+ contexts stay separate. Normal CJS and ESM imports continue to work.
85
+
86
+ `package-lock.json` records npm selections plus a `zyrk` extension. Warm installs
87
+ reuse compatible locked selections; frozen installs reject drift. Non-frozen
88
+ installation can adopt supported npm v2/v3 lockfiles without reselecting versions.
89
+ Offline installs use locked verified sources and fail if an input is missing.
90
+ Manifest, lock, and projection changes are journaled for process-crash recovery;
91
+ a failed resolution, verification, or build-policy check keeps the prior graph.
92
+
93
+ Dependency install scripts and implicit native builds are blocked by default.
94
+ `--ignore-scripts` explicitly produces an **intentionally unbuilt** installation.
95
+ Root install hooks are reported and never run automatically. Script approval,
96
+ private native builds, runtime-mutable packages, workspaces, overrides, Git/local
97
+ sources, and authenticated or mixed scoped registries are not supported by this
98
+ install path yet. Unsupported platform constraints and failed optional packages
99
+ currently fail installation instead of being silently omitted. macOS/Windows
100
+ compatibility and framework support remain unverified.
101
+
102
+ Use `--registry=https://your-registry.example/` for a compatible unauthenticated
103
+ registry. The adapter reads the default registry from user/project `.npmrc`, and
104
+ fails closed on scoped registry routing or disabled TLS verification. Tool
105
+ commands (`dlx`/`create`) still use their existing public-registry configuration.
54
106
 
55
107
  ### Run a one-off tool
56
108
 
@@ -79,15 +131,16 @@ not a sandbox.
79
131
 
80
132
  ### Current command coverage
81
133
 
82
- | Surface | Current behavior |
83
- | -------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
84
- | `--help`, `--version` | Show the CLI surface and package version. |
85
- | `dlx`, `create` | Resolve, fetch, verify archives, and run temporary tool environments. |
86
- | `publish`, `unpublish`, `access`, `deprecate`, `dist-tag`, `owner` | Explicitly delegate to your npm CLI. |
87
- | `token`, `adduser`, `login`, `logout`, `whoami`, `ping`, `pack` | Explicitly delegate to your npm CLI. |
88
- | `install`, `add`, `remove`, `update`, `run`, `exec` | Report `ZYRK_NOT_IMPLEMENTED`. |
89
- | `store`, `projects`, `repair`, `import`, `deploy`, `docker` | Report `ZYRK_NOT_IMPLEMENTED`. |
90
- | `list`, `why`, `doctor`, `approve-builds`, `rebuild`, `audit`, `patch`, `patch-commit` | Report `ZYRK_NOT_IMPLEMENTED`. |
134
+ | Surface | Current behavior |
135
+ | -------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
136
+ | `install`, `i` | Resolve or reuse the lock, verify sources, and publish shared project dependencies. |
137
+ | `--help`, `--version` | Show the CLI surface and package version. |
138
+ | `dlx`, `create` | Resolve, fetch, verify archives, and run temporary tool environments. |
139
+ | `publish`, `unpublish`, `access`, `deprecate`, `dist-tag`, `owner` | Explicitly delegate to your npm CLI. |
140
+ | `token`, `adduser`, `login`, `logout`, `whoami`, `ping`, `pack` | Explicitly delegate to your npm CLI. |
141
+ | `add`, `remove`, `update`, `run`, `exec` | Report `ZYRK_NOT_IMPLEMENTED`. |
142
+ | `store`, `projects`, `repair`, `import`, `deploy`, `docker` | Report `ZYRK_NOT_IMPLEMENTED`. |
143
+ | `list`, `why`, `doctor`, `approve-builds`, `rebuild`, `audit`, `patch`, `patch-commit` | Report `ZYRK_NOT_IMPLEMENTED`. |
91
144
 
92
145
  Native commands that support `--json` return a versioned result envelope.
93
146
  `dlx` and `create` reject that option because the tool owns stdout. Delegated
@@ -173,7 +226,7 @@ GitHub release is created; ordinary commits do not trigger that action.
173
226
 
174
227
  The contributor guide is [AGENTS.md](AGENTS.md). Bug reports and reproducible
175
228
  compatibility cases belong in
176
- [GitHub issues](https://github.com/kingjethro999/nexu/issues).
229
+ [GitHub issues](https://github.com/kingjethro999/zyrk/issues).
177
230
 
178
231
  ## Naming and local state
179
232
 
package/VERSION CHANGED
@@ -1 +1 @@
1
- v0.2.3
1
+ v0.3.7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zyrk",
3
- "version": "0.2.3",
3
+ "version": "0.3.7",
4
4
  "description": "Storage-efficient Node package manager: one verified store per user, shared across projects through links.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -36,12 +36,12 @@
36
36
  },
37
37
  "repository": {
38
38
  "type": "git",
39
- "url": "git+https://github.com/kingjethro999/nexu.git"
39
+ "url": "git+https://github.com/kingjethro999/zyrk.git"
40
40
  },
41
41
  "bugs": {
42
- "url": "https://github.com/kingjethro999/nexu/issues"
42
+ "url": "https://github.com/kingjethro999/zyrk/issues"
43
43
  },
44
- "homepage": "https://github.com/kingjethro999/nexu#readme",
44
+ "homepage": "https://github.com/kingjethro999/zyrk#readme",
45
45
  "keywords": [
46
46
  "package-manager",
47
47
  "npm",
package/src/cli/main.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * This is the roadmap skeleton, not the whole implementation: `dlx` and
5
5
  * `create` run temporary tool environments; `--help`, `--version`, the
6
6
  * global-install rejection, and explicit npm delegation do their jobs.
7
- * Every other native dependency command fails with ZYRK_NOT_IMPLEMENTED
7
+ * `install` publishes a verified shared project graph. Other dependency commands fail with ZYRK_NOT_IMPLEMENTED
8
8
  * rather than pretending to work; unknown commands fail with ZYRK_USAGE
9
9
  * and the help text.
10
10
  * Nothing here launches a subprocess: rejection happens before resolution,
@@ -12,6 +12,8 @@
12
12
  * process (SPEC 15.3.1).
13
13
  */
14
14
 
15
+ import { installProject, type InstallOptions } from "../install/run.ts";
16
+ import { installRegistry } from "../install/registry.ts";
15
17
  import { readFileSync } from "node:fs";
16
18
  import * as path from "node:path";
17
19
  import { fileURLToPath } from "node:url";
@@ -60,7 +62,7 @@ for (const [names] of NATIVE_COMMANDS) {
60
62
  }
61
63
 
62
64
  /** Commands with a real implementation behind runCli. */
63
- const IMPLEMENTED_COMMANDS = new Set(["dlx", "create"]);
65
+ const IMPLEMENTED_COMMANDS = new Set(["dlx", "create", "install", "i"]);
64
66
 
65
67
  export function helpText(version: string): string {
66
68
  const lines = [
@@ -301,6 +303,89 @@ export async function runCli(options: RunCliOptions): Promise<number> {
301
303
  );
302
304
  }
303
305
  }
306
+ if (
307
+ !parsed.help &&
308
+ !parsed.version &&
309
+ (parsed.command === "install" || parsed.command === "i")
310
+ ) {
311
+ if (isGlobalInstallRequest(parsed.command, parsed.commandArgs, options.env))
312
+ return emit(fail(globalInstallError(parsed.command), parsed));
313
+ try {
314
+ if (
315
+ parsed.commandArgs.includes("--help") ||
316
+ parsed.commandArgs.includes("-h")
317
+ ) {
318
+ return emit(
319
+ ok(
320
+ "Usage: zyrk install [package[@version] ...] [-D] [--save-exact] [--frozen-lockfile] [--offline] [--ignore-scripts] [--lockfile-only] [--omit=dev] [--registry=https://registry.npmjs.org/] [--json]\n",
321
+ parsed,
322
+ ),
323
+ );
324
+ }
325
+ const specs: string[] = [];
326
+ const flags: Partial<InstallOptions> = {};
327
+ let registryUrl: string | undefined;
328
+ for (const token of parsed.commandArgs) {
329
+ if (token === "--frozen-lockfile") flags.frozen = true;
330
+ else if (token === "--offline") flags.offline = true;
331
+ else if (token === "--ignore-scripts") flags.ignoreScripts = true;
332
+ else if (token === "--lockfile-only") flags.lockfileOnly = true;
333
+ else if (token === "--omit=dev" || token === "--production")
334
+ flags.omitDev = true;
335
+ else if (token === "-D" || token === "--save-dev") flags.saveDev = true;
336
+ else if (token === "-E" || token === "--save-exact")
337
+ flags.saveExact = true;
338
+ else if (token === "--no-global" || token === "--global=false")
339
+ continue;
340
+ else if (token.startsWith("--registry="))
341
+ registryUrl = token.slice("--registry=".length);
342
+ else if (token.startsWith("-"))
343
+ throw new ZyrkError("ZYRK_USAGE", "Unsupported install option.", {
344
+ remedy:
345
+ "Run zyrk install --help for supported options; use --registry=URL for a custom registry.",
346
+ });
347
+ else specs.push(token);
348
+ }
349
+ const cwd = options.cwd ?? process.cwd();
350
+ const tools = options.tools
351
+ ? await options.tools()
352
+ : {
353
+ registry: await installRegistry({
354
+ cwd,
355
+ env: options.env,
356
+ version,
357
+ ...(registryUrl === undefined ? {} : { registry: registryUrl }),
358
+ }),
359
+ store: await Store.open({
360
+ root: resolveStoreRoot({ env: options.env }).root,
361
+ create: true,
362
+ }),
363
+ };
364
+ const result = await installProject({
365
+ ...flags,
366
+ specs,
367
+ projectDir: cwd,
368
+ ...tools,
369
+ });
370
+ if (parsed.json)
371
+ return emit({
372
+ exitCode: 0,
373
+ stdout: `${JSON.stringify({ schemaVersion: CLI_JSON_SCHEMA_VERSION, status: "ok", ...result })}\n`,
374
+ stderr: "",
375
+ });
376
+ const summary =
377
+ result.readiness === "lockfile-only"
378
+ ? "Lockfile written; dependencies were not installed."
379
+ : `Installed ${result.packages} packages using shared storage (${result.reused} realizations reused); ${result.readiness}.`;
380
+ return emit({
381
+ exitCode: 0,
382
+ stdout: `${summary}\n`,
383
+ stderr: result.warnings.map((w) => `zyrk: ${w}\n`).join(""),
384
+ });
385
+ } catch (error) {
386
+ return emit(fail(toZyrkError(error, "zyrk install failed."), parsed));
387
+ }
388
+ }
304
389
  if (
305
390
  !parsed.help &&
306
391
  !parsed.version &&
@@ -0,0 +1,154 @@
1
+ /** Parse dependency edits; the installer owns resolution and atomic publication. */
2
+ import npa from "npm-package-arg";
3
+ import { ZyrkError } from "../errors.ts";
4
+ import type { RegistryClient } from "../fetch/registry.ts";
5
+ import type { Store } from "../store/layout.ts";
6
+ import { packageName } from "../install/lock.ts";
7
+ import {
8
+ installProject,
9
+ type InstallOptions,
10
+ type InstallResult,
11
+ } from "../install/run.ts";
12
+
13
+ export type DependencyCommand = "add" | "remove" | "update" | "import";
14
+
15
+ function usage(command: DependencyCommand, reason: string): ZyrkError {
16
+ return new ZyrkError(
17
+ "ZYRK_USAGE",
18
+ `Cannot ${command} dependencies: ${reason}.`,
19
+ {
20
+ remedy:
21
+ "Use add <spec...>, remove <name...>, update [--latest] [name...], or import --from npm; review zyrk --help for supported options.",
22
+ },
23
+ );
24
+ }
25
+
26
+ export async function runDependencyCommand(options: {
27
+ command: DependencyCommand;
28
+ args: readonly string[];
29
+ projectDir: string;
30
+ store: Store;
31
+ registry: RegistryClient;
32
+ }): Promise<InstallResult> {
33
+ const { command } = options;
34
+ const install: InstallOptions = {
35
+ projectDir: options.projectDir,
36
+ store: options.store,
37
+ registry: options.registry,
38
+ };
39
+ const names: string[] = [];
40
+ let from: string | undefined;
41
+ let latest = false;
42
+ let positional = false;
43
+ for (let i = 0; i < options.args.length; i++) {
44
+ const arg = options.args[i]!;
45
+ if (!positional && arg === "--") {
46
+ positional = true;
47
+ continue;
48
+ }
49
+ if (!positional && arg.startsWith("-")) {
50
+ switch (arg) {
51
+ case "-D":
52
+ case "--save-dev":
53
+ if (command !== "add")
54
+ throw usage(command, "--save-dev is only valid for add");
55
+ install.saveDev = true;
56
+ break;
57
+ case "-O":
58
+ case "--save-optional":
59
+ if (command !== "add")
60
+ throw usage(command, "--save-optional is only valid for add");
61
+ install.saveOptional = true;
62
+ break;
63
+ case "-E":
64
+ case "--save-exact":
65
+ if (command !== "add" && command !== "update")
66
+ throw usage(
67
+ command,
68
+ "--save-exact is only valid for add or update",
69
+ );
70
+ install.saveExact = true;
71
+ break;
72
+ case "--latest":
73
+ if (command !== "update")
74
+ throw usage(command, "--latest is only valid for update");
75
+ latest = true;
76
+ break;
77
+ case "--ignore-scripts":
78
+ install.ignoreScripts = true;
79
+ break;
80
+ case "--lockfile-only":
81
+ install.lockfileOnly = true;
82
+ break;
83
+ case "--from":
84
+ if (command !== "import" || from !== undefined)
85
+ throw usage(command, "unexpected --from option");
86
+ from = options.args[++i];
87
+ if (from === undefined) throw usage(command, "--from requires npm");
88
+ break;
89
+ default:
90
+ if (
91
+ arg.startsWith("--from=") &&
92
+ command === "import" &&
93
+ from === undefined
94
+ ) {
95
+ from = arg.slice(7);
96
+ break;
97
+ }
98
+ if (
99
+ command === "add" &&
100
+ ["-g", "--global", "--global=true"].includes(arg)
101
+ )
102
+ throw new ZyrkError(
103
+ "ZYRK_GLOBAL_INSTALL_UNSUPPORTED",
104
+ "Cannot install global dependencies through zyrk.",
105
+ { remedy: "Run npm install --global directly instead." },
106
+ );
107
+ throw usage(command, "an unsupported option was supplied");
108
+ }
109
+ } else names.push(arg);
110
+ }
111
+ if (install.saveDev && install.saveOptional)
112
+ throw usage(command, "development and optional dependency groups conflict");
113
+ switch (command) {
114
+ case "add":
115
+ if (!names.length)
116
+ throw usage(command, "at least one package specification is required");
117
+ for (const name of names) {
118
+ try {
119
+ const parsed = npa(name);
120
+ if (
121
+ !parsed.name ||
122
+ !packageName(parsed.name) ||
123
+ !["version", "range", "tag", "alias"].includes(parsed.type)
124
+ )
125
+ throw new Error("unsupported");
126
+ } catch {
127
+ throw usage(command, "a registry package specification is invalid");
128
+ }
129
+ }
130
+ install.specs = names;
131
+ break;
132
+ case "remove":
133
+ case "update":
134
+ if (command === "remove" && !names.length)
135
+ throw usage(command, "at least one direct dependency name is required");
136
+ if (names.some((name) => !packageName(name)))
137
+ throw usage(
138
+ command,
139
+ "expected direct dependency names without versions",
140
+ );
141
+ install.mutation =
142
+ command === "remove" ? { remove: names } : { update: names, latest };
143
+ break;
144
+ case "import":
145
+ if (from !== "npm" || names.length)
146
+ throw usage(
147
+ command,
148
+ "import requires --from npm and no package arguments",
149
+ );
150
+ install.importNpm = true;
151
+ break;
152
+ }
153
+ return installProject(install);
154
+ }