@savvy-web/silk 1.3.7 → 1.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/changesets-changelog.cjs +70165 -72721
  2. package/changesets-changelog.d.cts +1130 -938
  3. package/changesets-changelog.d.ts +1130 -938
  4. package/changesets-markdownlint.cjs +70165 -72721
  5. package/changesets-markdownlint.d.cts +1130 -938
  6. package/changesets-markdownlint.d.ts +1130 -938
  7. package/package.json +4 -4
  8. package/packages/silk-effects/dist/dev/pkg/changesets/errors.cjs +45 -0
  9. package/packages/silk-effects/dist/dev/pkg/changesets/errors.js +44 -1
  10. package/packages/silk-effects/dist/dev/pkg/changesets/index.cjs +16 -21
  11. package/packages/silk-effects/dist/dev/pkg/changesets/index.js +8 -11
  12. package/packages/silk-effects/dist/dev/pkg/changesets/services/branch-analyzer.cjs +2 -1
  13. package/packages/silk-effects/dist/dev/pkg/changesets/services/branch-analyzer.js +2 -1
  14. package/packages/silk-effects/dist/dev/pkg/changesets/services/config-inspector.cjs +7 -7
  15. package/packages/silk-effects/dist/dev/pkg/changesets/services/config-inspector.js +4 -4
  16. package/packages/silk-effects/dist/dev/pkg/changesets/services/deps-regen.cjs +159 -115
  17. package/packages/silk-effects/dist/dev/pkg/changesets/services/deps-regen.js +157 -113
  18. package/packages/silk-effects/dist/dev/pkg/changesets/services/release-planner.cjs +105 -81
  19. package/packages/silk-effects/dist/dev/pkg/changesets/services/release-planner.js +105 -81
  20. package/packages/silk-effects/dist/dev/pkg/changesets/utils/dep-diff.cjs +81 -47
  21. package/packages/silk-effects/dist/dev/pkg/changesets/utils/dep-diff.js +81 -47
  22. package/packages/silk-effects/dist/dev/pkg/changesets/utils/git.cjs +51 -0
  23. package/packages/silk-effects/dist/dev/pkg/changesets/utils/git.js +51 -0
  24. package/packages/silk-effects/dist/dev/pkg/changesets/utils/publishability.cjs +16 -4
  25. package/packages/silk-effects/dist/dev/pkg/changesets/utils/publishability.js +15 -3
  26. package/packages/silk-effects/dist/dev/pkg/services/ChangesetConfig.cjs +2 -1
  27. package/packages/silk-effects/dist/dev/pkg/services/ChangesetConfig.js +1 -1
  28. package/packages/silk-effects/dist/dev/pkg/services/ChangesetConfigReader.cjs +2 -1
  29. package/packages/silk-effects/dist/dev/pkg/services/ChangesetConfigReader.js +1 -1
  30. package/packages/silk-effects/dist/dev/pkg/services/SilkPublishability.cjs +20 -13
  31. package/packages/silk-effects/dist/dev/pkg/services/SilkPublishability.js +10 -4
  32. package/tsconfig/node/dist/tsconfig.tsbuildinfo +1 -1
  33. package/packages/silk-effects/dist/dev/pkg/changesets/services/workspace-snapshot.cjs +0 -162
  34. package/packages/silk-effects/dist/dev/pkg/changesets/services/workspace-snapshot.js +0 -160
  35. package/packages/silk-effects/dist/dev/pkg/changesets/utils/worktree-snapshot.cjs +0 -144
  36. package/packages/silk-effects/dist/dev/pkg/changesets/utils/worktree-snapshot.js +0 -142
@@ -1,13 +1,21 @@
1
+ import { ChangesetConfigReaderLive } from "../../services/ChangesetConfigReader.js";
2
+ import { ChangesetConfig, ChangesetConfigLive } from "../../services/ChangesetConfig.js";
3
+ import { WorkspaceDiscovery } from "../../../../../../../node_modules/.pnpm/workspaces-effect@2.0.1_@effect_platform@0.96.2_effect@3.21.4__effect@3.21.4/node_modules/workspaces-effect/services/WorkspaceDiscovery.js";
4
+ import { PointInTimeWorkspace } from "../../../../../../../node_modules/.pnpm/workspaces-effect@2.0.1_@effect_platform@0.96.2_effect@3.21.4__effect@3.21.4/node_modules/workspaces-effect/services/PointInTimeWorkspace.js";
5
+ import { PointInTimeWorkspaceLive } from "../../../../../../../node_modules/.pnpm/workspaces-effect@2.0.1_@effect_platform@0.96.2_effect@3.21.4__effect@3.21.4/node_modules/workspaces-effect/layers/PointInTimeWorkspaceLive.js";
6
+ import { PublishabilityDetector } from "../../../../../../../node_modules/.pnpm/workspaces-effect@2.0.1_@effect_platform@0.96.2_effect@3.21.4__effect@3.21.4/node_modules/workspaces-effect/services/PublishabilityDetector.js";
7
+ import { WorkspaceDiscoveryLive } from "../../../../../../../node_modules/.pnpm/workspaces-effect@2.0.1_@effect_platform@0.96.2_effect@3.21.4__effect@3.21.4/node_modules/workspaces-effect/layers/WorkspaceDiscoveryLive.js";
8
+ import { WorkspaceRootLive } from "../../../../../../../node_modules/.pnpm/workspaces-effect@2.0.1_@effect_platform@0.96.2_effect@3.21.4__effect@3.21.4/node_modules/workspaces-effect/layers/WorkspaceRootLive.js";
9
+ import { PublishabilityDetectorAdaptiveLive } from "../../services/SilkPublishability.js";
10
+ import { ChangesetIOError } from "../errors.js";
1
11
  import { serializeDependencyTableToMarkdown, sortDependencyRows } from "../utils/dependency-table.js";
