relizy 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1230 -0
- package/bin/relizy.mjs +2 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.mjs +160 -0
- package/dist/index.d.mts +559 -0
- package/dist/index.d.ts +559 -0
- package/dist/index.mjs +15 -0
- package/dist/shared/relizy.Blp-ymeX.mjs +2954 -0
- package/package.json +106 -0
|
@@ -0,0 +1,2954 @@
|
|
|
1
|
+
import { logger, execPromise } from '@maz-ui/node';
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync, statSync } from 'node:fs';
|
|
4
|
+
import path, { join } from 'node:path';
|
|
5
|
+
import { formatCompareChanges, formatReference, resolveRepoConfig, getRepoConfig, createGithubRelease, getGitDiff, parseCommits, determineSemverChange } from 'changelogen';
|
|
6
|
+
import fastGlob from 'fast-glob';
|
|
7
|
+
import { confirm } from '@inquirer/prompts';
|
|
8
|
+
import { upperFirst, formatJson } from '@maz-ui/utils';
|
|
9
|
+
import * as semver from 'semver';
|
|
10
|
+
import process$1 from 'node:process';
|
|
11
|
+
import { setupDotenv, loadConfig } from 'c12';
|
|
12
|
+
import { defu } from 'defu';
|
|
13
|
+
import { convert } from 'convert-gitmoji';
|
|
14
|
+
import { fetch as fetch$1 } from 'node-fetch-native';
|
|
15
|
+
|
|
16
|
+
async function generateMarkDown(commits, config) {
|
|
17
|
+
const typeGroups = groupBy(commits, "type");
|
|
18
|
+
const markdown = [];
|
|
19
|
+
const breakingChanges = [];
|
|
20
|
+
const v = config.newVersion && config.templates.tagBody.replaceAll("{{newVersion}}", config.newVersion);
|
|
21
|
+
markdown.push("", `## ${v || `${config.from || ""}...${config.to}`}`, "");
|
|
22
|
+
if (config.repo && config.from && v) {
|
|
23
|
+
markdown.push(formatCompareChanges(v, config));
|
|
24
|
+
}
|
|
25
|
+
for (const type in config.types) {
|
|
26
|
+
const group = typeGroups[type];
|
|
27
|
+
if (!group || group.length === 0) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (typeof config.types[type] === "boolean") {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
markdown.push("", `### ${config.types[type]?.title}`, "");
|
|
34
|
+
for (const commit of group.reverse()) {
|
|
35
|
+
const line = formatCommit(commit, config);
|
|
36
|
+
markdown.push(line);
|
|
37
|
+
if (commit.isBreaking) {
|
|
38
|
+
breakingChanges.push(line);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (breakingChanges.length > 0) {
|
|
43
|
+
markdown.push("", "#### \u26A0\uFE0F Breaking Changes", "", ...breakingChanges);
|
|
44
|
+
}
|
|
45
|
+
const _authors = /* @__PURE__ */ new Map();
|
|
46
|
+
for (const commit of commits) {
|
|
47
|
+
if (!commit.author) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const name = formatName(commit.author.name);
|
|
51
|
+
if (!name || name.includes("[bot]")) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (config.excludeAuthors && config.excludeAuthors.some(
|
|
55
|
+
(v2) => name.includes(v2) || commit.author.email?.includes(v2)
|
|
56
|
+
)) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (_authors.has(name)) {
|
|
60
|
+
const entry = _authors.get(name);
|
|
61
|
+
entry?.email.add(commit.author.email);
|
|
62
|
+
} else {
|
|
63
|
+
_authors.set(name, { email: /* @__PURE__ */ new Set([commit.author.email]), name });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
await Promise.all(
|
|
67
|
+
[..._authors.keys()].map(async (authorName) => {
|
|
68
|
+
const meta = _authors.get(authorName);
|
|
69
|
+
if (!meta) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
for (const data of [...meta.email, meta.name]) {
|
|
73
|
+
const { user } = await fetch$1(`https://ungh.cc/users/find/${data}`).then((r) => r.json()).catch(() => ({ user: null }));
|
|
74
|
+
if (user) {
|
|
75
|
+
meta.github = user.username;
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
);
|
|
81
|
+
const authors = [..._authors.entries()].map((e) => ({
|
|
82
|
+
name: e[0],
|
|
83
|
+
...e[1]
|
|
84
|
+
}));
|
|
85
|
+
if (authors.length > 0 && !config.noAuthors) {
|
|
86
|
+
markdown.push(
|
|
87
|
+
"",
|
|
88
|
+
"### \u2764\uFE0F Contributors",
|
|
89
|
+
"",
|
|
90
|
+
...authors.map((i) => {
|
|
91
|
+
const _email = [...i.email].find(
|
|
92
|
+
(e) => !e.includes("noreply.github.com")
|
|
93
|
+
);
|
|
94
|
+
const email = config.hideAuthorEmail !== true && _email ? ` <${_email}>` : "";
|
|
95
|
+
const github = i.github ? ` ([@${i.github}](https://github.com/${i.github}))` : "";
|
|
96
|
+
return `- ${i.name}${github || email || ""}`;
|
|
97
|
+
})
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
return convert(markdown.join("\n").trim(), true);
|
|
101
|
+
}
|
|
102
|
+
function getCommitBody(commit) {
|
|
103
|
+
if (!commit.body) {
|
|
104
|
+
return "";
|
|
105
|
+
}
|
|
106
|
+
const lines = commit.body.split("\n");
|
|
107
|
+
const contentLines = lines.filter((line) => {
|
|
108
|
+
const trimmedLine = line.trim();
|
|
109
|
+
if (!trimmedLine) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
const isFileLine = /^[AMDRC]\s+/.test(trimmedLine);
|
|
113
|
+
return !isFileLine;
|
|
114
|
+
});
|
|
115
|
+
if (contentLines.length === 0) {
|
|
116
|
+
return "";
|
|
117
|
+
}
|
|
118
|
+
const indentedBody = contentLines.map((line) => ` ${line}`).join("\n");
|
|
119
|
+
return `
|
|
120
|
+
|
|
121
|
+
${indentedBody}
|
|
122
|
+
`;
|
|
123
|
+
}
|
|
124
|
+
function formatCommit(commit, config) {
|
|
125
|
+
const body = config.changelog.includeCommitBody ? getCommitBody(commit) : "";
|
|
126
|
+
return `- ${commit.scope ? `**${commit.scope.trim()}:** ` : ""}${commit.isBreaking ? "\u26A0\uFE0F " : ""}${upperFirst(commit.description)}${formatReferences(commit.references, config)}${body}`;
|
|
127
|
+
}
|
|
128
|
+
function formatReferences(references, config) {
|
|
129
|
+
const pr = references.filter((ref) => ref.type === "pull-request");
|
|
130
|
+
const issue = references.filter((ref) => ref.type === "issue");
|
|
131
|
+
if (pr.length > 0 || issue.length > 0) {
|
|
132
|
+
return ` (${[...pr, ...issue].map((ref) => formatReference(ref, config.repo)).join(", ")})`;
|
|
133
|
+
}
|
|
134
|
+
if (references.length > 0) {
|
|
135
|
+
return ` (${formatReference(references[0], config.repo)})`;
|
|
136
|
+
}
|
|
137
|
+
return "";
|
|
138
|
+
}
|
|
139
|
+
function formatName(name = "") {
|
|
140
|
+
return name.split(" ").map((p) => upperFirst(p.trim())).join(" ");
|
|
141
|
+
}
|
|
142
|
+
function groupBy(items, key) {
|
|
143
|
+
const groups = {};
|
|
144
|
+
for (const item of items) {
|
|
145
|
+
groups[item[key]] = groups[item[key]] || [];
|
|
146
|
+
groups[item[key]]?.push(item);
|
|
147
|
+
}
|
|
148
|
+
return groups;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function fromTagIsFirstCommit(fromTag, cwd) {
|
|
152
|
+
return fromTag === getFirstCommit(cwd);
|
|
153
|
+
}
|
|
154
|
+
async function generateChangelog({
|
|
155
|
+
pkg,
|
|
156
|
+
commits,
|
|
157
|
+
config,
|
|
158
|
+
from,
|
|
159
|
+
dryRun
|
|
160
|
+
}) {
|
|
161
|
+
let fromTag = config.from || config.templates.tagBody.replace("{{newVersion}}", pkg.currentVersion) || from;
|
|
162
|
+
const isFirstCommit = fromTagIsFirstCommit(fromTag, config.cwd);
|
|
163
|
+
if (isFirstCommit) {
|
|
164
|
+
fromTag = config.monorepo?.versionMode === "independent" ? `${pkg.name}@0.0.0` : config.templates.tagBody.replace("{{newVersion}}", "0.0.0");
|
|
165
|
+
}
|
|
166
|
+
const toTag = config.to || (config.monorepo?.versionMode === "independent" ? `${pkg.name}@${pkg.version}` : config.templates.tagBody.replace("{{newVersion}}", pkg.version));
|
|
167
|
+
try {
|
|
168
|
+
logger.debug(`Generating changelog for ${pkg.name} - from ${fromTag} to ${toTag}`);
|
|
169
|
+
config = {
|
|
170
|
+
...config,
|
|
171
|
+
from: fromTag,
|
|
172
|
+
to: toTag
|
|
173
|
+
};
|
|
174
|
+
let changelog = await generateMarkDown(commits, config);
|
|
175
|
+
logger.verbose(`Output changelog for ${pkg.name}:
|
|
176
|
+
${changelog}`);
|
|
177
|
+
if (commits.length === 0) {
|
|
178
|
+
changelog = `${changelog}
|
|
179
|
+
|
|
180
|
+
${config.templates.emptyChangelogContent}`;
|
|
181
|
+
}
|
|
182
|
+
logger.debug(`Changelog generated for ${pkg.name} (${commits.length} commits)`);
|
|
183
|
+
logger.verbose(`Final changelog for ${pkg.name}:
|
|
184
|
+
|
|
185
|
+
${changelog}
|
|
186
|
+
|
|
187
|
+
`);
|
|
188
|
+
if (dryRun) {
|
|
189
|
+
logger.info(`[dry-run] ${pkg.name} - Generate changelog ${fromTag}...${toTag}`);
|
|
190
|
+
}
|
|
191
|
+
return changelog;
|
|
192
|
+
} catch (error) {
|
|
193
|
+
throw new Error(`Error generating changelog for ${pkg.name} (${fromTag}...${toTag}): ${error}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function writeChangelogToFile({
|
|
197
|
+
pkg,
|
|
198
|
+
changelog,
|
|
199
|
+
dryRun = false
|
|
200
|
+
}) {
|
|
201
|
+
const changelogPath = join(pkg.path, "CHANGELOG.md");
|
|
202
|
+
let existingChangelog = "";
|
|
203
|
+
if (existsSync(changelogPath)) {
|
|
204
|
+
existingChangelog = readFileSync(changelogPath, "utf8");
|
|
205
|
+
}
|
|
206
|
+
const lines = existingChangelog.split("\n");
|
|
207
|
+
const titleIndex = lines.findIndex((line) => line.startsWith("# "));
|
|
208
|
+
let updatedChangelog;
|
|
209
|
+
if (titleIndex !== -1) {
|
|
210
|
+
const beforeTitle = lines.slice(0, titleIndex + 1);
|
|
211
|
+
const afterTitle = lines.slice(titleIndex + 1);
|
|
212
|
+
updatedChangelog = [...beforeTitle, "", changelog, "", ...afterTitle].join("\n");
|
|
213
|
+
} else {
|
|
214
|
+
const title = "# Changelog\n";
|
|
215
|
+
updatedChangelog = `${title}
|
|
216
|
+
${changelog}
|
|
217
|
+
${existingChangelog}`;
|
|
218
|
+
}
|
|
219
|
+
if (dryRun) {
|
|
220
|
+
logger.info(`[dry-run] ${pkg.name} - Write changelog to ${changelogPath}`);
|
|
221
|
+
} else {
|
|
222
|
+
logger.debug(`Writing changelog to ${changelogPath}`);
|
|
223
|
+
writeFileSync(changelogPath, updatedChangelog, "utf8");
|
|
224
|
+
logger.info(`Changelog updated for ${pkg.name}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
async function executeFormatCmd({
|
|
228
|
+
config,
|
|
229
|
+
dryRun
|
|
230
|
+
}) {
|
|
231
|
+
if (config.changelog?.formatCmd) {
|
|
232
|
+
logger.info("Running format command");
|
|
233
|
+
logger.debug(`Running format command: ${config.changelog.formatCmd}`);
|
|
234
|
+
try {
|
|
235
|
+
if (!dryRun) {
|
|
236
|
+
await execPromise(config.changelog.formatCmd, {
|
|
237
|
+
noStderr: true,
|
|
238
|
+
noStdout: true,
|
|
239
|
+
logLevel: config.logLevel,
|
|
240
|
+
cwd: config.cwd
|
|
241
|
+
});
|
|
242
|
+
logger.info("Format completed");
|
|
243
|
+
} else {
|
|
244
|
+
logger.log("[dry-run] exec format command: ", config.changelog.formatCmd);
|
|
245
|
+
}
|
|
246
|
+
} catch (error) {
|
|
247
|
+
logger.error("Format command failed:", error);
|
|
248
|
+
process.exit(1);
|
|
249
|
+
}
|
|
250
|
+
} else {
|
|
251
|
+
logger.debug("No format command specified");
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
async function executeBuildCmd({
|
|
255
|
+
config,
|
|
256
|
+
dryRun
|
|
257
|
+
}) {
|
|
258
|
+
if (config.publish?.buildCmd) {
|
|
259
|
+
logger.info("Running build command");
|
|
260
|
+
logger.debug(`Running build command: ${config.publish.buildCmd}`);
|
|
261
|
+
if (!dryRun) {
|
|
262
|
+
await execPromise(config.publish.buildCmd, {
|
|
263
|
+
noStderr: true,
|
|
264
|
+
noStdout: true,
|
|
265
|
+
logLevel: config.logLevel,
|
|
266
|
+
cwd: config.cwd
|
|
267
|
+
});
|
|
268
|
+
logger.info("Build completed");
|
|
269
|
+
} else {
|
|
270
|
+
logger.log("[dry-run] exec build command: ", config.publish.buildCmd);
|
|
271
|
+
}
|
|
272
|
+
} else {
|
|
273
|
+
logger.debug("No build command specified");
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function getDefaultConfig() {
|
|
278
|
+
return {
|
|
279
|
+
cwd: process$1.cwd(),
|
|
280
|
+
types: {
|
|
281
|
+
feat: { title: "\u{1F680} Enhancements", semver: "minor" },
|
|
282
|
+
perf: { title: "\u{1F525} Performance", semver: "patch" },
|
|
283
|
+
fix: { title: "\u{1FA79} Fixes", semver: "patch" },
|
|
284
|
+
refactor: { title: "\u{1F485} Refactors", semver: "patch" },
|
|
285
|
+
docs: { title: "\u{1F4D6} Documentation", semver: "patch" },
|
|
286
|
+
build: { title: "\u{1F4E6} Build", semver: "patch" },
|
|
287
|
+
types: { title: "\u{1F30A} Types", semver: "patch" },
|
|
288
|
+
chore: { title: "\u{1F3E1} Chore" },
|
|
289
|
+
examples: { title: "\u{1F3C0} Examples" },
|
|
290
|
+
test: { title: "\u2705 Tests" },
|
|
291
|
+
style: { title: "\u{1F3A8} Styles" },
|
|
292
|
+
ci: { title: "\u{1F916} CI" }
|
|
293
|
+
},
|
|
294
|
+
templates: {
|
|
295
|
+
commitMessage: "chore(release): bump version to {{newVersion}}",
|
|
296
|
+
tagMessage: "Bump version to v{{newVersion}}",
|
|
297
|
+
tagBody: "v{{newVersion}}",
|
|
298
|
+
emptyChangelogContent: "No relevant changes for this release"
|
|
299
|
+
},
|
|
300
|
+
excludeAuthors: [],
|
|
301
|
+
noAuthors: false,
|
|
302
|
+
bump: {
|
|
303
|
+
type: "release",
|
|
304
|
+
clean: true,
|
|
305
|
+
dependencyTypes: ["dependencies"],
|
|
306
|
+
yes: false
|
|
307
|
+
},
|
|
308
|
+
changelog: {
|
|
309
|
+
rootChangelog: true,
|
|
310
|
+
includeCommitBody: true
|
|
311
|
+
},
|
|
312
|
+
publish: {
|
|
313
|
+
private: false,
|
|
314
|
+
args: []
|
|
315
|
+
},
|
|
316
|
+
tokens: {
|
|
317
|
+
gitlab: process$1.env.RELIZY_TOKENS_GITLAB || process$1.env.GITLAB_TOKEN || process$1.env.GITLAB_API_TOKEN || process$1.env.CI_JOB_TOKEN,
|
|
318
|
+
github: process$1.env.RELIZY_TOKENS_GITHUB || process$1.env.GITHUB_TOKEN || process$1.env.GH_TOKEN
|
|
319
|
+
},
|
|
320
|
+
scopeMap: {},
|
|
321
|
+
release: {
|
|
322
|
+
commit: true,
|
|
323
|
+
publish: true,
|
|
324
|
+
changelog: true,
|
|
325
|
+
push: true,
|
|
326
|
+
clean: true,
|
|
327
|
+
release: true,
|
|
328
|
+
noVerify: false
|
|
329
|
+
},
|
|
330
|
+
logLevel: "default"
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
function setupLogger(logLevel) {
|
|
334
|
+
if (logLevel) {
|
|
335
|
+
logger.setLevel(logLevel);
|
|
336
|
+
logger.debug(`Log level set to: ${logLevel}`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
async function resolveConfig(config, cwd) {
|
|
340
|
+
if (!config.repo) {
|
|
341
|
+
const resolvedRepoConfig = await resolveRepoConfig(cwd);
|
|
342
|
+
config.repo = {
|
|
343
|
+
...resolvedRepoConfig,
|
|
344
|
+
provider: resolvedRepoConfig.provider
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
if (typeof config.repo === "string") {
|
|
348
|
+
const resolvedRepoConfig = getRepoConfig(config.repo);
|
|
349
|
+
config.repo = {
|
|
350
|
+
...resolvedRepoConfig,
|
|
351
|
+
provider: resolvedRepoConfig.provider
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
return config;
|
|
355
|
+
}
|
|
356
|
+
async function loadMonorepoConfig({ baseConfig, overrides, configName = "relizy" }) {
|
|
357
|
+
const cwd = overrides?.cwd ?? process$1.cwd();
|
|
358
|
+
configName ??= "relizy";
|
|
359
|
+
await setupDotenv({ cwd });
|
|
360
|
+
const defaultConfig = getDefaultConfig();
|
|
361
|
+
const overridesConfig = defu(overrides, baseConfig);
|
|
362
|
+
const results = await loadConfig({
|
|
363
|
+
cwd,
|
|
364
|
+
name: configName,
|
|
365
|
+
packageJson: true,
|
|
366
|
+
defaults: defaultConfig,
|
|
367
|
+
overrides: overridesConfig
|
|
368
|
+
});
|
|
369
|
+
if (!results._configFile) {
|
|
370
|
+
logger.error(`No config file found with name "${configName}"`);
|
|
371
|
+
process$1.exit(1);
|
|
372
|
+
}
|
|
373
|
+
setupLogger(overrides?.logLevel || results.config.logLevel);
|
|
374
|
+
logger.verbose("User config:", formatJson(results.config.changelog));
|
|
375
|
+
const resolvedConfig = await resolveConfig(results.config, cwd);
|
|
376
|
+
logger.debug("Resolved config:", formatJson(resolvedConfig));
|
|
377
|
+
return resolvedConfig;
|
|
378
|
+
}
|
|
379
|
+
function defineConfig(config) {
|
|
380
|
+
return config;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function getPackageDependencies(packagePath, allPackageNames, dependencyTypes) {
|
|
384
|
+
const packageJsonPath = join(packagePath, "package.json");
|
|
385
|
+
if (!existsSync(packageJsonPath)) {
|
|
386
|
+
return [];
|
|
387
|
+
}
|
|
388
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
389
|
+
const deps = [];
|
|
390
|
+
const allDeps = {
|
|
391
|
+
...dependencyTypes?.includes("dependencies") ? packageJson.dependencies : {},
|
|
392
|
+
...dependencyTypes?.includes("peerDependencies") ? packageJson.peerDependencies : {},
|
|
393
|
+
...dependencyTypes?.includes("devDependencies") ? packageJson.devDependencies : {}
|
|
394
|
+
};
|
|
395
|
+
for (const depName of Object.keys(allDeps)) {
|
|
396
|
+
if (allPackageNames.has(depName)) {
|
|
397
|
+
deps.push(depName);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return deps;
|
|
401
|
+
}
|
|
402
|
+
function getPackagesWithDependencies(packages, dependencyTypes) {
|
|
403
|
+
const allPackageNames = new Set(packages.map((p) => p.name));
|
|
404
|
+
return packages.map((pkg) => ({
|
|
405
|
+
...pkg,
|
|
406
|
+
dependencies: getPackageDependencies(pkg.path, allPackageNames, dependencyTypes)
|
|
407
|
+
}));
|
|
408
|
+
}
|
|
409
|
+
function getDependentsOf(packageName, allPackages) {
|
|
410
|
+
return allPackages.filter(
|
|
411
|
+
(pkg) => pkg.dependencies.includes(packageName)
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
function expandPackagesToBumpWithDependents({
|
|
415
|
+
packagesWithCommits,
|
|
416
|
+
allPackages,
|
|
417
|
+
dependencyTypes
|
|
418
|
+
}) {
|
|
419
|
+
const packagesWithDeps = getPackagesWithDependencies(allPackages, dependencyTypes);
|
|
420
|
+
const result = /* @__PURE__ */ new Map();
|
|
421
|
+
logger.debug(`Expanding packages to bump: ${packagesWithCommits.length} packages with commits, ${allPackages.length} total packages`);
|
|
422
|
+
for (const pkg of packagesWithCommits) {
|
|
423
|
+
result.set(pkg.name, {
|
|
424
|
+
...pkg,
|
|
425
|
+
reason: "commits",
|
|
426
|
+
commits: pkg.commits
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
const toProcess = [...packagesWithCommits.map((p) => p.name)];
|
|
430
|
+
const processed = /* @__PURE__ */ new Set();
|
|
431
|
+
while (toProcess.length > 0) {
|
|
432
|
+
const currentPkgName = toProcess.shift();
|
|
433
|
+
if (!currentPkgName || processed.has(currentPkgName)) {
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
processed.add(currentPkgName);
|
|
437
|
+
const dependents = getDependentsOf(currentPkgName, packagesWithDeps);
|
|
438
|
+
for (const dependent of dependents) {
|
|
439
|
+
if (!result.has(dependent.name)) {
|
|
440
|
+
const currentChain = result.get(currentPkgName)?.dependencyChain || [];
|
|
441
|
+
const chain = [...currentChain, currentPkgName];
|
|
442
|
+
const packageInfo = allPackages.find((p) => p.name === dependent.name);
|
|
443
|
+
if (packageInfo) {
|
|
444
|
+
result.set(dependent.name, {
|
|
445
|
+
...packageInfo,
|
|
446
|
+
reason: "dependency",
|
|
447
|
+
dependencyChain: chain,
|
|
448
|
+
commits: []
|
|
449
|
+
});
|
|
450
|
+
toProcess.push(dependent.name);
|
|
451
|
+
logger.debug(`${dependent.name} will be bumped (depends on ${chain.join(" \u2192 ")})`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return Array.from(result.values());
|
|
457
|
+
}
|
|
458
|
+
function topologicalSort(packages) {
|
|
459
|
+
const sorted = [];
|
|
460
|
+
const visited = /* @__PURE__ */ new Set();
|
|
461
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
462
|
+
const packageMap = /* @__PURE__ */ new Map();
|
|
463
|
+
for (const pkg of packages) {
|
|
464
|
+
packageMap.set(pkg.name, pkg);
|
|
465
|
+
}
|
|
466
|
+
function visit(pkgName) {
|
|
467
|
+
if (visited.has(pkgName))
|
|
468
|
+
return;
|
|
469
|
+
if (visiting.has(pkgName)) {
|
|
470
|
+
logger.warn(`Circular dependency detected involving ${pkgName}`);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
visiting.add(pkgName);
|
|
474
|
+
const pkg = packageMap.get(pkgName);
|
|
475
|
+
if (!pkg)
|
|
476
|
+
return;
|
|
477
|
+
for (const depName of pkg.dependencies) {
|
|
478
|
+
visit(depName);
|
|
479
|
+
}
|
|
480
|
+
visiting.delete(pkgName);
|
|
481
|
+
visited.add(pkgName);
|
|
482
|
+
sorted.push(pkg);
|
|
483
|
+
}
|
|
484
|
+
for (const pkg of packages) {
|
|
485
|
+
visit(pkg.name);
|
|
486
|
+
}
|
|
487
|
+
return sorted;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function getGitStatus(cwd) {
|
|
491
|
+
return execSync("git status --porcelain", {
|
|
492
|
+
cwd,
|
|
493
|
+
encoding: "utf8"
|
|
494
|
+
}).trim();
|
|
495
|
+
}
|
|
496
|
+
function checkGitStatusIfDirty() {
|
|
497
|
+
logger.debug("Checking git status");
|
|
498
|
+
const dirty = getGitStatus();
|
|
499
|
+
if (dirty) {
|
|
500
|
+
logger.debug("git status:", `
|
|
501
|
+
${dirty.trim().split("\n").map((line) => line.trim()).join("\n")}`);
|
|
502
|
+
throw new Error("Working directory is not clean");
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
async function fetchGitTags(cwd) {
|
|
506
|
+
logger.debug("Fetching git tags from remote");
|
|
507
|
+
try {
|
|
508
|
+
await execPromise("git fetch --tags", { cwd, noStderr: true, noStdout: true, noSuccess: true });
|
|
509
|
+
logger.debug("Git tags fetched successfully");
|
|
510
|
+
} catch (error) {
|
|
511
|
+
logger.warn("Failed to fetch some git tags from remote (tags might already exist locally)", error);
|
|
512
|
+
logger.info("Continuing with local tags");
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
function detectGitProvider(cwd = process.cwd()) {
|
|
516
|
+
try {
|
|
517
|
+
const remoteUrl = execSync("git remote get-url origin", {
|
|
518
|
+
cwd,
|
|
519
|
+
encoding: "utf8"
|
|
520
|
+
}).trim();
|
|
521
|
+
if (remoteUrl.includes("github.com")) {
|
|
522
|
+
return "github";
|
|
523
|
+
}
|
|
524
|
+
if (remoteUrl.includes("gitlab.com") || remoteUrl.includes("gitlab")) {
|
|
525
|
+
return "gitlab";
|
|
526
|
+
}
|
|
527
|
+
return null;
|
|
528
|
+
} catch {
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
function parseGitRemoteUrl(remoteUrl) {
|
|
533
|
+
const sshRegex = /git@[\w.-]+:([\w.-]+)\/([\w.-]+?)(?:\.git)?$/;
|
|
534
|
+
const httpsRegex = /https?:\/\/[\w.-]+\/([\w.-]+)\/([\w.-]+?)(?:\.git)?$/;
|
|
535
|
+
const sshMatch = remoteUrl.match(sshRegex);
|
|
536
|
+
if (sshMatch) {
|
|
537
|
+
return {
|
|
538
|
+
owner: sshMatch[1],
|
|
539
|
+
repo: sshMatch[2]
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
const httpsMatch = remoteUrl.match(httpsRegex);
|
|
543
|
+
if (httpsMatch) {
|
|
544
|
+
return {
|
|
545
|
+
owner: httpsMatch[1],
|
|
546
|
+
repo: httpsMatch[2]
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
551
|
+
async function createCommitAndTags({
|
|
552
|
+
config,
|
|
553
|
+
noVerify,
|
|
554
|
+
bumpedPackages,
|
|
555
|
+
newVersion,
|
|
556
|
+
dryRun,
|
|
557
|
+
logLevel
|
|
558
|
+
}) {
|
|
559
|
+
const filePatternsToAdd = [
|
|
560
|
+
"package.json",
|
|
561
|
+
"lerna.json",
|
|
562
|
+
"CHANGELOG.md",
|
|
563
|
+
"**/CHANGELOG.md",
|
|
564
|
+
"**/package.json"
|
|
565
|
+
];
|
|
566
|
+
logger.start("Start commit and tag");
|
|
567
|
+
logger.debug("Adding files to git staging area...");
|
|
568
|
+
for (const pattern of filePatternsToAdd) {
|
|
569
|
+
if (pattern === "lerna.json" && !hasLernaJson(config.cwd)) {
|
|
570
|
+
logger.verbose(`Skipping lerna.json as it doesn't exist`);
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
if ((pattern === "lerna.json" || pattern === "CHANGELOG.md") && !existsSync(join(config.cwd, pattern))) {
|
|
574
|
+
logger.verbose(`Skipping ${pattern} as it doesn't exist`);
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
if (dryRun) {
|
|
578
|
+
logger.info(`[dry-run] git add ${pattern}`);
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
try {
|
|
582
|
+
logger.debug(`git add ${pattern}`);
|
|
583
|
+
execSync(`git add ${pattern}`);
|
|
584
|
+
} catch {
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
const rootPackage = getRootPackage(config.cwd);
|
|
588
|
+
newVersion = newVersion || rootPackage.version;
|
|
589
|
+
const versionForMessage = config.monorepo?.versionMode === "independent" ? bumpedPackages?.map((pkg) => `${pkg.name}@${pkg.version}`).join(", ") || "unknown" : newVersion || "unknown";
|
|
590
|
+
const commitMessage = config.templates.commitMessage?.replaceAll("{{newVersion}}", versionForMessage) || `chore(release): bump version to ${versionForMessage}`;
|
|
591
|
+
const noVerifyFlag = noVerify ? "--no-verify " : "";
|
|
592
|
+
logger.debug(`No verify: ${noVerify}`);
|
|
593
|
+
if (dryRun) {
|
|
594
|
+
logger.info(`[dry-run] git commit ${noVerifyFlag}-m "${commitMessage}"`);
|
|
595
|
+
} else {
|
|
596
|
+
logger.debug(`Executing: git commit ${noVerifyFlag}-m "${commitMessage}"`);
|
|
597
|
+
await execPromise(`git commit ${noVerifyFlag}-m "${commitMessage}"`, {
|
|
598
|
+
logLevel,
|
|
599
|
+
noStderr: true,
|
|
600
|
+
noStdout: true,
|
|
601
|
+
cwd: config.cwd
|
|
602
|
+
});
|
|
603
|
+
logger.success(`Committed: ${commitMessage}${noVerify ? " (--no-verify)" : ""}`);
|
|
604
|
+
}
|
|
605
|
+
const signTags = config.signTags ? "-s" : "";
|
|
606
|
+
logger.debug(`Sign tags: ${config.signTags}`);
|
|
607
|
+
const createdTags = [];
|
|
608
|
+
if (config.monorepo?.versionMode === "independent" && bumpedPackages && bumpedPackages.length > 0) {
|
|
609
|
+
logger.debug(`Creating ${bumpedPackages.length} independent package tags`);
|
|
610
|
+
for (const pkg of bumpedPackages) {
|
|
611
|
+
const tagName = `${pkg.name}@${pkg.version}`;
|
|
612
|
+
const tagMessage = config.templates?.tagMessage?.replaceAll("{{newVersion}}", pkg.version || "") || tagName;
|
|
613
|
+
if (dryRun) {
|
|
614
|
+
logger.info(`[dry-run] git tag ${signTags} -a ${tagName} -m "${tagMessage}"`);
|
|
615
|
+
} else {
|
|
616
|
+
const cmd = `git tag ${signTags} -a ${tagName} -m "${tagMessage}"`;
|
|
617
|
+
logger.debug(`Executing: ${cmd}`);
|
|
618
|
+
try {
|
|
619
|
+
await execPromise(cmd, {
|
|
620
|
+
logLevel,
|
|
621
|
+
noStderr: true,
|
|
622
|
+
noStdout: true,
|
|
623
|
+
cwd: config.cwd
|
|
624
|
+
});
|
|
625
|
+
logger.debug(`Tag created: ${tagName}`);
|
|
626
|
+
} catch (error) {
|
|
627
|
+
logger.error(`Failed to create tag ${tagName}:`, error);
|
|
628
|
+
throw error;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
createdTags.push(tagName);
|
|
632
|
+
}
|
|
633
|
+
logger.success(`Created ${createdTags.length} tags for independent packages, ${createdTags.join(", ")}`);
|
|
634
|
+
} else {
|
|
635
|
+
const tagName = config.templates.tagBody?.replaceAll("{{newVersion}}", newVersion);
|
|
636
|
+
const tagMessage = config.templates?.tagMessage?.replaceAll("{{newVersion}}", newVersion) || tagName;
|
|
637
|
+
if (dryRun) {
|
|
638
|
+
logger.info(`[dry-run] git tag ${signTags} -a ${tagName} -m "${tagMessage}"`);
|
|
639
|
+
} else {
|
|
640
|
+
const cmd = `git tag ${signTags} -a ${tagName} -m "${tagMessage}"`;
|
|
641
|
+
logger.debug(`Executing: ${cmd}`);
|
|
642
|
+
try {
|
|
643
|
+
await execPromise(cmd, {
|
|
644
|
+
logLevel,
|
|
645
|
+
noStderr: true,
|
|
646
|
+
noStdout: true,
|
|
647
|
+
cwd: config.cwd
|
|
648
|
+
});
|
|
649
|
+
logger.debug(`Tag created: ${tagName}`);
|
|
650
|
+
} catch (error) {
|
|
651
|
+
logger.error(`Failed to create tag ${tagName}:`, error);
|
|
652
|
+
throw error;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
createdTags.push(tagName);
|
|
656
|
+
}
|
|
657
|
+
logger.debug("Created Tags:", createdTags.join(", "));
|
|
658
|
+
logger.success("Commit and tag completed!");
|
|
659
|
+
return createdTags;
|
|
660
|
+
}
|
|
661
|
+
async function pushCommitAndTags({ dryRun, logLevel, cwd }) {
|
|
662
|
+
logger.start("Start push changes and tags");
|
|
663
|
+
if (dryRun) {
|
|
664
|
+
logger.info("[dry-run] git push --follow-tags");
|
|
665
|
+
} else {
|
|
666
|
+
logger.debug("Executing: git push --follow-tags");
|
|
667
|
+
await execPromise("git push --follow-tags", { noStderr: true, noStdout: true, logLevel, cwd });
|
|
668
|
+
}
|
|
669
|
+
logger.success("End push changes and tags");
|
|
670
|
+
}
|
|
671
|
+
function getFirstCommit(cwd) {
|
|
672
|
+
const result = execSync(
|
|
673
|
+
"git rev-list --max-parents=0 HEAD",
|
|
674
|
+
{
|
|
675
|
+
cwd,
|
|
676
|
+
encoding: "utf8"
|
|
677
|
+
}
|
|
678
|
+
);
|
|
679
|
+
return result.trim();
|
|
680
|
+
}
|
|
681
|
+
function getCurrentGitBranch(cwd) {
|
|
682
|
+
const result = execSync("git rev-parse --abbrev-ref HEAD", {
|
|
683
|
+
cwd,
|
|
684
|
+
encoding: "utf8"
|
|
685
|
+
});
|
|
686
|
+
return result.trim();
|
|
687
|
+
}
|
|
688
|
+
function getCurrentGitRef(cwd) {
|
|
689
|
+
const branch = getCurrentGitBranch(cwd);
|
|
690
|
+
return branch || "HEAD";
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
async function githubIndependentMode({
|
|
694
|
+
config,
|
|
695
|
+
dryRun,
|
|
696
|
+
bumpedPackages
|
|
697
|
+
}) {
|
|
698
|
+
const repoConfig = config.repo;
|
|
699
|
+
if (!repoConfig) {
|
|
700
|
+
throw new Error("No repository configuration found. Please check your changelog config.");
|
|
701
|
+
}
|
|
702
|
+
logger.debug(`GitHub token: ${config.tokens.github ? "\u2713 provided" : "\u2717 missing"}`);
|
|
703
|
+
if (!config.tokens.github) {
|
|
704
|
+
throw new Error("No GitHub token specified. Set GITHUB_TOKEN or GH_TOKEN environment variable.");
|
|
705
|
+
}
|
|
706
|
+
const packages = bumpedPackages || getPackages({
|
|
707
|
+
cwd: config.cwd,
|
|
708
|
+
patterns: config.monorepo?.packages,
|
|
709
|
+
ignorePackageNames: config.monorepo?.ignorePackageNames
|
|
710
|
+
});
|
|
711
|
+
logger.info(`Creating ${packages.length} GitHub release(s)`);
|
|
712
|
+
const postedReleases = [];
|
|
713
|
+
for (const pkg of packages) {
|
|
714
|
+
const to = `${pkg.name}@${pkg.version}`;
|
|
715
|
+
const from = pkg.fromTag;
|
|
716
|
+
if (!from) {
|
|
717
|
+
logger.warn(`No fromTag found for ${pkg.name}, skipping release`);
|
|
718
|
+
continue;
|
|
719
|
+
}
|
|
720
|
+
const toTag = dryRun ? "HEAD" : to;
|
|
721
|
+
logger.debug(`Processing ${pkg.name}: ${from} \u2192 ${toTag}`);
|
|
722
|
+
const commits = await getPackageCommits({
|
|
723
|
+
pkg,
|
|
724
|
+
to: toTag,
|
|
725
|
+
from,
|
|
726
|
+
config,
|
|
727
|
+
changelog: true
|
|
728
|
+
});
|
|
729
|
+
const changelog = await generateChangelog({
|
|
730
|
+
pkg,
|
|
731
|
+
commits,
|
|
732
|
+
config,
|
|
733
|
+
from,
|
|
734
|
+
dryRun
|
|
735
|
+
});
|
|
736
|
+
const releaseBody = changelog.split("\n").slice(2).join("\n");
|
|
737
|
+
const release = {
|
|
738
|
+
tag_name: to,
|
|
739
|
+
name: to,
|
|
740
|
+
body: releaseBody,
|
|
741
|
+
prerelease: isPrerelease(pkg.version)
|
|
742
|
+
};
|
|
743
|
+
logger.debug(`Creating release for ${to}${release.prerelease ? " (prerelease)" : ""}`);
|
|
744
|
+
if (dryRun) {
|
|
745
|
+
logger.info(`[dry-run] Publish GitHub release for ${to}`);
|
|
746
|
+
postedReleases.push({
|
|
747
|
+
name: pkg.name,
|
|
748
|
+
tag: release.tag_name,
|
|
749
|
+
version: pkg.version,
|
|
750
|
+
prerelease: release.prerelease
|
|
751
|
+
});
|
|
752
|
+
} else {
|
|
753
|
+
logger.debug(`Publishing release ${to} to GitHub...`);
|
|
754
|
+
await createGithubRelease({
|
|
755
|
+
...config,
|
|
756
|
+
from,
|
|
757
|
+
to,
|
|
758
|
+
repo: repoConfig
|
|
759
|
+
}, release);
|
|
760
|
+
postedReleases.push({
|
|
761
|
+
name: pkg.name,
|
|
762
|
+
tag: release.tag_name,
|
|
763
|
+
version: pkg.version,
|
|
764
|
+
prerelease: release.prerelease
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
if (postedReleases.length === 0) {
|
|
769
|
+
logger.warn("No releases created");
|
|
770
|
+
} else {
|
|
771
|
+
logger.success(`Releases ${postedReleases.map((r) => r.tag).join(", ")} published to GitHub!`);
|
|
772
|
+
}
|
|
773
|
+
return postedReleases;
|
|
774
|
+
}
|
|
775
|
+
async function githubUnified({
|
|
776
|
+
config,
|
|
777
|
+
dryRun,
|
|
778
|
+
rootPackage,
|
|
779
|
+
fromTag,
|
|
780
|
+
oldVersion
|
|
781
|
+
}) {
|
|
782
|
+
const repoConfig = config.repo;
|
|
783
|
+
if (!repoConfig) {
|
|
784
|
+
throw new Error("No repository configuration found. Please check your changelog config.");
|
|
785
|
+
}
|
|
786
|
+
logger.debug(`GitHub token: ${config.tokens.github ? "\u2713 provided" : "\u2717 missing"}`);
|
|
787
|
+
if (!config.tokens.github) {
|
|
788
|
+
throw new Error("No GitHub token specified. Set GITHUB_TOKEN or GH_TOKEN environment variable.");
|
|
789
|
+
}
|
|
790
|
+
const to = config.templates.tagBody.replace("{{newVersion}}", rootPackage.version);
|
|
791
|
+
const toTag = dryRun ? "HEAD" : to;
|
|
792
|
+
const commits = await getPackageCommits({
|
|
793
|
+
pkg: rootPackage,
|
|
794
|
+
config,
|
|
795
|
+
from: fromTag || getFirstCommit(config.cwd),
|
|
796
|
+
to: toTag,
|
|
797
|
+
changelog: true
|
|
798
|
+
});
|
|
799
|
+
logger.debug(`Found ${commits.length} commit(s)`);
|
|
800
|
+
const changelog = await generateChangelog({
|
|
801
|
+
pkg: rootPackage,
|
|
802
|
+
commits,
|
|
803
|
+
config,
|
|
804
|
+
from: fromTag || oldVersion || "v0.0.0",
|
|
805
|
+
dryRun
|
|
806
|
+
});
|
|
807
|
+
const releaseBody = changelog.split("\n").slice(2).join("\n");
|
|
808
|
+
const release = {
|
|
809
|
+
tag_name: to,
|
|
810
|
+
name: to,
|
|
811
|
+
body: releaseBody,
|
|
812
|
+
prerelease: isPrerelease(to)
|
|
813
|
+
};
|
|
814
|
+
logger.debug(`Creating release for ${to}${release.prerelease ? " (prerelease)" : ""}`);
|
|
815
|
+
logger.debug("Release details:", formatJson({
|
|
816
|
+
tag_name: release.tag_name,
|
|
817
|
+
name: release.name,
|
|
818
|
+
prerelease: release.prerelease
|
|
819
|
+
}));
|
|
820
|
+
if (dryRun) {
|
|
821
|
+
logger.info("[dry-run] Publish GitHub release for", release.tag_name);
|
|
822
|
+
} else {
|
|
823
|
+
logger.debug("Publishing release to GitHub...");
|
|
824
|
+
await createGithubRelease({
|
|
825
|
+
...config,
|
|
826
|
+
from: fromTag || oldVersion || "v0.0.0",
|
|
827
|
+
to,
|
|
828
|
+
repo: repoConfig
|
|
829
|
+
}, release);
|
|
830
|
+
}
|
|
831
|
+
logger.success(`Release ${to} published to GitHub!`);
|
|
832
|
+
return [{
|
|
833
|
+
name: to,
|
|
834
|
+
tag: to,
|
|
835
|
+
version: to,
|
|
836
|
+
prerelease: release.prerelease
|
|
837
|
+
}];
|
|
838
|
+
}
|
|
839
|
+
async function github(options = {}) {
|
|
840
|
+
try {
|
|
841
|
+
logger.start("Start publishing GitHub release");
|
|
842
|
+
const dryRun = options.dryRun ?? false;
|
|
843
|
+
logger.debug(`Dry run: ${dryRun}`);
|
|
844
|
+
const config = await loadMonorepoConfig({
|
|
845
|
+
configName: options.configName,
|
|
846
|
+
baseConfig: options.config,
|
|
847
|
+
overrides: {
|
|
848
|
+
from: options.from,
|
|
849
|
+
to: options.to,
|
|
850
|
+
logLevel: options.logLevel,
|
|
851
|
+
tokens: {
|
|
852
|
+
github: options.token || process.env.CHANGELOGEN_TOKENS_GITHUB || process.env.GITHUB_TOKEN || process.env.GH_TOKEN
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
});
|
|
856
|
+
if (!options.bumpResult?.bumped) {
|
|
857
|
+
logger.warn("No bump result found, skipping release");
|
|
858
|
+
return [];
|
|
859
|
+
}
|
|
860
|
+
if (config.monorepo?.versionMode === "independent") {
|
|
861
|
+
return await githubIndependentMode({ config, dryRun, bumpedPackages: options.bumpResult.bumpedPackages });
|
|
862
|
+
}
|
|
863
|
+
const rootPackage = getRootPackage(config.cwd);
|
|
864
|
+
return await githubUnified({
|
|
865
|
+
config,
|
|
866
|
+
dryRun,
|
|
867
|
+
rootPackage,
|
|
868
|
+
fromTag: options.bumpResult.fromTag,
|
|
869
|
+
oldVersion: options.bumpResult.oldVersion
|
|
870
|
+
});
|
|
871
|
+
} catch (error) {
|
|
872
|
+
logger.error("Error publishing GitHub release:", error);
|
|
873
|
+
throw error;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
async function createGitlabRelease({
|
|
878
|
+
config,
|
|
879
|
+
release,
|
|
880
|
+
dryRun
|
|
881
|
+
}) {
|
|
882
|
+
const token = config.tokens.gitlab || process.env.GITLAB_TOKEN || process.env.CI_JOB_TOKEN;
|
|
883
|
+
if (!token && !dryRun) {
|
|
884
|
+
throw new Error(
|
|
885
|
+
"No GitLab token found. Set GITLAB_TOKEN or CI_JOB_TOKEN environment variable or configure tokens.gitlab"
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
const repoConfig = config.repo?.repo;
|
|
889
|
+
if (!repoConfig) {
|
|
890
|
+
throw new Error("No repository URL found in config");
|
|
891
|
+
}
|
|
892
|
+
logger.debug(`Parsed repository URL: ${repoConfig}`);
|
|
893
|
+
const projectPath = encodeURIComponent(repoConfig);
|
|
894
|
+
const gitlabDomain = config.repo?.domain || "gitlab.com";
|
|
895
|
+
const apiUrl = `https://${gitlabDomain}/api/v4/projects/${projectPath}/releases`;
|
|
896
|
+
logger.info(`Creating GitLab release at: ${apiUrl}`);
|
|
897
|
+
const payload = {
|
|
898
|
+
tag_name: release.tag_name,
|
|
899
|
+
name: release.name || release.tag_name,
|
|
900
|
+
description: release.description || "",
|
|
901
|
+
ref: release.ref || "main"
|
|
902
|
+
};
|
|
903
|
+
try {
|
|
904
|
+
if (dryRun) {
|
|
905
|
+
logger.info("[dry-run] GitLab release:", formatJson(payload));
|
|
906
|
+
return {
|
|
907
|
+
tag_name: release.tag_name,
|
|
908
|
+
name: release.name || release.tag_name,
|
|
909
|
+
description: release.description || "",
|
|
910
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
911
|
+
released_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
912
|
+
_links: {
|
|
913
|
+
self: `${apiUrl}/${encodeURIComponent(release.tag_name)}`
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
logger.debug(`POST GitLab release to ${apiUrl} with payload: ${formatJson(payload)}`);
|
|
918
|
+
const response = await fetch(apiUrl, {
|
|
919
|
+
method: "POST",
|
|
920
|
+
headers: {
|
|
921
|
+
"Content-Type": "application/json",
|
|
922
|
+
"PRIVATE-TOKEN": token || ""
|
|
923
|
+
},
|
|
924
|
+
body: JSON.stringify(payload)
|
|
925
|
+
});
|
|
926
|
+
if (!response.ok) {
|
|
927
|
+
const errorText = await response.text();
|
|
928
|
+
throw new Error(`GitLab API error (${response.status}): ${errorText}`);
|
|
929
|
+
}
|
|
930
|
+
const result = await response.json();
|
|
931
|
+
logger.debug(`Created GitLab release: ${result._links.self}`);
|
|
932
|
+
return result;
|
|
933
|
+
} catch (error) {
|
|
934
|
+
logger.error("Failed to create GitLab release:", error);
|
|
935
|
+
throw error;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
async function gitlabIndependentMode({
|
|
939
|
+
config,
|
|
940
|
+
dryRun,
|
|
941
|
+
bumpedPackages
|
|
942
|
+
}) {
|
|
943
|
+
logger.debug(`GitLab token: ${config.tokens.gitlab ? "\u2713 provided" : "\u2717 missing"}`);
|
|
944
|
+
const packages = bumpedPackages || getPackages({
|
|
945
|
+
cwd: config.cwd,
|
|
946
|
+
patterns: config.monorepo?.packages,
|
|
947
|
+
ignorePackageNames: config.monorepo?.ignorePackageNames
|
|
948
|
+
});
|
|
949
|
+
logger.info(`Creating ${packages.length} GitLab release(s) for independent packages`);
|
|
950
|
+
logger.debug("Getting current branch...");
|
|
951
|
+
const { stdout: currentBranch } = await execPromise("git rev-parse --abbrev-ref HEAD", {
|
|
952
|
+
noSuccess: true,
|
|
953
|
+
noStdout: true,
|
|
954
|
+
logLevel: config.logLevel,
|
|
955
|
+
cwd: config.cwd
|
|
956
|
+
});
|
|
957
|
+
const postedReleases = [];
|
|
958
|
+
for (const pkg of packages) {
|
|
959
|
+
const to = `${pkg.name}@${pkg.version}`;
|
|
960
|
+
const from = pkg.fromTag;
|
|
961
|
+
if (!from) {
|
|
962
|
+
logger.warn(`No fromTag found for ${pkg.name}, skipping release`);
|
|
963
|
+
continue;
|
|
964
|
+
}
|
|
965
|
+
logger.debug(`Processing ${pkg.name}: ${from} \u2192 ${to}`);
|
|
966
|
+
const commits = await getPackageCommits({
|
|
967
|
+
pkg,
|
|
968
|
+
config,
|
|
969
|
+
from,
|
|
970
|
+
to,
|
|
971
|
+
changelog: true
|
|
972
|
+
});
|
|
973
|
+
const changelog = await generateChangelog({
|
|
974
|
+
pkg,
|
|
975
|
+
commits,
|
|
976
|
+
config,
|
|
977
|
+
from,
|
|
978
|
+
dryRun
|
|
979
|
+
});
|
|
980
|
+
if (!changelog) {
|
|
981
|
+
logger.warn(`No changelog found for ${pkg.name}`);
|
|
982
|
+
continue;
|
|
983
|
+
}
|
|
984
|
+
const releaseBody = changelog.split("\n").slice(2).join("\n");
|
|
985
|
+
const release = {
|
|
986
|
+
tag_name: to,
|
|
987
|
+
name: to,
|
|
988
|
+
description: releaseBody,
|
|
989
|
+
ref: currentBranch.trim()
|
|
990
|
+
};
|
|
991
|
+
logger.debug(`Creating release for ${to} (ref: ${release.ref})`);
|
|
992
|
+
if (dryRun) {
|
|
993
|
+
logger.info(`[dry-run] Publish GitLab release for ${to}`);
|
|
994
|
+
} else {
|
|
995
|
+
logger.debug(`Publishing release ${to} to GitLab...`);
|
|
996
|
+
await createGitlabRelease({
|
|
997
|
+
config,
|
|
998
|
+
release,
|
|
999
|
+
dryRun
|
|
1000
|
+
});
|
|
1001
|
+
postedReleases.push({
|
|
1002
|
+
name: pkg.name,
|
|
1003
|
+
tag: release.tag_name,
|
|
1004
|
+
version: pkg.version,
|
|
1005
|
+
prerelease: isPrerelease(pkg.version)
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
if (postedReleases.length === 0) {
|
|
1010
|
+
logger.warn("No releases created");
|
|
1011
|
+
} else {
|
|
1012
|
+
logger.success(`Releases ${postedReleases.map((r) => r.tag).join(", ")} published to GitLab!`);
|
|
1013
|
+
}
|
|
1014
|
+
return postedReleases;
|
|
1015
|
+
}
|
|
1016
|
+
async function gitlabUnified({
|
|
1017
|
+
config,
|
|
1018
|
+
dryRun,
|
|
1019
|
+
rootPackage,
|
|
1020
|
+
fromTag,
|
|
1021
|
+
oldVersion
|
|
1022
|
+
}) {
|
|
1023
|
+
logger.debug(`GitLab token: ${config.tokens.gitlab ? "\u2713 provided" : "\u2717 missing"}`);
|
|
1024
|
+
const to = config.templates.tagBody.replace("{{newVersion}}", rootPackage.version);
|
|
1025
|
+
const commits = await getPackageCommits({
|
|
1026
|
+
pkg: rootPackage,
|
|
1027
|
+
config,
|
|
1028
|
+
from: fromTag || getFirstCommit(config.cwd),
|
|
1029
|
+
to,
|
|
1030
|
+
changelog: true
|
|
1031
|
+
});
|
|
1032
|
+
logger.debug(`Found ${commits.length} commit(s)`);
|
|
1033
|
+
const changelog = await generateChangelog({
|
|
1034
|
+
pkg: rootPackage,
|
|
1035
|
+
commits,
|
|
1036
|
+
config,
|
|
1037
|
+
from: fromTag || oldVersion || "v0.0.0",
|
|
1038
|
+
dryRun
|
|
1039
|
+
});
|
|
1040
|
+
const releaseBody = changelog.split("\n").slice(2).join("\n");
|
|
1041
|
+
logger.debug("Getting current branch...");
|
|
1042
|
+
const { stdout: currentBranch } = await execPromise("git rev-parse --abbrev-ref HEAD", {
|
|
1043
|
+
noSuccess: true,
|
|
1044
|
+
noStdout: true,
|
|
1045
|
+
logLevel: config.logLevel,
|
|
1046
|
+
cwd: config.cwd
|
|
1047
|
+
});
|
|
1048
|
+
const release = {
|
|
1049
|
+
tag_name: to,
|
|
1050
|
+
name: to,
|
|
1051
|
+
description: releaseBody,
|
|
1052
|
+
ref: currentBranch.trim()
|
|
1053
|
+
};
|
|
1054
|
+
logger.info(`Creating release for ${to} (ref: ${release.ref})`);
|
|
1055
|
+
logger.debug("Release details:", formatJson({
|
|
1056
|
+
tag_name: release.tag_name,
|
|
1057
|
+
name: release.name,
|
|
1058
|
+
ref: release.ref
|
|
1059
|
+
}));
|
|
1060
|
+
if (dryRun) {
|
|
1061
|
+
logger.info("[dry-run] Publish GitLab release for", release.tag_name);
|
|
1062
|
+
} else {
|
|
1063
|
+
logger.debug("Publishing release to GitLab...");
|
|
1064
|
+
await createGitlabRelease({
|
|
1065
|
+
config,
|
|
1066
|
+
release,
|
|
1067
|
+
dryRun
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1070
|
+
logger.success(`Release ${to} published to GitLab!`);
|
|
1071
|
+
return [{
|
|
1072
|
+
name: to,
|
|
1073
|
+
tag: to,
|
|
1074
|
+
version: to,
|
|
1075
|
+
prerelease: isPrerelease(rootPackage.version)
|
|
1076
|
+
}];
|
|
1077
|
+
}
|
|
1078
|
+
async function gitlab(options = {}) {
|
|
1079
|
+
try {
|
|
1080
|
+
logger.start("Start publishing GitLab release");
|
|
1081
|
+
const dryRun = options.dryRun ?? false;
|
|
1082
|
+
logger.debug(`Dry run: ${dryRun}`);
|
|
1083
|
+
const config = await loadMonorepoConfig({
|
|
1084
|
+
configName: options.configName,
|
|
1085
|
+
baseConfig: options.config,
|
|
1086
|
+
overrides: {
|
|
1087
|
+
from: options.from,
|
|
1088
|
+
to: options.to,
|
|
1089
|
+
logLevel: options.logLevel,
|
|
1090
|
+
tokens: {
|
|
1091
|
+
gitlab: options.token || process.env.CHANGELOGEN_TOKENS_GITLAB || process.env.GITLAB_TOKEN || process.env.GITLAB_API_TOKEN || process.env.CI_JOB_TOKEN
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
});
|
|
1095
|
+
if (!options.bumpResult?.bumped) {
|
|
1096
|
+
logger.warn("No bump result found, skipping release");
|
|
1097
|
+
return [];
|
|
1098
|
+
}
|
|
1099
|
+
if (config.monorepo?.versionMode === "independent") {
|
|
1100
|
+
return await gitlabIndependentMode({
|
|
1101
|
+
config,
|
|
1102
|
+
dryRun,
|
|
1103
|
+
bumpedPackages: options.bumpResult.bumpedPackages
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
1106
|
+
const rootPackage = getRootPackage(process.cwd());
|
|
1107
|
+
logger.debug(`Root package: ${rootPackage.name}@${rootPackage.version}`);
|
|
1108
|
+
return await gitlabUnified({
|
|
1109
|
+
config,
|
|
1110
|
+
dryRun,
|
|
1111
|
+
rootPackage,
|
|
1112
|
+
fromTag: options.bumpResult.fromTag,
|
|
1113
|
+
oldVersion: options.bumpResult.oldVersion
|
|
1114
|
+
});
|
|
1115
|
+
} catch (error) {
|
|
1116
|
+
logger.error("Error publishing GitLab release:", error);
|
|
1117
|
+
throw error;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
function getPackageInfo(packagePath, ignorePackageNames) {
|
|
1122
|
+
const packageJsonPath = join(packagePath, "package.json");
|
|
1123
|
+
if (!existsSync(packageJsonPath))
|
|
1124
|
+
return null;
|
|
1125
|
+
if (!statSync(packagePath).isDirectory())
|
|
1126
|
+
return null;
|
|
1127
|
+
try {
|
|
1128
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
1129
|
+
if (packageJson.private) {
|
|
1130
|
+
logger.debug(`${packageJson.name} is private and will be ignored`);
|
|
1131
|
+
return null;
|
|
1132
|
+
}
|
|
1133
|
+
if (ignorePackageNames?.includes(packageJson.name)) {
|
|
1134
|
+
logger.debug(`${packageJson.name} ignored by config monorepo.ignorePackageNames`);
|
|
1135
|
+
return null;
|
|
1136
|
+
}
|
|
1137
|
+
if (!packageJson.version) {
|
|
1138
|
+
logger.warn(`${packageJson.name} has no version and will be ignored`);
|
|
1139
|
+
return null;
|
|
1140
|
+
}
|
|
1141
|
+
return {
|
|
1142
|
+
name: packageJson.name,
|
|
1143
|
+
currentVersion: packageJson.version,
|
|
1144
|
+
path: packagePath,
|
|
1145
|
+
version: packageJson.version
|
|
1146
|
+
};
|
|
1147
|
+
} catch (error) {
|
|
1148
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1149
|
+
logger.warn(`Unable to read ${packageJsonPath}:`, errorMessage);
|
|
1150
|
+
return null;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
function getPackages({
|
|
1154
|
+
cwd,
|
|
1155
|
+
patterns,
|
|
1156
|
+
ignorePackageNames
|
|
1157
|
+
}) {
|
|
1158
|
+
const packages = [];
|
|
1159
|
+
const foundPaths = /* @__PURE__ */ new Set();
|
|
1160
|
+
if (!patterns)
|
|
1161
|
+
patterns = ["."];
|
|
1162
|
+
logger.debug(`Getting packages from patterns: ${patterns.join(", ")}`);
|
|
1163
|
+
for (const pattern of patterns) {
|
|
1164
|
+
try {
|
|
1165
|
+
const matches = fastGlob.sync(pattern, {
|
|
1166
|
+
cwd,
|
|
1167
|
+
onlyDirectories: true,
|
|
1168
|
+
absolute: true,
|
|
1169
|
+
ignore: ["**/node_modules/**", "**/dist/**", "**/.git/**"]
|
|
1170
|
+
});
|
|
1171
|
+
for (const matchPath of matches) {
|
|
1172
|
+
if (foundPaths.has(matchPath))
|
|
1173
|
+
continue;
|
|
1174
|
+
const packageInfo = getPackageInfo(matchPath, ignorePackageNames);
|
|
1175
|
+
if (packageInfo) {
|
|
1176
|
+
foundPaths.add(matchPath);
|
|
1177
|
+
packages.push(packageInfo);
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
} catch (error) {
|
|
1181
|
+
logger.error(`Unable to match pattern "${pattern}":`, error);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
return packages;
|
|
1185
|
+
}
|
|
1186
|
+
function isAllowedCommit({
|
|
1187
|
+
commit,
|
|
1188
|
+
type,
|
|
1189
|
+
changelog
|
|
1190
|
+
}) {
|
|
1191
|
+
if (commit.type === "chore" && ["deps", "release"].includes(commit.scope) && !commit.isBreaking) {
|
|
1192
|
+
return false;
|
|
1193
|
+
}
|
|
1194
|
+
if (typeof type === "object") {
|
|
1195
|
+
return !!type.semver || changelog && !!type.title;
|
|
1196
|
+
}
|
|
1197
|
+
if (typeof type === "boolean") {
|
|
1198
|
+
return type;
|
|
1199
|
+
}
|
|
1200
|
+
return false;
|
|
1201
|
+
}
|
|
1202
|
+
async function getPackageCommits({
|
|
1203
|
+
pkg,
|
|
1204
|
+
from,
|
|
1205
|
+
to,
|
|
1206
|
+
config,
|
|
1207
|
+
changelog
|
|
1208
|
+
}) {
|
|
1209
|
+
logger.debug(`Analyzing commits for ${pkg.name} since ${from} to ${to}`);
|
|
1210
|
+
const changelogConfig = {
|
|
1211
|
+
...config,
|
|
1212
|
+
from,
|
|
1213
|
+
to
|
|
1214
|
+
};
|
|
1215
|
+
const rawCommits = await getGitDiff(from, to, changelogConfig.cwd);
|
|
1216
|
+
const allCommits = parseCommits(rawCommits, changelogConfig);
|
|
1217
|
+
const hasBreakingChanges = allCommits.some((commit) => commit.isBreaking);
|
|
1218
|
+
logger.debug(`Has breaking changes: ${hasBreakingChanges}`);
|
|
1219
|
+
const rootPackage = getRootPackage(changelogConfig.cwd);
|
|
1220
|
+
const commits = allCommits.filter((commit) => {
|
|
1221
|
+
const type = changelogConfig?.types[commit.type];
|
|
1222
|
+
if (!isAllowedCommit({ commit, type, changelog })) {
|
|
1223
|
+
return false;
|
|
1224
|
+
}
|
|
1225
|
+
if (pkg.path === changelogConfig.cwd || pkg.name === rootPackage.name) {
|
|
1226
|
+
return true;
|
|
1227
|
+
}
|
|
1228
|
+
const packageRelativePath = pkg.path.replace(`${changelogConfig.cwd}/`, "");
|
|
1229
|
+
const scopeMatches = commit.scope === pkg.name;
|
|
1230
|
+
const bodyContainsPath = commit.body.includes(packageRelativePath);
|
|
1231
|
+
return scopeMatches || bodyContainsPath;
|
|
1232
|
+
});
|
|
1233
|
+
logger.debug(`Found ${commits.length} commit(s) for ${pkg.name} from ${from} to ${to}`);
|
|
1234
|
+
if (commits.length > 0) {
|
|
1235
|
+
logger.debug(`${pkg.name}: ${commits.length} commit(s) found`);
|
|
1236
|
+
} else {
|
|
1237
|
+
logger.debug(`${pkg.name}: No commits found`);
|
|
1238
|
+
}
|
|
1239
|
+
return commits;
|
|
1240
|
+
}
|
|
1241
|
+
function getRootPackage(rootDir) {
|
|
1242
|
+
const packageJsonPath = join(rootDir, "package.json");
|
|
1243
|
+
if (!existsSync(packageJsonPath)) {
|
|
1244
|
+
throw new Error(`package.json not found at ${packageJsonPath}`);
|
|
1245
|
+
}
|
|
1246
|
+
try {
|
|
1247
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
1248
|
+
return {
|
|
1249
|
+
name: packageJson.name,
|
|
1250
|
+
currentVersion: packageJson.version || "0.0.0",
|
|
1251
|
+
path: rootDir,
|
|
1252
|
+
version: packageJson.version || "0.0.0"
|
|
1253
|
+
};
|
|
1254
|
+
} catch (error) {
|
|
1255
|
+
throw new Error(`Unable to read ${packageJsonPath}: ${error}`);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
function hasLernaJson(rootDir) {
|
|
1259
|
+
const lernaJsonPath = join(rootDir, "lerna.json");
|
|
1260
|
+
return existsSync(lernaJsonPath);
|
|
1261
|
+
}
|
|
1262
|
+
async function getPackageToBump({
|
|
1263
|
+
packages,
|
|
1264
|
+
config,
|
|
1265
|
+
from,
|
|
1266
|
+
to
|
|
1267
|
+
}) {
|
|
1268
|
+
const packagesWithCommits = [];
|
|
1269
|
+
for (const pkg of packages) {
|
|
1270
|
+
const commits = await getPackageCommits({
|
|
1271
|
+
pkg,
|
|
1272
|
+
from,
|
|
1273
|
+
to,
|
|
1274
|
+
config,
|
|
1275
|
+
changelog: false
|
|
1276
|
+
});
|
|
1277
|
+
const graduating = isGraduating(pkg.version, config.bump.type) || isChangedPreid(pkg.version, config.bump.preid);
|
|
1278
|
+
if (commits.length > 0 || graduating) {
|
|
1279
|
+
packagesWithCommits.push({ ...pkg, commits });
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
const allPackagesToBump = expandPackagesToBumpWithDependents({
|
|
1283
|
+
allPackages: packages,
|
|
1284
|
+
packagesWithCommits,
|
|
1285
|
+
dependencyTypes: config.bump?.dependencyTypes
|
|
1286
|
+
});
|
|
1287
|
+
return allPackagesToBump;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
function detectReleaseTypeFromCommits(commits, config) {
|
|
1291
|
+
return determineSemverChange(commits, config);
|
|
1292
|
+
}
|
|
1293
|
+
function validatePrereleaseDowngrade(currentVersion, targetPreid, configuredType) {
|
|
1294
|
+
if (configuredType !== "prerelease" || !targetPreid || !isPrerelease(currentVersion)) {
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
1297
|
+
const testVersion = semver.inc(currentVersion, "prerelease", targetPreid);
|
|
1298
|
+
if (testVersion && !semver.gt(testVersion, currentVersion)) {
|
|
1299
|
+
throw new Error(`Unable to graduate from ${currentVersion} to ${testVersion}, it's not a valid prerelease`);
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
function handleStableVersionWithReleaseType(commits, config, force) {
|
|
1303
|
+
if (!commits?.length && !force) {
|
|
1304
|
+
logger.debug('No commits found for stable version with "release" type, skipping bump');
|
|
1305
|
+
return null;
|
|
1306
|
+
}
|
|
1307
|
+
const detectedType = commits?.length ? detectReleaseTypeFromCommits(commits, config) : null;
|
|
1308
|
+
if (!detectedType && !force) {
|
|
1309
|
+
logger.debug("No significant commits found, skipping bump");
|
|
1310
|
+
return null;
|
|
1311
|
+
}
|
|
1312
|
+
logger.debug(`Auto-detected release type from commits: ${detectedType}`);
|
|
1313
|
+
return detectedType;
|
|
1314
|
+
}
|
|
1315
|
+
function handleStableVersionWithPrereleaseType(commits, config, force) {
|
|
1316
|
+
if (!commits?.length && !force) {
|
|
1317
|
+
logger.debug('No commits found for stable version with "prerelease" type, skipping bump');
|
|
1318
|
+
return null;
|
|
1319
|
+
}
|
|
1320
|
+
const detectedType = commits?.length ? detectReleaseTypeFromCommits(commits, config) : null;
|
|
1321
|
+
if (!detectedType) {
|
|
1322
|
+
logger.debug("No significant commits found, using prepatch as default");
|
|
1323
|
+
return "prepatch";
|
|
1324
|
+
}
|
|
1325
|
+
const prereleaseType = `pre${detectedType}`;
|
|
1326
|
+
logger.debug(`Auto-detected prerelease type from commits: ${prereleaseType}`);
|
|
1327
|
+
return prereleaseType;
|
|
1328
|
+
}
|
|
1329
|
+
function handlePrereleaseVersionToStable(currentVersion) {
|
|
1330
|
+
logger.debug(`Graduating from prerelease ${currentVersion} to stable release`);
|
|
1331
|
+
return "release";
|
|
1332
|
+
}
|
|
1333
|
+
function handlePrereleaseVersionWithPrereleaseType(currentVersion, targetPreid, commits, config, force) {
|
|
1334
|
+
const currentPreid = getPreid(currentVersion);
|
|
1335
|
+
const hasChangedPreid = targetPreid && currentPreid && currentPreid !== targetPreid;
|
|
1336
|
+
if (hasChangedPreid) {
|
|
1337
|
+
const testVersion = semver.inc(currentVersion, "prerelease", targetPreid);
|
|
1338
|
+
if (!testVersion) {
|
|
1339
|
+
throw new Error(`Unable to change preid from ${currentPreid} to ${targetPreid} for version ${currentVersion}`);
|
|
1340
|
+
}
|
|
1341
|
+
const isUpgrade = semver.gt(testVersion, currentVersion);
|
|
1342
|
+
if (!isUpgrade) {
|
|
1343
|
+
throw new Error(`Unable to change preid from ${currentVersion} to ${testVersion}, it's not a valid upgrade (cannot downgrade from ${currentPreid} to ${targetPreid})`);
|
|
1344
|
+
}
|
|
1345
|
+
const detectedType = commits?.length ? detectReleaseTypeFromCommits(commits, config) : null;
|
|
1346
|
+
const prereleaseType = detectedType ? `pre${detectedType}` : "prepatch";
|
|
1347
|
+
logger.debug(`Changing preid from ${currentPreid} to ${targetPreid} with release type: ${prereleaseType}`);
|
|
1348
|
+
return prereleaseType;
|
|
1349
|
+
}
|
|
1350
|
+
if (!commits?.length && !force) {
|
|
1351
|
+
logger.debug("No commits found for prerelease version, skipping bump");
|
|
1352
|
+
return null;
|
|
1353
|
+
}
|
|
1354
|
+
logger.debug(`Incrementing prerelease version: ${currentVersion}`);
|
|
1355
|
+
return "prerelease";
|
|
1356
|
+
}
|
|
1357
|
+
function handleExplicitReleaseType(configuredType, currentVersion) {
|
|
1358
|
+
const isCurrentPrerelease = isPrerelease(currentVersion);
|
|
1359
|
+
const isGraduatingToStable = isCurrentPrerelease && isStableReleaseType(configuredType);
|
|
1360
|
+
if (isGraduatingToStable) {
|
|
1361
|
+
logger.debug(`Graduating from prerelease ${currentVersion} to stable with type: ${configuredType}`);
|
|
1362
|
+
} else {
|
|
1363
|
+
logger.debug(`Using explicit release type: ${configuredType}`);
|
|
1364
|
+
}
|
|
1365
|
+
return configuredType;
|
|
1366
|
+
}
|
|
1367
|
+
function determineReleaseType({
|
|
1368
|
+
currentVersion,
|
|
1369
|
+
from,
|
|
1370
|
+
to,
|
|
1371
|
+
commits,
|
|
1372
|
+
config,
|
|
1373
|
+
force
|
|
1374
|
+
}) {
|
|
1375
|
+
const configWithRange = {
|
|
1376
|
+
...config,
|
|
1377
|
+
from,
|
|
1378
|
+
to
|
|
1379
|
+
};
|
|
1380
|
+
const configuredType = configWithRange.bump.type;
|
|
1381
|
+
const targetPreid = configWithRange.bump.preid;
|
|
1382
|
+
if (configuredType === "release" && targetPreid) {
|
|
1383
|
+
throw new Error('You cannot use a "release" type with a "preid", to use a preid you must use a "prerelease" type');
|
|
1384
|
+
}
|
|
1385
|
+
validatePrereleaseDowngrade(currentVersion, targetPreid, configuredType);
|
|
1386
|
+
if (force) {
|
|
1387
|
+
logger.debug(`Force flag enabled, using configured type: ${configuredType}`);
|
|
1388
|
+
return configuredType;
|
|
1389
|
+
}
|
|
1390
|
+
const isCurrentPrerelease = isPrerelease(currentVersion);
|
|
1391
|
+
if (!isCurrentPrerelease) {
|
|
1392
|
+
if (configuredType === "release") {
|
|
1393
|
+
return handleStableVersionWithReleaseType(commits, configWithRange, force);
|
|
1394
|
+
}
|
|
1395
|
+
if (configuredType === "prerelease") {
|
|
1396
|
+
return handleStableVersionWithPrereleaseType(commits, configWithRange, force);
|
|
1397
|
+
}
|
|
1398
|
+
return handleExplicitReleaseType(configuredType, currentVersion);
|
|
1399
|
+
}
|
|
1400
|
+
if (configuredType === "release") {
|
|
1401
|
+
return handlePrereleaseVersionToStable(currentVersion);
|
|
1402
|
+
}
|
|
1403
|
+
if (configuredType === "prerelease") {
|
|
1404
|
+
return handlePrereleaseVersionWithPrereleaseType(
|
|
1405
|
+
currentVersion,
|
|
1406
|
+
targetPreid,
|
|
1407
|
+
commits,
|
|
1408
|
+
configWithRange,
|
|
1409
|
+
force
|
|
1410
|
+
);
|
|
1411
|
+
}
|
|
1412
|
+
return handleExplicitReleaseType(configuredType, currentVersion);
|
|
1413
|
+
}
|
|
1414
|
+
function writeVersion(pkgPath, version, dryRun = false) {
|
|
1415
|
+
const packageJsonPath = join(pkgPath, "package.json");
|
|
1416
|
+
try {
|
|
1417
|
+
logger.debug(`Writing ${version} to ${pkgPath}`);
|
|
1418
|
+
const content = readFileSync(packageJsonPath, "utf8");
|
|
1419
|
+
const packageJson = JSON.parse(content);
|
|
1420
|
+
const oldVersion = packageJson.version;
|
|
1421
|
+
packageJson.version = version;
|
|
1422
|
+
if (dryRun) {
|
|
1423
|
+
logger.info(`[dry-run] Updated ${packageJson.name}: ${oldVersion} \u2192 ${version}`);
|
|
1424
|
+
return;
|
|
1425
|
+
}
|
|
1426
|
+
writeFileSync(packageJsonPath, `${formatJson(packageJson)}
|
|
1427
|
+
`, "utf8");
|
|
1428
|
+
logger.info(`Updated ${packageJson.name}: ${oldVersion} \u2192 ${version}`);
|
|
1429
|
+
} catch (error) {
|
|
1430
|
+
throw new Error(`Unable to write version to ${packageJsonPath}: ${error}`);
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
function bumpPackageVersion({
|
|
1434
|
+
currentVersion,
|
|
1435
|
+
releaseType,
|
|
1436
|
+
preid,
|
|
1437
|
+
suffix
|
|
1438
|
+
}) {
|
|
1439
|
+
let newVersion = semver.inc(currentVersion, releaseType, preid);
|
|
1440
|
+
if (!newVersion) {
|
|
1441
|
+
throw new Error(`Unable to bump version "${currentVersion}" with release type "${releaseType}"`);
|
|
1442
|
+
}
|
|
1443
|
+
if (isPrereleaseReleaseType(releaseType) && suffix) {
|
|
1444
|
+
newVersion = newVersion.replace(/\.(\d+)$/, `.${suffix}`);
|
|
1445
|
+
}
|
|
1446
|
+
const isValidVersion = semver.gt(newVersion, currentVersion);
|
|
1447
|
+
if (!isValidVersion) {
|
|
1448
|
+
throw new Error(`Unable to bump version "${currentVersion}" to "${newVersion}", new version is not greater than current version`);
|
|
1449
|
+
}
|
|
1450
|
+
if (isGraduating(currentVersion, releaseType)) {
|
|
1451
|
+
logger.info(`Graduating from prerelease ${currentVersion} to stable ${newVersion}`);
|
|
1452
|
+
}
|
|
1453
|
+
return newVersion;
|
|
1454
|
+
}
|
|
1455
|
+
function updateLernaVersion({
|
|
1456
|
+
rootDir,
|
|
1457
|
+
versionMode,
|
|
1458
|
+
version,
|
|
1459
|
+
dryRun = false
|
|
1460
|
+
}) {
|
|
1461
|
+
const lernaJsonExists = hasLernaJson(rootDir);
|
|
1462
|
+
if (!lernaJsonExists) {
|
|
1463
|
+
return;
|
|
1464
|
+
}
|
|
1465
|
+
const lernaJsonPath = join(rootDir, "lerna.json");
|
|
1466
|
+
if (!existsSync(lernaJsonPath)) {
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
try {
|
|
1470
|
+
logger.debug("Updating lerna.json version");
|
|
1471
|
+
const content = readFileSync(lernaJsonPath, "utf8");
|
|
1472
|
+
const lernaJson = JSON.parse(content);
|
|
1473
|
+
const oldVersion = lernaJson.version;
|
|
1474
|
+
if (lernaJson.version === "independent" || versionMode === "independent") {
|
|
1475
|
+
logger.debug("Lerna version is independent or version mode is independent, skipping update");
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1478
|
+
lernaJson.version = version;
|
|
1479
|
+
if (dryRun) {
|
|
1480
|
+
logger.info(`[dry-run] update lerna.json: ${oldVersion} \u2192 ${version}`);
|
|
1481
|
+
return;
|
|
1482
|
+
}
|
|
1483
|
+
writeFileSync(lernaJsonPath, `${formatJson(lernaJson)}
|
|
1484
|
+
`, "utf8");
|
|
1485
|
+
logger.success(`Updated lerna.json: ${oldVersion} \u2192 ${version}`);
|
|
1486
|
+
} catch (error) {
|
|
1487
|
+
logger.warn(`Unable to update lerna.json: ${error}`);
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
function extractVersionFromPackageTag(tag) {
|
|
1491
|
+
const atIndex = tag.lastIndexOf("@");
|
|
1492
|
+
if (atIndex === -1) {
|
|
1493
|
+
return null;
|
|
1494
|
+
}
|
|
1495
|
+
return tag.slice(atIndex + 1);
|
|
1496
|
+
}
|
|
1497
|
+
function isPrerelease(version) {
|
|
1498
|
+
if (!version)
|
|
1499
|
+
return false;
|
|
1500
|
+
const prerelease = semver.prerelease(version);
|
|
1501
|
+
return prerelease ? prerelease.length > 0 : false;
|
|
1502
|
+
}
|
|
1503
|
+
function isStableReleaseType(releaseType) {
|
|
1504
|
+
const stableTypes = ["release", "major", "minor", "patch"];
|
|
1505
|
+
return stableTypes.includes(releaseType);
|
|
1506
|
+
}
|
|
1507
|
+
function isPrereleaseReleaseType(releaseType) {
|
|
1508
|
+
const prereleaseTypes = ["prerelease", "premajor", "preminor", "prepatch"];
|
|
1509
|
+
return prereleaseTypes.includes(releaseType);
|
|
1510
|
+
}
|
|
1511
|
+
function isGraduating(currentVersion, releaseType) {
|
|
1512
|
+
return isPrerelease(currentVersion) && isStableReleaseType(releaseType);
|
|
1513
|
+
}
|
|
1514
|
+
function getPreid(version) {
|
|
1515
|
+
if (!version)
|
|
1516
|
+
return null;
|
|
1517
|
+
const prerelease = semver.prerelease(version);
|
|
1518
|
+
if (!prerelease || prerelease.length === 0) {
|
|
1519
|
+
return null;
|
|
1520
|
+
}
|
|
1521
|
+
return prerelease[0];
|
|
1522
|
+
}
|
|
1523
|
+
function isChangedPreid(currentVersion, targetPreid) {
|
|
1524
|
+
if (!targetPreid || !isPrerelease(currentVersion)) {
|
|
1525
|
+
return false;
|
|
1526
|
+
}
|
|
1527
|
+
const currentPreid = getPreid(currentVersion);
|
|
1528
|
+
if (!currentPreid) {
|
|
1529
|
+
return false;
|
|
1530
|
+
}
|
|
1531
|
+
return currentPreid !== targetPreid;
|
|
1532
|
+
}
|
|
1533
|
+
function bumpPackageIndependently({
|
|
1534
|
+
pkg,
|
|
1535
|
+
dryRun
|
|
1536
|
+
}) {
|
|
1537
|
+
logger.debug(`Analyzing ${pkg.name}`);
|
|
1538
|
+
const currentVersion = pkg.version || "0.0.0";
|
|
1539
|
+
const newVersion = pkg.version;
|
|
1540
|
+
logger.debug(`Bumping ${pkg.name} from ${currentVersion} to ${newVersion}`);
|
|
1541
|
+
writeVersion(pkg.path, newVersion, dryRun);
|
|
1542
|
+
return { bumped: true, newVersion, oldVersion: currentVersion };
|
|
1543
|
+
}
|
|
1544
|
+
function displayRootAndLernaUpdates({
|
|
1545
|
+
versionMode,
|
|
1546
|
+
currentVersion,
|
|
1547
|
+
newVersion,
|
|
1548
|
+
dryRun,
|
|
1549
|
+
lernaJsonExists
|
|
1550
|
+
}) {
|
|
1551
|
+
if (versionMode !== "independent" && currentVersion && newVersion) {
|
|
1552
|
+
logger.log(`${dryRun ? "[dry-run] " : ""}Root package.json: ${currentVersion} \u2192 ${newVersion}`);
|
|
1553
|
+
logger.log("");
|
|
1554
|
+
if (lernaJsonExists) {
|
|
1555
|
+
logger.log(`${dryRun ? "[dry-run] " : ""}lerna.json: ${currentVersion} \u2192 ${newVersion}`);
|
|
1556
|
+
logger.log("");
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
function displayUnifiedModePackages({
|
|
1561
|
+
packages,
|
|
1562
|
+
newVersion,
|
|
1563
|
+
force
|
|
1564
|
+
}) {
|
|
1565
|
+
logger.log(`${packages.length} package(s)${force ? " (force)" : ""}:`);
|
|
1566
|
+
packages.forEach((pkg) => {
|
|
1567
|
+
logger.log(` \u2022 ${pkg.name}: ${pkg.version} \u2192 ${newVersion}`);
|
|
1568
|
+
});
|
|
1569
|
+
logger.log("");
|
|
1570
|
+
}
|
|
1571
|
+
function displaySelectiveModePackages({
|
|
1572
|
+
packages,
|
|
1573
|
+
newVersion,
|
|
1574
|
+
force
|
|
1575
|
+
}) {
|
|
1576
|
+
if (force) {
|
|
1577
|
+
logger.log(`${packages.length} package(s) (force):`);
|
|
1578
|
+
packages.forEach((pkg) => {
|
|
1579
|
+
logger.log(` \u2022 ${pkg.name}: ${pkg.version} \u2192 ${newVersion}`);
|
|
1580
|
+
});
|
|
1581
|
+
logger.log("");
|
|
1582
|
+
} else {
|
|
1583
|
+
const packagesWithCommits = packages.filter((p) => "reason" in p && p.reason === "commits");
|
|
1584
|
+
const packagesAsDependents = packages.filter((p) => "reason" in p && p.reason === "dependency");
|
|
1585
|
+
if (packagesWithCommits.length > 0) {
|
|
1586
|
+
logger.log(`${packagesWithCommits.length} package(s) with commits:`);
|
|
1587
|
+
packagesWithCommits.forEach((pkg) => {
|
|
1588
|
+
logger.log(` \u2022 ${pkg.name}: ${pkg.version} \u2192 ${newVersion}`);
|
|
1589
|
+
});
|
|
1590
|
+
logger.log("");
|
|
1591
|
+
}
|
|
1592
|
+
if (packagesAsDependents.length > 0) {
|
|
1593
|
+
logger.log(`${packagesAsDependents.length} dependent package(s):`);
|
|
1594
|
+
packagesAsDependents.forEach((pkg) => {
|
|
1595
|
+
logger.log(` \u2022 ${pkg.name}: ${pkg.version} \u2192 ${newVersion}`);
|
|
1596
|
+
});
|
|
1597
|
+
logger.log("");
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
function displayIndependentModePackages({
|
|
1602
|
+
packages,
|
|
1603
|
+
force,
|
|
1604
|
+
dryRun
|
|
1605
|
+
}) {
|
|
1606
|
+
if (force) {
|
|
1607
|
+
logger.log(`${dryRun ? "[dry-run] " : ""}${packages.length} package(s) (force):`);
|
|
1608
|
+
packages.forEach((pkg) => {
|
|
1609
|
+
logger.log(` \u2022 ${pkg.name}: ${pkg.currentVersion} \u2192 ${pkg.version}`);
|
|
1610
|
+
});
|
|
1611
|
+
logger.log("");
|
|
1612
|
+
} else {
|
|
1613
|
+
const packagesWithCommits = packages.filter((p) => "reason" in p && p.reason === "commits");
|
|
1614
|
+
const packagesAsDependents = packages.filter((p) => "reason" in p && p.reason === "dependency");
|
|
1615
|
+
if (packagesWithCommits.length > 0) {
|
|
1616
|
+
logger.log(`${dryRun ? "[dry-run] " : ""}${packagesWithCommits.length} package(s) with commits:`);
|
|
1617
|
+
packagesWithCommits.forEach((pkg) => {
|
|
1618
|
+
logger.log(` \u2022 ${pkg.name}: ${pkg.currentVersion} \u2192 ${pkg.version}`);
|
|
1619
|
+
});
|
|
1620
|
+
logger.log("");
|
|
1621
|
+
}
|
|
1622
|
+
if (packagesAsDependents.length > 0) {
|
|
1623
|
+
logger.log(`${dryRun ? "[dry-run] " : ""}${packagesAsDependents.length} dependent package(s):`);
|
|
1624
|
+
packagesAsDependents.forEach((pkg) => {
|
|
1625
|
+
logger.log(` \u2022 ${pkg.name}: ${pkg.currentVersion} \u2192 ${pkg.version}`);
|
|
1626
|
+
});
|
|
1627
|
+
logger.log("");
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
async function confirmBump({
|
|
1632
|
+
versionMode,
|
|
1633
|
+
config,
|
|
1634
|
+
packages,
|
|
1635
|
+
force,
|
|
1636
|
+
currentVersion,
|
|
1637
|
+
newVersion,
|
|
1638
|
+
dryRun
|
|
1639
|
+
}) {
|
|
1640
|
+
const lernaJsonExists = hasLernaJson(config.cwd);
|
|
1641
|
+
logger.log("");
|
|
1642
|
+
logger.info(`${dryRun ? "[dry-run] " : ""}The following packages will be updated:
|
|
1643
|
+
`);
|
|
1644
|
+
displayRootAndLernaUpdates({
|
|
1645
|
+
versionMode,
|
|
1646
|
+
currentVersion,
|
|
1647
|
+
newVersion,
|
|
1648
|
+
lernaJsonExists,
|
|
1649
|
+
dryRun
|
|
1650
|
+
});
|
|
1651
|
+
if (versionMode === "unified" && newVersion) {
|
|
1652
|
+
displayUnifiedModePackages({ packages, newVersion, force });
|
|
1653
|
+
} else if (versionMode === "selective" && newVersion) {
|
|
1654
|
+
displaySelectiveModePackages({ packages, newVersion, force });
|
|
1655
|
+
} else if (versionMode === "independent") {
|
|
1656
|
+
displayIndependentModePackages({ packages, force, dryRun });
|
|
1657
|
+
}
|
|
1658
|
+
try {
|
|
1659
|
+
const confirmed = await confirm({
|
|
1660
|
+
message: `${dryRun ? "[dry-run] " : ""}Do you want to proceed with these version updates?`,
|
|
1661
|
+
default: true
|
|
1662
|
+
});
|
|
1663
|
+
if (!confirmed) {
|
|
1664
|
+
logger.warn("Bump cancelled by user");
|
|
1665
|
+
process.exit(0);
|
|
1666
|
+
}
|
|
1667
|
+
} catch {
|
|
1668
|
+
logger.error("Error while confirming bump");
|
|
1669
|
+
process.exit(1);
|
|
1670
|
+
}
|
|
1671
|
+
logger.log("");
|
|
1672
|
+
}
|
|
1673
|
+
async function findPackagesWithCommits({
|
|
1674
|
+
packages,
|
|
1675
|
+
config,
|
|
1676
|
+
force
|
|
1677
|
+
}) {
|
|
1678
|
+
const packagesWithCommits = [];
|
|
1679
|
+
logger.debug(`Checking for commits in ${packages.length} package(s)`);
|
|
1680
|
+
for (const pkg of packages) {
|
|
1681
|
+
const { from, to } = await resolveTags({
|
|
1682
|
+
config,
|
|
1683
|
+
versionMode: "independent",
|
|
1684
|
+
step: "bump",
|
|
1685
|
+
currentVersion: pkg.version,
|
|
1686
|
+
newVersion: void 0,
|
|
1687
|
+
pkg,
|
|
1688
|
+
logLevel: config.logLevel
|
|
1689
|
+
});
|
|
1690
|
+
const commits = await getPackageCommits({
|
|
1691
|
+
pkg,
|
|
1692
|
+
from,
|
|
1693
|
+
to,
|
|
1694
|
+
config,
|
|
1695
|
+
changelog: false
|
|
1696
|
+
});
|
|
1697
|
+
if (commits.length <= 0 && !force) {
|
|
1698
|
+
logger.debug(`${pkg.name}: No commits found, skipping`);
|
|
1699
|
+
continue;
|
|
1700
|
+
}
|
|
1701
|
+
packagesWithCommits.push({ ...pkg, commits });
|
|
1702
|
+
logger.debug(`${pkg.name}: ${commits.length} commit(s)`);
|
|
1703
|
+
}
|
|
1704
|
+
return packagesWithCommits;
|
|
1705
|
+
}
|
|
1706
|
+
async function calculatePackageNewVersion({
|
|
1707
|
+
pkg,
|
|
1708
|
+
config,
|
|
1709
|
+
force,
|
|
1710
|
+
suffix
|
|
1711
|
+
}) {
|
|
1712
|
+
const releaseType = config.bump.type;
|
|
1713
|
+
const { from, to } = await resolveTags({
|
|
1714
|
+
config,
|
|
1715
|
+
versionMode: "independent",
|
|
1716
|
+
step: "bump",
|
|
1717
|
+
currentVersion: pkg.version,
|
|
1718
|
+
newVersion: void 0,
|
|
1719
|
+
pkg,
|
|
1720
|
+
logLevel: config.logLevel
|
|
1721
|
+
});
|
|
1722
|
+
const forcedBump = pkg.reason === "dependency";
|
|
1723
|
+
let forcedBumpType;
|
|
1724
|
+
if (forcedBump) {
|
|
1725
|
+
if (isStableReleaseType(releaseType)) {
|
|
1726
|
+
forcedBumpType = "patch";
|
|
1727
|
+
} else {
|
|
1728
|
+
forcedBumpType = isPrerelease(pkg.version) ? "prerelease" : "prepatch";
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
const commits = pkg.commits?.length > 0 ? pkg.commits : await getPackageCommits({
|
|
1732
|
+
pkg,
|
|
1733
|
+
from,
|
|
1734
|
+
to,
|
|
1735
|
+
config,
|
|
1736
|
+
changelog: false
|
|
1737
|
+
});
|
|
1738
|
+
let calculatedReleaseType = null;
|
|
1739
|
+
if (forcedBumpType) {
|
|
1740
|
+
calculatedReleaseType = forcedBumpType;
|
|
1741
|
+
} else if (commits.length === 0 && !force) {
|
|
1742
|
+
return null;
|
|
1743
|
+
} else {
|
|
1744
|
+
calculatedReleaseType = determineReleaseType({ from, to, commits, config, force, currentVersion: pkg.version });
|
|
1745
|
+
if (!calculatedReleaseType) {
|
|
1746
|
+
return null;
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
const currentVersion = pkg.version || "0.0.0";
|
|
1750
|
+
const newVersion = bumpPackageVersion({
|
|
1751
|
+
currentVersion,
|
|
1752
|
+
releaseType: calculatedReleaseType,
|
|
1753
|
+
preid: config.bump.preid,
|
|
1754
|
+
suffix
|
|
1755
|
+
});
|
|
1756
|
+
return {
|
|
1757
|
+
name: pkg.name,
|
|
1758
|
+
path: pkg.path,
|
|
1759
|
+
currentVersion,
|
|
1760
|
+
version: newVersion,
|
|
1761
|
+
fromTag: from,
|
|
1762
|
+
reason: pkg.reason,
|
|
1763
|
+
commits,
|
|
1764
|
+
dependencyChain: pkg.dependencyChain
|
|
1765
|
+
};
|
|
1766
|
+
}
|
|
1767
|
+
async function calculateNewVersionsForPackages({
|
|
1768
|
+
allPackagesToBump,
|
|
1769
|
+
config,
|
|
1770
|
+
force,
|
|
1771
|
+
suffix
|
|
1772
|
+
}) {
|
|
1773
|
+
const packagesWithNewVersions = [];
|
|
1774
|
+
for (const pkgToBump of allPackagesToBump) {
|
|
1775
|
+
const packageWithNewVersion = await calculatePackageNewVersion({
|
|
1776
|
+
pkg: pkgToBump,
|
|
1777
|
+
config,
|
|
1778
|
+
force,
|
|
1779
|
+
suffix
|
|
1780
|
+
});
|
|
1781
|
+
if (packageWithNewVersion) {
|
|
1782
|
+
packagesWithNewVersions.push(packageWithNewVersion);
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
return packagesWithNewVersions;
|
|
1786
|
+
}
|
|
1787
|
+
async function findPackagesWithCommitsAndCalculateVersions({
|
|
1788
|
+
packages,
|
|
1789
|
+
config,
|
|
1790
|
+
force,
|
|
1791
|
+
suffix
|
|
1792
|
+
}) {
|
|
1793
|
+
const packagesWithCommits = await findPackagesWithCommits({
|
|
1794
|
+
packages,
|
|
1795
|
+
config,
|
|
1796
|
+
force
|
|
1797
|
+
});
|
|
1798
|
+
if (packagesWithCommits.length === 0) {
|
|
1799
|
+
return [];
|
|
1800
|
+
}
|
|
1801
|
+
const allPackagesToBump = expandPackagesToBumpWithDependents({
|
|
1802
|
+
allPackages: packages,
|
|
1803
|
+
packagesWithCommits,
|
|
1804
|
+
dependencyTypes: config.bump.dependencyTypes
|
|
1805
|
+
});
|
|
1806
|
+
logger.debug(`Total packages to bump (including dependents): ${allPackagesToBump.length}`);
|
|
1807
|
+
const packagesWithNewVersions = await calculateNewVersionsForPackages({
|
|
1808
|
+
allPackagesToBump,
|
|
1809
|
+
config,
|
|
1810
|
+
force,
|
|
1811
|
+
suffix
|
|
1812
|
+
});
|
|
1813
|
+
logger.debug(`Found ${packagesWithNewVersions.length} package(s) to bump`);
|
|
1814
|
+
return packagesWithNewVersions;
|
|
1815
|
+
}
|
|
1816
|
+
function bumpIndependentPackages({
|
|
1817
|
+
packages,
|
|
1818
|
+
dryRun
|
|
1819
|
+
}) {
|
|
1820
|
+
const bumpedPackages = [];
|
|
1821
|
+
for (const pkgToBump of packages) {
|
|
1822
|
+
logger.debug(`Bumping ${pkgToBump.name} from ${pkgToBump.currentVersion} to ${pkgToBump.version} (reason: ${pkgToBump.reason})`);
|
|
1823
|
+
const result = bumpPackageIndependently({
|
|
1824
|
+
pkg: pkgToBump,
|
|
1825
|
+
dryRun
|
|
1826
|
+
});
|
|
1827
|
+
if (result.bumped) {
|
|
1828
|
+
bumpedPackages.push({
|
|
1829
|
+
...pkgToBump,
|
|
1830
|
+
version: result.newVersion
|
|
1831
|
+
});
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
return bumpedPackages;
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
async function getLastRepoTag(options) {
|
|
1838
|
+
let lastTag = null;
|
|
1839
|
+
if (options?.onlyStable) {
|
|
1840
|
+
const { stdout: stdout2 } = await execPromise(
|
|
1841
|
+
`git tag --sort=-creatordate | grep -E '^[^0-9]*[0-9]+\\.[0-9]+\\.[0-9]+$' | head -n 1`,
|
|
1842
|
+
{
|
|
1843
|
+
logLevel: options?.logLevel,
|
|
1844
|
+
noStderr: true,
|
|
1845
|
+
noStdout: true,
|
|
1846
|
+
noSuccess: true,
|
|
1847
|
+
cwd: options?.cwd
|
|
1848
|
+
}
|
|
1849
|
+
);
|
|
1850
|
+
lastTag = stdout2.trim();
|
|
1851
|
+
logger.debug("Last stable tag:", lastTag || "No stable tags found");
|
|
1852
|
+
return lastTag;
|
|
1853
|
+
}
|
|
1854
|
+
const { stdout } = await execPromise(`git tag --sort=-creatordate | head -n 1`, {
|
|
1855
|
+
logLevel: options?.logLevel,
|
|
1856
|
+
noStderr: true,
|
|
1857
|
+
noStdout: true,
|
|
1858
|
+
noSuccess: true,
|
|
1859
|
+
cwd: options?.cwd
|
|
1860
|
+
});
|
|
1861
|
+
lastTag = stdout.trim();
|
|
1862
|
+
logger.debug("Last tag:", lastTag || "No tags found");
|
|
1863
|
+
return lastTag;
|
|
1864
|
+
}
|
|
1865
|
+
async function getLastPackageTag({
|
|
1866
|
+
packageName,
|
|
1867
|
+
onlyStable,
|
|
1868
|
+
logLevel,
|
|
1869
|
+
cwd
|
|
1870
|
+
}) {
|
|
1871
|
+
try {
|
|
1872
|
+
const escapedPackageName = packageName.replace(/[@/]/g, "\\$&");
|
|
1873
|
+
let grepPattern;
|
|
1874
|
+
if (onlyStable) {
|
|
1875
|
+
grepPattern = `^${escapedPackageName}@[0-9]+\\.[0-9]+\\.[0-9]+$`;
|
|
1876
|
+
} else {
|
|
1877
|
+
grepPattern = `^${escapedPackageName}@`;
|
|
1878
|
+
}
|
|
1879
|
+
const { stdout } = await execPromise(
|
|
1880
|
+
`git tag --sort=-creatordate | grep -E '${grepPattern}' | sed -n '1p'`,
|
|
1881
|
+
{
|
|
1882
|
+
logLevel,
|
|
1883
|
+
noStderr: true,
|
|
1884
|
+
noStdout: true,
|
|
1885
|
+
noSuccess: true,
|
|
1886
|
+
cwd
|
|
1887
|
+
}
|
|
1888
|
+
);
|
|
1889
|
+
const tag = stdout.trim();
|
|
1890
|
+
return tag || null;
|
|
1891
|
+
} catch {
|
|
1892
|
+
return null;
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
async function resolveTagsIndependent({
|
|
1896
|
+
cwd,
|
|
1897
|
+
pkg,
|
|
1898
|
+
graduating,
|
|
1899
|
+
newVersion,
|
|
1900
|
+
step,
|
|
1901
|
+
logLevel
|
|
1902
|
+
}) {
|
|
1903
|
+
let to;
|
|
1904
|
+
if (step === "bump") {
|
|
1905
|
+
to = getCurrentGitRef(cwd);
|
|
1906
|
+
} else {
|
|
1907
|
+
to = newVersion ? `${pkg.name}@${newVersion}` : getCurrentGitRef(cwd);
|
|
1908
|
+
}
|
|
1909
|
+
const lastPackageTag = await getLastPackageTag({
|
|
1910
|
+
packageName: pkg.name,
|
|
1911
|
+
onlyStable: graduating,
|
|
1912
|
+
logLevel
|
|
1913
|
+
});
|
|
1914
|
+
if (!lastPackageTag) {
|
|
1915
|
+
const from = await getLastRepoTag({ logLevel }) || getFirstCommit(cwd);
|
|
1916
|
+
return { from, to };
|
|
1917
|
+
}
|
|
1918
|
+
return { from: lastPackageTag, to };
|
|
1919
|
+
}
|
|
1920
|
+
async function resolveTagsUnified({
|
|
1921
|
+
config,
|
|
1922
|
+
newVersion,
|
|
1923
|
+
graduating,
|
|
1924
|
+
step,
|
|
1925
|
+
logLevel
|
|
1926
|
+
}) {
|
|
1927
|
+
let to;
|
|
1928
|
+
if (step === "bump") {
|
|
1929
|
+
to = getCurrentGitRef(config.cwd);
|
|
1930
|
+
} else {
|
|
1931
|
+
to = newVersion ? config.templates.tagBody.replace("{{newVersion}}", newVersion) : getCurrentGitRef(config.cwd);
|
|
1932
|
+
}
|
|
1933
|
+
const from = await getLastRepoTag({ onlyStable: graduating, logLevel }) || getFirstCommit(config.cwd);
|
|
1934
|
+
return { from, to };
|
|
1935
|
+
}
|
|
1936
|
+
async function resolveTags({
|
|
1937
|
+
config,
|
|
1938
|
+
versionMode,
|
|
1939
|
+
step,
|
|
1940
|
+
pkg,
|
|
1941
|
+
currentVersion,
|
|
1942
|
+
newVersion,
|
|
1943
|
+
logLevel
|
|
1944
|
+
}) {
|
|
1945
|
+
logger.debug(`[${versionMode}](${step}) Resolving tags`);
|
|
1946
|
+
const graduating = currentVersion && isGraduating(currentVersion, config.bump.type) || false;
|
|
1947
|
+
const newTags = newVersion && config.templates.tagBody.replace("{{newVersion}}", newVersion) || null;
|
|
1948
|
+
if (config.from) {
|
|
1949
|
+
const tags2 = {
|
|
1950
|
+
from: config.from,
|
|
1951
|
+
to: config.to || newTags || getCurrentGitRef(config.cwd)
|
|
1952
|
+
};
|
|
1953
|
+
logger.debug(`[${versionMode}](${step}) Using specified tags: ${tags2.from} \u2192 ${tags2.to}`);
|
|
1954
|
+
return tags2;
|
|
1955
|
+
}
|
|
1956
|
+
if (versionMode === "independent") {
|
|
1957
|
+
const tags2 = await resolveTagsIndependent({
|
|
1958
|
+
cwd: config.cwd,
|
|
1959
|
+
pkg,
|
|
1960
|
+
graduating,
|
|
1961
|
+
newVersion,
|
|
1962
|
+
step,
|
|
1963
|
+
logLevel
|
|
1964
|
+
});
|
|
1965
|
+
logger.debug(`[${versionMode}](${step}) Using tags: ${tags2.from} \u2192 ${tags2.to}`);
|
|
1966
|
+
return tags2;
|
|
1967
|
+
}
|
|
1968
|
+
const tags = await resolveTagsUnified({
|
|
1969
|
+
config,
|
|
1970
|
+
newVersion,
|
|
1971
|
+
graduating,
|
|
1972
|
+
step,
|
|
1973
|
+
logLevel
|
|
1974
|
+
});
|
|
1975
|
+
logger.debug(`[${versionMode}](${step}) Using tags: ${tags.from} \u2192 ${tags.to}`);
|
|
1976
|
+
return tags;
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
function detectPackageManager(cwd = process.cwd()) {
|
|
1980
|
+
try {
|
|
1981
|
+
const packageJsonPath = join(cwd, "package.json");
|
|
1982
|
+
if (existsSync(packageJsonPath)) {
|
|
1983
|
+
try {
|
|
1984
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
1985
|
+
const pmField = packageJson.packageManager;
|
|
1986
|
+
if (typeof pmField === "string") {
|
|
1987
|
+
const pmName = pmField.split("@")[0];
|
|
1988
|
+
if (["npm", "pnpm", "yarn", "bun"].includes(pmName)) {
|
|
1989
|
+
logger.debug(`Detected package manager from package.json: ${pmName}`);
|
|
1990
|
+
return pmName;
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
} catch (e) {
|
|
1994
|
+
logger.warn(`Failed to parse package.json: ${e.message}`);
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
const lockFiles = {
|
|
1998
|
+
pnpm: "pnpm-lock.yaml",
|
|
1999
|
+
yarn: "yarn.lock",
|
|
2000
|
+
npm: "package-lock.json",
|
|
2001
|
+
bun: "bun.lockb"
|
|
2002
|
+
};
|
|
2003
|
+
for (const [manager, file] of Object.entries(lockFiles)) {
|
|
2004
|
+
if (existsSync(join(cwd, file))) {
|
|
2005
|
+
logger.debug(`Detected package manager from lockfile: ${manager}`);
|
|
2006
|
+
return manager;
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
const ua = process.env.npm_config_user_agent;
|
|
2010
|
+
if (ua) {
|
|
2011
|
+
const match = /(pnpm|yarn|npm|bun)/.exec(ua);
|
|
2012
|
+
if (match) {
|
|
2013
|
+
logger.debug(`Detected package manager from user agent: ${match[1]}`);
|
|
2014
|
+
return match[1];
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
logger.debug("No package manager detected, defaulting to npm");
|
|
2018
|
+
return "npm";
|
|
2019
|
+
} catch (error) {
|
|
2020
|
+
logger.warn(`Error detecting package manager: ${error}, defaulting to npm`);
|
|
2021
|
+
return "npm";
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
function determinePublishTag(version, configTag) {
|
|
2025
|
+
let tag = "latest";
|
|
2026
|
+
if (configTag) {
|
|
2027
|
+
tag = configTag;
|
|
2028
|
+
}
|
|
2029
|
+
if (isPrerelease(version) && !configTag) {
|
|
2030
|
+
logger.warn('You are about to publish a "prerelease" version with the "latest" tag. To avoid mistake, the tag is set to "next"');
|
|
2031
|
+
tag = "next";
|
|
2032
|
+
}
|
|
2033
|
+
if (isPrerelease(version) && configTag === "latest") {
|
|
2034
|
+
logger.warn('Please note, you are about to publish a "prerelease" version with the "latest" tag.');
|
|
2035
|
+
}
|
|
2036
|
+
return tag;
|
|
2037
|
+
}
|
|
2038
|
+
function getPackagesToPublishInSelectiveMode(sortedPackages, rootVersion) {
|
|
2039
|
+
const packagesToPublish = [];
|
|
2040
|
+
for (const pkg of sortedPackages) {
|
|
2041
|
+
const pkgJsonPath = join(pkg.path, "package.json");
|
|
2042
|
+
const pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
|
|
2043
|
+
if (pkgJson.version === rootVersion) {
|
|
2044
|
+
packagesToPublish.push(pkg);
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
return packagesToPublish;
|
|
2048
|
+
}
|
|
2049
|
+
async function getPackagesToPublishInIndependentMode(sortedPackages, config) {
|
|
2050
|
+
const packagesToPublish = [];
|
|
2051
|
+
for (const pkg of sortedPackages) {
|
|
2052
|
+
const { from, to } = await resolveTags({
|
|
2053
|
+
config,
|
|
2054
|
+
versionMode: "independent",
|
|
2055
|
+
step: "publish",
|
|
2056
|
+
pkg,
|
|
2057
|
+
newVersion: pkg.version,
|
|
2058
|
+
currentVersion: void 0,
|
|
2059
|
+
logLevel: config.logLevel
|
|
2060
|
+
});
|
|
2061
|
+
const commits = await getPackageCommits({
|
|
2062
|
+
pkg,
|
|
2063
|
+
from,
|
|
2064
|
+
to,
|
|
2065
|
+
config,
|
|
2066
|
+
changelog: false
|
|
2067
|
+
});
|
|
2068
|
+
if (commits.length > 0) {
|
|
2069
|
+
packagesToPublish.push(pkg);
|
|
2070
|
+
logger.debug(`${pkg.name}: ${commits.length} commit(s) since ${from} \u2192 ${to}`);
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
return packagesToPublish;
|
|
2074
|
+
}
|
|
2075
|
+
function isYarnBerry() {
|
|
2076
|
+
return existsSync(path.join(process.cwd(), ".yarnrc.yml"));
|
|
2077
|
+
}
|
|
2078
|
+
function getCommandArgs({
|
|
2079
|
+
packageManager,
|
|
2080
|
+
tag,
|
|
2081
|
+
config
|
|
2082
|
+
}) {
|
|
2083
|
+
const args = ["publish", "--tag", tag];
|
|
2084
|
+
if (packageManager === "pnpm") {
|
|
2085
|
+
args.push("--no-git-checks");
|
|
2086
|
+
} else if (packageManager === "yarn") {
|
|
2087
|
+
args.push("--non-interactive");
|
|
2088
|
+
if (isYarnBerry())
|
|
2089
|
+
args.push("--no-git-checks");
|
|
2090
|
+
} else if (packageManager === "npm") {
|
|
2091
|
+
args.push("--yes");
|
|
2092
|
+
}
|
|
2093
|
+
const registry = config.publish.registry;
|
|
2094
|
+
if (registry) {
|
|
2095
|
+
args.push("--registry", registry);
|
|
2096
|
+
}
|
|
2097
|
+
const access = config.publish.access;
|
|
2098
|
+
if (access) {
|
|
2099
|
+
args.push("--access", access);
|
|
2100
|
+
}
|
|
2101
|
+
const otp = config.publish.otp;
|
|
2102
|
+
if (otp) {
|
|
2103
|
+
args.push("--otp", otp);
|
|
2104
|
+
}
|
|
2105
|
+
return args;
|
|
2106
|
+
}
|
|
2107
|
+
async function publishPackage({
|
|
2108
|
+
pkg,
|
|
2109
|
+
config,
|
|
2110
|
+
packageManager,
|
|
2111
|
+
dryRun
|
|
2112
|
+
}) {
|
|
2113
|
+
const tag = determinePublishTag(pkg.version, config.publish.tag);
|
|
2114
|
+
logger.debug(`Building publish command for ${pkg.name}`);
|
|
2115
|
+
const args = getCommandArgs({
|
|
2116
|
+
packageManager,
|
|
2117
|
+
tag,
|
|
2118
|
+
config
|
|
2119
|
+
});
|
|
2120
|
+
const baseCommand = packageManager === "yarn" && isYarnBerry() ? "yarn npm" : packageManager;
|
|
2121
|
+
const command = `${baseCommand} ${args.join(" ")}`;
|
|
2122
|
+
const packageNameAndVersion = `${pkg.name}@${pkg.version}`;
|
|
2123
|
+
logger.debug(`Publishing ${packageNameAndVersion} with tag '${tag}' with command: ${command}`);
|
|
2124
|
+
try {
|
|
2125
|
+
process.chdir(pkg.path);
|
|
2126
|
+
logger.debug(`Executing publish command (${command}) in ${pkg.path}`);
|
|
2127
|
+
if (dryRun) {
|
|
2128
|
+
logger.info(`[dry-run] ${packageNameAndVersion}: Run ${command}`);
|
|
2129
|
+
} else {
|
|
2130
|
+
const { stdout } = await execPromise(command, {
|
|
2131
|
+
noStderr: true,
|
|
2132
|
+
noStdout: true,
|
|
2133
|
+
logLevel: config.logLevel,
|
|
2134
|
+
cwd: pkg.path
|
|
2135
|
+
});
|
|
2136
|
+
if (stdout) {
|
|
2137
|
+
logger.debug(stdout);
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
if (!dryRun) {
|
|
2141
|
+
logger.info(`Published ${packageNameAndVersion}`);
|
|
2142
|
+
}
|
|
2143
|
+
} catch (error) {
|
|
2144
|
+
logger.error(`Failed to publish ${packageNameAndVersion}:`, error);
|
|
2145
|
+
throw error;
|
|
2146
|
+
} finally {
|
|
2147
|
+
process.chdir(config.cwd);
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
async function bumpUnifiedMode({
|
|
2152
|
+
config,
|
|
2153
|
+
packages,
|
|
2154
|
+
dryRun,
|
|
2155
|
+
force,
|
|
2156
|
+
suffix
|
|
2157
|
+
}) {
|
|
2158
|
+
logger.debug("Starting bump in unified mode");
|
|
2159
|
+
const rootPackage = getRootPackage(config.cwd);
|
|
2160
|
+
const currentVersion = rootPackage.version;
|
|
2161
|
+
const { from, to } = await resolveTags({
|
|
2162
|
+
config,
|
|
2163
|
+
versionMode: "unified",
|
|
2164
|
+
step: "bump",
|
|
2165
|
+
newVersion: void 0,
|
|
2166
|
+
pkg: void 0,
|
|
2167
|
+
currentVersion,
|
|
2168
|
+
logLevel: config.logLevel
|
|
2169
|
+
});
|
|
2170
|
+
logger.debug(`Bump from ${from} to ${to}`);
|
|
2171
|
+
const commits = await getPackageCommits({
|
|
2172
|
+
pkg: rootPackage,
|
|
2173
|
+
from,
|
|
2174
|
+
to,
|
|
2175
|
+
config,
|
|
2176
|
+
changelog: false
|
|
2177
|
+
});
|
|
2178
|
+
logger.debug(`Found ${commits.length} commits since ${from}`);
|
|
2179
|
+
const releaseType = determineReleaseType({
|
|
2180
|
+
from,
|
|
2181
|
+
to,
|
|
2182
|
+
currentVersion,
|
|
2183
|
+
commits,
|
|
2184
|
+
config,
|
|
2185
|
+
force
|
|
2186
|
+
});
|
|
2187
|
+
if (!releaseType) {
|
|
2188
|
+
logger.debug("No commits require a version bump");
|
|
2189
|
+
return { bumped: false };
|
|
2190
|
+
}
|
|
2191
|
+
if (config.bump.type && force) {
|
|
2192
|
+
logger.debug(`Using specified release type: ${releaseType}`);
|
|
2193
|
+
} else {
|
|
2194
|
+
logger.debug(`Detected release type from commits: ${releaseType}`);
|
|
2195
|
+
}
|
|
2196
|
+
const newVersion = bumpPackageVersion({
|
|
2197
|
+
currentVersion,
|
|
2198
|
+
releaseType,
|
|
2199
|
+
preid: config.bump.preid,
|
|
2200
|
+
suffix
|
|
2201
|
+
});
|
|
2202
|
+
logger.debug(`${currentVersion} \u2192 ${newVersion} (unified mode)`);
|
|
2203
|
+
const allPackages = [rootPackage, ...packages];
|
|
2204
|
+
if (!config.bump.yes) {
|
|
2205
|
+
await confirmBump({
|
|
2206
|
+
versionMode: "unified",
|
|
2207
|
+
config,
|
|
2208
|
+
packages,
|
|
2209
|
+
force,
|
|
2210
|
+
currentVersion,
|
|
2211
|
+
newVersion,
|
|
2212
|
+
dryRun
|
|
2213
|
+
});
|
|
2214
|
+
} else {
|
|
2215
|
+
logger.info(`${allPackages.length === 1 ? allPackages[0].name : allPackages.length} package(s) bumped from ${currentVersion} to ${newVersion} (unified mode)`);
|
|
2216
|
+
}
|
|
2217
|
+
for (const pkg of allPackages) {
|
|
2218
|
+
writeVersion(pkg.path, newVersion, dryRun);
|
|
2219
|
+
}
|
|
2220
|
+
if (newVersion) {
|
|
2221
|
+
updateLernaVersion({
|
|
2222
|
+
rootDir: config.cwd,
|
|
2223
|
+
versionMode: config.monorepo?.versionMode,
|
|
2224
|
+
version: newVersion,
|
|
2225
|
+
dryRun
|
|
2226
|
+
});
|
|
2227
|
+
}
|
|
2228
|
+
if (!dryRun) {
|
|
2229
|
+
logger.info(`All ${allPackages.length} package(s) bumped to ${newVersion}`);
|
|
2230
|
+
}
|
|
2231
|
+
return {
|
|
2232
|
+
bumped: true,
|
|
2233
|
+
oldVersion: currentVersion,
|
|
2234
|
+
newVersion,
|
|
2235
|
+
bumpedPackages: packages.map((pkg) => ({
|
|
2236
|
+
...pkg,
|
|
2237
|
+
currentVersion: pkg.version,
|
|
2238
|
+
version: newVersion,
|
|
2239
|
+
fromTag: from
|
|
2240
|
+
}))
|
|
2241
|
+
};
|
|
2242
|
+
}
|
|
2243
|
+
async function bumpIndependentMode({
|
|
2244
|
+
config,
|
|
2245
|
+
packages,
|
|
2246
|
+
dryRun,
|
|
2247
|
+
force,
|
|
2248
|
+
suffix
|
|
2249
|
+
}) {
|
|
2250
|
+
logger.debug("Starting bump in independent mode");
|
|
2251
|
+
const packagesWithNewVersions = await findPackagesWithCommitsAndCalculateVersions({
|
|
2252
|
+
packages,
|
|
2253
|
+
config,
|
|
2254
|
+
force,
|
|
2255
|
+
suffix
|
|
2256
|
+
});
|
|
2257
|
+
if (packagesWithNewVersions.length === 0) {
|
|
2258
|
+
logger.debug("No packages have commits");
|
|
2259
|
+
return { bumped: false };
|
|
2260
|
+
}
|
|
2261
|
+
if (!config.bump.yes) {
|
|
2262
|
+
await confirmBump({
|
|
2263
|
+
versionMode: "independent",
|
|
2264
|
+
config,
|
|
2265
|
+
packages: packagesWithNewVersions,
|
|
2266
|
+
force,
|
|
2267
|
+
dryRun
|
|
2268
|
+
});
|
|
2269
|
+
} else {
|
|
2270
|
+
const bumpedByCommits2 = packagesWithNewVersions.filter((p) => p.reason === "commits").length;
|
|
2271
|
+
const bumpedByDependency2 = packagesWithNewVersions.length - bumpedByCommits2;
|
|
2272
|
+
logger.info(
|
|
2273
|
+
`${bumpedByCommits2 + bumpedByDependency2} package(s) will be bumped independently (${bumpedByCommits2} from commits, ${bumpedByDependency2} from dependencies)`
|
|
2274
|
+
);
|
|
2275
|
+
}
|
|
2276
|
+
const bumpedPackages = bumpIndependentPackages({
|
|
2277
|
+
packages: packagesWithNewVersions,
|
|
2278
|
+
dryRun
|
|
2279
|
+
});
|
|
2280
|
+
const bumpedByCommits = bumpedPackages.filter(
|
|
2281
|
+
(p) => packagesWithNewVersions.find((pkg) => pkg.name === p.name)?.reason === "commits"
|
|
2282
|
+
).length;
|
|
2283
|
+
const bumpedByDependency = bumpedPackages.length - bumpedByCommits;
|
|
2284
|
+
if (bumpedPackages.length === 0) {
|
|
2285
|
+
logger.debug("No packages were bumped");
|
|
2286
|
+
} else {
|
|
2287
|
+
logger.info(
|
|
2288
|
+
`${dryRun ? "[dry-run] " : ""}${bumpedPackages.length} package(s) bumped independently (${bumpedByCommits} from commits, ${bumpedByDependency} from dependencies)`
|
|
2289
|
+
);
|
|
2290
|
+
}
|
|
2291
|
+
return {
|
|
2292
|
+
bumped: bumpedPackages.length > 0,
|
|
2293
|
+
bumpedPackages
|
|
2294
|
+
};
|
|
2295
|
+
}
|
|
2296
|
+
async function bumpSelectiveMode({
|
|
2297
|
+
config,
|
|
2298
|
+
packages,
|
|
2299
|
+
dryRun,
|
|
2300
|
+
force,
|
|
2301
|
+
suffix
|
|
2302
|
+
}) {
|
|
2303
|
+
logger.debug("Starting bump in selective mode");
|
|
2304
|
+
const rootPackage = getRootPackage(config.cwd);
|
|
2305
|
+
const currentVersion = rootPackage.version;
|
|
2306
|
+
const { from, to } = await resolveTags({
|
|
2307
|
+
config,
|
|
2308
|
+
versionMode: "selective",
|
|
2309
|
+
step: "bump",
|
|
2310
|
+
currentVersion,
|
|
2311
|
+
newVersion: void 0,
|
|
2312
|
+
pkg: void 0,
|
|
2313
|
+
logLevel: config.logLevel
|
|
2314
|
+
});
|
|
2315
|
+
logger.debug(`Bump from ${from} to ${to}`);
|
|
2316
|
+
const commits = await getPackageCommits({
|
|
2317
|
+
pkg: rootPackage,
|
|
2318
|
+
from,
|
|
2319
|
+
to,
|
|
2320
|
+
config,
|
|
2321
|
+
changelog: false
|
|
2322
|
+
});
|
|
2323
|
+
const releaseType = determineReleaseType({ from, to, commits, config, force, currentVersion });
|
|
2324
|
+
if (!releaseType) {
|
|
2325
|
+
logger.debug("No commits require a version bump");
|
|
2326
|
+
return { bumped: false };
|
|
2327
|
+
}
|
|
2328
|
+
const newVersion = bumpPackageVersion({
|
|
2329
|
+
currentVersion,
|
|
2330
|
+
releaseType,
|
|
2331
|
+
preid: config.bump.preid,
|
|
2332
|
+
suffix
|
|
2333
|
+
});
|
|
2334
|
+
logger.debug("Determining packages to bump...");
|
|
2335
|
+
const packagesToBump = force ? packages : await getPackageToBump({
|
|
2336
|
+
packages,
|
|
2337
|
+
from,
|
|
2338
|
+
to,
|
|
2339
|
+
config
|
|
2340
|
+
});
|
|
2341
|
+
if (packagesToBump.length === 0) {
|
|
2342
|
+
logger.debug("No packages have commits, skipping bump");
|
|
2343
|
+
return { bumped: false };
|
|
2344
|
+
}
|
|
2345
|
+
if (!config.bump.yes) {
|
|
2346
|
+
await confirmBump({
|
|
2347
|
+
versionMode: "selective",
|
|
2348
|
+
config,
|
|
2349
|
+
packages: packagesToBump,
|
|
2350
|
+
force,
|
|
2351
|
+
currentVersion,
|
|
2352
|
+
newVersion,
|
|
2353
|
+
dryRun
|
|
2354
|
+
});
|
|
2355
|
+
} else {
|
|
2356
|
+
if (force) {
|
|
2357
|
+
logger.info(`${packagesToBump.length} package(s) bumped to ${newVersion} (force)`);
|
|
2358
|
+
} else {
|
|
2359
|
+
const bumpedByCommits = packagesToBump.filter((p) => "reason" in p && p.reason === "commits").length;
|
|
2360
|
+
const bumpedByDependency = packagesToBump.filter((p) => "reason" in p && p.reason === "dependency").length;
|
|
2361
|
+
logger.info(
|
|
2362
|
+
`${currentVersion} \u2192 ${newVersion} (selective mode: ${bumpedByCommits} with commits, ${bumpedByDependency} as dependents)`
|
|
2363
|
+
);
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
logger.debug(`Writing version to ${packagesToBump.length} package(s)`);
|
|
2367
|
+
writeVersion(rootPackage.path, newVersion, dryRun);
|
|
2368
|
+
for (const pkg of packagesToBump) {
|
|
2369
|
+
writeVersion(pkg.path, newVersion, dryRun);
|
|
2370
|
+
}
|
|
2371
|
+
updateLernaVersion({
|
|
2372
|
+
rootDir: config.cwd,
|
|
2373
|
+
versionMode: config.monorepo?.versionMode,
|
|
2374
|
+
version: newVersion,
|
|
2375
|
+
dryRun
|
|
2376
|
+
});
|
|
2377
|
+
if (!dryRun) {
|
|
2378
|
+
logger.info(
|
|
2379
|
+
`${packagesToBump.length} package(s) bumped to ${newVersion} (${packages.length - packagesToBump.length} skipped)`
|
|
2380
|
+
);
|
|
2381
|
+
}
|
|
2382
|
+
return {
|
|
2383
|
+
bumped: true,
|
|
2384
|
+
oldVersion: currentVersion,
|
|
2385
|
+
fromTag: from,
|
|
2386
|
+
newVersion,
|
|
2387
|
+
bumpedPackages: packagesToBump.map((pkg) => ({
|
|
2388
|
+
...pkg,
|
|
2389
|
+
currentVersion: pkg.version,
|
|
2390
|
+
version: newVersion,
|
|
2391
|
+
fromTag: from
|
|
2392
|
+
}))
|
|
2393
|
+
};
|
|
2394
|
+
}
|
|
2395
|
+
async function bump(options = {}) {
|
|
2396
|
+
try {
|
|
2397
|
+
logger.start("Start bumping versions");
|
|
2398
|
+
const dryRun = options.dryRun ?? false;
|
|
2399
|
+
logger.debug(`Dry run: ${dryRun}`);
|
|
2400
|
+
const force = options.force ?? false;
|
|
2401
|
+
logger.debug(`Bump forced: ${force}`);
|
|
2402
|
+
const config = await loadMonorepoConfig({
|
|
2403
|
+
configName: options.configName,
|
|
2404
|
+
baseConfig: options.config,
|
|
2405
|
+
overrides: {
|
|
2406
|
+
bump: {
|
|
2407
|
+
yes: options.yes,
|
|
2408
|
+
type: options.type,
|
|
2409
|
+
clean: options.clean,
|
|
2410
|
+
preid: options.preid
|
|
2411
|
+
},
|
|
2412
|
+
logLevel: options.logLevel
|
|
2413
|
+
}
|
|
2414
|
+
});
|
|
2415
|
+
if (config.bump.clean && config.release.clean) {
|
|
2416
|
+
try {
|
|
2417
|
+
checkGitStatusIfDirty();
|
|
2418
|
+
} catch {
|
|
2419
|
+
logger.error("Git status is dirty, please commit or stash your changes before bumping or use --no-clean flag");
|
|
2420
|
+
process.exit(1);
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
await fetchGitTags(config.cwd);
|
|
2424
|
+
logger.info(`Version mode: ${config.monorepo?.versionMode || "standalone"}`);
|
|
2425
|
+
const packages = getPackages({
|
|
2426
|
+
cwd: config.cwd,
|
|
2427
|
+
patterns: config.monorepo?.packages,
|
|
2428
|
+
ignorePackageNames: config.monorepo?.ignorePackageNames
|
|
2429
|
+
});
|
|
2430
|
+
logger.debug(`Found ${packages.length} package(s)`);
|
|
2431
|
+
let result;
|
|
2432
|
+
const payload = {
|
|
2433
|
+
config,
|
|
2434
|
+
packages,
|
|
2435
|
+
dryRun,
|
|
2436
|
+
force,
|
|
2437
|
+
suffix: options.suffix
|
|
2438
|
+
};
|
|
2439
|
+
if (config.monorepo?.versionMode === "unified" || !config.monorepo) {
|
|
2440
|
+
result = await bumpUnifiedMode(payload);
|
|
2441
|
+
} else if (config.monorepo?.versionMode === "selective") {
|
|
2442
|
+
result = await bumpSelectiveMode(payload);
|
|
2443
|
+
} else {
|
|
2444
|
+
result = await bumpIndependentMode(payload);
|
|
2445
|
+
}
|
|
2446
|
+
if (result.bumped) {
|
|
2447
|
+
const resultLog = result.bumpedPackages.length === 1 ? result.bumpedPackages[0].name : result.bumpedPackages.length;
|
|
2448
|
+
logger.success(`${dryRun ? "[dry-run] " : ""}Version bump completed (${resultLog} package${resultLog === 1 || typeof resultLog === "string" ? "" : "s"} bumped)`);
|
|
2449
|
+
} else {
|
|
2450
|
+
logger.fail("No packages to bump, no commits found");
|
|
2451
|
+
}
|
|
2452
|
+
return result;
|
|
2453
|
+
} catch (error) {
|
|
2454
|
+
logger.error("Error bumping versions:", error);
|
|
2455
|
+
throw error;
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
function getPackagesToGenerateChangelogFor({
|
|
2460
|
+
config,
|
|
2461
|
+
bumpedPackages
|
|
2462
|
+
}) {
|
|
2463
|
+
if (bumpedPackages && bumpedPackages.length > 0) {
|
|
2464
|
+
return bumpedPackages;
|
|
2465
|
+
}
|
|
2466
|
+
return getPackages({
|
|
2467
|
+
cwd: config.cwd,
|
|
2468
|
+
ignorePackageNames: config.monorepo?.ignorePackageNames,
|
|
2469
|
+
patterns: config.monorepo?.packages
|
|
2470
|
+
});
|
|
2471
|
+
}
|
|
2472
|
+
async function generateIndependentRootChangelog({
|
|
2473
|
+
packages,
|
|
2474
|
+
config,
|
|
2475
|
+
dryRun
|
|
2476
|
+
}) {
|
|
2477
|
+
if (!config.changelog?.rootChangelog) {
|
|
2478
|
+
logger.debug("Skipping root changelog generation");
|
|
2479
|
+
return;
|
|
2480
|
+
}
|
|
2481
|
+
logger.debug("Generating aggregated root changelog for independent mode");
|
|
2482
|
+
const rootPackage = getRootPackage(config.cwd);
|
|
2483
|
+
const packageChangelogs = [];
|
|
2484
|
+
for (const pkg of packages) {
|
|
2485
|
+
const { from, to } = await resolveTags({
|
|
2486
|
+
config,
|
|
2487
|
+
versionMode: "independent",
|
|
2488
|
+
step: "changelog",
|
|
2489
|
+
currentVersion: pkg.version,
|
|
2490
|
+
newVersion: void 0,
|
|
2491
|
+
pkg,
|
|
2492
|
+
logLevel: config.logLevel
|
|
2493
|
+
});
|
|
2494
|
+
const lastTag = from;
|
|
2495
|
+
logger.debug(`Generating changelog for ${pkg.name} (${lastTag}...${to})`);
|
|
2496
|
+
const commits = await getPackageCommits({
|
|
2497
|
+
pkg,
|
|
2498
|
+
from: lastTag,
|
|
2499
|
+
to,
|
|
2500
|
+
config,
|
|
2501
|
+
changelog: true
|
|
2502
|
+
});
|
|
2503
|
+
const changelog2 = await generateChangelog({
|
|
2504
|
+
config,
|
|
2505
|
+
pkg,
|
|
2506
|
+
commits,
|
|
2507
|
+
from: lastTag,
|
|
2508
|
+
dryRun
|
|
2509
|
+
});
|
|
2510
|
+
if (changelog2) {
|
|
2511
|
+
const cleanedChangelog = changelog2.split("\n").slice(2).join("\n");
|
|
2512
|
+
packageChangelogs.push(`## ${pkg.name}@${pkg.version}
|
|
2513
|
+
|
|
2514
|
+
${cleanedChangelog}`);
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
if (packageChangelogs.length === 0) {
|
|
2518
|
+
logger.debug("No changelogs to aggregate");
|
|
2519
|
+
return;
|
|
2520
|
+
}
|
|
2521
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
2522
|
+
const aggregatedChangelog = `**Multiple Packages Updated** - ${date}
|
|
2523
|
+
|
|
2524
|
+
${packageChangelogs.join("\n\n")}`;
|
|
2525
|
+
logger.verbose(`Aggregated root changelog: ${aggregatedChangelog}`);
|
|
2526
|
+
writeChangelogToFile({
|
|
2527
|
+
pkg: rootPackage,
|
|
2528
|
+
changelog: aggregatedChangelog,
|
|
2529
|
+
dryRun
|
|
2530
|
+
});
|
|
2531
|
+
logger.debug("Aggregated root changelog written");
|
|
2532
|
+
}
|
|
2533
|
+
async function generateSimpleRootChangelog({
|
|
2534
|
+
config,
|
|
2535
|
+
dryRun
|
|
2536
|
+
}) {
|
|
2537
|
+
if (!config.changelog?.rootChangelog) {
|
|
2538
|
+
logger.debug("Skipping root changelog generation");
|
|
2539
|
+
return;
|
|
2540
|
+
}
|
|
2541
|
+
logger.debug("Generating simple root changelog");
|
|
2542
|
+
const rootPackage = getRootPackage(config.cwd);
|
|
2543
|
+
const { from, to } = await resolveTags({
|
|
2544
|
+
config,
|
|
2545
|
+
versionMode: config.monorepo?.versionMode || "unified",
|
|
2546
|
+
step: "changelog",
|
|
2547
|
+
currentVersion: rootPackage.version,
|
|
2548
|
+
newVersion: void 0,
|
|
2549
|
+
pkg: void 0,
|
|
2550
|
+
logLevel: config.logLevel
|
|
2551
|
+
});
|
|
2552
|
+
logger.debug(`Generating root changelog (${from}...${to})`);
|
|
2553
|
+
logger.debug(`Root package: ${rootPackage.name} at ${rootPackage.path}`);
|
|
2554
|
+
const rootCommits = await getPackageCommits({
|
|
2555
|
+
pkg: rootPackage,
|
|
2556
|
+
from,
|
|
2557
|
+
to,
|
|
2558
|
+
config,
|
|
2559
|
+
changelog: true
|
|
2560
|
+
});
|
|
2561
|
+
const rootChangelog = await generateChangelog({
|
|
2562
|
+
pkg: rootPackage,
|
|
2563
|
+
commits: rootCommits,
|
|
2564
|
+
config,
|
|
2565
|
+
from,
|
|
2566
|
+
dryRun
|
|
2567
|
+
});
|
|
2568
|
+
if (rootChangelog) {
|
|
2569
|
+
writeChangelogToFile({
|
|
2570
|
+
changelog: rootChangelog,
|
|
2571
|
+
pkg: rootPackage,
|
|
2572
|
+
dryRun
|
|
2573
|
+
});
|
|
2574
|
+
logger.debug("Root changelog written");
|
|
2575
|
+
} else {
|
|
2576
|
+
logger.debug("No changelog to generate for root package");
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
async function changelog(options = {}) {
|
|
2580
|
+
try {
|
|
2581
|
+
logger.start("Start generating changelogs");
|
|
2582
|
+
const dryRun = options.dryRun ?? false;
|
|
2583
|
+
logger.debug(`Dry run: ${dryRun}`);
|
|
2584
|
+
const config = await loadMonorepoConfig({
|
|
2585
|
+
configName: options.configName,
|
|
2586
|
+
baseConfig: options.config,
|
|
2587
|
+
overrides: {
|
|
2588
|
+
from: options.from,
|
|
2589
|
+
to: options.to,
|
|
2590
|
+
logLevel: options.logLevel,
|
|
2591
|
+
changelog: {
|
|
2592
|
+
rootChangelog: options.rootChangelog,
|
|
2593
|
+
formatCmd: options.formatCmd
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
});
|
|
2597
|
+
logger.info(`Version mode: ${config.monorepo?.versionMode || "standalone"}`);
|
|
2598
|
+
const packages = getPackagesToGenerateChangelogFor({
|
|
2599
|
+
config,
|
|
2600
|
+
bumpedPackages: options.bumpedPackages
|
|
2601
|
+
});
|
|
2602
|
+
if (config.changelog?.rootChangelog && config.monorepo) {
|
|
2603
|
+
if (config.monorepo.versionMode === "independent") {
|
|
2604
|
+
await generateIndependentRootChangelog({
|
|
2605
|
+
packages,
|
|
2606
|
+
config,
|
|
2607
|
+
dryRun
|
|
2608
|
+
});
|
|
2609
|
+
} else {
|
|
2610
|
+
await generateSimpleRootChangelog({
|
|
2611
|
+
config,
|
|
2612
|
+
dryRun
|
|
2613
|
+
});
|
|
2614
|
+
}
|
|
2615
|
+
} else {
|
|
2616
|
+
logger.debug("Skipping root changelog generation");
|
|
2617
|
+
}
|
|
2618
|
+
logger.debug("Generating package changelogs...");
|
|
2619
|
+
logger.debug(`Processing ${packages.length} package(s)`);
|
|
2620
|
+
let generatedCount = 0;
|
|
2621
|
+
for await (const pkg of packages) {
|
|
2622
|
+
const { from, to } = await resolveTags({
|
|
2623
|
+
config,
|
|
2624
|
+
versionMode: config.monorepo?.versionMode || "unified",
|
|
2625
|
+
step: "changelog",
|
|
2626
|
+
currentVersion: pkg.version,
|
|
2627
|
+
newVersion: void 0,
|
|
2628
|
+
pkg,
|
|
2629
|
+
logLevel: config.logLevel
|
|
2630
|
+
});
|
|
2631
|
+
logger.debug(`Processing ${pkg.name} (${from}...${to})`);
|
|
2632
|
+
const commits = await getPackageCommits({
|
|
2633
|
+
pkg,
|
|
2634
|
+
from,
|
|
2635
|
+
to,
|
|
2636
|
+
config,
|
|
2637
|
+
changelog: true
|
|
2638
|
+
});
|
|
2639
|
+
logger.debug(`${pkg.name}: ${commits.length} commit(s)`);
|
|
2640
|
+
const changelog2 = await generateChangelog({
|
|
2641
|
+
pkg,
|
|
2642
|
+
commits,
|
|
2643
|
+
config,
|
|
2644
|
+
from,
|
|
2645
|
+
dryRun
|
|
2646
|
+
});
|
|
2647
|
+
if (changelog2) {
|
|
2648
|
+
writeChangelogToFile({
|
|
2649
|
+
pkg,
|
|
2650
|
+
changelog: changelog2,
|
|
2651
|
+
dryRun
|
|
2652
|
+
});
|
|
2653
|
+
generatedCount++;
|
|
2654
|
+
logger.debug(`${pkg.name}: changelog written`);
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
logger.debug(`Generated ${generatedCount} package changelog(s)`);
|
|
2658
|
+
await executeFormatCmd({
|
|
2659
|
+
config,
|
|
2660
|
+
dryRun
|
|
2661
|
+
});
|
|
2662
|
+
logger.success(`${dryRun ? "[dry run] " : ""}Changelog generation completed!`);
|
|
2663
|
+
} catch (error) {
|
|
2664
|
+
logger.error("Error generating changelogs:", error);
|
|
2665
|
+
throw error;
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
|
|
2669
|
+
async function providerRelease(options = {}) {
|
|
2670
|
+
const dryRun = options.dryRun ?? false;
|
|
2671
|
+
logger.debug(`Dry run: ${dryRun}`);
|
|
2672
|
+
const config = await loadMonorepoConfig({
|
|
2673
|
+
configName: options.configName,
|
|
2674
|
+
baseConfig: options.config,
|
|
2675
|
+
overrides: {
|
|
2676
|
+
from: options.from,
|
|
2677
|
+
to: options.to,
|
|
2678
|
+
tokens: {
|
|
2679
|
+
github: options.token,
|
|
2680
|
+
gitlab: options.token
|
|
2681
|
+
},
|
|
2682
|
+
logLevel: options.logLevel
|
|
2683
|
+
}
|
|
2684
|
+
});
|
|
2685
|
+
logger.info(`Version mode: ${config.monorepo?.versionMode || "standalone"}`);
|
|
2686
|
+
const detectedProvider = options.provider || detectGitProvider();
|
|
2687
|
+
if (!detectedProvider) {
|
|
2688
|
+
logger.warn("Unable to detect Git provider. Skipping release publication.");
|
|
2689
|
+
throw new Error("Unable to detect Git provider");
|
|
2690
|
+
} else {
|
|
2691
|
+
logger.info(
|
|
2692
|
+
options.provider ? `Using Git provider: ${options.provider}` : `Detected Git provider: ${detectedProvider}`
|
|
2693
|
+
);
|
|
2694
|
+
}
|
|
2695
|
+
let postedReleases = [];
|
|
2696
|
+
const payload = {
|
|
2697
|
+
from: config.from,
|
|
2698
|
+
dryRun,
|
|
2699
|
+
config,
|
|
2700
|
+
logLevel: config.logLevel,
|
|
2701
|
+
bumpResult: options.bumpResult
|
|
2702
|
+
};
|
|
2703
|
+
if (detectedProvider === "github") {
|
|
2704
|
+
postedReleases = await github(payload);
|
|
2705
|
+
} else if (detectedProvider === "gitlab") {
|
|
2706
|
+
postedReleases = await gitlab(payload);
|
|
2707
|
+
} else {
|
|
2708
|
+
logger.warn(`Unsupported Git provider: ${detectedProvider}`);
|
|
2709
|
+
}
|
|
2710
|
+
return {
|
|
2711
|
+
detectedProvider,
|
|
2712
|
+
postedReleases
|
|
2713
|
+
};
|
|
2714
|
+
}
|
|
2715
|
+
|
|
2716
|
+
async function publish(options = {}) {
|
|
2717
|
+
try {
|
|
2718
|
+
logger.start("Start publishing packages");
|
|
2719
|
+
const dryRun = options.dryRun ?? false;
|
|
2720
|
+
logger.debug(`Dry run: ${dryRun}`);
|
|
2721
|
+
const packageManager = detectPackageManager(process.cwd());
|
|
2722
|
+
logger.debug(`Package manager: ${packageManager}`);
|
|
2723
|
+
const config = await loadMonorepoConfig({
|
|
2724
|
+
configName: options.configName,
|
|
2725
|
+
baseConfig: options.config,
|
|
2726
|
+
overrides: {
|
|
2727
|
+
publish: {
|
|
2728
|
+
access: options.access,
|
|
2729
|
+
otp: options.otp,
|
|
2730
|
+
registry: options.registry,
|
|
2731
|
+
tag: options.tag,
|
|
2732
|
+
buildCmd: options.buildCmd
|
|
2733
|
+
},
|
|
2734
|
+
logLevel: options.logLevel
|
|
2735
|
+
}
|
|
2736
|
+
});
|
|
2737
|
+
logger.info(`Version mode: ${config.monorepo?.versionMode || "standalone"}`);
|
|
2738
|
+
if (config.publish.registry) {
|
|
2739
|
+
logger.debug(`Registry: ${config.publish.registry}`);
|
|
2740
|
+
}
|
|
2741
|
+
if (config.publish.tag) {
|
|
2742
|
+
logger.debug(`Tag: ${config.publish.tag}`);
|
|
2743
|
+
}
|
|
2744
|
+
const packages = options.bumpedPackages || getPackages({
|
|
2745
|
+
cwd: config.cwd,
|
|
2746
|
+
patterns: config.publish.packages ?? config.monorepo?.packages,
|
|
2747
|
+
ignorePackageNames: config.monorepo?.ignorePackageNames
|
|
2748
|
+
});
|
|
2749
|
+
const rootPackage = getRootPackage(config.cwd);
|
|
2750
|
+
logger.debug(`Found ${packages.length} package(s)`);
|
|
2751
|
+
logger.debug("Building dependency graph and sorting...");
|
|
2752
|
+
const packagesWithDeps = getPackagesWithDependencies(packages, config.bump.dependencyTypes);
|
|
2753
|
+
const sortedPackages = topologicalSort(packagesWithDeps);
|
|
2754
|
+
let publishedPackages = packages || [];
|
|
2755
|
+
if (publishedPackages.length === 0 && config.monorepo?.versionMode === "independent") {
|
|
2756
|
+
logger.debug("Determining packages to publish in independent mode...");
|
|
2757
|
+
publishedPackages = await getPackagesToPublishInIndependentMode(sortedPackages, config);
|
|
2758
|
+
logger.info(`Publishing ${publishedPackages.length} package(s) (independent mode)`);
|
|
2759
|
+
logger.debug(`Packages: ${publishedPackages.join(", ")}`);
|
|
2760
|
+
} else if (publishedPackages.length === 0 && config.monorepo?.versionMode === "selective") {
|
|
2761
|
+
logger.debug("Determining packages to publish in selective mode...");
|
|
2762
|
+
publishedPackages = getPackagesToPublishInSelectiveMode(sortedPackages, rootPackage.version);
|
|
2763
|
+
logger.info(`Publishing ${publishedPackages.length} package(s) matching root version (selective mode)`);
|
|
2764
|
+
logger.debug(`Packages: ${publishedPackages.join(", ")}`);
|
|
2765
|
+
} else if (publishedPackages.length === 0) {
|
|
2766
|
+
publishedPackages = sortedPackages;
|
|
2767
|
+
logger.info(`Publishing ${publishedPackages.length} package(s) (unified mode)`);
|
|
2768
|
+
}
|
|
2769
|
+
if (publishedPackages.length === 0) {
|
|
2770
|
+
logger.warn("No packages need to be published");
|
|
2771
|
+
return;
|
|
2772
|
+
}
|
|
2773
|
+
await executeBuildCmd({
|
|
2774
|
+
config,
|
|
2775
|
+
dryRun
|
|
2776
|
+
});
|
|
2777
|
+
for (const pkg of sortedPackages) {
|
|
2778
|
+
if (publishedPackages.some((p) => p.name === pkg.name)) {
|
|
2779
|
+
logger.debug(`Publishing ${pkg.name}@${pkg.version}...`);
|
|
2780
|
+
await publishPackage({
|
|
2781
|
+
pkg,
|
|
2782
|
+
config,
|
|
2783
|
+
packageManager,
|
|
2784
|
+
dryRun
|
|
2785
|
+
});
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2788
|
+
if (!dryRun) {
|
|
2789
|
+
logger.info("Package(s) have been published to npm registry");
|
|
2790
|
+
}
|
|
2791
|
+
logger.success("Publishing completed!");
|
|
2792
|
+
return {
|
|
2793
|
+
publishedPackages
|
|
2794
|
+
};
|
|
2795
|
+
} catch (error) {
|
|
2796
|
+
logger.error("Error publishing packages:", error);
|
|
2797
|
+
throw error;
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
|
|
2801
|
+
function getReleaseConfig(options = {}) {
|
|
2802
|
+
return loadMonorepoConfig({
|
|
2803
|
+
configName: options.configName,
|
|
2804
|
+
overrides: {
|
|
2805
|
+
logLevel: options.logLevel,
|
|
2806
|
+
from: options.from,
|
|
2807
|
+
to: options.to,
|
|
2808
|
+
tokens: {
|
|
2809
|
+
github: options.token,
|
|
2810
|
+
gitlab: options.token
|
|
2811
|
+
},
|
|
2812
|
+
bump: {
|
|
2813
|
+
type: options.type,
|
|
2814
|
+
preid: options.preid,
|
|
2815
|
+
clean: options.clean,
|
|
2816
|
+
yes: options.yes
|
|
2817
|
+
},
|
|
2818
|
+
changelog: {
|
|
2819
|
+
formatCmd: options.formatCmd,
|
|
2820
|
+
rootChangelog: options.rootChangelog
|
|
2821
|
+
},
|
|
2822
|
+
publish: {
|
|
2823
|
+
access: options.access,
|
|
2824
|
+
otp: options.otp,
|
|
2825
|
+
registry: options.registry,
|
|
2826
|
+
tag: options.tag,
|
|
2827
|
+
buildCmd: options.buildCmd
|
|
2828
|
+
},
|
|
2829
|
+
release: {
|
|
2830
|
+
commit: options.commit,
|
|
2831
|
+
changelog: options.changelog,
|
|
2832
|
+
push: options.push,
|
|
2833
|
+
publish: options.publish,
|
|
2834
|
+
noVerify: options.noVerify,
|
|
2835
|
+
release: options.release,
|
|
2836
|
+
clean: options.clean
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
});
|
|
2840
|
+
}
|
|
2841
|
+
async function release(options = {}) {
|
|
2842
|
+
try {
|
|
2843
|
+
const dryRun = options.dryRun ?? false;
|
|
2844
|
+
logger.debug(`Dry run: ${dryRun}`);
|
|
2845
|
+
const force = options.force ?? false;
|
|
2846
|
+
logger.debug(`Force bump: ${force}`);
|
|
2847
|
+
const config = await getReleaseConfig(options);
|
|
2848
|
+
logger.debug(`Version mode: ${config.monorepo?.versionMode || "standalone"}`);
|
|
2849
|
+
logger.debug(`Push: ${config.release.push}, Publish: ${config.release.publish}, Release: ${config.release.release}`);
|
|
2850
|
+
logger.box("Step 1/6: Bump versions");
|
|
2851
|
+
const bumpResult = await bump({
|
|
2852
|
+
type: config.bump.type,
|
|
2853
|
+
preid: config.bump.preid,
|
|
2854
|
+
dryRun,
|
|
2855
|
+
config,
|
|
2856
|
+
force,
|
|
2857
|
+
clean: config.release.clean,
|
|
2858
|
+
configName: options.configName
|
|
2859
|
+
});
|
|
2860
|
+
if (!bumpResult.bumped) {
|
|
2861
|
+
logger.debug("No packages bumped");
|
|
2862
|
+
return;
|
|
2863
|
+
}
|
|
2864
|
+
logger.box("Step 2/6: Generate changelogs");
|
|
2865
|
+
if (config.release.changelog) {
|
|
2866
|
+
await changelog({
|
|
2867
|
+
from: config.from,
|
|
2868
|
+
to: config.to,
|
|
2869
|
+
dryRun,
|
|
2870
|
+
formatCmd: config.changelog.formatCmd,
|
|
2871
|
+
rootChangelog: config.changelog.rootChangelog,
|
|
2872
|
+
bumpedPackages: bumpResult.bumpedPackages,
|
|
2873
|
+
config,
|
|
2874
|
+
logLevel: config.logLevel,
|
|
2875
|
+
configName: options.configName
|
|
2876
|
+
});
|
|
2877
|
+
} else {
|
|
2878
|
+
logger.info("Skipping changelog generation (--no-changelog)");
|
|
2879
|
+
}
|
|
2880
|
+
logger.box("Step 3/6: Commit changes and create tag");
|
|
2881
|
+
let createdTags = [];
|
|
2882
|
+
if (config.release.commit) {
|
|
2883
|
+
createdTags = await createCommitAndTags({
|
|
2884
|
+
config,
|
|
2885
|
+
noVerify: config.release.noVerify,
|
|
2886
|
+
bumpedPackages: bumpResult.bumpedPackages,
|
|
2887
|
+
newVersion: bumpResult.newVersion,
|
|
2888
|
+
dryRun,
|
|
2889
|
+
logLevel: config.logLevel
|
|
2890
|
+
});
|
|
2891
|
+
} else {
|
|
2892
|
+
logger.info("Skipping commit and tag (--no-commit)");
|
|
2893
|
+
}
|
|
2894
|
+
logger.box("Step 4/6: Push changes and tags");
|
|
2895
|
+
if (config.release.push && config.release.commit) {
|
|
2896
|
+
await pushCommitAndTags({ dryRun, logLevel: config.logLevel, cwd: config.cwd });
|
|
2897
|
+
} else {
|
|
2898
|
+
logger.info("Skipping push (--no-push or --no-commit)");
|
|
2899
|
+
}
|
|
2900
|
+
logger.box("Step 5/6: Publish packages to registry");
|
|
2901
|
+
let publishResponse;
|
|
2902
|
+
if (config.release.publish) {
|
|
2903
|
+
publishResponse = await publish({
|
|
2904
|
+
registry: config.publish.registry,
|
|
2905
|
+
tag: config.publish.tag,
|
|
2906
|
+
access: config.publish.access,
|
|
2907
|
+
otp: config.publish.otp,
|
|
2908
|
+
bumpedPackages: bumpResult.bumpedPackages,
|
|
2909
|
+
dryRun,
|
|
2910
|
+
config,
|
|
2911
|
+
configName: options.configName
|
|
2912
|
+
});
|
|
2913
|
+
} else {
|
|
2914
|
+
logger.info("Skipping publish (--no-publish)");
|
|
2915
|
+
}
|
|
2916
|
+
let provider = config.repo?.provider;
|
|
2917
|
+
let postedReleases = [];
|
|
2918
|
+
logger.box("Step 6/6: Publish Git release");
|
|
2919
|
+
if (config.release.release) {
|
|
2920
|
+
logger.debug(`Provider from config: ${provider}`);
|
|
2921
|
+
try {
|
|
2922
|
+
const response = await providerRelease({
|
|
2923
|
+
provider,
|
|
2924
|
+
dryRun,
|
|
2925
|
+
config,
|
|
2926
|
+
logLevel: config.logLevel,
|
|
2927
|
+
bumpResult,
|
|
2928
|
+
configName: options.configName
|
|
2929
|
+
});
|
|
2930
|
+
provider = response.detectedProvider;
|
|
2931
|
+
postedReleases = response.postedReleases;
|
|
2932
|
+
} catch (error) {
|
|
2933
|
+
logger.error("Error during release publication:", error);
|
|
2934
|
+
}
|
|
2935
|
+
} else {
|
|
2936
|
+
logger.info("Skipping release (--no-release)");
|
|
2937
|
+
}
|
|
2938
|
+
const publishedPackageCount = publishResponse?.publishedPackages.length ?? 0;
|
|
2939
|
+
const versionDisplay = config.monorepo?.versionMode === "independent" ? `${bumpResult.bumpedPackages.length} packages bumped independently` : bumpResult.newVersion || getRootPackage(config.cwd).version;
|
|
2940
|
+
logger.box(`Release workflow completed!
|
|
2941
|
+
|
|
2942
|
+
Version: ${versionDisplay}
|
|
2943
|
+
Tag(s): ${createdTags.length ? createdTags.join(", ") : "No"}
|
|
2944
|
+
Pushed: ${config.release.push ? "Yes" : "Disabled"}
|
|
2945
|
+
Published packages: ${config.release.publish ? publishedPackageCount : "Disabled"}
|
|
2946
|
+
Published release: ${config.release.release ? postedReleases.length : "Disabled"}
|
|
2947
|
+
Git provider: ${provider}`);
|
|
2948
|
+
} catch (error) {
|
|
2949
|
+
logger.error("Error during release workflow:", error);
|
|
2950
|
+
throw error;
|
|
2951
|
+
}
|
|
2952
|
+
}
|
|
2953
|
+
|
|
2954
|
+
export { isChangedPreid as $, getCurrentGitRef as A, github as B, createGitlabRelease as C, gitlab as D, getPackages as E, getPackageCommits as F, getRootPackage as G, hasLernaJson as H, getPackageToBump as I, detectPackageManager as J, determinePublishTag as K, getPackagesToPublishInSelectiveMode as L, getPackagesToPublishInIndependentMode as M, publishPackage as N, getLastRepoTag as O, getLastPackageTag as P, resolveTags as Q, determineReleaseType as R, writeVersion as S, bumpPackageVersion as T, updateLernaVersion as U, extractVersionFromPackageTag as V, isPrerelease as W, isStableReleaseType as X, isPrereleaseReleaseType as Y, isGraduating as Z, getPreid as _, publish as a, bumpPackageIndependently as a0, confirmBump as a1, findPackagesWithCommitsAndCalculateVersions as a2, bumpIndependentPackages as a3, bump as b, changelog as c, executeBuildCmd as d, executeFormatCmd as e, getDefaultConfig as f, generateChangelog as g, defineConfig as h, getPackageDependencies as i, getPackagesWithDependencies as j, getDependentsOf as k, loadMonorepoConfig as l, expandPackagesToBumpWithDependents as m, getGitStatus as n, checkGitStatusIfDirty as o, providerRelease as p, fetchGitTags as q, release as r, detectGitProvider as s, topologicalSort as t, parseGitRemoteUrl as u, createCommitAndTags as v, writeChangelogToFile as w, pushCommitAndTags as x, getFirstCommit as y, getCurrentGitBranch as z };
|