@savvy-web/silk-effects 0.6.0 → 1.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.
Files changed (155) hide show
  1. package/README.md +48 -17
  2. package/_virtual/_rolldown/runtime.js +18 -0
  3. package/changesets/api/categories.js +247 -0
  4. package/changesets/api/changelog.js +134 -0
  5. package/changesets/api/dependency-table.js +163 -0
  6. package/changesets/api/linter.js +168 -0
  7. package/changesets/api/transformer.js +140 -0
  8. package/changesets/categories/index.js +299 -0
  9. package/changesets/categories/types.js +66 -0
  10. package/changesets/changelog/formatting.js +119 -0
  11. package/changesets/changelog/getDependencyReleaseLine.js +114 -0
  12. package/changesets/changelog/getReleaseLine.js +122 -0
  13. package/changesets/changelog/index.js +99 -0
  14. package/changesets/constants.js +43 -0
  15. package/changesets/errors.js +305 -0
  16. package/changesets/index.js +146 -0
  17. package/changesets/markdownlint/index.js +29 -0
  18. package/changesets/markdownlint/rules/content-structure.js +98 -0
  19. package/changesets/markdownlint/rules/dependency-table-format.js +170 -0
  20. package/changesets/markdownlint/rules/heading-hierarchy.js +61 -0
  21. package/changesets/markdownlint/rules/required-sections.js +54 -0
  22. package/changesets/markdownlint/rules/uncategorized-content.js +54 -0
  23. package/changesets/markdownlint/rules/utils.js +30 -0
  24. package/changesets/remark/plugins/aggregate-dependency-tables.js +47 -0
  25. package/changesets/remark/plugins/contributor-footnotes.js +123 -0
  26. package/changesets/remark/plugins/deduplicate-items.js +30 -0
  27. package/changesets/remark/plugins/issue-link-refs.js +58 -0
  28. package/changesets/remark/plugins/merge-sections.js +43 -0
  29. package/changesets/remark/plugins/normalize-format.js +47 -0
  30. package/changesets/remark/plugins/reorder-sections.js +34 -0
  31. package/changesets/remark/presets.js +119 -0
  32. package/changesets/remark/rules/content-structure.js +22 -0
  33. package/changesets/remark/rules/dependency-table-format.js +40 -0
  34. package/changesets/remark/rules/heading-hierarchy.js +19 -0
  35. package/changesets/remark/rules/required-sections.js +17 -0
  36. package/changesets/remark/rules/uncategorized-content.js +31 -0
  37. package/changesets/schemas/changeset.js +146 -0
  38. package/changesets/schemas/dependency-table.js +189 -0
  39. package/changesets/schemas/git.js +69 -0
  40. package/changesets/schemas/github.js +175 -0
  41. package/changesets/schemas/options.js +182 -0
  42. package/changesets/schemas/package-scope.js +128 -0
  43. package/changesets/schemas/primitives.js +72 -0
  44. package/changesets/schemas/version-files.js +151 -0
  45. package/changesets/services/branch-analyzer.js +278 -0
  46. package/changesets/services/changelog.js +50 -0
  47. package/changesets/services/config-inspector.js +390 -0
  48. package/changesets/services/github.js +178 -0
  49. package/changesets/services/markdown.js +106 -0
  50. package/changesets/services/workspace-snapshot.js +182 -0
  51. package/changesets/utils/commit-parser.js +80 -0
  52. package/changesets/utils/dep-diff.js +77 -0
  53. package/changesets/utils/dependency-table.js +347 -0
  54. package/changesets/utils/issue-refs.js +101 -0
  55. package/changesets/utils/jsonpath.js +175 -0
  56. package/changesets/utils/logger.js +50 -0
  57. package/changesets/utils/markdown-link.js +57 -0
  58. package/changesets/utils/publishability.js +39 -0
  59. package/changesets/utils/remark-pipeline.js +79 -0
  60. package/changesets/utils/section-parser.js +94 -0
  61. package/changesets/utils/strip-frontmatter.js +46 -0
  62. package/changesets/utils/version-blocks.js +108 -0
  63. package/changesets/utils/version-files.js +336 -0
  64. package/changesets/utils/worktree-snapshot.js +142 -0
  65. package/changesets/vendor/github-info.js +55 -0
  66. package/commitlint/config/factory.js +69 -0
  67. package/commitlint/config/plugins.js +227 -0
  68. package/commitlint/config/rules.js +155 -0
  69. package/commitlint/config/schema.js +46 -0
  70. package/commitlint/detection/dco.js +53 -0
  71. package/commitlint/detection/scopes.js +45 -0
  72. package/commitlint/formatter/format.js +85 -0
  73. package/commitlint/formatter/messages.js +79 -0
  74. package/commitlint/hook/diagnostics/branch.js +36 -0
  75. package/commitlint/hook/diagnostics/cache.js +37 -0
  76. package/commitlint/hook/diagnostics/commitlint-config.js +36 -0
  77. package/commitlint/hook/diagnostics/open-issues.js +56 -0
  78. package/commitlint/hook/diagnostics/package-manager.js +51 -0
  79. package/commitlint/hook/diagnostics/signing.js +107 -0
  80. package/commitlint/hook/envelope.js +46 -0
  81. package/commitlint/hook/output.js +45 -0
  82. package/commitlint/hook/parse-bash-command.js +105 -0
  83. package/commitlint/hook/rules/closes-trailer.js +31 -0
  84. package/commitlint/hook/rules/forbidden-content.js +32 -0
  85. package/commitlint/hook/rules/plan-leakage.js +36 -0
  86. package/commitlint/hook/rules/signing-flag-conflict.js +25 -0
  87. package/commitlint/hook/rules/soft-wrap.js +37 -0
  88. package/commitlint/hook/rules/types.js +14 -0
  89. package/commitlint/hook/rules/verbosity.js +31 -0
  90. package/commitlint/hook/silence-logger.js +39 -0
  91. package/commitlint/index.js +146 -0
  92. package/commitlint/prompt/config.js +91 -0
  93. package/commitlint/prompt/emojis.js +74 -0
  94. package/commitlint/prompt/prompter.js +135 -0
  95. package/commitlint/static.js +73 -0
  96. package/errors/BiomeSyncError.js +21 -0
  97. package/errors/ChangesetConfigError.js +20 -0
  98. package/errors/ConfigNotFoundError.js +21 -0
  99. package/errors/SectionParseError.js +16 -0
  100. package/errors/SectionValidationError.js +16 -0
  101. package/errors/SectionWriteError.js +16 -0
  102. package/errors/TagFormatError.js +20 -0
  103. package/errors/ToolNotFoundError.js +11 -0
  104. package/errors/ToolResolutionError.js +11 -0
  105. package/errors/ToolVersionMismatchError.js +11 -0
  106. package/errors/VersioningDetectionError.js +20 -0
  107. package/errors/WorkspaceAnalysisError.js +21 -0
  108. package/index.d.ts +9743 -8380
  109. package/index.js +36 -6657
  110. package/lint/Handler.js +39 -0
  111. package/lint/cli/sections.js +65 -0
  112. package/lint/cli/templates/markdownlint.gen.js +183 -0
  113. package/lint/config/Preset.js +152 -0
  114. package/lint/config/createConfig.js +89 -0
  115. package/lint/handlers/Biome.js +179 -0
  116. package/lint/handlers/Markdown.js +139 -0
  117. package/lint/handlers/PackageJson.js +130 -0
  118. package/lint/handlers/PnpmWorkspace.js +141 -0
  119. package/lint/handlers/ShellScripts.js +58 -0
  120. package/lint/handlers/TypeScript.js +134 -0
  121. package/lint/handlers/Yaml.js +167 -0
  122. package/lint/index.js +52 -0
  123. package/lint/utils/Command.js +285 -0
  124. package/lint/utils/Filter.js +100 -0
  125. package/lint/utils/Workspace.js +86 -0
  126. package/package.json +52 -63
  127. package/schemas/CommentStyle.js +16 -0
  128. package/schemas/ResolvedTool.js +63 -0
  129. package/schemas/SavvySections.js +113 -0
  130. package/schemas/SectionBlock.js +70 -0
  131. package/schemas/SectionDefinition.js +121 -0
  132. package/schemas/SectionResults.js +12 -0
  133. package/schemas/TagStrategySchemas.js +18 -0
  134. package/schemas/ToolDefinition.js +39 -0
  135. package/schemas/ToolResults.js +14 -0
  136. package/schemas/VersioningSchemas.js +95 -0
  137. package/schemas/WorkspaceAnalysisSchemas.js +190 -0
  138. package/services/BiomeSchemaSync.js +133 -0
  139. package/services/ChangesetConfig.js +78 -0
  140. package/services/ChangesetConfigReader.js +106 -0
  141. package/services/ConfigDiscovery.js +71 -0
  142. package/services/ManagedSection.js +288 -0
  143. package/services/SilkPublishability.js +193 -0
  144. package/services/SilkWorkspaceAnalyzer.js +213 -0
  145. package/services/TagStrategy.js +54 -0
  146. package/services/ToolDiscovery.js +229 -0
  147. package/services/VersioningStrategy.js +67 -0
  148. package/tsdoc-metadata.json +11 -11
  149. package/turbo/digest.js +127 -0
  150. package/turbo/errors.js +48 -0
  151. package/turbo/index.js +32 -0
  152. package/turbo/schemas/DryRun.js +57 -0
  153. package/turbo/schemas/results.js +61 -0
  154. package/turbo/services/TurboInspector.js +100 -0
  155. package/utils/ToolCommand.js +40 -0
