@savvy-web/silk-effects 3.2.2 → 3.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.
package/README.md
CHANGED
|
@@ -145,7 +145,7 @@ See [Publishability](./docs/publishability.md) for the adaptive layer and the `C
|
|
|
145
145
|
|
|
146
146
|
#### ChangesetConfig
|
|
147
147
|
|
|
148
|
-
Typed accessor over a workspace root's `.changeset/config.json`, reading through `ChangesetConfigReader` with a per-root cache. Every accessor is total — a missing or unreadable config collapses to `mode: "none"` and empty defaults. Methods: `mode`, `versionPrivate`, `ignorePatterns`, `isIgnored`, `fixed
|
|
148
|
+
Typed accessor over a workspace root's `.changeset/config.json`, reading through `ChangesetConfigReader` with a per-root cache. Every accessor is total — a missing or unreadable config collapses to `mode: "none"` and empty defaults. Methods: `mode`, `versionPrivate`, `ignorePatterns`, `isIgnored`, `fixed` and `refresh` (drops every cached read so a long-lived host observes on-disk config edits made since the last call), plus a static `ChangesetConfig.matches(name, pattern)`.
|
|
149
149
|
|
|
150
150
|
```typescript
|
|
151
151
|
import { Effect } from "effect";
|
|
@@ -402,9 +402,14 @@ function makeShape(reader, discovery, fs) {
|
|
|
402
402
|
const inspected = yield* inspect(cwd);
|
|
403
403
|
return paths.map((p) => classifyOne(inspected, p));
|
|
404
404
|
});
|
|
405
|
+
const refresh = () => Effect.gen(function* () {
|
|
406
|
+
cache.clear();
|
|
407
|
+
yield* discovery.refresh();
|
|
408
|
+
});
|
|
405
409
|
return {
|
|
406
410
|
inspect,
|
|
407
|
-
classify
|
|
411
|
+
classify,
|
|
412
|
+
refresh
|
|
408
413
|
};
|
|
409
414
|
}
|
|
410
415
|
/**
|
|
@@ -474,7 +479,8 @@ const ConfigInspectorLive = Layer.effect(ConfigInspector, Effect.gen(function* (
|
|
|
474
479
|
function makeConfigInspectorTest(fixed) {
|
|
475
480
|
return Layer.succeed(ConfigInspector, {
|
|
476
481
|
inspect: () => Effect.succeed(fixed),
|
|
477
|
-
classify: (_cwd, paths) => Effect.succeed(paths.map((p) => classifyOne(fixed, p)))
|
|
482
|
+
classify: (_cwd, paths) => Effect.succeed(paths.map((p) => classifyOne(fixed, p))),
|
|
483
|
+
refresh: () => Effect.void
|
|
478
484
|
});
|
|
479
485
|
}
|
|
480
486
|
|
|
@@ -5,10 +5,10 @@ import { ChangesetConfig, ChangesetConfigLive } from "../../services/ChangesetCo
|
|
|
5
5
|
import { PublishabilityDetectorAdaptiveLive } from "../../services/SilkPublishability.js";
|
|
6
6
|
import { ConfigInspector, ConfigInspectorLive } from "./config-inspector.js";
|
|
7
7
|
import { computeWorkspaceDependencyDiffs } from "../utils/dep-diff.js";
|
|
8
|
-
import { gitMergeBase } from "../utils/git.js";
|
|
8
|
+
import { gitListChangesetFilesAtRef, gitMergeBase } from "../utils/git.js";
|
|
9
9
|
import { listPublishablePackageNames } from "../utils/publishability.js";
|
|
10
10
|
import { Context, Effect, Layer, Option } from "effect";
|
|
11
|
-
import { join, resolve } from "node:path";
|
|
11
|
+
import { basename, join, resolve } from "node:path";
|
|
12
12
|
import { FileSystem } from "@effect/platform";
|
|
13
13
|
import { PointInTimeWorkspace, PointInTimeWorkspaceLive, PublishabilityDetector, WorkspaceDiscovery, WorkspaceDiscoveryLive, WorkspaceRootLive } from "workspaces-effect";
|
|
14
14
|
|
|
@@ -30,6 +30,15 @@ import { PointInTimeWorkspace, PointInTimeWorkspaceLive, PublishabilityDetector,
|
|
|
30
30
|
* first, then deleting the stale pure-dependency ones (so an interrupted
|
|
31
31
|
* run loses nothing and is safely re-runnable).
|
|
32
32
|
*
|
|
33
|
+
* A pure dependency changeset is only ever a delete candidate when BOTH:
|
|
34
|
+
* its package is in scope AND actually produced a fresh diff this run
|
|
35
|
+
* (present in {@link RegenPlan.toWrite}), and the file was authored on
|
|
36
|
+
* this branch rather than already committed at the merge-base ref. Either
|
|
37
|
+
* gap silently destroyed a still-relevant release note before this was
|
|
38
|
+
* fixed (#258) — a devDependency-only diff drops all its rows and was
|
|
39
|
+
* never rewritten, and a changeset an earlier, already-merged PR
|
|
40
|
+
* committed was swept away by an unrelated branch's regen run.
|
|
41
|
+
*
|
|
33
42
|
* This is the single source of truth for regen/detect: the CLI commands
|
|
34
43
|
* and MCP tools are thin adapters over this service.
|
|
35
44
|
*
|
|
@@ -250,7 +259,8 @@ function makeShape(pit, inspector, discovery, detector, config, fs) {
|
|
|
250
259
|
const plan = (options) => Effect.gen(function* () {
|
|
251
260
|
const resolvedCwd = resolve(options.cwd);
|
|
252
261
|
const changesetDir = join(resolvedCwd, ".changeset");
|
|
253
|
-
yield*
|
|
262
|
+
yield* inspector.refresh();
|
|
263
|
+
yield* config.refresh();
|
|
254
264
|
let fromRef = options.from;
|
|
255
265
|
if (!fromRef) {
|
|
256
266
|
let baseBranch = options.base;
|
|
@@ -287,7 +297,10 @@ function makeShape(pit, inspector, discovery, detector, config, fs) {
|
|
|
287
297
|
}
|
|
288
298
|
const existingPure = yield* findPureDependencyChangesets(fs, changesetDir);
|
|
289
299
|
const skippedMixed = yield* findMixedDependencyChangesets(fs, changesetDir);
|
|
290
|
-
const
|
|
300
|
+
const rewrittenPackages = new Set(resolved.map((d) => d.package));
|
|
301
|
+
const atMergeBase = yield* gitListChangesetFilesAtRef(resolvedCwd, fromRef);
|
|
302
|
+
const authoredOnBranch = (file) => !atMergeBase.has(basename(file));
|
|
303
|
+
const toDelete = existingPure.filter((p) => inScopeFor(p.package) && rewrittenPackages.has(p.package) && authoredOnBranch(p.file));
|
|
291
304
|
const chosenFilenames = /* @__PURE__ */ new Set();
|
|
292
305
|
const toWrite = [];
|
|
293
306
|
for (const diff of resolved) {
|
package/changesets/utils/git.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { GitError } from "../errors.js";
|
|
2
2
|
import { Effect } from "effect";
|
|
3
|
+
import { basename } from "node:path";
|
|
3
4
|
import { execFileSync } from "node:child_process";
|
|
4
5
|
|
|
5
6
|
//#region src/changesets/utils/git.ts
|
|
@@ -46,6 +47,48 @@ function gitMergeBase(cwd, base) {
|
|
|
46
47
|
}
|
|
47
48
|
});
|
|
48
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* List the basenames of `.changeset/*.md` files tracked at `ref` (e.g. the
|
|
52
|
+
* merge base), via `git ls-tree -r --name-only`. Used by `DepsRegen.plan()`
|
|
53
|
+
* to protect changesets authored by already-merged PRs from being deleted by
|
|
54
|
+
* an unrelated branch's regen run (#258).
|
|
55
|
+
*
|
|
56
|
+
* @remarks
|
|
57
|
+
* Deliberately tolerant, unlike {@link gitMergeBase}: `cwd` may not be a git
|
|
58
|
+
* repository at all (many `DepsRegen` unit tests pass synthetic refs like
|
|
59
|
+
* `"BEFORE"`/`"AFTER"` against a bare tmpdir), and an unresolvable ref is a
|
|
60
|
+
* plausible caller mistake rather than a fatal condition. Either failure mode
|
|
61
|
+
* resolves to an empty set — "nothing protected" — rather than propagating a
|
|
62
|
+
* {@link GitError}, so a missing/invalid git context degrades the
|
|
63
|
+
* authorship filter to a no-op instead of blocking the whole plan.
|
|
64
|
+
*
|
|
65
|
+
* @internal
|
|
66
|
+
*/
|
|
67
|
+
function gitListChangesetFilesAtRef(cwd, ref) {
|
|
68
|
+
return Effect.sync(() => {
|
|
69
|
+
try {
|
|
70
|
+
const out = execFileSync("git", [
|
|
71
|
+
"ls-tree",
|
|
72
|
+
"-r",
|
|
73
|
+
"--name-only",
|
|
74
|
+
ref,
|
|
75
|
+
"--",
|
|
76
|
+
".changeset"
|
|
77
|
+
], {
|
|
78
|
+
cwd,
|
|
79
|
+
encoding: "utf8",
|
|
80
|
+
stdio: [
|
|
81
|
+
"ignore",
|
|
82
|
+
"pipe",
|
|
83
|
+
"pipe"
|
|
84
|
+
]
|
|
85
|
+
});
|
|
86
|
+
return new Set(out.split(/\r?\n/).filter((line) => line.trim().length > 0).map((path) => basename(path)));
|
|
87
|
+
} catch {
|
|
88
|
+
return /* @__PURE__ */ new Set();
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
49
92
|
|
|
50
93
|
//#endregion
|
|
51
|
-
export { gitMergeBase };
|
|
94
|
+
export { gitListChangesetFilesAtRef, gitMergeBase };
|
package/index.d.ts
CHANGED
|
@@ -3002,6 +3002,17 @@ interface ConfigInspectorShape {
|
|
|
3002
3002
|
* input path, in the same order
|
|
3003
3003
|
*/
|
|
3004
3004
|
readonly classify: (cwd: string, paths: ReadonlyArray<string>) => Effect.Effect<ReadonlyArray<Classification>, ConfigurationError>;
|
|
3005
|
+
/**
|
|
3006
|
+
* Drop the cached {@link InspectedConfig} for every previously-inspected
|
|
3007
|
+
* root, and refresh the underlying `WorkspaceDiscovery` snapshot. Callers
|
|
3008
|
+
* that hold this service across multiple logical operations in a single
|
|
3009
|
+
* process (e.g. a long-lived MCP server) must call this before an
|
|
3010
|
+
* operation that needs to observe on-disk edits made since the last
|
|
3011
|
+
* `inspect`/`classify` call — the cache never expires on its own.
|
|
3012
|
+
*
|
|
3013
|
+
* @returns An Effect that clears the cache and succeeds with `void`.
|
|
3014
|
+
*/
|
|
3015
|
+
readonly refresh: () => Effect.Effect<void>;
|
|
3005
3016
|
}
|
|
3006
3017
|
/**
|
|
3007
3018
|
* Base class for {@link ConfigInspector}.
|
|
@@ -3697,6 +3708,14 @@ declare const ChangesetConfig_base: Context.TagClass<ChangesetConfig, "@savvy-we
|
|
|
3697
3708
|
readonly ignorePatterns: (root: string) => Effect.Effect<ReadonlyArray<string>>;
|
|
3698
3709
|
readonly isIgnored: (name: string, root: string) => Effect.Effect<boolean>;
|
|
3699
3710
|
readonly fixed: (root: string) => Effect.Effect<ReadonlyArray<ReadonlyArray<string>>>;
|
|
3711
|
+
/**
|
|
3712
|
+
* Drop the cached read for every previously-read root. Callers that hold
|
|
3713
|
+
* this service across multiple logical operations in a single process
|
|
3714
|
+
* (e.g. a long-lived MCP server) must call this before an operation that
|
|
3715
|
+
* needs to observe an on-disk edit made since the last accessor call —
|
|
3716
|
+
* the cache never expires on its own.
|
|
3717
|
+
*/
|
|
3718
|
+
readonly refresh: () => Effect.Effect<void>;
|
|
3700
3719
|
}>;
|
|
3701
3720
|
/**
|
|
3702
3721
|
* Accessor service over a workspace root's `.changeset/config.json`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@savvy-web/silk-effects",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.4",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Shared Effect library for Silk Suite conventions",
|
|
6
6
|
"homepage": "https://github.com/savvy-web/systems/tree/main/packages/silk-effects",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"jsonc-effect": "^0.3.1",
|
|
37
37
|
"mdast-util-heading-range": "^4.0.0",
|
|
38
38
|
"mdast-util-to-string": "^4.0.0",
|
|
39
|
-
"prettier": "^3.9.
|
|
39
|
+
"prettier": "^3.9.5",
|
|
40
40
|
"remark-gfm": "^4.0.1",
|
|
41
41
|
"remark-parse": "^11.0.0",
|
|
42
42
|
"remark-stringify": "^11.0.0",
|
|
@@ -72,7 +72,8 @@ const ChangesetConfigLive = Layer.effect(ChangesetConfig, Effect.gen(function* (
|
|
|
72
72
|
fixed: (root) => read(root).pipe(Effect.map(Option.match({
|
|
73
73
|
onNone: () => [],
|
|
74
74
|
onSome: (cfg) => cfg.fixed ?? []
|
|
75
|
-
})))
|
|
75
|
+
}))),
|
|
76
|
+
refresh: () => Effect.sync(() => cache.clear())
|
|
76
77
|
};
|
|
77
78
|
}));
|
|
78
79
|
|