@maple-dev/changesets 0.0.0-stage ā 0.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.
- package/LICENSE +48 -0
- package/README.md +63 -2
- package/dist/bin.d.mts +1 -0
- package/dist/bin.mjs +7 -0
- package/dist/cli-_8EGJc1S.mjs +2566 -0
- package/dist/index.d.mts +255 -0
- package/dist/index.mjs +1 -0
- package/package.json +66 -5
|
@@ -0,0 +1,2566 @@
|
|
|
1
|
+
import { Config, Console, Context, Effect, FileSystem, Glob, Layer, Option, Path, Predicate, Random, Result, Schema, SemVer, SemVerRange, Stream } from "effect";
|
|
2
|
+
import { ChildProcess, ChildProcessSpawner } from "effect/process";
|
|
3
|
+
import { Command, Flag, Prompt } from "effect/cli";
|
|
4
|
+
// src/semver.ts
|
|
5
|
+
/**
|
|
6
|
+
* The handful of node-semver operations changesets relies on, over `effect/SemVer` and
|
|
7
|
+
* `effect/SemVerRange`. `rangeComparators` re-implements node-semver's desugaring for the two
|
|
8
|
+
* places where changesets reads a range's structure instead of only matching against it.
|
|
9
|
+
*/
|
|
10
|
+
const parseVersion = (version) => version === void 0 ? Result.failVoid : Result.mapError(SemVer.parse(version.trim()), () => void 0);
|
|
11
|
+
const isValidRange = (range) => Result.isSuccess(SemVerRange.parse(range.trim()));
|
|
12
|
+
/** `semver.satisfies`: an invalid version or range never satisfies. */
|
|
13
|
+
const satisfies = (version, range) => {
|
|
14
|
+
if (range === void 0) return false;
|
|
15
|
+
const parsedVersion = parseVersion(version);
|
|
16
|
+
const parsedRange = SemVerRange.parse(range.trim());
|
|
17
|
+
return Result.isSuccess(parsedVersion) && Result.isSuccess(parsedRange) && SemVerRange.matches(parsedRange.success, parsedVersion.success);
|
|
18
|
+
};
|
|
19
|
+
const bumps = {
|
|
20
|
+
major: SemVer.bumpMajor,
|
|
21
|
+
minor: SemVer.bumpMinor,
|
|
22
|
+
patch: SemVer.bumpPatch
|
|
23
|
+
};
|
|
24
|
+
/** `semver.inc` for the three release types; `undefined` for an invalid version, like `null` there. */
|
|
25
|
+
const increment = (version, type) => {
|
|
26
|
+
const parsed = parseVersion(version);
|
|
27
|
+
return Result.isSuccess(parsed) ? SemVer.format(bumps[type](parsed.success)) : void 0;
|
|
28
|
+
};
|
|
29
|
+
const isPrerelease = (version) => {
|
|
30
|
+
const parsed = parseVersion(version);
|
|
31
|
+
return Result.isSuccess(parsed) && SemVer.isPrerelease(parsed.success);
|
|
32
|
+
};
|
|
33
|
+
const greaterThan = (a, b) => {
|
|
34
|
+
const left = parseVersion(a);
|
|
35
|
+
const right = parseVersion(b);
|
|
36
|
+
return Result.isSuccess(left) && Result.isSuccess(right) && SemVer.isGreaterThan(left.success, right.success);
|
|
37
|
+
};
|
|
38
|
+
const lessThan = (a, b) => {
|
|
39
|
+
const left = parseVersion(a);
|
|
40
|
+
const right = parseVersion(b);
|
|
41
|
+
return Result.isSuccess(left) && Result.isSuccess(right) && SemVer.isLessThan(left.success, right.success);
|
|
42
|
+
};
|
|
43
|
+
const isX = (part) => part === void 0 || part === "x" || part === "X" || part === "*";
|
|
44
|
+
const partialPattern = /^v?(\d+|[xX*])?(?:\.(\d+|[xX*]))?(?:\.(\d+|[xX*]))?(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
45
|
+
const operatorPattern = /^(<=|>=|<|>|=|~>|~|\^)?(.*)$/;
|
|
46
|
+
const desugarToken = (operator, version) => {
|
|
47
|
+
const match = partialPattern.exec(version);
|
|
48
|
+
if (match === null) return [{
|
|
49
|
+
operator,
|
|
50
|
+
value: version
|
|
51
|
+
}];
|
|
52
|
+
const [, major, minor, patch, pre] = match;
|
|
53
|
+
if (isX(major)) return operator === "<" || operator === ">" ? [{
|
|
54
|
+
operator: "<",
|
|
55
|
+
value: "<0.0.0-0"
|
|
56
|
+
}] : [];
|
|
57
|
+
const M = Number(major);
|
|
58
|
+
const m = Number(minor);
|
|
59
|
+
const full = `${major}.${minor}.${patch}${pre === void 0 ? "" : `-${pre}`}`;
|
|
60
|
+
switch (operator) {
|
|
61
|
+
case ">": return isX(minor) ? [{
|
|
62
|
+
operator: ">=",
|
|
63
|
+
value: `>=${M + 1}.0.0`
|
|
64
|
+
}] : isX(patch) ? [{
|
|
65
|
+
operator: ">=",
|
|
66
|
+
value: `>=${M}.${m + 1}.0`
|
|
67
|
+
}] : [{
|
|
68
|
+
operator: ">",
|
|
69
|
+
value: `>${full}`
|
|
70
|
+
}];
|
|
71
|
+
case ">=": {
|
|
72
|
+
const value = isX(minor) ? `>=${M}.0.0` : isX(patch) ? `>=${M}.${m}.0` : `>=${full}`;
|
|
73
|
+
return value === ">=0.0.0" ? [] : [{
|
|
74
|
+
operator: ">=",
|
|
75
|
+
value
|
|
76
|
+
}];
|
|
77
|
+
}
|
|
78
|
+
case "<": return [{
|
|
79
|
+
operator: "<",
|
|
80
|
+
value: isX(minor) ? `<${M}.0.0-0` : isX(patch) ? `<${M}.${m}.0-0` : `<${full}`
|
|
81
|
+
}];
|
|
82
|
+
case "<=": return isX(minor) ? [{
|
|
83
|
+
operator: "<",
|
|
84
|
+
value: `<${M + 1}.0.0-0`
|
|
85
|
+
}] : isX(patch) ? [{
|
|
86
|
+
operator: "<",
|
|
87
|
+
value: `<${M}.${m + 1}.0-0`
|
|
88
|
+
}] : [{
|
|
89
|
+
operator: "<=",
|
|
90
|
+
value: `<=${full}`
|
|
91
|
+
}];
|
|
92
|
+
case "":
|
|
93
|
+
case "=": return isX(minor) || isX(patch) ? [{
|
|
94
|
+
operator: ">=",
|
|
95
|
+
value: ">="
|
|
96
|
+
}, {
|
|
97
|
+
operator: "<",
|
|
98
|
+
value: "<"
|
|
99
|
+
}] : [{
|
|
100
|
+
operator: "",
|
|
101
|
+
value: full
|
|
102
|
+
}];
|
|
103
|
+
default: return [{
|
|
104
|
+
operator: ">=",
|
|
105
|
+
value: ">="
|
|
106
|
+
}, {
|
|
107
|
+
operator: "<",
|
|
108
|
+
value: "<"
|
|
109
|
+
}];
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
const tokens = (set) => {
|
|
113
|
+
return set.trim().split(/\s+/).filter((part) => part.length > 0).reduce((merged, part) => {
|
|
114
|
+
const previous = merged.at(-1);
|
|
115
|
+
return previous !== void 0 && /^(<=|>=|<|>|=|~>|~|\^)$/.test(previous) ? [...merged.slice(0, -1), `${previous}${part}`] : [...merged, part];
|
|
116
|
+
}, []);
|
|
117
|
+
};
|
|
118
|
+
/** The comparators node-semver builds for one `||`-free comparator set. */
|
|
119
|
+
const setComparators = (set) => {
|
|
120
|
+
const parts = tokens(set);
|
|
121
|
+
if (parts.length === 3 && parts[1] === "-") return isX(partialPattern.exec(parts[0])?.[1]) && isX(partialPattern.exec(parts[2])?.[1]) ? [] : [{
|
|
122
|
+
operator: ">=",
|
|
123
|
+
value: ">="
|
|
124
|
+
}, {
|
|
125
|
+
operator: "<=",
|
|
126
|
+
value: "<="
|
|
127
|
+
}];
|
|
128
|
+
return parts.flatMap((part) => {
|
|
129
|
+
const [, operator = "", version = ""] = operatorPattern.exec(part) ?? [];
|
|
130
|
+
return desugarToken(operator, version);
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
/** `new Range(range).range === ""`: some alternative accepts every version. */
|
|
134
|
+
const isAnyRange = (range) => range.split("||").some((set) => setComparators(set).length === 0);
|
|
135
|
+
/** `new Range(range).set`: one comparator list per `||` alternative. */
|
|
136
|
+
const rangeComparators = (range) => range.split("||").map(setComparators);
|
|
137
|
+
// src/changelog.ts
|
|
138
|
+
/**
|
|
139
|
+
* CHANGELOG.md entries byte-for-byte as `@changesets/apply-release-plan` writes them with the
|
|
140
|
+
* default `@changesets/cli/changelog` (which is `@changesets/changelog-git`).
|
|
141
|
+
*/
|
|
142
|
+
const shortCommit = (commit) => commit ? commit.slice(0, 7) : void 0;
|
|
143
|
+
/** `@changesets/changelog-git`. */
|
|
144
|
+
const gitChangelog = {
|
|
145
|
+
getReleaseLine: (changeset) => {
|
|
146
|
+
const [firstLine, ...futureLines] = changeset.summary.split("\n").map((line) => line.trimEnd());
|
|
147
|
+
const commit = shortCommit(changeset.commit);
|
|
148
|
+
const head = `- ${commit === void 0 ? "" : `${commit}: `}${firstLine}`;
|
|
149
|
+
return Effect.succeed(futureLines.length > 0 ? `${head}\n${futureLines.map((line) => ` ${line}`).join("\n")}` : head);
|
|
150
|
+
},
|
|
151
|
+
getDependencyReleaseLine: (changesets, dependenciesUpdated) => {
|
|
152
|
+
if (dependenciesUpdated.length === 0) return Effect.succeed("");
|
|
153
|
+
const links = changesets.map((changeset) => {
|
|
154
|
+
const commit = shortCommit(changeset.commit);
|
|
155
|
+
return `- Updated dependencies${commit === void 0 ? "" : ` [${commit}]`}`;
|
|
156
|
+
});
|
|
157
|
+
const list = dependenciesUpdated.map((dependency) => ` - ${dependency.name}@${dependency.newVersion}`);
|
|
158
|
+
return Effect.succeed([...links, ...list].join("\n"));
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
const bumpLevel = {
|
|
162
|
+
none: 0,
|
|
163
|
+
patch: 1,
|
|
164
|
+
minor: 2,
|
|
165
|
+
major: 3
|
|
166
|
+
};
|
|
167
|
+
/** `shouldUpdateDependencyBasedOnConfig`. */
|
|
168
|
+
const shouldUpdateDependency = (release, depVersionRange, depType, options) => {
|
|
169
|
+
if (release.newVersion === void 0) return false;
|
|
170
|
+
const workspace = depVersionRange.startsWith("workspace:");
|
|
171
|
+
const stripped = workspace ? depVersionRange.slice(10) : depVersionRange;
|
|
172
|
+
if (workspace && stripped === "*") return true;
|
|
173
|
+
if (workspace && stripped !== "^" && stripped !== "~" && !isValidRange(stripped)) return normalizePath(stripped) === release.relativeDir;
|
|
174
|
+
const range = workspace && (stripped === "^" || stripped === "~") ? `${stripped}${release.oldVersion}` : stripped;
|
|
175
|
+
if (!satisfies(release.newVersion, range)) return true;
|
|
176
|
+
if (depType === "peerDependencies") return !options.onlyUpdatePeerDependentsWhenOutOfRange;
|
|
177
|
+
return bumpLevel[release.type] >= bumpLevel[options.updateInternalDependencies];
|
|
178
|
+
};
|
|
179
|
+
const normalizePath = (path) => path.split("/").reduce((parts, part) => {
|
|
180
|
+
if (part === "" || part === ".") return parts;
|
|
181
|
+
if (part === ".." && parts.length > 0 && parts.at(-1) !== "..") return parts.slice(0, -1);
|
|
182
|
+
return [...parts, part];
|
|
183
|
+
}, []).join("/") || ".";
|
|
184
|
+
const capitalize = (text) => text.charAt(0).toUpperCase() + text.slice(1);
|
|
185
|
+
/** `generateMarkdownForVersionType`: joins release lines, keeping at most one blank line between them. */
|
|
186
|
+
const markdownForType = (type, lines) => {
|
|
187
|
+
const releaseLines = lines.filter((line) => line);
|
|
188
|
+
if (releaseLines.length === 0) return void 0;
|
|
189
|
+
return releaseLines.reduce(({ content, newLines }, line) => {
|
|
190
|
+
const leading = newLines + (/^\n*/.exec(line)?.[0].length ?? 0);
|
|
191
|
+
return {
|
|
192
|
+
content: content + "\n".repeat(Math.min(Math.max(leading, 1), 2)) + line.trim(),
|
|
193
|
+
newLines: /\n*$/.exec(line)?.[0].length ?? 0
|
|
194
|
+
};
|
|
195
|
+
}, {
|
|
196
|
+
content: `### ${capitalize(type)} Changes`,
|
|
197
|
+
newLines: 2
|
|
198
|
+
}).content;
|
|
199
|
+
};
|
|
200
|
+
/** `getChangelogEntry` for one release; `undefined` for a release that does not bump. */
|
|
201
|
+
const changelogEntry = Effect.fn("changelogEntry")(function* (release, releases, changesets, functions, functionOptions, options) {
|
|
202
|
+
if (release.type === "none") return void 0;
|
|
203
|
+
const linesOf = (type) => Effect.forEach(changesets.flatMap((changeset) => {
|
|
204
|
+
const own = changeset.releases.find((r) => r.name === release.name);
|
|
205
|
+
return own !== void 0 && own.type === type ? [changeset] : [];
|
|
206
|
+
}), (changeset) => functions.getReleaseLine(changeset, type, functionOptions));
|
|
207
|
+
const dependentReleases = releases.filter((rel) => {
|
|
208
|
+
const dependencyRange = release.packageJson.dependencies?.[rel.name];
|
|
209
|
+
const peerRange = release.packageJson.peerDependencies?.[rel.name];
|
|
210
|
+
const versionRange = dependencyRange || peerRange;
|
|
211
|
+
return versionRange !== void 0 && versionRange !== "" && (versionRange.startsWith("workspace:") || isValidRange(versionRange)) && shouldUpdateDependency(rel, versionRange, dependencyRange ? "dependencies" : "peerDependencies", options);
|
|
212
|
+
});
|
|
213
|
+
const relevantIds = new Set(dependentReleases.flatMap((rel) => rel.changesets));
|
|
214
|
+
const major = yield* linesOf("major");
|
|
215
|
+
const minor = yield* linesOf("minor");
|
|
216
|
+
const patch = yield* linesOf("patch");
|
|
217
|
+
const dependencyLine = yield* functions.getDependencyReleaseLine(changesets.filter((changeset) => relevantIds.has(changeset.id)), dependentReleases, functionOptions);
|
|
218
|
+
const rendered = [
|
|
219
|
+
`## ${release.newVersion}`,
|
|
220
|
+
markdownForType("major", major),
|
|
221
|
+
markdownForType("minor", minor),
|
|
222
|
+
markdownForType("patch", [...patch, dependencyLine])
|
|
223
|
+
].filter((line) => line !== void 0 && line !== "");
|
|
224
|
+
return (rendered.length === 1 ? [...rendered, "No changes in this release."] : rendered).join("\n\n");
|
|
225
|
+
});
|
|
226
|
+
/** `updateChangelog`: the new entry goes above the first version heading, or under the title. */
|
|
227
|
+
const prependEntry = (existing, entry, packageName) => {
|
|
228
|
+
const template = `\n\n${entry.trim()}\n`;
|
|
229
|
+
if (existing === void 0 || existing === "") return `# ${packageName}${template}`;
|
|
230
|
+
const firstVersionHeading = existing.search(/^#{1,6}\s+\d+\.\d+/m);
|
|
231
|
+
if (firstVersionHeading >= 0) return existing.slice(0, firstVersionHeading) + template.trimStart() + "\n" + existing.slice(firstVersionHeading);
|
|
232
|
+
const newline = existing.indexOf("\n");
|
|
233
|
+
return newline === -1 ? existing + template : existing.slice(0, newline) + template + existing.slice(newline + 1);
|
|
234
|
+
};
|
|
235
|
+
// src/types.ts
|
|
236
|
+
const DEPENDENCY_TYPES = [
|
|
237
|
+
"dependencies",
|
|
238
|
+
"devDependencies",
|
|
239
|
+
"peerDependencies",
|
|
240
|
+
"optionalDependencies"
|
|
241
|
+
];
|
|
242
|
+
// src/dependents.ts
|
|
243
|
+
/** `@changesets/get-dependents-graph`: which workspace packages depend on which. */
|
|
244
|
+
const allDependencies = (json, ignoreDevDependencies) => DEPENDENCY_TYPES.reduce((all, type) => {
|
|
245
|
+
Object.entries(json[type] ?? {}).forEach(([name, range]) => {
|
|
246
|
+
if (type === "devDependencies" && (ignoreDevDependencies || range.startsWith("link:") || range.startsWith("file:"))) return;
|
|
247
|
+
all.set(name, range);
|
|
248
|
+
});
|
|
249
|
+
return all;
|
|
250
|
+
}, /* @__PURE__ */ new Map());
|
|
251
|
+
const isProtocolRange = (range) => range.includes(":");
|
|
252
|
+
const isUsableRange = (range) => !isProtocolRange(range) && isValidRange(range);
|
|
253
|
+
const normalizePosix = (path) => path.split("/").reduce((parts, part) => {
|
|
254
|
+
if (part === "" || part === ".") return parts;
|
|
255
|
+
if (part === ".." && parts.length > 0 && parts.at(-1) !== "..") return parts.slice(0, -1);
|
|
256
|
+
return [...parts, part];
|
|
257
|
+
}, []).join("/") || ".";
|
|
258
|
+
const relativeDirOf = (rootDir, pkg) => pkg.dir === rootDir ? "." : pkg.dir.slice(rootDir.length + 1).replaceAll("\\", "/");
|
|
259
|
+
const workspacePathMatches = (range, rootDir, pkg) => normalizePosix(range) === relativeDirOf(rootDir, pkg);
|
|
260
|
+
const dependenciesOf = (pkg, byName, rootDir, options, invalid) => [...allDependencies(pkg.packageJson, options.ignoreDevDependencies ?? false)].flatMap(([name, rawRange]) => {
|
|
261
|
+
const match = byName.get(name);
|
|
262
|
+
if (match === void 0) return [];
|
|
263
|
+
const expected = match.packageJson.version;
|
|
264
|
+
const report = () => {
|
|
265
|
+
invalid.push(`Package ${pkg.packageJson.name} must depend on the current version of ${name}: ${expected} vs ${rawRange}`);
|
|
266
|
+
return [];
|
|
267
|
+
};
|
|
268
|
+
if (rawRange.startsWith("workspace:")) {
|
|
269
|
+
const range = rawRange.slice(10);
|
|
270
|
+
if (range === "*" || range === "^" || range === "~") return [name];
|
|
271
|
+
if (workspacePathMatches(range, rootDir, match)) return [name];
|
|
272
|
+
if (!isUsableRange(range)) return report();
|
|
273
|
+
return satisfies(expected, range) ? [name] : report();
|
|
274
|
+
}
|
|
275
|
+
if (options.bumpVersionsWithWorkspaceProtocolOnly === true) return [];
|
|
276
|
+
if (isProtocolRange(rawRange)) return report();
|
|
277
|
+
if (!isUsableRange(rawRange)) return [];
|
|
278
|
+
return satisfies(expected, rawRange) ? [name] : report();
|
|
279
|
+
});
|
|
280
|
+
const dependentsGraph = (packages, options = {}) => {
|
|
281
|
+
const byName = new Map([[packages.rootPackage.packageJson.name, packages.rootPackage], ...packages.packages.map((pkg) => [pkg.packageJson.name, pkg])]);
|
|
282
|
+
const invalid = [];
|
|
283
|
+
const graph = new Map([[packages.rootPackage.packageJson.name, []], ...packages.packages.map((pkg) => [pkg.packageJson.name, []])]);
|
|
284
|
+
dependenciesOf(packages.rootPackage, byName, packages.rootDir, options, invalid);
|
|
285
|
+
packages.packages.forEach((pkg) => {
|
|
286
|
+
dependenciesOf(pkg, byName, packages.rootDir, options, invalid).forEach((dependency) => graph.get(dependency)?.push(pkg.packageJson.name));
|
|
287
|
+
});
|
|
288
|
+
return {
|
|
289
|
+
graph,
|
|
290
|
+
invalid
|
|
291
|
+
};
|
|
292
|
+
};
|
|
293
|
+
// src/config.ts
|
|
294
|
+
/** `.changeset/config.json`: the written schema, its defaults, and the `@changesets/config` rules. */
|
|
295
|
+
const ModuleTuple = Schema.Tuple([Schema.String, Schema.NullOr(Schema.Record(Schema.String, Schema.Unknown))]);
|
|
296
|
+
const WrittenConfig = Schema.Struct({
|
|
297
|
+
baseBranch: Schema.optionalKey(Schema.String),
|
|
298
|
+
access: Schema.optionalKey(Schema.Literals(["public", "restricted"])),
|
|
299
|
+
changedFilePatterns: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
300
|
+
format: Schema.optionalKey(Schema.Literals([
|
|
301
|
+
"auto",
|
|
302
|
+
"prettier",
|
|
303
|
+
"oxfmt",
|
|
304
|
+
"deno",
|
|
305
|
+
"dprint",
|
|
306
|
+
false
|
|
307
|
+
])),
|
|
308
|
+
ignore: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
309
|
+
fixed: Schema.optionalKey(Schema.Array(Schema.Array(Schema.String))),
|
|
310
|
+
linked: Schema.optionalKey(Schema.Array(Schema.Array(Schema.String))),
|
|
311
|
+
updateInternalDependencies: Schema.optionalKey(Schema.Literals(["minor", "patch"])),
|
|
312
|
+
privatePackages: Schema.optionalKey(Schema.Union([Schema.Struct({
|
|
313
|
+
version: Schema.optionalKey(Schema.Boolean),
|
|
314
|
+
tag: Schema.optionalKey(Schema.Boolean)
|
|
315
|
+
}), Schema.Boolean])),
|
|
316
|
+
bumpVersionsWithWorkspaceProtocolOnly: Schema.optionalKey(Schema.Boolean),
|
|
317
|
+
snapshot: Schema.optionalKey(Schema.Struct({
|
|
318
|
+
useCalculatedVersion: Schema.optionalKey(Schema.Boolean),
|
|
319
|
+
prereleaseTemplate: Schema.optionalKey(Schema.String)
|
|
320
|
+
})),
|
|
321
|
+
changelog: Schema.optionalKey(Schema.Union([
|
|
322
|
+
Schema.Literal(false),
|
|
323
|
+
Schema.String,
|
|
324
|
+
ModuleTuple
|
|
325
|
+
])),
|
|
326
|
+
commit: Schema.optionalKey(Schema.Union([
|
|
327
|
+
Schema.Boolean,
|
|
328
|
+
Schema.String,
|
|
329
|
+
ModuleTuple
|
|
330
|
+
])),
|
|
331
|
+
___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: Schema.optionalKey(Schema.Struct({
|
|
332
|
+
onlyUpdatePeerDependentsWhenOutOfRange: Schema.optionalKey(Schema.Boolean),
|
|
333
|
+
updateInternalDependents: Schema.optionalKey(Schema.Literals(["always", "out-of-range"]))
|
|
334
|
+
}))
|
|
335
|
+
});
|
|
336
|
+
/** A picomatch-style matcher: a `!pattern` matches what `pattern` does not. */
|
|
337
|
+
const matcher = (pattern) => {
|
|
338
|
+
const negated = pattern.startsWith("!");
|
|
339
|
+
const glob = Glob.make(negated ? pattern.slice(1) : pattern);
|
|
340
|
+
return {
|
|
341
|
+
negated,
|
|
342
|
+
test: (value) => negated !== Glob.match(glob, value)
|
|
343
|
+
};
|
|
344
|
+
};
|
|
345
|
+
/** changesets' glob reduction: a positive pattern admits, a later `!pattern` it matches takes back. */
|
|
346
|
+
const matchesPatterns = (value, patterns) => patterns.map(matcher).reduce((passed, m) => passed ? !(m.negated && !m.test(value)) : !m.negated && m.test(value), false);
|
|
347
|
+
const globMatch = (names, patterns) => names.filter((name) => matchesPatterns(name, patterns));
|
|
348
|
+
const moduleReference = (value, fallback) => {
|
|
349
|
+
if (value === void 0) return fallback;
|
|
350
|
+
if (typeof value === "string") return [value, null];
|
|
351
|
+
if (value === true) return ["@changesets/cli/commit", { skipCI: "version" }];
|
|
352
|
+
return value;
|
|
353
|
+
};
|
|
354
|
+
/** `normalizeWrittenConfig`: fills defaults and expands package-name globs. */
|
|
355
|
+
const normalizeConfig = (written, packageNames) => ({
|
|
356
|
+
baseBranch: written.baseBranch ?? "main",
|
|
357
|
+
access: written.access ?? "restricted",
|
|
358
|
+
changedFilePatterns: written.changedFilePatterns ?? ["**"],
|
|
359
|
+
format: written.format ?? "auto",
|
|
360
|
+
ignore: globMatch(packageNames, written.ignore ?? []),
|
|
361
|
+
fixed: (written.fixed ?? []).map((group) => globMatch(packageNames, group)),
|
|
362
|
+
linked: (written.linked ?? []).map((group) => globMatch(packageNames, group)),
|
|
363
|
+
updateInternalDependencies: written.updateInternalDependencies ?? "patch",
|
|
364
|
+
privatePackages: typeof written.privatePackages === "boolean" ? {
|
|
365
|
+
version: written.privatePackages,
|
|
366
|
+
tag: written.privatePackages
|
|
367
|
+
} : {
|
|
368
|
+
version: written.privatePackages?.version ?? false,
|
|
369
|
+
tag: written.privatePackages?.tag ?? false
|
|
370
|
+
},
|
|
371
|
+
bumpVersionsWithWorkspaceProtocolOnly: written.bumpVersionsWithWorkspaceProtocolOnly ?? false,
|
|
372
|
+
snapshot: {
|
|
373
|
+
useCalculatedVersion: written.snapshot?.useCalculatedVersion ?? false,
|
|
374
|
+
prereleaseTemplate: written.snapshot?.prereleaseTemplate
|
|
375
|
+
},
|
|
376
|
+
changelog: moduleReference(written.changelog, ["@changesets/cli/changelog", null]),
|
|
377
|
+
commit: moduleReference(written.commit, false),
|
|
378
|
+
onlyUpdatePeerDependentsWhenOutOfRange: written.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH?.onlyUpdatePeerDependentsWhenOutOfRange ?? false,
|
|
379
|
+
updateInternalDependents: written.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH?.updateInternalDependents ?? "out-of-range"
|
|
380
|
+
});
|
|
381
|
+
/** `shouldSkipPackage`: ignored, private without `privatePackages.version`, or versionless. */
|
|
382
|
+
const shouldSkipPackage = ({ packageJson }, { ignore, allowPrivatePackages }) => ignore.includes(packageJson.name) || packageJson.private === true && !allowPrivatePackages || !packageJson.version;
|
|
383
|
+
const skipOptions = (config) => ({
|
|
384
|
+
ignore: config.ignore,
|
|
385
|
+
allowPrivatePackages: config.privatePackages.version
|
|
386
|
+
});
|
|
387
|
+
const picomatchNote = `Note that glob expressions must be defined according to https://npmx.dev/picomatch.`;
|
|
388
|
+
const unmatched = (patterns, names) => patterns.filter((pattern) => !names.some((name) => matcher(pattern).test(name)));
|
|
389
|
+
const duplicates = (groups) => {
|
|
390
|
+
const flat = groups.flat();
|
|
391
|
+
return [...new Set(flat.filter((name, index) => flat.indexOf(name) !== index))];
|
|
392
|
+
};
|
|
393
|
+
/** The `@changesets/config` validation rules, in the same order and wording. */
|
|
394
|
+
const validateConfig = (config, written, packages) => {
|
|
395
|
+
const names = packages.packages.map((pkg) => pkg.packageJson.name);
|
|
396
|
+
const groupWarnings = (kind, groups) => config[kind].length === 0 ? [] : (groups ?? []).flatMap((group) => unmatched(group, names).map((pattern) => `${kind}: Invalid path: The package or glob "${pattern}" does not match any package in the project. ${picomatchNote}`));
|
|
397
|
+
const groupErrors = (kind) => duplicates(config[kind]).map((name) => `${kind}: Invalid group: The package or glob "${name}" is defined in multiple groups of ${kind} packages. Packages can only be belong to a single group. ${picomatchNote}`);
|
|
398
|
+
const linkedNames = new Set(config.linked.flat());
|
|
399
|
+
const fixedAndLinked = config.fixed.length === 0 || config.linked.length === 0 ? [] : [...new Set(config.fixed.flat())].filter((name) => linkedNames.has(name)).map((name) => `Invalid group: The package "${name}" can be found in both fixed and linked groups. A package can only be either fixed or linked.`);
|
|
400
|
+
const skipped = config.ignore.length === 0 && config.privatePackages.version ? [] : skippedDependentErrors(packages, config).map(({ dependent, skipped }) => `Invalid tree: "${dependent}" depends on the skipped package "${skipped}", but "${dependent}" is not skipped. Please add "${dependent}" to the "ignore" option.`);
|
|
401
|
+
const privateTag = config.privatePackages.version === false && config.privatePackages.tag === true ? [`privatePackages: Invalid combination: "tag" is set to "true" but "version" is set to "false".`] : [];
|
|
402
|
+
return {
|
|
403
|
+
warnings: [...groupWarnings("fixed", written.fixed), ...groupWarnings("linked", written.linked)],
|
|
404
|
+
errors: [
|
|
405
|
+
...groupErrors("fixed"),
|
|
406
|
+
...groupErrors("linked"),
|
|
407
|
+
...fixedAndLinked,
|
|
408
|
+
...skipped,
|
|
409
|
+
...privateTag
|
|
410
|
+
]
|
|
411
|
+
};
|
|
412
|
+
};
|
|
413
|
+
/** Public packages that depend (outside devDependencies) on a package changesets skips. */
|
|
414
|
+
const skippedDependentErrors = (packages, config) => {
|
|
415
|
+
const graph = dependentsGraph(packages, {
|
|
416
|
+
ignoreDevDependencies: true,
|
|
417
|
+
bumpVersionsWithWorkspaceProtocolOnly: config.bumpVersionsWithWorkspaceProtocolOnly
|
|
418
|
+
}).graph;
|
|
419
|
+
const byName = new Map(packages.packages.map((pkg) => [pkg.packageJson.name, pkg]));
|
|
420
|
+
return packages.packages.filter((pkg) => shouldSkipPackage(pkg, skipOptions(config))).flatMap((pkg) => (graph.get(pkg.packageJson.name) ?? []).flatMap((dependent) => {
|
|
421
|
+
const dependentPkg = byName.get(dependent);
|
|
422
|
+
return dependentPkg === void 0 || shouldSkipPackage(dependentPkg, skipOptions(config)) || dependentPkg.packageJson.private === true ? [] : [{
|
|
423
|
+
dependent,
|
|
424
|
+
skipped: pkg.packageJson.name
|
|
425
|
+
}];
|
|
426
|
+
}));
|
|
427
|
+
};
|
|
428
|
+
// src/dependency-edits.ts
|
|
429
|
+
/** Which internal dependency ranges `changeset version` rewrites, and to what. */
|
|
430
|
+
const rangeType = (range) => {
|
|
431
|
+
if (range.startsWith("^")) return "^";
|
|
432
|
+
if (range.startsWith("~")) return "~";
|
|
433
|
+
if (range.startsWith(">=")) return ">=";
|
|
434
|
+
if (range.startsWith("<=")) return "<=";
|
|
435
|
+
if (range.startsWith(">")) return ">";
|
|
436
|
+
return "";
|
|
437
|
+
};
|
|
438
|
+
/** `getNewDependencyRange`: keep the range's operator, or move both bounds of `>=a <b`. */
|
|
439
|
+
const newDependencyRange = (range, newVersion, type) => {
|
|
440
|
+
const sets = rangeComparators(range);
|
|
441
|
+
const comparators = sets.length === 1 ? sets[0] ?? [] : [];
|
|
442
|
+
if (comparators.length === 2 && range.includes(">") && range.includes("<")) {
|
|
443
|
+
const lower = comparators.find(({ operator }) => operator.startsWith(">"));
|
|
444
|
+
const upper = comparators.find(({ operator }) => operator.startsWith("<"));
|
|
445
|
+
if (lower !== void 0 && upper !== void 0) {
|
|
446
|
+
const newUpper = satisfies(newVersion, upper.value) ? upper.value : `<${increment(newVersion, type)}`;
|
|
447
|
+
return comparators[0] === lower ? `>=${newVersion} ${newUpper}` : `${newUpper} >=${newVersion}`;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return `${rangeType(range)}${newVersion}`;
|
|
451
|
+
};
|
|
452
|
+
/** `getDependencyVersionEdits` for one manifest against every release in the plan. */
|
|
453
|
+
const dependencyEdits = (packageJson, releases, options) => DEPENDENCY_TYPES.flatMap((depType) => {
|
|
454
|
+
const deps = packageJson[depType];
|
|
455
|
+
if (deps === void 0) return [];
|
|
456
|
+
return releases.flatMap((release) => {
|
|
457
|
+
const { name, newVersion, type } = release;
|
|
458
|
+
if (newVersion === void 0 || type === "none") return [];
|
|
459
|
+
const current = deps[name];
|
|
460
|
+
if (!current || current.startsWith("file:") || current.startsWith("link:") || !shouldUpdateDependency(release, current, depType, options)) return [];
|
|
461
|
+
const workspace = current.startsWith("workspace:");
|
|
462
|
+
if (!workspace && (options.bumpVersionsWithWorkspaceProtocolOnly || !isValidRange(current))) return [];
|
|
463
|
+
const range = workspace ? current.slice(10) : current;
|
|
464
|
+
if (workspace && (range === "*" || range === "^" || range === "~" || !isValidRange(range))) return [];
|
|
465
|
+
if (isAnyRange(range) && !isPrerelease(newVersion)) return [];
|
|
466
|
+
const next = newDependencyRange(range, newVersion, type);
|
|
467
|
+
return [{
|
|
468
|
+
keys: [depType, name],
|
|
469
|
+
value: workspace ? `workspace:${next}` : next
|
|
470
|
+
}];
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
// src/errors.ts
|
|
474
|
+
/** A `.changeset/*.md` file that changesets itself would also reject. */
|
|
475
|
+
var ChangesetParseError = class extends Schema.TaggedError()("@maple-dev/changesets/ChangesetParseError", {
|
|
476
|
+
message: Schema.String,
|
|
477
|
+
file: Schema.String
|
|
478
|
+
}) {};
|
|
479
|
+
/** `.changeset/config.json` is missing, malformed, or breaks one of the config rules. */
|
|
480
|
+
var ConfigError = class extends Schema.TaggedError()("@maple-dev/changesets/ConfigError", {
|
|
481
|
+
message: Schema.String,
|
|
482
|
+
problems: Schema.Array(Schema.String)
|
|
483
|
+
}) {};
|
|
484
|
+
/** The workspace root or one of its package manifests could not be read. */
|
|
485
|
+
var WorkspaceError = class extends Schema.TaggedError()("@maple-dev/changesets/WorkspaceError", {
|
|
486
|
+
message: Schema.String,
|
|
487
|
+
path: Schema.String,
|
|
488
|
+
cause: Schema.optionalKey(Schema.Defect())
|
|
489
|
+
}) {};
|
|
490
|
+
/** The changesets and the workspace disagree, e.g. a changeset names an unknown package. */
|
|
491
|
+
var ReleasePlanError = class extends Schema.TaggedError()("@maple-dev/changesets/ReleasePlanError", { message: Schema.String }) {};
|
|
492
|
+
var GitError = class extends Schema.TaggedError()("@maple-dev/changesets/GitError", {
|
|
493
|
+
message: Schema.String,
|
|
494
|
+
args: Schema.Array(Schema.String),
|
|
495
|
+
stderr: Schema.String
|
|
496
|
+
}) {};
|
|
497
|
+
var FormatterError = class extends Schema.TaggedError()("@maple-dev/changesets/FormatterError", {
|
|
498
|
+
message: Schema.String,
|
|
499
|
+
formatter: Schema.String,
|
|
500
|
+
stderr: Schema.String
|
|
501
|
+
}) {};
|
|
502
|
+
var JsonEditError = class extends Schema.TaggedError()("@maple-dev/changesets/JsonEditError", {
|
|
503
|
+
message: Schema.String,
|
|
504
|
+
file: Schema.String
|
|
505
|
+
}) {};
|
|
506
|
+
var ChangelogError = class extends Schema.TaggedError()("@maple-dev/changesets/ChangelogError", {
|
|
507
|
+
message: Schema.String,
|
|
508
|
+
module: Schema.String,
|
|
509
|
+
cause: Schema.optionalKey(Schema.Defect())
|
|
510
|
+
}) {};
|
|
511
|
+
/** A changesets feature this tool deliberately does not implement (pre mode, snapshots, commit). */
|
|
512
|
+
var UnsupportedFeatureError = class extends Schema.TaggedError()("@maple-dev/changesets/UnsupportedFeatureError", {
|
|
513
|
+
message: Schema.String,
|
|
514
|
+
feature: Schema.String
|
|
515
|
+
}) {};
|
|
516
|
+
/** An expected non-zero exit that changesets reports the same way, e.g. "No unreleased changesets found." */
|
|
517
|
+
var CommandExit = class extends Schema.TaggedError()("@maple-dev/changesets/CommandExit", {
|
|
518
|
+
message: Schema.String,
|
|
519
|
+
code: Schema.Number
|
|
520
|
+
}) {};
|
|
521
|
+
// src/formatter.ts
|
|
522
|
+
/**
|
|
523
|
+
* `@changesets/format`: with `format: "auto"`, changesets formats the CHANGELOG.md files it
|
|
524
|
+
* wrote (and new changeset files) with the project's formatter, run through the package manager.
|
|
525
|
+
* Detection walks up from the root: config files first, then an installed formatter package.
|
|
526
|
+
*/
|
|
527
|
+
const formatters = {
|
|
528
|
+
dprint: {
|
|
529
|
+
packageName: "dprint",
|
|
530
|
+
configFiles: [
|
|
531
|
+
"dprint.json",
|
|
532
|
+
"dprint.jsonc",
|
|
533
|
+
".dprint.json",
|
|
534
|
+
".dprint.jsonc"
|
|
535
|
+
],
|
|
536
|
+
args: (files) => [
|
|
537
|
+
"dprint",
|
|
538
|
+
"fmt",
|
|
539
|
+
"--allow-no-files",
|
|
540
|
+
...files
|
|
541
|
+
]
|
|
542
|
+
},
|
|
543
|
+
deno: {
|
|
544
|
+
packageName: void 0,
|
|
545
|
+
configFiles: [
|
|
546
|
+
"deno.json",
|
|
547
|
+
"deno.jsonc",
|
|
548
|
+
{
|
|
549
|
+
file: "deno.json",
|
|
550
|
+
key: "fmt"
|
|
551
|
+
}
|
|
552
|
+
],
|
|
553
|
+
args: (files) => [
|
|
554
|
+
"fmt",
|
|
555
|
+
"--permit-no-files",
|
|
556
|
+
...files
|
|
557
|
+
]
|
|
558
|
+
},
|
|
559
|
+
oxfmt: {
|
|
560
|
+
packageName: "oxfmt",
|
|
561
|
+
configFiles: [
|
|
562
|
+
".oxfmtrc.json",
|
|
563
|
+
".oxfmtrc.jsonc",
|
|
564
|
+
"oxfmt.config.ts",
|
|
565
|
+
"oxfmt.config.mts"
|
|
566
|
+
],
|
|
567
|
+
args: (files) => [
|
|
568
|
+
"oxfmt",
|
|
569
|
+
"--write",
|
|
570
|
+
"--no-error-on-unmatched-pattern",
|
|
571
|
+
...files
|
|
572
|
+
]
|
|
573
|
+
},
|
|
574
|
+
prettier: {
|
|
575
|
+
packageName: "prettier",
|
|
576
|
+
configFiles: [
|
|
577
|
+
".prettierrc",
|
|
578
|
+
...[
|
|
579
|
+
"json",
|
|
580
|
+
"yml",
|
|
581
|
+
"yaml",
|
|
582
|
+
"json5",
|
|
583
|
+
"js",
|
|
584
|
+
"ts",
|
|
585
|
+
"mjs",
|
|
586
|
+
"mts",
|
|
587
|
+
"cjs",
|
|
588
|
+
"cts",
|
|
589
|
+
"toml"
|
|
590
|
+
].map((ext) => `.prettierrc.${ext}`),
|
|
591
|
+
...[
|
|
592
|
+
"js",
|
|
593
|
+
"ts",
|
|
594
|
+
"mjs",
|
|
595
|
+
"mts",
|
|
596
|
+
"cjs",
|
|
597
|
+
"cts"
|
|
598
|
+
].map((ext) => `prettier.config.${ext}`),
|
|
599
|
+
{
|
|
600
|
+
file: "package.json",
|
|
601
|
+
key: "prettier"
|
|
602
|
+
}
|
|
603
|
+
],
|
|
604
|
+
args: (files) => [
|
|
605
|
+
"prettier",
|
|
606
|
+
"--write",
|
|
607
|
+
"--no-error-on-unmatched-pattern",
|
|
608
|
+
...files
|
|
609
|
+
]
|
|
610
|
+
}
|
|
611
|
+
};
|
|
612
|
+
const detectOrder = [
|
|
613
|
+
"dprint",
|
|
614
|
+
"deno",
|
|
615
|
+
"oxfmt",
|
|
616
|
+
"prettier"
|
|
617
|
+
];
|
|
618
|
+
const locks = [
|
|
619
|
+
["bun.lock", "bun"],
|
|
620
|
+
["bun.lockb", "bun"],
|
|
621
|
+
["deno.lock", "deno"],
|
|
622
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
623
|
+
["pnpm-workspace.yaml", "pnpm"],
|
|
624
|
+
["yarn.lock", "yarn"],
|
|
625
|
+
["package-lock.json", "npm"],
|
|
626
|
+
["npm-shrinkwrap.json", "npm"]
|
|
627
|
+
];
|
|
628
|
+
const executeLocal = (agent, args) => {
|
|
629
|
+
switch (agent) {
|
|
630
|
+
case "bun": return ["bun", ["x", ...args]];
|
|
631
|
+
case "pnpm": return ["pnpm", ["exec", ...args]];
|
|
632
|
+
case "yarn": return ["yarn", ["exec", ...args]];
|
|
633
|
+
case "deno": return ["deno", [
|
|
634
|
+
"task",
|
|
635
|
+
"--eval",
|
|
636
|
+
...args
|
|
637
|
+
]];
|
|
638
|
+
case "npm": return ["npx", args];
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
const PackageManagerField = Schema.Struct({ packageManager: Schema.optionalKey(Schema.String) });
|
|
642
|
+
const decodeKeys = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Record(Schema.String, Schema.Unknown)));
|
|
643
|
+
const decodePackageManager = Schema.decodeUnknownOption(Schema.fromJsonString(PackageManagerField));
|
|
644
|
+
var Formatter = class extends Context.Service()("@maple-dev/changesets/Formatter", { make: Effect.gen(function* () {
|
|
645
|
+
const fs = yield* FileSystem.FileSystem;
|
|
646
|
+
const path = yield* Path.Path;
|
|
647
|
+
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
648
|
+
const exists = (file) => fs.exists(file).pipe(Effect.orElseSucceed(() => false));
|
|
649
|
+
const readText = (file) => fs.readFileString(file).pipe(Effect.option);
|
|
650
|
+
const ancestors = (dir) => {
|
|
651
|
+
const parent = path.dirname(dir);
|
|
652
|
+
return parent === dir ? [dir] : [dir, ...ancestors(parent)];
|
|
653
|
+
};
|
|
654
|
+
const firstSome = (items, f) => {
|
|
655
|
+
const [head, ...rest] = items;
|
|
656
|
+
if (head === void 0) return Effect.succeed(Option.none());
|
|
657
|
+
return Effect.flatMap(f(head), (found) => Option.isSome(found) ? Effect.succeed(found) : firstSome(rest, f));
|
|
658
|
+
};
|
|
659
|
+
const hasConfig = (dir, configFile) => typeof configFile === "string" ? exists(path.join(dir, configFile)) : Effect.map(readText(path.join(dir, configFile.file)), (text) => Option.isSome(Option.flatMap(text, decodeKeys).pipe(Option.filter((keys) => configFile.key in keys))));
|
|
660
|
+
const byConfig = (dir) => firstSome(detectOrder, (name) => Effect.map(Effect.forEach(formatters[name].configFiles, (configFile) => hasConfig(dir, configFile)), (found) => found.some(Boolean) ? Option.some(name) : Option.none()));
|
|
661
|
+
const byPackage = (dir) => firstSome(detectOrder, (name) => {
|
|
662
|
+
const packageName = formatters[name].packageName;
|
|
663
|
+
return packageName === void 0 ? Effect.succeed(Option.none()) : Effect.map(exists(path.join(dir, "node_modules", packageName)), (found) => found ? Option.some(name) : Option.none());
|
|
664
|
+
});
|
|
665
|
+
const detect = Effect.fn("Formatter.detect")(function* (format, rootDir) {
|
|
666
|
+
if (format === false) return Option.none();
|
|
667
|
+
if (format !== "auto") return Option.some(format);
|
|
668
|
+
const dirs = ancestors(path.resolve(rootDir));
|
|
669
|
+
const configured = yield* firstSome(dirs, byConfig);
|
|
670
|
+
return Option.isSome(configured) ? configured : yield* firstSome(dirs, byPackage);
|
|
671
|
+
});
|
|
672
|
+
const packageManagerIn = (dir) => Effect.map(readText(path.join(dir, "package.json")), (text) => Option.flatMap(Option.flatMap(text, decodePackageManager), (json) => Option.fromNullishOr(json.packageManager?.split("@")[0]).pipe(Option.filter((agent) => [
|
|
673
|
+
"bun",
|
|
674
|
+
"deno",
|
|
675
|
+
"pnpm",
|
|
676
|
+
"yarn",
|
|
677
|
+
"npm"
|
|
678
|
+
].includes(agent)))));
|
|
679
|
+
const agentFor = (rootDir) => Effect.map(firstSome(ancestors(path.resolve(rootDir)), (dir) => Effect.gen(function* () {
|
|
680
|
+
const lock = yield* firstSome(locks.map(([file]) => file), (file) => Effect.map(exists(path.join(dir, file)), (found) => found ? Option.some(file) : Option.none()));
|
|
681
|
+
const declared = yield* packageManagerIn(dir);
|
|
682
|
+
if (Option.isSome(lock)) {
|
|
683
|
+
const fromLock = locks.find(([file]) => file === lock.value)?.[1] ?? "npm";
|
|
684
|
+
return Option.some(Option.getOrElse(declared, () => fromLock));
|
|
685
|
+
}
|
|
686
|
+
return declared;
|
|
687
|
+
})), Option.getOrElse(() => "npm"));
|
|
688
|
+
return {
|
|
689
|
+
detect,
|
|
690
|
+
format: Effect.fn("Formatter.format")(function* (config, rootDir, files) {
|
|
691
|
+
const name = yield* detect(config, rootDir);
|
|
692
|
+
if (Option.isNone(name) || files.length === 0) return;
|
|
693
|
+
const spec = formatters[name.value];
|
|
694
|
+
const [command, args] = name.value === "deno" ? ["deno", spec.args(files)] : executeLocal(yield* agentFor(rootDir), spec.args(files));
|
|
695
|
+
const result = yield* Effect.scoped(Effect.gen(function* () {
|
|
696
|
+
const handle = yield* spawner.spawn(ChildProcess.make(command, [...args], { cwd: rootDir }));
|
|
697
|
+
const [, stderr, exitCode] = yield* Effect.all([
|
|
698
|
+
Stream.runDrain(handle.stdout),
|
|
699
|
+
Stream.mkString(Stream.decodeText(handle.stderr)),
|
|
700
|
+
handle.exitCode
|
|
701
|
+
], { concurrency: "unbounded" });
|
|
702
|
+
return {
|
|
703
|
+
stderr,
|
|
704
|
+
exitCode: Number(exitCode)
|
|
705
|
+
};
|
|
706
|
+
})).pipe(Effect.mapError((cause) => new FormatterError({
|
|
707
|
+
message: `Could not run ${command}: ${cause.message}`,
|
|
708
|
+
formatter: name.value,
|
|
709
|
+
stderr: ""
|
|
710
|
+
})));
|
|
711
|
+
if (result.exitCode !== 0) return yield* new FormatterError({
|
|
712
|
+
message: `${[command, ...args.slice(0, 2)].join(" ")} exited with code ${result.exitCode}`,
|
|
713
|
+
formatter: name.value,
|
|
714
|
+
stderr: result.stderr
|
|
715
|
+
});
|
|
716
|
+
})
|
|
717
|
+
};
|
|
718
|
+
}) }) {
|
|
719
|
+
static layer = Layer.effect(this, this.make);
|
|
720
|
+
};
|
|
721
|
+
// src/frontmatter.ts
|
|
722
|
+
/**
|
|
723
|
+
* `@changesets/parse` without a YAML dependency. The frontmatter grammar covers what changesets
|
|
724
|
+
* accepts in practice: a block or flow mapping of plain, single- or double-quoted package names to
|
|
725
|
+
* release types, with comments and folded values. Anything else is reported as invalid YAML.
|
|
726
|
+
*/
|
|
727
|
+
const isSpace = (char) => /^\s$/.test(char);
|
|
728
|
+
/** For each index, the first index at or after it whose character is not whitespace (or `length`). */
|
|
729
|
+
const nextNonSpaceIndex = (text) => Array.from({ length: text.length }, (_, i) => i).reduceRight((next, i) => {
|
|
730
|
+
next[i] = isSpace(text.charAt(i)) ? next[i + 1] ?? text.length : i;
|
|
731
|
+
return next;
|
|
732
|
+
}, Array(text.length + 1).fill(text.length));
|
|
733
|
+
/**
|
|
734
|
+
* `@changesets/parse`'s `/\s*---([^]*?)\r?\n\s*---(\s*(?:\n|$)[^]*)/` as one linear scan.
|
|
735
|
+
* Only the first `---` can open a match: a later one has a subset of the same closing lines.
|
|
736
|
+
* The closing line is the first `\n` (or `\r\n`) then optional whitespace then `---`, followed
|
|
737
|
+
* by whitespace that holds a newline or runs to the end.
|
|
738
|
+
*/
|
|
739
|
+
const splitFrontmatter = (text) => {
|
|
740
|
+
const opener = text.indexOf("---");
|
|
741
|
+
if (opener === -1) return void 0;
|
|
742
|
+
const start = opener + 3;
|
|
743
|
+
const nextNonSpace = nextNonSpaceIndex(text);
|
|
744
|
+
const closes = (e) => {
|
|
745
|
+
const lineStart = text.startsWith("\r\n", e) ? e + 2 : text.charAt(e) === "\n" ? e + 1 : -1;
|
|
746
|
+
if (lineStart === -1) return void 0;
|
|
747
|
+
const dashes = nextNonSpace[lineStart] ?? text.length;
|
|
748
|
+
if (!text.startsWith("---", dashes)) return void 0;
|
|
749
|
+
const after = dashes + 3;
|
|
750
|
+
const end = nextNonSpace[after] ?? text.length;
|
|
751
|
+
const newline = text.indexOf("\n", after);
|
|
752
|
+
return end === text.length || newline !== -1 && newline < end ? after : void 0;
|
|
753
|
+
};
|
|
754
|
+
const close = Array.from({ length: Math.max(text.length - start, 0) }, (_, i) => start + i).find((e) => closes(e) !== void 0);
|
|
755
|
+
if (close === void 0) return void 0;
|
|
756
|
+
return {
|
|
757
|
+
releases: text.slice(start, close),
|
|
758
|
+
summary: text.slice(closes(close))
|
|
759
|
+
};
|
|
760
|
+
};
|
|
761
|
+
const EXAMPLE_FORMAT = `---\n"package-name": patch\n---`;
|
|
762
|
+
const validVersionTypes = [
|
|
763
|
+
"major",
|
|
764
|
+
"minor",
|
|
765
|
+
"patch",
|
|
766
|
+
"none"
|
|
767
|
+
];
|
|
768
|
+
const truncate = (s, max = 200) => s.length > max ? s.slice(0, max) + "..." : s;
|
|
769
|
+
const resolvePlain = (text) => {
|
|
770
|
+
if (/^(~|null|Null|NULL)?$/.test(text)) return null;
|
|
771
|
+
if (/^(true|True|TRUE)$/.test(text)) return true;
|
|
772
|
+
if (/^(false|False|FALSE)$/.test(text)) return false;
|
|
773
|
+
if (/^[-+]?[0-9]+$/.test(text)) return Number.parseInt(text, 10);
|
|
774
|
+
if (/^0o[0-7]+$/.test(text)) return Number.parseInt(text.slice(2), 8);
|
|
775
|
+
if (/^0x[0-9a-fA-F]+$/.test(text)) return Number.parseInt(text.slice(2), 16);
|
|
776
|
+
if (/^[-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?$/.test(text)) return Number.parseFloat(text);
|
|
777
|
+
if (/^[-+]?\.(inf|Inf|INF)$/.test(text)) return text.startsWith("-") ? -Infinity : Infinity;
|
|
778
|
+
if (/^\.(nan|NaN|NAN)$/.test(text)) return NaN;
|
|
779
|
+
return text;
|
|
780
|
+
};
|
|
781
|
+
const escapes = /* @__PURE__ */ new Map([
|
|
782
|
+
["0", "\0"],
|
|
783
|
+
["a", "\x07"],
|
|
784
|
+
["b", "\b"],
|
|
785
|
+
["t", " "],
|
|
786
|
+
[" ", " "],
|
|
787
|
+
["n", "\n"],
|
|
788
|
+
["v", "\v"],
|
|
789
|
+
["f", "\f"],
|
|
790
|
+
["r", "\r"],
|
|
791
|
+
["e", "\x1B"],
|
|
792
|
+
[" ", " "],
|
|
793
|
+
["\"", "\""],
|
|
794
|
+
["/", "/"],
|
|
795
|
+
["\\", "\\"],
|
|
796
|
+
["N", "Ā
"],
|
|
797
|
+
["_", "\xA0"],
|
|
798
|
+
["L", "\u2028"],
|
|
799
|
+
["P", "\u2029"]
|
|
800
|
+
]);
|
|
801
|
+
/** Reads a quoted scalar starting at `text[0]`; returns the value and the rest of the line. */
|
|
802
|
+
const readQuoted = (text) => {
|
|
803
|
+
const quote = text[0];
|
|
804
|
+
const chars = Array.from(text.slice(1));
|
|
805
|
+
const read = (index, value) => {
|
|
806
|
+
const char = chars[index];
|
|
807
|
+
if (char === void 0) return Result.fail("Missing closing quote");
|
|
808
|
+
if (quote === "'") {
|
|
809
|
+
if (char !== "'") return read(index + 1, value + char);
|
|
810
|
+
return chars[index + 1] === "'" ? read(index + 2, value + "'") : Result.succeed({
|
|
811
|
+
value,
|
|
812
|
+
rest: chars.slice(index + 1).join("")
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
if (char === "\"") return Result.succeed({
|
|
816
|
+
value,
|
|
817
|
+
rest: chars.slice(index + 1).join("")
|
|
818
|
+
});
|
|
819
|
+
if (char !== "\\") return read(index + 1, value + char);
|
|
820
|
+
const escape = chars[index + 1] ?? "";
|
|
821
|
+
const hexLength = escape === "x" ? 2 : escape === "u" ? 4 : escape === "U" ? 8 : 0;
|
|
822
|
+
if (hexLength > 0) {
|
|
823
|
+
const hex = chars.slice(index + 2, index + 2 + hexLength).join("");
|
|
824
|
+
return new RegExp(`^[0-9a-fA-F]{${hexLength}}$`).test(hex) ? read(index + 2 + hexLength, value + String.fromCodePoint(Number.parseInt(hex, 16))) : Result.fail("Invalid escape sequence");
|
|
825
|
+
}
|
|
826
|
+
const replacement = escapes.get(escape);
|
|
827
|
+
return replacement === void 0 ? Result.fail(`Invalid escape sequence \\${escape}`) : read(index + 2, value + replacement);
|
|
828
|
+
};
|
|
829
|
+
return read(0, "");
|
|
830
|
+
};
|
|
831
|
+
const stripComment = (text) => {
|
|
832
|
+
const at = text.search(/(?:^|\s)#/);
|
|
833
|
+
return (at === -1 ? text : text.slice(0, at)).trim();
|
|
834
|
+
};
|
|
835
|
+
const isQuote = (text) => text.startsWith("\"") || text.startsWith("'");
|
|
836
|
+
const readValue = (raw) => {
|
|
837
|
+
const text = raw.trim();
|
|
838
|
+
if (isQuote(text)) return Result.flatMap(readQuoted(text), ({ value, rest }) => stripComment(rest) === "" ? Result.succeed(value) : Result.fail("Unexpected content after a quoted scalar"));
|
|
839
|
+
if (text.startsWith("[") || text.startsWith("{")) return Result.succeed("object");
|
|
840
|
+
if (/^[&*!|>%@`]/.test(text)) return Result.fail(`Unsupported YAML syntax: ${truncate(text, 40)}`);
|
|
841
|
+
const plain = stripComment(text);
|
|
842
|
+
return /:(\s|$)/.test(plain) ? Result.fail("Nested mappings are not allowed in compact mappings") : Result.succeed(resolvePlain(plain));
|
|
843
|
+
};
|
|
844
|
+
/** Reads `key:` at the start of `line`; `undefined` when the line is not a mapping entry. */
|
|
845
|
+
const readKey = (line) => {
|
|
846
|
+
if (isQuote(line)) return Result.map(readQuoted(line), ({ value, rest }) => {
|
|
847
|
+
const colon = /^\s*:(\s|$)/.exec(rest);
|
|
848
|
+
return colon === null ? void 0 : {
|
|
849
|
+
key: value,
|
|
850
|
+
rest: rest.slice(colon[0].length)
|
|
851
|
+
};
|
|
852
|
+
});
|
|
853
|
+
const colon = /:(\s|$)/.exec(line);
|
|
854
|
+
if (colon === null) return Result.succeed(void 0);
|
|
855
|
+
const key = line.slice(0, colon.index).trim();
|
|
856
|
+
if (/^[&*!|>%@`[\]{},#]/.test(key) || /^[-?:]\s/.test(key)) return Result.fail(`Plain value cannot start with reserved character ${key[0]}`);
|
|
857
|
+
const resolved = resolvePlain(key);
|
|
858
|
+
return Result.succeed({
|
|
859
|
+
key: resolved === null ? "" : String(resolved),
|
|
860
|
+
rest: line.slice(colon.index + 1)
|
|
861
|
+
});
|
|
862
|
+
};
|
|
863
|
+
const readFlowValue = (text) => {
|
|
864
|
+
if (isQuote(text)) return Result.map(readQuoted(text), ({ value, rest }) => ({
|
|
865
|
+
value,
|
|
866
|
+
after: rest
|
|
867
|
+
}));
|
|
868
|
+
const comma = text.indexOf(",");
|
|
869
|
+
const end = comma === -1 ? text.length : comma;
|
|
870
|
+
return Result.succeed({
|
|
871
|
+
value: resolvePlain(text.slice(0, end).trim()),
|
|
872
|
+
after: text.slice(end)
|
|
873
|
+
});
|
|
874
|
+
};
|
|
875
|
+
const parseFlowEntries = (rest, entries) => Result.gen(function* () {
|
|
876
|
+
const trimmed = rest.trimStart();
|
|
877
|
+
if (trimmed === "") return entries;
|
|
878
|
+
const keyed = yield* readKey(trimmed.replace(/^([^"',]*?):(?=[^\s])/, "$1: "));
|
|
879
|
+
if (keyed === void 0) return yield* Result.fail("Expected a key in the flow mapping");
|
|
880
|
+
const { value, after } = yield* readFlowValue(keyed.rest.trimStart());
|
|
881
|
+
const tail = after.trimStart();
|
|
882
|
+
if (tail !== "" && !tail.startsWith(",")) return yield* Result.fail("Expected , in the flow mapping");
|
|
883
|
+
return yield* parseFlowEntries(tail.slice(1), [...entries, {
|
|
884
|
+
key: keyed.key,
|
|
885
|
+
value
|
|
886
|
+
}]);
|
|
887
|
+
});
|
|
888
|
+
const parseFlowMapping = (text) => {
|
|
889
|
+
const body = text.trim();
|
|
890
|
+
return body.endsWith("}") ? parseFlowEntries(body.slice(1, -1), []) : Result.fail("Unterminated flow mapping");
|
|
891
|
+
};
|
|
892
|
+
const indentOf = (line) => /^[ \t]*/.exec(line)?.[0].length ?? 0;
|
|
893
|
+
const readNestedValue = (nested) => Result.gen(function* () {
|
|
894
|
+
const first = nested[0]?.trim() ?? "";
|
|
895
|
+
if (first.startsWith("- ") || (yield* readKey(first)) !== void 0) return "object";
|
|
896
|
+
return yield* readValue(nested.map((line) => line.trim()).join(" "));
|
|
897
|
+
});
|
|
898
|
+
const parseBlockMapping = (lines) => {
|
|
899
|
+
const baseIndent = indentOf(lines[0] ?? "");
|
|
900
|
+
const step = (index, entries) => Result.gen(function* () {
|
|
901
|
+
const line = lines[index];
|
|
902
|
+
if (line === void 0) return entries;
|
|
903
|
+
if (indentOf(line) !== baseIndent) return yield* Result.fail("Bad indentation of a mapping entry");
|
|
904
|
+
const keyed = yield* readKey(line.trim());
|
|
905
|
+
if (keyed === void 0) return yield* Result.fail("Implicit map keys need to be followed by map values");
|
|
906
|
+
const end = lines.slice(index + 1).findIndex((next) => indentOf(next) <= baseIndent);
|
|
907
|
+
const nested = lines.slice(index + 1, end === -1 ? lines.length : index + 1 + end);
|
|
908
|
+
const inline = stripComment(keyed.rest) === "" ? "" : keyed.rest;
|
|
909
|
+
if (inline !== "" && nested.length > 0) return yield* Result.fail("Bad indentation of a mapping entry");
|
|
910
|
+
const value = nested.length === 0 ? yield* readValue(inline) : yield* readNestedValue(nested);
|
|
911
|
+
return yield* step(index + 1 + nested.length, [...entries, {
|
|
912
|
+
key: keyed.key,
|
|
913
|
+
value
|
|
914
|
+
}]);
|
|
915
|
+
});
|
|
916
|
+
return step(0, []);
|
|
917
|
+
};
|
|
918
|
+
const parseDocument$1 = (text) => Result.gen(function* () {
|
|
919
|
+
const lines = text.split(/\r?\n/).map((line) => line.replace(/\r$/, "")).filter((line) => line.trim() !== "" && !line.trim().startsWith("#"));
|
|
920
|
+
const first = lines[0]?.trim();
|
|
921
|
+
if (first === void 0) return { _tag: "Empty" };
|
|
922
|
+
if (first.startsWith("{")) return {
|
|
923
|
+
_tag: "Map",
|
|
924
|
+
entries: yield* parseFlowMapping(lines.map(stripComment).join(" "))
|
|
925
|
+
};
|
|
926
|
+
if (first.startsWith("- ") || first === "-" || first.startsWith("[")) return { _tag: "Other" };
|
|
927
|
+
if (lines.length === 1 && (yield* readKey(first)) === void 0) {
|
|
928
|
+
yield* readValue(first);
|
|
929
|
+
return { _tag: "Other" };
|
|
930
|
+
}
|
|
931
|
+
return {
|
|
932
|
+
_tag: "Map",
|
|
933
|
+
entries: yield* parseBlockMapping(lines)
|
|
934
|
+
};
|
|
935
|
+
});
|
|
936
|
+
const typeName = (value) => value === null || value === "object" ? "object" : typeof value;
|
|
937
|
+
const validateReleases = (entries, contents) => {
|
|
938
|
+
const [first] = entries.flatMap((entry) => {
|
|
939
|
+
if (entry.key.trim() === "") return [`could not parse changeset - invalid package name in frontmatter.\nExpected a non-empty string for package name, but got: ${JSON.stringify(entry.key)}\nChangeset contents:\n${truncate(contents)}`];
|
|
940
|
+
if (typeof entry.value !== "string" || entry.value === "object") return [`could not parse changeset - invalid release type for package "${entry.key}".\nExpected a string for release type, but got: ${typeName(entry.value)}\nChangeset contents:\n${truncate(contents)}`];
|
|
941
|
+
if (!validVersionTypes.includes(entry.value)) return [`could not parse changeset - invalid version type ${JSON.stringify(entry.value)} for package "${entry.key}".\nValid version types are: ${validVersionTypes.join(", ")}\nChangeset contents:\n${truncate(contents)}`];
|
|
942
|
+
return [];
|
|
943
|
+
});
|
|
944
|
+
if (first !== void 0) return Result.fail(first);
|
|
945
|
+
return Result.succeed(entries.flatMap((entry) => entry.value === "major" || entry.value === "minor" || entry.value === "patch" || entry.value === "none" ? [{
|
|
946
|
+
name: entry.key,
|
|
947
|
+
type: entry.value
|
|
948
|
+
}] : []));
|
|
949
|
+
};
|
|
950
|
+
const readDocument = (yaml) => Result.flatMap(parseDocument$1(yaml), (document) => {
|
|
951
|
+
if (document._tag !== "Map") return Result.succeed(document);
|
|
952
|
+
const keys = document.entries.map((entry) => entry.key);
|
|
953
|
+
return new Set(keys).size === keys.length ? Result.succeed(document) : Result.fail("Map keys must be unique");
|
|
954
|
+
});
|
|
955
|
+
/** Parses one changeset file; the failure is the message changesets would print. */
|
|
956
|
+
const parseChangesetFile = (contents) => {
|
|
957
|
+
const trimmedContents = contents.trim();
|
|
958
|
+
if (!trimmedContents) return Result.fail(`could not parse changeset - file is empty.\nChangesets must have frontmatter with package names and version types.\nExample:\n${EXAMPLE_FORMAT}\n\nYour changeset summary here.`);
|
|
959
|
+
const split = splitFrontmatter(contents);
|
|
960
|
+
if (split === void 0) return Result.fail(`could not parse changeset - missing or invalid frontmatter.\nChangesets must start with frontmatter delimited by "---".\nExample:\n${EXAMPLE_FORMAT}\n\nYour changeset summary here.\nReceived content:\n${truncate(trimmedContents)}`);
|
|
961
|
+
const { releases: roughReleases, summary: roughSummary } = split;
|
|
962
|
+
const summary = roughSummary.trim();
|
|
963
|
+
const document = readDocument(roughReleases);
|
|
964
|
+
if (Result.isFailure(document)) return Result.fail(`could not parse changeset - invalid YAML in frontmatter.\nThe frontmatter between the "---" delimiters must be valid YAML.\nYAML error: ${document.failure}\nFrontmatter content:\n${roughReleases}`);
|
|
965
|
+
if (document.success._tag === "Other") return Result.fail(`could not parse changeset - frontmatter must be an object mapping package names to version types.\nExpected format:\n${EXAMPLE_FORMAT}\nReceived:\n${roughReleases}`);
|
|
966
|
+
const entries = document.success._tag === "Empty" ? [] : document.success.entries;
|
|
967
|
+
return Result.map(validateReleases(entries, contents), (releases) => ({
|
|
968
|
+
releases,
|
|
969
|
+
summary
|
|
970
|
+
}));
|
|
971
|
+
};
|
|
972
|
+
// src/git.ts
|
|
973
|
+
/** The git queries changesets makes (`@changesets/git`), over `effect/process`. */
|
|
974
|
+
var Git = class extends Context.Service()("@maple-dev/changesets/Git", { make: Effect.gen(function* () {
|
|
975
|
+
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
976
|
+
const fs = yield* FileSystem.FileSystem;
|
|
977
|
+
const path = yield* Path.Path;
|
|
978
|
+
const run = (args, cwd) => Effect.scoped(Effect.gen(function* () {
|
|
979
|
+
const handle = yield* spawner.spawn(ChildProcess.make("git", [...args], { cwd }));
|
|
980
|
+
const [stdout, stderr, exitCode] = yield* Effect.all([
|
|
981
|
+
Stream.mkString(Stream.decodeText(handle.stdout)),
|
|
982
|
+
Stream.mkString(Stream.decodeText(handle.stderr)),
|
|
983
|
+
handle.exitCode
|
|
984
|
+
], { concurrency: "unbounded" });
|
|
985
|
+
return {
|
|
986
|
+
exitCode: Number(exitCode),
|
|
987
|
+
stdout,
|
|
988
|
+
stderr
|
|
989
|
+
};
|
|
990
|
+
})).pipe(Effect.mapError((cause) => new GitError({
|
|
991
|
+
message: `Could not run git: ${cause.message}`,
|
|
992
|
+
args: [...args],
|
|
993
|
+
stderr: ""
|
|
994
|
+
})));
|
|
995
|
+
const runOk = (args, cwd, message) => Effect.flatMap(run(args, cwd), (result) => result.exitCode === 0 ? Effect.succeed(result.stdout) : Effect.fail(new GitError({
|
|
996
|
+
message,
|
|
997
|
+
args: [...args],
|
|
998
|
+
stderr: result.stderr
|
|
999
|
+
})));
|
|
1000
|
+
const isShallow = Effect.fn("Git.isShallow")(function* (cwd) {
|
|
1001
|
+
const output = (yield* run(["rev-parse", "--is-shallow-repository"], cwd)).stdout.trim();
|
|
1002
|
+
if (output !== "--is-shallow-repository") return output === "true";
|
|
1003
|
+
const gitDir = (yield* run(["rev-parse", "--git-dir"], cwd)).stdout.trim();
|
|
1004
|
+
return yield* fs.exists(path.join(path.resolve(cwd, gitDir), "shallow")).pipe(Effect.orElseSucceed(() => false));
|
|
1005
|
+
});
|
|
1006
|
+
const lookup = (paths, cwd, found) => Effect.gen(function* () {
|
|
1007
|
+
const infos = yield* Effect.forEach(paths, (gitPath) => Effect.map(run([
|
|
1008
|
+
"log",
|
|
1009
|
+
"--diff-filter=AC",
|
|
1010
|
+
"--follow",
|
|
1011
|
+
"--max-count=1",
|
|
1012
|
+
"--pretty=format:%H:%p",
|
|
1013
|
+
gitPath
|
|
1014
|
+
], cwd), ({ stdout }) => {
|
|
1015
|
+
const [commitSha = "", parentSha = ""] = stdout.split(":");
|
|
1016
|
+
return {
|
|
1017
|
+
gitPath,
|
|
1018
|
+
commitSha,
|
|
1019
|
+
parentSha
|
|
1020
|
+
};
|
|
1021
|
+
}), { concurrency: 8 });
|
|
1022
|
+
const resolved = new Map(found);
|
|
1023
|
+
infos.forEach((info) => {
|
|
1024
|
+
if (info.commitSha && info.parentSha) resolved.set(info.gitPath, info.commitSha);
|
|
1025
|
+
});
|
|
1026
|
+
const missingParents = infos.filter((info) => info.commitSha && !info.parentSha);
|
|
1027
|
+
if (missingParents.length === 0) return resolved;
|
|
1028
|
+
if (yield* isShallow(cwd)) {
|
|
1029
|
+
yield* runOk(["fetch", "--deepen=50"], cwd, "Could not deepen the shallow clone");
|
|
1030
|
+
return yield* lookup(missingParents.map((info) => info.gitPath), cwd, resolved);
|
|
1031
|
+
}
|
|
1032
|
+
missingParents.forEach((info) => resolved.set(info.gitPath, info.commitSha));
|
|
1033
|
+
return resolved;
|
|
1034
|
+
});
|
|
1035
|
+
const commitsThatAddFiles = Effect.fn("Git.commitsThatAddFiles")(function* (paths, cwd) {
|
|
1036
|
+
const commits = yield* lookup(paths, cwd, /* @__PURE__ */ new Map());
|
|
1037
|
+
return paths.map((gitPath) => commits.get(gitPath));
|
|
1038
|
+
});
|
|
1039
|
+
const divergedCommit = (ref, cwd) => Effect.map(runOk([
|
|
1040
|
+
"merge-base",
|
|
1041
|
+
ref,
|
|
1042
|
+
"HEAD"
|
|
1043
|
+
], cwd, `Failed to find where HEAD diverged from "${ref}". Does "${ref}" exist and it's synced with remote?`), (stdout) => stdout.trim());
|
|
1044
|
+
return {
|
|
1045
|
+
run,
|
|
1046
|
+
commitsThatAddFiles,
|
|
1047
|
+
changedFilesSince: Effect.fn("Git.changedFilesSince")(function* (ref, cwd) {
|
|
1048
|
+
const diverged = yield* divergedCommit(ref, cwd);
|
|
1049
|
+
const files = (yield* runOk([
|
|
1050
|
+
"diff",
|
|
1051
|
+
"--name-only",
|
|
1052
|
+
"--no-relative",
|
|
1053
|
+
diverged
|
|
1054
|
+
], cwd, `Failed to diff against ${diverged}. Is ${diverged} a valid ref?`)).trim().split("\n").filter((file) => file);
|
|
1055
|
+
const cdup = (yield* runOk(["rev-parse", "--show-cdup"], cwd, "Could not find the git root")).trim();
|
|
1056
|
+
const repoRoot = path.resolve(cwd, cdup.replace(/\n|\r/g, ""));
|
|
1057
|
+
return files.map((file) => path.resolve(repoRoot, file));
|
|
1058
|
+
}),
|
|
1059
|
+
changedChangesetFilesSince: Effect.fn("Git.changedChangesetFilesSince")(function* (ref, cwd) {
|
|
1060
|
+
const diverged = yield* divergedCommit(ref, cwd);
|
|
1061
|
+
return (yield* runOk([
|
|
1062
|
+
"diff",
|
|
1063
|
+
"--name-only",
|
|
1064
|
+
"--diff-filter=d",
|
|
1065
|
+
"--no-relative",
|
|
1066
|
+
diverged
|
|
1067
|
+
], cwd, `Failed to diff against ${diverged}`)).trim().split("\n").filter((file) => /\.changeset\/[^/]+\.md$/.test(file) || /\.changeset\/pre\/[^/]+\.md$/.test(file));
|
|
1068
|
+
})
|
|
1069
|
+
};
|
|
1070
|
+
}) }) {
|
|
1071
|
+
static layer = Layer.effect(this, this.make);
|
|
1072
|
+
};
|
|
1073
|
+
// src/human-id.ts
|
|
1074
|
+
/** `adjective-nouns-verb` file names in the style of `human-id`, which changesets uses. */
|
|
1075
|
+
const adjectives = [
|
|
1076
|
+
"brave",
|
|
1077
|
+
"bright",
|
|
1078
|
+
"calm",
|
|
1079
|
+
"clean",
|
|
1080
|
+
"clever",
|
|
1081
|
+
"cool",
|
|
1082
|
+
"cuddly",
|
|
1083
|
+
"curly",
|
|
1084
|
+
"cute",
|
|
1085
|
+
"dirty",
|
|
1086
|
+
"dry",
|
|
1087
|
+
"eager",
|
|
1088
|
+
"early",
|
|
1089
|
+
"easy",
|
|
1090
|
+
"empty",
|
|
1091
|
+
"fair",
|
|
1092
|
+
"fancy",
|
|
1093
|
+
"fast",
|
|
1094
|
+
"fluffy",
|
|
1095
|
+
"free",
|
|
1096
|
+
"fresh",
|
|
1097
|
+
"friendly",
|
|
1098
|
+
"funny",
|
|
1099
|
+
"fuzzy",
|
|
1100
|
+
"gentle",
|
|
1101
|
+
"giant",
|
|
1102
|
+
"good",
|
|
1103
|
+
"great",
|
|
1104
|
+
"green",
|
|
1105
|
+
"happy",
|
|
1106
|
+
"heavy",
|
|
1107
|
+
"honest",
|
|
1108
|
+
"hot",
|
|
1109
|
+
"huge",
|
|
1110
|
+
"kind",
|
|
1111
|
+
"large",
|
|
1112
|
+
"lazy",
|
|
1113
|
+
"light",
|
|
1114
|
+
"little",
|
|
1115
|
+
"long",
|
|
1116
|
+
"loud",
|
|
1117
|
+
"lovely",
|
|
1118
|
+
"lucky",
|
|
1119
|
+
"mean",
|
|
1120
|
+
"modern",
|
|
1121
|
+
"neat",
|
|
1122
|
+
"nice",
|
|
1123
|
+
"odd",
|
|
1124
|
+
"old",
|
|
1125
|
+
"plenty",
|
|
1126
|
+
"polite",
|
|
1127
|
+
"proud",
|
|
1128
|
+
"purple",
|
|
1129
|
+
"quick",
|
|
1130
|
+
"quiet",
|
|
1131
|
+
"rare",
|
|
1132
|
+
"red",
|
|
1133
|
+
"rich",
|
|
1134
|
+
"rotten",
|
|
1135
|
+
"rude",
|
|
1136
|
+
"shaggy",
|
|
1137
|
+
"sharp",
|
|
1138
|
+
"shiny",
|
|
1139
|
+
"short",
|
|
1140
|
+
"shy",
|
|
1141
|
+
"silent",
|
|
1142
|
+
"silly",
|
|
1143
|
+
"slimy",
|
|
1144
|
+
"slow",
|
|
1145
|
+
"small",
|
|
1146
|
+
"smart",
|
|
1147
|
+
"smooth",
|
|
1148
|
+
"soft",
|
|
1149
|
+
"sour",
|
|
1150
|
+
"spicy",
|
|
1151
|
+
"strong",
|
|
1152
|
+
"sweet",
|
|
1153
|
+
"tall",
|
|
1154
|
+
"tame",
|
|
1155
|
+
"tasty",
|
|
1156
|
+
"thick",
|
|
1157
|
+
"thin",
|
|
1158
|
+
"tidy",
|
|
1159
|
+
"tiny",
|
|
1160
|
+
"tough",
|
|
1161
|
+
"twelve",
|
|
1162
|
+
"violet",
|
|
1163
|
+
"warm",
|
|
1164
|
+
"weak",
|
|
1165
|
+
"wet",
|
|
1166
|
+
"whole",
|
|
1167
|
+
"wicked",
|
|
1168
|
+
"wide",
|
|
1169
|
+
"wild",
|
|
1170
|
+
"wise",
|
|
1171
|
+
"witty",
|
|
1172
|
+
"young"
|
|
1173
|
+
];
|
|
1174
|
+
const nouns = [
|
|
1175
|
+
"ants",
|
|
1176
|
+
"apes",
|
|
1177
|
+
"apples",
|
|
1178
|
+
"bags",
|
|
1179
|
+
"bananas",
|
|
1180
|
+
"bars",
|
|
1181
|
+
"baths",
|
|
1182
|
+
"beans",
|
|
1183
|
+
"bears",
|
|
1184
|
+
"beds",
|
|
1185
|
+
"bees",
|
|
1186
|
+
"berries",
|
|
1187
|
+
"bikes",
|
|
1188
|
+
"birds",
|
|
1189
|
+
"boats",
|
|
1190
|
+
"bobcats",
|
|
1191
|
+
"books",
|
|
1192
|
+
"boxes",
|
|
1193
|
+
"buckets",
|
|
1194
|
+
"bugs",
|
|
1195
|
+
"buses",
|
|
1196
|
+
"buttons",
|
|
1197
|
+
"cameras",
|
|
1198
|
+
"candles",
|
|
1199
|
+
"carrots",
|
|
1200
|
+
"cars",
|
|
1201
|
+
"cats",
|
|
1202
|
+
"chairs",
|
|
1203
|
+
"cheetahs",
|
|
1204
|
+
"chefs",
|
|
1205
|
+
"cherries",
|
|
1206
|
+
"chicken",
|
|
1207
|
+
"clocks",
|
|
1208
|
+
"clouds",
|
|
1209
|
+
"coats",
|
|
1210
|
+
"coins",
|
|
1211
|
+
"cooks",
|
|
1212
|
+
"cougars",
|
|
1213
|
+
"cows",
|
|
1214
|
+
"crabs",
|
|
1215
|
+
"crews",
|
|
1216
|
+
"cups",
|
|
1217
|
+
"dancers",
|
|
1218
|
+
"deer",
|
|
1219
|
+
"dodos",
|
|
1220
|
+
"dogs",
|
|
1221
|
+
"dolls",
|
|
1222
|
+
"donkeys",
|
|
1223
|
+
"doors",
|
|
1224
|
+
"dots",
|
|
1225
|
+
"dragons",
|
|
1226
|
+
"drinks",
|
|
1227
|
+
"ducks",
|
|
1228
|
+
"eagles",
|
|
1229
|
+
"eels",
|
|
1230
|
+
"eggs",
|
|
1231
|
+
"experts",
|
|
1232
|
+
"falcons",
|
|
1233
|
+
"fans",
|
|
1234
|
+
"feet",
|
|
1235
|
+
"files",
|
|
1236
|
+
"fireants",
|
|
1237
|
+
"fish",
|
|
1238
|
+
"flies",
|
|
1239
|
+
"flowers",
|
|
1240
|
+
"forks",
|
|
1241
|
+
"foxes",
|
|
1242
|
+
"frogs",
|
|
1243
|
+
"garlics",
|
|
1244
|
+
"geckos",
|
|
1245
|
+
"geese",
|
|
1246
|
+
"ghosts",
|
|
1247
|
+
"gifts",
|
|
1248
|
+
"goats",
|
|
1249
|
+
"grapes",
|
|
1250
|
+
"guests",
|
|
1251
|
+
"hairs",
|
|
1252
|
+
"hats",
|
|
1253
|
+
"hornets",
|
|
1254
|
+
"horses",
|
|
1255
|
+
"houses",
|
|
1256
|
+
"icons",
|
|
1257
|
+
"insects",
|
|
1258
|
+
"islands",
|
|
1259
|
+
"jars",
|
|
1260
|
+
"jeans",
|
|
1261
|
+
"jobs",
|
|
1262
|
+
"keys",
|
|
1263
|
+
"kids",
|
|
1264
|
+
"kings",
|
|
1265
|
+
"kiwis",
|
|
1266
|
+
"knives",
|
|
1267
|
+
"ladybugs",
|
|
1268
|
+
"lamps",
|
|
1269
|
+
"laws",
|
|
1270
|
+
"lemons",
|
|
1271
|
+
"lies",
|
|
1272
|
+
"lights",
|
|
1273
|
+
"lions",
|
|
1274
|
+
"lizards",
|
|
1275
|
+
"llamas",
|
|
1276
|
+
"maps",
|
|
1277
|
+
"masks",
|
|
1278
|
+
"meals",
|
|
1279
|
+
"melons",
|
|
1280
|
+
"mice",
|
|
1281
|
+
"mirrors",
|
|
1282
|
+
"moles",
|
|
1283
|
+
"monkeys",
|
|
1284
|
+
"moons",
|
|
1285
|
+
"moose",
|
|
1286
|
+
"mugs",
|
|
1287
|
+
"mules",
|
|
1288
|
+
"nails",
|
|
1289
|
+
"news",
|
|
1290
|
+
"nights",
|
|
1291
|
+
"numbers",
|
|
1292
|
+
"olives",
|
|
1293
|
+
"onions",
|
|
1294
|
+
"oranges",
|
|
1295
|
+
"otters",
|
|
1296
|
+
"owls",
|
|
1297
|
+
"pandas",
|
|
1298
|
+
"pans",
|
|
1299
|
+
"pants",
|
|
1300
|
+
"papayas",
|
|
1301
|
+
"parents",
|
|
1302
|
+
"parrots",
|
|
1303
|
+
"paths",
|
|
1304
|
+
"peaches",
|
|
1305
|
+
"pears",
|
|
1306
|
+
"peas",
|
|
1307
|
+
"pens",
|
|
1308
|
+
"pets",
|
|
1309
|
+
"phones",
|
|
1310
|
+
"pianos",
|
|
1311
|
+
"pigs",
|
|
1312
|
+
"pillows",
|
|
1313
|
+
"planes",
|
|
1314
|
+
"planets",
|
|
1315
|
+
"plants",
|
|
1316
|
+
"plums",
|
|
1317
|
+
"points",
|
|
1318
|
+
"poets",
|
|
1319
|
+
"pots",
|
|
1320
|
+
"pugs",
|
|
1321
|
+
"queens",
|
|
1322
|
+
"rabbits",
|
|
1323
|
+
"radios",
|
|
1324
|
+
"rats",
|
|
1325
|
+
"ravens",
|
|
1326
|
+
"rice",
|
|
1327
|
+
"rings",
|
|
1328
|
+
"rivers",
|
|
1329
|
+
"rocks",
|
|
1330
|
+
"roses",
|
|
1331
|
+
"rules",
|
|
1332
|
+
"schools",
|
|
1333
|
+
"seals",
|
|
1334
|
+
"seas",
|
|
1335
|
+
"sheep",
|
|
1336
|
+
"shirts",
|
|
1337
|
+
"shoes",
|
|
1338
|
+
"shrimps",
|
|
1339
|
+
"singers",
|
|
1340
|
+
"sloths",
|
|
1341
|
+
"snails",
|
|
1342
|
+
"snakes",
|
|
1343
|
+
"socks",
|
|
1344
|
+
"spiders",
|
|
1345
|
+
"spies",
|
|
1346
|
+
"spoons",
|
|
1347
|
+
"squids",
|
|
1348
|
+
"stamps",
|
|
1349
|
+
"stars",
|
|
1350
|
+
"steaks",
|
|
1351
|
+
"suits",
|
|
1352
|
+
"swans",
|
|
1353
|
+
"tables",
|
|
1354
|
+
"teachers",
|
|
1355
|
+
"teams",
|
|
1356
|
+
"teeth",
|
|
1357
|
+
"terms",
|
|
1358
|
+
"ties",
|
|
1359
|
+
"tigers",
|
|
1360
|
+
"toes",
|
|
1361
|
+
"tools",
|
|
1362
|
+
"towns",
|
|
1363
|
+
"toys",
|
|
1364
|
+
"trains",
|
|
1365
|
+
"trees",
|
|
1366
|
+
"turkeys",
|
|
1367
|
+
"turtles",
|
|
1368
|
+
"vans",
|
|
1369
|
+
"walls",
|
|
1370
|
+
"wasps",
|
|
1371
|
+
"waves",
|
|
1372
|
+
"ways",
|
|
1373
|
+
"weeks",
|
|
1374
|
+
"windows",
|
|
1375
|
+
"wings",
|
|
1376
|
+
"wolves",
|
|
1377
|
+
"worms",
|
|
1378
|
+
"zebras"
|
|
1379
|
+
];
|
|
1380
|
+
const verbs = [
|
|
1381
|
+
"accept",
|
|
1382
|
+
"add",
|
|
1383
|
+
"admire",
|
|
1384
|
+
"agree",
|
|
1385
|
+
"allow",
|
|
1386
|
+
"appear",
|
|
1387
|
+
"argue",
|
|
1388
|
+
"arrive",
|
|
1389
|
+
"attack",
|
|
1390
|
+
"attend",
|
|
1391
|
+
"bake",
|
|
1392
|
+
"bathe",
|
|
1393
|
+
"battle",
|
|
1394
|
+
"beam",
|
|
1395
|
+
"beg",
|
|
1396
|
+
"begin",
|
|
1397
|
+
"behave",
|
|
1398
|
+
"bet",
|
|
1399
|
+
"boil",
|
|
1400
|
+
"bow",
|
|
1401
|
+
"brake",
|
|
1402
|
+
"breathe",
|
|
1403
|
+
"brush",
|
|
1404
|
+
"build",
|
|
1405
|
+
"burn",
|
|
1406
|
+
"buy",
|
|
1407
|
+
"call",
|
|
1408
|
+
"camp",
|
|
1409
|
+
"care",
|
|
1410
|
+
"carry",
|
|
1411
|
+
"change",
|
|
1412
|
+
"cheat",
|
|
1413
|
+
"check",
|
|
1414
|
+
"cheer",
|
|
1415
|
+
"chew",
|
|
1416
|
+
"clap",
|
|
1417
|
+
"clean",
|
|
1418
|
+
"collect",
|
|
1419
|
+
"compare",
|
|
1420
|
+
"compete",
|
|
1421
|
+
"complain",
|
|
1422
|
+
"confess",
|
|
1423
|
+
"construct",
|
|
1424
|
+
"cough",
|
|
1425
|
+
"count",
|
|
1426
|
+
"cover",
|
|
1427
|
+
"crash",
|
|
1428
|
+
"cross",
|
|
1429
|
+
"cry",
|
|
1430
|
+
"dance",
|
|
1431
|
+
"deliver",
|
|
1432
|
+
"deny",
|
|
1433
|
+
"design",
|
|
1434
|
+
"destroy",
|
|
1435
|
+
"develop",
|
|
1436
|
+
"draw",
|
|
1437
|
+
"dream",
|
|
1438
|
+
"dress",
|
|
1439
|
+
"drive",
|
|
1440
|
+
"drop",
|
|
1441
|
+
"eat",
|
|
1442
|
+
"enjoy",
|
|
1443
|
+
"exercise",
|
|
1444
|
+
"explain",
|
|
1445
|
+
"fail",
|
|
1446
|
+
"fetch",
|
|
1447
|
+
"film",
|
|
1448
|
+
"fix",
|
|
1449
|
+
"flash",
|
|
1450
|
+
"float",
|
|
1451
|
+
"fly",
|
|
1452
|
+
"fold",
|
|
1453
|
+
"fry",
|
|
1454
|
+
"give",
|
|
1455
|
+
"glow",
|
|
1456
|
+
"greet",
|
|
1457
|
+
"grin",
|
|
1458
|
+
"grow",
|
|
1459
|
+
"guess",
|
|
1460
|
+
"hammer",
|
|
1461
|
+
"hang",
|
|
1462
|
+
"happen",
|
|
1463
|
+
"heal",
|
|
1464
|
+
"hear",
|
|
1465
|
+
"help",
|
|
1466
|
+
"hide",
|
|
1467
|
+
"hope",
|
|
1468
|
+
"hug",
|
|
1469
|
+
"hunt",
|
|
1470
|
+
"impress",
|
|
1471
|
+
"invent",
|
|
1472
|
+
"invite",
|
|
1473
|
+
"itch",
|
|
1474
|
+
"jam",
|
|
1475
|
+
"jog",
|
|
1476
|
+
"join",
|
|
1477
|
+
"joke",
|
|
1478
|
+
"judge",
|
|
1479
|
+
"juggle",
|
|
1480
|
+
"jump",
|
|
1481
|
+
"kick",
|
|
1482
|
+
"kiss",
|
|
1483
|
+
"kneel",
|
|
1484
|
+
"knock",
|
|
1485
|
+
"know",
|
|
1486
|
+
"laugh",
|
|
1487
|
+
"lay",
|
|
1488
|
+
"lead",
|
|
1489
|
+
"learn",
|
|
1490
|
+
"leave",
|
|
1491
|
+
"lick",
|
|
1492
|
+
"lie",
|
|
1493
|
+
"listen",
|
|
1494
|
+
"live",
|
|
1495
|
+
"look",
|
|
1496
|
+
"love",
|
|
1497
|
+
"make",
|
|
1498
|
+
"march",
|
|
1499
|
+
"marry",
|
|
1500
|
+
"matter",
|
|
1501
|
+
"melt",
|
|
1502
|
+
"mix",
|
|
1503
|
+
"move",
|
|
1504
|
+
"nail",
|
|
1505
|
+
"notice",
|
|
1506
|
+
"obey",
|
|
1507
|
+
"occur",
|
|
1508
|
+
"own",
|
|
1509
|
+
"pay",
|
|
1510
|
+
"peel",
|
|
1511
|
+
"perform",
|
|
1512
|
+
"play",
|
|
1513
|
+
"poke",
|
|
1514
|
+
"pretend",
|
|
1515
|
+
"promise",
|
|
1516
|
+
"provide",
|
|
1517
|
+
"pull",
|
|
1518
|
+
"punch",
|
|
1519
|
+
"push",
|
|
1520
|
+
"raise",
|
|
1521
|
+
"rescue",
|
|
1522
|
+
"rest",
|
|
1523
|
+
"retire",
|
|
1524
|
+
"return",
|
|
1525
|
+
"rhyme",
|
|
1526
|
+
"roll",
|
|
1527
|
+
"rule",
|
|
1528
|
+
"run",
|
|
1529
|
+
"rush",
|
|
1530
|
+
"say",
|
|
1531
|
+
"scream",
|
|
1532
|
+
"search",
|
|
1533
|
+
"sell",
|
|
1534
|
+
"serve",
|
|
1535
|
+
"shake",
|
|
1536
|
+
"share",
|
|
1537
|
+
"shave",
|
|
1538
|
+
"shine",
|
|
1539
|
+
"shop",
|
|
1540
|
+
"shout",
|
|
1541
|
+
"sin",
|
|
1542
|
+
"sing",
|
|
1543
|
+
"sink",
|
|
1544
|
+
"sip",
|
|
1545
|
+
"sit",
|
|
1546
|
+
"sleep",
|
|
1547
|
+
"slide",
|
|
1548
|
+
"smash",
|
|
1549
|
+
"smell",
|
|
1550
|
+
"smile",
|
|
1551
|
+
"smoke",
|
|
1552
|
+
"sneeze",
|
|
1553
|
+
"sniff",
|
|
1554
|
+
"sort",
|
|
1555
|
+
"speak",
|
|
1556
|
+
"spend",
|
|
1557
|
+
"sparkle",
|
|
1558
|
+
"stand",
|
|
1559
|
+
"stare",
|
|
1560
|
+
"start",
|
|
1561
|
+
"stay",
|
|
1562
|
+
"suffer",
|
|
1563
|
+
"swim",
|
|
1564
|
+
"switch",
|
|
1565
|
+
"talk",
|
|
1566
|
+
"taste",
|
|
1567
|
+
"teach",
|
|
1568
|
+
"tell",
|
|
1569
|
+
"thank",
|
|
1570
|
+
"think",
|
|
1571
|
+
"tickle",
|
|
1572
|
+
"trade",
|
|
1573
|
+
"travel",
|
|
1574
|
+
"try",
|
|
1575
|
+
"turn",
|
|
1576
|
+
"type",
|
|
1577
|
+
"unite",
|
|
1578
|
+
"vanish",
|
|
1579
|
+
"visit",
|
|
1580
|
+
"wait",
|
|
1581
|
+
"walk",
|
|
1582
|
+
"warn",
|
|
1583
|
+
"wash",
|
|
1584
|
+
"watch",
|
|
1585
|
+
"wave",
|
|
1586
|
+
"whisper",
|
|
1587
|
+
"win",
|
|
1588
|
+
"wink",
|
|
1589
|
+
"wonder",
|
|
1590
|
+
"work",
|
|
1591
|
+
"worry",
|
|
1592
|
+
"yawn",
|
|
1593
|
+
"yell"
|
|
1594
|
+
];
|
|
1595
|
+
const pick = (words) => Effect.map(Random.nextIntBetween(0, words.length), (i) => words[i] ?? words[0] ?? "");
|
|
1596
|
+
const humanId = Effect.all([
|
|
1597
|
+
pick(adjectives),
|
|
1598
|
+
pick(nouns),
|
|
1599
|
+
pick(verbs)
|
|
1600
|
+
]).pipe(Effect.map((parts) => parts.join("-")));
|
|
1601
|
+
// src/json-edit.ts
|
|
1602
|
+
/**
|
|
1603
|
+
* Formatting-preserving `package.json` edits, as `@changesets/apply-release-plan` does with
|
|
1604
|
+
* jsonc-parser: each edit replaces only the text of an existing value with `JSON.stringify(value)`.
|
|
1605
|
+
*/
|
|
1606
|
+
const skipWhitespace = (text, at) => {
|
|
1607
|
+
return at + (/^[ \t\n\r]*/.exec(text.slice(at))?.[0].length ?? 0);
|
|
1608
|
+
};
|
|
1609
|
+
const stringPattern = /^"(?:[^"\\\u0000-\u001f]|\\(?:["\\/bfnrt]|u[0-9a-fA-F]{4}))*"/;
|
|
1610
|
+
const literalPattern = /^(?:true|false|null|-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][-+]?[0-9]+)?)/;
|
|
1611
|
+
const parseString = (text, at) => {
|
|
1612
|
+
const match = stringPattern.exec(text.slice(at));
|
|
1613
|
+
return match === null ? Result.fail(`Invalid string at offset ${at}`) : Result.succeed({
|
|
1614
|
+
value: JSON.parse(match[0]),
|
|
1615
|
+
end: at + match[0].length
|
|
1616
|
+
});
|
|
1617
|
+
};
|
|
1618
|
+
const parseMembers = (text, at, close, member, members) => Result.gen(function* () {
|
|
1619
|
+
const parsed = yield* member(skipWhitespace(text, at));
|
|
1620
|
+
const next = skipWhitespace(text, parsed.end);
|
|
1621
|
+
const all = [...members, parsed.value];
|
|
1622
|
+
if (text[next] === ",") return yield* parseMembers(text, next + 1, close, member, all);
|
|
1623
|
+
if (text[next] === close) return {
|
|
1624
|
+
value: all,
|
|
1625
|
+
end: next + 1
|
|
1626
|
+
};
|
|
1627
|
+
return yield* Result.fail(`Expected "," or "${close}" at offset ${next}`);
|
|
1628
|
+
});
|
|
1629
|
+
const parseValue = (text, at) => Result.gen(function* () {
|
|
1630
|
+
const char = text[at];
|
|
1631
|
+
if (char === "{") {
|
|
1632
|
+
const first = skipWhitespace(text, at + 1);
|
|
1633
|
+
if (text[first] === "}") return {
|
|
1634
|
+
offset: at,
|
|
1635
|
+
length: first + 1 - at,
|
|
1636
|
+
properties: []
|
|
1637
|
+
};
|
|
1638
|
+
const members = yield* parseMembers(text, first, "}", (start) => parseProperty(text, start), []);
|
|
1639
|
+
return {
|
|
1640
|
+
offset: at,
|
|
1641
|
+
length: members.end - at,
|
|
1642
|
+
properties: members.value
|
|
1643
|
+
};
|
|
1644
|
+
}
|
|
1645
|
+
if (char === "[") {
|
|
1646
|
+
const first = skipWhitespace(text, at + 1);
|
|
1647
|
+
if (text[first] === "]") return {
|
|
1648
|
+
offset: at,
|
|
1649
|
+
length: first + 1 - at,
|
|
1650
|
+
properties: void 0
|
|
1651
|
+
};
|
|
1652
|
+
return {
|
|
1653
|
+
offset: at,
|
|
1654
|
+
length: (yield* parseMembers(text, first, "]", (start) => parseItem(text, start), [])).end - at,
|
|
1655
|
+
properties: void 0
|
|
1656
|
+
};
|
|
1657
|
+
}
|
|
1658
|
+
if (char === "\"") return {
|
|
1659
|
+
offset: at,
|
|
1660
|
+
length: (yield* parseString(text, at)).end - at,
|
|
1661
|
+
properties: void 0
|
|
1662
|
+
};
|
|
1663
|
+
const literal = literalPattern.exec(text.slice(at));
|
|
1664
|
+
if (literal === null) return yield* Result.fail(`Unexpected token at offset ${at}`);
|
|
1665
|
+
return {
|
|
1666
|
+
offset: at,
|
|
1667
|
+
length: literal[0].length,
|
|
1668
|
+
properties: void 0
|
|
1669
|
+
};
|
|
1670
|
+
});
|
|
1671
|
+
const parseItem = (text, at) => Result.map(parseValue(text, at), (node) => ({
|
|
1672
|
+
value: node,
|
|
1673
|
+
end: node.offset + node.length
|
|
1674
|
+
}));
|
|
1675
|
+
const parseProperty = (text, at) => Result.gen(function* () {
|
|
1676
|
+
const key = yield* parseString(text, at);
|
|
1677
|
+
const colon = skipWhitespace(text, key.end);
|
|
1678
|
+
if (text[colon] !== ":") return yield* Result.fail(`Expected ":" at offset ${colon}`);
|
|
1679
|
+
const node = yield* parseValue(text, skipWhitespace(text, colon + 1));
|
|
1680
|
+
return {
|
|
1681
|
+
value: {
|
|
1682
|
+
key: key.value,
|
|
1683
|
+
node
|
|
1684
|
+
},
|
|
1685
|
+
end: node.offset + node.length
|
|
1686
|
+
};
|
|
1687
|
+
});
|
|
1688
|
+
const parseDocument = (text) => Result.gen(function* () {
|
|
1689
|
+
const root = yield* parseValue(text, skipWhitespace(text, 0));
|
|
1690
|
+
const end = skipWhitespace(text, root.offset + root.length);
|
|
1691
|
+
if (end !== text.length) return yield* Result.fail(`Unexpected content at offset ${end}`);
|
|
1692
|
+
return root;
|
|
1693
|
+
});
|
|
1694
|
+
const valueNode = (root, keys) => keys.reduce((node, key) => node?.properties?.find((property) => property.key === key)?.node, root);
|
|
1695
|
+
/** Applies every edit to `json`; fails when the JSON is invalid or a key path is missing. */
|
|
1696
|
+
const editJson = (json, edits) => Result.gen(function* () {
|
|
1697
|
+
const root = yield* parseDocument(json);
|
|
1698
|
+
return (yield* Result.all(edits.map((edit) => {
|
|
1699
|
+
const node = valueNode(root, edit.keys);
|
|
1700
|
+
return node === void 0 ? Result.fail(`Key path "${edit.keys.join(".")}" not found in JSON`) : Result.succeed({
|
|
1701
|
+
node,
|
|
1702
|
+
content: JSON.stringify(edit.value)
|
|
1703
|
+
});
|
|
1704
|
+
}))).toSorted((a, b) => b.node.offset - a.node.offset).reduce((text, { node, content }) => text.slice(0, node.offset) + content + text.slice(node.offset + node.length), json);
|
|
1705
|
+
});
|
|
1706
|
+
// src/plan.ts
|
|
1707
|
+
/**
|
|
1708
|
+
* `@changesets/assemble-release-plan` outside pre mode and snapshots: flatten changesets into
|
|
1709
|
+
* releases, then bump dependents and apply fixed/linked groups until nothing changes.
|
|
1710
|
+
* Release order is insertion order, which decides changelog and status output order.
|
|
1711
|
+
*/
|
|
1712
|
+
const rank = {
|
|
1713
|
+
major: 3,
|
|
1714
|
+
minor: 2,
|
|
1715
|
+
patch: 1,
|
|
1716
|
+
none: 0
|
|
1717
|
+
};
|
|
1718
|
+
const newVersionOf = (release) => release.type === "none" ? release.oldVersion : increment(release.oldVersion, release.type);
|
|
1719
|
+
const highestType = (releases) => releases.reduce((highest, release) => rank[release.type] > rank[highest] ? release.type : highest, "none");
|
|
1720
|
+
const highestVersion = (group, byName) => group.reduce((highest, name) => {
|
|
1721
|
+
const version = byName.get(name)?.packageJson.version;
|
|
1722
|
+
return version !== void 0 && (highest === void 0 || greaterThan(version, highest)) ? version : highest;
|
|
1723
|
+
}, void 0);
|
|
1724
|
+
const flattenReleases = (changesets, byName, config) => changesets.reduce((releases, changeset) => {
|
|
1725
|
+
changeset.releases.forEach(({ name, type }) => {
|
|
1726
|
+
const pkg = byName.get(name);
|
|
1727
|
+
if (pkg === void 0 || shouldSkipPackage(pkg, skipOptions(config))) return;
|
|
1728
|
+
const release = releases.get(name);
|
|
1729
|
+
if (release === void 0) {
|
|
1730
|
+
releases.set(name, {
|
|
1731
|
+
name,
|
|
1732
|
+
type,
|
|
1733
|
+
oldVersion: pkg.packageJson.version,
|
|
1734
|
+
changesets: [changeset.id]
|
|
1735
|
+
});
|
|
1736
|
+
return;
|
|
1737
|
+
}
|
|
1738
|
+
if (rank[type] > rank[release.type]) release.type = type;
|
|
1739
|
+
release.changesets.push(changeset.id);
|
|
1740
|
+
});
|
|
1741
|
+
return releases;
|
|
1742
|
+
}, /* @__PURE__ */ new Map());
|
|
1743
|
+
const dependencyRanges = (rootDir, dependent, release, dependency) => DEPENDENCY_TYPES.flatMap((depType) => {
|
|
1744
|
+
const raw = dependent.packageJson[depType]?.[release.name];
|
|
1745
|
+
if (!raw) return [];
|
|
1746
|
+
if (!raw.startsWith("workspace:")) return [{
|
|
1747
|
+
depType,
|
|
1748
|
+
versionRange: raw
|
|
1749
|
+
}];
|
|
1750
|
+
const range = raw.slice(10);
|
|
1751
|
+
if (range === "*") return [{
|
|
1752
|
+
depType,
|
|
1753
|
+
versionRange: release.oldVersion
|
|
1754
|
+
}];
|
|
1755
|
+
if (range === "^" || range === "~") return [{
|
|
1756
|
+
depType,
|
|
1757
|
+
versionRange: `${range}${release.oldVersion}`
|
|
1758
|
+
}];
|
|
1759
|
+
if (isValidRange(range)) return [{
|
|
1760
|
+
depType,
|
|
1761
|
+
versionRange: range
|
|
1762
|
+
}];
|
|
1763
|
+
return workspacePathMatches(range, rootDir, dependency) ? [{
|
|
1764
|
+
depType,
|
|
1765
|
+
versionRange: release.oldVersion
|
|
1766
|
+
}] : [];
|
|
1767
|
+
});
|
|
1768
|
+
const dependentType = (ctx, dependentPkg, next) => {
|
|
1769
|
+
if (shouldSkipPackage(dependentPkg, skipOptions(ctx.config))) return "none";
|
|
1770
|
+
const dependency = ctx.byName.get(next.name);
|
|
1771
|
+
if (dependency === void 0 || next.type === "none") return void 0;
|
|
1772
|
+
const existing = ctx.releases.get(dependentPkg.packageJson.name);
|
|
1773
|
+
return dependencyRanges(ctx.rootDir, dependentPkg, next, dependency).reduce((type, { depType, versionRange }) => {
|
|
1774
|
+
if (!((existing === void 0 || existing.type === "none") && (ctx.config.updateInternalDependents === "always" || !satisfies(newVersionOf(next), versionRange)))) return type;
|
|
1775
|
+
if (depType === "devDependencies") return type !== "major" && type !== "minor" && type !== "patch" ? "none" : type;
|
|
1776
|
+
return type !== "major" && type !== "minor" ? "patch" : type;
|
|
1777
|
+
}, void 0);
|
|
1778
|
+
};
|
|
1779
|
+
const determineDependents = (ctx) => {
|
|
1780
|
+
const visit = (queue, updated) => {
|
|
1781
|
+
const [next, ...rest] = queue;
|
|
1782
|
+
if (next === void 0) return updated;
|
|
1783
|
+
const found = (ctx.graph.get(next.name) ?? []).flatMap((dependent) => {
|
|
1784
|
+
const dependentPkg = ctx.byName.get(dependent);
|
|
1785
|
+
if (dependentPkg === void 0) return [];
|
|
1786
|
+
const computed = dependentType(ctx, dependentPkg, next);
|
|
1787
|
+
const type = ctx.releases.get(dependent)?.type === computed ? void 0 : computed;
|
|
1788
|
+
return type === void 0 ? [] : [{
|
|
1789
|
+
name: dependent,
|
|
1790
|
+
type,
|
|
1791
|
+
oldVersion: dependentPkg.packageJson.version
|
|
1792
|
+
}];
|
|
1793
|
+
});
|
|
1794
|
+
const added = found.flatMap(({ name, type, oldVersion }) => {
|
|
1795
|
+
const existing = ctx.releases.get(name);
|
|
1796
|
+
if (existing !== void 0 && type === "major" && existing.type !== "major") {
|
|
1797
|
+
existing.type = "major";
|
|
1798
|
+
return [existing];
|
|
1799
|
+
}
|
|
1800
|
+
const release = {
|
|
1801
|
+
name,
|
|
1802
|
+
type,
|
|
1803
|
+
oldVersion,
|
|
1804
|
+
changesets: []
|
|
1805
|
+
};
|
|
1806
|
+
ctx.releases.set(name, release);
|
|
1807
|
+
return [release];
|
|
1808
|
+
});
|
|
1809
|
+
return visit([...rest, ...added], updated || found.length > 0);
|
|
1810
|
+
};
|
|
1811
|
+
return visit([...ctx.releases.values()], false);
|
|
1812
|
+
};
|
|
1813
|
+
const matchFixedConstraint = (ctx) => ctx.config.fixed.reduce((updated, group) => {
|
|
1814
|
+
const releasing = [...ctx.releases.values()].filter((r) => group.includes(r.name) && r.type !== "none");
|
|
1815
|
+
if (releasing.length === 0) return updated;
|
|
1816
|
+
const type = highestType(releasing);
|
|
1817
|
+
const version = highestVersion(group, ctx.byName);
|
|
1818
|
+
return group.reduce((changed, name) => {
|
|
1819
|
+
const pkg = ctx.byName.get(name);
|
|
1820
|
+
if (pkg === void 0 || shouldSkipPackage(pkg, skipOptions(ctx.config))) return changed;
|
|
1821
|
+
const release = ctx.releases.get(name);
|
|
1822
|
+
if (release === void 0) {
|
|
1823
|
+
ctx.releases.set(name, {
|
|
1824
|
+
name,
|
|
1825
|
+
type,
|
|
1826
|
+
oldVersion: version,
|
|
1827
|
+
changesets: []
|
|
1828
|
+
});
|
|
1829
|
+
return true;
|
|
1830
|
+
}
|
|
1831
|
+
const differs = release.type !== type || release.oldVersion !== version;
|
|
1832
|
+
release.type = type;
|
|
1833
|
+
release.oldVersion = version;
|
|
1834
|
+
return changed || differs;
|
|
1835
|
+
}, updated);
|
|
1836
|
+
}, false);
|
|
1837
|
+
const applyLinks = (ctx) => ctx.config.linked.reduce((updated, group) => {
|
|
1838
|
+
const releasing = [...ctx.releases.values()].filter((r) => group.includes(r.name) && r.type !== "none");
|
|
1839
|
+
if (releasing.length === 0) return updated;
|
|
1840
|
+
const type = highestType(releasing);
|
|
1841
|
+
const version = highestVersion(group, ctx.byName);
|
|
1842
|
+
return releasing.reduce((changed, release) => {
|
|
1843
|
+
const differs = release.type !== type || release.oldVersion !== version;
|
|
1844
|
+
release.type = type;
|
|
1845
|
+
release.oldVersion = version;
|
|
1846
|
+
return changed || differs;
|
|
1847
|
+
}, updated);
|
|
1848
|
+
}, false);
|
|
1849
|
+
/** Rejects a changeset naming an unknown package, or mixing skipped and released packages. */
|
|
1850
|
+
const relevantChangesets = (changesets, byName, config) => {
|
|
1851
|
+
const [first] = changesets.flatMap((changeset) => {
|
|
1852
|
+
const unknown = changeset.releases.find((release) => !byName.has(release.name));
|
|
1853
|
+
if (unknown !== void 0) return [`Found changeset ${changeset.id} for package ${unknown.name} which is not in the workspace`];
|
|
1854
|
+
const skipped = changeset.releases.filter((r) => {
|
|
1855
|
+
const pkg = byName.get(r.name);
|
|
1856
|
+
return pkg !== void 0 && shouldSkipPackage(pkg, skipOptions(config));
|
|
1857
|
+
});
|
|
1858
|
+
const kept = changeset.releases.filter((r) => !skipped.includes(r));
|
|
1859
|
+
return skipped.length > 0 && kept.length > 0 ? [`Found mixed changeset ${changeset.id}\nFound ignored packages: ${skipped.map((r) => r.name).join(" ")}\nFound not ignored packages: ${kept.map((r) => r.name).join(" ")}\nMixed changesets that contain both ignored and not ignored packages are not allowed`] : [];
|
|
1860
|
+
});
|
|
1861
|
+
return first === void 0 ? Result.succeed(changesets) : Result.fail(first);
|
|
1862
|
+
};
|
|
1863
|
+
const assembleReleasePlan = (changesets, packages, config) => {
|
|
1864
|
+
const byName = new Map(packages.packages.map((pkg) => [pkg.packageJson.name, pkg]));
|
|
1865
|
+
return Result.map(relevantChangesets(changesets, byName, config), (relevant) => {
|
|
1866
|
+
const ctx = {
|
|
1867
|
+
releases: flattenReleases(relevant, byName, config),
|
|
1868
|
+
byName,
|
|
1869
|
+
rootDir: packages.rootDir,
|
|
1870
|
+
graph: dependentsGraph(packages, { bumpVersionsWithWorkspaceProtocolOnly: config.bumpVersionsWithWorkspaceProtocolOnly }).graph,
|
|
1871
|
+
config
|
|
1872
|
+
};
|
|
1873
|
+
const settle = () => {
|
|
1874
|
+
const dependentAdded = determineDependents(ctx);
|
|
1875
|
+
const fixedUpdated = matchFixedConstraint(ctx);
|
|
1876
|
+
const linksUpdated = applyLinks(ctx);
|
|
1877
|
+
if (dependentAdded || fixedUpdated || linksUpdated) settle();
|
|
1878
|
+
};
|
|
1879
|
+
settle();
|
|
1880
|
+
return {
|
|
1881
|
+
changesets: relevant,
|
|
1882
|
+
releases: [...ctx.releases.values()].map((release) => ({
|
|
1883
|
+
name: release.name,
|
|
1884
|
+
type: release.type,
|
|
1885
|
+
oldVersion: release.oldVersion,
|
|
1886
|
+
changesets: release.changesets,
|
|
1887
|
+
newVersion: newVersionOf(release)
|
|
1888
|
+
}))
|
|
1889
|
+
};
|
|
1890
|
+
});
|
|
1891
|
+
};
|
|
1892
|
+
// src/workspace.ts
|
|
1893
|
+
/**
|
|
1894
|
+
* Workspace discovery as `@manypkg/get-packages` does it for package.json `workspaces` with a
|
|
1895
|
+
* bun, yarn or npm lockfile: the nearest such ancestor is the root, else the nearest package.json
|
|
1896
|
+
* is a single-package root. Packages are the matching dirs with a package.json, sorted by path.
|
|
1897
|
+
*/
|
|
1898
|
+
const Ranges = Schema.optionalKey(Schema.Record(Schema.String, Schema.String));
|
|
1899
|
+
const Manifest = Schema.Struct({
|
|
1900
|
+
name: Schema.optionalKey(Schema.String),
|
|
1901
|
+
version: Schema.optionalKey(Schema.String),
|
|
1902
|
+
private: Schema.optionalKey(Schema.Boolean),
|
|
1903
|
+
workspaces: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Struct({ packages: Schema.optionalKey(Schema.Array(Schema.String)) })])),
|
|
1904
|
+
dependencies: Ranges,
|
|
1905
|
+
devDependencies: Ranges,
|
|
1906
|
+
peerDependencies: Ranges,
|
|
1907
|
+
optionalDependencies: Ranges
|
|
1908
|
+
});
|
|
1909
|
+
const decodeManifest = Schema.decodeUnknownEffect(Schema.fromJsonString(Manifest));
|
|
1910
|
+
const isList = (value) => Array.isArray(value);
|
|
1911
|
+
const workspaceGlobs = ({ workspaces }) => workspaces === void 0 ? void 0 : isList(workspaces) ? workspaces : workspaces.packages ?? [];
|
|
1912
|
+
const toPackageJson = (manifest, name) => ({
|
|
1913
|
+
...manifest,
|
|
1914
|
+
name
|
|
1915
|
+
});
|
|
1916
|
+
const readManifest = Effect.fn("readManifest")(function* (dir) {
|
|
1917
|
+
const fs = yield* FileSystem.FileSystem;
|
|
1918
|
+
const file = (yield* Path.Path).join(dir, "package.json");
|
|
1919
|
+
const text = yield* fs.readFileString(file);
|
|
1920
|
+
return yield* decodeManifest(text).pipe(Effect.mapError((cause) => new WorkspaceError({
|
|
1921
|
+
message: `Could not read ${file}: ${cause.message}`,
|
|
1922
|
+
path: file,
|
|
1923
|
+
cause
|
|
1924
|
+
})));
|
|
1925
|
+
});
|
|
1926
|
+
const manifestIn = (dir) => Effect.gen(function* () {
|
|
1927
|
+
const fs = yield* FileSystem.FileSystem;
|
|
1928
|
+
const path = yield* Path.Path;
|
|
1929
|
+
return (yield* fs.exists(path.join(dir, "package.json"))) ? Option.some(yield* readManifest(dir)) : Option.none();
|
|
1930
|
+
});
|
|
1931
|
+
const isWorkspaceRoot = (dir, manifest) => Effect.gen(function* () {
|
|
1932
|
+
const fs = yield* FileSystem.FileSystem;
|
|
1933
|
+
const path = yield* Path.Path;
|
|
1934
|
+
if (manifest.workspaces === void 0) return false;
|
|
1935
|
+
const has = (file) => fs.exists(path.join(dir, file));
|
|
1936
|
+
if ((yield* has("bun.lock")) || (yield* has("bun.lockb")) || (yield* has("yarn.lock"))) return true;
|
|
1937
|
+
return isList(manifest.workspaces) && (yield* has("package-lock.json"));
|
|
1938
|
+
});
|
|
1939
|
+
const findRoot = (dir, nearest) => Effect.gen(function* () {
|
|
1940
|
+
const path = yield* Path.Path;
|
|
1941
|
+
const manifest = yield* manifestIn(dir);
|
|
1942
|
+
if (Option.isSome(manifest) && (yield* isWorkspaceRoot(dir, manifest.value))) return Option.some({
|
|
1943
|
+
dir,
|
|
1944
|
+
manifest: manifest.value
|
|
1945
|
+
});
|
|
1946
|
+
const nextNearest = Option.isNone(nearest) && Option.isSome(manifest) ? Option.some({
|
|
1947
|
+
dir,
|
|
1948
|
+
manifest: manifest.value
|
|
1949
|
+
}) : nearest;
|
|
1950
|
+
const parent = path.dirname(dir);
|
|
1951
|
+
return parent === dir ? nextNearest : yield* findRoot(parent, nextNearest);
|
|
1952
|
+
});
|
|
1953
|
+
const expandGlobs = Effect.fn("expandGlobs")(function* (globs, rootDir) {
|
|
1954
|
+
const fs = yield* FileSystem.FileSystem;
|
|
1955
|
+
const path = yield* Path.Path;
|
|
1956
|
+
const exclude = ["**/node_modules", ...globs.filter((g) => g.startsWith("!")).map((g) => g.slice(1))];
|
|
1957
|
+
const matched = yield* Effect.forEach(globs.filter((g) => !g.startsWith("!")), (glob) => fs.glob(glob.replace(/\/+$/, ""), {
|
|
1958
|
+
root: rootDir,
|
|
1959
|
+
exclude
|
|
1960
|
+
}));
|
|
1961
|
+
const candidates = [...new Set(matched.flat().map((relative) => path.resolve(rootDir, relative)))].toSorted();
|
|
1962
|
+
return (yield* Effect.forEach(candidates, (dir) => Effect.gen(function* () {
|
|
1963
|
+
if ((yield* fs.stat(dir)).type !== "Directory") return Option.none();
|
|
1964
|
+
const manifest = yield* manifestIn(dir);
|
|
1965
|
+
return Option.map(manifest, (value) => ({
|
|
1966
|
+
dir,
|
|
1967
|
+
manifest: value
|
|
1968
|
+
}));
|
|
1969
|
+
}))).flatMap(Option.toArray);
|
|
1970
|
+
});
|
|
1971
|
+
/** Finds the workspace containing `cwd` and reads every package manifest in it. */
|
|
1972
|
+
const getPackages = Effect.fn("getPackages")(function* (cwd) {
|
|
1973
|
+
const path = yield* Path.Path;
|
|
1974
|
+
const start = path.resolve(cwd);
|
|
1975
|
+
const root = yield* findRoot(start, Option.none());
|
|
1976
|
+
if (Option.isNone(root)) return yield* new WorkspaceError({
|
|
1977
|
+
message: `No package.json found from ${start} upwards`,
|
|
1978
|
+
path: start
|
|
1979
|
+
});
|
|
1980
|
+
const rootDir = root.value.dir;
|
|
1981
|
+
const rootPackage = {
|
|
1982
|
+
dir: rootDir,
|
|
1983
|
+
relativeDir: ".",
|
|
1984
|
+
packageJson: toPackageJson(root.value.manifest, root.value.manifest.name ?? "")
|
|
1985
|
+
};
|
|
1986
|
+
const globs = workspaceGlobs(root.value.manifest);
|
|
1987
|
+
if (globs === void 0) return {
|
|
1988
|
+
rootDir,
|
|
1989
|
+
rootPackage,
|
|
1990
|
+
packages: [rootPackage]
|
|
1991
|
+
};
|
|
1992
|
+
const found = yield* expandGlobs(globs, rootDir);
|
|
1993
|
+
const unnamed = found.filter(({ manifest }) => manifest.name === void 0);
|
|
1994
|
+
if (unnamed.length > 0) return yield* new WorkspaceError({
|
|
1995
|
+
message: `The following package.jsons are missing the "name" field:\n${unnamed.map(({ dir }) => path.join(path.relative(rootDir, dir), "package.json")).toSorted().join("\n")}`,
|
|
1996
|
+
path: rootDir
|
|
1997
|
+
});
|
|
1998
|
+
return {
|
|
1999
|
+
rootDir,
|
|
2000
|
+
rootPackage,
|
|
2001
|
+
packages: found.map(({ dir, manifest }) => ({
|
|
2002
|
+
dir,
|
|
2003
|
+
relativeDir: path.relative(rootDir, dir),
|
|
2004
|
+
packageJson: toPackageJson(manifest, manifest.name ?? "")
|
|
2005
|
+
}))
|
|
2006
|
+
};
|
|
2007
|
+
});
|
|
2008
|
+
// src/changesets.ts
|
|
2009
|
+
/**
|
|
2010
|
+
* The `add`, `version` and `status` commands of `@changesets/cli` 3.x, reading and writing the
|
|
2011
|
+
* same files. Pre mode, snapshots, `commit` and `publish` are out of scope and fail loudly.
|
|
2012
|
+
*/
|
|
2013
|
+
const ignoredMarkdown = (file) => /^README\.md$/i.test(file) || file === "AGENTS.md" || file === "CLAUDE.md" || file === "GEMINI.md";
|
|
2014
|
+
const encoder = new TextEncoder();
|
|
2015
|
+
/** libuv's `scandir` order (`strcmp` on UTF-8), which is what `fs.readdir` returns under Node. */
|
|
2016
|
+
const byteOrder = (a, b) => {
|
|
2017
|
+
const left = encoder.encode(a);
|
|
2018
|
+
const right = encoder.encode(b);
|
|
2019
|
+
const index = left.findIndex((byte, i) => byte !== right[i]);
|
|
2020
|
+
if (index === -1) return left.length - right.length;
|
|
2021
|
+
return (left[index] ?? 0) - (right[index] ?? -1);
|
|
2022
|
+
};
|
|
2023
|
+
const readSorted = (fs, dir) => Effect.map(fs.readDirectory(dir), (entries) => entries.toSorted(byteOrder));
|
|
2024
|
+
const builtinChangelogs = /* @__PURE__ */ new Set(["@changesets/cli/changelog", "@changesets/changelog-git"]);
|
|
2025
|
+
const decodeConfig = Schema.decodeUnknownEffect(Schema.fromJsonString(WrittenConfig));
|
|
2026
|
+
var Changesets = class extends Context.Service()("@maple-dev/changesets/Changesets", { make: Effect.gen(function* () {
|
|
2027
|
+
const fs = yield* FileSystem.FileSystem;
|
|
2028
|
+
const path = yield* Path.Path;
|
|
2029
|
+
const git = yield* Git;
|
|
2030
|
+
const formatter = yield* Formatter;
|
|
2031
|
+
const workspaceError = (file) => (cause) => new WorkspaceError({
|
|
2032
|
+
message: `Could not access ${file}: ${cause.message}`,
|
|
2033
|
+
path: file,
|
|
2034
|
+
cause
|
|
2035
|
+
});
|
|
2036
|
+
const changesetDir = (packages) => path.join(packages.rootDir, ".changeset");
|
|
2037
|
+
const ensureChangesetFolder = (packages) => Effect.flatMap(fs.exists(changesetDir(packages)).pipe(Effect.mapError(workspaceError(changesetDir(packages)))), (exists) => exists ? Effect.void : Effect.fail(new CommandExit({
|
|
2038
|
+
code: 1,
|
|
2039
|
+
message: "There is no .changeset folder.\nIf this is the first time Changesets have been used in this project, run `changeset init` to get set up.\nIf you expected there to be changesets, you should check git history for when the folder was removed to ensure you do not lose any configuration."
|
|
2040
|
+
})));
|
|
2041
|
+
/** Reads the workspace and `.changeset/config.json`; config rule violations fail. */
|
|
2042
|
+
const load = Effect.fn("Changesets.load")(function* (cwd) {
|
|
2043
|
+
const packages = yield* getPackages(cwd).pipe(Effect.catchTag("PlatformError", (e) => workspaceError(cwd)(e)));
|
|
2044
|
+
yield* ensureChangesetFolder(packages);
|
|
2045
|
+
const file = path.join(changesetDir(packages), "config.json");
|
|
2046
|
+
const text = yield* fs.readFileString(file).pipe(Effect.mapError(workspaceError(file)));
|
|
2047
|
+
const written = yield* decodeConfig(text).pipe(Effect.mapError((cause) => new ConfigError({
|
|
2048
|
+
message: `Found issues in your config:\n- ${cause.message}`,
|
|
2049
|
+
problems: [cause.message]
|
|
2050
|
+
})));
|
|
2051
|
+
const config = normalizeConfig(written, packages.packages.map((pkg) => pkg.packageJson.name));
|
|
2052
|
+
const { errors, warnings } = validateConfig(config, written, packages);
|
|
2053
|
+
if (errors.length > 0) return yield* new ConfigError({
|
|
2054
|
+
message: `Found issues in your config:\n- ${[...warnings, ...errors].join("\n- ")}`,
|
|
2055
|
+
problems: [...warnings, ...errors]
|
|
2056
|
+
});
|
|
2057
|
+
return {
|
|
2058
|
+
packages,
|
|
2059
|
+
config,
|
|
2060
|
+
warnings
|
|
2061
|
+
};
|
|
2062
|
+
});
|
|
2063
|
+
const refuseUnsupported = Effect.fn("Changesets.refuseUnsupported")(function* (project) {
|
|
2064
|
+
const preState = path.join(changesetDir(project.packages), "pre.json");
|
|
2065
|
+
if (yield* fs.exists(preState).pipe(Effect.mapError(workspaceError(preState)))) return yield* new UnsupportedFeatureError({
|
|
2066
|
+
feature: "pre",
|
|
2067
|
+
message: "Pre mode (.changeset/pre.json) is not supported by this changesets implementation."
|
|
2068
|
+
});
|
|
2069
|
+
if (project.config.commit !== false) return yield* new UnsupportedFeatureError({
|
|
2070
|
+
feature: "commit",
|
|
2071
|
+
message: "The \"commit\" option is not supported by this changesets implementation; set it to false."
|
|
2072
|
+
});
|
|
2073
|
+
});
|
|
2074
|
+
/** `@changesets/read`: every changeset in `.changeset` and `.changeset/pre`, in directory order. */
|
|
2075
|
+
const readChangesets = Effect.fn("Changesets.readChangesets")(function* (packages, since) {
|
|
2076
|
+
const base = changesetDir(packages);
|
|
2077
|
+
const top = yield* readSorted(fs, base).pipe(Effect.mapError(workspaceError(base)));
|
|
2078
|
+
const preDir = path.join(base, "pre");
|
|
2079
|
+
const pre = (yield* fs.exists(preDir).pipe(Effect.mapError(workspaceError(preDir)))) ? (yield* readSorted(fs, preDir).pipe(Effect.mapError(workspaceError(preDir)))).map((f) => `pre/${f}`) : [];
|
|
2080
|
+
const changed = since === void 0 ? void 0 : (yield* git.changedChangesetFilesSince(since, base)).map((file) => file.replace(/^.*\.changeset\//, ""));
|
|
2081
|
+
const files = [...top, ...pre].filter((file) => changed === void 0 || changed.includes(file)).filter((file) => {
|
|
2082
|
+
const name = path.basename(file);
|
|
2083
|
+
return !name.startsWith(".") && name.endsWith(".md") && !ignoredMarkdown(name);
|
|
2084
|
+
});
|
|
2085
|
+
return yield* Effect.forEach(files, (file) => Effect.gen(function* () {
|
|
2086
|
+
const full = path.join(base, file);
|
|
2087
|
+
const contents = yield* fs.readFileString(full).pipe(Effect.mapError(workspaceError(full)));
|
|
2088
|
+
const parsed = parseChangesetFile(contents);
|
|
2089
|
+
if (Result.isFailure(parsed)) return yield* new ChangesetParseError({
|
|
2090
|
+
message: parsed.failure,
|
|
2091
|
+
file: full
|
|
2092
|
+
});
|
|
2093
|
+
return {
|
|
2094
|
+
...parsed.success,
|
|
2095
|
+
id: file.replace(/\.md$/, "")
|
|
2096
|
+
};
|
|
2097
|
+
}));
|
|
2098
|
+
});
|
|
2099
|
+
const plan = (changesets, packages, config) => {
|
|
2100
|
+
const result = assembleReleasePlan(changesets, packages, config);
|
|
2101
|
+
return Result.isSuccess(result) ? Effect.succeed(result.success) : Effect.fail(new ReleasePlanError({ message: result.failure }));
|
|
2102
|
+
};
|
|
2103
|
+
const logGraphWarnings = (packages, config) => Effect.forEach(dependentsGraph(packages, { bumpVersionsWithWorkspaceProtocolOnly: config.bumpVersionsWithWorkspaceProtocolOnly }).invalid, (line) => Console.error(line), { discard: true });
|
|
2104
|
+
const changelogFunctions = Effect.fn("Changesets.changelogFunctions")(function* (reference, packages) {
|
|
2105
|
+
const [specifier] = reference;
|
|
2106
|
+
if (builtinChangelogs.has(specifier)) return gitChangelog;
|
|
2107
|
+
if (!specifier.startsWith(".") && !path.isAbsolute(specifier)) return yield* new UnsupportedFeatureError({
|
|
2108
|
+
feature: "changelog",
|
|
2109
|
+
message: `Changelog module "${specifier}" is not supported; use "@changesets/cli/changelog", false, or a path relative to .changeset.`
|
|
2110
|
+
});
|
|
2111
|
+
const file = path.resolve(changesetDir(packages), specifier);
|
|
2112
|
+
const loaded = yield* Effect.tryPromise({
|
|
2113
|
+
try: () => import(file),
|
|
2114
|
+
catch: (cause) => new ChangelogError({
|
|
2115
|
+
message: `Could not load ${file}`,
|
|
2116
|
+
module: specifier,
|
|
2117
|
+
cause
|
|
2118
|
+
})
|
|
2119
|
+
});
|
|
2120
|
+
return yield* changelogFromModule(loaded, specifier);
|
|
2121
|
+
});
|
|
2122
|
+
const applyPlan = Effect.fn("Changesets.applyPlan")(function* (releasePlan, packages, config) {
|
|
2123
|
+
const byName = new Map(packages.packages.map((pkg) => [pkg.packageJson.name, pkg]));
|
|
2124
|
+
const releases = releasePlan.releases.flatMap((release) => {
|
|
2125
|
+
const pkg = byName.get(release.name);
|
|
2126
|
+
return pkg === void 0 ? [] : [{
|
|
2127
|
+
...release,
|
|
2128
|
+
...pkg,
|
|
2129
|
+
relativeDir: relativeDirOf(packages.rootDir, pkg)
|
|
2130
|
+
}];
|
|
2131
|
+
});
|
|
2132
|
+
const updateOptions = {
|
|
2133
|
+
rootDir: packages.rootDir,
|
|
2134
|
+
updateInternalDependencies: config.updateInternalDependencies,
|
|
2135
|
+
onlyUpdatePeerDependentsWhenOutOfRange: config.onlyUpdatePeerDependentsWhenOutOfRange
|
|
2136
|
+
};
|
|
2137
|
+
const changelogs = yield* changelogsFor(releasePlan, releases, packages, config, updateOptions);
|
|
2138
|
+
const editOptions = {
|
|
2139
|
+
...updateOptions,
|
|
2140
|
+
bumpVersionsWithWorkspaceProtocolOnly: config.bumpVersionsWithWorkspaceProtocolOnly
|
|
2141
|
+
};
|
|
2142
|
+
const written = yield* Effect.forEach(releases, (release, index) => Effect.gen(function* () {
|
|
2143
|
+
const versionEdit = release.newVersion === void 0 ? [] : [{
|
|
2144
|
+
keys: ["version"],
|
|
2145
|
+
value: release.newVersion
|
|
2146
|
+
}];
|
|
2147
|
+
yield* updatePackageJson(release, [...dependencyEdits(release.packageJson, releases, editOptions), ...versionEdit]);
|
|
2148
|
+
const changelog = changelogs[index];
|
|
2149
|
+
if (changelog === void 0 || changelog.length === 0) return Option.none();
|
|
2150
|
+
const file = path.resolve(release.dir, "CHANGELOG.md");
|
|
2151
|
+
const existing = yield* fs.readFileString(file).pipe(Effect.map(Option.some), Effect.catchReason("PlatformError", "NotFound", () => Effect.succeed(Option.none())), Effect.mapError(workspaceError(file)));
|
|
2152
|
+
yield* fs.writeFileString(file, prependEntry(Option.getOrUndefined(existing), changelog, release.name)).pipe(Effect.mapError(workspaceError(file)));
|
|
2153
|
+
return Option.some(file);
|
|
2154
|
+
}));
|
|
2155
|
+
yield* updatePackageJson(packages.rootPackage, dependencyEdits(packages.rootPackage.packageJson, releases, editOptions));
|
|
2156
|
+
yield* formatter.format(config.format, packages.rootDir, written.flatMap(Option.toArray));
|
|
2157
|
+
yield* Effect.forEach(releasePlan.changesets, (changeset) => Effect.gen(function* () {
|
|
2158
|
+
const file = path.resolve(changesetDir(packages), `${changeset.id}.md`);
|
|
2159
|
+
if (changeset.releases.some((r) => {
|
|
2160
|
+
const pkg = byName.get(r.name);
|
|
2161
|
+
return pkg !== void 0 && shouldSkipPackage(pkg, skipOptions(config));
|
|
2162
|
+
}) || !(yield* fs.exists(file).pipe(Effect.mapError(workspaceError(file))))) return;
|
|
2163
|
+
yield* fs.remove(file, { force: true }).pipe(Effect.mapError(workspaceError(file)));
|
|
2164
|
+
}), { discard: true });
|
|
2165
|
+
});
|
|
2166
|
+
const changelogsFor = Effect.fn("Changesets.changelogsFor")(function* (releasePlan, releases, packages, config, options) {
|
|
2167
|
+
if (config.changelog === false) return releases.map(() => void 0);
|
|
2168
|
+
const functions = yield* changelogFunctions(config.changelog, packages);
|
|
2169
|
+
const commits = yield* git.commitsThatAddFiles(releasePlan.changesets.map((changeset) => `.changeset/${changeset.id}.md`), packages.rootDir);
|
|
2170
|
+
const withCommits = releasePlan.changesets.map((changeset, i) => ({
|
|
2171
|
+
...changeset,
|
|
2172
|
+
commit: commits[i]
|
|
2173
|
+
}));
|
|
2174
|
+
return yield* Effect.forEach(releases, (release) => changelogEntry(release, releases, withCommits, functions, config.changelog === false ? null : config.changelog[1], options));
|
|
2175
|
+
});
|
|
2176
|
+
const updatePackageJson = (pkg, edits) => Effect.gen(function* () {
|
|
2177
|
+
if (edits.length === 0) return;
|
|
2178
|
+
const file = path.resolve(pkg.dir, "package.json");
|
|
2179
|
+
const text = yield* fs.readFileString(file).pipe(Effect.mapError(workspaceError(file)));
|
|
2180
|
+
const edited = editJson(text, edits);
|
|
2181
|
+
if (Result.isFailure(edited)) return yield* new JsonEditError({
|
|
2182
|
+
message: edited.failure,
|
|
2183
|
+
file
|
|
2184
|
+
});
|
|
2185
|
+
yield* fs.writeFileString(file, edited.success).pipe(Effect.mapError(workspaceError(file)));
|
|
2186
|
+
});
|
|
2187
|
+
/** `changeset version [--ignore pkg]`. */
|
|
2188
|
+
const version = Effect.fn("Changesets.version")(function* (options) {
|
|
2189
|
+
const project = yield* load(options.cwd);
|
|
2190
|
+
yield* Effect.forEach(project.warnings, (warning) => Console.warn(warning), { discard: true });
|
|
2191
|
+
const { packages } = project;
|
|
2192
|
+
const messages = [];
|
|
2193
|
+
const cliIgnore = options.ignore !== void 0 && options.ignore.length > 0 ? options.ignore : void 0;
|
|
2194
|
+
if (cliIgnore !== void 0 && project.config.ignore.length > 0) messages.push("It looks like you are trying to use the `--ignore` option while ignore is defined in the config file. This is currently not allowed, you can only use one of them at a time.");
|
|
2195
|
+
const config = {
|
|
2196
|
+
...project.config,
|
|
2197
|
+
ignore: cliIgnore ?? project.config.ignore
|
|
2198
|
+
};
|
|
2199
|
+
const names = new Set(packages.packages.map((pkg) => pkg.packageJson.name));
|
|
2200
|
+
(cliIgnore ?? []).filter((name) => !names.has(name)).forEach((name) => messages.push(`The package ${name} is passed to the \`--ignore\` option but it is not found in the project. You may have misspelled the package name.`));
|
|
2201
|
+
skippedDependentErrors(packages, config).forEach(({ dependent, skipped }) => messages.push(`The package ${dependent} depends on the skipped package ${skipped} (either by \`ignore\` option or by \`privatePackages.version\`), but ${dependent} is not being skipped. Please pass ${dependent} to the --ignore flag.`));
|
|
2202
|
+
if (messages.length > 0) return yield* new CommandExit({
|
|
2203
|
+
code: 1,
|
|
2204
|
+
message: messages.join("\n")
|
|
2205
|
+
});
|
|
2206
|
+
yield* refuseUnsupported(project);
|
|
2207
|
+
const changesets = yield* readChangesets(packages);
|
|
2208
|
+
if (changesets.length === 0) return yield* new CommandExit({
|
|
2209
|
+
code: 1,
|
|
2210
|
+
message: "No unreleased changesets found."
|
|
2211
|
+
});
|
|
2212
|
+
yield* logGraphWarnings(packages, config);
|
|
2213
|
+
const releasePlan = yield* plan(changesets, packages, config);
|
|
2214
|
+
yield* applyPlan(releasePlan, packages, config);
|
|
2215
|
+
return releasePlan;
|
|
2216
|
+
});
|
|
2217
|
+
/** Workspace packages changed since `ref` that changesets would version. */
|
|
2218
|
+
const changedPackages = Effect.fn("Changesets.changedPackages")(function* (project, ref) {
|
|
2219
|
+
const files = [...yield* git.changedFilesSince(ref ?? project.config.baseBranch, project.packages.rootDir)];
|
|
2220
|
+
return project.packages.packages.toSorted((a, b) => b.dir.length - a.dir.length).filter((pkg) => {
|
|
2221
|
+
const owned = files.filter((file) => !path.relative(pkg.dir, file).startsWith(".."));
|
|
2222
|
+
owned.forEach((file) => files.splice(files.indexOf(file), 1));
|
|
2223
|
+
const relative = owned.map((file) => file.slice(pkg.dir.length + 1));
|
|
2224
|
+
return relative.length > 0 && matchesSome(relative, project.config.changedFilePatterns);
|
|
2225
|
+
}).filter((pkg) => !shouldSkipPackage(pkg, skipOptions(project.config)));
|
|
2226
|
+
});
|
|
2227
|
+
return {
|
|
2228
|
+
load,
|
|
2229
|
+
refuseUnsupported,
|
|
2230
|
+
readChangesets,
|
|
2231
|
+
version,
|
|
2232
|
+
status: Effect.fn("Changesets.status")(function* (options) {
|
|
2233
|
+
const project = yield* load(options.cwd);
|
|
2234
|
+
yield* refuseUnsupported(project);
|
|
2235
|
+
const changesets = yield* readChangesets(project.packages, options.since);
|
|
2236
|
+
const releasePlan = yield* plan(changesets, project.packages, project.config);
|
|
2237
|
+
if ((yield* changedPackages(project, options.since)).length > 0 && releasePlan.changesets.length === 0) return yield* new CommandExit({
|
|
2238
|
+
code: 1,
|
|
2239
|
+
message: "Some packages have been changed but no changesets were found. Run `changeset add` to resolve this error.\nIf this change doesn't need a release, run `changeset add --empty`."
|
|
2240
|
+
});
|
|
2241
|
+
if (options.output !== void 0) {
|
|
2242
|
+
const file = path.resolve(options.cwd, options.output);
|
|
2243
|
+
yield* fs.writeFileString(file, JSON.stringify(releasePlan, void 0, 2)).pipe(Effect.mapError(workspaceError(file)));
|
|
2244
|
+
}
|
|
2245
|
+
return releasePlan;
|
|
2246
|
+
}),
|
|
2247
|
+
changedPackages,
|
|
2248
|
+
writeChangeset: Effect.fn("Changesets.writeChangeset")(function* (project, changeset) {
|
|
2249
|
+
const base = changesetDir(project.packages);
|
|
2250
|
+
const pickId = Effect.flatMap(humanId, (id) => Effect.flatMap(fs.exists(path.join(base, `${id}.md`)).pipe(Effect.mapError(workspaceError(base))), (taken) => taken ? pickId : Effect.succeed(id)));
|
|
2251
|
+
const id = yield* pickId;
|
|
2252
|
+
const file = path.join(base, `${id}.md`);
|
|
2253
|
+
const contents = `---\n${changeset.releases.map((r) => `"${r.name}": ${r.type}`).join("\n")}\n---\n\n${changeset.summary}\n`;
|
|
2254
|
+
yield* fs.makeDirectory(base, { recursive: true }).pipe(Effect.mapError(workspaceError(base)));
|
|
2255
|
+
yield* fs.writeFileString(file, contents).pipe(Effect.mapError(workspaceError(file)));
|
|
2256
|
+
yield* formatter.format(project.config.format, project.packages.rootDir, [file]);
|
|
2257
|
+
return {
|
|
2258
|
+
id,
|
|
2259
|
+
file
|
|
2260
|
+
};
|
|
2261
|
+
})
|
|
2262
|
+
};
|
|
2263
|
+
}) }) {
|
|
2264
|
+
static layer = Layer.effect(this, this.make).pipe(Layer.provide([Git.layer, Formatter.layer]));
|
|
2265
|
+
};
|
|
2266
|
+
const matchesSome = (paths, patterns) => paths.some((file) => matchesPatterns(file.replaceAll("\\", "/"), patterns));
|
|
2267
|
+
const isModuleFunction = (value) => typeof value === "function";
|
|
2268
|
+
const findFunctions = (module, depth) => {
|
|
2269
|
+
if (Predicate.hasProperty(module, "default") && module.default && depth < 2) return findFunctions(module.default, depth + 1);
|
|
2270
|
+
return Predicate.hasProperty(module, "getReleaseLine") && Predicate.hasProperty(module, "getDependencyReleaseLine") && isModuleFunction(module.getReleaseLine) && isModuleFunction(module.getDependencyReleaseLine) ? Option.some({
|
|
2271
|
+
getReleaseLine: module.getReleaseLine,
|
|
2272
|
+
getDependencyReleaseLine: module.getDependencyReleaseLine
|
|
2273
|
+
}) : Option.none();
|
|
2274
|
+
};
|
|
2275
|
+
const callModule = (fn, args, specifier) => Effect.tryPromise({
|
|
2276
|
+
try: () => Promise.resolve(fn(...args)),
|
|
2277
|
+
catch: (cause) => new ChangelogError({
|
|
2278
|
+
message: `Changelog module ${specifier} failed`,
|
|
2279
|
+
module: specifier,
|
|
2280
|
+
cause
|
|
2281
|
+
})
|
|
2282
|
+
}).pipe(Effect.map((line) => typeof line === "string" ? line : String(line ?? "")));
|
|
2283
|
+
/** Adapts a changelog module (`getReleaseLine`, `getDependencyReleaseLine`) to Effects. */
|
|
2284
|
+
const changelogFromModule = (module, specifier) => Option.match(findFunctions(module, 0), {
|
|
2285
|
+
onNone: () => Effect.fail(new ChangelogError({
|
|
2286
|
+
message: "Could not resolve changelog generation functions",
|
|
2287
|
+
module: specifier
|
|
2288
|
+
})),
|
|
2289
|
+
onSome: ({ getReleaseLine, getDependencyReleaseLine }) => Effect.succeed({
|
|
2290
|
+
getReleaseLine: (changeset, type, options) => callModule(getReleaseLine, [
|
|
2291
|
+
changeset,
|
|
2292
|
+
type,
|
|
2293
|
+
options
|
|
2294
|
+
], specifier),
|
|
2295
|
+
getDependencyReleaseLine: (changesets, dependencies, options) => callModule(getDependencyReleaseLine, [
|
|
2296
|
+
changesets,
|
|
2297
|
+
dependencies,
|
|
2298
|
+
options
|
|
2299
|
+
], specifier)
|
|
2300
|
+
})
|
|
2301
|
+
});
|
|
2302
|
+
// src/cli.ts
|
|
2303
|
+
/**
|
|
2304
|
+
* The `changeset` command line: `add` (also the default), `version` and `status` with the
|
|
2305
|
+
* `@changesets/cli` 3.x flags. Editors are spawned without a shell, `$EDITOR` split on whitespace.
|
|
2306
|
+
*/
|
|
2307
|
+
const version = "0.1.0";
|
|
2308
|
+
const listFlag = (name, description) => Flag.String(name).pipe(Flag.withDescription(description), Flag.atLeast(0), Flag.map((values) => values.flatMap((value) => value.split(",").map((part) => part.trim()).filter(Boolean))));
|
|
2309
|
+
const addFlags = {
|
|
2310
|
+
empty: Flag.Boolean("empty").pipe(Flag.withDescription("Add an empty changeset"), Flag.withDefault(false)),
|
|
2311
|
+
open: Flag.Boolean("open").pipe(Flag.withDescription("Open the changeset in $VISUAL or $EDITOR after creating it"), Flag.withDefault(false)),
|
|
2312
|
+
since: Flag.String("since").pipe(Flag.withDescription("Detect changed packages since the provided git ref"), Flag.optional),
|
|
2313
|
+
message: Flag.String("message").pipe(Flag.withAlias("m"), Flag.withDescription("Directly provide a message to the changeset"), Flag.optional),
|
|
2314
|
+
major: listFlag("major", "Package(s) to major bump (comma-separated)"),
|
|
2315
|
+
minor: listFlag("minor", "Package(s) to minor bump (comma-separated)"),
|
|
2316
|
+
patch: listFlag("patch", "Package(s) to patch bump (comma-separated)")
|
|
2317
|
+
};
|
|
2318
|
+
const cwd = Effect.gen(function* () {
|
|
2319
|
+
return (yield* Path.Path).resolve(".");
|
|
2320
|
+
});
|
|
2321
|
+
const success = (message) => Console.log(`š¦ ${message}`);
|
|
2322
|
+
const warn = (message) => Console.warn(`š¦ warn ${message.split("\n").join("\nš¦ warn ")}`);
|
|
2323
|
+
const ask = (prompt) => Prompt.run(prompt).pipe(Effect.catchTag("QuitError", () => Effect.fail(new CommandExit({
|
|
2324
|
+
code: 0,
|
|
2325
|
+
message: "Canceled... š"
|
|
2326
|
+
}))));
|
|
2327
|
+
const editorCommand = Effect.gen(function* () {
|
|
2328
|
+
const visual = yield* Config.option(Config.String("VISUAL"));
|
|
2329
|
+
const editor = yield* Config.option(Config.String("EDITOR"));
|
|
2330
|
+
return Option.orElse(visual, () => editor).pipe(Option.map((value) => value.trim().split(/\s+/)), Option.filter((parts) => parts.length > 0 && parts[0] !== ""));
|
|
2331
|
+
}).pipe(Effect.orElseSucceed(() => Option.none()));
|
|
2332
|
+
/** Opens `file` in the user's editor and waits for it to exit. */
|
|
2333
|
+
const openInEditor = (file) => Effect.gen(function* () {
|
|
2334
|
+
const command = yield* editorCommand;
|
|
2335
|
+
if (Option.isNone(command)) return false;
|
|
2336
|
+
const [bin = "", ...args] = command.value;
|
|
2337
|
+
const exit = yield* (yield* ChildProcessSpawner.ChildProcessSpawner).exitCode(ChildProcess.make(bin, [...args, file], {
|
|
2338
|
+
stdin: "inherit",
|
|
2339
|
+
stdout: "inherit",
|
|
2340
|
+
stderr: "inherit"
|
|
2341
|
+
}));
|
|
2342
|
+
return Number(exit) === 0;
|
|
2343
|
+
}).pipe(Effect.orElseSucceed(() => false));
|
|
2344
|
+
const summaryFromEditor = Effect.gen(function* () {
|
|
2345
|
+
const fs = yield* FileSystem.FileSystem;
|
|
2346
|
+
const path = yield* Path.Path;
|
|
2347
|
+
const dir = yield* fs.makeTempDirectory();
|
|
2348
|
+
const file = path.join(dir, "changeset.md");
|
|
2349
|
+
yield* fs.writeFileString(file, "\n\n# Please enter a summary for your changes.\n# An empty message aborts the editor.");
|
|
2350
|
+
const contents = (yield* openInEditor(file)) ? yield* fs.readFileString(file) : "";
|
|
2351
|
+
yield* fs.remove(dir, { recursive: true });
|
|
2352
|
+
return contents.replace(/^#.*\n?/gm, "").replace(/\n+$/g, "").trim();
|
|
2353
|
+
}).pipe(Effect.orElseSucceed(() => ""));
|
|
2354
|
+
const askSummary = Effect.gen(function* () {
|
|
2355
|
+
const typed = yield* ask(Prompt.String({ message: "Please enter a summary for this change (this will be in the changelogs). (submit nothing to open an external editor)" }));
|
|
2356
|
+
if (typed.length > 0) return typed;
|
|
2357
|
+
const edited = yield* summaryFromEditor;
|
|
2358
|
+
if (edited.length > 0) return edited;
|
|
2359
|
+
return yield* ask(Prompt.String({
|
|
2360
|
+
message: "Did not find a summary in the edited file. Please enter one:",
|
|
2361
|
+
validate: (value) => value.length === 0 ? Effect.fail("Can't be empty.") : Effect.succeed(value)
|
|
2362
|
+
}));
|
|
2363
|
+
});
|
|
2364
|
+
const confirmMajor = (pkg) => pkg.packageJson.version !== void 0 && lessThan(pkg.packageJson.version, "1.0.0") ? ask(Prompt.Confirm({
|
|
2365
|
+
message: `The major version of ${pkg.packageJson.name} will be its first major release (1.0.0). Are you sure you want to release the first major version of ${pkg.packageJson.name}?`,
|
|
2366
|
+
initial: true
|
|
2367
|
+
})) : Effect.succeed(true);
|
|
2368
|
+
const releasesFromFlags = (options, packages) => {
|
|
2369
|
+
const names = new Set(packages.map((pkg) => pkg.packageJson.name));
|
|
2370
|
+
const byType = [
|
|
2371
|
+
["major", [...new Set(options.major)]],
|
|
2372
|
+
["minor", [...new Set(options.minor)]],
|
|
2373
|
+
["patch", [...new Set(options.patch)]]
|
|
2374
|
+
];
|
|
2375
|
+
const unknown = byType.flatMap(([type, list]) => list.filter((name) => !names.has(name)).map((name) => `The package ${name} is passed to the \`--${type}\` option but it is not found in the project. You may have misspelled the package name.`));
|
|
2376
|
+
const duplicated = [...names].flatMap((name) => {
|
|
2377
|
+
const flags = byType.filter(([, list]) => list.includes(name)).map(([type]) => `\`--${type}\``);
|
|
2378
|
+
return flags.length > 1 ? [`The package ${name} is passed to multiple release type options: ${flags.join(", ")}. Please select only one release type for this package.`] : [];
|
|
2379
|
+
});
|
|
2380
|
+
const problems = [...unknown, ...duplicated];
|
|
2381
|
+
return problems.length > 0 ? Effect.fail(new CommandExit({
|
|
2382
|
+
code: 1,
|
|
2383
|
+
message: problems.join("\n")
|
|
2384
|
+
})) : Effect.succeed(byType.flatMap(([type, list]) => list.map((name) => ({
|
|
2385
|
+
name,
|
|
2386
|
+
type
|
|
2387
|
+
}))));
|
|
2388
|
+
};
|
|
2389
|
+
const pickReleases = Effect.fn("pickReleases")(function* (changed, packages) {
|
|
2390
|
+
const byName = new Map(packages.map((pkg) => [pkg.packageJson.name, pkg]));
|
|
2391
|
+
const label = (name) => `${name}@${byName.get(name)?.packageJson.version ?? ""}`;
|
|
2392
|
+
const [only, ...others] = packages;
|
|
2393
|
+
if (only !== void 0 && others.length === 0) {
|
|
2394
|
+
const pkg = only;
|
|
2395
|
+
const type = yield* ask(Prompt.Select({
|
|
2396
|
+
message: `What kind of change is this for ${pkg.packageJson.name}? (current version is ${pkg.packageJson.version})`,
|
|
2397
|
+
choices: [
|
|
2398
|
+
{
|
|
2399
|
+
title: "patch",
|
|
2400
|
+
value: "patch"
|
|
2401
|
+
},
|
|
2402
|
+
{
|
|
2403
|
+
title: "minor",
|
|
2404
|
+
value: "minor"
|
|
2405
|
+
},
|
|
2406
|
+
{
|
|
2407
|
+
title: "major",
|
|
2408
|
+
value: "major"
|
|
2409
|
+
}
|
|
2410
|
+
]
|
|
2411
|
+
}));
|
|
2412
|
+
if (type === "major" && !(yield* confirmMajor(pkg))) return yield* new CommandExit({
|
|
2413
|
+
code: 1,
|
|
2414
|
+
message: "Aborted."
|
|
2415
|
+
});
|
|
2416
|
+
return [{
|
|
2417
|
+
name: pkg.packageJson.name,
|
|
2418
|
+
type
|
|
2419
|
+
}];
|
|
2420
|
+
}
|
|
2421
|
+
const sorted = packages.toSorted((a, b) => a.packageJson.name.localeCompare(b.packageJson.name));
|
|
2422
|
+
const ordered = [...sorted.filter((pkg) => changed.includes(pkg.packageJson.name)), ...sorted.filter((pkg) => !changed.includes(pkg.packageJson.name))];
|
|
2423
|
+
const chosen = (yield* ask(Prompt.MultiSelect({
|
|
2424
|
+
message: "Which packages were affected by the changes you made?",
|
|
2425
|
+
min: 1,
|
|
2426
|
+
choices: ordered.map((pkg) => ({
|
|
2427
|
+
title: pkg.packageJson.name + (pkg.packageJson.private === true ? " (private)" : ""),
|
|
2428
|
+
value: pkg.packageJson.name,
|
|
2429
|
+
description: changed.includes(pkg.packageJson.name) ? "changed" : void 0
|
|
2430
|
+
}))
|
|
2431
|
+
}))).toSorted((a, b) => a.localeCompare(b));
|
|
2432
|
+
const majorPicked = yield* ask(Prompt.MultiSelect({
|
|
2433
|
+
message: "Which packages should have a major (X.0.0) bump?",
|
|
2434
|
+
choices: chosen.map((name) => ({
|
|
2435
|
+
title: label(name),
|
|
2436
|
+
value: name
|
|
2437
|
+
}))
|
|
2438
|
+
}));
|
|
2439
|
+
const majors = yield* Effect.filter(majorPicked, (name) => {
|
|
2440
|
+
const pkg = byName.get(name);
|
|
2441
|
+
return pkg === void 0 ? Effect.succeed(false) : confirmMajor(pkg);
|
|
2442
|
+
});
|
|
2443
|
+
const afterMajor = chosen.filter((name) => !majors.includes(name));
|
|
2444
|
+
const minors = afterMajor.length === 0 ? [] : yield* ask(Prompt.MultiSelect({
|
|
2445
|
+
message: "Which packages should have a minor (0.X.0) bump?",
|
|
2446
|
+
choices: afterMajor.map((name) => ({
|
|
2447
|
+
title: label(name),
|
|
2448
|
+
value: name
|
|
2449
|
+
}))
|
|
2450
|
+
}));
|
|
2451
|
+
const patches = afterMajor.filter((name) => !minors.includes(name));
|
|
2452
|
+
if (patches.length > 0) yield* success(`The following packages will be patch (0.0.X) bumped:\n${patches.map(label).join(", ")}`);
|
|
2453
|
+
return [
|
|
2454
|
+
...majors.map((name) => ({
|
|
2455
|
+
name,
|
|
2456
|
+
type: "major"
|
|
2457
|
+
})),
|
|
2458
|
+
...minors.map((name) => ({
|
|
2459
|
+
name,
|
|
2460
|
+
type: "minor"
|
|
2461
|
+
})),
|
|
2462
|
+
...patches.map((name) => ({
|
|
2463
|
+
name,
|
|
2464
|
+
type: "patch"
|
|
2465
|
+
}))
|
|
2466
|
+
];
|
|
2467
|
+
});
|
|
2468
|
+
const changedNames = (changesets, project, since) => changesets.changedPackages(project, Option.getOrUndefined(since)).pipe(Effect.map((packages) => packages.map((pkg) => pkg.packageJson.name)), Effect.catch((error) => Effect.as(warn(`Failed to identify which packages have changed since the ${Option.isSome(since) ? "ref" : "base branch"} due to an error:\n${error.message}`), [])));
|
|
2469
|
+
const add = Effect.fn("add")(function* (options) {
|
|
2470
|
+
const changesets = yield* Changesets;
|
|
2471
|
+
const project = yield* changesets.load(yield* cwd);
|
|
2472
|
+
yield* changesets.refuseUnsupported(project);
|
|
2473
|
+
const versionable = project.packages.packages.filter((pkg) => !shouldSkipPackage(pkg, skipOptions(project.config)));
|
|
2474
|
+
if (project.packages.packages.length === 0 || versionable.length === 0) return yield* new CommandExit({
|
|
2475
|
+
code: 1,
|
|
2476
|
+
message: "No versionable packages found\n Ensure the packages to version are not ignored by the config\n Ensure that relevant package.json files have a `version` field"
|
|
2477
|
+
});
|
|
2478
|
+
const fromFlags = options.major.length + options.minor.length + options.patch.length > 0;
|
|
2479
|
+
const releases = options.empty ? [] : fromFlags ? yield* releasesFromFlags(options, versionable) : yield* pickReleases(yield* changedNames(changesets, project, options.since), versionable);
|
|
2480
|
+
const summary = Option.isSome(options.message) ? options.message.value : options.empty ? "" : yield* askSummary;
|
|
2481
|
+
if (!options.empty) {
|
|
2482
|
+
const summaryLines = [
|
|
2483
|
+
"major",
|
|
2484
|
+
"minor",
|
|
2485
|
+
"patch"
|
|
2486
|
+
].flatMap((type) => {
|
|
2487
|
+
const names = releases.filter((r) => r.type === type).map((r) => r.name);
|
|
2488
|
+
return names.length > 0 ? [`${type}: ${names.join(", ")}`] : [];
|
|
2489
|
+
});
|
|
2490
|
+
yield* success(["Summary of changesets:", ...summaryLines].join("\n"));
|
|
2491
|
+
}
|
|
2492
|
+
const { file } = yield* changesets.writeChangeset(project, {
|
|
2493
|
+
releases,
|
|
2494
|
+
summary
|
|
2495
|
+
});
|
|
2496
|
+
const relative = (yield* Path.Path).relative(yield* cwd, file);
|
|
2497
|
+
if (releases.some((r) => r.type === "major")) yield* warn("This Changeset includes a major change and we STRONGLY recommend adding more information to the changeset:\n WHAT the breaking change is\n WHY the change was made\n HOW a consumer should update their code");
|
|
2498
|
+
yield* success(`${options.empty ? "Empty " : ""}Changeset added - you can now commit it!\n${relative}`);
|
|
2499
|
+
if (options.open) yield* openInEditor(relative);
|
|
2500
|
+
});
|
|
2501
|
+
const printStatus = (releases, verbose) => {
|
|
2502
|
+
const sorted = releases.toSorted((a, b) => a.name.localeCompare(b.name));
|
|
2503
|
+
const groups = [
|
|
2504
|
+
"major",
|
|
2505
|
+
"minor",
|
|
2506
|
+
"patch"
|
|
2507
|
+
].flatMap((type) => {
|
|
2508
|
+
const group = sorted.filter((r) => r.type === type);
|
|
2509
|
+
if (group.length === 0) return [];
|
|
2510
|
+
return [`- ${type}`, ...group.flatMap((r) => [` - ${r.name}${verbose ? ` -> ${r.newVersion}` : ""}`, ...verbose ? r.changesets.map((id) => ` - .changeset/${id}.md`) : []])];
|
|
2511
|
+
});
|
|
2512
|
+
return Console.log(`š¦ info Packages to be bumped:\n${groups.join("\n")}`.trim());
|
|
2513
|
+
};
|
|
2514
|
+
const unsupported = (name) => Command.make(name, {}, () => Effect.fail(new UnsupportedFeatureError({
|
|
2515
|
+
feature: name,
|
|
2516
|
+
message: `\`changeset ${name}\` is not part of this changesets implementation. Only add, version and status are.`
|
|
2517
|
+
}))).pipe(Command.withDescription(`Not supported (use @changesets/cli for ${name})`));
|
|
2518
|
+
const addCommand = Command.make("add", addFlags, add).pipe(Command.withDescription("Add a new changeset (default)"));
|
|
2519
|
+
const versionCommand = Command.make("version", {
|
|
2520
|
+
ignore: listFlag("ignore", "Packages to ignore"),
|
|
2521
|
+
snapshot: Flag.String("snapshot").pipe(Flag.withDescription("Not supported"), Flag.optional)
|
|
2522
|
+
}, (options) => Effect.gen(function* () {
|
|
2523
|
+
if (Option.isSome(options.snapshot)) return yield* new UnsupportedFeatureError({
|
|
2524
|
+
feature: "snapshot",
|
|
2525
|
+
message: "Snapshot releases are not supported."
|
|
2526
|
+
});
|
|
2527
|
+
yield* (yield* Changesets).version({
|
|
2528
|
+
cwd: yield* cwd,
|
|
2529
|
+
ignore: options.ignore
|
|
2530
|
+
});
|
|
2531
|
+
yield* success("All files have been updated. Review them and commit at your leisure");
|
|
2532
|
+
})).pipe(Command.withDescription("Version packages and create changelogs"));
|
|
2533
|
+
const statusCommand = Command.make("status", {
|
|
2534
|
+
since: Flag.String("since").pipe(Flag.withDescription("Show changesets since the provided git ref"), Flag.optional),
|
|
2535
|
+
verbose: Flag.Boolean("verbose").pipe(Flag.withAlias("v"), Flag.withDescription("Show more information about the changesets"), Flag.withDefault(false)),
|
|
2536
|
+
output: Flag.String("output").pipe(Flag.withAlias("o"), Flag.withDescription("Output the status as JSON to a file"), Flag.withFallbackConfig(Config.String("CHANGESETS_OUTPUT")), Flag.optional)
|
|
2537
|
+
}, (options) => Effect.gen(function* () {
|
|
2538
|
+
const plan = yield* (yield* Changesets).status({
|
|
2539
|
+
cwd: yield* cwd,
|
|
2540
|
+
since: Option.getOrUndefined(options.since),
|
|
2541
|
+
output: Option.getOrUndefined(options.output)
|
|
2542
|
+
});
|
|
2543
|
+
if (Option.isNone(options.output)) yield* printStatus(plan.releases, options.verbose);
|
|
2544
|
+
})).pipe(Command.withDescription("Show the changesets that currently exist"));
|
|
2545
|
+
const changesetCommand = Command.make("changeset", addFlags, add).pipe(Command.withDescription("Manage versioning and changelogs with changesets"), Command.withSubcommands([
|
|
2546
|
+
addCommand,
|
|
2547
|
+
versionCommand,
|
|
2548
|
+
statusCommand,
|
|
2549
|
+
...[
|
|
2550
|
+
"init",
|
|
2551
|
+
"publish",
|
|
2552
|
+
"publish-plan",
|
|
2553
|
+
"pack",
|
|
2554
|
+
"pre",
|
|
2555
|
+
"git-tag",
|
|
2556
|
+
"tag"
|
|
2557
|
+
].map(unsupported)
|
|
2558
|
+
]));
|
|
2559
|
+
const ownError = (error) => Predicate.hasProperty(error, "_tag") && typeof error._tag === "string" && (error._tag.startsWith("@maple-dev/changesets/") || error._tag === "PlatformError") && Predicate.hasProperty(error, "message") && typeof error.message === "string";
|
|
2560
|
+
/** Runs the CLI; expected failures print their message once and exit non-zero. */
|
|
2561
|
+
const main = Command.run(changesetCommand, { version }).pipe(Effect.catch((error) => {
|
|
2562
|
+
if (!ownError(error)) return Effect.fail(error);
|
|
2563
|
+
if (error._tag === "@maple-dev/changesets/CommandExit" && error.message.startsWith("Canceled")) return Console.log(error.message);
|
|
2564
|
+
return Effect.andThen(Console.error(`š¦ error ${error.message.split("\n").join("\nš¦ error ")}`), Effect.fail(error));
|
|
2565
|
+
}));
|
|
2566
|
+
export { parseChangesetFile as a, CommandExit as c, GitError as d, JsonEditError as f, WorkspaceError as h, assembleReleasePlan as i, ConfigError as l, UnsupportedFeatureError as m, main as n, ChangelogError as o, ReleasePlanError as p, Changesets as r, ChangesetParseError as s, changesetCommand as t, FormatterError as u };
|