@savvy-web/cli 0.4.2 → 1.0.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/README.md CHANGED
@@ -53,7 +53,7 @@ npx savvy clean --globs dist,.turbo,coverage
53
53
  - `savvy check` — orchestrator that runs all three checks and reports every failure (it does not short-circuit).
54
54
  - `savvy clean` — removes build and cache artifacts (`dist`, `.turbo`, `coverage`, `node_modules`, `.rslib` by default) from every workspace package (leaves first) and the repo root (last); `--globs` to customize, `--dry-run` to preview.
55
55
  - `savvy commit` — the husky/Claude hook handlers (session-start, pre-commit-message, post-commit-verify, user-prompt-submit).
56
- - `savvy changeset` — changeset lint, transform, version, classify, branch analysis, release-surface and config inspection.
56
+ - `savvy changeset` — changeset lint, check, transform, version, config validation, and dependency changesets.
57
57
  - `savvy lint` — formatters for package.json, the pnpm workspace file and YAML.
58
58
 
59
59
  Run any command with `--help` to see its full surface:
package/cli/index.js CHANGED
@@ -38,7 +38,7 @@ import { PackageManagerDetectorLive, WorkspaceDiscoveryLive, WorkspaceRootLive }
38
38
  * `Changesets.BranchAnalyzerLive`, which shares the single `ConfigInspectorLive`
39
39
  * instance built once via `provideMerge`.
40
40
  *
41
- * The CLI version is injected at build time via `__PACKAGE_VERSION__`.
41
+ * The CLI version is injected at build time via `process.env.__PACKAGE_VERSION__`.
42
42
  *
43
43
  * @internal
44
44
  */
@@ -56,7 +56,7 @@ const rootCommand = Command.make("savvy").pipe(Command.withSubcommands([
56
56
  ]));
57
57
  const cli = Command.run(rootCommand, {
58
58
  name: "savvy",
59
- version: process.env.__PACKAGE_VERSION__ ?? "0.0.0"
59
+ version: "1.0.0"
60
60
  });
61
61
  /**
62
62
  * Shared base layer: workspace services, the changeset config reader, and the
@@ -90,13 +90,15 @@ const BaseLive = Layer.mergeAll(WorkspaceLive, ChangesetConfigReaderLive, Manage
90
90
  * The upper services depend on members of `BaseLive`:
91
91
  * `ToolDiscoveryLive` needs `WorkspaceRoot`, `PackageManagerDetector`, and
92
92
  * `CommandExecutor`; `VersioningStrategyLive` needs `ChangesetConfigReader`;
93
- * `Changesets.ConfigInspectorLive` needs `ChangesetConfigReader` and
94
- * `WorkspaceDiscovery`; `Changesets.BranchAnalyzerLive` needs `ConfigInspector`.
93
+ * `Changesets.ConfigInspectorLive` needs `ChangesetConfigReader`,
94
+ * `WorkspaceDiscovery`, and `FileSystem` (the last for its publishConfig-driven
95
+ * fallback when no explicit `packages` record is configured);
96
+ * `Changesets.BranchAnalyzerLive` needs `ConfigInspector`.
95
97
  *
96
98
  * `ConfigInspectorLive` is built once via {@link Layer.provideMerge}: the merge
97
99
  * feeds that single `ConfigInspector` instance into `BranchAnalyzerLive` AND
98
- * re-exposes it for the `classify` / `config` handlers that yield it directly,
99
- * so it is never constructed twice per run.
100
+ * re-exposes it for the surviving `config validate` handler that yields it
101
+ * directly, so it is never constructed twice per run.
100
102
  *
101
103
  * `provideMerge(BaseLive)` feeds the remaining deps and re-exposes the base
102
104
  * services for handlers that yield them directly. `provideMerge(NodeContext.layer)`
@@ -68,4 +68,4 @@ function runChangesetCheck(dir) {
68
68
  const checkCommand = Command.make("check", { dir: dirArg }, ({ dir }) => runChangesetCheck(dir)).pipe(Command.withDescription("Full changeset validation with summary"));
69
69
 
70
70
  //#endregion
71
- export { runChangesetCheck };
71
+ export { checkCommand, runChangesetCheck };
@@ -1,6 +1,6 @@
1
1
  import { Command, Options } from "@effect/cli";
2
2
  import { Changesets } from "@savvy-web/silk-effects";
3
- import { Effect, Option } from "effect";
3
+ import { Console, Effect, Option } from "effect";
4
4
  import { WorkspaceDiscovery } from "workspaces-effect";
5
5
  import { resolve } from "node:path";
6
6
 
@@ -15,8 +15,8 @@ import { resolve } from "node:path";
15
15
  *
16
16
  * Defaults:
17
17
  * - `--from` → `git merge-base <baseBranch> HEAD`
18
- * - `--to` → working tree (i.e., `HEAD` plus staged + unstaged + untracked,
19
- * matching `analyze-branch`'s coverage). Passed as the special value
18
+ * - `--to` → working tree (i.e., `HEAD` plus staged + unstaged + untracked).
19
+ * Passed as the special value
20
20
  * `WORKTREE` to {@link WorkspaceSnapshotReader} — implementations resolve
21
21
  * this against the live working tree rather than `git show`.
22
22
  *
@@ -86,7 +86,7 @@ function runDepsDetect(cwd, from, to, pkg, json, markdown) {
86
86
  yield* Effect.log(renderMarkdownBlocks(diffs));
87
87
  return;
88
88
  }
89
- yield* Effect.log(JSON.stringify(diffs, null, 2));
89
+ yield* Console.log(JSON.stringify(diffs, null, 2));
90
90
  });
91
91
  }
92
92
  /* v8 ignore next 7 */
