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.
@@ -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 };