@savvy-web/cli 3.5.0 → 3.6.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 (43) hide show
  1. package/cli/index.js +19 -3
  2. package/commands/changeset/commands/check.js +82 -21
  3. package/commands/changeset/commands/config-validate.js +9 -6
  4. package/commands/changeset/commands/deps-detect.js +2 -2
  5. package/commands/changeset/commands/deps-regen.js +94 -48
  6. package/commands/changeset/commands/init.js +56 -23
  7. package/commands/changeset/commands/transform.js +82 -8
  8. package/commands/changeset/commands/validate-file.js +12 -11
  9. package/commands/changeset/commands/version.js +48 -17
  10. package/commands/changeset/index.js +3 -3
  11. package/commands/changeset/utils/config-gate.js +12 -10
  12. package/commands/check-section.js +34 -0
  13. package/commands/check.js +48 -39
  14. package/commands/clean/progress.js +71 -0
  15. package/commands/clean/view.js +26 -0
  16. package/commands/clean.js +113 -50
  17. package/commands/commit/check.js +80 -77
  18. package/commands/commit/init.js +21 -10
  19. package/commands/commit/lint.js +2 -2
  20. package/commands/init.js +122 -34
  21. package/commands/lint/check.js +126 -171
  22. package/commands/lint/init.js +52 -33
  23. package/commands/lint/text.js +28 -13
  24. package/commands/repos/commands/add.js +14 -17
  25. package/commands/repos/commands/deregister.js +37 -18
  26. package/commands/repos/commands/note.js +78 -26
  27. package/commands/repos/commands/pin.js +39 -24
  28. package/commands/repos/commands/remove.js +30 -56
  29. package/commands/repos/commands/rename.js +32 -23
  30. package/commands/repos/commands/restore.js +42 -56
  31. package/commands/repos/commands/status.js +43 -47
  32. package/commands/repos/commands/sync.js +11 -40
  33. package/commands/repos/index.js +7 -10
  34. package/commands/repos/shared.js +129 -0
  35. package/index.d.ts +162 -61
  36. package/internal/command-error.js +60 -0
  37. package/internal/confirm.js +52 -0
  38. package/internal/failure-line.js +26 -12
  39. package/internal/report.js +101 -0
  40. package/main.js +23 -16
  41. package/package.json +17 -12
  42. package/version.js +1 -1
  43. package/internal/output.js +0 -52