@@ -1,6 +1,6 @@
1
1
  import { Command, Options } from "@effect/cli";
2
2
  import { Changesets } from "@savvy-web/silk-effects";
3
- import { Effect, Option } from "effect";
3
+ import { Console, Effect, Option } from "effect";
4
4
  import { WorkspaceDiscovery } from "workspaces-effect";
5
5
  import { join, resolve } from "node:path";
6
6
  import { existsSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
@@ -231,7 +231,7 @@ function runDepsRegen(cwd, base, pkg, dryRun, json) {
231
231
  skippedMixed
232
232
  };
233
233
  if (dryRun) {
234
- if (json) yield* Effect.log(JSON.stringify(plan, null, 2));
234
+ if (json) yield* Console.log(JSON.stringify(plan, null, 2));
235
235
  else yield* renderHumanPlan(plan);
236
236
  return;
237
237
  }
@@ -241,7 +241,7 @@ function runDepsRegen(cwd, base, pkg, dryRun, json) {
241
241
  yield* Effect.logWarning(`Failed to delete ${entry.file}: ${error instanceof Error ? error.message : String(error)}`);
242
242
  }
243
243
  for (const entry of toWrite) writeFileSync(entry.file, renderChangesetContent(entry.diff));
244
- if (json) yield* Effect.log(JSON.stringify(plan, null, 2));
244
+ if (json) yield* Console.log(JSON.stringify(plan, null, 2));
245
245
  else yield* renderHumanPlan(plan);
246
246
  });
247
247
  }
@@ -1,6 +1,6 @@
1
1
  import { Args, Command, Options } from "@effect/cli";
2
2
  import { Changesets } from "@savvy-web/silk-effects";
3
- import { Effect } from "effect";
3
+ import { Console, Effect } from "effect";
4
4
  import { resolve } from "node:path";
5
5
 
6
6
  //#region src/commands/changeset/commands/lint.ts
@@ -47,8 +47,8 @@ function runLint(dir, quiet) {
47
47
  return Effect.gen(function* () {
48
48
  const resolved = resolve(dir);
49
49
  const messages = yield* Effect.try(() => ChangesetLinter.validate(resolved));
50
- for (const msg of messages) yield* Effect.log(`${msg.file}:${msg.line}:${msg.column} ${msg.rule} ${msg.message}`);
51
- if (!quiet && messages.length === 0) yield* Effect.log("No lint errors found.");
50
+ for (const msg of messages) yield* Console.log(`${msg.file}:${msg.line}:${msg.column} ${msg.rule} ${msg.message}`);
51
+ if (!quiet && messages.length === 0) yield* Console.log("No lint errors found.");
52
52
  if (messages.length > 0) process.exitCode = 1;
53
53
  });
54
54
  }
@@ -1,30 +1,24 @@
1
- import { analyzeBranchCommand } from "./commands/analyze-branch.js";
2
- import { classifyCommand } from "./commands/classify.js";
3
- import { configShowCommand } from "./commands/config-show.js";
1
+ import { checkCommand, runChangesetCheck } from "./commands/check.js";
4
2
  import { configValidateCommand } from "./commands/config-validate.js";
