@savvy-web/silk-effects 5.0.1 → 5.1.0

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.
@@ -49,7 +49,7 @@ function makeShape(inspector, fs) {
49
49
  reason: errMsg(e)
50
50
  })
51
51
  });
52
- const preview = (root) => previewEffect(root, fs);
52
+ const preview = (root, options) => previewEffect(root, options?.changelogModules, fs);
53
53
  const apply = (root, options) => applyEffect(root, options?.dryRun ?? false, options?.changelogModules, inspector, fs);
54
54
  return {
55
55
  plan,
@@ -78,6 +78,44 @@ function makeReleasePlannerTest(fixed) {
78
78
  apply: () => fixed.apply ? Effect.succeed(fixed.apply) : fail("apply")
79
79
  });
80
80
  }
81
+ /**
82
+ * Rewrite the engine config for a caller that supplies its own changelog module
83
+ * paths: `config.changelog[0]` becomes the mapped absolute path and the engine's
84
+ * `format` integration is switched off.
85
+ *
86
+ * @remarks
87
+ * Shared by `preview` and `apply` — both hand the result to
88
+ * `applyReleasePlan`, and both are called from no-`node_modules` contexts where
89
+ * neither the configured id nor a formatter can be resolved. An unmapped id
90
+ * fails typed (naming the supported keys) rather than reaching
91
+ * `import-meta-resolve`, which reports it as `expected to be defined`.
92
+ *
93
+ * Exported for tests only — not re-exported from the package index, so it is
94
+ * not public API. Driving it directly is the only deterministic way to assert
95
+ * the `format: false` half: every formatter the changesets config accepts
96
+ * either shells out through `npx` (network) or to an ambient binary, so a
97
+ * fixture that names one passes or fails on what the machine happens to have
98
+ * installed rather than on this rewrite.
99
+ */
100
+ function withChangelogModules(config, changelogModules, phase) {
101
+ const unformatted = {
102
+ ...config,
103
+ format: false
104
+ };
105
+ if (!Array.isArray(config.changelog)) return Effect.succeed(unformatted);
106
+ const configuredId = config.changelog[0];
107
+ if (!Object.hasOwn(changelogModules, configuredId)) {
108
+ const supported = Object.keys(changelogModules).join(", ");
109
+ return Effect.fail(new ReleasePlanError({
110
+ phase,
111
+ reason: `changelog id "${configuredId}" is not in changelogModules (supported: ${supported})`
112
+ }));
113
+ }
114
+ return Effect.succeed({
115
+ ...unformatted,
116
+ changelog: [changelogModules[configuredId], config.changelog[1]]
117
+ });
118
+ }
81
119
  /** Extract the `## <version>` block (down to the next H2 or EOF) from a changelog. */
