@savvy-web/cli 1.6.1 → 2.0.1

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 CHANGED
@@ -5,15 +5,16 @@ import { commitCommand } from "../commands/commit/index.js";
5
5
  import { initCommand } from "../commands/init.js";
6
6
  import { lintCommand } from "../commands/lint/index.js";
7
7
  import { reposCommand } from "../commands/repos/index.js";
8
- import { Command } from "@effect/cli";
9
- import { NodeContext, NodeRuntime } from "@effect/platform-node";
10
- import { BiomeSchemaSyncLive, ChangesetConfigLive, ChangesetConfigReaderLive, Changesets, ConfigDiscoveryLive, ManagedSectionLive, Repos, SilkPublishabilityDetectorLive, ToolDiscoveryLive, VersioningStrategyLive } from "@savvy-web/silk-effects";
8
+ import { NodeRuntime, NodeServices } from "@effect/platform-node";
9
+ import { Git } from "@effected/git";
10
+ import { PackageManagerDetector, WorkspaceDiscovery, WorkspaceRoot } from "@effected/workspaces";
11
+ import { BiomeSchemaSyncLive, ChangesetConfigReaderLive, Changesets, ConfigDiscoveryLive, ManagedSectionLive, Repos, SilkPublishabilityDetectorLive, ToolDiscoveryLive, VersioningStrategyLive } from "@savvy-web/silk-effects";
11
12
  import { Effect, Layer } from "effect";
12
- import { PackageManagerDetectorLive, PointInTimeWorkspaceLive, PublishabilityDetectorLive, WorkspaceDiscoveryLive, WorkspaceRootLive } from "workspaces-effect";
13
+ import { Command } from "effect/unstable/cli";
13
14
 
14
15
  //#region src/cli/index.ts
15
16
  /**
16
- * Root `savvy` CLI entry point using `@effect/cli`.
17
+ * Root `savvy` CLI entry point using `effect/unstable/cli`.
17
18
  *
18
19
  * @remarks
19
20
  * Assembles the five Phase-B command pieces — the `init` and `check` top-level
@@ -21,23 +22,30 @@ import { PackageManagerDetectorLive, PointInTimeWorkspaceLive, PublishabilityDet
21
22
  * single `savvy` root command, then provides the merged runtime Layer stack
22
23
  * that satisfies every command's service requirements.
23
24
  *
24
- * The layer stack is the union of the three standalone CLIs' stacks
25
- * (`@savvy-web/changesets`, `@savvy-web/commitlint`, `@savvy-web/lint-staged`),
26
- * with each service's transitive dependencies wired:
25
+ * The layer stack wires the silk-effects Lives over their v4 requirement
26
+ * channels:
27
27
  *
28
- * - `NodeContext.layer` — `FileSystem`, `Path`, and `CommandExecutor`, consumed
29
- * by every config reader, workspace service, and tool-discovery layer.
30
- * - Workspace services `WorkspaceRootLive`, `PackageManagerDetectorLive`, and
31
- * `WorkspaceDiscoveryLive` (provided `WorkspaceRootLive`), the minimal hand-wired
32
- * trio shared by the three source CLIs.
28
+ * - `NodeServices.layer` — `FileSystem`, `Path`, `ChildProcessSpawner`, `Stdio`,
29
+ * and `Terminal`, consumed by every config reader, workspace service, git
30
+ * layer, and the CLI framework itself.
31
+ * - `Git.layer` (from `@effected/git`) the typed git service backing
32
+ * `BranchAnalyzer`, `ReposManager`, the commit hooks
33
+ * (`readBranchInfo` / `readSigningDiagnostic`), and `detectGitHubRepo`.
34
+ * - Workspace services from `@effected/workspaces` — `WorkspaceRoot.layer`,
35
+ * `PackageManagerDetector.layer`, and a single root-bound
36
+ * `WorkspaceDiscovery.layer()` (the kit resolves the root through
37
+ * `WorkspaceRoot` from `process.cwd()` lazily on first use, so the CLI's
38
+ * startup cwd is the discovery root).
33
39
  * - Flat silk-effects services — `ChangesetConfigReaderLive`,
34
40
  * `SilkPublishabilityDetectorLive`, `ManagedSectionLive`, `BiomeSchemaSyncLive`,
35
- * `ConfigDiscoveryLive`, `ToolDiscoveryLive`, and `VersioningStrategyLive`
36
- * (provided `ChangesetConfigReaderLive`).
37
- * - Changesets-namespace services `Changesets.ConfigInspectorLive` (provided
38
- * `ChangesetConfigReaderLive`), `Changesets.ReleasePlannerLive` (provided
39
- * `ConfigInspectorLive`), and `Changesets.BranchAnalyzerLive`, which shares
40
- * the single `ConfigInspectorLive` instance built once via `provideMerge`.
41
+ * `ConfigDiscoveryLive`, `ToolDiscoveryLive`, and `VersioningStrategyLive`.
42
+ * - Changesets-namespace services — `Changesets.ConfigInspectorLive`,
43
+ * `Changesets.ReleasePlannerLive`, and `Changesets.BranchAnalyzerLive`
44
+ * sharing a single `ConfigInspector` via `provideMerge`. `DepsRegen` is NOT
45
+ * part of AppLive: its graph is root-bound at layer build, so the deps
46
+ * commands compose `Changesets.makeDepsRegenDefault({ cwd })` per invocation
47
+ * against their parsed `--cwd` (platform services flow up to
48
+ * `NodeServices.layer` here).
41
49
  *
42
50
  * The CLI version is injected at build time via `process.env.__PACKAGE_VERSION__`.
43
51
  *
@@ -56,99 +64,65 @@ const rootCommand = Command.make("savvy").pipe(Command.withSubcommands([
56
64
  lintCommand,
57
65
  reposCommand
58
66
  ]));
59
- const cli = Command.run(rootCommand, {
60
- name: "savvy",
61
- version: "1.6.1"
62
- });
63
67
  /**
64
- * Shared base layer: workspace services, the changeset config reader, and the
65
- * leaf silk-effects services that depend only on the platform. Built once and
66
- * `provideMerge`d so the upper services draw from it AND it stays exposed in the
67
- * final context for the handlers that yield these tags directly.
68
- *
69
- * @remarks
70
- * The workspace services (`WorkspaceRoot`, `WorkspaceDiscovery`,
71
- * `PackageManagerDetector`) are wired as a self-contained unit:
72
- * `WorkspaceDiscoveryLive` is provided `WorkspaceRootLive`, and the bare
73
- * `WorkspaceRootLive` / `PackageManagerDetectorLive` are exposed for the
74
- * handlers that yield those tags directly. This mirrors the three source CLIs'
75
- * minimal workspace wiring rather than pulling in the heavier `WorkspacesLive`
76
- * (which also forks `DependencyGraph` / `PublishabilityDetector` background work).
68
+ * CLI application: reads argv from the Stdio service provided by NodeServices.
69
+ * (v4's `Command.run` takes only `version` the name comes from the root command.)
70
+ */
71
+ const cli = Command.run(rootCommand, { version: "2.0.1" });
72
+ /**
73
+ * Shared workspace services from `@effected/workspaces`, wired as a
74
+ * self-contained unit and built ONCE (layers memoize by reference).
75
+ * `WorkspaceDiscovery.layer()` is root-bound at first use via `WorkspaceRoot`
76
+ * from the CLI's startup cwd the single-root semantics every downstream
77
+ * consumer (analyzer, deps-regen, publishability) assumes.
77
78
  */
