zyrk 0.7.2 → 0.7.3

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
@@ -143,7 +143,9 @@ still take precedence.
143
143
 
144
144
  Use `zyrk run <script>` or the package-manager shortcut `zyrk <script>`; both
145
145
  use the same project-script policy, installed-generation checks, and local
146
- binary path. Arguments after `--` are passed to the script:
146
+ binary path. Run the shortcut from a directory whose `package.json` declares
147
+ that script; for example, run `cd web` before `zyrk dev` when the script belongs
148
+ to `web/package.json`. Arguments after `--` are passed to the script:
147
149
 
148
150
  ```bash
149
151
  zyrk dev
@@ -456,14 +458,18 @@ overrides are ignored, matching npm root-only policy. Override rule order is
456
458
  recorded in the manifest fingerprint; frozen installs reject policy drift.
457
459
 
458
460
  `zyrk help` and `zyrk --help` both show help successfully. Installs report
459
- preparation, resolution, verified source counts, realization, approved builds,
460
- and project linking on stderr. Interactive terminals show an activity spinner;
461
- redirected output contains plain progress messages. Both `zyrk add prettier` and
462
- `zyrk install prettier` show these details. Source counters distinguish verified
463
- reuse from newly downloaded sources; downloads show received bytes. A checkmark
464
- lists each unique package/version linked after the project transaction commits.
465
- Source counts differ from dependency contexts when peers split a package.
466
- `--json` disables progress.
461
+ preparation, verified source counts, realization, approved builds, and project
462
+ linking on stderr. Interactive terminals show the stages as a calm, multi-line
463
+ sequence, followed by a success footer; package downloads show received bytes,
464
+ and a checkmark lists each unique package/version linked after the project
465
+ transaction commits. Both `zyrk add prettier` and `zyrk install prettier` show
466
+ these details. Redirected output uses plain progress messages. Source counters
467
+ distinguish verified reuse from newly downloaded sources. Source counts differ
468
+ from dependency contexts when peers split a package. `--json` disables progress.
469
+ Other native commands use the same terminal language: successful summaries get
470
+ a checkmark, structured inspection output is rendered readably, and actionable
471
+ errors show their stable code and remedy together. `NO_COLOR=1` or `TERM=dumb`
472
+ disables color; redirected output and `--json` remain plain and script-friendly.
467
473
 
468
474
  Install resolution has a five-minute deadline, registry requests (including
469
475
  archive downloads) have a fifteen-minute deadline, a complete fetch operation
package/VERSION CHANGED
@@ -1 +1 @@
1
- v0.7.2
1
+ v0.7.3
@@ -28,6 +28,7 @@ export type RunCliOptions = {
28
28
  stdin?: NodeJS.ReadStream;
29
29
  stdout: {
30
30
  write(chunk: string): unknown;
31
+ isTTY?: boolean;
31
32
  };
32
33
  stderr: {
33
34
  write(chunk: string): unknown;
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":"AA+BA,OAAO,EAAe,SAAS,EAAe,MAAM,cAAc,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,CAAC;AACrB,OAAO,EAAoB,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAExE,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAM3C,OAAO,EAAqC,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAEhF,eAAO,MAAM,uBAAuB,IAAI,CAAC;AA6DzC,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CA0BhD;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AA6EF;;;GAGG;AACH,wBAAgB,QAAQ,CACtB,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAA;CAAE,GACnD,SAAS,CAgDX;AAmBD,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,cAAc,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IAC1B,MAAM,EAAE;QAAE,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;IAC1C,MAAM,EAAE;QAAE,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC3D,WAAW,CAAC,EAAE,MAAM,MAAM,CAAC;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,OAAO,CAAC,eAAe,CAAC,CAAC;CACxC,CAAC;AAuDF,gFAAgF;AAChF,wBAAsB,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CA2fpE"}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":"AAoCA,OAAO,EAAe,SAAS,EAAe,MAAM,cAAc,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,CAAC;AACrB,OAAO,EAAoB,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAExE,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAM3C,OAAO,EAAqC,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAEhF,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAmFzC,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CA0BhD;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AA6EF;;;GAGG;AACH,wBAAgB,QAAQ,CACtB,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAA;CAAE,GACnD,SAAS,CAgDX;AAmBD,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,cAAc,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IAC1B,MAAM,EAAE;QAAE,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC3D,MAAM,EAAE;QAAE,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC3D,WAAW,CAAC,EAAE,MAAM,MAAM,CAAC;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,OAAO,CAAC,eAAe,CAAC,CAAC;CACxC,CAAC;AAuDF,gFAAgF;AAChF,wBAAsB,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAiiBpE"}
package/dist/cli/main.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { runNextCommand } from "../commands/next.js";
2
2
  import { createInstallProgress } from "./progress.js";
3
+ import { renderCommandError, renderCommandOutput } from "./terminal.js";
3
4
  import { runMaintenanceCommand } from "../commands/maintenance.js";
4
5
  import { runDeployCommand } from "../commands/deploy.js";
5
6
  import { runPatchCommand } from "../commands/patch.js";
@@ -9,7 +10,7 @@ import { sanitizeDiagnostic, redactSecrets } from "../log.js";
9
10
  * operations preserve npm arguments. Global install rejection precedes effects.
10
11
  * Unsupported command modes fail explicitly in their handler.
11
12
  */
12
- import { installProject } from "../install/run.js";
13
+ import { installProject, } from "../install/run.js";
13
14
  import { installRegistry } from "../install/registry.js";
14
15
  import { readFileSync } from "node:fs";
15
16
  import { readFile } from "node:fs/promises";
@@ -29,6 +30,26 @@ import { Store } from "../store/layout.js";
29
30
  import { delegateToNpm, delegatedHelpLines, isDelegatedCommand, } from "./npm-delegate.js";
30
31
  import { isGlobalInstallRequest, parseArgs } from "./parse.js";
31
32
  export const CLI_JSON_SCHEMA_VERSION = 1;
33
+ function installCompletion(readiness) {
34
+ switch (readiness) {
35
+ case "ready":
36
+ return {
37
+ title: "Installed from the verified shared store.",
38
+ detail: "Your project still uses node_modules and normal imports.",
39
+ };
40
+ case "intentionally-unbuilt":
41
+ return {
42
+ title: "Installed; some dependency builds were intentionally skipped.",
43
+ detail: "Review build policy before running packages that need those builds.",
44
+ };
45
+ case "fetched":
46
+ return { title: "Verified package sources in the shared store." };
47
+ case "lockfile-only":
48
+ return { title: "Lockfile updated; dependencies were not installed." };
49
+ case "build-review":
50
+ return { title: "Build review saved; dependency scripts were not run." };
51
+ }
52
+ }
32
53
  /** Spec-defined dependency commands (SPEC 15.1-15.2, fetch, docker). */
33
54
  const NATIVE_COMMANDS = [
34
55
  ["install, i", "Install the locked or newly resolved graph."],
@@ -253,10 +274,29 @@ export async function runCli(options) {
253
274
  const parsed = parseArgs(options.argv);
254
275
  const version = (options.readVersion ?? readCliVersion)();
255
276
  const emit = (result) => {
277
+ const interactive = !parsed.json &&
278
+ !parsed.help &&
279
+ !parsed.version &&
280
+ options.stdout.isTTY === true &&
281
+ parsed.command !== undefined &&
282
+ !isDelegatedCommand(parsed.command);
283
+ const style = {
284
+ isTTY: interactive,
285
+ color: interactive &&
286
+ options.env.NO_COLOR === undefined &&
287
+ options.env.TERM !== "dumb",
288
+ };
256
289
  if (result.stdout !== "")
257
- options.stdout.write(result.stdout);
290
+ options.stdout.write(interactive
291
+ ? renderCommandOutput(parsed.command ?? "command", result.stdout, style)
292
+ : result.stdout);
258
293
  if (result.stderr !== "")
259
- options.stderr.write(result.stderr);
294
+ options.stderr.write(result.exitCode !== 0
295
+ ? renderCommandError(result.stderr, {
296
+ ...style,
297
+ isTTY: options.stderr.isTTY === true && !parsed.json,
298
+ })
299
+ : result.stderr);
260
300
  return result.exitCode;
261
301
  };
262
302
  if (!parsed.help &&
@@ -443,7 +483,9 @@ export async function runCli(options) {
443
483
  (parsed.command === "install" || parsed.command === "i")) {
444
484
  if (isGlobalInstallRequest(parsed.command, parsed.commandArgs, options.env))
445
485
  return emit(fail(globalInstallError(parsed.command), parsed));
446
- const progress = createInstallProgress(options.stderr, parsed.json);
486
+ const progress = createInstallProgress(options.stderr, parsed.json, {
487
+ color: options.env.NO_COLOR === undefined && options.env.TERM !== "dumb",
488
+ });
447
489
  try {
448
490
  if (parsed.commandArgs.includes("--help") ||
449
491
  parsed.commandArgs.includes("-h")) {
@@ -507,7 +549,7 @@ export async function runCli(options) {
507
549
  : {}),
508
550
  ...tools,
509
551
  });
510
- progress.close();
552
+ progress.close(installCompletion(result.readiness));
511
553
  if (parsed.json)
512
554
  return emit({
513
555
  exitCode: 0,
@@ -548,7 +590,9 @@ export async function runCli(options) {
548
590
  parsed.command === "remove" ||
549
591
  parsed.command === "update" ||
550
592
  parsed.command === "import")) {
551
- const progress = createInstallProgress(options.stderr, parsed.json);
593
+ const progress = createInstallProgress(options.stderr, parsed.json, {
594
+ color: options.env.NO_COLOR === undefined && options.env.TERM !== "dumb",
595
+ });
552
596
  try {
553
597
  const tools = options.tools === undefined
554
598
  ? await defaultTools(options.env, version, options.cwd ?? process.cwd())
@@ -567,7 +611,10 @@ export async function runCli(options) {
567
611
  }
568
612
  : {}),
569
613
  });
570
- progress.close();
614
+ progress.close({
615
+ title: `Dependency changes installed from the verified shared store.`,
616
+ detail: `${result.packages} packages; ${result.reused} realizations reused.`,
617
+ });
571
618
  if (parsed.json) {
572
619
  return emit({
573
620
  exitCode: 0,
@@ -730,7 +777,13 @@ async function runToolCommand(command, parsed, options, version) {
730
777
  env: options.env,
731
778
  };
732
779
  if (command === "dlx") {
733
- return (await runDlx({ ...base, spec: first })).exitCode;
780
+ const result = await runDlx({ ...base, spec: first });
781
+ if (result.exitCode === 0 && options.stderr.isTTY)
782
+ options.stderr.write(renderCommandOutput("dlx", `zyrk dlx finished ${result.name}@${result.version}.\n`, {
783
+ isTTY: true,
784
+ color: options.env.NO_COLOR === undefined && options.env.TERM !== "dumb",
785
+ }));
786
+ return result.exitCode;
734
787
  }
735
788
  const isNext = starterSpecFor(first).startsWith("create-next-app@");
736
789
  options.stderr.write(isNext
@@ -738,13 +791,22 @@ async function runToolCommand(command, parsed, options, version) {
738
791
  ? "zyrk: Next scaffolding only; dependency installation was explicitly skipped.\n"
739
792
  : "zyrk: Creating Next.js application; dependencies will install with zyrk.\n"
740
793
  : "zyrk: This initializer controls its own dependency installation.\n");
741
- const progress = createInstallProgress(options.stderr, false);
794
+ const progress = createInstallProgress(options.stderr, false, {
795
+ color: options.env.NO_COLOR === undefined && options.env.TERM !== "dumb",
796
+ });
742
797
  try {
743
- return (await runCreate({
798
+ const result = await runCreate({
744
799
  ...base,
745
800
  input: first,
746
801
  onInstallProgress: progress.update,
747
- })).exitCode;
802
+ });
803
+ progress.close(result.exitCode === 0
804
+ ? {
805
+ title: "Project created successfully.",
806
+ detail: "Open the project directory and run its declared scripts with zyrk.",
807
+ }
808
+ : undefined);
809
+ return result.exitCode;
748
810
  }
749
811
  finally {
750
812
  progress.close();
@@ -1,10 +1,16 @@
1
1
  import type { InstallProgress } from "../install/run.ts";
2
+ export type InstallCompletion = {
3
+ title: string;
4
+ detail?: string;
5
+ };
2
6
  /** Presentation only: never writes to stdout or exposes package URLs or paths. */
3
7
  export declare function createInstallProgress(stream: {
4
8
  write(chunk: string): unknown;
5
9
  isTTY?: boolean;
6
- }, quiet: boolean): {
10
+ }, quiet: boolean, options?: {
11
+ color?: boolean;
12
+ }): {
7
13
  update(event: InstallProgress): void;
8
- close(): void;
14
+ close(completion?: InstallCompletion): void;
9
15
  };
10
16
  //# sourceMappingURL=progress.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"progress.d.ts","sourceRoot":"","sources":["../../src/cli/progress.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAYzD,kFAAkF;AAClF,wBAAgB,qBAAqB,CACnC,MAAM,EAAE;IAAE,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,EAC1D,KAAK,EAAE,OAAO,GACb;IAAE,MAAM,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAAC;IAAC,KAAK,IAAI,IAAI,CAAA;CAAE,CA2DzD"}
1
+ {"version":3,"file":"progress.d.ts","sourceRoot":"","sources":["../../src/cli/progress.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAYzD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,kFAAkF;AAClF,wBAAgB,qBAAqB,CACnC,MAAM,EAAE;IAAE,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,EAC1D,KAAK,EAAE,OAAO,EACd,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GAChC;IACD,MAAM,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAAC;IACrC,KAAK,CAAC,UAAU,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;CAC7C,CA2FA"}
@@ -1,6 +1,6 @@
1
1
  const labels = {
2
2
  planning: "Preparing installation",
3
- resolving: "Resolving dependencies",
3
+ resolving: "Preparing installation",
4
4
  fetching: "Verifying package sources",
5
5
  realizing: "Preparing shared realizations",
6
6
  building: "Preparing approved private builds",
@@ -8,10 +8,16 @@ const labels = {
8
8
  complete: "Installation complete",
9
9
  };
10
10
  /** Presentation only: never writes to stdout or exposes package URLs or paths. */
11
- export function createInstallProgress(stream, quiet) {
12
- let timer;
13
- let current = "", last = "", frame = 0, closed = false;
11
+ export function createInstallProgress(stream, quiet, options = {}) {
12
+ const tty = Boolean(stream.isTTY);
13
+ const color = tty && options.color !== false;
14
+ const dim = (value) => (color ? `\u001b[2m${value}\u001b[0m` : value);
15
+ const green = (value) => color ? `\u001b[32m${value}\u001b[0m` : value;
16
+ let closed = false;
17
+ let currentStage = "";
18
+ let statusLine = false;
14
19
  let lastDownload = 0;
20
+ let lastPlain = "";
15
21
  const write = (text) => {
16
22
  try {
17
23
  stream.write(text);
@@ -20,57 +26,77 @@ export function createInstallProgress(stream, quiet) {
20
26
  /* A display failure cannot alter installation. */
21
27
  }
22
28
  };
23
- const render = () => write(`\r\u001b[2K${["|", "/", "-", "\\"][frame++ % 4]} ${current}`);
29
+ const clearStatus = (lineBreak) => {
30
+ if (!statusLine)
31
+ return;
32
+ write(`\r\u001b[2K${lineBreak ? "\n" : ""}`);
33
+ statusLine = false;
34
+ };
24
35
  return {
25
36
  update(event) {
26
37
  if (quiet || closed)
27
38
  return;
28
39
  const pkg = event.package;
29
- // Registry names are validated elsewhere; strip control characters here
30
- // as presentation defense for direct API callers and hostile metadata.
31
- const label = pkg
40
+ // Strip control characters as presentation defense for direct API
41
+ // callers and hostile registry metadata.
42
+ const packageLabel = pkg
32
43
  ? `${pkg.name}@${pkg.version}`.replace(/[\x00-\x1f\x7f-\x9f]/g, "")
33
44
  : "";
34
45
  if (pkg?.status === "linked") {
35
- if (stream.isTTY)
36
- write("\r\u001b[2K");
37
- write(`✓ ${label} linked\n`);
46
+ if (tty)
47
+ clearStatus(true);
48
+ write(`${green("✓")} ${packageLabel} linked\n`);
38
49
  return;
39
50
  }
40
- const downloading = pkg?.status === "downloading";
41
- if (downloading && pkg.bytes && Date.now() - lastDownload < 100)
42
- return;
43
- if (downloading)
44
- lastDownload = Date.now();
45
- current =
46
- (downloading
47
- ? `Downloading ${label}: ${((pkg.bytes ?? 0) / 1024).toFixed(1)} KiB`
48
- : labels[event.phase]) +
49
- (event.total === undefined
50
- ? ""
51
- : ` (${event.completed ?? 0}/${event.total})`) +
52
- (event.reused === undefined
53
- ? ""
54
- : ` — reused ${event.reused}, downloaded ${event.downloaded ?? 0}`);
55
- if (stream.isTTY) {
56
- render();
57
- if (!timer) {
58
- timer = setInterval(render, 100);
59
- timer.unref();
51
+ const stage = labels[event.phase];
52
+ if (tty) {
53
+ if (stage !== "Installation complete" && stage !== currentStage) {
54
+ clearStatus(true);
55
+ write(`${dim(`› zyrk: ${stage}`)}\n`);
56
+ currentStage = stage;
60
57
  }
58
+ if (pkg?.status === "downloading" || event.phase === "fetching") {
59
+ if (pkg?.status === "downloading" &&
60
+ pkg.bytes &&
61
+ Date.now() - lastDownload < 100)
62
+ return;
63
+ lastDownload = Date.now();
64
+ const status = pkg?.status === "downloading"
65
+ ? ` Downloading ${packageLabel}: ${((pkg.bytes ?? 0) / 1024).toFixed(1)} KiB`
66
+ : ` Sources verified ${event.completed ?? 0}/${event.total ?? 0} · reused ${event.reused ?? 0} · downloaded ${event.downloaded ?? 0}`;
67
+ write(`\r\u001b[2K${dim(status)}`);
68
+ statusLine = true;
69
+ }
70
+ return;
61
71
  }
62
- else if (current !== last)
72
+ const current = (pkg?.status === "downloading"
73
+ ? `Downloading ${packageLabel}: ${((pkg.bytes ?? 0) / 1024).toFixed(1)} KiB`
74
+ : stage) +
75
+ (event.total === undefined
76
+ ? ""
77
+ : ` (${event.completed ?? 0}/${event.total})`) +
78
+ (event.reused === undefined
79
+ ? ""
80
+ : ` — reused ${event.reused}, downloaded ${event.downloaded ?? 0}`);
81
+ if (current !== lastPlain)
63
82
  write(`zyrk: ${current}\n`);
64
- last = current;
83
+ lastPlain = current;
65
84
  },
66
- close() {
85
+ close(completion) {
67
86
  if (closed)
68
87
  return;
69
88
  closed = true;
70
- if (timer)
71
- clearInterval(timer);
72
- if (!quiet && stream.isTTY && current)
73
- write("\r\u001b[2K");
89
+ if (quiet)
90
+ return;
91
+ if (tty) {
92
+ clearStatus(true);
93
+ if (completion) {
94
+ write(`${dim("─".repeat(46))}\n`);
95
+ write(`${green("✓")} ${completion.title}\n`);
96
+ if (completion.detail)
97
+ write(`${dim(completion.detail)}\n`);
98
+ }
99
+ }
74
100
  },
75
101
  };
76
102
  }
@@ -0,0 +1,9 @@
1
+ export type TerminalStyle = {
2
+ isTTY?: boolean;
3
+ color?: boolean;
4
+ };
5
+ /** Render command summaries for people while preserving redirected output. */
6
+ export declare function renderCommandOutput(command: string, output: string, style?: TerminalStyle): string;
7
+ /** Make stable CLI diagnostics easier to scan without changing their content. */
8
+ export declare function renderCommandError(error: string, style?: TerminalStyle): string;
9
+ //# sourceMappingURL=terminal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal.d.ts","sourceRoot":"","sources":["../../src/cli/terminal.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAQjE,8EAA8E;AAC9E,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,KAAK,GAAE,aAAkB,GACxB,MAAM,CA0BR;AAED,iFAAiF;AACjF,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EACb,KAAK,GAAE,aAAkB,GACxB,MAAM,CAUR"}
@@ -0,0 +1,41 @@
1
+ function paint(value, code, style) {
2
+ return style.isTTY && style.color !== false
3
+ ? `\u001b[${code}m${value}\u001b[0m`
4
+ : value;
5
+ }
6
+ /** Render command summaries for people while preserving redirected output. */
7
+ export function renderCommandOutput(command, output, style = {}) {
8
+ if (!style.isTTY || output === "")
9
+ return output;
10
+ let value;
11
+ try {
12
+ value = JSON.parse(output);
13
+ }
14
+ catch {
15
+ value = undefined;
16
+ }
17
+ if (value && typeof value === "object" && !Array.isArray(value)) {
18
+ const summary = { ...value };
19
+ delete summary.schemaVersion;
20
+ delete summary.status;
21
+ delete summary.command;
22
+ const body = JSON.stringify(summary, null, 2).replace(/^(\s*)"([^\"]+)":/gm, (_match, indent, key) => `${indent}${paint(`${key}:`, 2, style)}`);
23
+ return `${paint("✓", 32, style)} zyrk ${command}\n${body}\n`;
24
+ }
25
+ const lines = output.trimEnd().split("\n");
26
+ const first = lines.shift() ?? "";
27
+ const heading = first.startsWith("✓")
28
+ ? `${paint("✓", 32, style)}${first.slice(1)}`
29
+ : `${paint("✓", 32, style)} ${first}`;
30
+ return `${heading}${lines.length ? `\n${lines.join("\n")}` : ""}\n`;
31
+ }
32
+ /** Make stable CLI diagnostics easier to scan without changing their content. */
33
+ export function renderCommandError(error, style = {}) {
34
+ if (!style.isTTY)
35
+ return error;
36
+ const match = /^zyrk: error ([A-Z0-9_]+): ([^\n]+)(?:\n([\s\S]*?))?\n?$/.exec(error);
37
+ if (!match)
38
+ return error;
39
+ const [, code, message, remedy] = match;
40
+ return `${paint("✖", 31, style)} zyrk ${code}\n ${message}${remedy ? `\n ${paint("↳", 33, style)} ${remedy}` : ""}\n`;
41
+ }
@@ -5,9 +5,11 @@ export type DeployCommandOptions = {
5
5
  env: NodeJS.ProcessEnv;
6
6
  stdout: {
7
7
  write(chunk: string): unknown;
8
+ isTTY?: boolean;
8
9
  };
9
10
  stderr: {
10
11
  write(chunk: string): unknown;
12
+ isTTY?: boolean;
11
13
  };
12
14
  json?: boolean;
13
15
  };
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AAwEA,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,CAoajB"}
1
+ {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AAyEA,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,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC3D,MAAM,EAAE;QAAE,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC3D,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AA8EF,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,MAAM,CAAC,CAoajB"}
@@ -10,6 +10,7 @@ import { validateNativeExport } from "./native-export.js";
10
10
  import { workspaceRegistryOccurrences, workspaceContextId, } from "../install/workspace-graph.js";
11
11
  import { findProjectBoundary, resolveStoreRoot } from "../config/paths.js";
12
12
  import { ZyrkError } from "../errors.js";
13
+ import { renderCommandOutput } from "../cli/terminal.js";
13
14
  import { lockedGraph } from "../install/lock.js";
14
15
  import { loadProjectPatches } from "./patch.js";
15
16
  import { currentPlatform } from "../resolve/resolver.js";
@@ -84,7 +85,12 @@ function output(options, result, human) {
84
85
  command: options.command,
85
86
  ...result,
86
87
  }) + "\n"
87
- : human + "\n");
88
+ : renderCommandOutput(options.command, `${human}\n`, {
89
+ ...(options.stdout.isTTY === undefined
90
+ ? {}
91
+ : { isTTY: options.stdout.isTTY }),
92
+ color: options.env.NO_COLOR === undefined && options.env.TERM !== "dumb",
93
+ }));
88
94
  }
89
95
  export async function runDeployCommand(options) {
90
96
  const boundary = (await findWorkspaceRoot(options.cwd)) ??
@@ -5,9 +5,11 @@ export type ProjectCommandOptions = {
5
5
  env: NodeJS.ProcessEnv;
6
6
  stdout: {
7
7
  write(chunk: string): unknown;
8
+ isTTY?: boolean;
8
9
  };
9
10
  stderr: {
10
11
  write(chunk: string): unknown;
12
+ isTTY?: boolean;
11
13
  };
12
14
  json?: boolean;
13
15
  workspaceLockHeld?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../../src/commands/project.ts"],"names":[],"mappings":"AA4DA,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,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;IACf,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AA+LF,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,MAAM,CAAC,CAgRjB"}
1
+ {"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../../src/commands/project.ts"],"names":[],"mappings":"AA6DA,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,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,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC3D,MAAM,EAAE;QAAE,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC3D,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAqMF,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,MAAM,CAAC,CAgRjB"}
@@ -21,6 +21,7 @@ import { randomId } from "../util/ids.js";
21
21
  import { canonicalJson } from "../util/json.js";
22
22
  import { sha512Hex } from "../util/hash.js";
23
23
  import { CLI_VERSION } from "../version.js";
24
+ import { renderCommandOutput } from "../cli/terminal.js";
24
25
  function object(value) {
25
26
  return typeof value === "object" && value !== null && !Array.isArray(value);
26
27
  }
@@ -137,7 +138,12 @@ function emit(options, result, human) {
137
138
  command: options.command,
138
139
  ...result,
139
140
  }) + "\n"
140
- : human + "\n");
141
+ : renderCommandOutput(options.command, `${human}\n`, {
142
+ ...(options.stdout.isTTY === undefined
143
+ ? {}
144
+ : { isTTY: options.stdout.isTTY }),
145
+ color: options.env.NO_COLOR === undefined && options.env.TERM !== "dumb",
146
+ }));
141
147
  }
142
148
  export async function runProjectCommand(options) {
143
149
  const selectors = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zyrk",
3
- "version": "0.7.2",
3
+ "version": "0.7.3",
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",