@savvy-web/silk-effects 0.6.1 → 1.0.1
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 +48 -17
- package/_virtual/_rolldown/runtime.js +18 -0
- package/changesets/api/categories.js +247 -0
- package/changesets/api/changelog.js +134 -0
- package/changesets/api/dependency-table.js +163 -0
- package/changesets/api/linter.js +168 -0
- package/changesets/api/transformer.js +140 -0
- package/changesets/categories/index.js +299 -0
- package/changesets/categories/types.js +66 -0
- package/changesets/changelog/formatting.js +119 -0
- package/changesets/changelog/getDependencyReleaseLine.js +114 -0
- package/changesets/changelog/getReleaseLine.js +122 -0
- package/changesets/changelog/index.js +99 -0
- package/changesets/constants.js +43 -0
- package/changesets/errors.js +305 -0
- package/changesets/index.js +146 -0
- package/changesets/markdownlint/index.js +29 -0
- package/changesets/markdownlint/rules/content-structure.js +98 -0
- package/changesets/markdownlint/rules/dependency-table-format.js +170 -0
- package/changesets/markdownlint/rules/heading-hierarchy.js +61 -0
- package/changesets/markdownlint/rules/required-sections.js +54 -0
- package/changesets/markdownlint/rules/uncategorized-content.js +54 -0
- package/changesets/markdownlint/rules/utils.js +30 -0
- package/changesets/remark/plugins/aggregate-dependency-tables.js +47 -0
- package/changesets/remark/plugins/contributor-footnotes.js +123 -0
- package/changesets/remark/plugins/deduplicate-items.js +30 -0
- package/changesets/remark/plugins/issue-link-refs.js +58 -0
- package/changesets/remark/plugins/merge-sections.js +43 -0
- package/changesets/remark/plugins/normalize-format.js +47 -0
- package/changesets/remark/plugins/reorder-sections.js +34 -0
- package/changesets/remark/presets.js +119 -0
- package/changesets/remark/rules/content-structure.js +22 -0
- package/changesets/remark/rules/dependency-table-format.js +40 -0
- package/changesets/remark/rules/heading-hierarchy.js +19 -0
- package/changesets/remark/rules/required-sections.js +17 -0
- package/changesets/remark/rules/uncategorized-content.js +31 -0
- package/changesets/schemas/changeset.js +146 -0
- package/changesets/schemas/dependency-table.js +189 -0
- package/changesets/schemas/git.js +69 -0
- package/changesets/schemas/github.js +175 -0
- package/changesets/schemas/options.js +182 -0
- package/changesets/schemas/package-scope.js +128 -0
- package/changesets/schemas/primitives.js +72 -0
- package/changesets/schemas/version-files.js +151 -0
- package/changesets/services/branch-analyzer.js +278 -0
- package/changesets/services/changelog.js +50 -0
- package/changesets/services/config-inspector.js +390 -0
- package/changesets/services/github.js +178 -0
- package/changesets/services/markdown.js +106 -0
- package/changesets/services/workspace-snapshot.js +182 -0
- package/changesets/utils/commit-parser.js +80 -0
- package/changesets/utils/dep-diff.js +77 -0
- package/changesets/utils/dependency-table.js +347 -0
- package/changesets/utils/issue-refs.js +101 -0
- package/changesets/utils/jsonpath.js +175 -0
- package/changesets/utils/logger.js +50 -0
- package/changesets/utils/markdown-link.js +57 -0
- package/changesets/utils/publishability.js +39 -0
- package/changesets/utils/remark-pipeline.js +79 -0
- package/changesets/utils/section-parser.js +94 -0
- package/changesets/utils/strip-frontmatter.js +46 -0
- package/changesets/utils/version-blocks.js +108 -0
- package/changesets/utils/version-files.js +336 -0
- package/changesets/utils/worktree-snapshot.js +142 -0
- package/changesets/vendor/github-info.js +55 -0
- package/commitlint/config/factory.js +69 -0
- package/commitlint/config/plugins.js +227 -0
- package/commitlint/config/rules.js +155 -0
- package/commitlint/config/schema.js +46 -0
- package/commitlint/detection/dco.js +53 -0
- package/commitlint/detection/scopes.js +45 -0
- package/commitlint/formatter/format.js +85 -0
- package/commitlint/formatter/messages.js +79 -0
- package/commitlint/hook/diagnostics/branch.js +36 -0
- package/commitlint/hook/diagnostics/cache.js +37 -0
- package/commitlint/hook/diagnostics/commitlint-config.js +36 -0
- package/commitlint/hook/diagnostics/open-issues.js +56 -0
- package/commitlint/hook/diagnostics/package-manager.js +51 -0
- package/commitlint/hook/diagnostics/signing.js +107 -0
- package/commitlint/hook/envelope.js +46 -0
- package/commitlint/hook/output.js +45 -0
- package/commitlint/hook/parse-bash-command.js +105 -0
- package/commitlint/hook/rules/closes-trailer.js +31 -0
- package/commitlint/hook/rules/forbidden-content.js +32 -0
- package/commitlint/hook/rules/plan-leakage.js +36 -0
- package/commitlint/hook/rules/signing-flag-conflict.js +25 -0
- package/commitlint/hook/rules/soft-wrap.js +37 -0
- package/commitlint/hook/rules/types.js +14 -0
- package/commitlint/hook/rules/verbosity.js +31 -0
- package/commitlint/hook/silence-logger.js +39 -0
- package/commitlint/index.js +146 -0
- package/commitlint/prompt/config.js +91 -0
- package/commitlint/prompt/emojis.js +74 -0
- package/commitlint/prompt/prompter.js +135 -0
- package/commitlint/static.js +73 -0
- package/errors/BiomeSyncError.js +21 -0
- package/errors/ChangesetConfigError.js +20 -0
- package/errors/ConfigNotFoundError.js +21 -0
- package/errors/SectionParseError.js +16 -0
- package/errors/SectionValidationError.js +16 -0
- package/errors/SectionWriteError.js +16 -0
- package/errors/TagFormatError.js +20 -0
- package/errors/ToolNotFoundError.js +11 -0
- package/errors/ToolResolutionError.js +11 -0
- package/errors/ToolVersionMismatchError.js +11 -0
- package/errors/VersioningDetectionError.js +20 -0
- package/errors/WorkspaceAnalysisError.js +21 -0
- package/index.d.ts +9743 -8380
- package/index.js +36 -6657
- package/lint/Handler.js +39 -0
- package/lint/cli/sections.js +65 -0
- package/lint/cli/templates/markdownlint.gen.js +183 -0
- package/lint/config/Preset.js +152 -0
- package/lint/config/createConfig.js +89 -0
- package/lint/handlers/Biome.js +179 -0
- package/lint/handlers/Markdown.js +139 -0
- package/lint/handlers/PackageJson.js +130 -0
- package/lint/handlers/PnpmWorkspace.js +141 -0
- package/lint/handlers/ShellScripts.js +58 -0
- package/lint/handlers/TypeScript.js +134 -0
- package/lint/handlers/Yaml.js +167 -0
- package/lint/index.js +52 -0
- package/lint/utils/Command.js +285 -0
- package/lint/utils/Filter.js +100 -0
- package/lint/utils/Workspace.js +86 -0
- package/package.json +52 -63
- package/schemas/CommentStyle.js +16 -0
- package/schemas/ResolvedTool.js +63 -0
- package/schemas/SavvySections.js +113 -0
- package/schemas/SectionBlock.js +70 -0
- package/schemas/SectionDefinition.js +121 -0
- package/schemas/SectionResults.js +12 -0
- package/schemas/TagStrategySchemas.js +18 -0
- package/schemas/ToolDefinition.js +39 -0
- package/schemas/ToolResults.js +14 -0
- package/schemas/VersioningSchemas.js +95 -0
- package/schemas/WorkspaceAnalysisSchemas.js +190 -0
- package/services/BiomeSchemaSync.js +133 -0
- package/services/ChangesetConfig.js +78 -0
- package/services/ChangesetConfigReader.js +106 -0
- package/services/ConfigDiscovery.js +71 -0
- package/services/ManagedSection.js +288 -0
- package/services/SilkPublishability.js +193 -0
- package/services/SilkWorkspaceAnalyzer.js +213 -0
- package/services/TagStrategy.js +54 -0
- package/services/ToolDiscovery.js +229 -0
- package/services/VersioningStrategy.js +67 -0
- package/tsdoc-metadata.json +11 -11
- package/turbo/digest.js +127 -0
- package/turbo/errors.js +48 -0
- package/turbo/index.js +32 -0
- package/turbo/schemas/DryRun.js +57 -0
- package/turbo/schemas/results.js +61 -0
- package/turbo/services/TurboInspector.js +100 -0
- 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 };
|