78
- const WorkspaceLive = Layer.mergeAll(WorkspaceRootLive, PackageManagerDetectorLive, WorkspaceDiscoveryLive.pipe(Layer.provide(WorkspaceRootLive)));
79
+ const WorkspaceRootLive = WorkspaceRoot.layer;
80
+ const WorkspaceDiscoveryLive = WorkspaceDiscovery.layer().pipe(Layer.provide(WorkspaceRootLive));
81
+ const WorkspaceLive = Layer.mergeAll(WorkspaceRootLive, PackageManagerDetector.layer, WorkspaceDiscoveryLive);
82
+ /**
83
+ * The typed git service, built once over the platform spawner and shared by
84
+ * every git-consuming layer AND exposed to handlers that yield `Git` directly
85
+ * (commit hooks, `detectGitHubRepo`).
86
+ */
87
+ const GitLive = Git.layer;
79
88
  /**
80
89
  * Base layer membership: silk-effects leaf services (`ManagedSection`,
81
90
  * `BiomeSchemaSync`, `ConfigDiscovery`, `SilkPublishabilityDetector`) that
82
91
  * depend only on the platform, plus the changeset base layers
83
- * (`WorkspaceLive`, `ChangesetConfigReader`) that `AppLive`'s upper services
84
- * build upon.
92
+ * (`WorkspaceLive`, `ChangesetConfigReader`, `GitLive`) that `AppLive`'s
93
+ * upper services build upon.
85
94
  */