5
3
  import { depsDetectCommand } from "./commands/deps-detect.js";
6
4
  import { depsRegenCommand } from "./commands/deps-regen.js";
7
5
  import { lintCommand } from "./commands/lint.js";
8
- import { releaseSurfaceCommand } from "./commands/release-surface.js";
9
6
  import { transformCommand } from "./commands/transform.js";
10
7
  import { validateFileCommand } from "./commands/validate-file.js";
11
8
  import { versionCommand } from "./commands/version.js";
12
- import { runChangesetCheck } from "./commands/check.js";
13
9
  import { runChangesetInit } from "./commands/init.js";
14
10
  import { Command } from "@effect/cli";
15
11
 
16
12
  //#region src/commands/changeset/index.ts
17
13
  /* v8 ignore start -- CLI registration; each command tested via exported handler */
18
- const configGroup = Command.make("config").pipe(Command.withSubcommands([configShowCommand, configValidateCommand]), Command.withDescription("Inspect or validate .changeset/config.json"));
14
+ const configGroup = Command.make("config").pipe(Command.withSubcommands([configValidateCommand]), Command.withDescription("Inspect or validate .changeset/config.json"));
19
15
  const depsGroup = Command.make("deps").pipe(Command.withSubcommands([depsDetectCommand, depsRegenCommand]), Command.withDescription("Generate or regenerate dependency changesets"));
20
16
  const _changesetCommand = Command.make("changeset").pipe(Command.withSubcommands([
21
17
  lintCommand,
18
+ checkCommand,
22
19
  transformCommand,
23
20
  validateFileCommand,
24
21
  versionCommand,
25
- classifyCommand,
26
- analyzeBranchCommand,
27
- releaseSurfaceCommand,
28
22
  configGroup,
29
23
  depsGroup
30
24
  ]), Command.withDescription("Section-aware changeset tooling"));
package/index.d.ts CHANGED
@@ -34,7 +34,7 @@ import { JsoncParseError } from "jsonc-effect";
34
34
  * `Changesets.BranchAnalyzerLive`, which shares the single `ConfigInspectorLive`
35
35
  * instance built once via `provideMerge`.
36
36
  *
37
- * The CLI version is injected at build time via `__PACKAGE_VERSION__`.
37
+ * The CLI version is injected at build time via `process.env.__PACKAGE_VERSION__`.
38
38
  *
39
39
  * @internal
40
40
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@savvy-web/cli",
3
- "version": "0.4.2",
3
+ "version": "1.0.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",
@@ -23,17 +23,18 @@
23
23
  ".": {
24
24
  "types": "./index.d.ts",
25
25
  "import": "./index.js"
26
- }
26
+ },
27
+ "./package.json": "./package.json"
27
28
  },
28
29
  "bin": {
29
30
  "savvy": "bin/savvy.js"
30
31
  },
