@savvy-web/silk-effects 2.1.0 → 3.0.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.
@@ -106,7 +106,7 @@ var Changelog = class {
106
106
  * Pass `null` to use defaults (no GitHub link resolution).
107
107
  * @returns A promise resolving to the formatted markdown string
108
108
  */
109
- static formatReleaseLine(changeset, versionType, options) {
109
+ static async formatReleaseLine(changeset, versionType, options) {
110
110
  return changelogFunctions.getReleaseLine(changeset, versionType, options);
111
111
  }
112
112
  /**
@@ -125,7 +125,7 @@ var Changelog = class {
125
125
  * @returns A promise resolving to the formatted markdown string containing
126
126
  * the dependency update table
127
127
  */
128
- static formatDependencyReleaseLine(changesets, dependenciesUpdated, options) {
128
+ static async formatDependencyReleaseLine(changesets, dependenciesUpdated, options) {
129
129
  return changelogFunctions.getDependencyReleaseLine(changesets, dependenciesUpdated, options);
130
130
  }
131
131
  };
@@ -6,9 +6,9 @@ import { VersionFiles } from "../utils/version-files.js";
6
6
  import { Context, Effect, Layer } from "effect";
7
7
  import { dirname, isAbsolute, join, relative } from "node:path";
8
8
  import { FileSystem } from "@effect/platform";
9
- import applyReleasePlan from "@changesets/apply-release-plan";
10
- import { read } from "@changesets/config";
11
- import getReleasePlan from "@changesets/get-release-plan";
9
+ import { applyReleasePlan } from "@changesets/apply-release-plan";
10
+ import { readConfig } from "@changesets/config";
11
+ import { getReleasePlan } from "@changesets/get-release-plan";
12
12
  import { getPackages } from "@manypkg/get-packages";
13
13
 
14
14
  //#region src/changesets/services/release-planner.ts
@@ -24,37 +24,21 @@ import { getPackages } from "@manypkg/get-packages";
24
24
  * (e.g. `getChangelogEntry`) is re-implemented.
25
25
  *
26
26
  */
27
- const V1_TOOLS = /* @__PURE__ */ new Set([
28
- "yarn",
29
- "bolt",
30
- "pnpm",
31
- "lerna",
32
- "root"
33
- ]);
27
+ const errMsg = (e) => e instanceof Error ? e.message : String(e);
34
28
  /**
35
- * Single workspace-discovery seam; swap to an Effect-native stack later here.
36
- *
37
- * Discovers with `@manypkg/get-packages@3.x` and adapts to the v1 shape:
38
- * `tool` collapses to its type string (tools unknown to v1 map to `"root"` —
39
- * the engine never reads `tool` at runtime, only `root.dir`), and
40
- * `rootDir`/`rootPackage` fold back into `root`.
29
+ * Read the changesets config, surfacing non-throwing `readConfig` errors as a
30
+ * thrown `Error` so callers inside `Effect.tryPromise` land on the existing
31
+ * `ReleasePlanError` mapping. Warnings are returned alongside the config so
32
+ * the caller can log them via the Effect runtime rather than console output.
41
33
  */
42
- const buildPackages = async (root) => {
43
- const { tool, rootDir, rootPackage, packages } = await getPackages(root);
44
- if (!rootPackage) throw new Error(`Workspace root has no package.json: ${rootDir}`);
34
+ async function loadConfig(root, packages) {
35
+ const configResult = await readConfig(root, packages);
36
+ if (configResult.config === void 0) throw new Error(`Invalid changeset config:\n${configResult.errors.join("\n")}`);
45
37
  return {
46
- tool: V1_TOOLS.has(tool.type) ? tool.type : "root",
47
- root: {
48
- dir: rootPackage.dir,
49
- packageJson: rootPackage.packageJson
50
- },
51
- packages: packages.map((p) => ({
52
- dir: p.dir,
53
- packageJson: p.packageJson
54
- }))
38
+ config: configResult.config,
39
+ warnings: configResult.warnings
55
40
  };
56
- };
57
- const errMsg = (e) => e instanceof Error ? e.message : String(e);
41
+ }
58
42
  const _tag = Context.Tag("ReleasePlanner");
59
43
  /**
60
44
  * Base class for {@link ReleasePlanner}.
@@ -75,7 +59,7 @@ function makeShape(inspector, fs) {
75
59
  })
76
60
  });
77
61
  const preview = (root) => previewEffect(root, fs);
78
- const apply = (root, options) => applyEffect(root, options?.dryRun ?? false, inspector, fs);
62
+ const apply = (root, options) => applyEffect(root, options?.dryRun ?? false, options?.changelogModules, inspector, fs);
79
63
  return {
80
64
  plan,
81
65
  preview,
@@ -133,19 +117,25 @@ function maintenanceReasons(plan, config) {
133
117
  function previewEffect(root, fs) {
134
118
  const program = Effect.gen(function* () {
135
119
  const [plan, packages] = yield* Effect.tryPromise({
136
- try: () => Promise.all([getReleasePlan(root), buildPackages(root)]),
120
+ try: () => Promise.all([getReleasePlan(root), getPackages(root)]),
137
121
  catch: (e) => new ReleasePlanError({
138
122
  phase: "preview",
139
123
  reason: errMsg(e)
140
124
  })
141
125
  });
142
- const config = yield* Effect.tryPromise({
143
- try: () => read(root, packages),
126
+ if (!packages.rootPackage) return yield* Effect.fail(new ReleasePlanError({
127
+ phase: "preview",
128
+ reason: `Workspace root has no package.json: ${root}`
129
+ }));
130
+ const rootPackage = packages.rootPackage;
131
+ const { config, warnings } = yield* Effect.tryPromise({
132
+ try: () => loadConfig(root, packages),
144
133
  catch: (e) => new ReleasePlanError({
145
134
  phase: "preview",
146
135
  reason: errMsg(e)
147
136
  })
148
137
  });
138
+ yield* Effect.forEach(warnings, (w) => Effect.logWarning(w));
149
139
  const reasonByName = maintenanceReasons(plan, config);
150
140
  const preMode = plan.preState ? plan.preState.mode : null;
151
141
  const changesets = plan.changesets.map((cs) => ({
@@ -164,7 +154,7 @@ function previewEffect(root, fs) {
164
154
  };
165
155
  const tempRoot = yield* fs.makeTempDirectoryScoped({ prefix: "silk-preview-" });
166
156
  const mapDir = (dir) => {
167
- const rel = relative(packages.root.dir, dir);
157
+ const rel = relative(packages.rootDir, dir);
168
158
  if (rel.startsWith("..") || isAbsolute(rel)) return Effect.fail(new ReleasePlanError({
169
159
  phase: "preview",
170
160
  reason: `Package directory is outside the workspace root: ${dir}`
@@ -174,13 +164,12 @@ function previewEffect(root, fs) {
174
164
  const tempDirs = yield* Effect.forEach(packages.packages, (p) => mapDir(p.dir));
175
165
  const tempPackages = {
176
166
  tool: packages.tool,
177
- root: {
178
- ...packages.root,
167
+ rootDir: tempRoot,
168
+ rootPackage: {
179
169
  dir: tempRoot,
180
- packageJson: structuredClone(packages.root.packageJson)
170
+ packageJson: structuredClone(rootPackage.packageJson)
181
171
  },
182
172
  packages: packages.packages.map((p, i) => ({
183
- ...p,
184
173
  dir: tempDirs[i],
185
174
  packageJson: structuredClone(p.packageJson)
186
175
  }))
@@ -197,7 +186,7 @@ function previewEffect(root, fs) {
197
186
  const realCl = join(p.dir, "CHANGELOG.md");
198
187
  if (yield* fs.exists(realCl)) yield* fs.copyFile(realCl, join(tDir, "CHANGELOG.md"));
199
188
  }
200
- const rootCl = join(packages.root.dir, "CHANGELOG.md");
189
+ const rootCl = join(packages.rootDir, "CHANGELOG.md");
201
190
  if (yield* fs.exists(rootCl)) yield* fs.copyFile(rootCl, join(tempRoot, "CHANGELOG.md"));
202
191
  yield* Effect.tryPromise({
203
192
  try: () => applyReleasePlan(plan, tempPackages, config, void 0, root),
@@ -208,7 +197,7 @@ function previewEffect(root, fs) {
208
197
  });
209
198
  const dirByName = /* @__PURE__ */ new Map();
210
199
  for (const p of tempPackages.packages) dirByName.set(p.packageJson.name, p.dir);
211
- if (tempPackages.root.packageJson.name) dirByName.set(tempPackages.root.packageJson.name, tempRoot);
200
+ if (tempPackages.rootPackage?.packageJson.name) dirByName.set(tempPackages.rootPackage.packageJson.name, tempRoot);
212
201
  const releases = [];
213
202
  for (const r of releasesToRender) {
214
203
  const dir = dirByName.get(r.name);
@@ -251,15 +240,17 @@ function previewEffect(root, fs) {
251
240
  function diskVersion(workspaceDir, fallback, fs) {
252
241
  return fs.readFileString(join(workspaceDir, "package.json")).pipe(Effect.flatMap((raw) => Effect.try(() => JSON.parse(raw).version ?? fallback)), Effect.orElseSucceed(() => fallback));
253
242
  }
254
- function applyEffect(root, dryRun, inspector, fs) {
243
+ function applyEffect(root, dryRun, changelogModules, inspector, fs) {
255
244
  return Effect.gen(function* () {
256
- const { plan, packages, config } = yield* Effect.tryPromise({
245
+ const { plan, packages, config, warnings } = yield* Effect.tryPromise({
257
246
  try: async () => {
258
- const [plan, packages] = await Promise.all([getReleasePlan(root), buildPackages(root)]);
247
+ const [plan, packages] = await Promise.all([getReleasePlan(root), getPackages(root)]);
248
+ const { config, warnings } = await loadConfig(root, packages);
259
249
  return {
260
250
  plan,
261
251
  packages,
262
- config: await read(root, packages)
252
+ config,
253
+ warnings
263
254
  };
264
255
  },
265
256
  catch: (e) => new ReleasePlanError({
@@ -267,6 +258,29 @@ function applyEffect(root, dryRun, inspector, fs) {
267
258
  reason: errMsg(e)
268
259
  })
269
260
  });
261
+ yield* Effect.forEach(warnings, (w) => Effect.logWarning(w));
262
+ let engineConfig = config;
263
+ if (changelogModules) {
264
+ engineConfig = {
265
+ ...config,
266
+ format: false
267
+ };
268
+ if (Array.isArray(config.changelog)) {
269
+ const configuredId = config.changelog[0];
270
+ const mapped = changelogModules[configuredId];
271
+ if (mapped === void 0) {
272
+ const supported = Object.keys(changelogModules).join(", ");
273
+ return yield* Effect.fail(new ReleasePlanError({
274
+ phase: "apply",
275
+ reason: `changelog id "${configuredId}" is not in changelogModules (supported: ${supported})`
276
+ }));
277
+ }
278
+ engineConfig = {
279
+ ...engineConfig,
280
+ changelog: [mapped, config.changelog[1]]
281
+ };
282
+ }
283
+ }
270
284
  const releases = plan.releases.filter((r) => r.type !== "none").map((r) => ({
271
285
  name: r.name,
272
286
  type: r.type,
@@ -279,10 +293,10 @@ function applyEffect(root, dryRun, inspector, fs) {
279
293
  const versionByPkgName = new Map(plan.releases.map((r) => [r.name, r.newVersion]));
280
294
  const nameByDir = /* @__PURE__ */ new Map();
281
295
  for (const p of packages.packages) nameByDir.set(p.dir, p.packageJson.name);
282
- if (packages.root.packageJson.name) nameByDir.set(packages.root.dir, packages.root.packageJson.name);
296
+ if (packages.rootPackage?.packageJson.name) nameByDir.set(packages.rootDir, packages.rootPackage.packageJson.name);
283
297
  touchedFiles = yield* Effect.tryPromise({
284
298
  try: async () => {
285
- const touched = await applyReleasePlan(plan, packages, config);
299
+ const touched = await applyReleasePlan(plan, packages, engineConfig);
286
300
  for (const f of touched) {
287
301
  if (!f.endsWith("CHANGELOG.md")) continue;
288
302
  const pkgName = nameByDir.get(dirname(f));
@@ -1,6 +1,6 @@
1
1
  import { GitHubApiError } from "../errors.js";
2
2
  import { Effect } from "effect";
3
- import { getInfo } from "@changesets/get-github-info";
3
+ import { getCommitInfo } from "@changesets/get-github-info";
4
4
 
5
5
  //#region src/changesets/vendor/github-info.ts
6
6
  /**
@@ -9,8 +9,14 @@ import { getInfo } from "@changesets/get-github-info";
9
9
  * @remarks
10
10
  * Bridges the `\@changesets/get-github-info` package (which returns
11
11
  * promises) into the Effect ecosystem. The {@link getGitHubInfo}
12
- * function wraps the upstream `getInfo()` call in `Effect.tryPromise`,
13
- * mapping failures to {@link GitHubApiError}.
12
+ * function wraps the upstream `getCommitInfo()` call in `Effect.tryPromise`,
13
+ * adapting its structured `CommitInfo | undefined` return back to the
14
+ * legacy {@link GitHubCommitInfo} shape and mapping failures (including a
15
+ * `not found` result) to {@link GitHubApiError}.
16
+ *
17
+ * The upstream v1 package added a `.env` fallback: it reads
18
+ * `GITHUB_TOKEN` from `process.env` directly when no token is otherwise
19
+ * configured, so the caller does not need to plumb one through.
14
20
  *
15
21
  * The {@link GitHubCommitInfo} type is the only item from this module
16
22
  * that is part of the public API (re-exported from the package root).
@@ -24,9 +30,13 @@ import { getInfo } from "@changesets/get-github-info";
24
30
  * Fetch GitHub info for a commit, wrapped in Effect.
25
31
  *
26
32
  * @remarks
27
- * Calls the upstream `getInfo()` from `\@changesets/get-github-info`
28
- * within `Effect.tryPromise`. Any thrown error is caught and mapped
29
- * to a {@link GitHubApiError} with the operation set to `"getInfo"`.
33
+ * Calls the upstream `getCommitInfo()` from `\@changesets/get-github-info`
34
+ * within `Effect.tryPromise`, adapting its structured `CommitInfo`
35
+ * return to the legacy {@link GitHubCommitInfo} shape. An `undefined`
36
+ * result (commit or repo not found) is treated as a thrown error so it
37
+ * is mapped to the same {@link GitHubApiError} failure channel. Any
38
+ * thrown error is caught and mapped to a {@link GitHubApiError} with
39
+ * the operation set to `"getCommitInfo"`.
30
40
  *
31
41
  * Requires a `GITHUB_TOKEN` environment variable to be set for
32
42
  * authenticated API access (the upstream library reads it directly).
@@ -39,13 +49,25 @@ import { getInfo } from "@changesets/get-github-info";
39
49
  */
40
50
  function getGitHubInfo(params) {
41
51
  return Effect.tryPromise({
42
- try: () => getInfo({
43
- commit: params.commit,
44
- repo: params.repo
45
- }),
52
+ try: async () => {
53
+ const info = await getCommitInfo({
54
+ commit: params.commit,
55
+ repo: params.repo
56
+ });
57
+ if (info === void 0) throw new Error(`commit ${params.commit} not found in ${params.repo}`);
58
+ return {
59
+ user: info.author?.login ?? null,
60
+ pull: info.pull?.number ?? null,
61
+ links: {
62
+ commit: info.commit.markdownLink,
63
+ pull: info.pull?.markdownLink ?? null,
64
+ user: info.author?.markdownLink ?? null
65
+ }
66
+ };
67
+ },
46
68
  /* v8 ignore next 5 -- error mapping tested via GitHubService test layer */
47
69
  catch: (error) => new GitHubApiError({
48
- operation: "getInfo",
70
+ operation: "getCommitInfo",
49
71
  reason: error instanceof Error ? error.message : String(error)
50
72
  })
51
73
  });
package/index.d.ts CHANGED
@@ -284,7 +284,9 @@ declare class Categories {
284
284
  static isValidHeading(heading: string): boolean;
285
285
  }
286
286
  //#endregion
287
- //#region ../../node_modules/.pnpm/@changesets+types@6.1.0/node_modules/@changesets/types/dist/declarations/src/index.d.ts
287
+ //#region ../../node_modules/.pnpm/@changesets+types@7.0.0-next.6/node_modules/@changesets/types/dist/index.d.mts
288
+ //#region src/index.d.ts
289
+ type MaybePromise<T> = T | Promise<T>;
288
290
  type VersionType$1 = "major" | "minor" | "patch" | "none";
289
291
  type AccessType = "public" | "restricted";
290
292
  type Release = {
@@ -333,6 +335,7 @@ type PackageJSON = {
333
335
  access?: AccessType;
334
336
  directory?: string;
335
337
  registry?: string;
338
+ [registry: `${string}:registry`]: string;
336
339
  };
337
340
  };
338
341
  type PackageGroup = ReadonlyArray<string>;
@@ -343,19 +346,23 @@ interface PrivatePackages {
343
346
  tag: boolean;
344
347
  }
345
348
  type Config = {
346
- changelog: false | readonly [string, any];
347
- commit: false | readonly [string, any];
349
+ changelog: false | readonly [string, null | Record<string, unknown>];
350
+ commit: false | readonly [string, null | Record<string, unknown>];
348
351
  fixed: Fixed;
349
352
  linked: Linked;
350
353
  access: AccessType;
351
354
  baseBranch: string;
352
- changedFilePatterns: readonly string[]; /** When false, Changesets won't format with Prettier */
353
- prettier: boolean; /** Features enabled for Private packages */
355
+ changedFilePatterns: readonly string[];
356
+ /**
357
+ * The formatter to use to format changesets and changelogs. Set `false` to disable formatting.
358
+ * The default value of `"auto"` will auto-detect the formatter based on the project's configuration files.
359
+ */
360
+ format: "auto" | "prettier" | "oxfmt" | "deno" | "dprint" | false; /** Features enabled for Private packages */
354
361
  privatePackages: PrivatePackages; /** The minimum bump type to trigger automatic update of internal dependencies that are part of the same release */
355
362
  updateInternalDependencies: "patch" | "minor";
356
363
  ignore: ReadonlyArray<string>; /** This is supposed to be used with pnpm's `link-workspace-packages: false` and Berry's `enableTransparentWorkspaces: false` */
357
364
  bumpVersionsWithWorkspaceProtocolOnly?: boolean;
358
- ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: Omit<Required<ExperimentalOptions>, "useCalculatedVersionForSnapshots">;
365
+ ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: Required<ExperimentalOptions>;
359
366
  snapshot: {
360
367
  useCalculatedVersion: boolean;
361
368
  prereleaseTemplate: string | null;
@@ -363,8 +370,7 @@ type Config = {
363
370
  };
364
371
  type ExperimentalOptions = {
365
372
  onlyUpdatePeerDependentsWhenOutOfRange?: boolean;
366
- updateInternalDependents?: "always" | "out-of-range"; /** @deprecated Since snapshot feature is now stable, you should migrate to use "snapshot.useCalculatedVersion". */
367
- useCalculatedVersionForSnapshots?: boolean;
373
+ updateInternalDependents?: "always" | "out-of-range";
368
374
  };
369
375
  type NewChangesetWithCommit = NewChangeset & {
370
376
  commit?: string;
@@ -373,8 +379,8 @@ type ModCompWithPackage = ComprehensiveRelease & {
373
379
  packageJson: PackageJSON;
374
380
  dir: string;
375
381
  };
376
- type GetReleaseLine = (changeset: NewChangesetWithCommit, type: VersionType$1, changelogOpts: null | Record<string, any>) => Promise<string>;
377
- type GetDependencyReleaseLine = (changesets: NewChangesetWithCommit[], dependenciesUpdated: ModCompWithPackage[], changelogOpts: any) => Promise<string>;
382
+ type GetReleaseLine = (changeset: NewChangesetWithCommit, type: VersionType$1, changelogOpts: null | Record<string, unknown>) => MaybePromise<string>;
383
+ type GetDependencyReleaseLine = (changesets: NewChangesetWithCommit[], dependenciesUpdated: ModCompWithPackage[], changelogOpts: null | Record<string, unknown>) => MaybePromise<string>;
378
384
  type ChangelogFunctions = {
379
385
  getReleaseLine: GetReleaseLine;
380
386
  getDependencyReleaseLine: GetDependencyReleaseLine;
@@ -382,9 +388,6 @@ type ChangelogFunctions = {
382
388
  type PreState = {
383
389
  mode: "pre" | "exit";
384
390
  tag: string;
385
- initialVersions: {
386
- [pkgName: string]: string;
387
- };
388
391
  changesets: string[];
389
392
  };
390
393
  //#endregion
@@ -2697,7 +2700,7 @@ declare class ChangesetConfigError extends ChangesetConfigError_base<{
2697
2700
  //#endregion
2698
2701
  //#region src/schemas/VersioningSchemas.d.ts
2699
2702
  /**
2700
- * Standard changesets configuration matching the `@changesets/config@3.1.1` spec.
2703
+ * Standard changesets configuration matching the `@changesets/config@4.0.0-next.6` spec.
2701
2704
  *
2702
2705
  * @remarks
2703
2706
  * Represents the parsed `.changeset/config.json` file. All fields are optional
@@ -3961,6 +3964,13 @@ interface ReleasePlannerShape {
3961
3964
  /** Natively apply the release (destructive unless `dryRun`). */
3962
3965
  readonly apply: (root: string, options?: {
3963
3966
  readonly dryRun?: boolean;
3967
+ /**
3968
+ * Map configured changelog ids to absolute module paths. When set,
3969
+ * `config.changelog[0]` must be a key of this map (rewritten before the
3970
+ * engine call; unmapped ids fail) and the engine's `format` integration
3971
+ * is disabled — callers in no-`node_modules` contexts own formatting.
3972
+ */
3973
+ readonly changelogModules?: Readonly<Record<string, string>>;
3964
3974
  }) => Effect.Effect<AppliedRelease, ReleasePlanError>;
3965
3975
  }
3966
3976
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@savvy-web/silk-effects",
3
- "version": "2.1.0",
3
+ "version": "3.0.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",
@@ -28,10 +28,10 @@
28
28
  "./package.json": "./package.json"
29
29
  },
30
30
  "dependencies": {
31
- "@changesets/apply-release-plan": "^7.1.1",
32
- "@changesets/config": "^3.1.4",
33
- "@changesets/get-github-info": "^0.8.0",
34
- "@changesets/get-release-plan": "^4.0.16",
31
+ "@changesets/apply-release-plan": "^8.0.0-next.7",
32
+ "@changesets/config": "^4.0.0-next.6",
33
+ "@changesets/get-github-info": "^1.0.0-next.3",
34
+ "@changesets/get-release-plan": "^5.0.0-next.7",
35
35
  "@manypkg/get-packages": "^3.1.0",
36
36
  "jsonc-effect": "^0.3.0",
37
37
  "mdast-util-heading-range": "^4.0.0",
@@ -30,7 +30,7 @@ const SnapshotConfig = Schema.Struct({
30
30
  prereleaseTemplate: Schema.optional(Schema.String)
31
31
  });
32
32
  /**
33
- * Standard changesets configuration matching the `@changesets/config@3.1.1` spec.
33
+ * Standard changesets configuration matching the `@changesets/config@4.0.0-next.6` spec.
34
34
  *
35
35
  * @remarks
36
36
  * Represents the parsed `.changeset/config.json` file. All fields are optional