86
- const BaseLive = Layer.mergeAll(WorkspaceLive, ChangesetConfigReaderLive, ManagedSectionLive, BiomeSchemaSyncLive, ConfigDiscoveryLive, SilkPublishabilityDetectorLive);
95
+ const BaseLive = Layer.mergeAll(WorkspaceLive, GitLive, ChangesetConfigReaderLive, ManagedSectionLive, BiomeSchemaSyncLive, ConfigDiscoveryLive, SilkPublishabilityDetectorLive);
87
96
  /**
88
- * Merged runtime Layer stack — the union of the three source CLIs' stacks with
89
- * every inter-layer dependency satisfied.
90
- *
91
- * @remarks
92
- * The upper services depend on members of `BaseLive`:
93
- * `ToolDiscoveryLive` needs `WorkspaceRoot`, `PackageManagerDetector`, and
94
- * `CommandExecutor`; `VersioningStrategyLive` needs `ChangesetConfigReader`;
95
- * `Changesets.ConfigInspectorLive` needs `ChangesetConfigReader`,
96
- * `WorkspaceDiscovery`, and `FileSystem` (the last for its publishConfig-driven
97
- * fallback when no explicit `packages` record is configured);
98
- * `Changesets.BranchAnalyzerLive` needs `ConfigInspector`;
99
- * `Changesets.ReleasePlannerLive` needs `ConfigInspector`.
100
- *
101
97
  * `ConfigInspectorLive` is built once via {@link Layer.provideMerge}: the merge
102
98
  * feeds that single `ConfigInspector` instance into `ReleasePlannerLive` and
103
99
  * `BranchAnalyzerLive` AND re-exposes it for the surviving `config validate`
104
100
  * handler that yields it directly, so it is never constructed twice per run.
105
- *
106
- * `Changesets.DepsRegenLive` (the `deps regen`/`deps detect` orchestration
107
- * service) needs `ConfigInspector`, `WorkspaceDiscovery` (from
108
- * `BaseLive`/`InspectorAndAnalyzerLive`), plus `PointInTimeWorkspace` and
109
- * `PublishabilityDetector` from `workspaces-effect`, and `ChangesetConfig`
110
- * (provided its own `ChangesetConfigReaderLive`, since `Layer.mergeAll` does
111
- * not cross-feed sibling layers) — none of which the other commands need, so
112
- * they're composed only for `DepsRegenGroupLive`. `PointInTimeWorkspaceLive`
113
- * in turn needs `WorkspaceRoot`, `WorkspaceDiscovery`, `CommandExecutor`,
114
- * `FileSystem`, `Path` — the workspace pair supplied by `WorkspaceLive`, the
115
- * platform trio by `NodeContext.layer` below.
116
- * `DepsRegenGroupLive` reuses the exact `InspectorAndAnalyzerLive` layer
117
- * reference (not a fresh copy), so Effect's layer memoization builds that
118
- * single `ConfigInspector` instance once and shares it here too.
119
- *
120
- * `provideMerge(BaseLive)` feeds the remaining deps and re-exposes the base
121
- * services for handlers that yield them directly. `provideMerge(NodeContext.layer)`
122
- * supplies `FileSystem`, `Path`, and `CommandExecutor` to everything underneath.
101
+ * `BranchAnalyzerLive`'s v4 requirements are `ConfigInspector |
102
+ * ChildProcessSpawner` (the spawner flows up to `NodeServices.layer`).
123
103
  */
124
104
  const InspectorAndAnalyzerLive = Changesets.BranchAnalyzerLive.pipe(Layer.provideMerge(Changesets.ReleasePlannerLive), Layer.provideMerge(Changesets.ConfigInspectorLive));
125
105
  /**
126
- * `Changesets.DepsRegen`, fully composed except for the base services
127
- * (`WorkspaceDiscovery`, platform layers) supplied by `BaseLive` /
128
- * `NodeContext.layer`. `PointInTimeWorkspaceLive` is provided `WorkspaceLive`
129
- * so its `WorkspaceRoot`/`WorkspaceDiscovery` requirements resolve; the
130
- * remaining `CommandExecutor`/`FileSystem`/`Path` flow up to `NodeContext.layer`.
131
- */
132
- const DepsRegenGroupLive = Changesets.DepsRegenLive.pipe(Layer.provide(InspectorAndAnalyzerLive), Layer.provide(PointInTimeWorkspaceLive.pipe(Layer.provide(WorkspaceLive))), Layer.provide(PublishabilityDetectorLive), Layer.provide(ChangesetConfigLive.pipe(Layer.provide(ChangesetConfigReaderLive))));
133
- /**
134
- * `Repos.ReposManager`, provided its `Repos.ReposConfigStoreLive` dependency.
135
- * Both draw their platform requirements (`FileSystem`, `Path`,
136
- * `CommandExecutor`) from `NodeContext.layer` below.
106
+ * `Repos.ReposManager`, provided its `Repos.ReposConfigStoreLive` dependency
107
+ * and the shared `Git` service. Platform requirements (`FileSystem`, `Path`)
108
+ * flow up to `NodeServices.layer`.
137
109
  */
138
- const ReposGroupLive = Repos.ReposManagerLive.pipe(Layer.provide(Repos.ReposConfigStoreLive));
139
- const AppLive = Layer.mergeAll(ToolDiscoveryLive, VersioningStrategyLive, InspectorAndAnalyzerLive, DepsRegenGroupLive, ReposGroupLive).pipe(Layer.provideMerge(BaseLive), Layer.provideMerge(NodeContext.layer));
110
+ const ReposGroupLive = Repos.ReposManagerLive.pipe(Layer.provide(Repos.ReposConfigStoreLive), Layer.provide(GitLive));
111
+ const AppLive = Layer.mergeAll(ToolDiscoveryLive, VersioningStrategyLive, InspectorAndAnalyzerLive, ReposGroupLive).pipe(Layer.provideMerge(BaseLive), Layer.provideMerge(NodeServices.layer));
140
112
  /**
141
113
  * Bootstrap and run the `savvy` CLI application.
142
114
  *
143
115
  * @remarks
144
- * Builds an Effect from the parsed `process.argv`, provides the merged layer
145
- * stack, and hands execution to `NodeRuntime.runMain`.
116
+ * `Command.run` returns an Effect reading `process.argv` from the Stdio
117
+ * service; the merged layer stack is provided and execution handed to
118
+ * `NodeRuntime.runMain`, whose default reporting covers defects (the v3
119
+ * `Cause.defects` wrapper is gone). No type casts: the layer graph is
120
+ * validated by the compiler.
146
121
  *
147
122
  * @internal
148
123
  */
149
124
  function runCli() {
150
- const main = Effect.suspend(() => cli(process.argv)).pipe(Effect.provide(AppLive));
151
- NodeRuntime.runMain(main);
125
+ NodeRuntime.runMain(cli.pipe(Effect.provide(AppLive)));
152
126
  }
153
127
  /* v8 ignore stop */
154
128
 