@@ -0,0 +1,129 @@
1
+ import { Report } from "../../internal/report.js";
2
+ import { FailureLine } from "../../internal/failure-line.js";
3
+ import { CommandError } from "../../internal/command-error.js";
4
+ import { CliError } from "effect/cli";
5
+ import { CliInteractive } from "@effected/cli";
6
+ import { Repos } from "@savvy-web/silk-effects";
7
+ import { Effect } from "effect";
8
+ import { CliUi, MultiSelect, Select, TextInput } from "@effected/cli/ui";
9
+
10
+ //#region src/commands/repos/shared.ts
11
+ /** What a person can do about each repos failure. */
12
+ const hints = {
13
+ ReposConfigError: "fix .repos/config.json (it is versioned: git diff / git checkout it), then run `savvy repos status`",
14
+ GitSubmoduleError: "run `savvy repos status --drift` to see what git disagrees with, then `savvy repos sync` to repair",
15
+ RepoNotFoundError: "run `savvy repos status` to list the vendored repos",
16
+ NoteNotFoundError: "the note ids are under the repo's `notes` in .repos/config.json",
17
+ ReposLockdownError: "run `savvy repos sync` to re-assert the read-only lockdown on the vendored trees"
18
+ };
19
+ /**
20
+ * Helpers shared by the `savvy repos` subcommands.
21
+ *
22
+ * @internal
23
+ */
24
+ var ReposCli = class ReposCli {
25
+ constructor() {}
26
+ /** The line every mutating repos command prints, exit 0, when there is no manifest yet. */
27
+ static nothingVendored = Report.print([Report.skip("no .repos/config.json — nothing vendored")]);
28
+ /**
29
+ * `error` as the {@link CommandError} a command fails with: `could not <action>`,
30
+ * the error's own description (a git failure's lines kept apart), and a hint
31
+ * for its kind. `hint` overrides the kind's own.
32
+ */
33
+ static toCommandError = (action, error, hint) => new CommandError({
34
+ message: `could not ${action}`,
35
+ detail: FailureLine.describe(error).split("\n"),
36
+ hint: hint ?? hints[error._tag],
37
+ cause: error
38
+ });
39
+ /** Fail with {@link ReposCli.toCommandError}; for `Effect.catchTags`/`catchTag` arms. */
40
+ static fail = (action, hint) => (error) => Effect.fail(ReposCli.toCommandError(action, error, hint));
41
+ /**
42
+ * A missing required positional exactly as core's parser reports one: a
43
+ * `ShowHelp` carrying `MissingArgument`, which `Command.runWith` renders as
44
+ * the error followed by the subcommand's help (on stderr under
45
+ * `helpOnUsageError: "stderr"`) and `CliRuntime.main` exits `64` on.
46
+ *
47
+ * @param command - The path below `savvy repos`, e.g. `["pin"]` or `["note", "add"]`.
48
+ */
49
+ static missingArgument = (command, argument) => new CliError.ShowHelp({
50
+ commandPath: [
51
+ "savvy",
52
+ "repos",
53
+ ...command
54
+ ],
55
+ errors: [new CliError.MissingArgument({ argument })]
56
+ });
57
+ /**
58
+ * The vendored repos (name, ref, and whether dirty), read from
59
+ * `ReposManager.status` for a picker.
60
+ */
61
+ static vendored = (cwd) => Effect.gen(function* () {
62
+ return (yield* (yield* Repos.ReposManager).status(cwd)).repos;
63
+ });
64
+ /**
65
+ * The repo name a command was given, or — when it was left off — one picked
66
+ * from the vendored repos on a `Select` when a person is at the terminal.
67
+ *
68
+ * @remarks
69
+ * Not interactive (an agent, CI, a pipe), a missing name stays what it was
70
+ * when the argument was required: a usage error, exit `64`, and the manifest
71
+ * is never read. With nothing vendored there is nothing to pick from, which
72
+ * is also the usage error. Esc on the picker is the kit's `Cancelled` (exit
73
+ * `130`).
74
+ */
75
+ static nameOrPick = (cwd, given, options) => Effect.gen(function* () {
76
+ if (given !== void 0) return given;
77
+ if (!(yield* CliInteractive)) return yield* Effect.fail(ReposCli.missingArgument(options.command, options.argument));
78
+ const repos = yield* ReposCli.vendored(cwd);
79
+ if (repos.length === 0) return yield* Effect.fail(ReposCli.missingArgument(options.command, options.argument));
80
+ return yield* CliUi.prompt(Select.screen({
81
+ message: options.message,
82
+ choices: repos.map((repo) => ({
83
+ label: repo.name,
84
+ value: repo.name,
85
+ detail: `@ ${repo.ref}`
86
+ }))
87
+ })).pipe(Effect.catchTag("NotInteractive", () => Effect.fail(ReposCli.missingArgument(options.command, options.argument))));
88
+ });
89
+ /**
90
+ * A positional the command was given, or — when it was left off at a
91
+ * terminal — typed on a `TextInput`; a usage error anywhere else.
92
+ *
93
+ * @remarks
94
+ * For the positional that follows an optional repo name (`pin <name> <ref>`):
95
+ * core binds a lone argument to the first optional positional, so once the
96
+ * name is optional the one after it must be optional too, and asked for here.
97
+ */
98
+ static textOrAsk = (given, options) => Effect.gen(function* () {
99
+ if (given !== void 0) return given;
100
+ if (!(yield* CliInteractive)) return yield* Effect.fail(ReposCli.missingArgument(options.command, options.argument));
101
+ return yield* CliUi.prompt(TextInput.screen({
102
+ message: options.message,
103
+ ...options.initial === void 0 ? {} : { initial: options.initial },
104
+ validate: (value) => value.trim() === "" ? `${options.argument} is required` : void 0
105
+ })).pipe(Effect.map((value) => value.trim()), Effect.catchTag("NotInteractive", () => Effect.fail(ReposCli.missingArgument(options.command, options.argument))));
106
+ });
107
+ /**
108
+ * Which dirty repos to restore, asked on a `MultiSelect` with every dirty
109
+ * repo preselected. Only called when a person is at the terminal.
110
+ */
111
+ static pickDirty = (names) => {
112
+ const items = names.map((name) => ({
113
+ key: name,
114
+ label: name,
115
+ value: name,
116
+ selected: true
117
+ }));
118
+ return CliUi.prompt(MultiSelect.screen({
119
+ message: "Restore which repos? (uncommitted edits are lost)",
120
+ sections: [{
121
+ title: "dirty",
122
+ items
123
+ }]
124
+ }), { otherwise: names }).pipe(Effect.catchTag("NotInteractive", () => Effect.succeed(names)));
125
+ };
126
+ };
127
+
128
+ //#endregion
129
+ export { ReposCli };
package/index.d.ts CHANGED
@@ -1,37 +1,130 @@
1
- import { CliExit } from "@effected/cli";
1
+ import { Block, Cancelled, CliDoc, CliDocSource, CliExit, CliLinks, CliTheme, Document } from "@effected/cli";
2
2
  import { BiomeSchemaSync, ChangesetConfigReader, Changesets, ConfigDiscovery, Repos } from "@savvy-web/silk-effects";
3
- import { Cause, Effect, FileSystem, Path, Stdio } from "effect";
4
- import { Command } from "effect/cli";
3
+ import { Cause, Effect, FileSystem, Path } from "effect";
4
+ import { CliError, Command } from "effect/cli";
5
5
  import { ChildProcessSpawner } from "effect/process";
