@savvy-web/silk-effects 5.1.0 → 5.1.3

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
@@ -107,6 +107,17 @@ const tags = strategy.tagsFor([{ name: "@savvy-web/silk-effects", version: "1.0.
107
107
 
108
108
  `WorkspaceAnalysis.versioning` and `WorkspaceAnalysis.tagStrategy` carry those kit types directly.
109
109
 
110
+ #### ChangesetLinter
111
+
112
+ Validate a changeset file against the Silk section rules. `ChangesetLinter.validateContent(content, filePath?)` and `ChangesetLinter.validateFile(filePath)` are static and synchronous, returning `LintMessage[]` — no Effect, no layers. Rules cover the valid section headings, structural constraints, and the dependency-table format, so a `## Dependencies` section written as prose or a bullet list is reported rather than accepted.
113
+
114
+ ```typescript
115
+ import { Changesets } from "@savvy-web/silk-effects";
116
+
117
+ const messages = Changesets.ChangesetLinter.validateFile(".changeset/quiet-moons-render.md");
118
+ // => [] when the file is valid, otherwise one LintMessage per violation
119
+ ```
120
+
110
121
  ---
111
122
 
112
123
  ### FileSystem layer required
@@ -258,10 +269,68 @@ const result = await Effect.runPromise(
258
269
  // => { updated: true, skipped: false, current: "2.0.0" }
259
270
  ```
260
271
 
272
+ #### ConfigInspector
273
+
274
+ Resolve `.changeset/config.json` into a fully attributed view of the workspace: the configured changelog, base branch, access and ignore list, plus one scope per package carrying its `workspaceDir`, version, `additionalScopes` and resolved `versionFiles`. `inspect(cwd)` returns that view and `classify` maps arbitrary file paths to the package that owns them, which is how a branch diff becomes a per-package attribution. `refresh()` clears the per-root cache, which a long-lived host needs in order to see config edits made between calls.
275
+
276
+ `Changesets.ConfigInspectorLive` requires `ChangesetConfigReader`, `WorkspaceDiscovery` from [`@effected/workspaces`](https://www.npmjs.com/package/@effected/workspaces) and `FileSystem`.
277
+
278
+ #### ReleasePlanner
279
+
280
+ Drive the genuine changesets engine rather than shelling out to the `changeset` binary. Three members:
281
+
282
+ - `plan(root)` computes the in-memory release plan. It renders nothing, so it resolves no changelog module.
283
+ - `preview(root, options?)` renders a non-destructive preview, running the real engine against a scope-managed temp directory and reading the generated CHANGELOG blocks back. The repository is never mutated.
284
+ - `apply(root, options?)` performs the release — version bumps, CHANGELOG writes and configured version-file updates. Pass `dryRun` to compute without writing.
285
+
286
+ Both `preview` and `apply` accept `changelogModules`, mapping the changelog id configured in `.changeset/config.json` to an absolute module path. Reach for it when running somewhere the configured id cannot be resolved — a bundled GitHub Action with no `node_modules`, for instance. When set, the configured id must be a key of the map, an unmapped id fails with a `ReleasePlanError` naming the supported keys, and the engine's formatter integration is disabled so the caller owns formatting.
287
+
288
+ ```typescript
289
+ const preview = yield* planner.preview(root, {
290
+ changelogModules: { "@savvy-web/changelog": changelogModulePath },
291
+ });
292
+ // => ChangesetPreview: per-release changelogEntry, versions and changeset ids
293
+ ```
294
+
295
+ `Changesets.ReleasePlannerLive` requires `ConfigInspector` and `FileSystem`.
296
+
261
297
  ---
262
298
 
263
299
  ### FileSystem + process layer required
264
300
 
301
+ #### BranchAnalyzer
302
+
303
+ `analyzeBranch` classifies a branch's diff by the package that owns each file, applying `ConfigInspector` attribution over the git range. This is what answers "what changed on this branch, and which package releases because of it", including the unmapped files that belong to no package.
304
+
305
+ `Changesets.BranchAnalyzerLive` requires `ConfigInspector` and the platform process spawner.
306
+
307
+ #### DepsRegen
308
+
309
+ Own dependency-changeset orchestration, split so that detection and regeneration share one code path: `plan(options)` computes a complete, side-effect-free `RegenPlan` — target filenames, each row's from/to version, and any stale pure-dependency changesets marked for deletion — and `execute(plan)` applies exactly what the plan describes. A dry run is `plan()` plus rendering.
310
+
311
+ Both sides of the diff are snapshotted at their own git ref, so `catalog:` and `workspace:` specifiers resolve per side before the comparison. A specifier that changes protocol without changing its resolved version produces no row.
312
+
313
+ `Changesets.DepsRegenDefault` is the batteries-included layer, composing the full graph with silk's opinionated defaults and leaving only the platform services open. Because snapshots read git history, provide a spawn-capable layer such as `NodeServices.layer` rather than a filesystem-only one.
314
+
315
+ ```typescript
316
+ import { Effect } from "effect";
317
+ import { NodeServices } from "@effect/platform-node";
318
+ import { Changesets } from "@savvy-web/silk-effects";
319
+
320
+ const plan = await Effect.runPromise(
321
+ Effect.gen(function* () {
322
+ const regen = yield* Changesets.DepsRegen;
323
+ return yield* regen.plan({});
324
+ }).pipe(
325
+ Effect.provide(Changesets.DepsRegenDefault),
326
+ Effect.provide(NodeServices.layer),
327
+ ),
328
+ );
329
+ // => RegenPlan: files to write, rows per package, changesets to delete
330
+ ```
331
+
332
+ `Changesets.DepsRegenLive` is the seam for callers injecting their own dependencies; it requires `WorkspaceSnapshots`, `ConfigInspector`, `WorkspaceDiscovery`, `PublishabilityDetector`, `ChangesetConfig`, `Git` and `FileSystem`.
333
+
265
334
  #### TurboInspector
266
335
 
267
336
  Read-only Turborepo inspection. Every method shells out to `turbo` with `--dry=json`, so no task ever runs. `diagnoseCache(task, cwd)` reports a per-package cache HIT/MISS breakdown for a task, `taskGraph(cwd, task?)` derives the task graph and its critical path and `affected(cwd, base?)` lists the packages affected relative to `base` (default `main`). It resolves the `turbo` binary through `ToolDiscovery` from [`@effected/commands`](https://www.npmjs.com/package/@effected/commands) and fails with a tagged error when `turbo` is missing or the directory is not a Turborepo. The service tag and its layer are exported under the `Turbo` namespace.
@@ -78,6 +78,22 @@ function inferDependencyType(dep) {
78
78
  return "dependency";
79
79
  }
80
80
  /**
81
+ * Narrow a dependency update to one with both version endpoints present.
82
+ *
83
+ * `@changesets/types` only guarantees `oldVersion`/`newVersion` on the
84
+ * `major`/`minor`/`patch` arms of `ComprehensiveRelease`; a `type: "none"`
85
+ * entry may carry neither. The table's `From`/`To` columns are validated
86
+ * version strings, so an entry missing either endpoint has no row to render.
87
+ *
88
+ * @param dep - The dependency update to test
89
+ * @returns `true` when both `oldVersion` and `newVersion` are present
90
+ *
91
+ * @internal
92
+ */
93
+ function isVersioned(dep) {
94
+ return dep.oldVersion !== void 0 && dep.newVersion !== void 0;
95
+ }
96
+ /**
81
97
  * Format dependency release lines as a structured markdown table.
82
98
  *
83
99
  * This is the core Effect program that implements the `getDependencyReleaseLine`
@@ -88,8 +104,9 @@ function inferDependencyType(dep) {
88
104
  * The function maps each `ModCompWithPackage` entry to a `DependencyTableRow`,
89
105
  * inferring the dependency type from the consuming package's `package.json`,
90
106
  * then delegates to `serializeDependencyTableToMarkdown` for GFM table
91
- * rendering, prefixed with a `### Dependencies` heading. Returns an empty
92
- * string when no dependencies were updated.
107
+ * rendering, prefixed with a `### Dependencies` heading. Entries missing
108
+ * either version endpoint are dropped by {@link isVersioned}; the function
109
+ * returns an empty string when no rows survive.
93
110
  *
94
111
  * The `_changesets` and `_options` parameters are part of the Changesets API
95
112
  * contract but are not used in the table format. They are retained for
@@ -98,19 +115,21 @@ function inferDependencyType(dep) {
98
115
  * @param _changesets - Changesets that caused the dependency updates (unused in table format)
99
116
  * @param dependenciesUpdated - The list of dependencies that were updated, including old/new versions
100
117
  * @param _options - Validated configuration options (unused in table format)
101
- * @returns An `Effect` that resolves to a `### Dependencies` heading followed by a formatted markdown table string, or empty string if no dependencies were updated
118
+ * @returns An `Effect` that resolves to a `### Dependencies` heading followed by a formatted markdown table string, or empty string if no dependencies with both version endpoints were updated
102
119
  */
103
120
  function getDependencyReleaseLine(_changesets, dependenciesUpdated, _options) {
104
121
  return Effect.gen(function* () {
105
122
  if (dependenciesUpdated.length === 0) return "";
106
123
  yield* GitHubService;
107
- return `### Dependencies\n\n${serializeDependencyTableToMarkdown(dependenciesUpdated.map((dep) => ({
124
+ const rows = dependenciesUpdated.filter(isVersioned).map((dep) => ({
108
125
  dependency: dep.name,
109
126
  type: inferDependencyType(dep),
110
127
  action: "updated",
111
128
  from: dep.oldVersion,
112
129
  to: dep.newVersion
113
- })))}`;
130
+ }));
131
+ if (rows.length === 0) return "";
132
+ return `### Dependencies\n\n${serializeDependencyTableToMarkdown(rows)}`;
114
133
  });
115
134
  }
116
135
 
@@ -44,6 +44,10 @@ const MaintenanceReasonSchema = Schema.Struct({
44
44
  * will not match here; the release then degrades gracefully to the
45
45
  * `"unspecified"` fallback sentence instead of naming its triggers.
46
46
  *
47
+ * Co-members releasing as `type: "none"` are never triggers — they carry no
48
+ * version bump (and, per `@changesets/types`, no guaranteed `newVersion`), so
49
+ * naming one would print an unchanged version as the cause of the release.
50
+ *
47
51
  * @public
48
52
  */
49
53
  function deriveMaintenanceReason(release, plan, config) {
@@ -51,7 +55,7 @@ function deriveMaintenanceReason(release, plan, config) {
51
55
  const groupKinds = [["fixed", config.fixed], ["linked", config.linked]];
52
56
  for (const [kind, groups] of groupKinds) for (const group of groups) {
53
57
  if (!group.some((pattern) => ChangesetConfig.matches(release.name, pattern))) continue;
54
- const triggers = plan.releases.filter((r) => r.name !== release.name && r.changesets.length > 0 && group.some((pattern) => ChangesetConfig.matches(r.name, pattern))).map((r) => ({
58
+ const triggers = plan.releases.filter((r) => r.name !== release.name && r.type !== "none" && r.changesets.length > 0 && group.some((pattern) => ChangesetConfig.matches(r.name, pattern))).map((r) => ({
55
59
  name: r.name,
56
60
  version: r.newVersion
57
61
  }));
package/index.d.ts CHANGED
@@ -289,7 +289,7 @@ declare class Categories {
289
289
  static isValidHeading(heading: string): boolean;
290
290
  }
291
291
  //#endregion
292
- //#region ../../node_modules/.pnpm/@changesets+types@7.0.0-next.7/node_modules/@changesets/types/dist/index.d.mts
292
+ //#region ../../node_modules/.pnpm/@changesets+types@7.0.0-next.8/node_modules/@changesets/types/dist/index.d.mts
293
293
  //#region src/index.d.ts
294
294
  type MaybePromise<T> = T | Promise<T>;
295
295
  type VersionType$1 = "major" | "minor" | "patch" | "none";
@@ -298,13 +298,34 @@ type Release = {
298
298
  name: string;
299
299
  type: VersionType$1;
300
300
  };
301
- type ComprehensiveRelease = {
301
+ interface ComprehensiveReleaseBase {
302
302
  name: string;
303
- type: VersionType$1;
303
+ type: "major" | "minor" | "patch" | "none";
304
+ changesets: string[];
305
+ oldVersion: string | undefined;
306
+ newVersion: string | undefined;
307
+ }
308
+ interface ComprehensiveMajorRelease extends ComprehensiveReleaseBase {
309
+ type: "major";
304
310
  oldVersion: string;
305
311
  newVersion: string;
306
- changesets: string[];
307
- };
312
+ }
313
+ interface ComprehensiveMinorRelease extends ComprehensiveReleaseBase {
314
+ type: "minor";
315
+ oldVersion: string;
316
+ newVersion: string;
317
+ }
318
+ interface ComprehensivePatchRelease extends ComprehensiveReleaseBase {
319
+ type: "patch";
320
+ oldVersion: string;
321
+ newVersion: string;
322
+ }
323
+ interface ComprehensiveNoneRelease extends ComprehensiveReleaseBase {
324
+ type: "none";
325
+ oldVersion: string | undefined;
326
+ newVersion: string | undefined;
327
+ }
328
+ type ComprehensiveRelease = ComprehensiveMajorRelease | ComprehensiveMinorRelease | ComprehensivePatchRelease | ComprehensiveNoneRelease;
308
329
  type Changeset$1 = {
309
330
  summary: string;
310
331
  releases: Array<Release>;
@@ -2163,6 +2184,10 @@ type MaintenanceReason = typeof MaintenanceReasonSchema.Type;
2163
2184
  * will not match here; the release then degrades gracefully to the
2164
2185
  * `"unspecified"` fallback sentence instead of naming its triggers.
2165
2186
  *
2187
+ * Co-members releasing as `type: "none"` are never triggers — they carry no
2188
+ * version bump (and, per `@changesets/types`, no guaranteed `newVersion`), so
2189
+ * naming one would print an unchanged version as the cause of the release.
2190
+ *
2166
2191
  * @public
2167
2192
  */
2168
2193
  declare function deriveMaintenanceReason(release: ComprehensiveRelease, plan: ReleasePlan, config: Config): MaintenanceReason | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@savvy-web/silk-effects",
3
- "version": "5.1.0",
3
+ "version": "5.1.3",
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",
@@ -29,18 +29,18 @@
29
29
  "./package.json": "./package.json"
30
30
  },
31
31
  "dependencies": {
32
- "@changesets/apply-release-plan": "^8.0.0-next.7",
32
+ "@changesets/apply-release-plan": "^8.0.0-next.9",
33
33
  "@changesets/config": "^4.0.0-next.6",
34
- "@changesets/get-github-info": "^1.0.0-next.3",
35
- "@changesets/get-release-plan": "^5.0.0-next.7",
36
- "@effected/commands": "^0.1.0",
34
+ "@changesets/get-github-info": "^1.0.0-next.4",
35
+ "@changesets/get-release-plan": "^5.0.0-next.9",
36
+ "@effected/commands": "^0.2.0",
37
37
  "@effected/git": "^0.5.1",
38
38
  "@effected/glob": "^0.2.1",
39
39
  "@effected/jsonc": "^0.5.1",
40
- "@effected/package-json": "^0.6.0",
40
+ "@effected/package-json": "^0.6.1",
41
41
  "@effected/templates": "^0.1.0",
42
42
  "@effected/walker": "^0.3.3",
43
- "@effected/workspaces": "^0.9.0",
43
+ "@effected/workspaces": "^0.9.1",
44
44
  "@effected/yaml": "^0.6.0",
45
45
  "@manypkg/get-packages": "^3.1.0",
46
46
  "mdast-util-heading-range": "^4.0.0",