@@ -1,6 +1,6 @@
1
- import { Args, Command } from "@effect/cli";
2
1
  import { Changesets } from "@savvy-web/silk-effects";
3
2
  import { Effect } from "effect";
3
+ import { Argument, Command } from "effect/unstable/cli";
4
4
  import { resolve } from "node:path";
5
5
 
6
6
  //#region src/commands/changeset/commands/check.ts
@@ -27,7 +27,7 @@ import { resolve } from "node:path";
27
27
  */
28
28
  const { ChangesetLinter } = Changesets;
29
29
  /* v8 ignore next */
30
- const dirArg = Args.directory({ name: "dir" }).pipe(Args.withDefault(".changeset"));
30
+ const dirArg = Argument.directory("dir").pipe(Argument.withDefault(".changeset"));
31
31
  /**
32
32
  * Run the check validation pipeline on all changeset files in `dir`.
33
33
  *
@@ -1,6 +1,6 @@
1
- import { Args, Command } from "@effect/cli";
2
1
  import { Changesets } from "@savvy-web/silk-effects";
3
2
  import { Effect } from "effect";
3
+ import { Argument, Command } from "effect/unstable/cli";
4
4
  import { resolve } from "node:path";
5
5
 
6
6
  //#region src/commands/changeset/commands/config-validate.ts
@@ -26,7 +26,7 @@ import { resolve } from "node:path";
26
26
  */
27
27
  const { ConfigInspector } = Changesets;
28
28
  /* v8 ignore next */
29
- const dirArg = Args.directory({ name: "dir" }).pipe(Args.withDefault("."));
29
+ const dirArg = Argument.directory("dir").pipe(Argument.withDefault("."));
30
30
  /**
31
31
  * Run validation. Logs a one-line OK on success; logs the error and sets
32
32
  * `process.exitCode = 1` on failure.
@@ -1,6 +1,7 @@
1
- import { Command, Options } from "@effect/cli";
2
1
  import { Changesets } from "@savvy-web/silk-effects";
3
2
  import { Console, Effect, Option } from "effect";
3
+ import { Command, Flag } from "effect/unstable/cli";
4
+ import { resolve } from "node:path";
4
5
 
5
6
  //#region src/commands/changeset/commands/deps-detect.ts
6
7
  /**
@@ -28,12 +29,12 @@ import { Console, Effect, Option } from "effect";
28
29
  */
29
30
  const { DepsRegen, serializeDependencyTableToMarkdown } = Changesets;
30
31
  /* v8 ignore start -- CLI option definitions */
31
- const fromOption = Options.text("from").pipe(Options.withDescription("Older ref to diff from (defaults to merge-base with base branch)"), Options.optional);
32
- const toOption = Options.text("to").pipe(Options.withDescription("Newer ref to diff to (defaults to working tree)"), Options.optional);
33
- const cwdOption = Options.directory("cwd").pipe(Options.withDescription("Project root (defaults to the current working directory)"), Options.withDefault("."));
34
- const packageOption = Options.text("package").pipe(Options.withDescription("Restrict output to a single workspace package"), Options.optional);
35
- const jsonOption = Options.boolean("json").pipe(Options.withDescription("Emit JSON (default)"), Options.withDefault(false));
36
- const markdownOption = Options.boolean("markdown").pipe(Options.withDescription("Emit one CSH005 markdown block per workspace package"), Options.withDefault(false));
32
+ const fromOption = Flag.string("from").pipe(Flag.withDescription("Older ref to diff from (defaults to merge-base with base branch)"), Flag.optional);
33
+ const toOption = Flag.string("to").pipe(Flag.withDescription("Newer ref to diff to (defaults to working tree)"), Flag.optional);
34
+ const cwdOption = Flag.directory("cwd").pipe(Flag.withDescription("Project root (defaults to the current working directory)"), Flag.withDefault("."));
35
+ const packageOption = Flag.string("package").pipe(Flag.withDescription("Restrict output to a single workspace package"), Flag.optional);
36
+ const jsonOption = Flag.boolean("json").pipe(Flag.withDescription("Emit JSON (default)"), Flag.withDefault(false));
37
+ const markdownOption = Flag.boolean("markdown").pipe(Flag.withDescription("Emit one CSH005 markdown block per workspace package"), Flag.withDefault(false));
37
38
  /* v8 ignore stop */
