relizy 0.2.5-beta.11 → 0.2.5-beta.12

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