@savvy-web/silk 2.2.2 → 2.2.4

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.
@@ -394,10 +394,28 @@ const ChangesetConfigLive = Layer.effect(ChangesetConfig, Effect.gen(function* (
394
394
  fixed: (root) => read(root).pipe(Effect.map(Option.match({
395
395
  onNone: () => [],
396
396
  onSome: (cfg) => cfg.fixed ?? []
397
- })))
397
+ }))),
398
+ refresh: () => Effect.sync(() => cache.clear())
398
399
  };
399
400
  }));
400
401
 
402
+ //#endregion
403
+ //#region ../silk-effects/dist/dev/pkg/utils/TrailingSlash.js
404
+ /**
405
+ * Trim trailing slashes from a string.
406
+ *
407
+ * @remarks
408
+ * Trims trailing slashes with an index scan rather than `/\/+$/`. That regex is
409
+ * unanchored at the start, so the engine retries the match from every position
410
+ * and degrades to O(n²) on a string of many slashes (CodeQL `js/polynomial-redos`).
411
+ * Only a trailing run of slashes is removed; interior slash runs are untouched.
412
+ */
413
+ const trimTrailingSlashes = (s) => {
414
+ let end = s.length;
415
+ while (end > 0 && s[end - 1] === "/") end -= 1;
416
+ return s.slice(0, end);
417
+ };
418
+
401
419
  //#endregion
402
420
  //#region ../../node_modules/.pnpm/workspaces-effect@2.0.3_@effect+platform@0.96.2_effect@3.21.4__effect@3.21.4/node_modules/workspaces-effect/errors/CatalogAssemblyError.js
403
421
  /**
@@ -11064,18 +11082,16 @@ var SilkPublishability = class {
11064
11082
  * normalizes to `.`, matching how a root-directory target is recorded.
11065
11083
  *
11066
11084
  * @remarks
11067
- * Trailing slashes are trimmed with an index scan rather than `/\/+$/`. That
11068
- * regex is unanchored at the start, so the engine retries the match from every
11069
- * position and degrades to O(n²) on a path of many slashes (CodeQL
11070
- * `js/polynomial-redos`). `dir` reaches here from `dist/prod/targets.json`, which
11071
- * the bundler writes but a consumer repo could hand-edit.
11085
+ * Trailing-slash trimming is delegated to the shared `trimTrailingSlashes`
11086
+ * index-scan helper rather than `/\/+$/`: that regex is unanchored at the
11087
+ * start, so the engine retries the match from every position and degrades to
11088
+ * O(n²) on a path of many slashes (CodeQL `js/polynomial-redos`). `dir` reaches
11089
+ * here from `dist/prod/targets.json`, which the bundler writes but a consumer
11090
+ * repo could hand-edit.
11072
11091
  */
11073
11092
  const normalizeDir = (dir) => {
11074
11093
  const slashed = dir.replaceAll("\\", "/");
11075
- const withoutPrefix = slashed.startsWith("./") ? slashed.slice(2) : slashed;
11076
- let end = withoutPrefix.length;
11077
- while (end > 0 && withoutPrefix[end - 1] === "/") end -= 1;
11078
- const normalized = withoutPrefix.slice(0, end);
11094
+ const normalized = trimTrailingSlashes(slashed.startsWith("./") ? slashed.slice(2) : slashed);
11079
11095
  return normalized === "" ? "." : normalized;
11080
11096
  };
11081
11097
  /** True when a built target directory's package.json is `private: true`. Missing/unreadable/malformed → false. */
@@ -38943,9 +38959,14 @@ function makeShape$3(reader, discovery, fs) {
38943
38959
  const inspected = yield* inspect(cwd);
38944
38960
  return paths.map((p) => classifyOne(inspected, p));
38945
38961
  });
38962
+ const refresh = () => Effect.gen(function* () {
38963
+ cache.clear();
38964
+ yield* discovery.refresh();
38965
+ });
38946
38966
  return {
38947
38967
  inspect,
38948
- classify
38968
+ classify,
38969
+ refresh
38949
38970
  };
38950
38971
  }
