@savvy-web/cli 1.3.4 → 1.3.6

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Savvy Web Strategy, LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/cli/index.js CHANGED
@@ -8,7 +8,7 @@ import { Command } from "@effect/cli";
8
8
  import { NodeContext, NodeRuntime } from "@effect/platform-node";
9
9
  import { BiomeSchemaSyncLive, ChangesetConfigReaderLive, Changesets, ConfigDiscoveryLive, ManagedSectionLive, SilkPublishabilityDetectorLive, ToolDiscoveryLive, VersioningStrategyLive } from "@savvy-web/silk-effects";
10
10
  import { Effect, Layer } from "effect";
11
- import { PackageManagerDetectorLive, WorkspaceDiscoveryLive, WorkspaceRootLive } from "workspaces-effect";
11
+ import { CatalogResolverLive, LockfileReaderLive, PackageManagerDetectorLive, PublishabilityDetectorLive, WorkspaceDiscoveryLive, WorkspaceRootLive } from "workspaces-effect";
12
12
 
13
13
  //#region src/cli/index.ts
14
14
  /**
@@ -57,7 +57,7 @@ const rootCommand = Command.make("savvy").pipe(Command.withSubcommands([
57
57
  ]));
58
58
  const cli = Command.run(rootCommand, {
59
59
  name: "savvy",
60
- version: "1.3.4"
60
+ version: "1.3.6"
61
61
  });
62
62
  /**
63
63
  * Shared base layer: workspace services, the changeset config reader, and the
@@ -102,12 +102,37 @@ const BaseLive = Layer.mergeAll(WorkspaceLive, ChangesetConfigReaderLive, Manage
102
102
  * `BranchAnalyzerLive` AND re-exposes it for the surviving `config validate`
103
103
  * handler that yields it directly, so it is never constructed twice per run.
104
104
  *
105
+ * `Changesets.DepsRegenLive` (the `deps regen`/`deps detect` orchestration
106
+ * service) needs `WorkspaceSnapshotReader`, `ConfigInspector`,
107
+ * `WorkspaceDiscovery` (all from `BaseLive`/`InspectorAndAnalyzerLive`),
108
+ * plus `CatalogResolver` and `PublishabilityDetector` from `workspaces-effect`
109
+ * — neither of which the other commands need, so they're composed only for
110
+ * `DepsRegenGroupLive`. `CatalogResolverLive` in turn needs `WorkspaceRoot`,
111
+ * `LockfileReader`, `WorkspaceDiscovery`, `FileSystem`, `Path`; `LockfileReaderLive`
112
+ * (composed via `CatalogLive`) needs `WorkspaceRoot`, `PackageManagerDetector`,
113
+ * `FileSystem`, `Path` — all satisfied by `BaseLive`/`NodeContext.layer` below.
114
+ * `DepsRegenGroupLive` reuses the exact `InspectorAndAnalyzerLive` layer
115
+ * reference (not a fresh copy), so Effect's layer memoization builds that
116
+ * single `ConfigInspector` instance once and shares it here too.
117
+ *
105
118
  * `provideMerge(BaseLive)` feeds the remaining deps and re-exposes the base
106
119
  * services for handlers that yield them directly. `provideMerge(NodeContext.layer)`
107
120
  * supplies `FileSystem`, `Path`, and `CommandExecutor` to everything underneath.
108
121
  */
109
122
  const InspectorAndAnalyzerLive = Changesets.BranchAnalyzerLive.pipe(Layer.provideMerge(Changesets.ReleasePlannerLive), Layer.provideMerge(Changesets.ConfigInspectorLive));