31
32
  "dependencies": {
32
- "@effect/cli": "^0.75.1",
33
+ "@effect/cli": "^0.75.2",
33
34
  "@effect/platform": "^0.96.1",
34
- "@effect/platform-node": "^0.106.0",
35
- "@savvy-web/silk-effects": "1.0.1",
36
- "effect": "^3.21.2",
35
+ "@effect/platform-node": "^0.107.0",
36
+ "@savvy-web/silk-effects": "1.1.0",
37
+ "effect": "^3.21.3",
37
38
  "jsonc-effect": "^0.2.1",
38
39
  "workspaces-effect": "^1.2.0",
39
40
  "yaml": "^2.9.0"
@@ -1,108 +0,0 @@
1
- import { Command, Options } from "@effect/cli";
2
- import { Changesets } from "@savvy-web/silk-effects";
3
- import { Effect, Option } from "effect";
4
- import { resolve } from "node:path";
5
-
6
- //#region src/commands/changeset/commands/analyze-branch.ts
7
- /**
8
- * `analyze-branch` command -- diff the current branch and classify every
9
- * changed file.
10
- *
11
- * @remarks
12
- * Wraps {@link BranchAnalyzer.analyzeBranch}. Returns the merge-base SHA,
13
- * the per-file classification, the deduped package list affected by the
14
- * branch, and the list of paths that didn't map to any release surface
15
- * (candidates for an AskUserQuestion in agent workflows).
16
- *
17
- * @example
18
- * ```bash
19
- * savvy changeset analyze-branch
20
- * savvy changeset analyze-branch --base main --json
21
- * savvy changeset analyze-branch --cwd ./project --base develop
22
- * ```
23
- *
24
- * @internal
25
- */
26
- const { BranchAnalyzer } = Changesets;
27
- /* v8 ignore start -- CLI option definitions */
28
- const cwdOption = Options.directory("cwd").pipe(Options.withDescription("Project root (defaults to the current working directory)"), Options.withDefault("."));
29
- const baseOption = Options.text("base").pipe(Options.withDescription("Override the base branch (defaults to config baseBranch or origin/HEAD)"), Options.optional);
30
- const jsonOption = Options.boolean("json").pipe(Options.withDescription("Emit JSON instead of human-readable output"), Options.withDefault(false));
31
- /* v8 ignore stop */
32
- /**
33
- * Render a {@link BranchAnalysis} as human-readable text.
34
- *
35
- * @internal
36
- */
37
- function renderHuman(analysis) {
38
- const lines = [];
39
- lines.push(`Base branch: ${analysis.baseBranch}`);
40
- lines.push(`Merge base SHA: ${analysis.mergeBaseSha}`);
41
- lines.push("");
42
- if (analysis.packagesAffected.length > 0) {
43
- lines.push(`Packages affected (${analysis.packagesAffected.length}):`);
44
- for (const p of analysis.packagesAffected) lines.push(` ${p}`);
45
- } else lines.push("Packages affected: (none)");
46
- lines.push("");
47
- if (analysis.files.length === 0) lines.push("Changes: (no files changed)");
48
- else {
49
- lines.push(`Changes (${analysis.files.length}):`);
50
- const statusGlyph = {
51
- added: "A",
52
- modified: "M",
53
- deleted: "D",
54
- renamed: "R",
55
- copied: "C",
56
- typechange: "T",
57
- unmerged: "U",
58
- unknown: "?"
59
- };
60
- for (const f of analysis.files) {
61
- const glyph = statusGlyph[f.status] ?? "?";
62
- const owner = f.package ?? "<unmapped>";
63
- const reason = f.reason === "workspace" ? "workspace" : f.reason !== null ? `${f.reason.kind}: ${f.reason.glob}` : "—";
64
- lines.push(` ${glyph} ${f.path}\t${owner}\t${reason}`);
65
- }
66
- }
67
- if (analysis.unmappedFiles.length > 0) {
68
- lines.push("");
69
- lines.push(`Unmapped (${analysis.unmappedFiles.length}):`);
70
- for (const p of analysis.unmappedFiles) lines.push(` ${p}`);
71
- }
72
- return lines.join("\n");
73
- }
74
- /**
75
- * Resolve cwd + base branch, invoke `BranchAnalyzer.analyzeBranch`, and
76
- * render the result. Sets `process.exitCode = 1` on `ConfigurationError`
77
- * or `GitError`.
78
- *
79
- * @internal
80
- */
81
- function runAnalyzeBranch(cwd, base, json) {
82
- return Effect.gen(function* () {
83
- const analyzer = yield* BranchAnalyzer;
84
- const resolvedCwd = resolve(cwd);
85
- const baseBranch = Option.getOrUndefined(base);
86
- const analysis = yield* analyzer.analyzeBranch(resolvedCwd, baseBranch ? { baseBranch } : void 0).pipe(Effect.catchTags({
87
- ConfigurationError: (err) => {
88
- process.exitCode = 1;
89
- return Effect.fail(err);
90
- },
91
- GitError: (err) => {
92
- process.exitCode = 1;
93
- return Effect.fail(err);
94
- }
95
- }));
96
- const output = json ? JSON.stringify(analysis, null, 2) : renderHuman(analysis);
97
- yield* Effect.log(output);
98
- });
99
- }
100
- /* v8 ignore next 6 -- CLI registration */
101
- const analyzeBranchCommand = Command.make("analyze-branch", {
102
- cwd: cwdOption,
103
- base: baseOption,
104
- json: jsonOption
105
- }, ({ cwd, base, json }) => runAnalyzeBranch(cwd, base, json)).pipe(Command.withDescription("Diff the current branch and classify every changed file by owning package"));
106
-
107
- //#endregion
108
- export { analyzeBranchCommand };
@@ -1,69 +0,0 @@
1
- import { Args, Command, Options } from "@effect/cli";
2
- import { Changesets } from "@savvy-web/silk-effects";
3
- import { Effect } from "effect";
4
- import { resolve } from "node:path";
5
-
6
- //#region src/commands/changeset/commands/classify.ts
7
- /**
8
- * `classify` command -- map one or more paths to their owning package.
9
- *
10
- * @remarks
11
- * Wraps {@link ConfigInspector.classify}. Each path resolves to a package
12
- * via (in order): workspace match → `additionalScopes` glob → `versionFiles`
13
- * glob → `null` (unmapped).
14
- *
15
- * @example
16
- * ```bash
17
- * savvy changeset classify packages/foo/src/index.ts plugin/SKILL.md
18
- * savvy changeset classify --cwd ./monorepo plugin/x.md --json
19
- * ```
20
- *
21
- * @internal
22
- */
23
- const { ConfigInspector } = Changesets;
24
- /* v8 ignore start -- CLI option definitions */
25
- const pathsArg = Args.text({ name: "path" }).pipe(Args.repeated);
26
- const cwdOption = Options.directory("cwd").pipe(Options.withDescription("Project root (defaults to the current working directory)"), Options.withDefault("."));
27
- const jsonOption = Options.boolean("json").pipe(Options.withDescription("Emit JSON instead of human-readable output"), Options.withDefault(false));
28
- /* v8 ignore stop */
29
- /**
30
- * Format a single {@link Classification} as a human-readable line.
31
- *
32
- * @internal
33
- */
34
- function renderClassificationLine(c) {
35
- if (c.package === null) return `${c.path}\t<unmapped>`;
36
- if (c.reason === "workspace") return `${c.path}\t${c.package}\tworkspace`;
37
- if (c.reason !== null) return `${c.path}\t${c.package}\t${c.reason.kind}: ${c.reason.glob}`;
38
- return `${c.path}\t${c.package}`;
39
- }
40
- /**
41
- * Resolve the cwd, invoke `ConfigInspector.classify`, and render the result.
42
- * Sets `process.exitCode = 1` on `ConfigurationError`.
43
- *
44
- * @internal
45
- */
46
- function runClassify(cwd, paths, json) {
47
- return Effect.gen(function* () {
48
- const inspector = yield* ConfigInspector;
49
- const resolvedCwd = resolve(cwd);
50
- const classifications = yield* inspector.classify(resolvedCwd, paths).pipe(Effect.catchTag("ConfigurationError", (err) => {
51
- process.exitCode = 1;
52
- return Effect.fail(err);
53
- }));
54
- if (json) {
55
- yield* Effect.log(JSON.stringify(classifications, null, 2));
56
- return;
57
- }
58
- for (const c of classifications) yield* Effect.log(renderClassificationLine(c));
59
- });
60
- }
61
- /* v8 ignore next 6 -- CLI registration */
62
- const classifyCommand = Command.make("classify", {
63
- paths: pathsArg,
64
- cwd: cwdOption,
65
- json: jsonOption
66
- }, ({ paths, cwd, json }) => runClassify(cwd, paths, json)).pipe(Command.withDescription("Map paths to their owning package per .changeset/config.json"));
67
-
68
- //#endregion
69
- export { classifyCommand };
@@ -1,100 +0,0 @@
1
- import { Args, Command, Options } from "@effect/cli";
2
- import { Changesets } from "@savvy-web/silk-effects";
3
- import { Effect } from "effect";
4
- import { resolve } from "node:path";
5
-
6
- //#region src/commands/changeset/commands/config-show.ts
7
- /**
8
- * `config show` command -- emit the resolved `.changeset/config.json`.
9
- *
10
- * @remarks
11
- * Wraps {@link ConfigInspector.inspect} and renders either human-readable
12
- * output (default) or JSON (`--json` / `--format=json`). The same data shape
13
- * the agent consumes is what the user sees, so debugging an unexpected
14
- * classification result becomes a single `savvy changeset config show --json`
15
- * invocation.
16
- *
17
- * @example
18
- * ```bash
19
- * savvy changeset config show
20
- * savvy changeset config show --json
21
- * savvy changeset config show ./path/to/project --json
22
- * ```
23
- *
24
- * @internal
25
- */
26
- const { ConfigInspector } = Changesets;
27
- /* v8 ignore start -- CLI option definitions */
28
- const dirArg = Args.directory({ name: "dir" }).pipe(Args.withDefault("."));
29
- const jsonOption = Options.boolean("json").pipe(Options.withDescription("Emit JSON instead of human-readable output"), Options.withDefault(false));
30
- /* v8 ignore stop */
31
- /**
32
- * Render an {@link InspectedConfig} as human-readable text.
33
- *
34
- * @internal
35
- */
36
- function renderHuman(config) {
37
- const lines = [];
38
- lines.push(`Config: ${config.configPath}`);
39
- lines.push(`Project: ${config.projectDir}`);
40
- lines.push(`Changelog: ${config.changelog ?? "(none)"}`);
41
- lines.push(`Base: ${config.baseBranch}`);
42
- lines.push(`Access: ${config.access}`);
43
- if (config.ignore.length > 0) lines.push(`Ignore: ${config.ignore.join(", ")}`);
44
- if (config.legacyVersionFilesUsed) {
45
- lines.push("");
46
- lines.push("⚠ This config still uses the deprecated top-level `versionFiles[]`.");
47
- lines.push(" Migrate to `packages[<name>].versionFiles` — required for 1.0.0.");
48
- }
49
- lines.push("");
50
- if (config.packages.length === 0) {
51
- lines.push("Packages: (none declared)");
52
- return lines.join("\n");
53
- }
54
- lines.push(`Packages (${config.packages.length}):`);
55
- for (const pkg of config.packages) {
56
- lines.push("");
57
- lines.push(` ${pkg.name} v${pkg.version}`);
58
- lines.push(` workspace: ${pkg.workspaceDir}`);
59
- if (pkg.additionalScopes.length > 0) {
60
- lines.push(` additionalScopes (${pkg.additionalScopes.length}):`);
61
- for (const g of pkg.additionalScopes) lines.push(` - ${g}`);
62
- lines.push(` additionalScopeFiles (${pkg.additionalScopeFiles.length}):`);
63
- for (const f of pkg.additionalScopeFiles) lines.push(` ${f}`);
64
- }
65
- if (pkg.versionFiles.length > 0) {
66
- lines.push(` versionFiles (${pkg.versionFiles.length}):`);
67
- for (const vf of pkg.versionFiles) {
68
- lines.push(` ${vf.glob} → ${vf.paths.join(", ")} (${vf.matchedFiles.length} file${vf.matchedFiles.length === 1 ? "" : "s"} matched)`);
69
- for (const f of vf.matchedFiles) lines.push(` ${f}`);
70
- }
71
- }
72
- }
73
- return lines.join("\n");
74
- }
75
- /**
76
- * Resolve the project dir, invoke `ConfigInspector.inspect`, and render the
77
- * result. Sets `process.exitCode = 1` on `ConfigurationError`.
78
- *
79
- * @internal
80
- */
81
- function runConfigShow(dir, json) {
82
- return Effect.gen(function* () {
83
- const inspector = yield* ConfigInspector;
84
- const resolved = resolve(dir);
85
- const config = yield* inspector.inspect(resolved).pipe(Effect.catchTag("ConfigurationError", (err) => {
86
- process.exitCode = 1;
87
- return Effect.fail(err);
88
- }));
89
- const output = json ? JSON.stringify(config, null, 2) : renderHuman(config);
90
- yield* Effect.log(output);
91
- });
92
- }
93
- /* v8 ignore next 6 -- CLI registration */
94
- const configShowCommand = Command.make("show", {
95
- dir: dirArg,
96
- json: jsonOption
97
- }, ({ dir, json }) => runConfigShow(dir, json)).pipe(Command.withDescription("Print the resolved .changeset/config.json"));
98
-
99
- //#endregion
100
- export { configShowCommand };
@@ -1,96 +0,0 @@
1
- import { Args, Command, Options } from "@effect/cli";
2
- import { Changesets } from "@savvy-web/silk-effects";
3
- import { Effect } from "effect";
4
- import { resolve } from "node:path";
5
-
6
- //#region src/commands/changeset/commands/release-surface.ts
7
- /**
8
- * `release-surface` command -- list every path owned by a named package.
9
- *
10
- * @remarks
11
- * For a given package name, emits the workspace directory, every
12
- * `additionalScopes` glob and its materialized files, and every
13
- * `versionFiles` entry and its targets. Useful for debugging "why is this
14
- * path attributed to this package?" or "what's actually in this package's
15
- * release surface right now?"
16
- *
17
- * @example
18
- * ```bash
19
- * savvy changeset release-surface @savvy-web/changesets
20
- * savvy changeset release-surface @scope/foo --json
21
- * ```
22
- *
23
- * @internal
24
- */
25
- const { ConfigInspector, ConfigurationError } = Changesets;
26
- /* v8 ignore start -- CLI option definitions */
27
- const packageArg = Args.text({ name: "package" });
28
- const cwdOption = Options.directory("cwd").pipe(Options.withDescription("Project root (defaults to the current working directory)"), Options.withDefault("."));
29
- const jsonOption = Options.boolean("json").pipe(Options.withDescription("Emit JSON instead of human-readable output"), Options.withDefault(false));
30
- /* v8 ignore stop */
31
- /**
32
- * Render a single package's resolved scope as human-readable text.
33
- *
34
- * @internal
35
- */
36
- function renderHuman(pkg) {
37
- const lines = [];
38
- lines.push(`Package: ${pkg.name} v${pkg.version}`);
39
- lines.push(`Workspace: ${pkg.workspaceDir}`);
40
- if (pkg.additionalScopes.length === 0 && pkg.versionFiles.length === 0) {
41
- lines.push("");
42
- lines.push("(no additionalScopes or versionFiles — workspace dir is the entire release surface)");
43
- return lines.join("\n");
44
- }
45
- if (pkg.additionalScopes.length > 0) {
46
- lines.push("");
47
- lines.push(`additionalScopes (${pkg.additionalScopes.length} glob${pkg.additionalScopes.length === 1 ? "" : "s"}):`);
48
- for (const g of pkg.additionalScopes) lines.push(` - ${g}`);
49
- lines.push(`Resolved files (${pkg.additionalScopeFiles.length}):`);
50
- for (const f of pkg.additionalScopeFiles) lines.push(` ${f}`);
51
- }
52
- if (pkg.versionFiles.length > 0) {
53
- lines.push("");
54
- lines.push(`versionFiles (${pkg.versionFiles.length}):`);
55
- for (const vf of pkg.versionFiles) {
56
- lines.push(` ${vf.glob} → ${vf.paths.join(", ")}`);
57
- for (const f of vf.matchedFiles) lines.push(` ${f}`);
58
- }
59
- }
60
- return lines.join("\n");
61
- }
62
- /**
63
- * Resolve cwd, invoke `ConfigInspector.inspect`, find the named package's
64
- * scope, and render it. Sets `process.exitCode = 1` on any error.
65
- *
66
- * @internal
67
- */
68
- function runReleaseSurface(cwd, pkgName, json) {
69
- return Effect.gen(function* () {
70
- const inspector = yield* ConfigInspector;
71
- const resolvedCwd = resolve(cwd);
72
- const config = yield* inspector.inspect(resolvedCwd).pipe(Effect.catchTag("ConfigurationError", (err) => {
73
- process.exitCode = 1;
74
- return Effect.fail(err);
75
- }));
76
- const scope = config.packages.find((p) => p.name === pkgName);
77
- if (!scope) {
78
- process.exitCode = 1;
79
- return yield* Effect.fail(new ConfigurationError({
80
- field: `packages["${pkgName}"]`,
81
- reason: `Package "${pkgName}" is not declared in .changeset/config.json#packages. Declared packages: ${config.packages.map((p) => p.name).join(", ") || "(none)"}.`
82
- }));
83
- }
84
- const output = json ? JSON.stringify(scope, null, 2) : renderHuman(scope);
85
- yield* Effect.log(output);
86
- });
87
- }
88
- /* v8 ignore next 6 -- CLI registration */
89
- const releaseSurfaceCommand = Command.make("release-surface", {
90
- package: packageArg,
91
- cwd: cwdOption,
92
- json: jsonOption
93
- }, ({ package: pkgName, cwd, json }) => runReleaseSurface(cwd, pkgName, json)).pipe(Command.withDescription("Print every path owned by a package — workspace dir, additionalScopes, versionFiles"));
94
-
95
- //#endregion
96
- export { releaseSurfaceCommand };