relizy 0.2.5-beta.10 → 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,2454 +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
557
  }
518
- function formatName(name = "") {
519
- return name.split(" ").map((p) => upperFirst(p.trim())).join(" ");
520
- }
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)`);
1048
+ const changelogPath = join(pkg.path, "CHANGELOG.md");
1049
+ let existingChangelog = "";
1050
+ if (existsSync(changelogPath)) {
1051
+ existingChangelog = readFileSync(changelogPath, "utf8");
1444
1052
  }
1445
- if (isPrereleaseReleaseType(releaseType) && suffix) {
1446
- newVersion = newVersion.replace(/\.(\d+)$/, `.${suffix}`);
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}`;
1065
+ }
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})`);
1073
+ }
1074
+ }
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}`);
1447
1138
  }
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`);
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
+ };
1451
1147
  }
1452
- if (isGraduating(currentVersion, releaseType)) {
1453
- logger.info(`Graduating from prerelease ${currentVersion} to stable ${newVersion}`);
1148
+ if (typeof config.repo === "string") {
1149
+ const resolvedRepoConfig = getRepoConfig(config.repo);
1150
+ config.repo = {
1151
+ ...resolvedRepoConfig,
1152
+ provider: resolvedRepoConfig.provider
1153
+ };
1454
1154
  }
1455
- if (isChangedPreid(currentVersion, preid)) {
1456
- logger.debug(`Graduating from ${getPreid(currentVersion)} to ${preid}`);
1155
+ return config;
1156
+ }
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
+ }
1457
1176
  }
1458
- return newVersion;
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;
1459
1182
  }
1460
- function updateLernaVersion({
1461
- rootDir,
1462
- versionMode,
1463
- version,
1464
- dryRun = false
1183
+ function defineConfig(config) {
1184
+ return config;
1185
+ }
1186
+
1187
+ async function githubIndependentMode({
1188
+ config,
1189
+ dryRun,
1190
+ bumpResult,
1191
+ force,
1192
+ suffix
1465
1193
  }) {
1466
- const lernaJsonExists = hasLernaJson(rootDir);
1467
- if (!lernaJsonExists) {
1468
- return;
1194
+ const repoConfig = config.repo;
1195
+ if (!repoConfig) {
1196
+ throw new Error("No repository configuration found. Please check your changelog config.");
1469
1197
  }
1470
- const lernaJsonPath = join(rootDir, "lerna.json");
1471
- if (!existsSync(lernaJsonPath)) {
1472
- return;
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.");
1473
1201
  }
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;
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;
1482
1217
  }
1483
- lernaJson.version = version;
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)" : ""}`);
1484
1234
  if (dryRun) {
1485
- logger.info(`[dry-run] update lerna.json: ${oldVersion} \u2192 ${version}`);
1486
- return;
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
+ });
1487
1256
  }
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}`);
1493
- }
1494
- }
1495
- function extractVersionFromPackageTag(tag) {
1496
- const atIndex = tag.lastIndexOf("@");
1497
- if (atIndex === -1) {
1498
- return null;
1499
- }
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);
1515
- }
1516
- function isGraduating(currentVersion, releaseType) {
1517
- return isPrerelease(currentVersion) && isStableReleaseType(releaseType);
1518
- }
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;
1525
- }
1526
- return prerelease[0];
1527
- }
1528
- function isChangedPreid(currentVersion, targetPreid) {
1529
- if (!targetPreid || !isPrerelease(currentVersion)) {
1530
- return false;
1531
- }
1532
- const currentPreid = getPreid(currentVersion);
1533
- if (!currentPreid) {
1534
- return false;
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
- logger.error("Cannot confirm bump in unified mode without a new version");
1681
- process.exit(1);
1682
- }
1683
- displayUnifiedModePackages({ packages, newVersion, force });
1684
- } else if (versionMode === "selective") {
1685
- if (!newVersion) {
1686
- logger.error("Cannot confirm bump in selective mode without a new version");
1687
- process.exit(1);
1688
- }
1689
- displaySelectiveModePackages({ packages, newVersion, force });
1690
- } else if (versionMode === "independent") {
1691
- displayIndependentModePackages({ packages, force });
1386
+ const repoConfig = config.repo?.repo;
1387
+ if (!repoConfig) {
1388
+ throw new Error("No repository URL found in config");
1692
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
+ };
1693
1401
  try {
1694
- const confirmed = await confirm({
1695
- message: `${dryRun ? "[dry-run] " : ""}Do you want to proceed with these version updates?`,
1696
- default: true
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
+ };
1414
+ }
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)
1697
1423
  });
1698
- if (!confirmed) {
1699
- logger.log("");
1700
- logger.fail("Bump refused");
1701
- process.exit(0);
1424
+ if (!response.ok) {
1425
+ const errorText = await response.text();
1426
+ throw new Error(`GitLab API error (${response.status}): ${errorText}`);
1427
+ }
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;
1434
+ }
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
1473
+ });
1474
+ if (!changelog) {
1475
+ logger.warn(`No changelog found for ${pkg.name}`);
1476
+ continue;
1702
1477
  }
1703
- } catch (error) {
1704
- const userHasExited = error instanceof Error && error.name === "ExitPromptError";
1705
- if (userHasExited) {
1706
- logger.log("");
1707
- logger.fail("Bump cancelled");
1708
- 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
+ });
1709
1501
  }
1710
- logger.fail("Error while confirming bump");
1711
- process.exit(1);
1712
1502
  }
1713
- 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;
1714
1509
  }
1715
- function getBumpedIndependentPackages({
1716
- packages,
1717
- dryRun
1510
+ async function gitlabUnified({
1511
+ config,
1512
+ dryRun,
1513
+ rootPackage,
1514
+ bumpResult
1718
1515
  }) {
1719
- const bumpedPackages = [];
1720
- for (const pkgToBump of packages) {
1721
- logger.debug(`Bumping ${pkgToBump.name} from ${pkgToBump.version} to ${pkgToBump.newVersion} (reason: ${pkgToBump.reason})`);
1722
- const result = getBumpedPackageIndependently({
1723
- 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,
1724
1552
  dryRun
1725
1553
  });
1726
- if (result.bumped) {
1727
- bumpedPackages.push({
1728
- ...pkgToBump,
1729
- 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
1730
1586
  });
1731
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;
1732
1617
  }
1733
- return bumpedPackages;
1734
1618
  }
1735
1619
 
1736
- function getIndependentTag({ version, name }) {
1737
- 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;
1738
1625
  }
1739
- async function getLastStableTag({ logLevel, cwd }) {
1740
- const { stdout } = await execPromise(
1741
- `git tag --sort=-creatordate | grep -E '^[^0-9]*[0-9]+\\.[0-9]+\\.[0-9]+$' | head -n 1`,
1742
- {
1743
- logLevel,
1744
- noStderr: true,
1745
- noStdout: true,
1746
- noSuccess: true,
1747
- 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;
1748
1632
  }
1749
- );
1750
- const lastTag = stdout.trim();
1751
- logger.debug("Last stable tag:", lastTag || "No stable tags found");
1752
- 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;
1753
1643
  }
1754
- async function getLastTag({ logLevel, cwd }) {
1755
- const { stdout } = await execPromise(`git tag --sort=-creatordate | head -n 1`, {
1756
- logLevel,
1757
- noStderr: true,
1758
- noStdout: true,
1759
- noSuccess: true,
1760
- cwd
1761
- });
1762
- const lastTag = stdout.trim();
1763
- logger.debug("Last tag:", lastTag || "No tags found");
1764
- return lastTag;
1644
+ function detectReleaseTypeFromCommits(commits, types) {
1645
+ return determineSemverChange(commits, types);
1765
1646
  }
1766
- function getLastRepoTag(options) {
1767
- if (options?.onlyStable) {
1768
- 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`);
1769
1655
  }
