@savvy-web/cli 3.5.1 → 3.7.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.
- package/cli/index.js +19 -3
- package/commands/changeset/commands/check.js +82 -21
- package/commands/changeset/commands/config-validate.js +9 -6
- package/commands/changeset/commands/deps-detect.js +2 -2
- package/commands/changeset/commands/deps-regen.js +94 -48
- package/commands/changeset/commands/init.js +56 -23
- package/commands/changeset/commands/transform.js +82 -8
- package/commands/changeset/commands/validate-file.js +12 -11
- package/commands/changeset/commands/version.js +48 -17
- package/commands/changeset/index.js +3 -3
- package/commands/changeset/utils/config-gate.js +12 -10
- package/commands/check-section.js +34 -0
- package/commands/check.js +48 -39
- package/commands/clean/progress.js +71 -0
- package/commands/clean/view.js +26 -0
- package/commands/clean.js +113 -50
- package/commands/commit/check.js +80 -77
- package/commands/commit/init.js +21 -10
- package/commands/commit/lint.js +2 -2
- package/commands/init.js +122 -34
- package/commands/lint/biome-version.js +1 -1
- package/commands/lint/check.js +126 -171
- package/commands/lint/init.js +52 -33
- package/commands/lint/text.js +28 -13
- package/commands/repos/commands/add.js +14 -17
- package/commands/repos/commands/deregister.js +37 -18
- package/commands/repos/commands/note.js +78 -26
- package/commands/repos/commands/pin.js +39 -24
- package/commands/repos/commands/remove.js +30 -56
- package/commands/repos/commands/rename.js +32 -23
- package/commands/repos/commands/restore.js +42 -56
- package/commands/repos/commands/status.js +43 -47
- package/commands/repos/commands/sync.js +11 -40
- package/commands/repos/index.js +7 -10
- package/commands/repos/shared.js +129 -0
- package/index.d.ts +162 -61
- package/internal/command-error.js +60 -0
- package/internal/confirm.js +52 -0
- package/internal/failure-line.js +26 -12
- package/internal/report.js +101 -0
- package/main.js +23 -16
- package/package.json +17 -12
- package/version.js +1 -1
- 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
|
|
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/
|
|
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
|
-
*
|
|
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
|
-
*
|
|
17
|
-
|
|
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
|
|
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 |
|
|
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
|
|
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
|
-
*
|
|
31
|
-
*
|
|
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
|
|
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 |
|
|
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>,
|
|
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
|
|
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
|
-
* @
|
|
73
|
-
*
|
|
74
|
-
*
|
|
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<
|
|
78
|
-
commit: Effect.Effect<
|
|
79
|
-
lint: Effect.Effect<
|
|
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").
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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,
|
|
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,
|
|
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 |
|
|
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:
|
|
164
|
-
}, {},
|
|
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
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
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
|
|
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,
|
|
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,
|
|
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 |
|
|
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,
|
|
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,
|
|
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
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
* `
|
|
251
|
-
*
|
|
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>,
|
|
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 };
|