@ucdjs/release-scripts 0.1.0-beta.3 → 0.1.0-beta.30
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/dist/eta-BV8TCRDW.mjs +481 -0
- package/dist/index.d.mts +58 -113
- package/dist/index.mjs +1465 -756
- package/package.json +20 -10
package/dist/index.mjs
CHANGED
|
@@ -1,835 +1,1544 @@
|
|
|
1
|
+
import { t as Eta } from "./eta-BV8TCRDW.mjs";
|
|
2
|
+
import { Console, Context, Data, Effect, Layer, Schema } from "effect";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { Command, CommandExecutor } from "@effect/platform";
|
|
5
|
+
import { NodeCommandExecutor, NodeFileSystem } from "@effect/platform-node";
|
|
6
|
+
import * as CommitParser from "commit-parser";
|
|
1
7
|
import process from "node:process";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import farver from "farver";
|
|
5
|
-
import { exec } from "tinyexec";
|
|
6
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
7
|
-
import { join } from "node:path";
|
|
8
|
-
import prompts from "prompts";
|
|
8
|
+
import semver from "semver";
|
|
9
|
+
import fs from "node:fs/promises";
|
|
9
10
|
|
|
10
|
-
//#region src/
|
|
11
|
-
|
|
12
|
-
const debug$2 = createDebug(namespace);
|
|
13
|
-
if (debug$2.enabled) return debug$2;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
//#endregion
|
|
17
|
-
//#region src/utils.ts
|
|
18
|
-
const globalOptions = { dryRun: false };
|
|
19
|
-
async function run(bin, args, opts = {}) {
|
|
20
|
-
return exec(bin, args, {
|
|
21
|
-
throwOnError: true,
|
|
22
|
-
...opts,
|
|
23
|
-
nodeOptions: {
|
|
24
|
-
stdio: "inherit",
|
|
25
|
-
...opts.nodeOptions
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
async function dryRun(bin, args, opts) {
|
|
30
|
-
return console.log(farver.blue(`[dryrun] ${bin} ${args.join(" ")}`), opts || "");
|
|
31
|
-
}
|
|
32
|
-
const runIfNotDry = globalOptions.dryRun ? dryRun : run;
|
|
33
|
-
|
|
34
|
-
//#endregion
|
|
35
|
-
//#region src/commits.ts
|
|
36
|
-
const debug$1 = createDebugger("ucdjs:release-scripts:commits");
|
|
37
|
-
async function getLastPackageTag(packageName, workspaceRoot) {
|
|
38
|
-
const { stdout } = await run("git", ["tag", "--list"], { nodeOptions: {
|
|
39
|
-
cwd: workspaceRoot,
|
|
40
|
-
stdio: "pipe"
|
|
41
|
-
} });
|
|
42
|
-
return stdout.split("\n").map((tag) => tag.trim()).filter(Boolean).reverse().find((tag) => tag.startsWith(`${packageName}@`));
|
|
43
|
-
}
|
|
44
|
-
function determineHighestBump(commits) {
|
|
45
|
-
if (commits.length === 0) return "none";
|
|
46
|
-
let highestBump = "none";
|
|
47
|
-
for (const commit of commits) {
|
|
48
|
-
const bump = determineBumpType(commit);
|
|
49
|
-
if (bump === "major") return "major";
|
|
50
|
-
if (bump === "minor") highestBump = "minor";
|
|
51
|
-
else if (bump === "patch" && highestBump === "none") highestBump = "patch";
|
|
52
|
-
}
|
|
53
|
-
return highestBump;
|
|
54
|
-
}
|
|
55
|
-
async function getPackageCommits(pkg, workspaceRoot) {
|
|
56
|
-
const lastTag = await getLastPackageTag(pkg.name, workspaceRoot);
|
|
57
|
-
const allCommits = getCommits({
|
|
58
|
-
from: lastTag,
|
|
59
|
-
to: "HEAD"
|
|
60
|
-
});
|
|
61
|
-
debug$1?.(`Found ${allCommits.length} commits for ${pkg.name} since ${lastTag || "beginning"}`);
|
|
62
|
-
const touchedCommitHashes = await getCommitsTouchingPackage(lastTag || "HEAD", "HEAD", pkg.path, workspaceRoot);
|
|
63
|
-
const touchedSet = new Set(touchedCommitHashes);
|
|
64
|
-
const packageCommits = allCommits.filter((commit) => touchedSet.has(commit.shortHash));
|
|
65
|
-
debug$1?.(`${packageCommits.length} commits affect ${pkg.name}`);
|
|
66
|
-
return packageCommits;
|
|
67
|
-
}
|
|
68
|
-
async function analyzePackageCommits(pkg, workspaceRoot) {
|
|
69
|
-
return determineHighestBump(await getPackageCommits(pkg, workspaceRoot));
|
|
70
|
-
}
|
|
71
|
-
function determineBumpType(commit) {
|
|
72
|
-
if (commit.isBreaking) return "major";
|
|
73
|
-
if (!commit.isConventional || !commit.type) return "none";
|
|
74
|
-
switch (commit.type) {
|
|
75
|
-
case "feat": return "minor";
|
|
76
|
-
case "fix":
|
|
77
|
-
case "perf": return "patch";
|
|
78
|
-
case "docs":
|
|
79
|
-
case "style":
|
|
80
|
-
case "refactor":
|
|
81
|
-
case "test":
|
|
82
|
-
case "build":
|
|
83
|
-
case "ci":
|
|
84
|
-
case "chore":
|
|
85
|
-
case "revert": return "none";
|
|
86
|
-
default: return "none";
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
async function getCommitsTouchingPackage(from, to, packagePath, workspaceRoot) {
|
|
90
|
-
try {
|
|
91
|
-
const { stdout } = await run("git", [
|
|
92
|
-
"log",
|
|
93
|
-
"--pretty=format:%h",
|
|
94
|
-
from === "HEAD" ? "HEAD" : `${from}...${to}`,
|
|
95
|
-
"--",
|
|
96
|
-
packagePath
|
|
97
|
-
], { nodeOptions: {
|
|
98
|
-
cwd: workspaceRoot,
|
|
99
|
-
stdio: "pipe"
|
|
100
|
-
} });
|
|
101
|
-
return stdout.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
102
|
-
} catch (error) {
|
|
103
|
-
debug$1?.(`Error getting commits touching package: ${error}`);
|
|
104
|
-
return [];
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
//#endregion
|
|
109
|
-
//#region src/validation.ts
|
|
11
|
+
//#region src/utils/changelog-formatters.ts
|
|
12
|
+
const eta$1 = new Eta();
|
|
110
13
|
/**
|
|
111
|
-
*
|
|
14
|
+
* Pure function to parse commits into changelog entries
|
|
112
15
|
*/
|
|
113
|
-
function
|
|
114
|
-
return
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
16
|
+
function parseCommits(commits) {
|
|
17
|
+
return commits.filter((commit) => commit.isConventional).map((commit) => ({
|
|
18
|
+
type: commit.type || "other",
|
|
19
|
+
scope: commit.scope,
|
|
20
|
+
description: commit.description,
|
|
21
|
+
breaking: commit.isBreaking || false,
|
|
22
|
+
hash: commit.hash,
|
|
23
|
+
shortHash: commit.shortHash,
|
|
24
|
+
references: commit.references.map((ref) => ({
|
|
25
|
+
type: ref.type,
|
|
26
|
+
value: ref.value
|
|
27
|
+
}))
|
|
28
|
+
}));
|
|
118
29
|
}
|
|
119
|
-
|
|
120
|
-
//#endregion
|
|
121
|
-
//#region src/version.ts
|
|
122
30
|
/**
|
|
123
|
-
*
|
|
124
|
-
* Pure function - no side effects, easily testable
|
|
31
|
+
* Pure function to group changelog entries by type
|
|
125
32
|
*/
|
|
126
|
-
function
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
let newMajor = Number.parseInt(major, 10);
|
|
133
|
-
let newMinor = Number.parseInt(minor, 10);
|
|
134
|
-
let newPatch = Number.parseInt(patch, 10);
|
|
135
|
-
switch (bump) {
|
|
136
|
-
case "major":
|
|
137
|
-
newMajor += 1;
|
|
138
|
-
newMinor = 0;
|
|
139
|
-
newPatch = 0;
|
|
140
|
-
break;
|
|
141
|
-
case "minor":
|
|
142
|
-
newMinor += 1;
|
|
143
|
-
newPatch = 0;
|
|
144
|
-
break;
|
|
145
|
-
case "patch":
|
|
146
|
-
newPatch += 1;
|
|
147
|
-
break;
|
|
33
|
+
function groupByType(entries) {
|
|
34
|
+
const groups = /* @__PURE__ */ new Map();
|
|
35
|
+
for (const entry of entries) {
|
|
36
|
+
const type = entry.breaking ? "breaking" : entry.type;
|
|
37
|
+
if (!groups.has(type)) groups.set(type, []);
|
|
38
|
+
groups.get(type).push(entry);
|
|
148
39
|
}
|
|
149
|
-
return
|
|
40
|
+
return groups;
|
|
150
41
|
}
|
|
151
42
|
/**
|
|
152
|
-
*
|
|
43
|
+
* Changelog template for Eta rendering
|
|
153
44
|
*/
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
45
|
+
const CHANGELOG_TEMPLATE = `# <%= it.packageName %> v<%= it.version %>
|
|
46
|
+
|
|
47
|
+
**Previous version**: \`<%= it.previousVersion %>\`
|
|
48
|
+
**New version**: \`<%= it.version %>\`
|
|
49
|
+
|
|
50
|
+
<% if (it.entries.length === 0) { %>
|
|
51
|
+
*No conventional commits found.*
|
|
52
|
+
<% } else { %>
|
|
53
|
+
<% const groups = it.groupedEntries; %>
|
|
54
|
+
<% const typeOrder = ["breaking", "feat", "fix", "perf", "docs", "style", "refactor", "test", "build", "ci", "chore"]; %>
|
|
55
|
+
<% const typeLabels = {
|
|
56
|
+
breaking: "💥 Breaking Changes",
|
|
57
|
+
feat: "✨ Features",
|
|
58
|
+
fix: "🐛 Bug Fixes",
|
|
59
|
+
perf: "⚡ Performance",
|
|
60
|
+
docs: "📝 Documentation",
|
|
61
|
+
style: "💄 Styling",
|
|
62
|
+
refactor: "♻️ Refactoring",
|
|
63
|
+
test: "✅ Tests",
|
|
64
|
+
build: "📦 Build",
|
|
65
|
+
ci: "👷 CI",
|
|
66
|
+
chore: "🔧 Chores"
|
|
67
|
+
}; %>
|
|
68
|
+
|
|
69
|
+
<% for (const type of typeOrder) { %>
|
|
70
|
+
<% const entries = groups.get(type); %>
|
|
71
|
+
<% if (entries && entries.length > 0) { %>
|
|
72
|
+
## <%= typeLabels[type] || type.charAt(0).toUpperCase() + type.slice(1) %>
|
|
73
|
+
|
|
74
|
+
<% for (const entry of entries) { %>
|
|
75
|
+
- <% if (entry.scope) { %>**<%= entry.scope %>**: <% } %><%= entry.description %><% if (entry.references.length > 0) { %> (<%= entry.references.map(r => "#" + r.value).join(", ") %>)<% } %> (\`<%= entry.shortHash %>\`)
|
|
76
|
+
<% } %>
|
|
77
|
+
|
|
78
|
+
<% } %>
|
|
79
|
+
<% } %>
|
|
80
|
+
|
|
81
|
+
<% for (const [type, entries] of groups) { %>
|
|
82
|
+
<% if (!typeOrder.includes(type)) { %>
|
|
83
|
+
## <%= type.charAt(0).toUpperCase() + type.slice(1) %>
|
|
84
|
+
|
|
85
|
+
<% for (const entry of entries) { %>
|
|
86
|
+
- <% if (entry.scope) { %>**<%= entry.scope %>**: <% } %><%= entry.description %> (\`<%= entry.shortHash %>\`)
|
|
87
|
+
<% } %>
|
|
88
|
+
|
|
89
|
+
<% } %>
|
|
90
|
+
<% } %>
|
|
91
|
+
<% } %>`;
|
|
164
92
|
/**
|
|
165
|
-
*
|
|
93
|
+
* Pure function to format changelog as markdown
|
|
166
94
|
*/
|
|
167
|
-
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
177
|
-
if (packageJson.devDependencies?.[depName]) {
|
|
178
|
-
if (packageJson.devDependencies[depName] === "workspace:*") continue;
|
|
179
|
-
packageJson.devDependencies[depName] = `^${depVersion}`;
|
|
180
|
-
}
|
|
181
|
-
if (packageJson.peerDependencies?.[depName]) {
|
|
182
|
-
if (packageJson.peerDependencies[depName] === "workspace:*") continue;
|
|
183
|
-
packageJson.peerDependencies[depName] = `^${depVersion}`;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
await writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, "utf-8");
|
|
95
|
+
function formatChangelogMarkdown(changelog) {
|
|
96
|
+
const groups = groupByType(changelog.entries);
|
|
97
|
+
return eta$1.renderString(CHANGELOG_TEMPLATE, {
|
|
98
|
+
packageName: changelog.packageName,
|
|
99
|
+
version: changelog.version,
|
|
100
|
+
previousVersion: changelog.previousVersion,
|
|
101
|
+
entries: changelog.entries,
|
|
102
|
+
groupedEntries: groups
|
|
103
|
+
});
|
|
187
104
|
}
|
|
188
105
|
/**
|
|
189
|
-
*
|
|
106
|
+
* Pure function to create a changelog object
|
|
190
107
|
*/
|
|
191
|
-
function
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
return updates;
|
|
108
|
+
function createChangelog(packageName, version, previousVersion, commits) {
|
|
109
|
+
return {
|
|
110
|
+
packageName,
|
|
111
|
+
version,
|
|
112
|
+
previousVersion,
|
|
113
|
+
entries: parseCommits(commits)
|
|
114
|
+
};
|
|
199
115
|
}
|
|
200
116
|
|
|
201
117
|
//#endregion
|
|
202
|
-
//#region src/
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
*
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
118
|
+
//#region src/services/changelog.service.ts
|
|
119
|
+
var ChangelogService = class extends Effect.Service()("@ucdjs/release-scripts/ChangelogService", {
|
|
120
|
+
effect: Effect.gen(function* () {
|
|
121
|
+
function generateChangelog(pkg, newVersion, commits) {
|
|
122
|
+
return Effect.gen(function* () {
|
|
123
|
+
const changelog = createChangelog(pkg.name, newVersion, pkg.version, commits);
|
|
124
|
+
return {
|
|
125
|
+
changelog,
|
|
126
|
+
markdown: formatChangelogMarkdown(changelog),
|
|
127
|
+
filePath: `${pkg.path}/CHANGELOG.md`
|
|
128
|
+
};
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
return { generateChangelog };
|
|
132
|
+
}),
|
|
133
|
+
dependencies: []
|
|
134
|
+
}) {};
|
|
135
|
+
|
|
136
|
+
//#endregion
|
|
137
|
+
//#region src/services/dependency-graph.service.ts
|
|
138
|
+
var DependencyGraphService = class extends Effect.Service()("@ucdjs/release-scripts/DependencyGraphService", {
|
|
139
|
+
effect: Effect.gen(function* () {
|
|
140
|
+
function buildGraph(packages) {
|
|
141
|
+
const nameToPackage = /* @__PURE__ */ new Map();
|
|
142
|
+
const adjacency = /* @__PURE__ */ new Map();
|
|
143
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
144
|
+
for (const pkg of packages) {
|
|
145
|
+
nameToPackage.set(pkg.name, pkg);
|
|
146
|
+
adjacency.set(pkg.name, /* @__PURE__ */ new Set());
|
|
147
|
+
inDegree.set(pkg.name, 0);
|
|
148
|
+
}
|
|
149
|
+
for (const pkg of packages) {
|
|
150
|
+
const deps = new Set([...pkg.workspaceDependencies, ...pkg.workspaceDevDependencies]);
|
|
151
|
+
for (const depName of deps) {
|
|
152
|
+
if (!nameToPackage.has(depName)) continue;
|
|
153
|
+
adjacency.get(depName)?.add(pkg.name);
|
|
154
|
+
inDegree.set(pkg.name, (inDegree.get(pkg.name) ?? 0) + 1);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
nameToPackage,
|
|
159
|
+
adjacency,
|
|
160
|
+
inDegree
|
|
161
|
+
};
|
|
221
162
|
}
|
|
163
|
+
function topologicalOrder(packages) {
|
|
164
|
+
return Effect.gen(function* () {
|
|
165
|
+
const { nameToPackage, adjacency, inDegree } = buildGraph(packages);
|
|
166
|
+
const queue = [];
|
|
167
|
+
const levels = /* @__PURE__ */ new Map();
|
|
168
|
+
for (const [name, degree] of inDegree) if (degree === 0) {
|
|
169
|
+
queue.push(name);
|
|
170
|
+
levels.set(name, 0);
|
|
171
|
+
}
|
|
172
|
+
let queueIndex = 0;
|
|
173
|
+
const ordered = [];
|
|
174
|
+
while (queueIndex < queue.length) {
|
|
175
|
+
const current = queue[queueIndex++];
|
|
176
|
+
const currentLevel = levels.get(current) ?? 0;
|
|
177
|
+
const pkg = nameToPackage.get(current);
|
|
178
|
+
if (pkg) ordered.push({
|
|
179
|
+
package: pkg,
|
|
180
|
+
level: currentLevel
|
|
181
|
+
});
|
|
182
|
+
for (const neighbor of adjacency.get(current) ?? []) {
|
|
183
|
+
const nextLevel = currentLevel + 1;
|
|
184
|
+
if (nextLevel > (levels.get(neighbor) ?? 0)) levels.set(neighbor, nextLevel);
|
|
185
|
+
const newDegree = (inDegree.get(neighbor) ?? 0) - 1;
|
|
186
|
+
inDegree.set(neighbor, newDegree);
|
|
187
|
+
if (newDegree === 0) queue.push(neighbor);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (ordered.length !== packages.length) {
|
|
191
|
+
const processed = new Set(ordered.map((o) => o.package.name));
|
|
192
|
+
const unprocessed = packages.filter((p) => !processed.has(p.name)).map((p) => p.name);
|
|
193
|
+
return yield* Effect.fail(/* @__PURE__ */ new Error(`Cycle detected in workspace dependencies. Packages involved: ${unprocessed.join(", ")}`));
|
|
194
|
+
}
|
|
195
|
+
return ordered;
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
return { topologicalOrder };
|
|
199
|
+
}),
|
|
200
|
+
dependencies: []
|
|
201
|
+
}) {};
|
|
202
|
+
|
|
203
|
+
//#endregion
|
|
204
|
+
//#region src/errors.ts
|
|
205
|
+
var GitCommandError = class extends Data.TaggedError("GitCommandError") {};
|
|
206
|
+
var ExternalCommitParserError = class extends Data.TaggedError("ExternalCommitParserError") {};
|
|
207
|
+
var WorkspaceError = class extends Data.TaggedError("WorkspaceError") {};
|
|
208
|
+
var GitHubError = class extends Data.TaggedError("GitHubError") {};
|
|
209
|
+
var VersionCalculationError = class extends Data.TaggedError("VersionCalculationError") {};
|
|
210
|
+
var OverridesLoadError = class extends Data.TaggedError("OverridesLoadError") {};
|
|
211
|
+
var NPMError = class extends Data.TaggedError("NPMError") {};
|
|
212
|
+
var PublishError = class extends Data.TaggedError("PublishError") {};
|
|
213
|
+
var TagError = class extends Data.TaggedError("TagError") {};
|
|
214
|
+
|
|
215
|
+
//#endregion
|
|
216
|
+
//#region src/options.ts
|
|
217
|
+
const DEFAULT_PR_BODY_TEMPLATE = `## Summary\n\nThis PR contains the following changes:\n\n- Updated package versions\n- Updated changelogs\n\n## Packages\n\nThe following packages will be released:\n\n{{packages}}`;
|
|
218
|
+
const DEFAULT_CHANGELOG_TEMPLATE = `# Changelog\n\n{{releases}}`;
|
|
219
|
+
const DEFAULT_TYPES = {
|
|
220
|
+
feat: {
|
|
221
|
+
title: "🚀 Features",
|
|
222
|
+
color: "green"
|
|
223
|
+
},
|
|
224
|
+
fix: {
|
|
225
|
+
title: "🐞 Bug Fixes",
|
|
226
|
+
color: "red"
|
|
227
|
+
},
|
|
228
|
+
refactor: {
|
|
229
|
+
title: "🔧 Code Refactoring",
|
|
230
|
+
color: "blue"
|
|
231
|
+
},
|
|
232
|
+
perf: {
|
|
233
|
+
title: "🏎 Performance",
|
|
234
|
+
color: "orange"
|
|
235
|
+
},
|
|
236
|
+
docs: {
|
|
237
|
+
title: "📚 Documentation",
|
|
238
|
+
color: "purple"
|
|
239
|
+
},
|
|
240
|
+
style: {
|
|
241
|
+
title: "🎨 Styles",
|
|
242
|
+
color: "pink"
|
|
222
243
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
244
|
+
};
|
|
245
|
+
function normalizeReleaseScriptsOptions(options) {
|
|
246
|
+
const { workspaceRoot = process.cwd(), githubToken = "", repo: fullRepo, packages = true, branch = {}, globalCommitMode = "dependencies", pullRequest = {}, changelog = {}, types = {}, dryRun = false, npm = {} } = options;
|
|
247
|
+
const token = githubToken.trim();
|
|
248
|
+
if (!token) throw new Error("GitHub token is required. Pass it in via options.");
|
|
249
|
+
if (!fullRepo || !fullRepo.trim() || !fullRepo.includes("/")) throw new Error("Repository (repo) is required. Specify in 'owner/repo' format (e.g., 'octocat/hello-world').");
|
|
250
|
+
const [owner, repo] = fullRepo.split("/");
|
|
251
|
+
if (!owner || !repo) throw new Error(`Invalid repo format: "${fullRepo}". Expected format: "owner/repo" (e.g., "octocat/hello-world").`);
|
|
252
|
+
return {
|
|
253
|
+
dryRun,
|
|
254
|
+
workspaceRoot,
|
|
255
|
+
githubToken: token,
|
|
256
|
+
owner,
|
|
257
|
+
repo,
|
|
258
|
+
packages: typeof packages === "object" && !Array.isArray(packages) ? {
|
|
259
|
+
exclude: packages.exclude ?? [],
|
|
260
|
+
include: packages.include ?? [],
|
|
261
|
+
excludePrivate: packages.excludePrivate ?? false
|
|
262
|
+
} : packages,
|
|
263
|
+
branch: {
|
|
264
|
+
release: branch.release ?? "release/next",
|
|
265
|
+
default: branch.default ?? "main"
|
|
266
|
+
},
|
|
267
|
+
globalCommitMode,
|
|
268
|
+
pullRequest: {
|
|
269
|
+
title: pullRequest.title ?? "chore: release new version",
|
|
270
|
+
body: pullRequest.body ?? DEFAULT_PR_BODY_TEMPLATE
|
|
271
|
+
},
|
|
272
|
+
changelog: {
|
|
273
|
+
enabled: changelog.enabled ?? true,
|
|
274
|
+
template: changelog.template ?? DEFAULT_CHANGELOG_TEMPLATE,
|
|
275
|
+
emojis: changelog.emojis ?? true
|
|
276
|
+
},
|
|
277
|
+
types: options.types ? {
|
|
278
|
+
...DEFAULT_TYPES,
|
|
279
|
+
...types
|
|
280
|
+
} : DEFAULT_TYPES,
|
|
281
|
+
npm: {
|
|
282
|
+
otp: npm.otp,
|
|
283
|
+
provenance: npm.provenance ?? true
|
|
284
|
+
}
|
|
285
|
+
};
|
|
230
286
|
}
|
|
287
|
+
var ReleaseScriptsOptions = class extends Context.Tag("@ucdjs/release-scripts/ReleaseScriptsOptions")() {};
|
|
231
288
|
|
|
232
289
|
//#endregion
|
|
233
|
-
//#region src/git.ts
|
|
234
|
-
|
|
235
|
-
*
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
290
|
+
//#region src/services/git.service.ts
|
|
291
|
+
var GitService = class extends Effect.Service()("@ucdjs/release-scripts/GitService", {
|
|
292
|
+
effect: Effect.gen(function* () {
|
|
293
|
+
const executor = yield* CommandExecutor.CommandExecutor;
|
|
294
|
+
const config = yield* ReleaseScriptsOptions;
|
|
295
|
+
const execGitCommand = (args) => executor.string(Command.make("git", ...args).pipe(Command.workingDirectory(config.workspaceRoot))).pipe(Effect.mapError((err) => {
|
|
296
|
+
return new GitCommandError({
|
|
297
|
+
command: `git ${args.join(" ")}`,
|
|
298
|
+
stderr: err.message
|
|
299
|
+
});
|
|
300
|
+
}));
|
|
301
|
+
const execGitCommandIfNotDry = config.dryRun ? (args) => Effect.succeed(`Dry run mode: skipping git command "git ${args.join(" ")}"`) : execGitCommand;
|
|
302
|
+
const isWithinRepository = Effect.gen(function* () {
|
|
303
|
+
return (yield* execGitCommand(["rev-parse", "--is-inside-work-tree"]).pipe(Effect.catchAll(() => Effect.succeed("false")))).trim() === "true";
|
|
304
|
+
});
|
|
305
|
+
const listBranches = Effect.gen(function* () {
|
|
306
|
+
return (yield* execGitCommand(["branch", "--list"])).trim().split("\n").filter((line) => line.length > 0).map((line) => line.replace(/^\* /, "").trim()).map((line) => line.trim());
|
|
307
|
+
});
|
|
308
|
+
const isWorkingDirectoryClean = Effect.gen(function* () {
|
|
309
|
+
return (yield* execGitCommand(["status", "--porcelain"])).trim().length === 0;
|
|
310
|
+
});
|
|
311
|
+
function doesBranchExist(branch) {
|
|
312
|
+
return listBranches.pipe(Effect.map((branches) => branches.includes(branch)));
|
|
313
|
+
}
|
|
314
|
+
function createBranch(branch, base = config.branch.default) {
|
|
315
|
+
return execGitCommandIfNotDry([
|
|
316
|
+
"branch",
|
|
317
|
+
branch,
|
|
318
|
+
base
|
|
319
|
+
]);
|
|
320
|
+
}
|
|
321
|
+
const getBranch = Effect.gen(function* () {
|
|
322
|
+
return (yield* execGitCommand([
|
|
323
|
+
"rev-parse",
|
|
324
|
+
"--abbrev-ref",
|
|
325
|
+
"HEAD"
|
|
326
|
+
])).trim();
|
|
327
|
+
});
|
|
328
|
+
function checkoutBranch(branch) {
|
|
329
|
+
return execGitCommand(["checkout", branch]);
|
|
330
|
+
}
|
|
331
|
+
function rebaseBranch(onto) {
|
|
332
|
+
return execGitCommandIfNotDry(["rebase", onto]);
|
|
333
|
+
}
|
|
334
|
+
function stageChanges(files) {
|
|
335
|
+
return Effect.gen(function* () {
|
|
336
|
+
if (files.length === 0) return yield* Effect.fail(/* @__PURE__ */ new Error("No files to stage."));
|
|
337
|
+
return yield* execGitCommandIfNotDry(["add", ...files]);
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
function writeCommit(message) {
|
|
341
|
+
return execGitCommandIfNotDry([
|
|
342
|
+
"commit",
|
|
343
|
+
"-m",
|
|
344
|
+
message
|
|
345
|
+
]);
|
|
346
|
+
}
|
|
347
|
+
function writeEmptyCommit(message) {
|
|
348
|
+
return execGitCommandIfNotDry([
|
|
349
|
+
"commit",
|
|
350
|
+
"--allow-empty",
|
|
351
|
+
"-m",
|
|
352
|
+
message
|
|
353
|
+
]);
|
|
354
|
+
}
|
|
355
|
+
function pushChanges(branch, remote = "origin") {
|
|
356
|
+
return execGitCommandIfNotDry([
|
|
357
|
+
"push",
|
|
358
|
+
remote,
|
|
359
|
+
branch
|
|
360
|
+
]);
|
|
361
|
+
}
|
|
362
|
+
function forcePushChanges(branch, remote = "origin") {
|
|
363
|
+
return execGitCommandIfNotDry([
|
|
364
|
+
"push",
|
|
365
|
+
"--force-with-lease",
|
|
366
|
+
remote,
|
|
367
|
+
branch
|
|
368
|
+
]);
|
|
369
|
+
}
|
|
370
|
+
function readFile(filePath, ref = "HEAD") {
|
|
371
|
+
return execGitCommand(["show", `${ref}:${filePath}`]);
|
|
372
|
+
}
|
|
373
|
+
function getMostRecentPackageTag(packageName) {
|
|
374
|
+
return execGitCommand([
|
|
375
|
+
"tag",
|
|
376
|
+
"--list",
|
|
377
|
+
"--sort=-version:refname",
|
|
378
|
+
`${packageName}@*`
|
|
379
|
+
]).pipe(Effect.map((tags) => {
|
|
380
|
+
return tags.trim().split("\n").map((tag) => tag.trim()).filter((tag) => tag.length > 0)[0] || null;
|
|
381
|
+
}), Effect.flatMap((tag) => {
|
|
382
|
+
if (tag === null) return Effect.succeed(null);
|
|
383
|
+
return execGitCommand(["rev-parse", tag]).pipe(Effect.map((sha) => ({
|
|
384
|
+
name: tag,
|
|
385
|
+
sha: sha.trim()
|
|
386
|
+
})));
|
|
387
|
+
}));
|
|
388
|
+
}
|
|
389
|
+
function createTag(name, message) {
|
|
390
|
+
return execGitCommandIfNotDry(message ? [
|
|
391
|
+
"tag",
|
|
392
|
+
"-a",
|
|
393
|
+
name,
|
|
394
|
+
"-m",
|
|
395
|
+
message
|
|
396
|
+
] : ["tag", name]).pipe(Effect.mapError((err) => new TagError({
|
|
397
|
+
message: `Failed to create tag "${name}"`,
|
|
398
|
+
tagName: name,
|
|
399
|
+
operation: "create",
|
|
400
|
+
cause: err
|
|
401
|
+
})));
|
|
402
|
+
}
|
|
403
|
+
function pushTag(name, remote = "origin") {
|
|
404
|
+
return execGitCommandIfNotDry([
|
|
405
|
+
"push",
|
|
406
|
+
remote,
|
|
407
|
+
name
|
|
408
|
+
]).pipe(Effect.mapError((err) => new TagError({
|
|
409
|
+
message: `Failed to push tag "${name}" to ${remote}`,
|
|
410
|
+
tagName: name,
|
|
411
|
+
operation: "push",
|
|
412
|
+
cause: err
|
|
413
|
+
})));
|
|
414
|
+
}
|
|
415
|
+
function getCommits(options) {
|
|
416
|
+
return Effect.tryPromise({
|
|
417
|
+
try: async () => CommitParser.getCommits({
|
|
418
|
+
from: options?.from,
|
|
419
|
+
to: options?.to,
|
|
420
|
+
folder: options?.folder,
|
|
421
|
+
cwd: config.workspaceRoot
|
|
422
|
+
}),
|
|
423
|
+
catch: (e) => new ExternalCommitParserError({
|
|
424
|
+
message: `commit-parser getCommits`,
|
|
425
|
+
cause: e instanceof Error ? e.message : String(e)
|
|
426
|
+
})
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
function filesChangesBetweenRefs(from, to) {
|
|
430
|
+
const commitsMap = /* @__PURE__ */ new Map();
|
|
431
|
+
return execGitCommand([
|
|
432
|
+
"log",
|
|
433
|
+
"--name-only",
|
|
434
|
+
"--format=%H",
|
|
435
|
+
`${from}^..${to}`
|
|
436
|
+
]).pipe(Effect.map((output) => {
|
|
437
|
+
const lines = output.trim().split("\n").filter((line) => line.trim() !== "");
|
|
438
|
+
let currentSha = null;
|
|
439
|
+
const HASH_REGEX = /^[0-9a-f]{40}$/i;
|
|
440
|
+
for (const line of lines) {
|
|
441
|
+
const trimmedLine = line.trim();
|
|
442
|
+
if (HASH_REGEX.test(trimmedLine)) {
|
|
443
|
+
currentSha = trimmedLine;
|
|
444
|
+
commitsMap.set(currentSha, []);
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
if (currentSha === null) continue;
|
|
448
|
+
commitsMap.get(currentSha).push(trimmedLine);
|
|
449
|
+
}
|
|
450
|
+
return commitsMap;
|
|
451
|
+
}));
|
|
452
|
+
}
|
|
453
|
+
const assertWorkspaceReady = Effect.gen(function* () {
|
|
454
|
+
if (!(yield* isWithinRepository)) return yield* Effect.fail(/* @__PURE__ */ new Error("Not within a Git repository."));
|
|
455
|
+
if (!(yield* isWorkingDirectoryClean)) return yield* Effect.fail(/* @__PURE__ */ new Error("Working directory is not clean."));
|
|
456
|
+
return true;
|
|
457
|
+
});
|
|
458
|
+
return {
|
|
459
|
+
branches: {
|
|
460
|
+
list: listBranches,
|
|
461
|
+
exists: doesBranchExist,
|
|
462
|
+
create: createBranch,
|
|
463
|
+
checkout: checkoutBranch,
|
|
464
|
+
rebase: rebaseBranch,
|
|
465
|
+
get: getBranch
|
|
466
|
+
},
|
|
467
|
+
commits: {
|
|
468
|
+
stage: stageChanges,
|
|
469
|
+
write: writeCommit,
|
|
470
|
+
writeEmpty: writeEmptyCommit,
|
|
471
|
+
push: pushChanges,
|
|
472
|
+
forcePush: forcePushChanges,
|
|
473
|
+
get: getCommits,
|
|
474
|
+
filesChangesBetweenRefs
|
|
475
|
+
},
|
|
476
|
+
tags: {
|
|
477
|
+
mostRecentForPackage: getMostRecentPackageTag,
|
|
478
|
+
create: createTag,
|
|
479
|
+
push: pushTag
|
|
480
|
+
},
|
|
481
|
+
workspace: {
|
|
482
|
+
readFile,
|
|
483
|
+
isWithinRepository,
|
|
484
|
+
isWorkingDirectoryClean,
|
|
485
|
+
assertWorkspaceReady
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
}),
|
|
489
|
+
dependencies: [NodeCommandExecutor.layer]
|
|
490
|
+
}) {};
|
|
491
|
+
|
|
492
|
+
//#endregion
|
|
493
|
+
//#region src/services/github.service.ts
|
|
494
|
+
const eta = new Eta();
|
|
495
|
+
const PullRequestSchema = Schema.Struct({
|
|
496
|
+
number: Schema.Number,
|
|
497
|
+
title: Schema.String,
|
|
498
|
+
body: Schema.String,
|
|
499
|
+
head: Schema.Struct({
|
|
500
|
+
ref: Schema.String,
|
|
501
|
+
sha: Schema.String
|
|
502
|
+
}),
|
|
503
|
+
base: Schema.Struct({
|
|
504
|
+
ref: Schema.String,
|
|
505
|
+
sha: Schema.String
|
|
506
|
+
}),
|
|
507
|
+
state: Schema.Literal("open", "closed", "merged"),
|
|
508
|
+
draft: Schema.Boolean,
|
|
509
|
+
mergeable: Schema.NullOr(Schema.Boolean),
|
|
510
|
+
url: Schema.String,
|
|
511
|
+
html_url: Schema.String
|
|
512
|
+
});
|
|
513
|
+
const CreatePullRequestOptionsSchema = Schema.Struct({
|
|
514
|
+
title: Schema.String,
|
|
515
|
+
body: Schema.NullOr(Schema.String),
|
|
516
|
+
head: Schema.String,
|
|
517
|
+
base: Schema.String,
|
|
518
|
+
draft: Schema.optional(Schema.Boolean)
|
|
519
|
+
});
|
|
520
|
+
const UpdatePullRequestOptionsSchema = Schema.Struct({
|
|
521
|
+
title: Schema.optional(Schema.String),
|
|
522
|
+
body: Schema.optional(Schema.String),
|
|
523
|
+
state: Schema.optional(Schema.Literal("open", "closed"))
|
|
524
|
+
});
|
|
525
|
+
const CommitStatusSchema = Schema.Struct({
|
|
526
|
+
state: Schema.Literal("pending", "success", "error", "failure"),
|
|
527
|
+
target_url: Schema.optional(Schema.String),
|
|
528
|
+
description: Schema.optional(Schema.String),
|
|
529
|
+
context: Schema.String
|
|
530
|
+
});
|
|
531
|
+
const RepositoryInfoSchema = Schema.Struct({
|
|
532
|
+
owner: Schema.String,
|
|
533
|
+
repo: Schema.String
|
|
534
|
+
});
|
|
535
|
+
var GitHubService = class extends Effect.Service()("@ucdjs/release-scripts/GitHubService", {
|
|
536
|
+
effect: Effect.gen(function* () {
|
|
537
|
+
const config = yield* ReleaseScriptsOptions;
|
|
538
|
+
function makeRequest(endpoint, schema, options = {}) {
|
|
539
|
+
const url = `https://api.github.com/repos/${config.owner}/${config.repo}/${endpoint}`;
|
|
540
|
+
return Effect.tryPromise({
|
|
541
|
+
try: async () => {
|
|
542
|
+
const res = await fetch(url, {
|
|
543
|
+
...options,
|
|
544
|
+
headers: {
|
|
545
|
+
"Authorization": `token ${config.githubToken}`,
|
|
546
|
+
"Accept": "application/vnd.github.v3+json",
|
|
547
|
+
"Content-Type": "application/json",
|
|
548
|
+
"User-Agent": "ucdjs-release-scripts (https://github.com/ucdjs/release-scripts)",
|
|
549
|
+
...options.headers
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
if (!res.ok) {
|
|
553
|
+
const text = await res.text();
|
|
554
|
+
throw new Error(`GitHub API request failed with status ${res.status}: ${text}`);
|
|
555
|
+
}
|
|
556
|
+
if (res.status === 204) return;
|
|
557
|
+
return res.json();
|
|
558
|
+
},
|
|
559
|
+
catch: (e) => new GitHubError({
|
|
560
|
+
message: String(e),
|
|
561
|
+
operation: "request",
|
|
562
|
+
cause: e
|
|
563
|
+
})
|
|
564
|
+
}).pipe(Effect.flatMap((json) => json === void 0 ? Effect.succeed(void 0) : Schema.decodeUnknown(schema)(json).pipe(Effect.mapError((e) => new GitHubError({
|
|
565
|
+
message: "Failed to decode GitHub response",
|
|
566
|
+
operation: "request",
|
|
567
|
+
cause: e
|
|
568
|
+
})))));
|
|
569
|
+
}
|
|
570
|
+
function getPullRequestByBranch(branch) {
|
|
571
|
+
const head = branch.includes(":") ? branch : `${config.owner}:${branch}`;
|
|
572
|
+
return makeRequest(`pulls?state=open&head=${encodeURIComponent(head)}`, Schema.Array(PullRequestSchema)).pipe(Effect.map((pulls) => pulls.length > 0 ? pulls[0] : null), Effect.mapError((e) => new GitHubError({
|
|
573
|
+
message: e.message,
|
|
574
|
+
operation: "getPullRequestByBranch",
|
|
575
|
+
cause: e.cause
|
|
576
|
+
})));
|
|
577
|
+
}
|
|
578
|
+
function setCommitStatus(sha, status) {
|
|
579
|
+
return makeRequest(`statuses/${sha}`, Schema.Unknown, {
|
|
580
|
+
method: "POST",
|
|
581
|
+
body: JSON.stringify(status)
|
|
582
|
+
}).pipe(Effect.map(() => status), Effect.catchAll((e) => Effect.fail(new GitHubError({
|
|
583
|
+
message: e.message,
|
|
584
|
+
operation: "setCommitStatus",
|
|
585
|
+
cause: e.cause
|
|
586
|
+
}))));
|
|
587
|
+
}
|
|
588
|
+
function updatePullRequest(number, options) {
|
|
589
|
+
return makeRequest(`pulls/${number}`, PullRequestSchema, {
|
|
590
|
+
method: "PATCH",
|
|
591
|
+
body: JSON.stringify(options)
|
|
592
|
+
}).pipe(Effect.mapError((e) => new GitHubError({
|
|
593
|
+
message: e.message,
|
|
594
|
+
operation: "updatePullRequest",
|
|
595
|
+
cause: e.cause
|
|
596
|
+
})));
|
|
597
|
+
}
|
|
598
|
+
function createPullRequest(options) {
|
|
599
|
+
return makeRequest("pulls", PullRequestSchema, {
|
|
600
|
+
method: "POST",
|
|
601
|
+
body: JSON.stringify(options)
|
|
602
|
+
}).pipe(Effect.mapError((e) => new GitHubError({
|
|
603
|
+
message: e.message,
|
|
604
|
+
operation: "createPullRequest",
|
|
605
|
+
cause: e.cause
|
|
606
|
+
})));
|
|
607
|
+
}
|
|
608
|
+
const prBodyTemplate = `## Release Summary
|
|
609
|
+
|
|
610
|
+
This PR prepares the release of <%= it.count %> package<%= it.count === 1 ? "" : "s" %>:
|
|
611
|
+
|
|
612
|
+
<% for (const release of it.releases) { %>
|
|
613
|
+
- **<%= release.packageName %>**: \`<%= release.previousVersion %>\` → \`<%= release.version %>\`
|
|
614
|
+
<% } %>
|
|
615
|
+
|
|
616
|
+
## Changes
|
|
617
|
+
|
|
618
|
+
See individual package changelogs for details.
|
|
619
|
+
`;
|
|
620
|
+
function generateReleasePRBody(releases) {
|
|
621
|
+
return Effect.gen(function* () {
|
|
622
|
+
return eta.renderString(prBodyTemplate, {
|
|
623
|
+
count: releases.length,
|
|
624
|
+
releases
|
|
625
|
+
});
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
return {
|
|
629
|
+
getPullRequestByBranch,
|
|
630
|
+
setCommitStatus,
|
|
631
|
+
createPullRequest,
|
|
632
|
+
updatePullRequest,
|
|
633
|
+
generateReleasePRBody
|
|
634
|
+
};
|
|
635
|
+
}),
|
|
636
|
+
dependencies: []
|
|
637
|
+
}) {};
|
|
638
|
+
|
|
639
|
+
//#endregion
|
|
640
|
+
//#region src/services/npm.service.ts
|
|
641
|
+
const PackumentSchema = Schema.Struct({
|
|
642
|
+
"name": Schema.String,
|
|
643
|
+
"dist-tags": Schema.Record({
|
|
644
|
+
key: Schema.String,
|
|
645
|
+
value: Schema.String
|
|
646
|
+
}),
|
|
647
|
+
"versions": Schema.Record({
|
|
648
|
+
key: Schema.String,
|
|
649
|
+
value: Schema.Struct({
|
|
650
|
+
name: Schema.String,
|
|
651
|
+
version: Schema.String,
|
|
652
|
+
description: Schema.optional(Schema.String),
|
|
653
|
+
dist: Schema.Struct({
|
|
654
|
+
tarball: Schema.String,
|
|
655
|
+
shasum: Schema.String,
|
|
656
|
+
integrity: Schema.optional(Schema.String)
|
|
657
|
+
})
|
|
658
|
+
})
|
|
659
|
+
})
|
|
660
|
+
});
|
|
661
|
+
var NPMService = class extends Effect.Service()("@ucdjs/release-scripts/NPMService", {
|
|
662
|
+
effect: Effect.gen(function* () {
|
|
663
|
+
const executor = yield* CommandExecutor.CommandExecutor;
|
|
664
|
+
const config = yield* ReleaseScriptsOptions;
|
|
665
|
+
const fetchPackument = (packageName) => Effect.tryPromise({
|
|
666
|
+
try: async () => {
|
|
667
|
+
const response = await fetch(`https://registry.npmjs.org/${packageName}`);
|
|
668
|
+
if (response.status === 404) return null;
|
|
669
|
+
if (!response.ok) throw new Error(`Failed to fetch packument: ${response.statusText}`);
|
|
670
|
+
return await response.json();
|
|
671
|
+
},
|
|
672
|
+
catch: (error) => {
|
|
673
|
+
return new NPMError({
|
|
674
|
+
message: error instanceof Error ? error.message : String(error),
|
|
675
|
+
operation: "fetchPackument"
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
}).pipe(Effect.flatMap((data) => {
|
|
679
|
+
if (data === null) return Effect.succeed(null);
|
|
680
|
+
return Schema.decodeUnknown(PackumentSchema)(data).pipe(Effect.mapError((error) => new NPMError({
|
|
681
|
+
message: `Failed to parse packument: ${error}`,
|
|
682
|
+
operation: "fetchPackument"
|
|
683
|
+
})));
|
|
684
|
+
}));
|
|
685
|
+
const versionExists = (packageName, version) => fetchPackument(packageName).pipe(Effect.map((packument) => {
|
|
686
|
+
if (!packument) return false;
|
|
687
|
+
return version in packument.versions;
|
|
688
|
+
}));
|
|
689
|
+
const getLatestVersion = (packageName) => fetchPackument(packageName).pipe(Effect.map((packument) => {
|
|
690
|
+
if (!packument) return null;
|
|
691
|
+
return packument["dist-tags"].latest || null;
|
|
692
|
+
}));
|
|
693
|
+
const publish = (options) => Effect.gen(function* () {
|
|
694
|
+
const args = ["publish"];
|
|
695
|
+
if (options.tagName) args.push("--tag", options.tagName);
|
|
696
|
+
if (options.otp) args.push("--otp", options.otp);
|
|
697
|
+
if (options.provenance !== false) args.push("--provenance");
|
|
698
|
+
if (options.dryRun ?? config.dryRun) args.push("--dry-run");
|
|
699
|
+
const command = Command.make("pnpm", ...args).pipe(Command.workingDirectory(options.packagePath));
|
|
700
|
+
return (yield* executor.string(command).pipe(Effect.mapError((err) => new PublishError({
|
|
701
|
+
message: `Failed to publish package at ${options.packagePath}: ${err.message}`,
|
|
702
|
+
cause: err
|
|
703
|
+
})))).trim();
|
|
704
|
+
});
|
|
705
|
+
return {
|
|
706
|
+
fetchPackument,
|
|
707
|
+
versionExists,
|
|
708
|
+
getLatestVersion,
|
|
709
|
+
publish
|
|
710
|
+
};
|
|
711
|
+
}),
|
|
712
|
+
dependencies: [NodeCommandExecutor.layer]
|
|
713
|
+
}) {};
|
|
714
|
+
|
|
715
|
+
//#endregion
|
|
716
|
+
//#region src/services/workspace.service.ts
|
|
717
|
+
const DependencyObjectSchema = Schema.Record({
|
|
718
|
+
key: Schema.String,
|
|
719
|
+
value: Schema.String
|
|
720
|
+
});
|
|
721
|
+
const PackageJsonSchema = Schema.Struct({
|
|
722
|
+
name: Schema.String,
|
|
723
|
+
private: Schema.optional(Schema.Boolean),
|
|
724
|
+
version: Schema.optional(Schema.String),
|
|
725
|
+
dependencies: Schema.optional(DependencyObjectSchema),
|
|
726
|
+
devDependencies: Schema.optional(DependencyObjectSchema),
|
|
727
|
+
peerDependencies: Schema.optional(DependencyObjectSchema)
|
|
728
|
+
});
|
|
729
|
+
const WorkspacePackageSchema = Schema.Struct({
|
|
730
|
+
name: Schema.String,
|
|
731
|
+
version: Schema.String,
|
|
732
|
+
path: Schema.String,
|
|
733
|
+
packageJson: PackageJsonSchema,
|
|
734
|
+
workspaceDependencies: Schema.Array(Schema.String),
|
|
735
|
+
workspaceDevDependencies: Schema.Array(Schema.String)
|
|
736
|
+
});
|
|
737
|
+
const WorkspaceListSchema = Schema.Array(Schema.Struct({
|
|
738
|
+
name: Schema.String,
|
|
739
|
+
path: Schema.String,
|
|
740
|
+
version: Schema.optional(Schema.String),
|
|
741
|
+
private: Schema.optional(Schema.Boolean),
|
|
742
|
+
dependencies: Schema.optional(DependencyObjectSchema),
|
|
743
|
+
devDependencies: Schema.optional(DependencyObjectSchema),
|
|
744
|
+
peerDependencies: Schema.optional(DependencyObjectSchema)
|
|
745
|
+
}));
|
|
746
|
+
var WorkspaceService = class extends Effect.Service()("@ucdjs/release-scripts/WorkspaceService", {
|
|
747
|
+
effect: Effect.gen(function* () {
|
|
748
|
+
const executor = yield* CommandExecutor.CommandExecutor;
|
|
749
|
+
const config = yield* ReleaseScriptsOptions;
|
|
750
|
+
const workspacePackageListOutput = yield* executor.string(Command.make("pnpm", "-r", "ls", "--json").pipe(Command.workingDirectory(config.workspaceRoot))).pipe(Effect.flatMap((stdout) => Effect.try({
|
|
751
|
+
try: () => JSON.parse(stdout),
|
|
752
|
+
catch: (e) => new WorkspaceError({
|
|
753
|
+
message: "Failed to parse pnpm JSON output",
|
|
754
|
+
operation: "discover",
|
|
755
|
+
cause: e
|
|
756
|
+
})
|
|
757
|
+
})), Effect.flatMap((json) => Schema.decodeUnknown(WorkspaceListSchema)(json).pipe(Effect.mapError((e) => new WorkspaceError({
|
|
758
|
+
message: "Failed to decode pnpm output",
|
|
759
|
+
operation: "discover",
|
|
760
|
+
cause: e
|
|
761
|
+
})))), Effect.cached);
|
|
762
|
+
function readPackageJson(pkgPath) {
|
|
763
|
+
return Effect.tryPromise({
|
|
764
|
+
try: async () => JSON.parse(await fs.readFile(path.join(pkgPath, "package.json"), "utf8")),
|
|
765
|
+
catch: (e) => new WorkspaceError({
|
|
766
|
+
message: `Failed to read package.json for ${pkgPath}`,
|
|
767
|
+
cause: e,
|
|
768
|
+
operation: "readPackageJson"
|
|
769
|
+
})
|
|
770
|
+
}).pipe(Effect.flatMap((json) => Schema.decodeUnknown(PackageJsonSchema)(json).pipe(Effect.mapError((e) => new WorkspaceError({
|
|
771
|
+
message: `Invalid package.json for ${pkgPath}`,
|
|
772
|
+
cause: e,
|
|
773
|
+
operation: "readPackageJson"
|
|
774
|
+
})))));
|
|
775
|
+
}
|
|
776
|
+
function writePackageJson(pkgPath, json) {
|
|
777
|
+
const fullPath = path.join(pkgPath, "package.json");
|
|
778
|
+
const content = `${JSON.stringify(json, null, 2)}\n`;
|
|
779
|
+
if (config.dryRun) return Effect.succeed(`Dry run: skip writing ${fullPath}`);
|
|
780
|
+
return Effect.tryPromise({
|
|
781
|
+
try: async () => await fs.writeFile(fullPath, content, "utf8"),
|
|
782
|
+
catch: (e) => new WorkspaceError({
|
|
783
|
+
message: `Failed to write package.json for ${pkgPath}`,
|
|
784
|
+
cause: e,
|
|
785
|
+
operation: "writePackageJson"
|
|
786
|
+
})
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
const discoverWorkspacePackages = Effect.gen(function* () {
|
|
790
|
+
let workspaceOptions;
|
|
791
|
+
let explicitPackages;
|
|
792
|
+
if (config.packages == null || config.packages === true) workspaceOptions = { excludePrivate: false };
|
|
793
|
+
else if (Array.isArray(config.packages)) {
|
|
794
|
+
workspaceOptions = {
|
|
795
|
+
excludePrivate: false,
|
|
796
|
+
include: config.packages
|
|
797
|
+
};
|
|
798
|
+
explicitPackages = config.packages;
|
|
799
|
+
} else {
|
|
800
|
+
workspaceOptions = config.packages;
|
|
801
|
+
if (config.packages.include) explicitPackages = config.packages.include;
|
|
802
|
+
}
|
|
803
|
+
const workspacePackages = yield* findWorkspacePackages(workspaceOptions);
|
|
804
|
+
if (explicitPackages) {
|
|
805
|
+
const foundNames = new Set(workspacePackages.map((p) => p.name));
|
|
806
|
+
const missing = explicitPackages.filter((p) => !foundNames.has(p));
|
|
807
|
+
if (missing.length > 0) return yield* Effect.fail(/* @__PURE__ */ new Error(`Package${missing.length > 1 ? "s" : ""} not found in workspace: ${missing.join(", ")}`));
|
|
808
|
+
}
|
|
809
|
+
return workspacePackages;
|
|
810
|
+
});
|
|
811
|
+
function findWorkspacePackages(options) {
|
|
812
|
+
return workspacePackageListOutput.pipe(Effect.flatMap((rawProjects) => {
|
|
813
|
+
const allPackageNames = new Set(rawProjects.map((p) => p.name));
|
|
814
|
+
return Effect.all(rawProjects.map((rawProject) => readPackageJson(rawProject.path).pipe(Effect.flatMap((packageJson) => {
|
|
815
|
+
if (!shouldIncludePackage(packageJson, options)) return Effect.succeed(null);
|
|
816
|
+
const pkg = {
|
|
817
|
+
name: rawProject.name,
|
|
818
|
+
version: rawProject.version,
|
|
819
|
+
path: rawProject.path,
|
|
820
|
+
packageJson,
|
|
821
|
+
workspaceDependencies: Object.keys(rawProject.dependencies || {}).filter((dep) => allPackageNames.has(dep)),
|
|
822
|
+
workspaceDevDependencies: Object.keys(rawProject.devDependencies || {}).filter((dep) => allPackageNames.has(dep))
|
|
823
|
+
};
|
|
824
|
+
return Schema.decodeUnknown(WorkspacePackageSchema)(pkg).pipe(Effect.mapError((e) => new WorkspaceError({
|
|
825
|
+
message: `Invalid workspace package structure for ${rawProject.name}`,
|
|
826
|
+
cause: e,
|
|
827
|
+
operation: "findWorkspacePackages"
|
|
828
|
+
})));
|
|
829
|
+
}), Effect.catchAll(() => {
|
|
830
|
+
return Effect.logWarning(`Skipping invalid package ${rawProject.name}`).pipe(Effect.as(null));
|
|
831
|
+
})))).pipe(Effect.map((packages) => packages.filter((pkg) => pkg !== null)));
|
|
832
|
+
}));
|
|
833
|
+
}
|
|
834
|
+
function shouldIncludePackage(pkg, options) {
|
|
835
|
+
if (!options) return true;
|
|
836
|
+
if (options.excludePrivate && pkg.private) return false;
|
|
837
|
+
if (options.include && options.include.length > 0) {
|
|
838
|
+
if (!options.include.includes(pkg.name)) return false;
|
|
839
|
+
}
|
|
840
|
+
if (options.exclude?.includes(pkg.name)) return false;
|
|
841
|
+
return true;
|
|
842
|
+
}
|
|
843
|
+
function findPackageByName(packageName) {
|
|
844
|
+
return discoverWorkspacePackages.pipe(Effect.map((packages) => packages.find((pkg) => pkg.name === packageName) || null));
|
|
845
|
+
}
|
|
846
|
+
return {
|
|
847
|
+
readPackageJson,
|
|
848
|
+
writePackageJson,
|
|
849
|
+
findWorkspacePackages,
|
|
850
|
+
discoverWorkspacePackages,
|
|
851
|
+
findPackageByName
|
|
852
|
+
};
|
|
853
|
+
}),
|
|
854
|
+
dependencies: []
|
|
855
|
+
}) {};
|
|
856
|
+
|
|
857
|
+
//#endregion
|
|
858
|
+
//#region src/services/package-updater.service.ts
|
|
859
|
+
const DASH_RE = / - /;
|
|
860
|
+
const RANGE_OPERATION_RE = /^(?:>=|<=|[><=])/;
|
|
861
|
+
function nextRange(oldRange, newVersion) {
|
|
862
|
+
const workspacePrefix = oldRange.startsWith("workspace:") ? "workspace:" : "";
|
|
863
|
+
const raw = workspacePrefix ? oldRange.slice(10) : oldRange;
|
|
864
|
+
if (raw === "*" || raw === "latest") return `${workspacePrefix}${raw}`;
|
|
865
|
+
if (raw.includes("||") || DASH_RE.test(raw) || RANGE_OPERATION_RE.test(raw) || raw.includes(" ") && !DASH_RE.test(raw)) {
|
|
866
|
+
if (semver.satisfies(newVersion, raw)) return `${workspacePrefix}${raw}`;
|
|
867
|
+
throw new Error(`Cannot update range "${oldRange}" to version ${newVersion}: new version is outside the existing range. Complex range updating is not yet implemented.`);
|
|
249
868
|
}
|
|
869
|
+
return `${workspacePrefix}${raw.startsWith("^") || raw.startsWith("~") ? raw[0] : ""}${newVersion}`;
|
|
250
870
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
} });
|
|
267
|
-
return true;
|
|
268
|
-
} catch {
|
|
269
|
-
return false;
|
|
871
|
+
function updateDependencyRecord(record, releaseMap) {
|
|
872
|
+
if (!record) return {
|
|
873
|
+
updated: false,
|
|
874
|
+
next: void 0
|
|
875
|
+
};
|
|
876
|
+
let changed = false;
|
|
877
|
+
const next = { ...record };
|
|
878
|
+
for (const [dep, currentRange] of Object.entries(record)) {
|
|
879
|
+
const bumped = releaseMap.get(dep);
|
|
880
|
+
if (!bumped) continue;
|
|
881
|
+
const updatedRange = nextRange(currentRange, bumped);
|
|
882
|
+
if (updatedRange !== currentRange) {
|
|
883
|
+
next[dep] = updatedRange;
|
|
884
|
+
changed = true;
|
|
885
|
+
}
|
|
270
886
|
}
|
|
887
|
+
return {
|
|
888
|
+
updated: changed,
|
|
889
|
+
next: changed ? next : record
|
|
890
|
+
};
|
|
271
891
|
}
|
|
272
|
-
|
|
273
|
-
*
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
892
|
+
var PackageUpdaterService = class extends Effect.Service()("@ucdjs/release-scripts/PackageUpdaterService", {
|
|
893
|
+
effect: Effect.gen(function* () {
|
|
894
|
+
const workspace = yield* WorkspaceService;
|
|
895
|
+
function applyReleases(allPackages, releases) {
|
|
896
|
+
const releaseMap = /* @__PURE__ */ new Map();
|
|
897
|
+
for (const release of releases) releaseMap.set(release.package.name, release.newVersion);
|
|
898
|
+
return Effect.all(allPackages.map((pkg) => Effect.gen(function* () {
|
|
899
|
+
const releaseVersion = releaseMap.get(pkg.name);
|
|
900
|
+
const nextJson = { ...pkg.packageJson };
|
|
901
|
+
let updated = false;
|
|
902
|
+
if (releaseVersion && pkg.packageJson.version !== releaseVersion) {
|
|
903
|
+
nextJson.version = releaseVersion;
|
|
904
|
+
updated = true;
|
|
905
|
+
}
|
|
906
|
+
const depsResult = updateDependencyRecord(pkg.packageJson.dependencies, releaseMap);
|
|
907
|
+
if (depsResult.updated) {
|
|
908
|
+
nextJson.dependencies = depsResult.next;
|
|
909
|
+
updated = true;
|
|
910
|
+
}
|
|
911
|
+
const devDepsResult = updateDependencyRecord(pkg.packageJson.devDependencies, releaseMap);
|
|
912
|
+
if (devDepsResult.updated) {
|
|
913
|
+
nextJson.devDependencies = devDepsResult.next;
|
|
914
|
+
updated = true;
|
|
915
|
+
}
|
|
916
|
+
const peerDepsResult = updateDependencyRecord(pkg.packageJson.peerDependencies, releaseMap);
|
|
917
|
+
if (peerDepsResult.updated) {
|
|
918
|
+
nextJson.peerDependencies = peerDepsResult.next;
|
|
919
|
+
updated = true;
|
|
920
|
+
}
|
|
921
|
+
if (!updated) return "skipped";
|
|
922
|
+
return yield* workspace.writePackageJson(pkg.path, nextJson).pipe(Effect.map(() => "written"));
|
|
923
|
+
})));
|
|
924
|
+
}
|
|
925
|
+
return { applyReleases };
|
|
926
|
+
}),
|
|
927
|
+
dependencies: [WorkspaceService.Default]
|
|
928
|
+
}) {};
|
|
929
|
+
|
|
930
|
+
//#endregion
|
|
931
|
+
//#region src/services/version-calculator.service.ts
|
|
932
|
+
const BUMP_PRIORITY = {
|
|
933
|
+
none: 0,
|
|
934
|
+
patch: 1,
|
|
935
|
+
minor: 2,
|
|
936
|
+
major: 3
|
|
937
|
+
};
|
|
938
|
+
function maxBump(current, incoming) {
|
|
939
|
+
return (BUMP_PRIORITY[incoming] ?? 0) > (BUMP_PRIORITY[current] ?? 0) ? incoming : current;
|
|
292
940
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
*/
|
|
299
|
-
async function createBranch(branch, base, workspaceRoot) {
|
|
300
|
-
await runIfNotDry("git", [
|
|
301
|
-
"checkout",
|
|
302
|
-
"-b",
|
|
303
|
-
branch,
|
|
304
|
-
base
|
|
305
|
-
], { nodeOptions: { cwd: workspaceRoot } });
|
|
941
|
+
function bumpFromCommit(commit) {
|
|
942
|
+
if (commit.isBreaking) return "major";
|
|
943
|
+
if (commit.type === "feat") return "minor";
|
|
944
|
+
if (commit.type === "fix" || commit.type === "perf") return "patch";
|
|
945
|
+
return "none";
|
|
306
946
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
* @param branch - The branch name to checkout
|
|
310
|
-
* @param workspaceRoot - The root directory of the workspace
|
|
311
|
-
* @returns Promise resolving to true if checkout succeeded, false otherwise
|
|
312
|
-
*/
|
|
313
|
-
async function checkoutBranch(branch, workspaceRoot) {
|
|
314
|
-
try {
|
|
315
|
-
await run("git", ["checkout", branch], { nodeOptions: { cwd: workspaceRoot } });
|
|
316
|
-
return true;
|
|
317
|
-
} catch {
|
|
318
|
-
return false;
|
|
319
|
-
}
|
|
947
|
+
function determineBump(commits) {
|
|
948
|
+
return commits.reduce((acc, commit) => maxBump(acc, bumpFromCommit(commit)), "none");
|
|
320
949
|
}
|
|
321
|
-
|
|
322
|
-
*
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
950
|
+
var VersionCalculatorService = class extends Effect.Service()("@ucdjs/release-scripts/VersionCalculatorService", {
|
|
951
|
+
effect: Effect.gen(function* () {
|
|
952
|
+
function calculateBumps(packages, overrides) {
|
|
953
|
+
return Effect.all(packages.map((pkg) => Effect.gen(function* () {
|
|
954
|
+
const bumpType = determineBump([...pkg.commits, ...pkg.globalCommits]);
|
|
955
|
+
const hasDirectChanges = pkg.commits.length > 0;
|
|
956
|
+
let nextVersion = null;
|
|
957
|
+
const override = overrides[pkg.name];
|
|
958
|
+
if (override) {
|
|
959
|
+
if (!semver.valid(override)) return yield* Effect.fail(new VersionCalculationError({
|
|
960
|
+
message: `Invalid override version for ${pkg.name}: ${override}`,
|
|
961
|
+
packageName: pkg.name
|
|
962
|
+
}));
|
|
963
|
+
nextVersion = override;
|
|
964
|
+
}
|
|
965
|
+
if (nextVersion === null) if (bumpType === "none") nextVersion = pkg.version;
|
|
966
|
+
else {
|
|
967
|
+
const bumped = semver.inc(pkg.version, bumpType);
|
|
968
|
+
if (!bumped) return yield* Effect.fail(new VersionCalculationError({
|
|
969
|
+
message: `Failed to bump version for ${pkg.name} using bump type ${bumpType}`,
|
|
970
|
+
packageName: pkg.name
|
|
971
|
+
}));
|
|
972
|
+
nextVersion = bumped;
|
|
973
|
+
}
|
|
974
|
+
return {
|
|
975
|
+
package: {
|
|
976
|
+
name: pkg.name,
|
|
977
|
+
version: pkg.version,
|
|
978
|
+
path: pkg.path,
|
|
979
|
+
packageJson: pkg.packageJson,
|
|
980
|
+
workspaceDependencies: pkg.workspaceDependencies,
|
|
981
|
+
workspaceDevDependencies: pkg.workspaceDevDependencies
|
|
982
|
+
},
|
|
983
|
+
currentVersion: pkg.version,
|
|
984
|
+
newVersion: nextVersion,
|
|
985
|
+
bumpType,
|
|
986
|
+
hasDirectChanges
|
|
987
|
+
};
|
|
988
|
+
})), { concurrency: 10 });
|
|
989
|
+
}
|
|
990
|
+
return { calculateBumps };
|
|
991
|
+
}),
|
|
992
|
+
dependencies: []
|
|
993
|
+
}) {};
|
|
994
|
+
|
|
995
|
+
//#endregion
|
|
996
|
+
//#region src/utils/helpers.ts
|
|
997
|
+
function loadOverrides(options) {
|
|
998
|
+
return Effect.gen(function* () {
|
|
999
|
+
return yield* (yield* GitService).workspace.readFile(options.overridesPath, options.sha).pipe(Effect.flatMap((content) => Effect.try({
|
|
1000
|
+
try: () => JSON.parse(content),
|
|
1001
|
+
catch: (err) => new OverridesLoadError({
|
|
1002
|
+
message: "Failed to parse overrides file.",
|
|
1003
|
+
cause: err
|
|
1004
|
+
})
|
|
1005
|
+
})), Effect.catchAll(() => Effect.succeed({})));
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
const GitCommitSchema = Schema.Struct({
|
|
1009
|
+
isConventional: Schema.Boolean,
|
|
1010
|
+
isBreaking: Schema.Boolean,
|
|
1011
|
+
type: Schema.String,
|
|
1012
|
+
scope: Schema.Union(Schema.String, Schema.Undefined),
|
|
1013
|
+
description: Schema.String,
|
|
1014
|
+
references: Schema.Array(Schema.Struct({
|
|
1015
|
+
type: Schema.Union(Schema.Literal("issue"), Schema.Literal("pull-request")),
|
|
1016
|
+
value: Schema.String
|
|
1017
|
+
})),
|
|
1018
|
+
authors: Schema.Array(Schema.Struct({
|
|
1019
|
+
name: Schema.String,
|
|
1020
|
+
email: Schema.String,
|
|
1021
|
+
profile: Schema.optional(Schema.String)
|
|
1022
|
+
})),
|
|
1023
|
+
hash: Schema.String,
|
|
1024
|
+
shortHash: Schema.String,
|
|
1025
|
+
body: Schema.String,
|
|
1026
|
+
message: Schema.String,
|
|
1027
|
+
date: Schema.String
|
|
1028
|
+
});
|
|
1029
|
+
const WorkspacePackageWithCommitsSchema = Schema.Struct({
|
|
1030
|
+
...WorkspacePackageSchema.fields,
|
|
1031
|
+
commits: Schema.Array(GitCommitSchema),
|
|
1032
|
+
globalCommits: Schema.Array(GitCommitSchema).pipe(Schema.propertySignature, Schema.withConstructorDefault(() => []))
|
|
1033
|
+
});
|
|
1034
|
+
function mergePackageCommitsIntoPackages(packages) {
|
|
1035
|
+
return Effect.gen(function* () {
|
|
1036
|
+
const git = yield* GitService;
|
|
1037
|
+
return yield* Effect.forEach(packages, (pkg) => Effect.gen(function* () {
|
|
1038
|
+
const lastTag = yield* git.tags.mostRecentForPackage(pkg.name);
|
|
1039
|
+
const commits = yield* git.commits.get({
|
|
1040
|
+
from: lastTag?.name || void 0,
|
|
1041
|
+
to: "HEAD",
|
|
1042
|
+
folder: pkg.path
|
|
1043
|
+
});
|
|
1044
|
+
const withCommits = {
|
|
1045
|
+
...pkg,
|
|
1046
|
+
commits,
|
|
1047
|
+
globalCommits: []
|
|
1048
|
+
};
|
|
1049
|
+
return yield* Schema.decode(WorkspacePackageWithCommitsSchema)(withCommits).pipe(Effect.mapError((e) => /* @__PURE__ */ new Error(`Failed to decode package with commits for ${pkg.name}: ${e}`)));
|
|
1050
|
+
}));
|
|
1051
|
+
});
|
|
335
1052
|
}
|
|
336
1053
|
/**
|
|
337
|
-
*
|
|
338
|
-
*
|
|
339
|
-
*
|
|
1054
|
+
* Retrieves global commits that affect all packages in a monorepo.
|
|
1055
|
+
*
|
|
1056
|
+
* This function handles an important edge case in monorepo releases:
|
|
1057
|
+
* When pkg-a is released, then a global change is made, and then pkg-b is released,
|
|
1058
|
+
* we need to ensure that the global change is only attributed to pkg-a's release,
|
|
1059
|
+
* not re-counted for pkg-b.
|
|
1060
|
+
*
|
|
1061
|
+
* Algorithm:
|
|
1062
|
+
* 1. Find the overall commit range across all packages
|
|
1063
|
+
* 2. Fetch all commits and file changes once for this range
|
|
1064
|
+
* 3. For each package, filter commits based on its last tag cutoff
|
|
1065
|
+
* 4. Apply mode-specific filtering for global commits
|
|
1066
|
+
*
|
|
1067
|
+
* Example scenario:
|
|
1068
|
+
* - pkg-a: last released at commit A
|
|
1069
|
+
* - global change at commit B (after A)
|
|
1070
|
+
* - pkg-b: last released at commit C (after B)
|
|
1071
|
+
*
|
|
1072
|
+
* Result:
|
|
1073
|
+
* - For pkg-a: includes commits from A to HEAD (including B)
|
|
1074
|
+
* - For pkg-b: includes commits from C to HEAD (excluding B, since it was already in pkg-b's release range)
|
|
1075
|
+
*
|
|
1076
|
+
* @param packages - Array of workspace packages with their associated commits
|
|
1077
|
+
* @param mode - Determines which global commits to include:
|
|
1078
|
+
* - "none": No global commits (returns empty map)
|
|
1079
|
+
* - "all": All commits that touch files outside any package directory
|
|
1080
|
+
* - "dependencies": Only commits that touch dependency-related files (package.json, lock files, etc.)
|
|
1081
|
+
*
|
|
1082
|
+
* @returns A map of package names to their relevant global commits
|
|
340
1083
|
*/
|
|
341
|
-
|
|
342
|
-
|
|
1084
|
+
function mergeCommitsAffectingGloballyIntoPackage(packages, mode) {
|
|
1085
|
+
return Effect.gen(function* () {
|
|
1086
|
+
const git = yield* GitService;
|
|
1087
|
+
if (mode === "none") return packages;
|
|
1088
|
+
const [oldestCommitSha, newestCommitSha] = findCommitRange(packages);
|
|
1089
|
+
if (oldestCommitSha == null || newestCommitSha == null) return packages;
|
|
1090
|
+
const allCommits = yield* git.commits.get({
|
|
1091
|
+
from: oldestCommitSha,
|
|
1092
|
+
to: newestCommitSha,
|
|
1093
|
+
folder: "."
|
|
1094
|
+
});
|
|
1095
|
+
const affectedFilesPerCommit = yield* git.commits.filesChangesBetweenRefs(oldestCommitSha, newestCommitSha);
|
|
1096
|
+
const commitTimestamps = new Map(allCommits.map((c) => [c.hash, new Date(c.date).getTime()]));
|
|
1097
|
+
const packagePaths = new Set(packages.map((p) => p.path));
|
|
1098
|
+
const result = /* @__PURE__ */ new Map();
|
|
1099
|
+
for (const pkg of packages) {
|
|
1100
|
+
const lastTag = yield* git.tags.mostRecentForPackage(pkg.name);
|
|
1101
|
+
const cutoffTimestamp = lastTag ? commitTimestamps.get(lastTag.sha) ?? 0 : 0;
|
|
1102
|
+
const globalCommits = [];
|
|
1103
|
+
for (const commit of allCommits) {
|
|
1104
|
+
const commitTimestamp = commitTimestamps.get(commit.hash);
|
|
1105
|
+
if (commitTimestamp == null || commitTimestamp <= cutoffTimestamp) continue;
|
|
1106
|
+
const files = affectedFilesPerCommit.get(commit.hash);
|
|
1107
|
+
if (!files) continue;
|
|
1108
|
+
if (isGlobalCommit(files, packagePaths)) if (mode === "dependencies") {
|
|
1109
|
+
if (files.some((file) => isDependencyFile(file))) globalCommits.push(commit);
|
|
1110
|
+
} else globalCommits.push(commit);
|
|
1111
|
+
}
|
|
1112
|
+
result.set(pkg.name, globalCommits);
|
|
1113
|
+
}
|
|
1114
|
+
return yield* Effect.succeed(packages.map((pkg) => ({
|
|
1115
|
+
...pkg,
|
|
1116
|
+
globalCommits: result.get(pkg.name) || []
|
|
1117
|
+
})));
|
|
1118
|
+
});
|
|
343
1119
|
}
|
|
344
1120
|
/**
|
|
345
|
-
*
|
|
346
|
-
*
|
|
347
|
-
* @param
|
|
348
|
-
* @
|
|
1121
|
+
* Determines if a commit is "global" (affects files outside any package directory).
|
|
1122
|
+
*
|
|
1123
|
+
* @param files - List of files changed in the commit
|
|
1124
|
+
* @param packagePaths - Set of package directory paths
|
|
1125
|
+
* @returns true if at least one file is outside all package directories
|
|
349
1126
|
*/
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
`origin/${branch}..${branch}`,
|
|
355
|
-
"--count"
|
|
356
|
-
], { nodeOptions: {
|
|
357
|
-
cwd: workspaceRoot,
|
|
358
|
-
stdio: "pipe"
|
|
359
|
-
} });
|
|
360
|
-
return Number.parseInt(result.stdout.trim(), 10) > 0;
|
|
361
|
-
} catch {
|
|
1127
|
+
function isGlobalCommit(files, packagePaths) {
|
|
1128
|
+
return files.some((file) => {
|
|
1129
|
+
const normalized = file.startsWith("./") ? file.slice(2) : file;
|
|
1130
|
+
for (const pkgPath of packagePaths) if (normalized === pkgPath || normalized.startsWith(`${pkgPath}/`)) return false;
|
|
362
1131
|
return true;
|
|
363
|
-
}
|
|
1132
|
+
});
|
|
364
1133
|
}
|
|
365
1134
|
/**
|
|
366
|
-
*
|
|
367
|
-
* @param workspaceRoot - The root directory of the workspace
|
|
368
|
-
* @returns Promise resolving to true if there are changes, false otherwise
|
|
1135
|
+
* Files that are considered dependency-related in a monorepo.
|
|
369
1136
|
*/
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
1137
|
+
const DEPENDENCY_FILES = new Set([
|
|
1138
|
+
"package.json",
|
|
1139
|
+
"pnpm-lock.yaml",
|
|
1140
|
+
"yarn.lock",
|
|
1141
|
+
"package-lock.json",
|
|
1142
|
+
"pnpm-workspace.yaml"
|
|
1143
|
+
]);
|
|
376
1144
|
/**
|
|
377
|
-
*
|
|
378
|
-
*
|
|
379
|
-
* @param
|
|
380
|
-
* @returns
|
|
1145
|
+
* Determines if a file is dependency-related.
|
|
1146
|
+
*
|
|
1147
|
+
* @param file - File path to check
|
|
1148
|
+
* @returns true if the file is a dependency file (package.json, lock files, etc.)
|
|
381
1149
|
*/
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
if (
|
|
385
|
-
|
|
386
|
-
"commit",
|
|
387
|
-
"-m",
|
|
388
|
-
message
|
|
389
|
-
], { nodeOptions: { cwd: workspaceRoot } });
|
|
390
|
-
return true;
|
|
1150
|
+
function isDependencyFile(file) {
|
|
1151
|
+
const normalized = file.startsWith("./") ? file.slice(2) : file;
|
|
1152
|
+
if (DEPENDENCY_FILES.has(normalized)) return true;
|
|
1153
|
+
return Array.from(DEPENDENCY_FILES).some((dep) => normalized.endsWith(`/${dep}`));
|
|
391
1154
|
}
|
|
392
1155
|
/**
|
|
393
|
-
*
|
|
394
|
-
*
|
|
395
|
-
*
|
|
396
|
-
*
|
|
397
|
-
* @param
|
|
398
|
-
* @
|
|
1156
|
+
* Finds the oldest and newest commits across all packages.
|
|
1157
|
+
*
|
|
1158
|
+
* This establishes the overall time range we need to analyze for global commits.
|
|
1159
|
+
*
|
|
1160
|
+
* @param packages - Array of packages with their commits
|
|
1161
|
+
* @returns Tuple of [oldestCommitSha, newestCommitSha], or [null, null] if no commits found
|
|
399
1162
|
*/
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
1163
|
+
function findCommitRange(packages) {
|
|
1164
|
+
let oldestCommit = null;
|
|
1165
|
+
let newestCommit = null;
|
|
1166
|
+
for (const pkg of packages) {
|
|
1167
|
+
if (pkg.commits.length === 0) continue;
|
|
1168
|
+
const firstCommit = pkg.commits[0];
|
|
1169
|
+
if (!firstCommit) throw new Error(`No commits found for package ${pkg.name}`);
|
|
1170
|
+
const lastCommit = pkg.commits[pkg.commits.length - 1];
|
|
1171
|
+
if (!lastCommit) throw new Error(`No commits found for package ${pkg.name}`);
|
|
1172
|
+
if (newestCommit == null || new Date(lastCommit.date) > new Date(newestCommit.date)) newestCommit = lastCommit;
|
|
1173
|
+
if (oldestCommit == null || new Date(firstCommit.date) < new Date(oldestCommit.date)) oldestCommit = firstCommit;
|
|
1174
|
+
}
|
|
1175
|
+
if (oldestCommit == null || newestCommit == null) return [null, null];
|
|
1176
|
+
return [oldestCommit.hash, newestCommit.hash];
|
|
409
1177
|
}
|
|
410
1178
|
|
|
411
1179
|
//#endregion
|
|
412
|
-
//#region src/
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
const
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
const requestBody = isUpdate ? {
|
|
444
|
-
title,
|
|
445
|
-
body
|
|
446
|
-
} : {
|
|
447
|
-
title,
|
|
448
|
-
body,
|
|
449
|
-
head,
|
|
450
|
-
base
|
|
451
|
-
};
|
|
452
|
-
const res = await fetch(url, {
|
|
453
|
-
method,
|
|
454
|
-
headers: {
|
|
455
|
-
Accept: "application/vnd.github.v3+json",
|
|
456
|
-
Authorization: `token ${githubToken}`
|
|
457
|
-
},
|
|
458
|
-
body: JSON.stringify(requestBody)
|
|
1180
|
+
//#region src/prepare.ts
|
|
1181
|
+
function constructPrepareProgram(config) {
|
|
1182
|
+
return Effect.gen(function* () {
|
|
1183
|
+
const changelog = yield* ChangelogService;
|
|
1184
|
+
const git = yield* GitService;
|
|
1185
|
+
const github = yield* GitHubService;
|
|
1186
|
+
const dependencyGraph = yield* DependencyGraphService;
|
|
1187
|
+
const packageUpdater = yield* PackageUpdaterService;
|
|
1188
|
+
const versionCalculator = yield* VersionCalculatorService;
|
|
1189
|
+
const workspace = yield* WorkspaceService;
|
|
1190
|
+
yield* git.workspace.assertWorkspaceReady;
|
|
1191
|
+
let releasePullRequest = yield* github.getPullRequestByBranch(config.branch.release);
|
|
1192
|
+
const isNewRelease = !releasePullRequest;
|
|
1193
|
+
const branchExists = yield* git.branches.exists(config.branch.release);
|
|
1194
|
+
if (!branchExists) {
|
|
1195
|
+
yield* Console.log(`🌿 Creating release branch "${config.branch.release}" from "${config.branch.default}"...`);
|
|
1196
|
+
yield* git.branches.create(config.branch.release, config.branch.default);
|
|
1197
|
+
yield* Console.log(`✅ Release branch created.`);
|
|
1198
|
+
}
|
|
1199
|
+
if ((yield* git.branches.get) !== config.branch.release) {
|
|
1200
|
+
yield* git.branches.checkout(config.branch.release);
|
|
1201
|
+
yield* Console.log(`✅ Checked out to release branch "${config.branch.release}".`);
|
|
1202
|
+
}
|
|
1203
|
+
if (!isNewRelease || branchExists) {
|
|
1204
|
+
yield* Console.log(`🔄 Rebasing "${config.branch.release}" onto "${config.branch.default}"...`);
|
|
1205
|
+
yield* git.branches.rebase(config.branch.default);
|
|
1206
|
+
yield* Console.log(`✅ Rebase complete.`);
|
|
1207
|
+
}
|
|
1208
|
+
const overrides = yield* loadOverrides({
|
|
1209
|
+
sha: config.branch.default,
|
|
1210
|
+
overridesPath: ".github/ucdjs-release.overrides.json"
|
|
459
1211
|
});
|
|
460
|
-
if (
|
|
461
|
-
const
|
|
462
|
-
|
|
463
|
-
const
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
1212
|
+
if (Object.keys(overrides).length > 0) yield* Console.log("📋 Loaded version overrides:", overrides);
|
|
1213
|
+
const originalBranch = yield* git.branches.get;
|
|
1214
|
+
yield* git.branches.checkout(config.branch.default);
|
|
1215
|
+
const packages = yield* workspace.discoverWorkspacePackages.pipe(Effect.flatMap(mergePackageCommitsIntoPackages), Effect.flatMap((pkgs) => mergeCommitsAffectingGloballyIntoPackage(pkgs, config.globalCommitMode)));
|
|
1216
|
+
yield* Console.log(`📦 Discovered ${packages.length} packages with commits.`);
|
|
1217
|
+
const releases = yield* versionCalculator.calculateBumps(packages, overrides);
|
|
1218
|
+
yield* dependencyGraph.topologicalOrder(packages);
|
|
1219
|
+
const releasesCount = releases.length;
|
|
1220
|
+
yield* Console.log(`📊 ${releasesCount} package${releasesCount === 1 ? "" : "s"} will be released.`);
|
|
1221
|
+
yield* git.branches.checkout(originalBranch);
|
|
1222
|
+
yield* Console.log("✏️ Updating package.json files...");
|
|
1223
|
+
yield* packageUpdater.applyReleases(packages, releases);
|
|
1224
|
+
yield* Console.log("✅ package.json files updated.");
|
|
1225
|
+
yield* Console.log("📝 Generating changelogs...");
|
|
1226
|
+
const changelogFiles = [];
|
|
1227
|
+
for (const release of releases) {
|
|
1228
|
+
const pkg = packages.find((p) => p.name === release.package.name);
|
|
1229
|
+
if (!pkg || !pkg.commits) continue;
|
|
1230
|
+
const result = yield* changelog.generateChangelog(pkg, release.newVersion, pkg.commits);
|
|
1231
|
+
yield* Effect.tryPromise({
|
|
1232
|
+
try: async () => {
|
|
1233
|
+
await (await import("node:fs/promises")).writeFile(result.filePath, result.markdown, "utf-8");
|
|
1234
|
+
},
|
|
1235
|
+
catch: (e) => /* @__PURE__ */ new Error(`Failed to write changelog: ${String(e)}`)
|
|
1236
|
+
});
|
|
1237
|
+
changelogFiles.push(result.filePath);
|
|
1238
|
+
}
|
|
1239
|
+
yield* Console.log(`✅ Generated ${changelogFiles.length} changelog file${changelogFiles.length === 1 ? "" : "s"}.`);
|
|
1240
|
+
const filesToStage = [...releases.map((r) => `${r.package.path}/package.json`), ...changelogFiles];
|
|
1241
|
+
yield* Console.log(`📌 Staging ${filesToStage.length} file${filesToStage.length === 1 ? "" : "s"}...`);
|
|
1242
|
+
yield* git.commits.stage(filesToStage);
|
|
1243
|
+
const commitMessage = `chore(release): prepare release
|
|
1244
|
+
|
|
1245
|
+
${releasesCount} package${releasesCount === 1 ? "" : "s"} updated:
|
|
1246
|
+
${releases.map((r) => ` - ${r.package.name}@${r.newVersion}`).join("\n")}`;
|
|
1247
|
+
yield* Console.log("💾 Creating commit...");
|
|
1248
|
+
yield* git.commits.write(commitMessage);
|
|
1249
|
+
yield* Console.log("✅ Commit created.");
|
|
1250
|
+
yield* Console.log(`⬆️ Pushing to "${config.branch.release}"...`);
|
|
1251
|
+
if (isNewRelease && !branchExists) yield* git.commits.push(config.branch.release);
|
|
1252
|
+
else yield* git.commits.forcePush(config.branch.release);
|
|
1253
|
+
yield* Console.log(`✅ Push complete.`);
|
|
1254
|
+
const prBody = yield* github.generateReleasePRBody(releases.map((r) => ({
|
|
1255
|
+
packageName: r.package.name,
|
|
1256
|
+
version: r.newVersion,
|
|
1257
|
+
previousVersion: r.package.version
|
|
1258
|
+
})));
|
|
1259
|
+
if (isNewRelease) {
|
|
1260
|
+
yield* Console.log("📋 Creating release pull request...");
|
|
1261
|
+
releasePullRequest = yield* github.createPullRequest({
|
|
1262
|
+
title: config.pullRequest.title,
|
|
1263
|
+
body: prBody,
|
|
1264
|
+
head: config.branch.release,
|
|
1265
|
+
base: config.branch.default,
|
|
1266
|
+
draft: true
|
|
1267
|
+
});
|
|
1268
|
+
yield* Console.log(`✅ Release pull request #${releasePullRequest.number} created.`);
|
|
1269
|
+
} else {
|
|
1270
|
+
yield* Console.log("📄 Updating pull request...");
|
|
1271
|
+
yield* github.updatePullRequest(releasePullRequest.number, { body: prBody });
|
|
1272
|
+
yield* Console.log("✅ Pull request updated.");
|
|
1273
|
+
}
|
|
1274
|
+
yield* Console.log(`\n🎉 Release preparation complete! View PR: #${releasePullRequest.number}`);
|
|
1275
|
+
});
|
|
499
1276
|
}
|
|
500
1277
|
|
|
501
1278
|
//#endregion
|
|
502
|
-
//#region src/
|
|
503
|
-
|
|
504
|
-
const
|
|
505
|
-
|
|
506
|
-
name: "selectedPackages",
|
|
507
|
-
message: "Select packages to release",
|
|
508
|
-
choices: packages.map((pkg) => ({
|
|
509
|
-
title: `${pkg.name} (${pkg.version})`,
|
|
510
|
-
value: pkg.name,
|
|
511
|
-
selected: true
|
|
512
|
-
})),
|
|
513
|
-
min: 1,
|
|
514
|
-
hint: "Space to select/deselect. Return to submit."
|
|
515
|
-
});
|
|
516
|
-
if (!response.selectedPackages || response.selectedPackages.length === 0) throw new Error("No packages selected");
|
|
517
|
-
return response.selectedPackages;
|
|
1279
|
+
//#region src/publish.ts
|
|
1280
|
+
function isPrerelease(version) {
|
|
1281
|
+
const parsed = semver.parse(version);
|
|
1282
|
+
return parsed !== null && parsed.prerelease.length > 0;
|
|
518
1283
|
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
"
|
|
526
|
-
|
|
527
|
-
"major"
|
|
528
|
-
]) if (bumpType !== suggestedBumpType) {
|
|
529
|
-
const version = calculateNewVersion(currentVersion, bumpType);
|
|
530
|
-
choices.push({
|
|
531
|
-
title: `${bumpType}: ${version}`,
|
|
532
|
-
value: bumpType
|
|
533
|
-
});
|
|
534
|
-
}
|
|
535
|
-
choices.push({
|
|
536
|
-
title: "Custom version",
|
|
537
|
-
value: "custom"
|
|
1284
|
+
function getDistTag(version) {
|
|
1285
|
+
return isPrerelease(version) ? "next" : "latest";
|
|
1286
|
+
}
|
|
1287
|
+
function buildPackage(packagePath) {
|
|
1288
|
+
return Effect.gen(function* () {
|
|
1289
|
+
const executor = yield* CommandExecutor.CommandExecutor;
|
|
1290
|
+
const command = Command.make("pnpm", "run", "build").pipe(Command.workingDirectory(packagePath));
|
|
1291
|
+
return (yield* executor.string(command).pipe(Effect.mapError((err) => /* @__PURE__ */ new Error(`Failed to build package at ${packagePath}: ${err.message}`)))).trim();
|
|
538
1292
|
});
|
|
539
|
-
const response = await prompts([{
|
|
540
|
-
type: "select",
|
|
541
|
-
name: "choice",
|
|
542
|
-
message: `${packageName} (${currentVersion}):`,
|
|
543
|
-
choices,
|
|
544
|
-
initial: 0
|
|
545
|
-
}, {
|
|
546
|
-
type: (prev) => prev === "custom" ? "text" : null,
|
|
547
|
-
name: "customVersion",
|
|
548
|
-
message: "Enter custom version:",
|
|
549
|
-
initial: suggestedVersion,
|
|
550
|
-
validate: (value) => {
|
|
551
|
-
return /^\d+\.\d+\.\d+(?:[-+].+)?$/.test(value) || "Invalid semver version (e.g., 1.0.0)";
|
|
552
|
-
}
|
|
553
|
-
}]);
|
|
554
|
-
if (response.choice === "suggested") return suggestedVersion;
|
|
555
|
-
else if (response.choice === "custom") return response.customVersion;
|
|
556
|
-
else return calculateNewVersion(currentVersion, response.choice);
|
|
557
1293
|
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
1294
|
+
function constructPublishProgram(config) {
|
|
1295
|
+
return Effect.gen(function* () {
|
|
1296
|
+
const git = yield* GitService;
|
|
1297
|
+
const npm = yield* NPMService;
|
|
1298
|
+
const workspace = yield* WorkspaceService;
|
|
1299
|
+
const dependencyGraph = yield* DependencyGraphService;
|
|
1300
|
+
yield* git.workspace.assertWorkspaceReady;
|
|
1301
|
+
const currentBranch = yield* git.branches.get;
|
|
1302
|
+
if (currentBranch !== config.branch.default) return yield* Effect.fail(/* @__PURE__ */ new Error(`Publish must be run on the default branch "${config.branch.default}". Current branch: "${currentBranch}"`));
|
|
1303
|
+
yield* Console.log(`✅ On default branch "${config.branch.default}".`);
|
|
1304
|
+
const publicPackages = (yield* workspace.discoverWorkspacePackages).filter((pkg) => !pkg.packageJson.private);
|
|
1305
|
+
yield* Console.log(`📦 Found ${publicPackages.length} public package${publicPackages.length === 1 ? "" : "s"} to check.`);
|
|
1306
|
+
const orderedPackages = yield* dependencyGraph.topologicalOrder(publicPackages);
|
|
1307
|
+
const results = [];
|
|
1308
|
+
for (const updateOrder of orderedPackages) {
|
|
1309
|
+
const pkg = updateOrder.package;
|
|
1310
|
+
const version = pkg.version;
|
|
1311
|
+
const tagName = `${pkg.name}@${version}`;
|
|
1312
|
+
if (yield* npm.versionExists(pkg.name, version)) {
|
|
1313
|
+
yield* Console.log(`⏭️ Skipping ${pkg.name}@${version} - already published.`);
|
|
1314
|
+
results.push({
|
|
1315
|
+
packageName: pkg.name,
|
|
1316
|
+
version,
|
|
1317
|
+
status: "skipped",
|
|
1318
|
+
reason: "Already published to npm"
|
|
1319
|
+
});
|
|
1320
|
+
continue;
|
|
1321
|
+
}
|
|
1322
|
+
yield* Console.log(`🔨 Building ${pkg.name}...`);
|
|
1323
|
+
yield* buildPackage(pkg.path);
|
|
1324
|
+
yield* Console.log(`✅ Build complete for ${pkg.name}.`);
|
|
1325
|
+
const distTag = getDistTag(version);
|
|
1326
|
+
yield* Console.log(`🚀 Publishing ${pkg.name}@${version} with tag "${distTag}"...`);
|
|
1327
|
+
const publishResult = yield* npm.publish({
|
|
1328
|
+
packagePath: pkg.path,
|
|
1329
|
+
tagName: distTag,
|
|
1330
|
+
otp: config.npm.otp,
|
|
1331
|
+
provenance: config.npm.provenance,
|
|
1332
|
+
dryRun: config.dryRun
|
|
1333
|
+
}).pipe(Effect.map(() => ({ success: true })), Effect.catchAll((err) => Effect.succeed({
|
|
1334
|
+
success: false,
|
|
1335
|
+
error: err
|
|
1336
|
+
})));
|
|
1337
|
+
if (publishResult.success) {
|
|
1338
|
+
yield* Console.log(`✅ Published ${pkg.name}@${version}.`);
|
|
1339
|
+
if (!config.dryRun) {
|
|
1340
|
+
yield* Console.log(`🏷️ Creating tag ${tagName}...`);
|
|
1341
|
+
yield* git.tags.create(tagName, `Release ${tagName}`);
|
|
1342
|
+
yield* git.tags.push(tagName);
|
|
1343
|
+
yield* Console.log(`✅ Tag ${tagName} created and pushed.`);
|
|
1344
|
+
} else yield* Console.log(`🏷️ [Dry Run] Would create and push tag ${tagName}.`);
|
|
1345
|
+
results.push({
|
|
1346
|
+
packageName: pkg.name,
|
|
1347
|
+
version,
|
|
1348
|
+
status: "published"
|
|
1349
|
+
});
|
|
1350
|
+
} else {
|
|
1351
|
+
const error = publishResult.error;
|
|
1352
|
+
yield* Console.log(`❌ Failed to publish ${pkg.name}@${version}: ${error.message}`);
|
|
1353
|
+
results.push({
|
|
1354
|
+
packageName: pkg.name,
|
|
1355
|
+
version,
|
|
1356
|
+
status: "failed",
|
|
1357
|
+
reason: error.message
|
|
1358
|
+
});
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
const published = results.filter((r) => r.status === "published");
|
|
1362
|
+
const skipped = results.filter((r) => r.status === "skipped");
|
|
1363
|
+
const failed = results.filter((r) => r.status === "failed");
|
|
1364
|
+
yield* Console.log("\n📊 Publish Summary:");
|
|
1365
|
+
yield* Console.log(` Published: ${published.length}`);
|
|
1366
|
+
yield* Console.log(` Skipped: ${skipped.length}`);
|
|
1367
|
+
yield* Console.log(` Failed: ${failed.length}`);
|
|
1368
|
+
if (failed.length > 0) {
|
|
1369
|
+
yield* Console.log("\n❌ Failed packages:");
|
|
1370
|
+
for (const f of failed) yield* Console.log(` - ${f.packageName}@${f.version}: ${f.reason}`);
|
|
1371
|
+
return yield* Effect.fail(/* @__PURE__ */ new Error("Some packages failed to publish."));
|
|
1372
|
+
}
|
|
1373
|
+
if (published.length === 0 && skipped.length > 0) yield* Console.log("\n✅ All packages were already published.");
|
|
1374
|
+
else if (published.length > 0) yield* Console.log("\n🎉 Publish complete!");
|
|
1375
|
+
});
|
|
565
1376
|
}
|
|
566
1377
|
|
|
567
1378
|
//#endregion
|
|
568
|
-
//#region src/
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
if (!options) return true;
|
|
572
|
-
if (options.excludePrivate && pkg.private) return false;
|
|
573
|
-
if (options.included && options.included.length > 0) {
|
|
574
|
-
if (!options.included.includes(pkg.name)) return false;
|
|
575
|
-
}
|
|
576
|
-
if (options.excluded?.includes(pkg.name)) return false;
|
|
577
|
-
return true;
|
|
578
|
-
}
|
|
579
|
-
async function findWorkspacePackages(workspaceRoot, options) {
|
|
580
|
-
const result = await run("pnpm", [
|
|
581
|
-
"-r",
|
|
582
|
-
"ls",
|
|
583
|
-
"--json"
|
|
584
|
-
], { nodeOptions: {
|
|
585
|
-
cwd: workspaceRoot,
|
|
586
|
-
stdio: "pipe"
|
|
587
|
-
} });
|
|
588
|
-
const rawProjects = JSON.parse(result.stdout);
|
|
589
|
-
const packages = [];
|
|
590
|
-
const allPackageNames = new Set(rawProjects.map((p) => p.name));
|
|
591
|
-
for (const rawProject of rawProjects) {
|
|
592
|
-
const content = await readFile(join(rawProject.path, "package.json"), "utf-8");
|
|
593
|
-
const packageJson = JSON.parse(content);
|
|
594
|
-
if (!shouldIncludePackage(packageJson, options)) {
|
|
595
|
-
debug?.(`Excluding package ${rawProject.name}`);
|
|
596
|
-
continue;
|
|
597
|
-
}
|
|
598
|
-
const workspaceDeps = extractWorkspaceDependencies(rawProject.dependencies, allPackageNames);
|
|
599
|
-
const workspaceDevDeps = extractWorkspaceDependencies(rawProject.devDependencies, allPackageNames);
|
|
600
|
-
packages.push({
|
|
601
|
-
name: rawProject.name,
|
|
602
|
-
version: rawProject.version,
|
|
603
|
-
path: rawProject.path,
|
|
604
|
-
packageJson,
|
|
605
|
-
workspaceDependencies: workspaceDeps,
|
|
606
|
-
workspaceDevDependencies: workspaceDevDeps
|
|
607
|
-
});
|
|
608
|
-
}
|
|
609
|
-
return packages;
|
|
1379
|
+
//#region src/verify.ts
|
|
1380
|
+
function satisfiesRange(range, version) {
|
|
1381
|
+
return semver.satisfies(version, range, { includePrerelease: true });
|
|
610
1382
|
}
|
|
611
|
-
function
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
1383
|
+
function snapshotPackageJson(pkg, ref) {
|
|
1384
|
+
return Effect.gen(function* () {
|
|
1385
|
+
return yield* (yield* GitService).workspace.readFile(`${pkg.path}/package.json`, ref).pipe(Effect.flatMap((content) => Effect.try({
|
|
1386
|
+
try: () => JSON.parse(content),
|
|
1387
|
+
catch: (e) => /* @__PURE__ */ new Error(`Failed to parse package.json for ${pkg.name} at ${ref}: ${String(e)}`)
|
|
1388
|
+
})));
|
|
615
1389
|
});
|
|
616
1390
|
}
|
|
617
|
-
function
|
|
618
|
-
const
|
|
619
|
-
const
|
|
1391
|
+
function findDrift(packages, releases, branchSnapshots) {
|
|
1392
|
+
const releaseVersionByName = /* @__PURE__ */ new Map();
|
|
1393
|
+
for (const rel of releases) releaseVersionByName.set(rel.package.name, rel.newVersion);
|
|
1394
|
+
const reasons = [];
|
|
620
1395
|
for (const pkg of packages) {
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
1396
|
+
const snapshot = branchSnapshots.get(pkg.name);
|
|
1397
|
+
if (snapshot == null) {
|
|
1398
|
+
reasons.push({
|
|
1399
|
+
packageName: pkg.name,
|
|
1400
|
+
reason: "package.json missing on release branch"
|
|
1401
|
+
});
|
|
1402
|
+
continue;
|
|
1403
|
+
}
|
|
1404
|
+
if (snapshot instanceof Error) {
|
|
1405
|
+
reasons.push({
|
|
1406
|
+
packageName: pkg.name,
|
|
1407
|
+
reason: snapshot.message
|
|
1408
|
+
});
|
|
1409
|
+
continue;
|
|
1410
|
+
}
|
|
1411
|
+
const expectedVersion = releaseVersionByName.get(pkg.name) ?? pkg.version;
|
|
1412
|
+
const branchVersion = typeof snapshot.version === "string" ? snapshot.version : void 0;
|
|
1413
|
+
if (!branchVersion) {
|
|
1414
|
+
reasons.push({
|
|
1415
|
+
packageName: pkg.name,
|
|
1416
|
+
reason: "package.json on release branch lacks version"
|
|
1417
|
+
});
|
|
1418
|
+
continue;
|
|
1419
|
+
}
|
|
1420
|
+
if (branchVersion !== expectedVersion) reasons.push({
|
|
1421
|
+
packageName: pkg.name,
|
|
1422
|
+
reason: `version mismatch: expected ${expectedVersion}, found ${branchVersion}`
|
|
1423
|
+
});
|
|
1424
|
+
for (const section of [
|
|
1425
|
+
"dependencies",
|
|
1426
|
+
"devDependencies",
|
|
1427
|
+
"peerDependencies"
|
|
1428
|
+
]) {
|
|
1429
|
+
const deps = snapshot[section];
|
|
1430
|
+
if (!deps || typeof deps !== "object") continue;
|
|
1431
|
+
for (const [depName, range] of Object.entries(deps)) {
|
|
1432
|
+
const bumpedVersion = releaseVersionByName.get(depName);
|
|
1433
|
+
if (!bumpedVersion) continue;
|
|
1434
|
+
if (typeof range !== "string") {
|
|
1435
|
+
reasons.push({
|
|
1436
|
+
packageName: pkg.name,
|
|
1437
|
+
reason: `${section}.${depName} is not a string range`
|
|
1438
|
+
});
|
|
1439
|
+
continue;
|
|
1440
|
+
}
|
|
1441
|
+
if (!satisfiesRange(range, bumpedVersion)) reasons.push({
|
|
1442
|
+
packageName: pkg.name,
|
|
1443
|
+
reason: `${section}.${depName} does not include ${bumpedVersion}`
|
|
1444
|
+
});
|
|
1445
|
+
}
|
|
629
1446
|
}
|
|
630
1447
|
}
|
|
631
|
-
return
|
|
632
|
-
packages: packagesMap,
|
|
633
|
-
dependents
|
|
634
|
-
};
|
|
1448
|
+
return reasons;
|
|
635
1449
|
}
|
|
636
|
-
function
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
const
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
1450
|
+
function constructVerifyProgram(config) {
|
|
1451
|
+
return Effect.gen(function* () {
|
|
1452
|
+
const git = yield* GitService;
|
|
1453
|
+
const github = yield* GitHubService;
|
|
1454
|
+
const dependencyGraph = yield* DependencyGraphService;
|
|
1455
|
+
const versionCalculator = yield* VersionCalculatorService;
|
|
1456
|
+
const workspace = yield* WorkspaceService;
|
|
1457
|
+
yield* git.workspace.assertWorkspaceReady;
|
|
1458
|
+
const releasePullRequest = yield* github.getPullRequestByBranch(config.branch.release);
|
|
1459
|
+
if (!releasePullRequest || !releasePullRequest.head) return yield* Effect.fail(/* @__PURE__ */ new Error(`Release pull request for branch "${config.branch.release}" does not exist.`));
|
|
1460
|
+
yield* Console.log(`✅ Release pull request #${releasePullRequest.number} exists.`);
|
|
1461
|
+
if ((yield* git.branches.get) !== config.branch.default) {
|
|
1462
|
+
yield* git.branches.checkout(config.branch.default);
|
|
1463
|
+
yield* Console.log(`✅ Checked out to default branch "${config.branch.default}".`);
|
|
646
1464
|
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
visited.add(pkgName);
|
|
651
|
-
const pkg = graph.packages.get(pkgName);
|
|
652
|
-
if (!pkg) return;
|
|
653
|
-
const allDeps = [...pkg.workspaceDependencies, ...pkg.workspaceDevDependencies];
|
|
654
|
-
let maxDepLevel = level;
|
|
655
|
-
for (const dep of allDeps) if (toUpdate.has(dep)) {
|
|
656
|
-
visit(dep, level);
|
|
657
|
-
const depResult = result.find((r) => r.package.name === dep);
|
|
658
|
-
if (depResult && depResult.level >= maxDepLevel) maxDepLevel = depResult.level + 1;
|
|
659
|
-
}
|
|
660
|
-
result.push({
|
|
661
|
-
package: pkg,
|
|
662
|
-
level: maxDepLevel
|
|
1465
|
+
const overrides = yield* loadOverrides({
|
|
1466
|
+
sha: releasePullRequest.head.sha,
|
|
1467
|
+
overridesPath: ".github/ucdjs-release.overrides.json"
|
|
663
1468
|
});
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
1469
|
+
yield* Console.log("Loaded overrides:", overrides);
|
|
1470
|
+
const packages = yield* workspace.discoverWorkspacePackages.pipe(Effect.flatMap(mergePackageCommitsIntoPackages), Effect.flatMap((pkgs) => mergeCommitsAffectingGloballyIntoPackage(pkgs, config.globalCommitMode)));
|
|
1471
|
+
yield* Console.log("Discovered packages with commits and global commits:", packages);
|
|
1472
|
+
const releases = yield* versionCalculator.calculateBumps(packages, overrides);
|
|
1473
|
+
const ordered = yield* dependencyGraph.topologicalOrder(packages);
|
|
1474
|
+
yield* Console.log("Calculated releases:", releases);
|
|
1475
|
+
yield* Console.log("Release order:", ordered);
|
|
1476
|
+
const releaseHeadSha = releasePullRequest.head.sha;
|
|
1477
|
+
const branchSnapshots = /* @__PURE__ */ new Map();
|
|
1478
|
+
for (const pkg of packages) {
|
|
1479
|
+
const snapshot = yield* snapshotPackageJson(pkg, releaseHeadSha).pipe(Effect.catchAll((err) => Effect.succeed(err instanceof Error ? err : new Error(String(err)))));
|
|
1480
|
+
branchSnapshots.set(pkg.name, snapshot);
|
|
1481
|
+
}
|
|
1482
|
+
const drift = findDrift(packages, releases, branchSnapshots);
|
|
1483
|
+
if (drift.length === 0) yield* Console.log("✅ Release branch is in sync with expected releases.");
|
|
1484
|
+
else yield* Console.log("❌ Release branch is out of sync:", drift);
|
|
1485
|
+
const status = drift.length === 0 ? {
|
|
1486
|
+
state: "success",
|
|
1487
|
+
description: "Release artifacts in sync",
|
|
1488
|
+
context: "release/verify"
|
|
1489
|
+
} : {
|
|
1490
|
+
state: "failure",
|
|
1491
|
+
description: "Release branch out of sync",
|
|
1492
|
+
context: "release/verify"
|
|
1493
|
+
};
|
|
1494
|
+
yield* github.setCommitStatus(releaseHeadSha, status);
|
|
1495
|
+
if (drift.length > 0) return yield* Effect.fail(/* @__PURE__ */ new Error("Release branch is out of sync."));
|
|
1496
|
+
});
|
|
668
1497
|
}
|
|
669
1498
|
|
|
670
1499
|
//#endregion
|
|
671
|
-
//#region src/
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
const
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
return null;
|
|
682
|
-
}
|
|
683
|
-
const { workspacePackages, packagesToAnalyze: initialPackages } = await discoverPackages(workspaceRoot, options);
|
|
684
|
-
if (initialPackages.length === 0) return null;
|
|
685
|
-
const isPackagePromptEnabled = options.prompts?.packages !== false;
|
|
686
|
-
const isPackagesPreConfigured = Array.isArray(options.packages) || typeof options.packages === "object" && options.packages.included != null;
|
|
687
|
-
let packagesToAnalyze = initialPackages;
|
|
688
|
-
if (!isCI && isPackagePromptEnabled && !isPackagesPreConfigured) {
|
|
689
|
-
const selectedNames = await promptPackageSelection(initialPackages);
|
|
690
|
-
packagesToAnalyze = initialPackages.filter((pkg) => selectedNames.includes(pkg.name));
|
|
691
|
-
}
|
|
692
|
-
const changedPackages = await analyzeCommits(packagesToAnalyze, workspaceRoot);
|
|
693
|
-
if (changedPackages.size === 0) throw new Error("No packages have changes requiring a release");
|
|
694
|
-
let versionUpdates = calculateVersions(workspacePackages, changedPackages);
|
|
695
|
-
const isVersionPromptEnabled = options.prompts?.versions !== false;
|
|
696
|
-
if (!isCI && isVersionPromptEnabled) {
|
|
697
|
-
const versionOverrides = await promptVersionOverrides(versionUpdates.map((u) => ({
|
|
698
|
-
name: u.package.name,
|
|
699
|
-
currentVersion: u.currentVersion,
|
|
700
|
-
suggestedVersion: u.newVersion,
|
|
701
|
-
bumpType: u.bumpType
|
|
702
|
-
})));
|
|
703
|
-
versionUpdates = versionUpdates.map((update) => {
|
|
704
|
-
const overriddenVersion = versionOverrides.get(update.package.name);
|
|
705
|
-
if (overriddenVersion && overriddenVersion !== update.newVersion) return {
|
|
706
|
-
...update,
|
|
707
|
-
newVersion: overriddenVersion
|
|
708
|
-
};
|
|
709
|
-
return update;
|
|
710
|
-
});
|
|
711
|
-
}
|
|
712
|
-
const allUpdates = createDependentUpdates(getPackageUpdateOrder(buildDependencyGraph(workspacePackages), new Set(versionUpdates.map((u) => u.package.name))), versionUpdates);
|
|
713
|
-
const currentBranch = await getCurrentBranch(workspaceRoot);
|
|
714
|
-
const existingPullRequest = await getExistingPullRequest({
|
|
715
|
-
owner,
|
|
716
|
-
repo,
|
|
717
|
-
branch: releaseBranch,
|
|
718
|
-
githubToken
|
|
1500
|
+
//#region src/index.ts
|
|
1501
|
+
async function createReleaseScripts(options) {
|
|
1502
|
+
const config = normalizeReleaseScriptsOptions(options);
|
|
1503
|
+
const AppLayer = Layer.mergeAll(ChangelogService.Default, GitService.Default, GitHubService.Default, DependencyGraphService.Default, NPMService.Default, PackageUpdaterService.Default, VersionCalculatorService.Default, WorkspaceService.Default).pipe(Layer.provide(Layer.succeed(ReleaseScriptsOptions, config)), Layer.provide(NodeCommandExecutor.layer), Layer.provide(NodeFileSystem.layer));
|
|
1504
|
+
const runProgram = (program) => {
|
|
1505
|
+
const provided = program.pipe(Effect.provide(AppLayer));
|
|
1506
|
+
return Effect.runPromise(provided);
|
|
1507
|
+
};
|
|
1508
|
+
const safeguardProgram = Effect.gen(function* () {
|
|
1509
|
+
return yield* (yield* GitService).workspace.assertWorkspaceReady;
|
|
719
1510
|
});
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
await createBranch(releaseBranch, currentBranch, workspaceRoot);
|
|
727
|
-
}
|
|
728
|
-
if (!await checkoutBranch(releaseBranch, workspaceRoot)) throw new Error(`Failed to checkout branch: ${releaseBranch}`);
|
|
729
|
-
if (branchExists) {
|
|
730
|
-
console.log("Pulling latest changes from remote");
|
|
731
|
-
if (!await pullLatestChanges(releaseBranch, workspaceRoot)) console.log("Warning: Failed to pull latest changes, continuing anyway");
|
|
732
|
-
}
|
|
733
|
-
console.log("Rebasing release branch onto", currentBranch);
|
|
734
|
-
await rebaseBranch(currentBranch, workspaceRoot);
|
|
735
|
-
await updatePackageJsonFiles(allUpdates);
|
|
736
|
-
const hasCommitted = await commitChanges("chore: update release versions", workspaceRoot);
|
|
737
|
-
const isBranchAhead = await isBranchAheadOfRemote(releaseBranch, workspaceRoot);
|
|
738
|
-
if (!hasCommitted && !isBranchAhead) {
|
|
739
|
-
console.log("No changes to commit and branch is in sync with remote");
|
|
740
|
-
await checkoutBranch(currentBranch, workspaceRoot);
|
|
741
|
-
if (prExists) {
|
|
742
|
-
console.log("No updates needed, PR is already up to date");
|
|
743
|
-
return {
|
|
744
|
-
updates: allUpdates,
|
|
745
|
-
prUrl: existingPullRequest.html_url,
|
|
746
|
-
created: false
|
|
747
|
-
};
|
|
748
|
-
} else {
|
|
749
|
-
console.error("No changes to commit, and no existing PR. Nothing to do.");
|
|
750
|
-
return null;
|
|
751
|
-
}
|
|
1511
|
+
try {
|
|
1512
|
+
await runProgram(safeguardProgram);
|
|
1513
|
+
} catch (err) {
|
|
1514
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1515
|
+
await Effect.runPromise(Console.error(`❌ Initialization failed: ${message}`));
|
|
1516
|
+
throw err;
|
|
752
1517
|
}
|
|
753
|
-
console.log("Pushing changes to remote");
|
|
754
|
-
await pushBranch(releaseBranch, workspaceRoot, { forceWithLease: true });
|
|
755
|
-
const prTitle = existingPullRequest?.title || options.pullRequest?.title || "chore: update package versions";
|
|
756
|
-
const prBody = generatePullRequestBody(allUpdates, options.pullRequest?.body);
|
|
757
|
-
const pullRequest = await upsertPullRequest({
|
|
758
|
-
owner,
|
|
759
|
-
repo,
|
|
760
|
-
pullNumber: existingPullRequest?.number,
|
|
761
|
-
title: prTitle,
|
|
762
|
-
body: prBody,
|
|
763
|
-
head: releaseBranch,
|
|
764
|
-
base: currentBranch,
|
|
765
|
-
githubToken
|
|
766
|
-
});
|
|
767
|
-
console.log(prExists ? "Updated pull request:" : "Created pull request:", pullRequest?.html_url);
|
|
768
|
-
await checkoutBranch(currentBranch, workspaceRoot);
|
|
769
1518
|
return {
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
included: packageNames
|
|
791
|
-
});
|
|
792
|
-
packagesToAnalyze = workspacePackages.filter((pkg) => packageNames.includes(pkg.name));
|
|
793
|
-
if (packagesToAnalyze.length !== packageNames.length) {
|
|
794
|
-
const found = new Set(packagesToAnalyze.map((p) => p.name));
|
|
795
|
-
const missing = packageNames.filter((p) => !found.has(p));
|
|
796
|
-
throw new Error(`Packages not found in workspace: ${missing.join(", ")}`);
|
|
1519
|
+
async verify() {
|
|
1520
|
+
return runProgram(constructVerifyProgram(config));
|
|
1521
|
+
},
|
|
1522
|
+
async prepare() {
|
|
1523
|
+
return runProgram(constructPrepareProgram(config));
|
|
1524
|
+
},
|
|
1525
|
+
async publish() {
|
|
1526
|
+
return runProgram(constructPublishProgram(config));
|
|
1527
|
+
},
|
|
1528
|
+
packages: {
|
|
1529
|
+
async list() {
|
|
1530
|
+
return runProgram(Effect.gen(function* () {
|
|
1531
|
+
return yield* (yield* WorkspaceService).discoverWorkspacePackages;
|
|
1532
|
+
}));
|
|
1533
|
+
},
|
|
1534
|
+
async get(packageName) {
|
|
1535
|
+
return runProgram(Effect.gen(function* () {
|
|
1536
|
+
return (yield* (yield* WorkspaceService).findPackageByName(packageName)) || null;
|
|
1537
|
+
}));
|
|
1538
|
+
}
|
|
797
1539
|
}
|
|
798
|
-
return {
|
|
799
|
-
workspacePackages,
|
|
800
|
-
packagesToAnalyze
|
|
801
|
-
};
|
|
802
|
-
}
|
|
803
|
-
workspacePackages = await findWorkspacePackages(workspaceRoot, options.packages);
|
|
804
|
-
packagesToAnalyze = workspacePackages;
|
|
805
|
-
return {
|
|
806
|
-
workspacePackages,
|
|
807
|
-
packagesToAnalyze
|
|
808
1540
|
};
|
|
809
1541
|
}
|
|
810
|
-
async function analyzeCommits(packages, workspaceRoot) {
|
|
811
|
-
const changedPackages = /* @__PURE__ */ new Map();
|
|
812
|
-
for (const pkg of packages) {
|
|
813
|
-
const bump = await analyzePackageCommits(pkg, workspaceRoot);
|
|
814
|
-
if (bump !== "none") changedPackages.set(pkg.name, bump);
|
|
815
|
-
}
|
|
816
|
-
return changedPackages;
|
|
817
|
-
}
|
|
818
|
-
function calculateVersions(allPackages, changedPackages) {
|
|
819
|
-
const updates = [];
|
|
820
|
-
for (const [pkgName, bump] of changedPackages) {
|
|
821
|
-
const pkg = allPackages.find((p) => p.name === pkgName);
|
|
822
|
-
if (!pkg) continue;
|
|
823
|
-
updates.push(createVersionUpdate(pkg, bump, true));
|
|
824
|
-
}
|
|
825
|
-
return updates;
|
|
826
|
-
}
|
|
827
|
-
async function updatePackageJsonFiles(updates) {
|
|
828
|
-
await Promise.all(updates.map(async (update) => {
|
|
829
|
-
const depUpdates = getDependencyUpdates(update.package, updates);
|
|
830
|
-
await updatePackageJson(update.package, update.newVersion, depUpdates);
|
|
831
|
-
}));
|
|
832
|
-
}
|
|
833
1542
|
|
|
834
1543
|
//#endregion
|
|
835
|
-
export {
|
|
1544
|
+
export { createReleaseScripts };
|