110
- const AppLive = Layer.mergeAll(ToolDiscoveryLive, VersioningStrategyLive, InspectorAndAnalyzerLive).pipe(Layer.provideMerge(BaseLive), Layer.provideMerge(NodeContext.layer));
123
+ /**
124
+ * `CatalogResolver` composed with its own `LockfileReader` dependency. The
125
+ * remaining requirements of both (`WorkspaceRoot`, `WorkspaceDiscovery`,
126
+ * `PackageManagerDetector`, `FileSystem`, `Path`) are left open, satisfied
127
+ * by `BaseLive` / `NodeContext.layer` in `AppLive` below.
128
+ */
129
+ const CatalogLive = CatalogResolverLive.pipe(Layer.provide(LockfileReaderLive));
130
+ /**
131
+ * `Changesets.DepsRegen`, fully composed except for the base services
132
+ * (`WorkspaceSnapshotReader`, `WorkspaceDiscovery`) supplied by `BaseLive`.
133
+ */
134
+ const DepsRegenGroupLive = Changesets.DepsRegenLive.pipe(Layer.provide(InspectorAndAnalyzerLive), Layer.provide(CatalogLive), Layer.provide(PublishabilityDetectorLive));
135
+ const AppLive = Layer.mergeAll(ToolDiscoveryLive, VersioningStrategyLive, InspectorAndAnalyzerLive, DepsRegenGroupLive).pipe(Layer.provideMerge(BaseLive), Layer.provideMerge(NodeContext.layer));
111
136
  /**
112
137
  * Bootstrap and run the `savvy` CLI application.
113
138
  *
@@ -1,24 +1,21 @@
1
1
  import { Command, Options } from "@effect/cli";
2
2
  import { Changesets } from "@savvy-web/silk-effects";
3
3
  import { Console, Effect, Option } from "effect";
4
- import { WorkspaceDiscovery } from "workspaces-effect";
5
- import { resolve } from "node:path";
6
4
 
7
5
  //#region src/commands/changeset/commands/deps-detect.ts
8
6
  /**
9
7
  * `deps detect` command — read-only dependency-diff inspection.
10
8
  *
11
9
  * @remarks
12
- * Computes the per-workspace-package dependency changes between two git
13
- * refs and renders them either as structured JSON (one row per change)
14
- * or as ready-to-paste CSH005 markdown. No file writes.
10
+ * A thin adapter over {@link Changesets.DepsRegen}: this command calls
11
+ * `plan({ includeDevDeps: true, ... })` the read-only path, so devDeps
12
+ * stay in the diff and no file is written or deleted — then renders
13
+ * `plan.toWrite`'s diffs either as structured JSON (one row per change) or
14
+ * as ready-to-paste CSH005 markdown. No file writes.
15
15
  *
16
16
  * Defaults:
17
17
  * - `--from` → `git merge-base <baseBranch> HEAD`
18
18
  * - `--to` → working tree (i.e., `HEAD` plus staged + unstaged + untracked).
19
- * Passed as the special value
20
- * `WORKTREE` to {@link WorkspaceSnapshotReader} — implementations resolve
21
- * this against the live working tree rather than `git show`.
22
19
  *
23
20
  * @example
24
21
  * ```bash
@@ -29,7 +26,7 @@ import { resolve } from "node:path";
29
26
  *
30
27
  * @internal
31
28
  */
32
- const { ConfigInspector, WorkspaceSnapshotReader, computeWorkspaceDependencyDiffs, gitMergeBase, listPublishablePackageNames, serializeDependencyTableToMarkdown, snapshotFromWorktree } = Changesets;
29
+ const { DepsRegen, serializeDependencyTableToMarkdown } = Changesets;
33
30
  /* v8 ignore start -- CLI option definitions */
34
31
  const fromOption = Options.text("from").pipe(Options.withDescription("Older ref to diff from (defaults to merge-base with base branch)"), Options.optional);
35
32
  const toOption = Options.text("to").pipe(Options.withDescription("Newer ref to diff to (defaults to working tree)"), Options.optional);
