@savvy-web/cli 3.2.7 → 3.3.0

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 (36) hide show
  1. package/cli/index.js +8 -5
  2. package/commands/changeset/commands/check.js +11 -7
  3. package/commands/changeset/commands/config-validate.js +6 -4
  4. package/commands/changeset/commands/deps-detect.js +4 -4
  5. package/commands/changeset/commands/deps-regen.js +11 -10
  6. package/commands/changeset/commands/init.js +13 -11
  7. package/commands/changeset/commands/lint.js +4 -3
  8. package/commands/changeset/commands/transform.js +7 -5
  9. package/commands/changeset/commands/validate-file.js +8 -6
  10. package/commands/changeset/commands/version.js +5 -4
  11. package/commands/changeset/utils/config-gate.js +3 -3
  12. package/commands/check.js +3 -2
  13. package/commands/clean.js +7 -8
  14. package/commands/commit/check.js +33 -35
  15. package/commands/commit/constants.js +5 -0
  16. package/commands/commit/init.js +8 -6
  17. package/commands/commit/lint.js +2 -1
  18. package/commands/lint/check.js +43 -47
  19. package/commands/lint/init.js +20 -22
  20. package/commands/repos/commands/add.js +8 -9
  21. package/commands/repos/commands/deregister.js +7 -7
  22. package/commands/repos/commands/note.js +7 -8
  23. package/commands/repos/commands/pin.js +11 -13
  24. package/commands/repos/commands/remove.js +13 -15
  25. package/commands/repos/commands/rename.js +10 -12
  26. package/commands/repos/commands/restore.js +12 -14
  27. package/commands/repos/commands/status.js +16 -8
  28. package/commands/repos/commands/sync.js +12 -13
  29. package/index.d.ts +16 -15
  30. package/internal/failure-line.js +39 -0
  31. package/internal/output.js +52 -0
  32. package/internal/version-line.js +19 -0
  33. package/main.d.ts +18 -2
  34. package/main.js +26 -16
  35. package/package.json +5 -3
  36. package/version.js +13 -0
@@ -1,4 +1,6 @@
1
+ import { Output } from "../../../internal/output.js";
1
2
  import { Command, Flag } from "effect/unstable/cli";
3
+ import { CliExit } from "@effected/cli";
2
4
  import { Repos } from "@savvy-web/silk-effects";
3
5
  import { Console, Effect } from "effect";
4
6
 