6
+ import { Audience, TerminalEnv } from "@effected/env";
6
7
  import { Git } from "@effected/git";
7
8
  import { PublishabilityDetector, WorkspaceDiscovery, WorkspaceRoot } from "@effected/workspaces";
8
9
  import { ManagedSection, SectionFileError, SectionParseError, SectionRenderError } from "@effected/templates";
9
10
  import { PlatformError } from "effect/PlatformError";
10
11
  import { ToolDiscovery } from "@effected/commands";
11
- import { JsoncParseError } from "@effected/jsonc";
12
- //#region src/commands/changeset/commands/check.d.ts
12
+ import { JsoncModificationError, JsoncParseError } from "@effected/jsonc";
13
+ //#region src/internal/command-error.d.ts
14
+ /** What {@link CommandError} carries besides its tag. */
15
+ interface CommandErrorFields {
16
+ /** One line saying what failed, e.g. `could not read .changeset/config.json`. */
17
+ readonly message: string;
18
+ /** Supporting lines under the message: what was found, which files, the underlying error. */
19
+ readonly detail?: ReadonlyArray<string> | undefined;
20
+ /** What to do about it, drawn as a tip callout. */
21
+ readonly hint?: string | undefined;
22
+ /** The error this one stands for, kept as `Error.cause` for diagnostics; never drawn. */
23
+ readonly cause?: unknown;
24
+ }
25
+ /** Options for {@link CommandError.from}. */
26
+ interface CommandErrorFromOptions {
27
+ /** One line saying what failed, in the command's words. */
28
+ readonly message: string;
29
+ /** Lines drawn before the foreign error's own description. */
30
+ readonly detail?: ReadonlyArray<string> | undefined;
31
+ /** What to do about it. */
32
+ readonly hint?: string | undefined;
33
+ }
34
+ declare const CommandError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
35
+ readonly _tag: "CommandError";
36
+ } & Readonly<A>;
37
+ /**
38
+ * A command's failure that draws itself: a failure status line, indented
39
+ * detail lines, and an optional remediation as a tip callout.
40
+ *
41
+ * @remarks
42
+ * Replaces the `Effect.logError(...)` + `CliExit.set(1)` pair: fail with it
43
+ * and `CliRuntime.main` reports it on stderr through `FailureLine.render`,
44
+ * which hands an error implementing `CliDoc` the kit's report of its
45
+ * document, rendered for the run's audience (ANSI for a person, plain for an
46
+ * agent, a GitHub Actions log under Actions). It is a typed failure, so the
47
+ * run exits `1`. A finding (a lint problem, a dirty repo) is still output
48
+ * plus `CliExit.set(1)`, never this.
49
+ *
50
+ * Map a silk-effects error at the command edge with
51
+ * {@link CommandError.from}, inside the handler's `catchTag`s.
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * Effect.catchTag("ChangesetConfigError", (error) =>
56
+ * Effect.fail(CommandError.from(error, { message: "could not read the changeset config", hint: "run savvy init" })),
57
+ * )
58
+ * ```
59
+ *
60
+ * @internal
61
+ */
62
+ declare class CommandError extends CommandError_base<CommandErrorFields> implements CliDocSource {
63
+ /** Wrap a foreign (silk-effects, kit) error: its own description becomes the last detail line, and it is kept as `cause`. */
64
+ static readonly from: (error: unknown, options: CommandErrorFromOptions) => CommandError;
65
+ [CliDoc](): Document;
66
+ }
67
+ //#endregion
68
+ //#region src/internal/report.d.ts
69
+ /** What {@link Report.print} reads to pick a renderer: the audience, the terminal, the theme and editor links. */
70
+ type ReportEnv = CliTheme | TerminalEnv | Audience | CliLinks;
71
+ //#endregion
72
+ //#region src/commands/check-section.d.ts
73
+ /**
74
+ * A tool's verdict: `failure` when it found a misconfiguration (exit 1),
75
+ * `warning` when it only has advice, `success` otherwise.
76
+ */
77
+ type CheckVerdict = "success" | "warning" | "failure";
13
78
  /**
14
- * Run the check validation pipeline on all changeset files in `dir`.
79
+ * One tool's check, as blocks the caller places: `savvy check` folds each into
80
+ * a collapsible (a `::group::` under GitHub Actions); a standalone run prints
81
+ * the title as a heading over the blocks.
15
82
  *
16
- * Groups lint messages by file and logs a human-readable summary. Sets
17
- * exit code 1 through `CliExit.set` when one or more errors are found.
83
+ * @internal
84
+ */
85
+ interface CheckSection {
86
+ /** The section title, e.g. `commitlint`. */
87
+ readonly title: string;
88
+ /** The section body. */
89
+ readonly blocks: ReadonlyArray<Block>;
90
+ /** The tool's verdict. */
91
+ readonly verdict: CheckVerdict;
92
+ /** Whether `savvy init` is the remediation for what was found. */
93
+ readonly fixedByInit: boolean;
94
+ }
95
+ //#endregion
96
+ //#region src/commands/changeset/commands/check.d.ts
97
+ /**
98
+ * Run the check validation pipeline on all changeset files in `dir` and
99
+ * print {@link changesetCheckSection}'s blocks (no title heading: the
100
+ * standalone `savvy changeset check` output). Sets exit code 1 through
101
+ * `CliExit.set` when one or more errors are found.
18
102
  *
19
103
  * @param dir - Path to the changeset directory (resolved relative to cwd)
20
- * @returns An Effect that performs validation and logs results
104
+ * @returns An Effect that performs validation and prints the report
21
105
  *
22
106
  * @internal
23
107
  */