@@ -61,27 +58,16 @@ function renderMarkdownBlocks(diffs) {
61
58
  */
62
59
  function runDepsDetect(cwd, from, to, pkg, json, markdown) {
63
60
  return Effect.gen(function* () {
64
- const reader = yield* WorkspaceSnapshotReader;
65
- const resolvedCwd = resolve(cwd);
66
- let fromRef = Option.getOrUndefined(from);
67
- if (!fromRef) fromRef = yield* gitMergeBase(resolvedCwd, (yield* (yield* ConfigInspector).inspect(resolvedCwd).pipe(Effect.catchTag("ConfigurationError", () => Effect.succeed({ baseBranch: "main" })))).baseBranch).pipe(Effect.catchTag("GitError", (err) => {
61
+ const diffs = (yield* (yield* DepsRegen).plan({
62
+ cwd,
63
+ includeDevDeps: true,
64
+ ...Option.isSome(pkg) ? { package: pkg.value } : {},
65
+ ...Option.isSome(from) ? { from: from.value } : {},
66
+ ...Option.isSome(to) ? { to: to.value } : {}
67
+ }).pipe(Effect.catchTag("GitError", (err) => {
68
68
  process.exitCode = 1;
69
69
  return Effect.fail(err);
70
- }));
71
- const toRef = Option.getOrUndefined(to);
72
- let diffs = computeWorkspaceDependencyDiffs(yield* reader.snapshotAt(resolvedCwd, fromRef).pipe(Effect.catchTag("GitError", (err) => {
73
- process.exitCode = 1;
74
- return Effect.fail(err);
75
- })), toRef ? yield* reader.snapshotAt(resolvedCwd, toRef).pipe(Effect.catchTag("GitError", (err) => {
76
- process.exitCode = 1;
77
- return Effect.fail(err);
78
- })) : snapshotFromWorktree(resolvedCwd));
79
- const targetPkg = Option.getOrUndefined(pkg);
80
- if (targetPkg) diffs = diffs.filter((d) => d.package === targetPkg);
81
- else {
82
- const publishable = yield* listPublishablePackageNames(yield* (yield* WorkspaceDiscovery).listPackages(resolvedCwd).pipe(Effect.catchAll(() => Effect.succeed([]))));
83
- diffs = diffs.filter((d) => publishable.has(d.package));
84
- }
70
+ }))).toWrite.map((entry) => entry.diff);
85
71
  if (markdown && !json) {
86
72
  yield* Effect.log(renderMarkdownBlocks(diffs));
87
73
  return;
@@ -1,9 +1,6 @@
1
1
  import { Command, Options } from "@effect/cli";
2
2
  import { Changesets } from "@savvy-web/silk-effects";
3
3
  import { Console, Effect, Option } from "effect";
4
- import { WorkspaceDiscovery } from "workspaces-effect";
5
- import { join, resolve } from "node:path";
6
- import { existsSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
7
4
 
8
5
  //#region src/commands/changeset/commands/deps-regen.ts
9
6
  /**
@@ -12,7 +9,14 @@ import { existsSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from
12
9
  * cumulative dep diff from base to working tree.
13
10
  *
14
11
  * @remarks
15
- * **The single-package-per-changeset rule.** This command enforces our
12
+ * This is a thin adapter over {@link Changesets.DepsRegen}: the plan/execute
13
+ * orchestration (pure-dependency-changeset detection, protocol-specifier
14
+ * resolution, filename generation, and the filesystem writes themselves)
15
+ * lives in `@savvy-web/silk-effects`. This command only translates CLI
16
+ * options into a `plan()` call, conditionally applies the plan via
17
+ * `execute()`, and renders the resulting {@link RegenPlan}.
18
+ *
19
+ * **The single-package-per-changeset rule.** The service enforces our
16
20
  * convention that each `.changeset/*.md` file lists exactly one package
17
21
  * in its frontmatter. `@changesets/cli` technically supports multi-package
18
22
  * frontmatter, but our agent (and this command) always produces single-
@@ -28,18 +32,9 @@ import { existsSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from
28
32
  * Anything else (multi-package frontmatter, additional sections, comments,
29
33
  * `### Sub-headings`, etc.) is treated as "mixed" and left untouched.
30
34
  * That's the safe default — if a human authored something idiosyncratic,
31
- * we don't clobber it.
32
- *
33
- * **The algorithm:**
34
- *
35
- * 1. Compute dep diff from `merge-base(baseBranch, HEAD)` to working
36
- * tree, grouped by workspace package.
37
- * 2. Find every pure-dependency changeset (strict definition).
38
- * 3. Delete every one of them — even those for packages with no current
39
- * dep changes (their changeset is stale by definition).
40
- * 4. Write a fresh `<adjective>-<noun>-<verb>.md` per workspace package
41
- * that has current dep changes: single-package frontmatter, `patch`
42
- * bump, one `## Dependencies` section, one CSH005 table.
35
+ * we don't clobber it. `devDependency` rows are dropped by the service
36
+ * (the regen default) and protocol specifiers (`catalog:`/`workspace:`)
37
+ * are resolved to concrete versions.
43
38
  *
44
39
  * @example
45
40
  * ```bash
@@ -50,7 +45,7 @@ import { existsSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from
50
45
  *
51
46
  * @internal
52
47
  */
53
- const { ConfigInspector, WorkspaceSnapshotReader, computeWorkspaceDependencyDiffs, gitMergeBase, listPublishablePackageNames, serializeDependencyTableToMarkdown, snapshotFromWorktree } = Changesets;
48
+ const { DepsRegen } = Changesets;
54
49
  /* v8 ignore start -- CLI option definitions */
55
50
  const cwdOption = Options.directory("cwd").pipe(Options.withDescription("Project root (defaults to the current working directory)"), Options.withDefault("."));
56
51
  const baseOption = Options.text("base").pipe(Options.withDescription("Override the base branch (defaults to config baseBranch)"), Options.optional);
@@ -58,141 +53,6 @@ const packageOption = Options.text("package").pipe(Options.withDescription("Rest
58
53
  const dryRunOption = Options.boolean("dry-run").pipe(Options.withDescription("Print the plan without writing or deleting"), Options.withDefault(false));
59
54
  const jsonOption = Options.boolean("json").pipe(Options.withDescription("Emit a structured plan as JSON"), Options.withDefault(false));
60
55
  /* v8 ignore stop */
61
- const ADJECTIVES = [
62
- "brave",
63
- "clever",
64
- "swift",
65
- "silver",
66
- "lucky",
67
- "happy",
68
- "calm",
69
- "bright",
70
- "quiet",
71
- "wild"
72
- ];
73
- const NOUNS = [
74
- "dogs",
75
- "cats",
76
- "wolves",
77
- "foxes",
78
- "cups",
79
- "ships",
80
- "trees",
81
- "owls",
82
- "cranes",
83
- "hills"
84
- ];
85
- const VERBS = [
86
- "laugh",
87
- "dream",
88
- "fly",
89
- "sing",
90
- "dance",
91
- "wander",
92
- "soar",
93
- "rest",
94
- "leap",
95
- "ponder"
96
- ];
97
- function pickRandomTriplet() {
98
- return `${ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)]}-${NOUNS[Math.floor(Math.random() * NOUNS.length)]}-${VERBS[Math.floor(Math.random() * VERBS.length)]}`;
99
- }
100
- /**
101
- * Pick a `<adjective>-<noun>-<verb>` filename slug that does not collide
102
- * with an existing `.changeset/*.md`. The triplet space is 1,000
103
- * combinations, so a busy repo can plausibly exhaust it across runs;
104
- * fall back to a timestamp suffix after 20 unlucky picks.
105
- *
106
- * @internal
107
- */
108
- function randomFilename(changesetDir) {
109
- for (let i = 0; i < 20; i++) {
110
- const candidate = pickRandomTriplet();
111
- if (!existsSync(join(changesetDir, `${candidate}.md`))) return candidate;
112
- }
113
- return `${pickRandomTriplet()}-${Date.now()}`;
114
- }
115
- /**
116
- * Strict detection of "pure dependency changesets" per the documented
117
- * rules: single-package frontmatter, single `## Dependencies` heading,
118
- * no other body content beyond that section.
119
- *
120
- * @internal
121
- */
122
- function isPureDependencyChangeset(content) {
123
- const fmMatch = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
124
- if (!fmMatch) return {
125
- isPure: false,
126
- package: null
127
- };
128
- const frontmatter = fmMatch[1];
129
- const body = (fmMatch[2] ?? "").trim();
130
- const fmLines = frontmatter.split(/\r?\n/).filter((l) => l.trim().length > 0 && !/^\s*#/.test(l));
131
- if (fmLines.length !== 1) return {
132
- isPure: false,
133
- package: null
134
- };
135
- const pkgMatch = fmLines[0].match(/^\s*["']?([^"':\s]+)["']?\s*:\s*([a-z]+)\s*$/);
136
- if (!pkgMatch) return {
137
- isPure: false,
138
- package: null
139
- };
140
- const pkg = pkgMatch[1];
141
- const bodyTrimmed = body.replace(/^\s+/, "");
142
- if (!/^## Dependencies\b/.test(bodyTrimmed)) return {
143
- isPure: false,
144
- package: null
145
- };
146
- if ((bodyTrimmed.match(/^## /gm) ?? []).length !== 1) return {
147
- isPure: false,
148
- package: null
149
- };
150
- if (/^# /m.test(bodyTrimmed)) return {
151
- isPure: false,
152
- package: null
153
- };
154
- return {
155
- isPure: true,
156
- package: pkg
157
- };
158
- }
159
- function listChangesetFiles(changesetDir) {
160
- if (!existsSync(changesetDir)) return [];
161
- return readdirSync(changesetDir).filter((f) => f.endsWith(".md") && f !== "README.md").map((f) => join(changesetDir, f));
162
- }
163
- function findPureDependencyChangesets(changesetDir) {
164
- const result = [];
165
- for (const file of listChangesetFiles(changesetDir)) {
166
- let content;
167
- try {
168
- content = readFileSync(file, "utf8");
169
- } catch {
170
- continue;
171
- }
172
- const detection = isPureDependencyChangeset(content);
173
- if (detection.isPure && detection.package) result.push({
174
- file,
175
- package: detection.package
176
- });
177
- }
178
- return result;
179
- }
180
- function findMixedDependencyChangesets(changesetDir) {
181
- const result = [];
182
- for (const file of listChangesetFiles(changesetDir)) {
183
- let content;
184
- try {
185
- content = readFileSync(file, "utf8");
186
- } catch {
187
- continue;
188
- }
189
- if (/^## Dependencies\b/m.test(content) && !isPureDependencyChangeset(content).isPure) result.push(file);
190
- }
191
- return result;
192
- }
193
- function renderChangesetContent(diff) {
194
- return `${`---\n"${diff.package}": patch\n---`}\n\n## Dependencies\n\n${serializeDependencyTableToMarkdown([...diff.rows])}\n`;
195
- }
196
56
  /**
197
57
  * Handler exported for direct invocation in tests.
198
58
  *
@@ -200,47 +60,16 @@ function renderChangesetContent(diff) {
200
60
  */
201
61
  function runDepsRegen(cwd, base, pkg, dryRun, json) {
202
62
  return Effect.gen(function* () {
203
- const resolvedCwd = resolve(cwd);
204
- const changesetDir = join(resolvedCwd, ".changeset");
205
- const reader = yield* WorkspaceSnapshotReader;
206
- let baseBranch = Option.getOrUndefined(base);
207
- if (!baseBranch) baseBranch = (yield* (yield* ConfigInspector).inspect(resolvedCwd).pipe(Effect.catchTag("ConfigurationError", () => Effect.succeed({ baseBranch: "main" })))).baseBranch;
208
- const mergeBase = yield* gitMergeBase(resolvedCwd, baseBranch).pipe(Effect.catchTag("GitError", (err) => {
209
- process.exitCode = 1;
210
- return Effect.fail(err);
211
- }));
212
- let diffs = computeWorkspaceDependencyDiffs(yield* reader.snapshotAt(resolvedCwd, mergeBase).pipe(Effect.catchTag("GitError", (err) => {
63
+ const service = yield* DepsRegen;
64
+ const plan = yield* service.plan({
65
+ cwd,
66
+ ...Option.isSome(base) ? { base: base.value } : {},
67
+ ...Option.isSome(pkg) ? { package: pkg.value } : {}
68
+ }).pipe(Effect.catchTag("GitError", (err) => {
213
69
  process.exitCode = 1;
214
70
  return Effect.fail(err);
215
- })), snapshotFromWorktree(resolvedCwd));
216
- const targetPkg = Option.getOrUndefined(pkg);
217
- const publishable = yield* listPublishablePackageNames(yield* (yield* WorkspaceDiscovery).listPackages(resolvedCwd).pipe(Effect.catchAll(() => Effect.succeed([]))));
218
- if (targetPkg) diffs = diffs.filter((d) => d.package === targetPkg);
219
- else diffs = diffs.filter((d) => publishable.has(d.package));
220
- const existingPure = findPureDependencyChangesets(changesetDir);
221
- const skippedMixed = findMixedDependencyChangesets(changesetDir);
222
- const toDelete = targetPkg ? existingPure.filter((p) => p.package === targetPkg) : existingPure.filter((p) => publishable.has(p.package));
223
- const toWrite = diffs.map((diff) => ({
224
- file: join(changesetDir, `${randomFilename(changesetDir)}.md`),
225
- package: diff.package,
226
- diff
227
71
  }));
228
- const plan = {
229
- toDelete,
230
- toWrite,
231
- skippedMixed
232
- };
233
- if (dryRun) {
234
- if (json) yield* Console.log(JSON.stringify(plan, null, 2));
235
- else yield* renderHumanPlan(plan);
236
- return;
237
- }
238
- for (const entry of toDelete) try {
239
- unlinkSync(entry.file);
240
- } catch (error) {
241
- yield* Effect.logWarning(`Failed to delete ${entry.file}: ${error instanceof Error ? error.message : String(error)}`);
242
- }
243
- for (const entry of toWrite) writeFileSync(entry.file, renderChangesetContent(entry.diff));
72
+ if (!dryRun) yield* service.execute(plan);
244
73
  if (json) yield* Console.log(JSON.stringify(plan, null, 2));
245
74
  else yield* renderHumanPlan(plan);
246
75
  });
@@ -274,4 +103,4 @@ const depsRegenCommand = Command.make("regen", {
274
103
  }, ({ 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"));
275
104
 
276
105
  //#endregion
277
- export { depsRegenCommand, isPureDependencyChangeset, runDepsRegen };
106
+ export { depsRegenCommand, runDepsRegen };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@savvy-web/cli",
3
- "version": "1.3.4",
3
+ "version": "1.3.6",
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",
@@ -36,9 +36,9 @@
36
36
  "@effect/platform-node": "^0.107.0",
37
37
  "@effect/rpc": "^0.75.1",
38
38
  "@effect/sql": "^0.51.1",
39
- "@savvy-web/silk-effects": "1.5.1",
39
+ "@savvy-web/silk-effects": "1.6.0",
40
40
  "effect": "^3.21.4",
41
- "jsonc-effect": "^0.2.1",
41
+ "jsonc-effect": "^0.3.0",
42
42
  "workspaces-effect": "^1.2.0",
43
43
  "yaml": "^2.9.0"
44
44
  }