1770
- return getLastTag({ logLevel: options?.logLevel, cwd: options?.cwd });
1771
1656
  }
1772
- async function getLastPackageTag({
1773
- packageName,
1774
- onlyStable,
1775
- logLevel,
1776
- cwd
1777
- }) {
1778
- try {
1779
- const escapedPackageName = packageName.replace(/[@/]/g, "\\$&");
1780
- let grepPattern;
1781
- if (onlyStable) {
1782
- grepPattern = `^${escapedPackageName}@[0-9]+\\.[0-9]+\\.[0-9]+$`;
1783
- } else {
1784
- grepPattern = `^${escapedPackageName}@`;
1785
- }
1786
- const { stdout } = await execPromise(
1787
- `git tag --sort=-creatordate | grep -E '${grepPattern}' | sed -n '1p'`,
1788
- {
1789
- logLevel,
1790
- noStderr: true,
1791
- noStdout: true,
1792
- noSuccess: true,
1793
- cwd
1794
- }
1795
- );
1796
- const tag = stdout.trim();
1797
- return tag || null;
1798
- } catch {
1799
- 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";
1800
1679
  }
1680
+ const prereleaseType = `pre${detectedType}`;
1681
+ logger.debug(`Auto-detected prerelease type from commits: ${prereleaseType}`);
1682
+ return prereleaseType;
1801
1683
  }