24
- export declare function runChangesetCheck(dir: string): Effect.Effect<void, Error, CliExit | Stdio.Stdio>;
108
+ export declare function runChangesetCheck(dir: string): Effect.Effect<void, Error, CliExit | ReportEnv>;
25
109
  //#endregion
26
110
  //#region src/commands/changeset/commands/init.d.ts
27
111
  /**
28
- * Run the full init pipeline.
112
+ * Run the changeset init as one step of the orchestrated `savvy init`: a
113
+ * failure is reported, not propagated, so the commit and lint steps still
114
+ * run.
29
115
  *
30
- * Exported so Task B5's unified `savvy init` orchestrator can invoke the
31
- * changeset init step directly without going through the CLI command layer.
116
+ * @remarks
117
+ * Runs {@link runChangesetInitOrFail}. Its {@link CommandError} is drawn on
118
+ * stderr exactly as the runtime draws a failure (its `CliDoc` document,
119
+ * rendered for the audience; never wrapped off a terminal), exit code 1 is
120
+ * set through `CliExit.set`, and the step succeeds. A caller that wants the
121
+ * failure to end the run calls {@link runChangesetInitOrFail} instead.
122
+ *
123
+ * Exported so the unified `savvy init` orchestrator can invoke the changeset
124
+ * init step directly without going through the CLI command layer.
32
125
  *
33
126
  * @param opts - The same options the CLI command receives
34
- * @returns An Effect that performs initialization
127
+ * @returns An Effect that never fails
35
128
  *
36
129
  * @internal
37
130
  */
@@ -40,7 +133,7 @@ export declare function runChangesetInit(opts: {
40
133
  quiet: boolean;
41
134
  skipMarkdownlint: boolean;
42
135
  check: boolean;
43
- }): Effect.Effect<void, never, WorkspaceRoot | Git | CliExit | Stdio.Stdio>;
136
+ }): Effect.Effect<void, never, WorkspaceRoot | Git | CliExit | ReportEnv>;
44
137
  //#endregion
45
138
  //#region src/commands/changeset/index.d.ts
46
139
  /**
@@ -58,26 +151,29 @@ export declare function runChangesetInit(opts: {
58
151
  * `Command.withSubcommands` propagates each subcommand's requirements up into
59
152
  * the group's `R`, which the root assembly discharges via `AppLive`.
60
153
  */
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>;
154
+ export declare const changesetCommand: Command.Command<"changeset", Record<string, never>, Record<string, never>, CommandError | Error | Changesets.ReleasePlanError | Cause.UnknownError | Changesets.DepsRegenPlanError, ChildProcessSpawner.ChildProcessSpawner | Changesets.ConfigInspector | FileSystem.FileSystem | Path.Path | Changesets.ReleasePlanner | CliExit | ReportEnv>;
62
155
  //#endregion
63
156
  //#region src/commands/check.d.ts
64
157
  /**
65
- * Run all three check step Effects without short-circuiting.
66
- *
67
- * All three checks always run, sequentially (concurrency 1) so per-tool
68
- * output stays grouped. If any fail, the combined Effect fails with all
69
- * accumulated errors. Uses `Effect.all` with `{ concurrency: 1, mode: "validate" }`
70
- * to collect every failure rather than stopping at the first.
158
+ * Run the three checks without short-circuiting and print them as one
159
+ * document.
71
160
  *
72
- * @param steps - The three step Effects to run. Injected for testability.
73
- * @returns An Effect that resolves to `void` on success, or fails with the
74
- * union of all failing steps' errors.
161
+ * @remarks
162
+ * Each step builds a {@link CheckSection}; each section is folded into a
163
+ * collapsible (a `::group::` under GitHub Actions, whose body is where a
164
+ * section's annotations are written, so its blocks are never nested deeper).
165
+ * A step that fails still lets the others run and report; the first failure
166
+ * is re-raised after the document is printed.
167
+ *
168
+ * @param steps - The three step Effects. Injected for testability.
169
+ * @returns An Effect that prints the report and sets exit code 1 on any
170
+ * finding, or fails with the first failing step's error.
75
171
  */