38
39
  /**
39
40
  * Render a per-workspace diff as markdown — one frontmatter+section block
@@ -64,16 +65,9 @@ function runDepsDetect(cwd, from, to, pkg, json, markdown) {
64
65
  ...Option.isSome(pkg) ? { package: pkg.value } : {},
65
66
  ...Option.isSome(from) ? { from: from.value } : {},
66
67
  ...Option.isSome(to) ? { to: to.value } : {}
67
- }).pipe(Effect.catchTags({
68
- GitError: (err) => {
69
- process.exitCode = 1;
70
- return Effect.fail(err);
71
- },
72
- GitReadError: (err) => {
73
- process.exitCode = 1;
74
- return Effect.fail(err);
75
- }
76
- }))).toWrite.map((entry) => entry.diff);
68
+ }).pipe(Effect.tapError(() => Effect.sync(() => {
69
+ process.exitCode = 1;
70
+ })))).toWrite.map((entry) => entry.diff);
77
71
  if (markdown && !json) {
78
72
  yield* Effect.log(renderMarkdownBlocks(diffs));
79
73
  return;
@@ -89,7 +83,7 @@ const depsDetectCommand = Command.make("detect", {
89
83
  package: packageOption,
90
84
  json: jsonOption,
91
85
  markdown: markdownOption
92
- }, ({ from, to, cwd, package: pkg, json, markdown }) => runDepsDetect(cwd, from, to, pkg, json, markdown)).pipe(Command.withDescription("Compute the dependency diff between two refs"));
86
+ }, ({ from, to, cwd, package: pkg, json, markdown }) => runDepsDetect(cwd, from, to, pkg, json, markdown).pipe(Effect.provide(Changesets.makeDepsRegenDefault({ cwd: resolve(cwd) })))).pipe(Command.withDescription("Compute the dependency diff between two refs"));
93
87
 
94
88
  //#endregion
95
89
  export { depsDetectCommand, renderMarkdownBlocks, runDepsDetect };
@@ -1,6 +1,7 @@
1
- import { Command, Options } from "@effect/cli";
2
1
  import { Changesets } from "@savvy-web/silk-effects";
3
2
  import { Console, Effect, Option } from "effect";
3
+ import { Command, Flag } from "effect/unstable/cli";
4
+ import { resolve } from "node:path";
4
5
 
5
6
  //#region src/commands/changeset/commands/deps-regen.ts
6
7
  /**
@@ -47,11 +48,11 @@ import { Console, Effect, Option } from "effect";
47
48
  */
48
49
  const { DepsRegen } = Changesets;
49
50
  /* v8 ignore start -- CLI option definitions */
50
- const cwdOption = Options.directory("cwd").pipe(Options.withDescription("Project root (defaults to the current working directory)"), Options.withDefault("."));
51
- const baseOption = Options.text("base").pipe(Options.withDescription("Override the base branch (defaults to config baseBranch)"), Options.optional);
52
- const packageOption = Options.text("package").pipe(Options.withDescription("Restrict regeneration to a single workspace package"), Options.optional);
53
- const dryRunOption = Options.boolean("dry-run").pipe(Options.withDescription("Print the plan without writing or deleting"), Options.withDefault(false));
54
- const jsonOption = Options.boolean("json").pipe(Options.withDescription("Emit a structured plan as JSON"), Options.withDefault(false));
51
+ const cwdOption = Flag.directory("cwd").pipe(Flag.withDescription("Project root (defaults to the current working directory)"), Flag.withDefault("."));
52
+ const baseOption = Flag.string("base").pipe(Flag.withDescription("Override the base branch (defaults to config baseBranch)"), Flag.optional);
53
+ const packageOption = Flag.string("package").pipe(Flag.withDescription("Restrict regeneration to a single workspace package"), Flag.optional);
54
+ const dryRunOption = Flag.boolean("dry-run").pipe(Flag.withDescription("Print the plan without writing or deleting"), Flag.withDefault(false));
55
+ const jsonOption = Flag.boolean("json").pipe(Flag.withDescription("Emit a structured plan as JSON"), Flag.withDefault(false));
55
56
  /* v8 ignore stop */
56
57
  /**
57
58
  * Handler exported for direct invocation in tests.
@@ -65,16 +66,9 @@ function runDepsRegen(cwd, base, pkg, dryRun, json) {
65
66
  cwd,
66
67
  ...Option.isSome(base) ? { base: base.value } : {},
67
68
  ...Option.isSome(pkg) ? { package: pkg.value } : {}
68
- }).pipe(Effect.catchTags({
69
- GitError: (err) => {
70
- process.exitCode = 1;
71
- return Effect.fail(err);
72
- },
73
- GitReadError: (err) => {
74
- process.exitCode = 1;
75
- return Effect.fail(err);
76
- }
77
- }));
69
+ }).pipe(Effect.tapError(() => Effect.sync(() => {
70
+ process.exitCode = 1;
71
+ })));
78
72
  if (!dryRun) yield* service.execute(plan);
79
73
  if (json) yield* Console.log(JSON.stringify(plan, null, 2));
80
74
  else yield* renderHumanPlan(plan);
@@ -99,14 +93,14 @@ function renderHumanPlan(plan) {
99
93
  }
100
94
  });
101
95
  }
102
- /* v8 ignore next 8 */
96
+ /* v8 ignore next 12 */
103
97
  const depsRegenCommand = Command.make("regen", {
104
98
  cwd: cwdOption,
105
99
  base: baseOption,
106
100
  package: packageOption,
107
101
  dryRun: dryRunOption,
108
102
  json: jsonOption
109
- }, ({ cwd, base, package: pkg, dryRun, json }) => runDepsRegen(cwd, base, pkg, dryRun, json)).pipe(Command.withDescription("Delete pure dependency changesets and regenerate them from the current diff"));
103
+ }, ({ cwd, base, package: pkg, dryRun, json }) => runDepsRegen(cwd, base, pkg, dryRun, json).pipe(Effect.provide(Changesets.makeDepsRegenDefault({ cwd: resolve(cwd) })))).pipe(Command.withDescription("Delete pure dependency changesets and regenerate them from the current diff"));
110
104
 
111
105
  //#endregion
112
106
  export { depsRegenCommand, runDepsRegen };