38951
38972
  /**
@@ -39015,7 +39036,8 @@ const ConfigInspectorLive = Layer.effect(ConfigInspector, Effect.gen(function* (
39015
39036
  function makeConfigInspectorTest(fixed) {
39016
39037
  return Layer.succeed(ConfigInspector, {
39017
39038
  inspect: () => Effect.succeed(fixed),
39018
- classify: (_cwd, paths) => Effect.succeed(paths.map((p) => classifyOne(fixed, p)))
39039
+ classify: (_cwd, paths) => Effect.succeed(paths.map((p) => classifyOne(fixed, p))),
39040
+ refresh: () => Effect.void
39019
39041
  });
39020
39042
  }
39021
39043
 
@@ -39533,6 +39555,48 @@ function gitMergeBase(cwd, base) {
39533
39555
  }
39534
39556
  });
39535
39557
  }
39558
+ /**
39559
+ * List the basenames of `.changeset/*.md` files tracked at `ref` (e.g. the
39560
+ * merge base), via `git ls-tree -r --name-only`. Used by `DepsRegen.plan()`
39561
+ * to protect changesets authored by already-merged PRs from being deleted by
39562
+ * an unrelated branch's regen run (#258).
39563
+ *
39564
+ * @remarks
39565
+ * Deliberately tolerant, unlike {@link gitMergeBase}: `cwd` may not be a git
39566
+ * repository at all (many `DepsRegen` unit tests pass synthetic refs like
39567
+ * `"BEFORE"`/`"AFTER"` against a bare tmpdir), and an unresolvable ref is a
39568
+ * plausible caller mistake rather than a fatal condition. Either failure mode
39569
+ * resolves to an empty set — "nothing protected" — rather than propagating a
39570
+ * {@link GitError}, so a missing/invalid git context degrades the
39571
+ * authorship filter to a no-op instead of blocking the whole plan.
39572
+ *
39573
+ * @internal
39574
+ */
39575
+ function gitListChangesetFilesAtRef(cwd, ref) {
39576
+ return Effect.sync(() => {
39577
+ try {
39578
+ const out = execFileSync("git", [
39579
+ "ls-tree",
39580
+ "-r",
39581
+ "--name-only",
39582
+ ref,
39583
+ "--",
39584
+ ".changeset"
39585
+ ], {
39586
+ cwd,
39587
+ encoding: "utf8",
39588
+ stdio: [
39589
+ "ignore",
39590
+ "pipe",
39591
+ "pipe"
39592
+ ]
39593
+ });
39594
+ return new Set(out.split(/\r?\n/).filter((line) => line.trim().length > 0).map((path) => basename(path)));
39595
+ } catch {
39596
+ return /* @__PURE__ */ new Set();
39597
+ }
39598
+ });
39599
+ }
39536
39600
 
39537
39601
  //#endregion
39538
39602
  //#region ../silk-effects/dist/dev/pkg/changesets/utils/publishability.js
@@ -39599,6 +39663,15 @@ function listPublishablePackageNames(packages, root) {
39599
39663
  * first, then deleting the stale pure-dependency ones (so an interrupted
39600
39664
  * run loses nothing and is safely re-runnable).
39601
39665
  *
39666
+ * A pure dependency changeset is only ever a delete candidate when BOTH:
39667
+ * its package is in scope AND actually produced a fresh diff this run
39668
+ * (present in {@link RegenPlan.toWrite}), and the file was authored on
39669
+ * this branch rather than already committed at the merge-base ref. Either
39670
+ * gap silently destroyed a still-relevant release note before this was
39671
+ * fixed (#258) — a devDependency-only diff drops all its rows and was
39672
+ * never rewritten, and a changeset an earlier, already-merged PR
39673
+ * committed was swept away by an unrelated branch's regen run.
39674
+ *
39602
39675
  * This is the single source of truth for regen/detect: the CLI commands
39603
39676
  * and MCP tools are thin adapters over this service.
39604
39677
  *
@@ -39818,7 +39891,8 @@ function makeShape$1(pit, inspector, discovery, detector, config, fs) {
39818
39891
  const plan = (options) => Effect.gen(function* () {
39819
39892
  const resolvedCwd = resolve(options.cwd);
39820
39893
  const changesetDir = join(resolvedCwd, ".changeset");
39821
- yield* discovery.refresh();
39894
+ yield* inspector.refresh();
39895
+ yield* config.refresh();
39822
39896
  let fromRef = options.from;
39823
39897
  if (!fromRef) {
39824
39898
  let baseBranch = options.base;
@@ -39855,7 +39929,10 @@ function makeShape$1(pit, inspector, discovery, detector, config, fs) {
39855
39929
  }
39856
39930
  const existingPure = yield* findPureDependencyChangesets(fs, changesetDir);
39857
39931
  const skippedMixed = yield* findMixedDependencyChangesets(fs, changesetDir);
39858
- const toDelete = existingPure.filter((p) => inScopeFor(p.package));
39932
+ const rewrittenPackages = new Set(resolved.map((d) => d.package));
39933
+ const atMergeBase = yield* gitListChangesetFilesAtRef(resolvedCwd, fromRef);
39934
+ const authoredOnBranch = (file) => !atMergeBase.has(basename(file));
39935
+ const toDelete = existingPure.filter((p) => inScopeFor(p.package) && rewrittenPackages.has(p.package) && authoredOnBranch(p.file));
39859
39936
  const chosenFilenames = /* @__PURE__ */ new Set();
39860
39937
  const toWrite = [];
39861
39938
  for (const diff of resolved) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@savvy-web/silk",
3
- "version": "2.2.2",
3
+ "version": "2.2.4",
4
4
  "private": false,
5
5
  "description": "The single Silk Suite dev-tooling package — changeset, commitlint, lint, and biome conventions",
6
6
  "homepage": "https://github.com/savvy-web/systems/tree/main/packages/silk",
@@ -67,9 +67,9 @@
67
67
  "dependencies": {
68
68
  "@effect/platform": "^0.96.2",
69
69
  "@savvy-web/changelog": "0.1.1",
70
- "@savvy-web/cli": "1.5.5",
71
- "@savvy-web/mcp": "1.7.2",
72
- "@savvy-web/silk-effects": "3.2.1",
70
+ "@savvy-web/cli": "1.5.7",
71
+ "@savvy-web/mcp": "1.7.4",
72
+ "@savvy-web/silk-effects": "3.2.3",
73
73
  "effect": "^3.21.4",
74
74
  "semver": "^7.8.5"
75
75
  },