76
172
  export declare function runCheck<EChangeset, RChangeset, ECommit, RCommit, ELint, RLint>(steps: {
77
- changeset: Effect.Effect<unknown, EChangeset, RChangeset>;
78
- commit: Effect.Effect<unknown, ECommit, RCommit>;
79
- lint: Effect.Effect<unknown, ELint, RLint>;
80
- }): Effect.Effect<void, EChangeset | ECommit | ELint, RChangeset | RCommit | RLint>;
173
+ changeset: Effect.Effect<CheckSection, EChangeset, RChangeset>;
174
+ commit: Effect.Effect<CheckSection, ECommit, RCommit>;
175
+ lint: Effect.Effect<CheckSection, ELint, RLint>;
176
+ }): Effect.Effect<void, EChangeset | ECommit | ELint, RChangeset | RCommit | RLint | CliExit | ReportEnv>;
81
177
  /**
82
178
  * The `savvy check` command for use in the Task B7 root assembly.
83
179
  *
@@ -89,20 +185,20 @@ export declare function runCheck<EChangeset, RChangeset, ECommit, RCommit, ELint
89
185
  export declare const checkCommand: Command.Command<"check", {
90
186
  readonly changesetDir: string;
91
187
  readonly quiet: boolean;
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>;
188
+ }, {}, Error | import("@effected/jsonc").JsoncParseError | import("effect/PlatformError").PlatformError | import("@effected/templates").SectionFileError | import("@effected/templates").SectionParseError, import("@effected/env").Audience | import("@savvy-web/silk-effects").ChangesetConfigReader | CliExit | import("@effected/cli").CliLinks | import("@effected/cli").CliTheme | import("@savvy-web/silk-effects").ConfigDiscovery | import("effect/FileSystem").FileSystem | import("@effected/templates").ManagedSection | import("@effected/workspaces").PublishabilityDetector | import("@effected/env").TerminalEnv | import("@effected/commands").ToolDiscovery | import("@effected/workspaces").WorkspaceDiscovery>;
93
189
  //#endregion
94
190
  //#region src/commands/commit/check.d.ts
191
+ /** What {@link commitCheckSection} needs from the environment. */
192
+ type CommitCheckRequirements = ManagedSection | FileSystem.FileSystem | ChangesetConfigReader | PublishabilityDetector | WorkspaceDiscovery;
95
193
  /**
96
- * Run the check validation pipeline.
194
+ * Check the commitlint setup, print its report on stdout, and set exit code 1
195
+ * when it found a misconfiguration (see {@link commitCheckSection}).
97
196
  *
98
- * Exported so Task B6's unified `savvy check` orchestrator can invoke the
99
- * commitlint check step directly without going through the CLI command layer.
100
- *
101
- * @returns An Effect that performs validation and logs results
197
+ * @returns An Effect that performs validation and prints its report
102
198
  *
103
199
  * @internal
104
200
  */
105
- export declare function runCommitCheck(): Effect.Effect<void, SectionParseError | SectionFileError | PlatformError, ManagedSection | FileSystem.FileSystem | ChangesetConfigReader | PublishabilityDetector | WorkspaceDiscovery | Stdio.Stdio>;
201
+ export declare function runCommitCheck(): Effect.Effect<void, SectionParseError | SectionFileError | PlatformError, CommitCheckRequirements | ReportEnv | CliExit>;
106
202
  //#endregion
107
203
  //#region src/commands/commit/init.d.ts
108
204
  /**
@@ -119,7 +215,7 @@ export declare function runCommitCheck(): Effect.Effect<void, SectionParseError
119
215
  export declare function runCommitInit(opts: {
120
216
  force: boolean;
121
217
  config: string;
122
- }): Effect.Effect<void, Error | SectionParseError | SectionRenderError | SectionFileError | PlatformError, ManagedSection | FileSystem.FileSystem | Stdio.Stdio>;
218
+ }): Effect.Effect<void, CommandError | SectionParseError | SectionRenderError | SectionFileError | PlatformError, ManagedSection | FileSystem.FileSystem | ReportEnv>;
123
219
  //#endregion
124
220
  //#region src/commands/commit/index.d.ts
125
221
  /**
@@ -130,9 +226,11 @@ export declare function runCommitInit(opts: {
130
226
  * errors from Effect's internal types. Task B7 should import and use this directly
131
227
  * as `Command.withSubcommands([commitCommand])` — the cast is for declaration emit only.
132
228
  */
133
- export declare const commitCommand: Command.Command<"commit", {}, {}, import("effect/cli/CliError").UserError, import("effect/process/ChildProcessSpawner").ChildProcessSpawner | import("@effected/git").Git | import("effect/Stdio").Stdio>;
229
+ export declare const commitCommand: Command.Command<"commit", {}, {}, import("effect/cli/CliError").UserError, import("effect/process/ChildProcessSpawner").ChildProcessSpawner | import("@effected/git").Git | ReportEnv>;
134
230
  //#endregion