@@ -1,10 +1,10 @@
1
+ import { Git } from "@effected/git";
2
+ import { WorkspaceRoot } from "@effected/workspaces";
1
3
  import { Changesets } from "@savvy-web/silk-effects";
2
- import { Data, Effect, Schema } from "effect";
3
- import { WorkspaceRoot } from "workspaces-effect";
4
+ import { Data, Effect, Option, Result, Schema } from "effect";
4
5
  import { join } from "node:path";
5
6
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
6
- import { execSync } from "node:child_process";
7
- import { applyEdits, modify, parse } from "jsonc-effect";
7
+ import { Jsonc, JsoncEdit, JsoncModifier } from "@effected/jsonc";
8
8
 
9
9
  //#region src/commands/changeset/commands/init.ts
10
10
  /**
@@ -117,17 +117,15 @@ var InitError = class extends InitErrorBase {
117
117
  * @internal
118
118
  */
119
119
  function detectGitHubRepo(cwd) {
120
- try {
121
- const url = execSync("git remote get-url origin", {
122
- cwd,
123
- encoding: "utf-8"
124
- }).trim();
125
- const https = url.match(/github\.com\/([^/]+)\/([^/.]+)/);
120
+ return Effect.gen(function* () {
121
+ const url = yield* (yield* Git).remoteUrl(cwd).pipe(Effect.catch(() => Effect.succeed(Option.none())));
122
+ if (Option.isNone(url)) return null;
123
+ const https = url.value.match(/github\.com\/([^/]+)\/([^/.]+)/);
126
124
  if (https) return `${https[1]}/${https[2]}`;
127
- const ssh = url.match(/github\.com:([^/]+)\/([^/.]+)/);
125
+ const ssh = url.value.match(/github\.com:([^/]+)\/([^/.]+)/);
128
126
  if (ssh) return `${ssh[1]}/${ssh[2]}`;
129
- } catch {}
130
- return null;
127
+ return null;
128
+ });
131
129
  }
132
130
  /**
133
131
  * Formatting options for `jsonc-effect` modify operations.
@@ -152,7 +150,7 @@ const JSONC_FORMAT = {
152
150
  * @internal
153
151
  */
154
152
  function resolveWorkspaceRoot(cwd) {
155
- return WorkspaceRoot.pipe(Effect.flatMap((wr) => wr.find(cwd)), Effect.catchAll(() => Effect.succeed(cwd)));
153
+ return WorkspaceRoot.pipe(Effect.flatMap((wr) => wr.find(cwd)), Effect.catch(() => Effect.succeed(cwd)));
156
154
  }