1802
- async function resolveFromTagIndependent({
1803
- cwd,
1804
- packageName,
1805
- graduating,
1806
- logLevel
1807
- }) {
1808
- const lastPackageTag = await getLastPackageTag({
1809
- packageName,
1810
- onlyStable: graduating,
1811
- logLevel
1812
- });
1813
- if (!lastPackageTag) {
1814
- return getFirstCommit(cwd);
1815
- }
1816
- return lastPackageTag;
1684
+ function handlePrereleaseVersionToStable(currentVersion) {
1685
+ logger.debug(`Graduating from prerelease ${currentVersion} to stable release`);
1686
+ return "release";
1817
1687
  }
1818
- async function resolveFromTagUnified({
1819
- config,
1820
- graduating,
1821
- logLevel
1822
- }) {
1823
- const from = await getLastRepoTag({ onlyStable: graduating, logLevel }) || getFirstCommit(config.cwd);
1824
- 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";
1825
1708
  }
1826
- async function resolveFromTag({
1827
- config,
1828
- versionMode,
1829
- step,
1830
- packageName,
1831
- graduating,
1832
- logLevel
1709
+ function handleExplicitReleaseType({
1710
+ releaseType,
1711
+ currentVersion
1833
1712
  }) {
1834
- let from;
1835
- if (versionMode === "independent") {
1836
- if (!packageName) {
1837
- throw new Error("Package name is required for independent version mode");
1838
- }
1839
- from = await resolveFromTagIndependent({
1840
- cwd: config.cwd,
1841
- packageName,
1842
- graduating,
1843
- logLevel
1844
- });
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}`);
1845
1717
  } else {
1846
- from = await resolveFromTagUnified({
1847
- config,
1848
- graduating,
1849
- logLevel
1850
- });
1718
+ logger.debug(`Using explicit release type: ${releaseType}`);
1851
1719
  }
1852
- logger.debug(`[${versionMode}](${step}) Using from tag: ${from}`);
1853
- return config.from || from;
1720
+ return releaseType;
1854
1721
  }
1855
- function resolveToTag({
1856
- config,
1857
- versionMode,
1858
- newVersion,
1859
- step,
1860
- packageName
1722
+ function determineReleaseType({
1723
+ currentVersion,
1724
+ commits,
1725
+ releaseType,
1726
+ preid,
1727
+ types,
1728
+ force
1861
1729
  }) {
1862
- const isUntaggedStep = step === "bump" || step === "changelog";
1863
- let to;
1864
- if (isUntaggedStep) {
1865
- to = getCurrentGitRef(config.cwd);
1866
- } else if (versionMode === "independent") {
1867
- if (!packageName) {
1868
- 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);
1869
1742
  }
1870
- if (!newVersion) {
1871
- throw new Error("New version is required for independent version mode");
1743
+ if (releaseType === "prerelease") {
1744
+ return handleStableVersionWithPrereleaseType(commits, types, force);
1872
1745
  }
1873
- to = getIndependentTag({ version: newVersion, name: packageName });
1874
- } else {
1875
- to = newVersion ? config.templates.tagBody.replace("{{newVersion}}", newVersion) : getCurrentGitRef(config.cwd);
1746
+ return handleExplicitReleaseType({ releaseType, currentVersion });
1876
1747
  }
1877
- logger.debug(`[${versionMode}](${step}) Using to tag: ${to}`);
1878
- 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 });
1879
1755
  }
1880
- async function resolveTags({
1881
- config,
1882
- step,
1883
- pkg,
1884
- newVersion
1885
- }) {
1886
- const versionMode = config.monorepo?.versionMode || "standalone";
1887
- const logLevel = config.logLevel;
1888
- logger.debug(`[${versionMode}](${step}) Resolving tags`);
1889
- const releaseType = config.bump.type;
1890
- const from = await resolveFromTag({
1891
- config,
1892
- versionMode,
1893
- step,
1894
- packageName: pkg.name,
1895
- graduating: isGraduating(pkg.version, releaseType),
1896
- logLevel
1897
- });
1898
- const to = resolveToTag({
1899
- config,
1900
- versionMode,
1901
- newVersion,
1902
- step,
1903
- packageName: pkg.name
1904
- });
1905
- logger.debug(`[${versionMode}](${step}) Using tags: ${from} \u2192 ${to}`);
1906
- 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
+ }
1907
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}"
1908
1784
 
1909
- let sessionOtp;
1910
- 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
+ }
1911
1816
  try {
1912
- const packageJsonPath = join(cwd, "package.json");
1913
- if (existsSync(packageJsonPath)) {
1914
- try {
1915
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
1916
- const pmField = packageJson.packageManager;
1917
- if (typeof pmField === "string") {
1918
- const pmName = pmField.split("@")[0];
1919
- if (["npm", "pnpm", "yarn", "bun"].includes(pmName)) {
1920
- logger.debug(`Detected package manager from package.json: ${pmName}`);
1921
- return pmName;
1922
- }
1923
- }
1924
- } catch (e) {
1925
- const errorString = e instanceof Error ? e.message : String(e);
1926
- logger.debug(`Failed to parse package.json: ${errorString}`);
1927
- }
1928
- }
1929
- const lockFiles = {
1930
- pnpm: "pnpm-lock.yaml",
1931
- yarn: "yarn.lock",
1932
- npm: "package-lock.json",
1933
- bun: "bun.lockb"
1934
- };
1935
- for (const [manager, file] of Object.entries(lockFiles)) {
1936
- if (existsSync(join(cwd, file))) {
1937
- logger.debug(`Detected package manager from lockfile: ${manager}`);
1938
- return manager;
1939
- }
1940
- }
1941
- const ua = process.env.npm_config_user_agent;
1942
- if (ua) {
1943
- const match = /(pnpm|yarn|npm|bun)/.exec(ua);
1944
- if (match) {
1945
- logger.debug(`Detected package manager from user agent: ${match[1]}`);
1946
- return match[1];
1947
- }
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;
1948
1824
  }
1949
- logger.debug("No package manager detected, defaulting to npm");
1950
- return "npm";
1825
+ lernaJson.version = version;
1826
+ if (dryRun) {
1827
+ logger.info(`[dry-run] update lerna.json: ${oldVersion} \u2192 ${version}`);
1828
+ return;
1829
+ }
1830
+ writeFileSync(lernaJsonPath, `${formatJson(lernaJson)}
1831
+ `, "utf8");
1832
+ logger.success(`Updated lerna.json: ${oldVersion} \u2192 ${version}`);
1951
1833
  } catch (error) {
1952
- logger.fail(`Error detecting package manager: ${error}, defaulting to npm`);
1953
- return "npm";
1834
+ logger.fail(`Unable to update lerna.json: ${error}`);
1954
1835
  }
1955
1836
  }
1956
- function determinePublishTag(version, configTag) {
1957
- let tag = "latest";
1958
- if (configTag) {
1959
- tag = configTag;
1960
- }
1961
- if (isPrerelease(version) && !configTag) {
1962
- logger.warn('You are about to publish a "prerelease" version with the "latest" tag. To avoid mistake, the tag is set to "next"');
1963
- tag = "next";
1964
- }
1965
- if (isPrerelease(version) && configTag === "latest") {
1966
- 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;
1967
1841
  }
1968
- return tag;
1842
+ return tag.slice(atIndex + 1);
1969
1843
  }
1970
- function getPackagesToPublishInSelectiveMode(sortedPackages, rootVersion) {
1971
- const packagesToPublish = [];
1972
- for (const pkg of sortedPackages) {
1973
- const pkgJsonPath = join(pkg.path, "package.json");
1974
- const pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
1975
- if (pkgJson.version === rootVersion) {
1976
- packagesToPublish.push(pkg);
1977
- }
1978
- }
1979
- 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;
1980
1849
  }
1981
- async function getPackagesToPublishInIndependentMode(sortedPackages, config) {
1982
- const packagesToPublish = [];
1983
- for (const pkg of sortedPackages) {
1984
- const { from, to } = await resolveTags({
1985
- config,
1986
- step: "publish",
1987
- pkg,
1988
- newVersion: pkg.version
1989
- });
1990
- if (pkg.commits.length > 0) {
1991
- packagesToPublish.push(pkg);
1992
- logger.debug(`${pkg.name}: ${pkg.commits.length} commit(s) since ${from} \u2192 ${to}`);
1993
- }
1994
- }
1995
- return packagesToPublish;
1850
+ function isStableReleaseType(releaseType) {
1851
+ const stableTypes = ["release", "major", "minor", "patch"];
1852
+ return stableTypes.includes(releaseType);
1996
1853
  }
1997
- function isYarnBerry() {
1998
- return existsSync(path.join(process.cwd(), ".yarnrc.yml"));
1854
+ function isPrereleaseReleaseType(releaseType) {
1855
+ const prereleaseTypes = ["prerelease", "premajor", "preminor", "prepatch"];
1856
+ return prereleaseTypes.includes(releaseType);
1999
1857
  }
2000
- function getCommandArgs({
2001
- packageManager,
2002
- tag,
2003
- config,
2004
- otp
2005
- }) {
2006
- const args = ["publish", "--tag", tag];
2007
- if (packageManager === "pnpm") {
2008
- args.push("--no-git-checks");
2009
- } else if (packageManager === "yarn") {
2010
- args.push("--non-interactive");
2011
- if (isYarnBerry())
2012
- args.push("--no-git-checks");
2013
- } else if (packageManager === "npm") {
2014
- 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;
2015
1867
  }
2016
- const registry = config.publish.registry;
2017
- if (registry) {
2018
- args.push("--registry", registry);
1868
+ return prerelease[0];
1869
+ }
1870
+ function isChangedPreid(currentVersion, targetPreid) {
1871
+ if (!targetPreid || !isPrerelease(currentVersion)) {
1872
+ return false;
2019
1873
  }
2020
- const access = config.publish.access;
2021
- if (access) {
2022
- args.push("--access", access);
1874
+ const currentPreid = getPreid(currentVersion);
1875
+ if (!currentPreid) {
1876
+ return false;
2023
1877
  }
2024
- const finalOtp = otp ?? sessionOtp ?? config.publish.otp;
2025
- if (finalOtp) {
2026
- 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 };
2027
1889
  }
2028
- 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 };
2029
1893
  }
2030
- function isOtpError(error) {
2031
- if (typeof error !== "object" || error === null)
2032
- return false;
2033
- const errorMessage = "message" in error && typeof error.message === "string" ? error.message.toLowerCase() : "";
2034
- 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
+ }
2035
1909
  }
2036
- function promptOtpWithTimeout(timeout = 9e4) {
2037
- return new Promise((resolve, reject) => {
2038
- const timer = setTimeout(() => {
2039
- reject(new Error("OTP input timeout"));
2040
- }, timeout);
2041
- input({
2042
- message: "This operation requires a one-time password (OTP). Please enter your OTP:"
2043
- }).then((otp) => {
2044
- clearTimeout(timer);
2045
- resolve(otp);
2046
- }).catch((error) => {
2047
- clearTimeout(timer);
2048
- reject(error);
2049
- });
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)" : ""}`);
2050
1918
  });
1919
+ logger.log("");
2051
1920
  }