135
231
  //#region src/commands/init.d.ts
232
+ /** The lint-staged presets `--lint-preset` accepts. */
233
+ type LintPreset = "minimal" | "standard" | "silk";
136
234
  /**
137
235
  * Run the three init step Effects in order: changeset → commit → lint.
138
236
  *
@@ -158,26 +256,28 @@ export declare function runInit<EChangeset, RChangeset, ECommit, RCommit, ELint,
158
256
  */
159
257
  export declare const initCommand: Command.Command<"init", {
160
258
  readonly force: boolean;
259
+ readonly yes: boolean;
161
260
  readonly commitConfig: string;
162
261
  readonly lintConfig: string;
163
- readonly lintPreset: "minimal" | "silk" | "standard";
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>;
262
+ readonly lintPreset: LintPreset;
263
+ }, {}, Cancelled | CommandError, import("@savvy-web/silk-effects").BiomeSchemaSync | import("@effected/cli").CliExit | FileSystem.FileSystem | import("@effected/git").Git | import("@effected/templates").ManagedSection | import("@effected/workspaces").WorkspaceDiscovery | import("@effected/workspaces").WorkspaceRoot | ReportEnv>;
165
264
  //#endregion
166
265
  //#region src/commands/lint/check.d.ts
266
+ /** What {@link lintCheckSection} needs from the environment. */
267
+ type LintCheckRequirements = ManagedSection | FileSystem.FileSystem | ToolDiscovery | ConfigDiscovery;
167
268
  /**
168
- * Run the lint check validation pipeline.
169
- *
170
- * Exported so Task B6's unified `savvy check` orchestrator can invoke the
171
- * lint check step directly without going through the CLI command layer.
269
+ * Check the lint-staged setup, print its report on stdout, and set exit code
270
+ * 1 when it found a misconfiguration (see {@link lintCheckSection} for the
271
+ * severity rule). Under `quiet` only the warnings are printed.
172
272
  *
173
273
  * @param opts - Options for the check command
174
- * @returns An Effect that performs validation and logs results
274
+ * @returns An Effect that performs validation and prints its report
175
275
  *
176
276
  * @internal
177
277
  */
178
278
  export declare function runLintCheck(opts: {
179
279
  quiet: boolean;
180
- }): Effect.Effect<void, JsoncParseError | SectionParseError | SectionFileError | PlatformError, ManagedSection | FileSystem.FileSystem | ToolDiscovery | ConfigDiscovery | Stdio.Stdio>;
280
+ }): Effect.Effect<void, JsoncParseError | SectionParseError | SectionFileError | PlatformError, LintCheckRequirements | ReportEnv | CliExit>;
181
281
  //#endregion
182
282
  //#region src/commands/lint/init.d.ts
183
283
  /**
@@ -186,6 +286,10 @@ export declare function runLintCheck(opts: {
186
286
  * Exported so Task B5's unified `savvy init` orchestrator can invoke the
187
287
  * lint-staged init step directly without going through the CLI command layer.
188
288
  *
289
+ * @remarks
290
+ * Prints a `lint-staged` report section on stdout, one step at a time as each
291
+ * completes, so a later step's failure still leaves what was written on screen.
292
+ *
189
293
  * @param opts - The same options the CLI command receives
190
294
  * @returns An Effect that performs initialization
191
295
  *
@@ -195,7 +299,7 @@ export declare function runLintInit(opts: {
195
299
  force: boolean;
196
300
  config: string;
197
301
  preset: "minimal" | "standard" | "silk";
198
- }): Effect.Effect<void, Error | SectionParseError | SectionRenderError | SectionFileError | PlatformError, ManagedSection | FileSystem.FileSystem | BiomeSchemaSync | WorkspaceDiscovery | Stdio.Stdio>;
302
+ }): Effect.Effect<void, CommandError | JsoncParseError | JsoncModificationError | SectionParseError | SectionRenderError | SectionFileError | PlatformError, ManagedSection | FileSystem.FileSystem | BiomeSchemaSync | WorkspaceDiscovery | ReportEnv>;
199
303
  //#endregion
200
304
  //#region src/commands/lint/index.d.ts
201
305
  /**
@@ -206,7 +310,7 @@ export declare function runLintInit(opts: {
206
310
  * errors from Effect's internal types. Task B7 should import and use this directly
207
311
  * as `Command.withSubcommands([lintCommand])` — the cast is for declaration emit only.
208
312
  */