157
155
  /**
158
156
  * Find the first existing markdownlint config file from candidate paths.
@@ -331,30 +329,30 @@ function handleBaseMarkdownlint(root) {
331
329
  reason: error instanceof Error ? error.message : String(error)
332
330
  }));
333
331
  }
334
- let parsed = yield* parse(text);
332
+ let parsed = yield* Jsonc.parse(text);
335
333
  const currentRules = Array.isArray(parsed.customRules) ? parsed.customRules : null;
336
334
  if (currentRules === null) {
337
- const edits = yield* modify(text, ["customRules"], [CUSTOM_RULES_ENTRY], { formattingOptions: JSONC_FORMAT });
338
- text = yield* applyEdits(text, edits);
335
+ const edits = yield* JsoncModifier.modify(text, ["customRules"], [CUSTOM_RULES_ENTRY], { formattingOptions: JSONC_FORMAT });
336
+ text = JsoncEdit.applyAll(text, edits);
339
337
  } else {
340
338
  const desired = currentRules.filter((r) => r !== LEGACY_CUSTOM_RULES_ENTRY && r !== CUSTOM_RULES_ENTRY);
341
339
  desired.push(CUSTOM_RULES_ENTRY);
342
340
  if (desired.length !== currentRules.length || desired.some((r, i) => r !== currentRules[i])) {
343
- const edits = yield* modify(text, ["customRules"], desired, { formattingOptions: JSONC_FORMAT });
344
- text = yield* applyEdits(text, edits);
341
+ const edits = yield* JsoncModifier.modify(text, ["customRules"], desired, { formattingOptions: JSONC_FORMAT });
342
+ text = JsoncEdit.applyAll(text, edits);
345
343
  }
346
344
  }
347
- parsed = yield* parse(text);
345
+ parsed = yield* Jsonc.parse(text);
348
346
  const currentConfig = parsed.config;
349
347
  if (typeof currentConfig !== "object" || currentConfig === null) {
350
- const edits = yield* modify(text, ["config"], {}, { formattingOptions: JSONC_FORMAT });
351
- text = yield* applyEdits(text, edits);
348
+ const edits = yield* JsoncModifier.modify(text, ["config"], {}, { formattingOptions: JSONC_FORMAT });
349
+ text = JsoncEdit.applyAll(text, edits);
352
350
  }
353
- parsed = yield* parse(text);
351
+ parsed = yield* Jsonc.parse(text);
354
352
  const config = parsed.config;
355
353
  for (const rule of RULE_NAMES) if (!(rule in config)) {
356
- const edits = yield* modify(text, ["config", rule], false, { formattingOptions: JSONC_FORMAT });
357
- text = yield* applyEdits(text, edits);
354
+ const edits = yield* JsoncModifier.modify(text, ["config", rule], false, { formattingOptions: JSONC_FORMAT });
355
+ text = JsoncEdit.applyAll(text, edits);
358
356
  }
359
357
  try {
360
358
  writeFileSync(fullPath, text);
@@ -365,7 +363,7 @@ function handleBaseMarkdownlint(root) {
365
363
  }));
366
364
  }
367
365
  return `Updated ${foundPath}`;
368
- }).pipe(Effect.catchAll((error) => {
366
+ }).pipe(Effect.catch((error) => {
369
367
  if (error instanceof InitError) return Effect.fail(error);
370
368
  return Effect.fail(new InitError({
371
369
  step: "markdownlint config",
@@ -465,7 +463,8 @@ function checkConfig(changesetDir, repoSlug) {
465
463
  });
466
464
  const options = Array.isArray(changelog) ? changelog[1] : void 0;
467
465
  if (options && typeof options === "object" && "versionFiles" in options) {
468
- if (Schema.decodeUnknownEither(LegacyVersionFilesSchema)(options.versionFiles)._tag === "Left") issues.push({
466
+ const result = Schema.decodeUnknownResult(LegacyVersionFilesSchema)(options.versionFiles);
467
+ if (Result.isFailure(result)) issues.push({
469
468
  file: ".changeset/config.json",
470
469
  message: "versionFiles config is invalid"
471
470
  });
@@ -499,7 +498,7 @@ function checkBaseMarkdownlint(root) {
499
498
  }];
500
499
  try {
501
500
  const raw = readFileSync(join(root, foundPath), "utf-8");
502
- const parsed = Effect.runSync(parse(raw));
501
+ const parsed = Effect.runSync(Jsonc.parse(raw));
503
502
  const issues = [];
504
503
  if (!Array.isArray(parsed.customRules) || !parsed.customRules.some((r) => ACCEPTED_CUSTOM_RULES_ENTRIES.includes(r))) issues.push({
505
504
  file: foundPath,
@@ -567,7 +566,7 @@ function runChangesetInit(opts) {
567
566
  const { force, quiet, skipMarkdownlint, check } = opts;
568
567
  return Effect.gen(function* () {
569
568
  const root = yield* resolveWorkspaceRoot(process.cwd());
570
- const repo = detectGitHubRepo(root);
569
+ const repo = yield* detectGitHubRepo(root);
571
570
  if (!repo && !quiet) yield* Effect.log("Warning: could not detect GitHub repo from git remote, using placeholder");
572
571
  const repoSlug = repo ?? "owner/repo";
573
572
  if (check) {
@@ -589,26 +588,26 @@ function runChangesetInit(opts) {
589
588
  const changesetDir = yield* ensureChangesetDir(root);
590
589
  yield* Effect.log("Ensured .changeset/ directory");
591
590
  const errors = [];
592
- const configResult = yield* handleConfig(changesetDir, repoSlug, force).pipe(Effect.either);
593
- if (configResult._tag === "Right") {
594
- yield* Effect.log(configResult.right);
591
+ const configResult = yield* handleConfig(changesetDir, repoSlug, force).pipe(Effect.result);
592
+ if (Result.isSuccess(configResult)) {
593
+ yield* Effect.log(configResult.success);
595
594
  if (!quiet) yield* warnIfLegacyVersionFiles(changesetDir);
596
- } else errors.push(configResult.left);
595
+ } else errors.push(configResult.failure);
597
596
  if (!skipMarkdownlint) {
598
- const baseResult = yield* handleBaseMarkdownlint(root).pipe(Effect.either);
599
- if (baseResult._tag === "Right") yield* Effect.log(baseResult.right);
600
- else errors.push(baseResult.left);
597
+ const baseResult = yield* handleBaseMarkdownlint(root).pipe(Effect.result);
598
+ if (Result.isSuccess(baseResult)) yield* Effect.log(baseResult.success);
599
+ else errors.push(baseResult.failure);
601
600
  }
602
- const mdlintResult = yield* handleChangesetMarkdownlint(changesetDir, root, force).pipe(Effect.either);
603
- if (mdlintResult._tag === "Right") yield* Effect.log(mdlintResult.right);
604
- else errors.push(mdlintResult.left);
601
+ const mdlintResult = yield* handleChangesetMarkdownlint(changesetDir, root, force).pipe(Effect.result);
602
+ if (Result.isSuccess(mdlintResult)) yield* Effect.log(mdlintResult.success);
603
+ else errors.push(mdlintResult.failure);
605
604
  if (errors.length > 0) {
606
605
  for (const err of errors) yield* Effect.logError(err.message);
607
606
  if (!quiet) process.exitCode = 1;
608
607
  return;
609
608
  }
610
609
  yield* Effect.log("Init complete.");
611
- }).pipe(Effect.catchAll((error) => Effect.gen(function* () {
610
+ }).pipe(Effect.catch((error) => Effect.gen(function* () {
612
611
  if (!quiet) {
613
612
  yield* Effect.logError(error instanceof InitError ? error.message : `Init failed: ${String(error)}`);
614
613
  process.exitCode = 1;
@@ -1,6 +1,6 @@
1
- import { Args, Command, Options } from "@effect/cli";
2
1
  import { Changesets } from "@savvy-web/silk-effects";
3
2
  import { Console, Effect } from "effect";
3
+ import { Argument, Command, Flag } from "effect/unstable/cli";
4
4
  import { resolve } from "node:path";
5
5
 
6
6
  //#region src/commands/changeset/commands/lint.ts
@@ -28,8 +28,8 @@ import { resolve } from "node:path";
28
28
  */