2
- import { CatalogResolver, PublishabilityDetector, WorkspaceDiscovery } from "../../../../../../../node_modules/.pnpm/workspaces-effect@1.2.0_@effect_platform@0.96.2_effect@3.21.4__effect@3.21.4/node_modules/workspaces-effect/index.js";
3
- import { ConfigInspector } from "./config-inspector.js";
12
+ import { ConfigInspector, ConfigInspectorLive } from "./config-inspector.js";
4
13
  import { computeWorkspaceDependencyDiffs } from "../utils/dep-diff.js";
14
+ import { gitMergeBase } from "../utils/git.js";
5
15
  import { listPublishablePackageNames } from "../utils/publishability.js";
6
- import { gitMergeBase, snapshotFromWorktree } from "../utils/worktree-snapshot.js";
7
- import { WorkspaceSnapshotReader } from "./workspace-snapshot.js";
8
16
  import { Context, Effect, Layer, Option } from "effect";
17
+ import { FileSystem } from "@effect/platform";
9
18
  import { join, resolve } from "node:path";
10
- import { existsSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
11
19
 
12
20
  //#region ../silk-effects/dist/dev/pkg/changesets/services/deps-regen.js
13
21
  /**
@@ -17,9 +25,11 @@ import { existsSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from
17
25
  *
18
26
  * @remarks
19
27
  * `plan()` computes the cumulative dependency diff (merge-base → working
20
- * tree by default, or between two explicit refs), resolves `catalog:` /
21
- * `workspace:` specifiers to concrete versions, drops `devDependency`
22
- * rows (unless `includeDevDeps`), and returns a complete {@link RegenPlan}
28
+ * tree by default, or between two explicit refs) by snapshotting both
29
+ * sides through `PointInTimeWorkspace`, which resolves `catalog:` /
30
+ * `workspace:` specifiers against each ref's own catalogs and package
31
+ * versions before diffing. It drops `devDependency` rows (unless
32
+ * `includeDevDeps`), and returns a complete {@link RegenPlan}
23
33
  * (target filenames + stale-changeset deletes) WITHOUT touching the
24
34
  * filesystem. `execute()` applies a plan — writing the fresh changesets
25
35
  * first, then deleting the stale pure-dependency ones (so an interrupted
@@ -32,41 +42,6 @@ import { existsSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from
32
42
  * @see {@link DepsRegenLive} for the production layer
33
43
  *
34
44
  */
35
- /** The em-dash sentinel (U+2014) used for added ("from") / removed ("to") cells. */
36
- const EM_DASH = "—";
37
- /** Whether a From/To cell holds a pnpm protocol specifier that could resolve to a version. */
38
- const isProtocol = (v) => /^(?:catalog|workspace|npm|jsr|file|link|portal):/.test(v);
39
- /**
40
- * Resolve protocol From/To cells to concrete versions (raw-string fallback
41
- * when unresolved or on resolver error), leave em-dash sentinels untouched,
42
- * then optionally drop `devDependency` rows, and re-sort.
43
- *
44
- * @param diff - One workspace package's dependency-table rows.
45
- * @param keepDevDeps - When `true`, retain `devDependency` rows; otherwise
46
- * drop them unconditionally (the regen default).
47
- * @returns An Effect yielding the transformed {@link WorkspaceDependencyDiff}.
48
- *
49
- * @public
50
- */
51
- const resolveDiffRows = (diff, keepDevDeps = false) => Effect.gen(function* () {
52
- const resolver = yield* CatalogResolver;
53
- const resolveCell = (dep, value) => isProtocol(value) ? resolver.resolveSpecifier(dep, value).pipe(Effect.map((opt) => Option.getOrElse(opt, () => value)), Effect.catchAll((error) => Effect.logWarning(`DepsRegen: catalog resolution failed for "${dep}" (${value}); keeping raw specifier: ${String(error)}`).pipe(Effect.as(value)))) : Effect.succeed(value);
54
- const rows = [];
55
- for (const row of diff.rows) {
56
- if (!keepDevDeps && row.type === "devDependency") continue;
57
- const from = row.from === EM_DASH ? EM_DASH : yield* resolveCell(row.dependency, row.from);
58
- const to = row.to === EM_DASH ? EM_DASH : yield* resolveCell(row.dependency, row.to);
59
- rows.push({
60
- ...row,
61
- from,
62
- to
63
- });
64
- }
65
- return {
66
- ...diff,
67
- rows: sortDependencyRows(rows)
68
- };
69
- });
70
45
  const ADJECTIVES = [
71
46
  "brave",
72
47
  "clever",
@@ -110,30 +85,35 @@ function pickRandomTriplet() {
110
85
  * Pick a `<adjective>-<noun>-<verb>` filename slug that does not collide
111
86
  * with an existing `.changeset/*.md` OR with a slug already claimed
112
87
  * earlier in the same {@link RegenPlan.toWrite} computation. `plan()`
113
- * never writes to disk, so `existsSync` alone cannot see slugs chosen
114
- * moments earlier in the same call — the `chosen` set closes that gap.
115
- * The triplet space is 1,000 combinations, so a busy repo can plausibly
116
- * exhaust it across runs; fall back to a timestamp suffix after 20
117
- * unlucky picks.
88
+ * never writes to disk, so an on-disk existence check alone cannot see
89
+ * slugs chosen moments earlier in the same call — the `chosen` set closes
90
+ * that gap. The triplet space is 1,000 combinations, so a busy repo can
91
+ * plausibly exhaust it across runs; fall back to a timestamp suffix after
92
+ * 20 unlucky picks.
118
93
  *
119
- * @param changesetDir - Directory checked via `existsSync` for on-disk collisions.
94
+ * @param fileExists - Effectful on-disk existence check (never fails; a
95
+ * filesystem error is treated as "does not exist" so filename selection
96
+ * degrades gracefully rather than blocking the plan).
97
+ * @param changesetDir - Directory checked for on-disk collisions.
120
98
  * @param chosen - Basenames (without extension) already picked within this plan;
121
99
  * the picked candidate is added to this set before returning.
122
100
  * @internal
123
101
  */
124
- function randomFilename(changesetDir, chosen) {
125
- for (let i = 0; i < 20; i++) {
126
- const candidate = pickRandomTriplet();
127
- if (!chosen.has(candidate) && !existsSync(join(changesetDir, `${candidate}.md`))) {
128
- chosen.add(candidate);
129
- return candidate;
102
+ function randomFilename(fileExists, changesetDir, chosen) {
103
+ return Effect.gen(function* () {
104
+ for (let i = 0; i < 20; i++) {
105
+ const candidate = pickRandomTriplet();
106
+ if (!chosen.has(candidate) && !(yield* fileExists(join(changesetDir, `${candidate}.md`)))) {
107
+ chosen.add(candidate);
108
+ return candidate;
109
+ }
130
110
  }
131
- }
132
- let attempt = 0;
133
- let fallback = `${pickRandomTriplet()}-${Date.now()}`;
134
- while (chosen.has(fallback) || existsSync(join(changesetDir, `${fallback}.md`))) fallback = `${pickRandomTriplet()}-${Date.now()}-${++attempt}`;
135
- chosen.add(fallback);
136
- return fallback;
111
+ let attempt = 0;
112
+ let fallback = `${pickRandomTriplet()}-${Date.now()}`;
113
+ while (chosen.has(fallback) || (yield* fileExists(join(changesetDir, `${fallback}.md`)))) fallback = `${pickRandomTriplet()}-${Date.now()}-${++attempt}`;
114
+ chosen.add(fallback);
115
+ return fallback;
116
+ });
137
117
  }
138
118
  /**
139
119
  * Strict detection of "pure dependency changesets" per the documented
@@ -184,43 +164,59 @@ function isPureDependencyChangeset(content) {
184
164
  package: pkg
185
165
  };
186
166
  }
187
- function listChangesetFiles(changesetDir) {
188
- if (!existsSync(changesetDir)) return [];
189
- return readdirSync(changesetDir).filter((f) => f.endsWith(".md") && f !== "README.md").map((f) => join(changesetDir, f));
190
- }
191
- function findPureDependencyChangesets(changesetDir) {
167
+ /**
168
+ * List `.changeset/*.md` files (excluding `README.md`) in `changesetDir`.
169
+ * A missing directory is today's behavior for "no changesets yet" and
170
+ * resolves to an empty list; any OTHER failure (e.g. a directory that
171
+ * exists but cannot be read) is a loud {@link ChangesetIOError} — an
172
+ * unreadable `.changeset` dir must not silently masquerade as "no
173
+ * changesets".
174
+ */
175
+ const listChangesetFiles = (fs, changesetDir) => fs.readDirectory(changesetDir).pipe(Effect.map((names) => names.filter((f) => f.endsWith(".md") && f !== "README.md").map((f) => join(changesetDir, f))), Effect.catchAll((e) => e._tag === "SystemError" && e.reason === "NotFound" ? Effect.succeed([]) : Effect.fail(new ChangesetIOError({
176
+ path: changesetDir,
177
+ operation: "list",
178
+ reason: String(e)
179
+ }))));
180
+ /**
181
+ * Pure-dependency changesets found in `changesetDir`. An individual file
182
+ * that cannot be read (e.g. removed mid-scan, permission race) is skipped —
183
+ * today's semantics — while a failure to list the directory itself
184
+ * propagates as a loud {@link ChangesetIOError}.
185
+ */
186
+ const findPureDependencyChangesets = (fs, changesetDir) => Effect.gen(function* () {
187
+ const files = yield* listChangesetFiles(fs, changesetDir);
192
188
  const result = [];
193
- for (const file of listChangesetFiles(changesetDir)) {
194
- let content;
195
- try {
196
- content = readFileSync(file, "utf8");
197
- } catch {
198
- continue;
199
- }
200
- const detection = isPureDependencyChangeset(content);
189
+ for (const file of files) {
190
+ const content = yield* fs.readFileString(file).pipe(Effect.option);
191
+ if (Option.isNone(content)) continue;
192
+ const detection = isPureDependencyChangeset(content.value);
201
193
  if (detection.isPure && detection.package) result.push({
202
194
  file,
203
195
  package: detection.package
204
196
  });
205
197
  }
206
198
  return result;
207
- }
208
- function findMixedDependencyChangesets(changesetDir) {
199
+ });
200
+ /**
201
+ * Mixed dependency changesets (have a `## Dependencies` heading but fail
202
+ * the strict pure-changeset test) found in `changesetDir`. Same
203
+ * skip-unreadable-file / loud-list-failure semantics as
204
+ * {@link findPureDependencyChangesets}.
205
+ */
206
+ const findMixedDependencyChangesets = (fs, changesetDir) => Effect.gen(function* () {
207
+ const files = yield* listChangesetFiles(fs, changesetDir);
209
208
  const result = [];
210
- for (const file of listChangesetFiles(changesetDir)) {
211
- let content;
212
- try {
213
- content = readFileSync(file, "utf8");
214
- } catch {
215
- continue;
216
- }
217
- if (/^## Dependencies\b/m.test(content) && !isPureDependencyChangeset(content).isPure) result.push(file);
209
+ for (const file of files) {
210
+ const content = yield* fs.readFileString(file).pipe(Effect.option);
211
+ if (Option.isNone(content)) continue;
212
+ if (/^## Dependencies\b/m.test(content.value) && !isPureDependencyChangeset(content.value).isPure) result.push(file);
218
213
  }
219
214
  return result;
220
- }
215
+ });
221
216
  /**
222
- * Render a single-package, patch-bump changeset for a diff whose rows have
223
- * already been resolved/filtered by {@link resolveDiffRows}.
217
+ * Render a single-package, patch-bump changeset for a diff whose rows are
218
+ * already resolved (per-side, inside {@link computeWorkspaceDependencyDiffs})
219
+ * and devDep-filtered.
224
220
  */
225
221
  function renderChangesetContent(diff) {
226
222
  return `${`---\n"${diff.package}": patch\n---`}\n\n## Dependencies\n\n${serializeDependencyTableToMarkdown([...diff.rows])}\n`;
@@ -252,9 +248,9 @@ var DepsRegen = class extends DepsRegenBase {};
252
248
  * implementations, keeping the public `plan`/`execute` signatures
253
249
  * requirement-free (`R = never`).
254
250
  */
255
- function makeShape(reader, inspector, discovery, resolver, detector) {
256
- const provideResolver = Layer.succeed(CatalogResolver, resolver);
251
+ function makeShape(pit, inspector, discovery, detector, config, fs) {
257
252
  const provideDetector = Layer.succeed(PublishabilityDetector, detector);
253
+ const fileExists = (p) => fs.exists(p).pipe(Effect.orElseSucceed(() => false));
258
254
  const plan = (options) => Effect.gen(function* () {
259
255
  const resolvedCwd = resolve(options.cwd);
260
256
  const changesetDir = join(resolvedCwd, ".changeset");
@@ -264,41 +260,58 @@ function makeShape(reader, inspector, discovery, resolver, detector) {
264
260
  if (!baseBranch) baseBranch = (yield* inspector.inspect(resolvedCwd).pipe(Effect.catchTag("ConfigurationError", () => Effect.succeed({ baseBranch: "main" })))).baseBranch;
265
261
  fromRef = yield* gitMergeBase(resolvedCwd, baseBranch);
266
262
  }
267
- const rawDiffs = computeWorkspaceDependencyDiffs(yield* reader.snapshotAt(resolvedCwd, fromRef), options.to ? yield* reader.snapshotAt(resolvedCwd, options.to) : snapshotFromWorktree(resolvedCwd));
263
+ const rawDiffs = computeWorkspaceDependencyDiffs(yield* pit.at(fromRef, { cwd: resolvedCwd }), options.to ? yield* pit.at(options.to, { cwd: resolvedCwd }) : yield* pit.worktree({ cwd: resolvedCwd }));
268
264
  const targetPkg = options.package;
269
- const publishable = yield* listPublishablePackageNames(yield* discovery.listPackages(resolvedCwd)).pipe(Effect.provide(provideDetector));
265
+ const livePackages = yield* discovery.listPackages(resolvedCwd);
266
+ const publishable = yield* listPublishablePackageNames(livePackages, resolvedCwd).pipe(Effect.provide(provideDetector));
267
+ const versionPrivate = yield* config.versionPrivate(resolvedCwd);
268
+ const inScope = /* @__PURE__ */ new Set();
269
+ for (const pkg of livePackages) {
270
+ if (yield* config.isIgnored(pkg.name, resolvedCwd)) continue;
271
+ if (publishable.has(pkg.name) || versionPrivate) inScope.add(pkg.name);
272
+ }
273
+ const targetIgnored = targetPkg ? yield* config.isIgnored(targetPkg, resolvedCwd) : false;
270
274
  const keepDevDeps = options.includeDevDeps === true;
271
- const scoped = targetPkg ? rawDiffs.filter((d) => d.package === targetPkg) : rawDiffs.filter((d) => publishable.has(d.package));
275
+ const scoped = targetPkg ? targetIgnored ? [] : rawDiffs.filter((d) => d.package === targetPkg) : rawDiffs.filter((d) => inScope.has(d.package));
272
276
  const resolved = [];
273
277
  for (const diff of scoped) {
274
- const next = yield* resolveDiffRows(diff, keepDevDeps).pipe(Effect.provide(provideResolver));
275
- if (next.rows.length > 0) resolved.push(next);
278
+ const rows = keepDevDeps ? [...diff.rows] : diff.rows.filter((r) => r.type !== "devDependency");
279
+ if (rows.length > 0) resolved.push({
280
+ ...diff,
281
+ rows: sortDependencyRows(rows)
282
+ });
276
283
  }
277
- const existingPure = findPureDependencyChangesets(changesetDir);
278
- const skippedMixed = findMixedDependencyChangesets(changesetDir);
279
- const toDelete = targetPkg ? existingPure.filter((p) => p.package === targetPkg) : existingPure.filter((p) => publishable.has(p.package));
284
+ const existingPure = yield* findPureDependencyChangesets(fs, changesetDir);
285
+ const skippedMixed = yield* findMixedDependencyChangesets(fs, changesetDir);
286
+ const toDelete = targetPkg ? targetIgnored ? [] : existingPure.filter((p) => p.package === targetPkg) : existingPure.filter((p) => inScope.has(p.package));
280
287
  const chosenFilenames = /* @__PURE__ */ new Set();
281
- return {
282
- toDelete,
283
- toWrite: resolved.map((diff) => ({
284
- file: join(changesetDir, `${randomFilename(changesetDir, chosenFilenames)}.md`),
288
+ const toWrite = [];
289
+ for (const diff of resolved) {
290
+ const filename = yield* randomFilename(fileExists, changesetDir, chosenFilenames);
291
+ toWrite.push({
292
+ file: join(changesetDir, `${filename}.md`),
285
293
  package: diff.package,
286
294
  diff
287
- })),
295
+ });
296
+ }
297
+ return {
298
+ toDelete,
299
+ toWrite,
288
300
  skippedMixed
289
301
  };
290
302
  });
291
- const execute = (plan) => Effect.sync(() => {
303
+ const execute = (plan) => Effect.gen(function* () {
292
304
  const deleted = [];
293
305
  const written = [];
294
306
  for (const entry of plan.toWrite) {
295
- writeFileSync(entry.file, renderChangesetContent(entry.diff));
307
+ yield* fs.writeFileString(entry.file, renderChangesetContent(entry.diff)).pipe(Effect.mapError((e) => new ChangesetIOError({
308
+ path: entry.file,
309
+ operation: "write",
310
+ reason: String(e)
311
+ })));
296
312
  written.push(entry.file);
297
313
  }
298
- for (const entry of plan.toDelete) try {
299
- unlinkSync(entry.file);
300
- deleted.push(entry.file);
301
- } catch {}
314
+ for (const entry of plan.toDelete) if (yield* Effect.isSuccess(fs.remove(entry.file))) deleted.push(entry.file);
302
315
  return {
303
316
  deleted,
304
317
  written,
@@ -313,15 +326,46 @@ function makeShape(reader, inspector, discovery, resolver, detector) {
313
326
  /**
314
327
  * Live layer for {@link DepsRegen}.
315
328
  *
316
- * Requires {@link WorkspaceSnapshotReader}, {@link ConfigInspector},
317
- * `WorkspaceDiscovery`, `CatalogResolver`, and `PublishabilityDetector`
318
- * (the last three from `workspaces-effect`).
329
+ * Requires `PointInTimeWorkspace`, `WorkspaceDiscovery`,
330
+ * `PublishabilityDetector` (all from `workspaces-effect`),
331
+ * {@link ConfigInspector}, {@link ChangesetConfig}, and
332
+ * `FileSystem.FileSystem` (resolved once at construction and closed over by
333
+ * the shape, keeping `plan`/`execute` themselves requirement-free).
319
334
  *
320
335
  * @public
321
336
  */
322
337
  const DepsRegenLive = Layer.effect(DepsRegen, Effect.gen(function* () {
323
- return makeShape(yield* WorkspaceSnapshotReader, yield* ConfigInspector, yield* WorkspaceDiscovery, yield* CatalogResolver, yield* PublishabilityDetector);
338
+ return makeShape(yield* PointInTimeWorkspace, yield* ConfigInspector, yield* WorkspaceDiscovery, yield* PublishabilityDetector, yield* ChangesetConfig, yield* FileSystem.FileSystem);
324
339
  }));
340
+ const WorkspaceGraph = Layer.mergeAll(WorkspaceRootLive, WorkspaceDiscoveryLive.pipe(Layer.provide(WorkspaceRootLive)));
341
+ const ConfigGraph = ChangesetConfigLive.pipe(Layer.provide(ChangesetConfigReaderLive));
342
+ /**
343
+ * Batteries-included {@link DepsRegen} layer: silk's opinionated default
344
+ * composition of the full dependency graph. Only the platform services
345
+ * remain — note that {@link PointInTimeWorkspace} reads git history, so
346
+ * this layer genuinely requires `CommandExecutor` in addition to
347
+ * `FileSystem`/`Path`: provide a git-capable platform layer
348
+ * (`NodeContext.layer`), not a bare filesystem-only layer.
349
+ *
350
+ * Gating uses silk's adaptive publishability detector
351
+ * ({@link PublishabilityDetectorAdaptiveLive}), so the default semantics
352
+ * are "versionable minus ignored" — identical to the savvy CLI and MCP
353
+ * runtimes. Consumers who need to swap any dependency (test detectors,
354
+ * alternate config sources) should keep composing {@link DepsRegenLive}
355
+ * directly; this layer is purely additive.
356
+ *
357
+ * @example
358
+ * ```typescript
359
+ * import { NodeContext } from "@effect/platform-node";
360
+ * import { Layer } from "effect";
361
+ * import { Changesets } from "@savvy-web/silk-effects";
362
+ *
363
+ * const depsRegen = Changesets.DepsRegenDefault.pipe(Layer.provide(NodeContext.layer));
364
+ * ```
365
+ *
366
+ * @public
367
+ */
368
+ const DepsRegenDefault = DepsRegenLive.pipe(Layer.provide(PointInTimeWorkspaceLive.pipe(Layer.provide(WorkspaceGraph))), Layer.provide(ConfigInspectorLive.pipe(Layer.provide(Layer.mergeAll(ChangesetConfigReaderLive, WorkspaceGraph)))), Layer.provide(PublishabilityDetectorAdaptiveLive.pipe(Layer.provide(ConfigGraph))), Layer.provide(ConfigGraph), Layer.provide(WorkspaceGraph));
325
369
 
326
370
  //#endregion
327
- export { DepsRegen, DepsRegenBase, DepsRegenLive, isPureDependencyChangeset, resolveDiffRows };
371
+ export { DepsRegen, DepsRegenBase, DepsRegenDefault, DepsRegenLive, isPureDependencyChangeset };
@@ -7,9 +7,8 @@ const require_changesets_config_esm = require('../../../../../../../node_modules
7
7
  const require_changesets_apply_release_plan_esm = require('../../../../../../../node_modules/.pnpm/@changesets_apply-release-plan@7.1.1/node_modules/@changesets/apply-release-plan/dist/changesets-apply-release-plan.esm.cjs');
8
8
  const require_changesets_get_release_plan_esm = require('../../../../../../../node_modules/.pnpm/@changesets_get-release-plan@4.0.16/node_modules/@changesets/get-release-plan/dist/changesets-get-release-plan.esm.cjs');
9
9
  let effect = require("effect");
10
+ let _effect_platform = require("@effect/platform");
10
11
  let node_path = require("node:path");
11
- let node_fs = require("node:fs");
12
- let node_os = require("node:os");
13
12
 
14
13
  //#region ../silk-effects/dist/dev/pkg/changesets/services/release-planner.js
15
14
  var import_get_packages_cjs = require_get_packages_cjs$1.default;
@@ -37,8 +36,8 @@ const errMsg = (e) => e instanceof Error ? e.message : String(e);
37
36
  const ReleasePlannerBase = effect.Context.Tag("ReleasePlanner")();
38
37
  /** Effect service tag for the release planner. @public */
39
38
  var ReleasePlanner = class extends ReleasePlannerBase {};
40
- /** Build the service shape over a resolved {@link ConfigInspector}. */
41
- function makeShape(inspector) {
39
+ /** Build the service shape over a resolved {@link ConfigInspector} and {@link FileSystem.FileSystem}. */
40
+ function makeShape(inspector, fs) {
42
41
  const plan = (root) => effect.Effect.tryPromise({
43
42
  try: () => require_changesets_get_release_plan_esm.default(root),
44
43
  catch: (e) => new require_errors.ReleasePlanError({
@@ -46,23 +45,17 @@ function makeShape(inspector) {
46
45
  reason: errMsg(e)
47
46
  })
48
47
  });
49
- const preview = (root) => effect.Effect.tryPromise({
50
- try: () => previewImpl(root),
51
- catch: (e) => new require_errors.ReleasePlanError({
52
- phase: "preview",
53
- reason: errMsg(e)
54
- })
55
- });
56
- const apply = (root, options) => applyEffect(root, options?.dryRun ?? false, inspector);
48
+ const preview = (root) => previewEffect(root, fs);
49
+ const apply = (root, options) => applyEffect(root, options?.dryRun ?? false, inspector, fs);
57
50
  return {
58
51
  plan,
59
52
  preview,
60
53
  apply
61
54
  };
62
55
  }
63
- /** Production layer. Requires {@link ConfigInspector} (used by `apply`). @public */
56
+ /** Production layer. Requires {@link ConfigInspector} (used by `apply`) and `FileSystem`. @public */
64
57
  const ReleasePlannerLive = effect.Layer.effect(ReleasePlanner, effect.Effect.gen(function* () {
65
- return makeShape(yield* require_config_inspector.ConfigInspector);
58
+ return makeShape(yield* require_config_inspector.ConfigInspector, yield* _effect_platform.FileSystem.FileSystem);
66
59
  }));
67
60
  /**
68
61
  * Test factory — supply fixed results for any subset of methods. Unsupplied
@@ -93,31 +86,52 @@ function extractVersionBlock(changelog, version) {
93
86
  }
94
87
  return lines.slice(start, end).join("\n").trim();
95
88
  }
96
- async function previewImpl(root) {
97
- const [plan, packages] = await Promise.all([require_changesets_get_release_plan_esm.default(root), buildPackages(root)]);
98
- const config = await require_changesets_config_esm.read(root, packages);
99
- const preMode = plan.preState ? plan.preState.mode : null;
100
- const changesets = plan.changesets.map((cs) => ({
101
- id: cs.id,
102
- summary: cs.summary,
103
- releases: cs.releases.filter((r) => r.type !== "none").map((r) => ({
104
- name: r.name,
105
- type: r.type
106
- }))
107
- }));
108
- const releasesToRender = plan.releases.filter((r) => r.type !== "none");
109
- if (releasesToRender.length === 0) return {
110
- preMode,
111
- releases: [],
112
- changesets
113
- };
114
- const tempRoot = (0, node_fs.mkdtempSync)((0, node_path.join)((0, node_os.tmpdir)(), "silk-preview-"));
115
- try {
89
+ /**
90
+ * Render a non-destructive preview by redirecting every write into a
91
+ * scope-managed temp directory (cleaned up automatically when the scope
92
+ * closes) and reading the generated CHANGELOG blocks back.
93
+ */
94
+ function previewEffect(root, fs) {
95
+ const program = effect.Effect.gen(function* () {
96
+ const [plan, packages] = yield* effect.Effect.tryPromise({
97
+ try: () => Promise.all([require_changesets_get_release_plan_esm.default(root), buildPackages(root)]),
98
+ catch: (e) => new require_errors.ReleasePlanError({
99
+ phase: "preview",
100
+ reason: errMsg(e)
101
+ })
102
+ });
103
+ const config = yield* effect.Effect.tryPromise({
104
+ try: () => require_changesets_config_esm.read(root, packages),
105
+ catch: (e) => new require_errors.ReleasePlanError({
106
+ phase: "preview",
107
+ reason: errMsg(e)
108
+ })
109
+ });
110
+ const preMode = plan.preState ? plan.preState.mode : null;
111
+ const changesets = plan.changesets.map((cs) => ({
112
+ id: cs.id,
113
+ summary: cs.summary,
114
+ releases: cs.releases.filter((r) => r.type !== "none").map((r) => ({
115
+ name: r.name,
116
+ type: r.type
117
+ }))
118
+ }));
119
+ const releasesToRender = plan.releases.filter((r) => r.type !== "none");
120
+ if (releasesToRender.length === 0) return {
121
+ preMode,
122
+ releases: [],
123
+ changesets
124
+ };
125
+ const tempRoot = yield* fs.makeTempDirectoryScoped({ prefix: "silk-preview-" });
116
126
  const mapDir = (dir) => {
117
127
  const rel = (0, node_path.relative)(packages.root.dir, dir);
118
- if (rel.startsWith("..") || (0, node_path.isAbsolute)(rel)) throw new Error(`Package directory is outside the workspace root: ${dir}`);
119
- return (0, node_path.join)(tempRoot, rel);
128
+ if (rel.startsWith("..") || (0, node_path.isAbsolute)(rel)) return effect.Effect.fail(new require_errors.ReleasePlanError({
129
+ phase: "preview",
130
+ reason: `Package directory is outside the workspace root: ${dir}`
131
+ }));
132
+ return effect.Effect.succeed((0, node_path.join)(tempRoot, rel));
120
133
  };
134
+ const tempDirs = yield* effect.Effect.forEach(packages.packages, (p) => mapDir(p.dir));
121
135
  const tempPackages = {
122
136
  tool: packages.tool,
123
137
  root: {
@@ -125,26 +139,33 @@ async function previewImpl(root) {
125
139
  dir: tempRoot,
126
140
  packageJson: structuredClone(packages.root.packageJson)
127
141
  },
128
- packages: packages.packages.map((p) => ({
142
+ packages: packages.packages.map((p, i) => ({
129
143
  ...p,
130
- dir: mapDir(p.dir),
144
+ dir: tempDirs[i],
131
145
  packageJson: structuredClone(p.packageJson)
132
146
  }))
133
147
  };
134
- (0, node_fs.mkdirSync)((0, node_path.join)(tempRoot, ".changeset"), { recursive: true });
135
- (0, node_fs.cpSync)((0, node_path.join)(root, "package.json"), (0, node_path.join)(tempRoot, "package.json"));
148
+ yield* fs.makeDirectory((0, node_path.join)(tempRoot, ".changeset"), { recursive: true });
149
+ yield* fs.copyFile((0, node_path.join)(root, "package.json"), (0, node_path.join)(tempRoot, "package.json"));
136
150
  const preJson = (0, node_path.join)(root, ".changeset", "pre.json");
137
- if ((0, node_fs.existsSync)(preJson)) (0, node_fs.cpSync)(preJson, (0, node_path.join)(tempRoot, ".changeset", "pre.json"));
138
- for (const p of packages.packages) {
139
- const tDir = mapDir(p.dir);
140
- (0, node_fs.mkdirSync)(tDir, { recursive: true });
141
- (0, node_fs.cpSync)((0, node_path.join)(p.dir, "package.json"), (0, node_path.join)(tDir, "package.json"));
151
+ if (yield* fs.exists(preJson)) yield* fs.copyFile(preJson, (0, node_path.join)(tempRoot, ".changeset", "pre.json"));
152
+ for (let i = 0; i < packages.packages.length; i++) {
153
+ const p = packages.packages[i];
154
+ const tDir = tempDirs[i];
155
+ yield* fs.makeDirectory(tDir, { recursive: true });
156
+ yield* fs.copyFile((0, node_path.join)(p.dir, "package.json"), (0, node_path.join)(tDir, "package.json"));
142
157
  const realCl = (0, node_path.join)(p.dir, "CHANGELOG.md");
143
- if ((0, node_fs.existsSync)(realCl)) (0, node_fs.cpSync)(realCl, (0, node_path.join)(tDir, "CHANGELOG.md"));
158
+ if (yield* fs.exists(realCl)) yield* fs.copyFile(realCl, (0, node_path.join)(tDir, "CHANGELOG.md"));
144
159
  }
145
160
  const rootCl = (0, node_path.join)(packages.root.dir, "CHANGELOG.md");
146
- if ((0, node_fs.existsSync)(rootCl)) (0, node_fs.cpSync)(rootCl, (0, node_path.join)(tempRoot, "CHANGELOG.md"));
147
- await require_changesets_apply_release_plan_esm.default(plan, tempPackages, config, void 0, root);
161
+ if (yield* fs.exists(rootCl)) yield* fs.copyFile(rootCl, (0, node_path.join)(tempRoot, "CHANGELOG.md"));
162
+ yield* effect.Effect.tryPromise({
163
+ try: () => require_changesets_apply_release_plan_esm.default(plan, tempPackages, config, void 0, root),
164
+ catch: (e) => new require_errors.ReleasePlanError({
165
+ phase: "preview",
166
+ reason: errMsg(e)
167
+ })
168
+ });
148
169
  const dirByName = /* @__PURE__ */ new Map();
149
170
  for (const p of tempPackages.packages) dirByName.set(p.packageJson.name, p.dir);
150
171
  if (tempPackages.root.packageJson.name) dirByName.set(tempPackages.root.packageJson.name, tempRoot);
@@ -153,9 +174,15 @@ async function previewImpl(root) {
153
174
  const dir = dirByName.get(r.name);
154
175
  if (!dir) continue;
155
176
  const clPath = (0, node_path.join)(dir, "CHANGELOG.md");
156
- if (!(0, node_fs.existsSync)(clPath)) continue;
157
- require_transformer.ChangelogTransformer.transformFile(clPath);
158
- const content = (0, node_fs.readFileSync)(clPath, "utf-8");
177
+ if (!(yield* fs.exists(clPath))) continue;
178
+ yield* effect.Effect.try({
179
+ try: () => require_transformer.ChangelogTransformer.transformFile(clPath),
180
+ catch: (e) => new require_errors.ReleasePlanError({
181
+ phase: "preview",
182
+ reason: errMsg(e)
183
+ })
184
+ });
185
+ const content = yield* fs.readFileString(clPath);
159
186
  releases.push({
160
187
  name: r.name,
161
188
  type: r.type,
@@ -170,22 +197,17 @@ async function previewImpl(root) {
170
197
  releases,
171
198
  changesets
172
199
  };
173
- } finally {
174
- (0, node_fs.rmSync)(tempRoot, {
175
- recursive: true,
176
- force: true
177
- });
178
- }
200
+ });
201
+ return effect.Effect.scoped(program).pipe(effect.Effect.mapError((e) => e instanceof require_errors.ReleasePlanError ? e : new require_errors.ReleasePlanError({
202
+ phase: "preview",
203
+ reason: errMsg(e)
204
+ })));
179
205
  }
180
- /** Re-read a package's version from disk (post-bump) to feed versionFiles. */
181
- function diskVersion(workspaceDir, fallback) {
182
- try {
183
- return JSON.parse((0, node_fs.readFileSync)((0, node_path.join)(workspaceDir, "package.json"), "utf-8")).version ?? fallback;
184
- } catch {
185
- return fallback;
186
- }
206
+ /** Re-read a package's version from disk (post-bump) to feed versionFiles; unreadable/unparseable falls back. */
207
+ function diskVersion(workspaceDir, fallback, fs) {
208
+ return fs.readFileString((0, node_path.join)(workspaceDir, "package.json")).pipe(effect.Effect.flatMap((raw) => effect.Effect.try(() => JSON.parse(raw).version ?? fallback)), effect.Effect.orElseSucceed(() => fallback));
187
209
  }
188
- function applyEffect(root, dryRun, inspector) {
210
+ function applyEffect(root, dryRun, inspector, fs) {
189
211
  return effect.Effect.gen(function* () {
190
212
  const { plan, packages, config } = yield* effect.Effect.tryPromise({
191
213
  try: async () => {
@@ -222,22 +244,24 @@ function applyEffect(root, dryRun, inspector) {
222
244
  const newVersionByName = new Map(plan.releases.map((r) => [r.name, r.newVersion]));
223
245
  const inspected = yield* inspector.inspect(root).pipe(effect.Effect.catchAll((error) => effect.Effect.logWarning(`Skipping versionFiles update: ${errMsg(error)}`).pipe(effect.Effect.as(null))));
224
246
  let versionFileUpdates = [];
225
- if (inspected) versionFileUpdates = yield* effect.Effect.try({
226
- try: () => {
227
- const scopes = inspected.packages.filter((p) => p.versionFiles.length > 0).map((p) => {
228
- const fresh = dryRun ? newVersionByName.get(p.name) ?? p.version : diskVersion(p.workspaceDir, p.version);
229
- return fresh !== p.version ? {
230
- ...p,
231
- version: fresh
232
- } : p;
233
- });
234
- return scopes.length > 0 ? require_version_files.VersionFiles.processResolvedVersionFiles(scopes, dryRun) : [];
235
- },
236
- catch: (e) => new require_errors.ReleasePlanError({
237
- phase: "apply",
238
- reason: errMsg(e)
239
- })
240
- });
247
+ if (inspected) {
248
+ const candidates = inspected.packages.filter((p) => p.versionFiles.length > 0);
249
+ const freshVersions = yield* effect.Effect.forEach(candidates, (p) => dryRun ? effect.Effect.succeed(newVersionByName.get(p.name) ?? p.version) : diskVersion(p.workspaceDir, p.version, fs));
250
+ const scopes = candidates.map((p, i) => {
251
+ const fresh = freshVersions[i];
252
+ return fresh !== p.version ? {
253
+ ...p,
254
+ version: fresh
255
+ } : p;
256
+ });
257
+ versionFileUpdates = yield* effect.Effect.try({
258
+ try: () => scopes.length > 0 ? require_version_files.VersionFiles.processResolvedVersionFiles(scopes, dryRun) : [],
259
+ catch: (e) => new require_errors.ReleasePlanError({
260
+ phase: "apply",
261
+ reason: errMsg(e)
262
+ })
263
+ });
264
+ }
241
265
  return {
242
266
  dryRun,
243
267
  touchedFiles,