209
- export declare const lintCommand: Command.Command<"lint", {}, {}, import("@effected/git").GitCommandError | import("@effected/git").NotARepositoryError | import("effect/PlatformError").PlatformError | import("@effected/git").UnknownRefError | import("@effected/yaml").YamlParseError, import("effect/process/ChildProcessSpawner").ChildProcessSpawner | import("@effected/cli").CliExit | import("effect/FileSystem").FileSystem | import("@effected/git").Git | import("effect/Path").Path | import("effect/Stdio").Stdio>;
313
+ export declare const lintCommand: Command.Command<"lint", {}, {}, import("@effected/git").GitCommandError | import("@effected/git").NotARepositoryError | import("effect/PlatformError").PlatformError | import("@effected/git").UnknownRefError | import("@effected/yaml").YamlParseError, import("effect/process/ChildProcessSpawner").ChildProcessSpawner | import("@effected/cli").CliExit | import("effect/FileSystem").FileSystem | import("@effected/git").Git | import("effect/Path").Path | ReportEnv>;
210
314
  //#endregion
211
315
  //#region src/commands/repos/commands/status.d.ts
212
316
  /**
@@ -214,7 +318,7 @@ export declare const lintCommand: Command.Command<"lint", {}, {}, import("@effec
214
318
  *
215
319
  * @internal
216
320
  */
217
- export declare const runReposStatus: (cwd: string, json: boolean, drift?: boolean) => Effect.Effect<void, Repos.GitSubmoduleError, CliExit | Repos.ReposDrift | Repos.ReposManager | Stdio.Stdio>;
321
+ export declare const runReposStatus: (cwd: string, json: boolean, drift?: boolean) => Effect.Effect<void, CommandError, CliExit | Repos.ReposDrift | Repos.ReposManager | ReportEnv>;
218
322
  //#endregion
219
323
  //#region src/commands/repos/commands/sync.d.ts
220
324
  /**
@@ -222,7 +326,7 @@ export declare const runReposStatus: (cwd: string, json: boolean, drift?: boolea
222
326
  *
223
327
  * @internal
224
328
  */
225
- export declare const runReposSync: (cwd: string) => Effect.Effect<void, never, CliExit | Repos.ReposManager | Stdio.Stdio>;
329
+ export declare const runReposSync: (cwd: string) => Effect.Effect<void, CommandError, Repos.ReposManager | ReportEnv>;
226
330
  //#endregion
227
331
  //#region src/commands/repos/index.d.ts
228
332
  /**
@@ -242,17 +346,14 @@ export declare const runReposSync: (cwd: string) => Effect.Effect<void, never, C
242
346
  * discharges via `AppLive`. `Repos.ReposDrift` joins the union because
243
347
  * `status --drift` runs `Repos.ReposDrift.check` after the status check.
244
348
  *
245
- * The error channel is `Repos.GitSubmoduleError` alone: `add`/`pin`/`note`/
246
- * `remove`/`rename`/`restore`/`sync`/`deregister` each `catchTag` every error
247
- * their underlying `ReposManager` method can produce — `ReposConfigError`,
248
- * `GitSubmoduleError`, `RepoNotFoundError`, `NoteNotFoundError`, and (for the
249
- * six ops that unlock/re-lock the vendored tree around a git mutation)
250
- * `ReposLockdownError` — down to a logged message and a non-zero exit code,
251
- * so none of those propagate past the handler. `status` only `catchTag`s
252
- * `ReposConfigError`; `GitSubmoduleError` from either `ReposManager.status`
253
- * or (under `--drift`) `Repos.ReposDrift.check` is the sole channel that
254
- * escapes uncaught, so it is the only member left in this union.
349
+ * The error channel is what a handler fails with, never a raw `Repos` error:
350
+ * every handler maps each error its `ReposManager` method (or the picker's
351
+ * `status` read) can produce to a self-rendering `CommandError` (exit 1),
352
+ * except the friendly missing-manifest case and `status --json`'s JSON error
353
+ * document. A repo name or other positional left off is a
354
+ * `CliError.ShowHelp` (exit 64) when no one can be asked, and backing out of
355
+ * a picker or a confirm is the kit's `Cancelled` (exit 130).
255
356
  */
256
- export declare const reposCommand: Command.Command<"repos", Record<string, never>, Record<string, never>, Repos.GitSubmoduleError, Repos.ReposManager | Repos.ReposDrift | CliExit | Stdio.Stdio>;
357
+ export declare const reposCommand: Command.Command<"repos", Record<string, never>, Record<string, never>, CommandError | CliError.ShowHelp | Cancelled, Repos.ReposManager | Repos.ReposDrift | CliExit | ReportEnv>;
257
358
  //#endregion
258
359
  //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,60 @@