@@ -0,0 +1,108 @@
1
+ import { toString } from "mdast-util-to-string";
2
+
3
+ //#region src/changesets/utils/version-blocks.ts
4
+ /**
5
+ * Extract all version blocks from a CHANGELOG AST.
6
+ *
7
+ * @remarks
8
+ * Scans `tree.children` linearly for h2-level headings. Each h2 starts
9
+ * a new version block whose content extends to the next h2 or the end
10
+ * of the document. The `endIndex` of each block is adjusted in a second
11
+ * pass to stop at the next block's `headingIndex`.
12
+ *
13
+ * h1 headings (typically the package name) are ignored and do not
14
+ * create version blocks.
15
+ *
16
+ * @param tree - The MDAST root node of a CHANGELOG document
17
+ * @returns Array of version blocks in document order
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * import { getVersionBlocks } from "../utils/version-blocks.js";
22
+ * import { parseMarkdown } from "../utils/remark-pipeline.js";
23
+ *
24
+ * const tree = parseMarkdown("# my-pkg\n\n## 1.1.0\n\nChanges.\n\n## 1.0.0\n\nInitial.");
25
+ * const blocks = getVersionBlocks(tree);
26
+ * // blocks.length === 2
27
+ * // blocks[0] covers "## 1.1.0" section
28
+ * // blocks[1] covers "## 1.0.0" section
29
+ * ```
30
+ *
31
+ * @internal
32
+ */
33
+ function getVersionBlocks(tree) {
34
+ const blocks = [];
35
+ for (let i = 0; i < tree.children.length; i++) {
36
+ const node = tree.children[i];
37
+ if (node.type === "heading" && node.depth === 2) blocks.push({
38
+ headingIndex: i,
39
+ startIndex: i + 1,
40
+ endIndex: tree.children.length
41
+ });
42
+ }
43
+ for (let i = 0; i < blocks.length - 1; i++) blocks[i].endIndex = blocks[i + 1].headingIndex;
44
+ return blocks;
45
+ }
46
+ /**
47
+ * Extract h3 sections within a version block.
48
+ *
49
+ * @remarks
50
+ * Iterates over the children of the given version block (from
51
+ * `block.startIndex` to `block.endIndex`). Each h3 heading starts
52
+ * a new section, and subsequent non-heading nodes are collected
53
+ * into that section's `contentNodes` array until the next h3 or
54
+ * the end of the block.
55
+ *
56
+ * Nodes before the first h3 within the block (e.g., a version
57
+ * summary paragraph) are not included in any section.
58
+ *
59
+ * @param tree - The MDAST root node of a CHANGELOG document
60
+ * @param block - The version block to extract sections from
61
+ * @returns Array of sections in document order
62
+ *
63
+ * @example
64
+ * ```typescript
65
+ * import { getVersionBlocks, getBlockSections } from "../utils/version-blocks.js";
66
+ * import { parseMarkdown } from "../utils/remark-pipeline.js";
67
+ *
68
+ * const tree = parseMarkdown("## 1.0.0\n\n### Features\n\n- New API\n\n### Bug Fixes\n\n- Fixed crash");
69
+ * const blocks = getVersionBlocks(tree);
70
+ * const sections = getBlockSections(tree, blocks[0]);
71
+ * // sections.length === 2
72
+ * // sections[0].heading text === "Features"
73
+ * ```
74
+ *
75
+ * @internal
76
+ */
77
+ function getBlockSections(tree, block) {
78
+ const sections = [];
79
+ for (let i = block.startIndex; i < block.endIndex; i++) {
80
+ const node = tree.children[i];
81
+ if (node.type === "heading" && node.depth === 3) sections.push({
82
+ heading: node,
83
+ headingIndex: i,
84
+ contentNodes: []
85
+ });
86
+ else if (sections.length > 0) sections[sections.length - 1].contentNodes.push(node);
87
+ }
88
+ return sections;
89
+ }
90
+ /**
91
+ * Get the plain text content of a heading node.
92
+ *
93
+ * @remarks
94
+ * Delegates to `mdast-util-to-string` to extract concatenated text
95
+ * from all inline children of the heading (handling bold, italic,
96
+ * code spans, etc.).
97
+ *
98
+ * @param heading - The MDAST heading node
99
+ * @returns The heading text content as a plain string
100
+ *
101
+ * @internal
102
+ */
103
+ function getHeadingText(heading) {
104
+ return toString(heading);
105
+ }
106
+
107
+ //#endregion
108
+ export { getBlockSections, getHeadingText, getVersionBlocks };
@@ -0,0 +1,336 @@
1
+ import { LegacyVersionFilesSchema } from "../schemas/version-files.js";
2
+ import { jsonPathGet, jsonPathSet } from "./jsonpath.js";
3
+ import { Schema } from "effect";
4
+ import { readFileSync, writeFileSync } from "node:fs";
5
+ import { join, relative, resolve } from "node:path";
6
+ import { globSync } from "tinyglobby";
7
+
8
+ //#region src/changesets/utils/version-files.ts
9
+ /**
10
+ * Utilities for updating version fields in additional JSON files.
11
+ *
12
+ * @remarks
13
+ * Implements the `versionFiles` feature of `\@savvy-web/changesets`, which
14
+ * allows version numbers to be synchronized across arbitrary JSON files
15
+ * (e.g., `tauri.conf.json`, `manifest.json`) during the changeset version
16
+ * step.
17
+ *
18
+ * The flow is:
19
+ * 1. Extract and validate `versionFiles` from a pre-parsed config
20
+ * 2. Resolve glob patterns to absolute file paths
21
+ * 3. Discover workspace packages to determine the correct version per file
22
+ * (using longest-prefix matching)
23
+ * 4. Update JSONPath-specified fields in each matched file
24
+ *
25
+ * @see {@link jsonPathGet} and {@link jsonPathSet} for the JSONPath implementation
26
+ *
27
+ * @internal
28
+ */
29
+ /**
30
+ * Static utility class for version file operations.
31
+ *
32
+ * @remarks
33
+ * Orchestrates the full version file update workflow: reading config,
34
+ * discovering workspace versions, resolving globs, and updating JSON
35
+ * files at specified JSONPath locations. Designed to be called from
36
+ * the CLI `version` command.
37
+ *
38
+ * Version resolution uses longest-prefix matching: for a file at
39
+ * `/repo/packages/ui/tauri.conf.json`, if workspace `packages/ui`
40
+ * has version `2.0.0`, that version is used rather than the root version.
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * import { VersionFiles } from "../utils/version-files.js";
45
+ *
46
+ * const configs = VersionFiles.extractVersionFiles(parsedConfig);
47
+ * if (configs) {
48
+ * const updates = VersionFiles.processVersionFiles("/path/to/project", configs);
49
+ * for (const update of updates) {
50
+ * console.log(`Updated ${update.filePath} to ${update.version}`);
51
+ * }
52
+ * }
53
+ * ```
54
+ *
55
+ * @internal
56
+ */
57
+ var VersionFiles = class VersionFiles {
58
+ /**
59
+ * Extract and validate `versionFiles` from a pre-parsed changeset config object.
60
+ *
61
+ * @remarks
62
+ * Accepts a config object (already parsed from `.changeset/config.json`),
63
+ * extracts the `changelog` tuple's second element (options object), and
64
+ * validates the `versionFiles` key against `LegacyVersionFilesSchema`. Returns
65
+ * `undefined` if `changelog` is not a tuple, the `versionFiles` key is
66
+ * absent, or the array is empty. Schema validation errors are logged as
67
+ * warnings but do not throw.
68
+ *
69
+ * File reading and JSONC parsing are delegated to the caller
70
+ * (e.g., `ChangesetConfigReader` from `\@savvy-web/silk-effects`).
71
+ *
72
+ * @param config - Pre-parsed changeset config object
73
+ * @returns Parsed config array, or `undefined` if not configured
74
+ */
75
+ static extractVersionFiles(config) {
76
+ const { changelog } = config;
77
+ if (!Array.isArray(changelog) || changelog.length < 2) return;
78
+ const options = changelog[1];
79
+ if (!options || typeof options !== "object" || !("versionFiles" in options)) return;
80
+ try {
81
+ const decoded = Schema.decodeUnknownSync(LegacyVersionFilesSchema)(options.versionFiles);
82
+ return decoded.length > 0 ? decoded : void 0;
83
+ } catch (error) {
84
+ console.warn(`[changesets] Invalid versionFiles configuration: ${error instanceof Error ? error.message : String(error)}`);
85
+ return;
86
+ }
87
+ }
88
+ /**
89
+ * Discover all workspace packages and their current versions.
90
+ *
91
+ * @remarks
92
+ * Accepts a pre-resolved list of workspace packages (e.g., from
93
+ * `WorkspaceDiscovery.listPackages()`). The root package is always
94
+ * included if not already present in the packages list.
95
+ * Deduplicates by absolute path and skips entries without a version.
96
+ *
97
+ * @param cwd - Project root directory
98
+ * @param packages - Pre-resolved workspace packages
99
+ * @returns Array of workspace packages with versions
100
+ */
101
+ static discoverVersions(cwd, packages) {
102
+ const resolvedCwd = resolve(cwd);
103
+ const results = [];
104
+ const seen = /* @__PURE__ */ new Set();
105
+ for (const pkg of packages) {
106
+ if (seen.has(pkg.path) || !pkg.version) continue;
107
+ seen.add(pkg.path);
108
+ results.push({
109
+ name: pkg.name,
110
+ path: pkg.path,
111
+ version: pkg.version
112
+ });
113
+ }
114
+ if (!seen.has(resolvedCwd)) {
115
+ const version = readPackageVersion(resolvedCwd);
116
+ if (version) {
117
+ let rootName = "root";
118
+ try {
119
+ const pkg = JSON.parse(readFileSync(join(resolvedCwd, "package.json"), "utf-8"));
120
+ if (pkg.name) rootName = pkg.name;
121
+ } catch {}
122
+ results.push({
123
+ name: rootName,
124
+ path: resolvedCwd,
125
+ version
126
+ });
127
+ }
128
+ }
129
+ return results;
130
+ }
131
+ /**
132
+ * Determine which workspace version applies to a given file path
133
+ * using longest-prefix matching.
134
+ *
135
+ * @remarks
136
+ * For each workspace, checks whether the file is contained within
137
+ * it (i.e., the relative path does not start with `..`). Among all
138
+ * matching workspaces, the one with the longest absolute path wins
139
+ * (most specific match). Falls back to `rootVersion` if no workspace
140
+ * contains the file.
141
+ *
142
+ * @param filePath - Absolute path to the file
143
+ * @param workspaces - Discovered workspace versions
144
+ * @param rootVersion - Fallback version from project root
145
+ * @returns The version string to use
146
+ */
147
+ static resolveVersion(filePath, workspaces, rootVersion) {
148
+ const resolved = resolve(filePath);
149
+ let bestMatch;
150
+ let bestLength = 0;
151
+ for (const ws of workspaces) if (!relative(ws.path, resolved).startsWith("..") && ws.path.length > bestLength) {
152
+ bestMatch = ws;
153
+ bestLength = ws.path.length;
154
+ }
155
+ return bestMatch?.version ?? rootVersion;
156
+ }
157
+ /**
158
+ * Resolve glob patterns to absolute file paths.
159
+ *
160
+ * @remarks
161
+ * Uses `tinyglobby` to expand each config's glob pattern relative to
162
+ * `cwd`. The `node_modules` directory is always ignored. Returns
163
+ * tuples pairing each resolved absolute path with its originating config.
164
+ *
165
+ * @param configs - Version file configurations
166
+ * @param cwd - Project root directory
167
+ * @returns Array of `[filePath, config]` tuples
168
+ */
169
+ static resolveGlobs(configs, cwd) {
170
+ const results = [];
171
+ const resolvedCwd = resolve(cwd);
172
+ for (const config of configs) {
173
+ const matches = globSync(config.glob, {
174
+ cwd: resolvedCwd,
175
+ ignore: ["**/node_modules/**"]
176
+ });
177
+ for (const match of matches) results.push([join(resolvedCwd, match), config]);
178
+ }
179
+ return results;
180
+ }
181
+ /**
182
+ * Detect indentation from file content.
183
+ *
184
+ * @remarks
185
+ * Looks for the first line starting with whitespace followed by a
186
+ * double-quote (typical JSON property). Defaults to 2 spaces if
187
+ * no indentation pattern is found.
188
+ *
189
+ * @param content - Raw file content
190
+ * @returns Detected indent string (defaults to 2 spaces)
191
+ */
192
+ static detectIndent(content) {
193
+ return content.match(/^(\s+)"/m)?.[1] ?? " ";
194
+ }
195
+ /**
196
+ * Update JSON file at specified JSONPath locations.
197
+ *
198
+ * @remarks
199
+ * Reads the file, detects its indentation style and trailing newline
200
+ * preference, applies all JSONPath updates via {@link jsonPathSet},
201
+ * and writes the result back preserving the original formatting.
202
+ * Returns `undefined` if no JSONPath locations matched (no write occurs).
203
+ *
204
+ * @param filePath - Absolute path to the JSON file
205
+ * @param jsonPaths - JSONPath expressions to update
206
+ * @param version - New version string
207
+ * @returns Update result, or `undefined` if no changes were made
208
+ */
209
+ static updateFile(filePath, jsonPaths, version) {
210
+ const content = readFileSync(filePath, "utf-8");
211
+ const indent = VersionFiles.detectIndent(content);
212
+ const trailingNewline = content.endsWith("\n");
213
+ const obj = JSON.parse(content);
214
+ const previousValues = jsonPaths.flatMap((jp) => jsonPathGet(obj, jp));
215
+ let totalUpdated = 0;
216
+ for (const jp of jsonPaths) totalUpdated += jsonPathSet(obj, jp, version);
217
+ if (totalUpdated === 0) return;
218
+ let output = JSON.stringify(obj, null, indent);
219
+ if (trailingNewline) output += "\n";
220
+ writeFileSync(filePath, output, "utf-8");
221
+ return {
222
+ filePath,
223
+ jsonPaths,
224
+ version,
225
+ previousValues
226
+ };
227
+ }
228
+ /**
229
+ * Orchestrate the full version file update flow.
230
+ *
231
+ * @remarks
232
+ * Combines {@link VersionFiles.discoverVersions},
233
+ * {@link VersionFiles.resolveGlobs}, {@link VersionFiles.resolveVersion},
234
+ * and {@link VersionFiles.updateFile} into a single operation. In dry-run
235
+ * mode, files are read and JSONPaths are resolved but no writes occur.
236
+ *
237
+ * @param cwd - Project root directory
238
+ * @param configs - Validated version file configurations
239
+ * @param dryRun - If true, do not write files (default: `false`)
240
+ * @returns Array of update results
241
+ * @throws If any file cannot be read or parsed
242
+ */
243
+ static processVersionFiles(cwd, configs, dryRun = false, packages = []) {
244
+ const workspaces = VersionFiles.discoverVersions(cwd, packages);
245
+ const rootVersion = workspaces.find((ws) => ws.path === resolve(cwd))?.version ?? "0.0.0";
246
+ const resolved = VersionFiles.resolveGlobs(configs, cwd);
247
+ const updates = [];
248
+ for (const [filePath, config] of resolved) {
249
+ const jsonPaths = config.paths ?? ["$.version"];
250
+ const version = config.package ? workspaces.find((ws) => ws.name === config.package)?.version ?? rootVersion : VersionFiles.resolveVersion(filePath, workspaces, rootVersion);
251
+ try {
252
+ if (dryRun) {
253
+ const content = readFileSync(filePath, "utf-8");
254
+ const obj = JSON.parse(content);
255
+ const previousValues = jsonPaths.flatMap((jp) => jsonPathGet(obj, jp));
256
+ if (previousValues.length > 0) updates.push({
257
+ filePath,
258
+ jsonPaths,
259
+ version,
260
+ previousValues
261
+ });
262
+ } else {
263
+ const result = VersionFiles.updateFile(filePath, jsonPaths, version);
264
+ if (result) updates.push(result);
265
+ }
266
+ } catch (error) {
267
+ throw new Error(`Failed to update ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
268
+ }
269
+ }
270
+ return updates;
271
+ }
272
+ /**
273
+ * Apply version-file updates from the resolved (post-`ConfigInspector`)
274
+ * representation. Each {@link ResolvedPackageScope} already names the
275
+ * owning package and carries its current version plus the materialized
276
+ * file paths, so no glob expansion or path-prefix workspace resolution
277
+ * is needed here — the inspector has done that work.
278
+ *
279
+ * @remarks
280
+ * This is the 0.9.0 successor to {@link VersionFiles.processVersionFiles}.
281
+ * The old method stays in place to handle the deprecated top-level
282
+ * `versionFiles[]` shape (read by `cli/commands/init.ts` for its
283
+ * validation pass), and is removed alongside the legacy schema in
284
+ * 1.0.0.
285
+ *
286
+ * @param scopes - Per-package resolved scopes from {@link ConfigInspector.inspect}
287
+ * @param dryRun - When `true`, do not write files
288
+ * @returns Array of update results, one per file that actually had a
289
+ * matching JSONPath
290
+ *
291
+ * @internal
292
+ */
293
+ static processResolvedVersionFiles(scopes, dryRun = false) {
294
+ const updates = [];
295
+ for (const scope of scopes) for (const vf of scope.versionFiles) {
296
+ const jsonPaths = vf.paths.length > 0 ? vf.paths : ["$.version"];
297
+ for (const filePath of vf.matchedFiles) try {
298
+ if (dryRun) {
299
+ const content = readFileSync(filePath, "utf-8");
300
+ const obj = JSON.parse(content);
301
+ const previousValues = jsonPaths.flatMap((jp) => jsonPathGet(obj, jp));
302
+ if (previousValues.length > 0) updates.push({
303
+ filePath,
304
+ jsonPaths,
305
+ version: scope.version,
306
+ previousValues
307
+ });
308
+ } else {
309
+ const result = VersionFiles.updateFile(filePath, jsonPaths, scope.version);
310
+ if (result) updates.push(result);
311
+ }
312
+ } catch (error) {
313
+ throw new Error(`Failed to update ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
314
+ }
315
+ }
316
+ return updates;
317
+ }
318
+ };
319
+ /**
320
+ * Read the `version` field from a `package.json` in the given directory.
321
+ *
322
+ * @param dir - Absolute path to the directory containing `package.json`
323
+ * @returns The version string, or `undefined` if the file is missing or has no `version` field
324
+ *
325
+ * @internal
326
+ */
327
+ function readPackageVersion(dir) {
328
+ try {
329
+ return JSON.parse(readFileSync(join(dir, "package.json"), "utf-8")).version;
330
+ } catch {
331
+ return;
332
+ }
333
+ }
334
+
335
+ //#endregion
336
+ export { VersionFiles };
@@ -0,0 +1,142 @@
1
+ import { GitError } from "../errors.js";
2
+ import { Effect } from "effect";
3
+ import { readFileSync, readdirSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { execFileSync } from "node:child_process";
6
+
7
+ //#region src/changesets/utils/worktree-snapshot.ts
8
+ /**
9
+ * Shared helpers for `deps detect` and `deps regen` — both need to read
10
+ * workspace package snapshots from the live working tree (the "after"
11
+ * side of a dep diff that isn't pinned to a git ref) and to resolve the
12
+ * merge-base for the default `--from` ref.
13
+ *
14
+ * @remarks
15
+ * `WorkspaceSnapshotReader` covers the git-ref side via `git show`. This
16
+ * module is the working-tree counterpart — staged and unstaged
17
+ * `package.json` edits show up here, matching `analyze-branch`'s
18
+ * coverage of the working tree.
19
+ *
20
+ * @internal
21
+ */
22
+ /**
23
+ * Run `git merge-base <base> HEAD`, returning the SHA. Errors propagate
24
+ * as {@link GitError}.
25
+ *
26
+ * @internal
27
+ */
28
+ function gitMergeBase(cwd, base) {
29
+ return Effect.try({
30
+ try: () => execFileSync("git", [
31
+ "merge-base",
32
+ base,
33
+ "HEAD"
34
+ ], {
35
+ cwd,
36
+ encoding: "utf8",
37
+ stdio: [
38
+ "ignore",
39
+ "pipe",
40
+ "pipe"
41
+ ]
42
+ }).trim(),
43
+ catch: (error) => {
44
+ const stderr = error.stderr;
45
+ const text = typeof stderr === "string" ? stderr : stderr?.toString() ?? "";
46
+ return new GitError({
47
+ command: `git merge-base ${base} HEAD`,
48
+ cwd,
49
+ reason: text.trim() || (error.message ?? String(error))
50
+ });
51
+ }
52
+ });
53
+ }
54
+ /**
55
+ * Normalize a pnpm-workspace.yaml glob entry for filesystem expansion.
56
+ *
57
+ * `packages/**` collapses to `packages/*` (retains the wildcard so the
58
+ * caller hits the directory-listing path); `packages/*` and a literal
59
+ * `packages/foo` are passed through unchanged.
60
+ *
61
+ * Returning the literal-path form for `packages/**` (i.e., `"packages"`)
62
+ * would route the caller through the "no wildcards" branch and silently
63
+ * skip every child workspace.
64
+ *
65
+ * @internal
66
+ */
67
+ function normalizeWorkspaceGlob(glob) {
68
+ return glob.replace(/\/\*\*$/, "/*");
69
+ }
70
+ /**
71
+ * Read every workspace package's `package.json` from the live working
72
+ * tree, returning {@link WorkspaceSnapshot} entries matching the shape
73
+ * `WorkspaceSnapshotReader.snapshotAt` produces for git refs.
74
+ *
75
+ * @remarks
76
+ * Falls back to root-only when `pnpm-workspace.yaml` is missing or
77
+ * unparseable. Uses `node:fs.readdirSync` for directory expansion
78
+ * (portable across platforms — `execFileSync("ls")` is not).
79
+ *
80
+ * @internal
81
+ */
82
+ function snapshotFromWorktree(cwd) {
83
+ const snapshots = [];
84
+ const dirs = new Set([cwd]);
85
+ for (const dir of expandWorkspaceDirs(cwd)) dirs.add(dir);
86
+ for (const dir of dirs) try {
87
+ const pkgJson = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
88
+ if (!pkgJson.name) continue;
89
+ const rel = dir === cwd ? "." : dir.slice(cwd.length + 1);
90
+ snapshots.push({
91
+ name: pkgJson.name,
92
+ relativePath: rel,
93
+ version: pkgJson.version ?? "0.0.0",
94
+ dependencies: pkgJson.dependencies ?? {},
95
+ devDependencies: pkgJson.devDependencies ?? {},
96
+ peerDependencies: pkgJson.peerDependencies ?? {},
97
+ optionalDependencies: pkgJson.optionalDependencies ?? {}
98
+ });
99
+ } catch {}
100
+ return snapshots;
101
+ }
102
+ function expandWorkspaceDirs(cwd) {
103
+ let yaml;
104
+ try {
105
+ yaml = readFileSync(join(cwd, "pnpm-workspace.yaml"), "utf8");
106
+ } catch {
107
+ return [];
108
+ }
109
+ const dirs = [];
110
+ const lines = yaml.split(/\r?\n/);
111
+ let inPackagesBlock = false;
112
+ for (const line of lines) {
113
+ if (/^\s*#/.test(line)) continue;
114
+ if (/^\s*packages\s*:\s*$/.test(line)) {
115
+ inPackagesBlock = true;
116
+ continue;
117
+ }
118
+ if (!inPackagesBlock) continue;
119
+ const m = line.match(/^\s+-\s+["']?(.+?)["']?\s*$/);
120
+ if (m) {
121
+ const glob = normalizeWorkspaceGlob(m[1]);
122
+ if (glob.includes("*") || glob.includes("?")) {
123
+ const prefix = glob.includes("/") ? glob.slice(0, glob.lastIndexOf("/") + 1) : "";
124
+ let entries = [];
125
+ try {
126
+ entries = readdirSync(join(cwd, prefix || "."));
127
+ } catch {
128
+ continue;
129
+ }
130
+ const regex = new RegExp(`^${glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]")}$`);
131
+ for (const entry of entries) {
132
+ const candidate = prefix ? `${prefix}${entry}` : entry;
133
+ if (regex.test(candidate)) dirs.push(join(cwd, candidate));
134
+ }
135
+ } else dirs.push(join(cwd, glob));
136
+ } else if (line.length > 0 && !line.startsWith(" ") && !line.startsWith(" ")) inPackagesBlock = false;
137
+ }
138
+ return dirs;
139
+ }
140
+
141
+ //#endregion
142
+ export { gitMergeBase, snapshotFromWorktree };
@@ -0,0 +1,55 @@
1
+ import { GitHubApiError } from "../errors.js";
2
+ import { Effect } from "effect";
3
+ import { getInfo } from "@changesets/get-github-info";
4
+
5
+ //#region src/changesets/vendor/github-info.ts
6
+ /**
7
+ * Effect wrapper around `\@changesets/get-github-info`.
8
+ *
9
+ * @remarks
10
+ * Bridges the `\@changesets/get-github-info` package (which returns
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}.
14
+ *
15
+ * The {@link GitHubCommitInfo} type is the only item from this module
16
+ * that is part of the public API (re-exported from the package root).
17
+ *
18
+ * @see {@link GitHubService} for the Effect service layer that uses
19
+ * this function
20
+ *
21
+ * @internal
22
+ */
23
+ /**
24
+ * Fetch GitHub info for a commit, wrapped in Effect.
25
+ *
26
+ * @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"`.
30
+ *
31
+ * Requires a `GITHUB_TOKEN` environment variable to be set for
32
+ * authenticated API access (the upstream library reads it directly).
33
+ *
34
+ * @param params - The commit hash and repo in `owner/repo` format
35
+ * @returns An Effect that resolves to {@link GitHubCommitInfo} or fails
36
+ * with {@link GitHubApiError}
37
+ *
38
+ * @internal
39
+ */
40
+ function getGitHubInfo(params) {
41
+ return Effect.tryPromise({
42
+ try: () => getInfo({
43
+ commit: params.commit,
44
+ repo: params.repo
45
+ }),
46
+ /* v8 ignore next 5 -- error mapping tested via GitHubService test layer */
47
+ catch: (error) => new GitHubApiError({
48
+ operation: "getInfo",
49
+ reason: error instanceof Error ? error.message : String(error)
50
+ })
51
+ });
52
+ }
53
+
54
+ //#endregion
55
+ export { getGitHubInfo };