82
120
  function extractVersionBlock(changelog, version) {
83
121
  const lines = changelog.split("\n");
@@ -105,7 +143,7 @@ function maintenanceReasons(plan, config) {
105
143
  * scope-managed temp directory (cleaned up automatically when the scope
106
144
  * closes) and reading the generated CHANGELOG blocks back.
107
145
  */
108
- function previewEffect(root, fs) {
146
+ function previewEffect(root, changelogModules, fs) {
109
147
  const program = Effect.gen(function* () {
110
148
  const [plan, packages] = yield* Effect.tryPromise({
111
149
  try: () => Promise.all([getReleasePlan(root), getPackages(root)]),
@@ -128,6 +166,7 @@ function previewEffect(root, fs) {
128
166
  });
129
167
  yield* Effect.forEach(warnings, (w) => Effect.logWarning(w));
130
168
  const reasonByName = maintenanceReasons(plan, config);
169
+ const engineConfig = changelogModules ? yield* withChangelogModules(config, changelogModules, "preview") : config;
131
170
  const preMode = plan.preState ? plan.preState.mode : null;
132
171
  const changesets = plan.changesets.map((cs) => ({
133
172
  id: cs.id,
@@ -180,7 +219,7 @@ function previewEffect(root, fs) {
180
219
  const rootCl = join(packages.rootDir, "CHANGELOG.md");
181
220
  if (yield* fs.exists(rootCl)) yield* fs.copyFile(rootCl, join(tempRoot, "CHANGELOG.md"));
182
221
  yield* Effect.tryPromise({
183
- try: () => applyReleasePlan(plan, tempPackages, config, void 0, root),
222
+ try: () => applyReleasePlan(plan, tempPackages, engineConfig, void 0, root),
184
223
  catch: (e) => new ReleasePlanError({
185
224
  phase: "preview",
186
225
  reason: errMsg(e)
@@ -250,28 +289,7 @@ function applyEffect(root, dryRun, changelogModules, inspector, fs) {
250
289
  })
251
290
  });
252
291
  yield* Effect.forEach(warnings, (w) => Effect.logWarning(w));
253
- let engineConfig = config;
254
- if (changelogModules) {
255
- engineConfig = {
256
- ...config,
257
- format: false
258
- };
259
- if (Array.isArray(config.changelog)) {
260
- const configuredId = config.changelog[0];
261
- const mapped = changelogModules[configuredId];
262
- if (mapped === void 0) {
263
- const supported = Object.keys(changelogModules).join(", ");
264
- return yield* Effect.fail(new ReleasePlanError({
265
- phase: "apply",
266
- reason: `changelog id "${configuredId}" is not in changelogModules (supported: ${supported})`
267
- }));
268
- }
269
- engineConfig = {
270
- ...engineConfig,
271
- changelog: [mapped, config.changelog[1]]
272
- };
273
- }
274
- }
292
+ const engineConfig = changelogModules ? yield* withChangelogModules(config, changelogModules, "apply") : config;
275
293
  const releases = plan.releases.filter((r) => r.type !== "none").map((r) => ({
276
294
  name: r.name,
277
295
  type: r.type,
@@ -337,4 +355,4 @@ function applyEffect(root, dryRun, changelogModules, inspector, fs) {
337
355
  }
338
356
 
339
357
  //#endregion
340
- export { ReleasePlanner, ReleasePlannerLive, extractVersionBlock, makeReleasePlannerTest };
358
+ export { ReleasePlanner, ReleasePlannerLive, extractVersionBlock, makeReleasePlannerTest, withChangelogModules };
package/index.d.ts CHANGED
@@ -3899,8 +3899,22 @@ type AppliedRelease = Schema.Schema.Type<typeof AppliedReleaseSchema>;
3899
3899
  interface ReleasePlannerShape {
3900
3900
  /** Compute the in-memory release plan (read-only). */
3901
3901
  readonly plan: (root: string) => Effect.Effect<ReleasePlan, ReleasePlanError>;
3902
- /** Render a non-destructive preview of the next release. */
3903
- readonly preview: (root: string) => Effect.Effect<ChangesetPreview, ReleasePlanError>;
3902
+ /**
3903
+ * Render a non-destructive preview of the next release.
3904
+ *
3905
+ * @remarks
3906
+ * Rendering `changelogEntry` means resolving the configured changelog module,
3907
+ * so `changelogModules` matters here for the same reason it does on `apply`.
3908
+ */
3909
+ readonly preview: (root: string, options?: {
3910
+ /**
3911
+ * Map configured changelog ids to absolute module paths. When set,
3912
+ * `config.changelog[0]` must be a key of this map (rewritten before the
3913
+ * engine call; unmapped ids fail) and the engine's `format` integration
3914
+ * is disabled — callers in no-`node_modules` contexts own formatting.
3915
+ */
3916
+ readonly changelogModules?: Readonly<Record<string, string>>;
3917
+ }) => Effect.Effect<ChangesetPreview, ReleasePlanError>;
3904
3918
  /** Natively apply the release (destructive unless `dryRun`). */
3905
3919
  readonly apply: (root: string, options?: {
3906
3920
  readonly dryRun?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@savvy-web/silk-effects",
3
- "version": "5.0.1",
3
+ "version": "5.1.0",
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",