1
+ import { FailureLine } from "./failure-line.js";
2
+ import { CliDoc, Doc, Status } from "@effected/cli";
3
+ import { Data } from "effect";
4
+
5
+ //#region src/internal/command-error.ts
6
+ /**
7
+ * A command's failure that draws itself: a failure status line, indented
8
+ * detail lines, and an optional remediation as a tip callout.
9
+ *
10
+ * @remarks
11
+ * Replaces the `Effect.logError(...)` + `CliExit.set(1)` pair: fail with it
12
+ * and `CliRuntime.main` reports it on stderr through `FailureLine.render`,
13
+ * which hands an error implementing `CliDoc` the kit's report of its
14
+ * document, rendered for the run's audience (ANSI for a person, plain for an
15
+ * agent, a GitHub Actions log under Actions). It is a typed failure, so the
16
+ * run exits `1`. A finding (a lint problem, a dirty repo) is still output
17
+ * plus `CliExit.set(1)`, never this.
18
+ *
19
+ * Map a silk-effects error at the command edge with
20
+ * {@link CommandError.from}, inside the handler's `catchTag`s.
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * Effect.catchTag("ChangesetConfigError", (error) =>
25
+ * Effect.fail(CommandError.from(error, { message: "could not read the changeset config", hint: "run savvy init" })),
26
+ * )
27
+ * ```
28
+ *
29
+ * @internal
30
+ */
31
+ var CommandError = class CommandError extends Data.TaggedError("CommandError") {
32
+ /** Wrap a foreign (silk-effects, kit) error: its own description becomes the last detail line, and it is kept as `cause`. */
33
+ static from = (error, options) => {
34
+ const own = FailureLine.describe(error);
35
+ const detail = [...options.detail ?? [], ...own === options.message ? [] : [own]];
36
+ return new CommandError({
37
+ message: options.message,
38
+ cause: error,
39
+ ...detail.length > 0 ? { detail } : {},
40
+ ...options.hint === void 0 ? {} : { hint: options.hint }
41
+ });
42
+ };
43
+ [CliDoc]() {
44
+ const head = Doc.line([
45
+ Doc.status(Status.core, "failure"),
46
+ " ",
47
+ this.message
48
+ ]);
49
+ const detail = this.detail === void 0 || this.detail.length === 0 ? [] : [Doc.lines(this.detail.map((line) => Doc.text(` ${line}`, "muted")))];
50
+ const hint = this.hint === void 0 ? [] : [Doc.callout("tip", [Doc.paragraph(this.hint)])];
51
+ return [
52
+ head,
53
+ ...detail,
54
+ ...hint
55
+ ];
56
+ }
57
+ };
58
+
59
+ //#endregion
60
+ export { CommandError };
@@ -0,0 +1,52 @@
1
+ import { Flag } from "effect/cli";
2
+ import { Effect } from "effect";
3
+ import { CliUi, Confirm } from "@effected/cli/ui";
4
+
5
+ //#region src/internal/confirm.ts
6
+ /**
7
+ * `--yes` / `-y`: answer yes to a destructive command's confirmation.
8
+ *
9
+ * @remarks
10
+ * Defaults to `false` explicitly — core's `Flag.Boolean` has no implicit
11
+ * `false`, so an omitted boolean would otherwise be a usage error. Add it to a
12
+ * command's flags as `yes: yesFlag` and pass the parsed value to
13
+ * {@link confirmDestructive}.
14
+ *
15
+ * @internal
16
+ */
17
+ const yesFlag = Flag.Boolean("yes").pipe(Flag.withAlias("y"), Flag.withDescription("Answer yes to the confirmation and proceed"), Flag.withDefault(false));
18
+ /**
19
+ * Whether a destructive command may proceed.
20
+ *
21
+ * @remarks
22
+ * - `yes` (the `--yes` flag) → `true`, without asking.
23
+ * - A run that cannot prompt (`CliInteractive` false: an agent, CI, a pipe,
24
+ * `TERM=dumb`) → `true`, without asking and without loading Ink or React:
25
+ * savvy has always proceeded there, and an agent or a script has no one to
26
+ * ask.
27
+ * - A person at a terminal → the kit's `Confirm` screen (`Enter` alone answers
28
+ * no). Its answer is a whole `{ confirmed, toggles }`, of which this
29
+ * returns `confirmed`.
30
+ *
31
+ * Esc, `q` or Ctrl-C on the screen fails with the kit's `Cancelled`, which
32
+ * `CliRuntime.main` reports as one line with exit `130`; a handler that wants
33
+ * cancel to mean "no" catches it with `Effect.catchTag("Cancelled", ...)`.
34
+ * A `false` answer is the handler's to report (a line saying nothing was
35
+ * changed) — it is not a failure.
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * if (!(yield* confirmDestructive({ message: `Remove ${name}?`, yes }))) {
40
+ * return yield* Report.print([Report.skip(`${name} kept`)]);
41
+ * }
42
+ * ```
43
+ *
44
+ * @internal
45
+ */
46
+ const confirmDestructive = (options) => options.yes ? Effect.succeed(true) : CliUi.prompt(Confirm.screen({ message: options.message }), { otherwise: {
47
+ confirmed: true,
48
+ toggles: {}
49
+ } }).pipe(Effect.map((result) => result.confirmed), Effect.catchTag("NotInteractive", () => Effect.succeed(true)));
50
+
51
+ //#endregion
52
+ export { confirmDestructive, yesFlag };