29
29
  const { ChangesetLinter } = Changesets;
30
30
  /* v8 ignore start -- CLI option definitions; handler tested via runLint */
31
- const dirArg = Args.directory({ name: "dir" }).pipe(Args.withDefault(".changeset"));
32
- const quietOption = Options.boolean("quiet").pipe(Options.withAlias("q"), Options.withDescription("Only output errors, no summary"), Options.withDefault(false));
31
+ const dirArg = Argument.directory("dir").pipe(Argument.withDefault(".changeset"));
32
+ const quietOption = Flag.boolean("quiet").pipe(Flag.withAlias("q"), Flag.withDescription("Only output errors, no summary"), Flag.withDefault(false));
33
33
  /* v8 ignore stop */
34
34
  /**
35
35
  * Run machine-readable lint validation on all changeset files in `dir`.
@@ -1,7 +1,7 @@
1
1
  import { requireValidConfig } from "../utils/config-gate.js";
2
- import { Args, Command, Options } from "@effect/cli";
3
2
  import { Changesets } from "@savvy-web/silk-effects";
4
3
  import { Effect } from "effect";
4
+ import { Argument, Command, Flag } from "effect/unstable/cli";
5
5
  import { dirname, resolve } from "node:path";
6
6
  import { readFileSync, writeFileSync } from "node:fs";
7
7
 
@@ -38,9 +38,9 @@ import { readFileSync, writeFileSync } from "node:fs";
38
38
  */
39
39
  const { ChangelogTransformer } = Changesets;
40
40
  /* v8 ignore start -- CLI option definitions; handler tested via runTransform */
41
- const fileArg = Args.file({ name: "file" }).pipe(Args.withDefault("CHANGELOG.md"));
42
- const dryRunOption = Options.boolean("dry-run").pipe(Options.withAlias("n"), Options.withDescription("Print transformed output instead of writing"), Options.withDefault(false));
43
- const checkOption = Options.boolean("check").pipe(Options.withAlias("c"), Options.withDescription("Exit 1 if file would change (for CI)"), Options.withDefault(false));
41
+ const fileArg = Argument.file("file").pipe(Argument.withDefault("CHANGELOG.md"));
42
+ const dryRunOption = Flag.boolean("dry-run").pipe(Flag.withAlias("n"), Flag.withDescription("Print transformed output instead of writing"), Flag.withDefault(false));
43
+ const checkOption = Flag.boolean("check").pipe(Flag.withAlias("c"), Flag.withDescription("Exit 1 if file would change (for CI)"), Flag.withDefault(false));
44
44
  /* v8 ignore stop */
45
45
  /**
46
46
  * Run the remark transform pipeline on a single changelog file.
@@ -1,6 +1,6 @@
1
- import { Args, Command } from "@effect/cli";
2
1
  import { Changesets } from "@savvy-web/silk-effects";
3
2
  import { Effect } from "effect";
3
+ import { Argument, Command } from "effect/unstable/cli";
4
4
 
5
5
  //#region src/commands/changeset/commands/validate-file.ts
6
6
  /**
@@ -19,7 +19,7 @@ import { Effect } from "effect";
19
19
  */
20
20
  const { ChangesetLinter } = Changesets;
21
21
  /* v8 ignore next */
22
- const fileArg = Args.file({ name: "file" });
22
+ const fileArg = Argument.file("file");
23
23
  /**
24
24
  * Run lint validation on a single changeset file.
25
25
  *
@@ -34,7 +34,7 @@ const fileArg = Args.file({ name: "file" });
34
34
  */
35
35
  function runValidateFile(filePath) {
36
36
  return Effect.gen(function* () {
37
- const result = yield* Effect.try(() => ChangesetLinter.validateFile(filePath)).pipe(Effect.catchAll((error) => Effect.gen(function* () {
37
+ const result = yield* Effect.try(() => ChangesetLinter.validateFile(filePath)).pipe(Effect.catch((error) => Effect.gen(function* () {
38
38
  yield* Effect.log(`Error: ${error instanceof Error ? error.message : String(error)}`);
39
39
  process.exitCode = 1;
40
40
  return null;
@@ -1,7 +1,7 @@
1
1
  import { requireValidConfig } from "../utils/config-gate.js";
2
- import { Command, Options } from "@effect/cli";
3
2
  import { Changesets } from "@savvy-web/silk-effects";
4
3
  import { Effect } from "effect";
4
+ import { Command, Flag } from "effect/unstable/cli";
5
5
 
6
6
  //#region src/commands/changeset/commands/version.ts
7
7
  /**
@@ -15,7 +15,7 @@ import { Effect } from "effect";
15
15
  * @internal
16
16
  */
17
17
  /* v8 ignore start -- CLI option definitions; handler tested via runVersion */
18
- const dryRunOption = Options.boolean("dry-run").pipe(Options.withAlias("n"), Options.withDescription("Compute and report the release without writing anything"), Options.withDefault(false));
18
+ const dryRunOption = Flag.boolean("dry-run").pipe(Flag.withAlias("n"), Flag.withDescription("Compute and report the release without writing anything"), Flag.withDefault(false));
19
19
  /* v8 ignore stop */
20
20
  /**
21
21
  * Validate config, then natively apply (or dry-run) the release via