2052
- async function handleOtpError() {
2053
- if (isInCI()) {
2054
- logger.error("OTP required but running in CI environment. Please provide OTP via config or `--otp` flag");
2055
- 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
+ }
2056
1957
  }
2057
- logger.warn("Publish failed: OTP required");
2058
- try {
2059
- const otp = await promptOtpWithTimeout();
2060
- logger.debug("OTP received, retrying publish...");
2061
- return otp;
2062
- } catch (promptError) {
2063
- logger.error("Failed to get OTP:", promptError);
2064
- 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
+ }
2065
1994
  }
2066
1995
  }
2067
- async function executePublishCommand({
2068
- command,
2069
- packageNameAndVersion,
2070
- pkg,
1996
+ async function confirmBump({
1997
+ versionMode,
2071
1998
  config,
1999
+ packages,
2000
+ force,
2001
+ currentVersion,
2002
+ newVersion,
2072
2003
  dryRun
2073
2004
  }) {
2074
- logger.debug(`Executing publish command (${command}) in ${pkg.path}`);
2075
- if (dryRun) {
2076
- logger.info(`[dry-run] ${packageNameAndVersion}: Run ${command}`);
2005
+ if (packages.length === 0) {
2006
+ logger.debug("No packages to bump");
2077
2007
  return;
2078
2008
  }
2079
- const { stdout } = await execPromise(command, {
2080
- noStderr: true,
2081
- noStdout: true,
2082
- logLevel: config.logLevel,
2083
- 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
2084
2019
  });
2085
- if (stdout) {
2086
- 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 });
2087
2032
  }
2088
- 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("");
2089
2054
  }
2090
- async function publishPackage({
2091
- pkg,
2092
- config,
2093
- packageManager,
2055
+ function getBumpedIndependentPackages({
2056
+ packages,
2094
2057
  dryRun
2095
2058
  }) {
2096
- const tag = determinePublishTag(pkg.version, config.publish.tag);
2097
- const packageNameAndVersion = getIndependentTag({ name: pkg.name, version: pkg.newVersion || pkg.version });
2098
- const baseCommand = packageManager === "yarn" && isYarnBerry() ? "yarn npm" : packageManager;
2099
- logger.debug(`Building publish command for ${pkg.name}`);
2100
- let dynamicOtp;
2101
- const maxAttempts = 2;
2102
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
2103
- try {
2104
- const args = getCommandArgs({
2105
- packageManager,
2106
- tag,
2107
- config,
2108
- otp: dynamicOtp
2109
- });
2110
- const command = `${baseCommand} ${args.join(" ")}`;
2111
- logger.debug(`Publishing ${packageNameAndVersion} with tag '${tag}' with command: ${command}`);
2112
- process.chdir(pkg.path);
2113
- await executePublishCommand({
2114
- command,
2115
- packageNameAndVersion,
2116
- pkg,
2117
- config,
2118
- 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
2119
2070
  });
2120
- if (dynamicOtp && !sessionOtp) {
2121
- sessionOtp = dynamicOtp;
2122
- logger.debug("OTP stored for session");
2123
- }
2124
- return;
2125
- } catch (error) {
2126
- if (isOtpError(error) && attempt < maxAttempts - 1) {
2127
- dynamicOtp = await handleOtpError();
2128
- } else {
2129
- logger.error(`Failed to publish ${packageNameAndVersion}:`, error);
2130
- throw error;
2131
- }
2132
- } finally {
2133
- process.chdir(config.cwd);
2134
2071
  }
2135
2072
  }
2073
+ return bumpedPackages;
2136
2074
  }
2137
2075
 
2138
- function readPackageJson(packagePath) {
2139
- const packageJsonPath = join(packagePath, "package.json");
2140
- if (!existsSync(packageJsonPath)) {
2141
- logger.fail(`package.json not found at ${packageJsonPath}`);
2142
- return;
2143
- }
2144
- if (!statSync(packagePath).isDirectory()) {
2145
- logger.fail(`Not a directory: ${packagePath}`);
2146
- return;
2147
- }
2148
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
2149
- if (!packageJson.name || !packageJson.version) {
2150
- throw new Error(`Invalid package.json at ${packagePath}`);
2151
- }
2152
- return {
2153
- name: packageJson.name,
2154
- version: packageJson.version,
2155
- private: packageJson.private || false,
2156
- path: packagePath
2157
- };
2076
+ function getIndependentTag({ version, name }) {
2077
+ return `${name}@${version}`;
2158
2078
  }
2159
- async function getRootPackage({
2160
- config,
2161
- force,
2162
- from,
2163
- to,
2164
- suffix,
2165
- changelog
2166
- }) {
2167
- try {
2168
- const packageJson = readPackageJson(config.cwd);
2169
- if (!packageJson) {
2170
- throw new Error("Failed to read root package.json");
2171
- }
2172
- const commits = await getPackageCommits({
2173
- pkg: packageJson,
2174
- from,
2175
- to,
2176
- config,
2177
- changelog
2178
- });
2179
- let newVersion;
2180
- if (config.monorepo?.versionMode !== "independent") {
2181
- const releaseType = determineReleaseType({
2182
- currentVersion: packageJson.version,
2183
- commits,
2184
- releaseType: config.bump.type,
2185
- preid: config.bump.preid,
2186
- types: config.types,
2187
- force
2188
- });
2189
- if (!releaseType) {
2190
- logger.fail("No commits require a version bump");
2191
- process.exit(1);
2192
- }
2193
- newVersion = getPackageNewVersion({
2194
- currentVersion: packageJson.version,
2195
- releaseType,
2196
- preid: config.bump.preid,
2197
- suffix
2198
- });
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
2199
2088
  }
2200
- return {
2201
- ...packageJson,
2202
- path: config.cwd,
2203
- fromTag: from,
2204
- commits,
2205
- newVersion
2206
- };
2207
- } catch (error) {
2208
- const errorMessage = error instanceof Error ? error.message : String(error);
2209
- 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 });
2210
2109
  }
2110
+ return getLastTag({ logLevel: options?.logLevel, cwd: options?.cwd });
2211
2111
  }
2212
- function readPackages({
2213
- cwd,
2214
- patterns,
2215
- ignorePackageNames
2112
+ async function getLastPackageTag({
2113
+ packageName,
2114
+ onlyStable,
2115
+ logLevel,
2116
+ cwd
2216
2117
  }) {
2217
- const packages = [];
2218
- const foundPaths = /* @__PURE__ */ new Set();
2219
- const patternsSet = new Set(patterns);
2220
- if (!patterns)
2221
- patternsSet.add(".");
2222
- logger.debug(`Read package.json files from patterns: ${patternsSet.values()}`);
2223
- for (const pattern of patternsSet) {
2224
- try {
2225
- const matches = fastGlob.sync(pattern, {
2226
- cwd,
2227
- onlyDirectories: true,
2228
- absolute: true,
2229
- ignore: ["**/node_modules/**", "**/dist/**", "**/.git/**"]
2230
- });
2231
- for (const matchPath of matches) {
2232
- if (foundPaths.has(matchPath))
2233
- continue;
2234
- const packageBase = readPackageJson(matchPath);
2235
- if (!packageBase || packageBase.private || ignorePackageNames?.includes(packageBase.name))
2236
- continue;
2237
- foundPaths.add(matchPath);
2238
- packages.push({
2239
- ...packageBase,
2240
- path: matchPath
2241
- });
2242
- }
2243
- } catch (error) {
2244
- 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}@`;
2245
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;
2246
2140
  }
2247
- return packages;
2248
2141
  }
2249
- function getPackageReleaseType({
2250
- 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({
2251
2159
  config,
2252
- force
2160
+ graduating,
2161
+ logLevel
2253
2162
  }) {
2254
- const releaseType = config.bump.type;
2255
- if (force) {
2256
- return determineReleaseType({
2257
- currentVersion: pkg.version,
2258
- commits: pkg.commits,
2259
- releaseType,
2260
- preid: config.bump.preid,
2261
- types: config.types,
2262
- 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
2263
2190
  });
2264
2191
  }
2265
- if (pkg.reason === "dependency") {
2266
- if (isStableReleaseType(releaseType))
2267
- return "patch";
2268
- if (isPrerelease(pkg.version))
2269
- return "prerelease";
2270
- 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);
2271
2216
  }
2272
- return determineReleaseType({
2273
- currentVersion: pkg.version,
2274
- commits: pkg.commits,
2275
- releaseType,
2276
- preid: config.bump.preid,
2277
- types: config.types,
2278
- force
2279
- });
2217
+ logger.debug(`[${versionMode}](${step}) Using to tag: ${to}`);
2218
+ return config.to || to;
2280
2219
  }
2281
- async function getPackages({
2282
- patterns,
2220
+ async function resolveTags({
2283
2221
  config,
2284
- suffix,
2285
- force
2222
+ step,
2223
+ pkg,
2224
+ newVersion
2286
2225
  }) {
2287
- const readedPackages = readPackages({
2288
- cwd: config.cwd,
2289
- patterns,
2290
- 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
2291
2238
  });
2292
- const packages = /* @__PURE__ */ new Map();
2293
- const foundPaths = /* @__PURE__ */ new Set();
2294
- const patternsSet = new Set(patterns);
2295
- if (!patterns)
2296
- patternsSet.add(".");
2297
- logger.debug(`Getting packages from patterns: ${patternsSet.values()}`);
2298
- for (const pattern of patternsSet) {
2299
- const matches = fastGlob.sync(pattern, {
2300
- cwd: config.cwd,
2301
- onlyDirectories: true,
2302
- absolute: true,
2303
- ignore: ["**/node_modules/**", "**/dist/**", "**/.git/**"]
2304
- });
2305
- for (const matchPath of matches) {
2306
- if (foundPaths.has(matchPath))
2307
- continue;
2308
- const packageBase = readPackageJson(matchPath);
2309
- if (!packageBase) {
2310
- logger.debug(`Failed to read package.json at ${matchPath} - ignored`);
2311
- continue;
2312
- }
2313
- if (packageBase.private) {
2314
- logger.debug(`${packageBase.name} is private and will be ignored`);
2315
- 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}`);
2316
2268
  }
2317
- if (config.monorepo?.ignorePackageNames?.includes(packageBase.name)) {
2318
- logger.debug(`${packageBase.name} ignored by config (monorepo.ignorePackageNames)`);
2319
- 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;
2320
2280
  }
2321
- if (!packageBase.version) {
2322
- logger.warn(`${packageBase.name} has no version and will be ignored`);
2323
- 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];
2324
2288
  }
2325
- const { from, to } = await resolveTags({
2326
- config,
2327
- step: "bump",
2328
- pkg: packageBase,
2329
- newVersion: void 0
2330
- });
2331
- const commits = await getPackageCommits({
2332
- pkg: packageBase,
2333
- from,
2334
- to,
2335
- config,
2336
- changelog: false
2337
- });
2338
- foundPaths.add(matchPath);
2339
- const dependencies = getPackageDependencies({
2340
- packagePath: matchPath,
2341
- allPackageNames: new Set(readedPackages.map((p) => p.name)),
2342
- dependencyTypes: config.bump?.dependencyTypes
2343
- });
2344
- packages.set(packageBase.name, {
2345
- ...packageBase,
2346
- path: matchPath,
2347
- fromTag: from,
2348
- dependencies,
2349
- commits,
2350
- reason: commits.length > 0 ? "commits" : void 0,
2351
- dependencyChain: void 0,
2352
- newVersion: void 0
2353
- });
2354
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";
2355
2305
  }
2356
- const packagesArray = Array.from(packages.values());
2357
- const packagesWithCommits = packagesArray.filter((p) => p.commits.length > 0);
2358
- const expandedPackages = expandPackagesToBumpWithDependents({
2359
- allPackages: packagesArray,
2360
- packagesWithCommits
2361
- });
2362
- for (const pkg of expandedPackages) {
2363
- 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.');
2364
2308
  }
2365
- for (const pkg of Array.from(packages.values())) {
2366
- const releaseType = getPackageReleaseType({
2367
- 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({
2368
2326
  config,
2369
- force
2370
- });
2371
- const newVersion = releaseType ? getPackageNewVersion({
2372
- currentVersion: pkg.version,
2373
- releaseType,
2374
- preid: config.bump.preid,
2375
- suffix
2376
- }) : void 0;
2377
- const graduating = releaseType && isGraduating(pkg.version, releaseType) || isChangedPreid(pkg.version, config.bump.preid);
2378
- packages.set(pkg.name, {
2379
- ...pkg,
2380
- newVersion,
2381
- reason: pkg.reason || releaseType && graduating && "graduation" || void 0
2327
+ step: "publish",
2328
+ pkg,
2329
+ newVersion: pkg.newVersion || pkg.version
2382
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
+ }
2383
2335
  }
2384
- const packagesToBump = Array.from(packages.values()).filter((p) => p.reason || force);
2385
- if (packagesToBump.length === 0) {
2386
- logger.debug("No packages to bump");
2387
- return [];
2388
- }
2389
- return packagesToBump;
2336
+ return packagesToPublish;
2390
2337
  }
2391
- function isAllowedCommit({
2392
- commit,
2393
- type,
2394
- 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
2395
2346
  }) {
2396
- if (commit.type === "chore" && ["deps", "release"].includes(commit.scope) && !commit.isBreaking) {
2397
- 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");
2398
2356
  }
2399
- if (typeof type === "object") {
2400
- return !!type.semver || changelog && !!type.title;
2357
+ const registry = config.publish.registry;
2358
+ if (registry) {
2359
+ args.push("--registry", registry);
2401
2360
  }
2402
- if (typeof type === "boolean") {
2403
- return type;
2361
+ const access = config.publish.access;
2362
+ if (access) {
2363
+ args.push("--access", access);
2404
2364
  }
2405
- return false;
2365
+ const finalOtp = otp ?? sessionOtp ?? config.publish.otp;
2366
+ if (finalOtp) {
2367
+ args.push("--otp", finalOtp);
2368
+ }
2369
+ return args;
2406
2370
  }
2407
- 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,
2408
2411
  pkg,
2409
- from,
2410
- to,
2411
2412
  config,
2412
- changelog
2413
+ dryRun
2413
2414
  }) {
2414
- logger.debug(`Analyzing commits for ${pkg.name} since ${from} to ${to}`);
2415
- const changelogConfig = {
2416
- ...config,
2417
- from,
2418
- to
2419
- };
2420
- const rawCommits = await getGitDiff(from, to, changelogConfig.cwd);
2421
- const allCommits = parseCommits(rawCommits, changelogConfig);
2422
- const hasBreakingChanges = allCommits.some((commit) => commit.isBreaking);
2423
- logger.debug(`Has breaking changes: ${hasBreakingChanges}`);
2424
- const rootPackage = readPackageJson(changelogConfig.cwd);
2425
- if (!rootPackage) {
2426
- 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;
2427
2419
  }
2428
- const commits = allCommits.filter((commit) => {
2429
- const type = changelogConfig?.types[commit.type];
2430
- if (!isAllowedCommit({ commit, type, changelog })) {
2431
- return false;
2432
- }
2433
- if (pkg.path === changelogConfig.cwd || pkg.name === rootPackage.name) {
2434
- return true;
2435
- }
2436
- const packageRelativePath = relative(changelogConfig.cwd, pkg.path);
2437
- const scopeMatches = commit.scope === pkg.name;
2438
- const bodyContainsPath = commit.body.includes(packageRelativePath);
2439
- return scopeMatches || bodyContainsPath;
2420
+ const { stdout } = await execPromise(command, {
2421
+ noStderr: true,
2422
+ noStdout: true,
2423
+ logLevel: config.logLevel,
2424
+ cwd: pkg.path
2440
2425
  });
2441
- logger.debug(`Found ${commits.length} commit(s) for ${pkg.name} from ${from} to ${to}`);
2442
- if (commits.length > 0) {
2443
- logger.debug(`${pkg.name}: ${commits.length} commit(s) found`);
2444
- } else {
2445
- logger.debug(`${pkg.name}: No commits found`);
2426
+ if (stdout) {
2427
+ logger.debug(stdout);
2446
2428
  }
2447
- return commits;
2429
+ logger.info(`Published ${packageNameAndVersion}`);
2448
2430
  }
2449
- function hasLernaJson(rootDir) {
2450
- const lernaJsonPath = join(rootDir, "lerna.json");
2451
- 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
+ }
2452
2477
  }
2453
2478
 
2454
2479
  async function bumpUnifiedMode({
@@ -2746,22 +2771,6 @@ async function bump(options = {}) {
2746
2771
  }
2747
2772
  }
2748
2773
 
2749
- async function getPackagesToGenerateChangelogFor({
2750
- config,
2751
- bumpResult,
2752
- suffix,
2753
- force
2754
- }) {
2755
- if (bumpResult?.bumpedPackages && bumpResult.bumpedPackages.length > 0) {
2756
- return bumpResult.bumpedPackages;
2757
- }
2758
- return await getPackages({
2759
- config,
2760
- patterns: config.monorepo?.packages,
2761
- suffix,
2762
- force
2763
- });
2764
- }
2765
2774
  async function generateIndependentRootChangelog({
2766
2775
  packages,
2767
2776
  config,
@@ -2821,10 +2830,11 @@ async function generateSimpleRootChangelog({
2821
2830
  if (!rootPackageRead) {
2822
2831
  throw new Error("Failed to read root package.json");
2823
2832
  }
2833
+ const newVersion = bumpResult?.newVersion || rootPackageRead.version;
2824
2834
  const { from, to } = await resolveTags({
2825
2835
  config,
2826
2836
  step: "changelog",
2827
- newVersion: void 0,
2837
+ newVersion,
2828
2838
  pkg: rootPackageRead
2829
2839
  });
2830
2840
  const fromTag = bumpResult?.fromTag || from;
@@ -2837,7 +2847,6 @@ async function generateSimpleRootChangelog({
2837
2847
  to
2838
2848
  });
2839
2849
  logger.debug(`Generating ${rootPackage.name} changelog (${fromTag}...${to})`);
2840
- const newVersion = bumpResult?.newVersion || rootPackage.version;
2841
2850
  const rootChangelog = await generateChangelog({
2842
2851
  pkg: rootPackage,
2843
2852
  config,
@@ -2878,7 +2887,7 @@ async function changelog(options = {}) {
2878
2887
  logger.start("Start generating changelogs");
2879
2888
  if (config.changelog?.rootChangelog && config.monorepo) {
2880
2889
  if (config.monorepo.versionMode === "independent") {
2881
- const packages2 = await getPackagesToGenerateChangelogFor({
2890
+ const packages2 = await getPackagesOrBumpedPackages({
2882
2891
  config,
2883
2892
  bumpResult: options.bumpResult,
2884
2893
  suffix: options.suffix,
@@ -2902,27 +2911,28 @@ async function changelog(options = {}) {
2902
2911
  logger.debug("Skipping root changelog generation");
2903
2912
  }
2904
2913
  logger.debug("Generating package changelogs...");
2905
- const packages = options.bumpResult?.bumpedPackages ? options.bumpResult.bumpedPackages : await getPackages({
2914
+ const packages = await getPackagesOrBumpedPackages({
2906
2915
  config,
2907
- patterns: config.monorepo?.packages,
2916
+ bumpResult: options.bumpResult,
2908
2917
  suffix: options.suffix,
2909
2918
  force: options.force ?? false
2910
2919
  });
2911
2920
  logger.debug(`Processing ${packages.length} package(s)`);
2912
2921
  let generatedCount = 0;
2913
2922
  for await (const pkg of packages) {
2923
+ const newVersion = options.bumpResult?.bumpedPackages?.find((p) => p.name === pkg.name)?.newVersion || pkg.newVersion || pkg.version;
2914
2924
  const { from, to } = await resolveTags({
2915
2925
  config,
2916
2926
  step: "changelog",
2917
2927
  pkg,
2918
- newVersion: void 0
2928
+ newVersion
2919
2929
  });
2920
2930
  logger.debug(`Processing ${pkg.name} (${from}...${to})`);
2921
2931
  const changelog2 = await generateChangelog({
2922
2932
  pkg,
2923
2933
  config,
2924
2934
  dryRun,
2925
- newVersion: options.bumpResult?.bumpedPackages?.find((p) => p.name === pkg.name)?.newVersion
2935
+ newVersion
2926
2936
  });
2927
2937
  if (changelog2) {
2928
2938
  writeChangelogToFile({
@@ -3066,9 +3076,9 @@ async function publish(options = {}) {
3066
3076
  throw new Error("Failed to read root package.json");
3067
3077
  }
3068
3078
  logger.start("Start publishing packages");
3069
- const packages = options.bumpedPackages || await getPackages({
3079
+ const packages = await getPackagesOrBumpedPackages({
3070
3080
  config,
3071
- patterns: config.publish.packages ?? config.monorepo?.packages,
3081
+ bumpResult: options.bumpResult,
3072
3082
  suffix: options.suffix,
3073
3083
  force: options.force ?? false
3074
3084
  });
@@ -3261,7 +3271,7 @@ async function release(options = {}) {
3261
3271
  tag: config.publish.tag,
3262
3272
  access: config.publish.access,
3263
3273
  otp: config.publish.otp,
3264
- bumpedPackages: bumpResult.bumpedPackages,
3274
+ bumpResult,
3265
3275
  dryRun,
3266
3276
  config,
3267
3277
  configName: options.configName,
@@ -3316,4 +3326,4 @@ Git provider: ${provider}`);
3316
3326
  }
3317
3327
  }
3318
3328
 
3319
- 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 };