@@ -46,11 +48,11 @@ const cwdOption = Flag.Directory("cwd").pipe(Flag.withDescription("Repo root to
46
48
  */
47
49
  const runReposStatus = (cwd, json, drift = false) => Effect.gen(function* () {
48
50
  const report = yield* (yield* Repos.ReposManager).status(cwd);
49
- if (!report.clean) process.exitCode = 1;
51
+ if (!report.clean) yield* CliExit.set(1);
50
52
  let driftReport;
51
53
  if (drift) {
52
54
  driftReport = yield* (yield* Repos.ReposDrift).check(cwd);
53
- if (!driftReport.clean) process.exitCode = 1;
55
+ if (!driftReport.clean) yield* CliExit.set(1);
54
56
  }
55
57
  if (json) {
56
58
  const payload = driftReport === void 0 ? report : {
@@ -66,20 +68,26 @@ const runReposStatus = (cwd, json, drift = false) => Effect.gen(function* () {
66
68
  repo.dirty ? "dirty" : void 0,
67
69
  repo.staleNoteIds.length > 0 ? `${repo.staleNoteIds.length} stale notes` : void 0
68
70
  ].filter((f) => f !== void 0);
69
- yield* Effect.log(`${repo.name} @ ${repo.ref}${flags.length > 0 ? ` [${flags.join(", ")}]` : " [ok]"}`);
71
+ yield* flags.length > 0 ? Output.warn(`${repo.name} @ ${repo.ref} [${flags.join(", ")}]`) : Output.ok(`${repo.name} @ ${repo.ref}`);
70
72
  }
71
- if (driftReport !== void 0) for (const item of driftReport.drifts) yield* Effect.log(`${item.name}: ${item.kind} — ${item.detail}`);
73
+ if (driftReport !== void 0) for (const item of driftReport.drifts) yield* Output.fail(`${item.name}: ${item.kind} — ${item.detail}`);
72
74
  }).pipe(Effect.catchTag("ReposConfigError", (error) => {
73
75
  if (error.kind === "missing") {
74
76
  if (json) return Console.log(JSON.stringify({
75
77
  repos: [],
76
78
  clean: true
77
79
  }, null, 2));
78
- return Effect.log("no .repos/config.json — nothing vendored");
80
+ return Output.skip("no .repos/config.json — nothing vendored");
79
81
  }
80
- process.exitCode = 1;
81
- return Effect.log(error.message);
82
- }));
82
+ const report = json ? Console.log(JSON.stringify({
83
+ error: error.message,
84
+ clean: false
85
+ }, null, 2)) : Effect.void;
86
+ return CliExit.set(1).pipe(Effect.andThen(report), Effect.andThen(Effect.logError(error.message)));
87
+ }), Effect.catchTag("GitSubmoduleError", (error) => json ? CliExit.set(1).pipe(Effect.andThen(Console.log(JSON.stringify({
88
+ error: error.message,
89
+ clean: false
90
+ }, null, 2))), Effect.andThen(Effect.logError(error.message))) : Effect.fail(error)));
83
91
  /* v8 ignore start -- CLI registration; handler tested via runReposStatus */
84
92
  const statusCommand = Command.make("status", {
85
93
  json: jsonOption,
@@ -1,4 +1,6 @@
1
+ import { Output } from "../../../internal/output.js";
1
2
  import { Command, Flag } from "effect/unstable/cli";
3
+ import { CliExit } from "@effected/cli";
2
4
  import { Repos } from "@savvy-web/silk-effects";
3
5
  import { Effect } from "effect";
4
6
 
@@ -37,22 +39,19 @@ const cwdOption = Flag.Directory("cwd").pipe(Flag.withDescription("Repo root to
37
39
  */
38
40
  const runReposSync = (cwd) => Effect.gen(function* () {
39
41
  const report = yield* (yield* Repos.ReposManager).sync(cwd);
40
- for (const name of report.clearedLocks) yield* Effect.log(`${name}: cleared stale lock`);
41
- for (const name of report.initialized) yield* Effect.log(`${name}: initialized`);
42
- for (const name of report.sparseApplied) yield* Effect.log(`${name}: sparse-checkout applied`);
43
- for (const name of report.urlSynced) yield* Effect.log(`${name}: url reconciled`);
44
- for (const name of report.registered) yield* Effect.log(`${name}: registered`);
45
- if (report.initialized.length === 0 && report.sparseApplied.length === 0 && report.clearedLocks.length === 0 && report.urlSynced.length === 0 && report.registered.length === 0) yield* Effect.log("all vendored repos up to date");
42
+ for (const name of report.clearedLocks) yield* Output.ok(`${name}: cleared stale lock`);
43
+ for (const name of report.initialized) yield* Output.ok(`${name}: initialized`);
44
+ for (const name of report.sparseApplied) yield* Output.ok(`${name}: sparse-checkout applied`);
45
+ for (const name of report.urlSynced) yield* Output.ok(`${name}: url reconciled`);
46
+ for (const name of report.registered) yield* Output.ok(`${name}: registered`);
47
+ if (report.initialized.length === 0 && report.sparseApplied.length === 0 && report.clearedLocks.length === 0 && report.urlSynced.length === 0 && report.registered.length === 0) yield* Output.ok("all vendored repos up to date");
46
48
  }).pipe(Effect.catchTag("ReposConfigError", (error) => {
47
- if (error.kind === "missing") return Effect.log("no .repos/config.json — nothing vendored");
48
- process.exitCode = 1;
49
- return Effect.log(error.message);
49
+ if (error.kind === "missing") return Output.skip("no .repos/config.json — nothing vendored");
50
+ return CliExit.set(1).pipe(Effect.andThen(Effect.logError(error.message)));
50
51
  }), Effect.catchTag("GitSubmoduleError", (error) => {
51
- process.exitCode = 1;
52
- return Effect.log(error.message);
52
+ return CliExit.set(1).pipe(Effect.andThen(Effect.logError(error.message)));
53
53
  }), Effect.catchTag("ReposLockdownError", (error) => {
54
- process.exitCode = 1;
55
- return Effect.log(error.message);
54
+ return CliExit.set(1).pipe(Effect.andThen(Effect.logError(error.message)));
56
55
  }));
57
56
  /* v8 ignore start -- CLI registration; handler tested via runReposSync */
58
57
  const syncCommand = Command.make("sync", { cwd: cwdOption }, ({ cwd }) => runReposSync(cwd)).pipe(Command.withDescription("Reconcile vendored submodules with the manifest: init missing, apply sparse, clear locks"));
package/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
+ import { CliExit } from "@effected/cli";
1
2
  import { BiomeSchemaSync, ChangesetConfigReader, Changesets, ConfigDiscovery, Repos } from "@savvy-web/silk-effects";
2
- import { Cause, Effect, FileSystem, Path } from "effect";
3
+ import { Cause, Effect, FileSystem, Path, Stdio } from "effect";
3
4
  import { Command } from "effect/unstable/cli";
4
5
  import { ChildProcessSpawner } from "effect/unstable/process";
5
6
  import { Git } from "@effected/git";
@@ -13,14 +14,14 @@ import { JsoncParseError } from "@effected/jsonc";
13
14
  * Run the check validation pipeline on all changeset files in `dir`.
14
15
  *
15
16
  * Groups lint messages by file and logs a human-readable summary. Sets
16
- * `process.exitCode = 1` when one or more errors are found.
17
+ * exit code 1 through `CliExit.set` when one or more errors are found.
17
18
  *
18
19
  * @param dir - Path to the changeset directory (resolved relative to cwd)
19
20
  * @returns An Effect that performs validation and logs results
20
21
  *
21
22
  * @internal
22
23
  */
23
- export declare function runChangesetCheck(dir: string): Effect.Effect<void, Error>;
24
+ export declare function runChangesetCheck(dir: string): Effect.Effect<void, Error, CliExit | Stdio.Stdio>;
24
25
  //#endregion
25
26
  //#region src/commands/changeset/commands/init.d.ts
26
27
  /**
@@ -39,7 +40,7 @@ export declare function runChangesetInit(opts: {
39
40
  quiet: boolean;
40
41
  skipMarkdownlint: boolean;
41
42
  check: boolean;
42
- }): Effect.Effect<void, never, WorkspaceRoot | Git>;
43
+ }): Effect.Effect<void, never, WorkspaceRoot | Git | CliExit | Stdio.Stdio>;
43
44
  //#endregion
44
45
  //#region src/commands/changeset/index.d.ts
45
46
  /**
@@ -57,7 +58,7 @@ export declare function runChangesetInit(opts: {
57
58
  * `Command.withSubcommands` propagates each subcommand's requirements up into
58
59
  * the group's `R`, which the root assembly discharges via `AppLive`.
59
60
  */
60
- export declare const changesetCommand: Command.Command<"changeset", Record<string, never>, Record<string, never>, Changesets.ConfigurationError | Error | Changesets.ReleasePlanError | Cause.UnknownError | Changesets.DepsRegenPlanError, ChildProcessSpawner.ChildProcessSpawner | Changesets.ConfigInspector | FileSystem.FileSystem | Path.Path | Changesets.ReleasePlanner>;
61
+ export declare const changesetCommand: Command.Command<"changeset", Record<string, never>, Record<string, never>, Changesets.ConfigurationError | Error | Changesets.ReleasePlanError | Cause.UnknownError | Changesets.DepsRegenPlanError, ChildProcessSpawner.ChildProcessSpawner | Changesets.ConfigInspector | FileSystem.FileSystem | Path.Path | Changesets.ReleasePlanner | CliExit | Stdio.Stdio>;
61
62
  //#endregion
62
63
  //#region src/commands/check.d.ts
63
64
  /**
@@ -88,7 +89,7 @@ export declare function runCheck<EChangeset, RChangeset, ECommit, RCommit, ELint
88
89
  export declare const checkCommand: Command.Command<"check", {
89
90
  readonly changesetDir: string;
90
91
  readonly quiet: boolean;
91
- }, {}, Error | import("@effected/jsonc").JsoncParseError | import("effect/PlatformError").PlatformError | import("@effected/templates").SectionFileError | import("@effected/templates").SectionParseError, import("@savvy-web/silk-effects").ChangesetConfigReader | import("@savvy-web/silk-effects").ConfigDiscovery | import("effect/FileSystem").FileSystem | import("@effected/templates").ManagedSection | import("@effected/workspaces").PublishabilityDetector | import("@effected/commands").ToolDiscovery | import("@effected/workspaces").WorkspaceDiscovery>;
92
+ }, {}, Error | import("@effected/jsonc").JsoncParseError | import("effect/PlatformError").PlatformError | import("@effected/templates").SectionFileError | import("@effected/templates").SectionParseError, import("@savvy-web/silk-effects").ChangesetConfigReader | import("@effected/cli").CliExit | import("@savvy-web/silk-effects").ConfigDiscovery | import("effect/FileSystem").FileSystem | import("@effected/templates").ManagedSection | import("@effected/workspaces").PublishabilityDetector | import("effect/Stdio").Stdio | import("@effected/commands").ToolDiscovery | import("@effected/workspaces").WorkspaceDiscovery>;
92
93
  //#endregion
93
94
  //#region src/commands/commit/check.d.ts
94
95
  /**
@@ -101,7 +102,7 @@ export declare const checkCommand: Command.Command<"check", {
101
102
  *
102
103
  * @internal
103
104
  */
104
- export declare function runCommitCheck(): Effect.Effect<void, SectionParseError | SectionFileError | PlatformError, ManagedSection | FileSystem.FileSystem | ChangesetConfigReader | PublishabilityDetector | WorkspaceDiscovery>;
105
+ export declare function runCommitCheck(): Effect.Effect<void, SectionParseError | SectionFileError | PlatformError, ManagedSection | FileSystem.FileSystem | ChangesetConfigReader | PublishabilityDetector | WorkspaceDiscovery | Stdio.Stdio>;
105
106
  //#endregion
106
107
  //#region src/commands/commit/init.d.ts
107
108
  /**
@@ -118,7 +119,7 @@ export declare function runCommitCheck(): Effect.Effect<void, SectionParseError
118
119
  export declare function runCommitInit(opts: {
119
120
  force: boolean;
120
121
  config: string;
121
- }): Effect.Effect<void, Error | SectionParseError | SectionRenderError | SectionFileError | PlatformError, ManagedSection | FileSystem.FileSystem>;
122
+ }): Effect.Effect<void, Error | SectionParseError | SectionRenderError | SectionFileError | PlatformError, ManagedSection | FileSystem.FileSystem | Stdio.Stdio>;
122
123
  //#endregion
123
124
  //#region src/commands/commit/index.d.ts
124
125
  /**
@@ -129,7 +130,7 @@ export declare function runCommitInit(opts: {
129
130
  * errors from Effect's internal types. Task B7 should import and use this directly
130
131
  * as `Command.withSubcommands([commitCommand])` — the cast is for declaration emit only.
131
132
  */
132
- export declare const commitCommand: Command.Command<"commit", {}, {}, import("effect/unstable/cli/CliError").UserError, import("effect/unstable/process/ChildProcessSpawner").ChildProcessSpawner | import("@effected/git").Git>;
133
+ export declare const commitCommand: Command.Command<"commit", {}, {}, import("effect/unstable/cli/CliError").UserError, import("effect/unstable/process/ChildProcessSpawner").ChildProcessSpawner | import("@effected/git").Git | import("effect/Stdio").Stdio>;
133
134
  //#endregion
134
135
  //#region src/commands/init.d.ts
135
136
  /**
@@ -160,7 +161,7 @@ export declare const initCommand: Command.Command<"init", {
160
161
  readonly commitConfig: string;
161
162
  readonly lintConfig: string;
162
163
  readonly lintPreset: "minimal" | "silk" | "standard";
163
- }, {}, Error | import("effect/PlatformError").PlatformError | import("@effected/templates").SectionFileError | import("@effected/templates").SectionParseError | import("@effected/templates").SectionRenderError, import("@savvy-web/silk-effects").BiomeSchemaSync | import("effect/FileSystem").FileSystem | import("@effected/git").Git | import("@effected/templates").ManagedSection | import("@effected/workspaces").WorkspaceDiscovery | import("@effected/workspaces").WorkspaceRoot>;
164
+ }, {}, Error | import("effect/PlatformError").PlatformError | import("@effected/templates").SectionFileError | import("@effected/templates").SectionParseError | import("@effected/templates").SectionRenderError, import("@savvy-web/silk-effects").BiomeSchemaSync | import("@effected/cli").CliExit | import("effect/FileSystem").FileSystem | import("@effected/git").Git | import("@effected/templates").ManagedSection | import("effect/Stdio").Stdio | import("@effected/workspaces").WorkspaceDiscovery | import("@effected/workspaces").WorkspaceRoot>;
164
165
  //#endregion
165
166
  //#region src/commands/lint/check.d.ts
166
167
  /**
@@ -176,7 +177,7 @@ export declare const initCommand: Command.Command<"init", {
176
177
  */
177
178
  export declare function runLintCheck(opts: {
178
179
  quiet: boolean;
179
- }): Effect.Effect<void, JsoncParseError | SectionParseError | SectionFileError | PlatformError, ManagedSection | FileSystem.FileSystem | ToolDiscovery | ConfigDiscovery>;
180
+ }): Effect.Effect<void, JsoncParseError | SectionParseError | SectionFileError | PlatformError, ManagedSection | FileSystem.FileSystem | ToolDiscovery | ConfigDiscovery | Stdio.Stdio>;
180
181
  //#endregion
181
182
  //#region src/commands/lint/init.d.ts
182
183
  /**
@@ -194,7 +195,7 @@ export declare function runLintInit(opts: {
194
195
  force: boolean;
195
196
  config: string;
196
197
  preset: "minimal" | "standard" | "silk";
197
- }): Effect.Effect<void, Error | SectionParseError | SectionRenderError | SectionFileError | PlatformError, ManagedSection | FileSystem.FileSystem | BiomeSchemaSync | WorkspaceDiscovery>;
198
+ }): Effect.Effect<void, Error | SectionParseError | SectionRenderError | SectionFileError | PlatformError, ManagedSection | FileSystem.FileSystem | BiomeSchemaSync | WorkspaceDiscovery | Stdio.Stdio>;
198
199
  //#endregion
199
200
  //#region src/commands/lint/index.d.ts
200
201
  /**
@@ -213,7 +214,7 @@ export declare const lintCommand: Command.Command<"lint", {}, {}, import("@effec
213
214
  *
214
215
  * @internal
215
216
  */
216
- export declare const runReposStatus: (cwd: string, json: boolean, drift?: boolean) => Effect.Effect<void, Repos.GitSubmoduleError, Repos.ReposDrift | Repos.ReposManager>;
217
+ export declare const runReposStatus: (cwd: string, json: boolean, drift?: boolean) => Effect.Effect<void, Repos.GitSubmoduleError, CliExit | Repos.ReposDrift | Repos.ReposManager | Stdio.Stdio>;
217
218
  //#endregion
218
219
  //#region src/commands/repos/commands/sync.d.ts
219
220
  /**
@@ -221,7 +222,7 @@ export declare const runReposStatus: (cwd: string, json: boolean, drift?: boolea
221
222
  *
222
223
  * @internal
223
224
  */
224
- export declare const runReposSync: (cwd: string) => Effect.Effect<void, never, Repos.ReposManager>;
225
+ export declare const runReposSync: (cwd: string) => Effect.Effect<void, never, CliExit | Repos.ReposManager | Stdio.Stdio>;
225
226
  //#endregion
226
227
  //#region src/commands/repos/index.d.ts
227
228
  /**
@@ -252,6 +253,6 @@ export declare const runReposSync: (cwd: string) => Effect.Effect<void, never, R
252
253
  * or (under `--drift`) `Repos.ReposDrift.check` is the sole channel that
253
254
  * escapes uncaught, so it is the only member left in this union.
254
255
  */
255
- export declare const reposCommand: Command.Command<"repos", Record<string, never>, Record<string, never>, Repos.GitSubmoduleError, Repos.ReposManager | Repos.ReposDrift>;
256
+ export declare const reposCommand: Command.Command<"repos", Record<string, never>, Record<string, never>, Repos.GitSubmoduleError, Repos.ReposManager | Repos.ReposDrift | CliExit | Stdio.Stdio>;
256
257
  //#endregion
257
258
  //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,39 @@
1
+ //#region src/internal/failure-line.ts
2
+ /**
3
+ * How a failure that reaches `CliRuntime.main` reads on stderr.
4
+ *
5
+ * @packageDocumentation
6
+ */
7
+ const HIDDEN = /* @__PURE__ */ new Set([
8
+ "_tag",
9
+ "message",
10
+ "name",
11
+ "stack",
12
+ "cause"
13
+ ]);
14
+ /**
15
+ * Renders a failure for `CliRuntime.main`'s `render` option.
16
+ *
17
+ * @remarks
18
+ * The kit's default is `String(error)`, which prints a `Data.TaggedError`
19
+ * with no `message` as its bare tag and drops the fields that say what went
20
+ * wrong (`CleanError`, with the `reason` lost). This prefers the error's own
21
+ * message, then its tag with its fields, then `String`.
22
+ *
23
+ * @internal
24
+ */
25
+ var FailureLine = class {
26
+ constructor() {}
27
+ static render = (error) => {
28
+ if (error instanceof Error && error.message !== "") return error.message;
29
+ if (typeof error === "object" && error !== null && "_tag" in error) {
30
+ const fields = Object.entries(error).filter(([key]) => !HIDDEN.has(key)).map(([key, value]) => `${key}: ${typeof value === "string" ? value : JSON.stringify(value)}`);
31
+ const tag = String(error._tag);
32
+ return fields.length > 0 ? `${tag}: ${fields.join(", ")}` : tag;
33
+ }
34
+ return String(error);
35
+ };
36
+ };
37
+
38
+ //#endregion
39
+ export { FailureLine };
@@ -0,0 +1,52 @@
1
+ import { CliColor } from "@effected/cli";
2
+ import { Console, Effect } from "effect";
3
+
4
+ //#region src/internal/output.ts
5
+ /**
6
+ * Human-readable command output.
7
+ *
8
+ * @packageDocumentation
9
+ */
10
+ const paint = (on, code, text) => on ? `\u001b[${code}m${text}\u001b[0m` : text;
11
+ const glyphLine = (code, glyph, text) => Effect.flatMap(CliColor.enabled, (on) => Console.log(`${paint(on, code, glyph)} ${text}`));
12
+ /**
13
+ * The result a person ran a command to see, written to stdout.
14
+ *
15
+ * @remarks
16
+ * Every line goes through `Console.log`, so stdout carries a command's product
17
+ * and nothing else; progress and diagnostics stay on `Effect.log*`, which the
18
+ * CLI logger sends to stderr. Colour follows `CliColor.enabled` — off when
19
+ * stdout is not a terminal or `NO_COLOR` is set — and only ever tints the
20
+ * glyph or heading, so the text reads the same piped.
21
+ *
22
+ * @internal
23
+ */
24
+ var Output = class {
25
+ constructor() {}
26
+ /** A passing item: `✓ text`. */
27
+ static ok = (text) => glyphLine("32", "✓", text);
28
+ /** An item that passed with a caveat: `⚠ text`. */
29
+ static warn = (text) => glyphLine("33", "⚠", text);
30
+ /** A finding — still output, not a crash: `✗ text`. */
31
+ static fail = (text) => glyphLine("31", "✗", text);
32
+ /** An item that is absent or not applicable — neither a pass nor a finding: `• text`, dimmed. */
33
+ static skip = (text) => glyphLine("2", "•", text);
34
+ /** A section title, bold on a terminal. */
35
+ static heading = (text) => Effect.flatMap(CliColor.enabled, (on) => Console.log(paint(on, "1", text)));
36
+ /** Supporting detail under the line before it, indented and dimmed on a terminal. */
37
+ static detail = (text) => Effect.flatMap(CliColor.enabled, (on) => Console.log(` ${paint(on, "2", text)}`));
38
+ /** A line printed as given — a blank separator, or a block already formatted. */
39
+ static line = (text) => Console.log(text);
40
+ /** `3 ok · 1 warning · 2 failed`, zero parts left out; `nothing to do` when every count is zero. */
41
+ static summary = (counts) => {
42
+ const parts = [
43
+ counts.ok ? `${counts.ok} ok` : void 0,
44
+ counts.warn ? `${counts.warn} ${counts.warn === 1 ? "warning" : "warnings"}` : void 0,
45
+ counts.fail ? `${counts.fail} failed` : void 0
46
+ ].filter((part) => part !== void 0);
47
+ return Console.log(parts.length === 0 ? "nothing to do" : parts.join(" · "));
48
+ };
49
+ };
50
+
51
+ //#endregion
52
+ export { Output };
@@ -0,0 +1,19 @@
1
+ import { distributionSuffix } from "@effected/engine";
2
+
3
+ //#region src/internal/version-line.ts
4
+ /**
5
+ * Renders the `savvy --version` line: the same `<name> v<version>` shape
6
+ * `effect/unstable/cli`'s default formatter prints, plus the
7
+ * `" via <carrier> <version>"` suffix when the bin was installed through a
8
+ * carrier such as `@savvy-web/silk`.
9
+ *
10
+ * @internal
11
+ */
12
+ var VersionLine = class {
13
+ constructor() {}
14
+ /** `savvy v1.2.3`, or `savvy v1.2.3 via @savvy-web/silk 4.5.6`. */
15
+ static format = (name, version, distribution) => `${name} v${version}${distributionSuffix(distribution)}`;
16
+ };
17
+
18
+ //#endregion
19
+ export { VersionLine };
package/main.d.ts CHANGED
@@ -1,7 +1,23 @@
1
+ import { Distribution } from "@effected/engine";
1
2
  //#region src/main.d.ts
2
3
  /**
3
- * Bootstrap and run the `savvy` CLI application.
4
+ * Options for {@link main}.
5
+ *
6
+ * @public
4
7
  */
5
- export declare const main: () => void;
8
+ export interface MainOptions {
9
+ /**
10
+ * The carrier this bin was installed through, for example
11
+ * `{ name: "@savvy-web/silk", version }`; `--version` names it. Absent for
12
+ * a direct install.
13
+ */
14
+ readonly distribution?: Distribution | undefined;
15
+ }
16
+ /**
17
+ * Bootstrap and run the `savvy` CLI application. Owns the process.
18
+ *
19
+ * @public
20
+ */
21
+ export declare const main: (options?: MainOptions) => void;
6
22
  //#endregion
7
23
  //# sourceMappingURL=main.d.ts.map
package/main.js CHANGED
@@ -1,7 +1,12 @@
1
- import { AppLive, rootCommand } from "./cli/index.js";
1
+ import { AppLive, CliPlatform, rootCommand } from "./cli/index.js";
2
+ import { FailureLine } from "./internal/failure-line.js";
3
+ import { VersionLine } from "./internal/version-line.js";
4
+ import { CLI_VERSION } from "./version.js";
2
5
  import { Command } from "effect/unstable/cli";
3
- import { Effect } from "effect";
6
+ import { CliColor, CliRuntime } from "@effected/cli";
7
+ import { Effect, Layer, Option } from "effect";
4
8
  import { NodeRuntime } from "@effect/platform-node";
9
+ import { CurrentDistribution } from "@effected/engine";
5
10
 
6
11
  //#region src/main.ts
7
12
  /**
@@ -10,24 +15,29 @@ import { NodeRuntime } from "@effect/platform-node";
10
15
  *
11
16
  * @remarks
12
17
  * Assembles the `Command.run` Effect over `rootCommand`, provides the merged
13
- * `AppLive` runtime layer stack from `./cli/index.js`, and hands execution to
14
- * `NodeRuntime.runMain`, whose default reporting covers defects (the v3
15
- * `Cause.defects` wrapper is gone). No type casts: the layer graph is
16
- * validated by the compiler.
18
+ * `AppLive` stack and the carrier-aware version formatter, and hands the
19
+ * program to `@effected/cli`'s `CliRuntime.main`, which provides the platform
20
+ * and the kit-default CLI logger (every log line on stderr), reports failures
21
+ * through that logger, applies the `CliExit` code a command set, and exits
22
+ * `64` on a usage error.
23
+ * No type casts: the layer graph is validated by the compiler.
17
24
  *
18
- * @internal
25
+ * @packageDocumentation
19
26
  */
20
- /* v8 ignore start -- bootstrap; commands tested individually */
27
+ /* v8 ignore start -- bootstrap; commands tested individually, the bin by __test__/e2e/bin.e2e.test.ts */
21
28
  /**
22
- * CLI application: reads argv from the Stdio service provided by NodeServices.
23
- * (v4's `Command.run` takes only `version` — the name comes from the root command.)
24
- */
25
- const cli = Command.run(rootCommand, { version: "3.2.7" });
26
- /**
27
- * Bootstrap and run the `savvy` CLI application.
29
+ * Bootstrap and run the `savvy` CLI application. Owns the process.
30
+ *
31
+ * @public
28
32
  */
29
- const main = () => {
30
- NodeRuntime.runMain(cli.pipe(Effect.provide(AppLive)));
33
+ const main = (options = {}) => {
34
+ const distribution = Option.fromNullishOr(options.distribution);
35
+ const VersionFormatterLive = CliColor.formatterLayer({ formatVersion: (name, version) => VersionLine.format(name, version, distribution) });
36
+ const program = Command.run(rootCommand, { version: CLI_VERSION }).pipe(Effect.provide(Layer.merge(AppLive, VersionFormatterLive)), Effect.provideService(CurrentDistribution, distribution));
37
+ NodeRuntime.runMain(CliRuntime.main(program, {
38
+ platform: CliPlatform,
39
+ render: FailureLine.render
40
+ }));
31
41
  };
32
42
  /* v8 ignore stop */
33
43
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@savvy-web/cli",
3
- "version": "3.2.7",
3
+ "version": "3.3.0",
4
4
  "private": false,
5
5
  "description": "The savvy CLI — unified commit, changeset, and lint commands for the Silk Suite",
6
6
  "homepage": "https://github.com/savvy-web/systems/tree/main/packages/cli",
@@ -37,13 +37,15 @@
37
37
  },
38
38
  "dependencies": {
39
39
  "@effect/platform-node": "4.0.0-rc.117",
40
+ "@effected/cli": "^0.8.0",
40
41
  "@effected/commands": "^0.9.0",
42
+ "@effected/engine": "^0.1.0",
41
43
  "@effected/git": "^0.17.0",
42
44
  "@effected/jsonc": "^0.13.0",
43
45
  "@effected/templates": "^0.8.0",
44
- "@effected/workspaces": "^0.25.0",
46
+ "@effected/workspaces": "^0.26.0",
45
47
  "@effected/yaml": "^0.17.0",
46
- "@savvy-web/silk-effects": "9.1.2",
48
+ "@savvy-web/silk-effects": "9.1.3",
47
49
  "effect": "4.0.0-rc.117"
48
50
  }
49
51
  }
package/version.js ADDED
@@ -0,0 +1,13 @@
1
+ //#region src/version.ts
2
+ /**
3
+ * The package version, replaced at build time by the bundler's
4
+ * `process.env.__PACKAGE_VERSION__` define; `0.0.0` in an unbuilt source run.
5
+ * Kept in its own module so the process read stays at the front end's edge.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+ /** @internal */
10
+ const CLI_VERSION = "3.3.0";
11
+
12
+ //#endregion
13
+ export { CLI_VERSION };