relizy 0.2.5-beta.9 → 0.2.6-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,446 +1,901 @@
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;
128
+ for (const pkg of packages) {
129
+ visit(pkg.name);
130
+ }
131
+ return sorted;
148
132
  }
149
133
 
150
- function getGitStatus(cwd) {
151
- return execSync("git status --porcelain", {
152
- cwd,
153
- encoding: "utf8"
154
- }).trim();
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`);
147
+ }
148
+ return {
149
+ name: packageJson.name,
150
+ version: packageJson.version,
151
+ private: packageJson.private || false,
152
+ path: packagePath
153
+ };
155
154
  }
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);
170
- }
171
- }
172
- async function fetchGitTags(cwd) {
173
- logger.debug("Fetching git tags from remote");
155
+ async function getRootPackage({
156
+ config,
157
+ force,
158
+ from,
159
+ to,
160
+ suffix,
161
+ changelog
162
+ }) {
174
163
  try {
175
- await execPromise("git fetch --tags", { cwd, noStderr: true, noStdout: true, noSuccess: true });
176
- logger.debug("Git tags fetched successfully");
164
+ const packageJson = readPackageJson(config.cwd);
165
+ if (!packageJson) {
166
+ throw new Error("Failed to read root package.json");
167
+ }
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
+ name: packageJson.name,
191
+ currentVersion: packageJson.version,
192
+ releaseType,
193
+ preid: config.bump.preid,
194
+ suffix
195
+ });
196
+ }
197
+ return {
198
+ ...packageJson,
199
+ path: config.cwd,
200
+ fromTag: from,
201
+ commits,
202
+ newVersion
203
+ };
177
204
  } 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");
205
+ const errorMessage = error instanceof Error ? error.message : String(error);
206
+ throw new Error(errorMessage);
180
207
  }
181
208
  }
182
- function detectGitProvider(cwd = process.cwd()) {
183
- 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";
190
- }
191
- if (remoteUrl.includes("gitlab.com") || remoteUrl.includes("gitlab")) {
192
- return "gitlab";
209
+ function readPackages({
210
+ cwd,
211
+ patterns,
212
+ ignorePackageNames
213
+ }) {
214
+ const packages = [];
215
+ const foundPaths = /* @__PURE__ */ new Set();
216
+ const patternsSet = new Set(patterns);
217
+ if (!patterns)
218
+ patternsSet.add(".");
219
+ logger.debug(`Read package.json files from patterns: ${patternsSet.values()}`);
220
+ for (const pattern of patternsSet) {
221
+ try {
222
+ const matches = fastGlob.sync(pattern, {
223
+ cwd,
224
+ onlyDirectories: true,
225
+ absolute: true,
226
+ ignore: ["**/node_modules/**", "**/dist/**", "**/.git/**"]
227
+ });
228
+ for (const matchPath of matches) {
229
+ if (foundPaths.has(matchPath))
230
+ continue;
231
+ const packageBase = readPackageJson(matchPath);
232
+ if (!packageBase || packageBase.private || ignorePackageNames?.includes(packageBase.name))
233
+ continue;
234
+ foundPaths.add(matchPath);
235
+ packages.push({
236
+ ...packageBase,
237
+ path: matchPath
238
+ });
239
+ }
240
+ } catch (error) {
241
+ logger.error(error);
193
242
  }
194
- return null;
195
- } catch {
196
- return null;
197
243
  }
244
+ return packages;
198
245
  }
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
- return {
205
- owner: sshMatch[1],
206
- repo: sshMatch[2]
207
- };
246
+ function getPackageReleaseType({
247
+ pkg,
248
+ config,
249
+ force
250
+ }) {
251
+ const releaseType = config.bump.type;
252
+ if (force) {
253
+ return determineReleaseType({
254
+ currentVersion: pkg.version,
255
+ commits: pkg.commits,
256
+ releaseType,
257
+ preid: config.bump.preid,
258
+ types: config.types,
259
+ force
260
+ });
208
261
  }
209
- const httpsMatch = remoteUrl.match(httpsRegex);
210
- if (httpsMatch) {
211
- return {
212
- owner: httpsMatch[1],
213
- repo: httpsMatch[2]
214
- };
262
+ if (pkg.reason === "dependency") {
263
+ if (isStableReleaseType(releaseType))
264
+ return "patch";
265
+ if (isPrerelease(pkg.version))
266
+ return "prerelease";
267
+ return "prepatch";
215
268
  }
216
- return null;
269
+ return determineReleaseType({
270
+ currentVersion: pkg.version,
271
+ commits: pkg.commits,
272
+ releaseType,
273
+ preid: config.bump.preid,
274
+ types: config.types,
275
+ force
276
+ });
217
277
  }
218
- async function createCommitAndTags({
278
+ async function getPackages({
279
+ patterns,
219
280
  config,
220
- noVerify,
221
- bumpedPackages,
222
- newVersion,
223
- dryRun,
224
- logLevel
281
+ suffix,
282
+ force
225
283
  }) {
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`);
284
+ const readedPackages = readPackages({
285
+ cwd: config.cwd,
286
+ patterns,
287
+ ignorePackageNames: config.monorepo?.ignorePackageNames
288
+ });
289
+ const packages = /* @__PURE__ */ new Map();
290
+ const foundPaths = /* @__PURE__ */ new Set();
291
+ const patternsSet = new Set(patterns);
292
+ if (!patterns)
293
+ patternsSet.add(".");
294
+ logger.debug(`Getting packages from patterns: ${patternsSet.values()}`);
295
+ for (const pattern of patternsSet) {
296
+ const matches = fastGlob.sync(pattern, {
297
+ cwd: config.cwd,
298
+ onlyDirectories: true,
299
+ absolute: true,
300
+ ignore: ["**/node_modules/**", "**/dist/**", "**/.git/**"]
301
+ });
302
+ for (const matchPath of matches) {
303
+ if (foundPaths.has(matchPath))
304
+ continue;
305
+ const packageBase = readPackageJson(matchPath);
306
+ if (!packageBase) {
307
+ logger.debug(`Failed to read package.json at ${matchPath} - ignored`);
241
308
  continue;
242
309
  }
243
- if ((pattern === "lerna.json" || pattern === "CHANGELOG.md") && !existsSync(join(internalConfig.cwd, pattern))) {
244
- logger.verbose(`Skipping ${pattern} as it doesn't exist`);
310
+ if (packageBase.private) {
311
+ logger.debug(`${packageBase.name} is private and will be ignored`);
245
312
  continue;
246
313
  }
247
- if (dryRun) {
248
- logger.info(`[dry-run] git add ${pattern}`);
314
+ if (config.monorepo?.ignorePackageNames?.includes(packageBase.name)) {
315
+ logger.debug(`${packageBase.name} ignored by config (monorepo.ignorePackageNames)`);
249
316
  continue;
250
317
  }
251
- try {
252
- logger.debug(`git add ${pattern}`);
253
- execSync(`git add ${pattern}`);
254
- } catch {
318
+ if (!packageBase.version) {
319
+ logger.warn(`${packageBase.name} has no version and will be ignored`);
320
+ continue;
255
321
  }
256
- }
257
- const rootPackage = readPackageJson(internalConfig.cwd);
258
- newVersion = newVersion || rootPackage.version;
259
- const versionForMessage = internalConfig.monorepo?.versionMode === "independent" ? bumpedPackages?.map((pkg) => getIndependentTag({ name: pkg.name, version: pkg.newVersion || pkg.version })).join(", ") || "unknown" : newVersion || "unknown";
260
- const commitMessage = internalConfig.templates.commitMessage?.replaceAll("{{newVersion}}", versionForMessage) || `chore(release): bump version to ${versionForMessage}`;
261
- const noVerifyFlag = noVerify ? "--no-verify " : "";
262
- logger.debug(`No verify: ${noVerify}`);
263
- if (dryRun) {
264
- logger.info(`[dry-run] git commit ${noVerifyFlag}-m "${commitMessage}"`);
265
- } else {
266
- logger.debug(`Executing: git commit ${noVerifyFlag}-m "${commitMessage}"`);
267
- await execPromise(`git commit ${noVerifyFlag}-m "${commitMessage}"`, {
268
- logLevel,
269
- noStderr: true,
270
- noStdout: true,
271
- cwd: internalConfig.cwd
322
+ const { from, to } = await resolveTags({
323
+ config,
324
+ step: "bump",
325
+ pkg: packageBase,
326
+ newVersion: void 0
327
+ });
328
+ const commits = await getPackageCommits({
329
+ pkg: packageBase,
330
+ from,
331
+ to,
332
+ config,
333
+ changelog: false
334
+ });
335
+ foundPaths.add(matchPath);
336
+ const dependencies = getPackageDependencies({
337
+ packagePath: matchPath,
338
+ allPackageNames: new Set(readedPackages.map((p) => p.name)),
339
+ dependencyTypes: config.bump?.dependencyTypes
340
+ });
341
+ packages.set(packageBase.name, {
342
+ ...packageBase,
343
+ path: matchPath,
344
+ fromTag: from,
345
+ dependencies,
346
+ commits,
347
+ reason: commits.length > 0 ? "commits" : void 0,
348
+ dependencyChain: void 0,
349
+ newVersion: void 0
272
350
  });
273
- logger.success(`Committed: ${commitMessage}${noVerify ? " (--no-verify)" : ""}`);
274
- }
275
- const signTags = internalConfig.signTags ? "-s" : "";
276
- logger.debug(`Sign tags: ${internalConfig.signTags}`);
277
- const createdTags = [];
278
- if (internalConfig.monorepo?.versionMode === "independent" && bumpedPackages && bumpedPackages.length > 0 && internalConfig.release.gitTag) {
279
- logger.debug(`Creating ${bumpedPackages.length} independent package tags`);
280
- for (const pkg of bumpedPackages) {
281
- if (!pkg.newVersion) {
282
- continue;
283
- }
284
- const tagName = getIndependentTag({ version: pkg.newVersion, name: pkg.name });
285
- const tagMessage = internalConfig.templates?.tagMessage?.replaceAll("{{newVersion}}", pkg.newVersion) || tagName;
286
- if (dryRun) {
287
- logger.info(`[dry-run] git tag ${signTags} -a ${tagName} -m "${tagMessage}"`);
288
- } else {
289
- const cmd = `git tag ${signTags} -a ${tagName} -m "${tagMessage}"`;
290
- logger.debug(`Executing: ${cmd}`);
291
- try {
292
- await execPromise(cmd, {
293
- logLevel,
294
- noStderr: true,
295
- noStdout: true,
296
- cwd: internalConfig.cwd
297
- });
298
- logger.debug(`Tag created: ${tagName}`);
299
- } catch (error) {
300
- logger.error(`Failed to create tag ${tagName}:`, error);
301
- throw error;
302
- }
303
- }
304
- createdTags.push(tagName);
305
- }
306
- logger.success(`Created ${createdTags.length} tags for independent packages, ${createdTags.join(", ")}`);
307
- } else if (internalConfig.release.gitTag) {
308
- const tagName = internalConfig.templates.tagBody?.replaceAll("{{newVersion}}", newVersion);
309
- const tagMessage = internalConfig.templates?.tagMessage?.replaceAll("{{newVersion}}", newVersion) || tagName;
310
- if (dryRun) {
311
- logger.info(`[dry-run] git tag ${signTags} -a ${tagName} -m "${tagMessage}"`);
312
- } else {
313
- const cmd = `git tag ${signTags} -a ${tagName} -m "${tagMessage}"`;
314
- logger.debug(`Executing: ${cmd}`);
315
- try {
316
- await execPromise(cmd, {
317
- logLevel,
318
- noStderr: true,
319
- noStdout: true,
320
- cwd: internalConfig.cwd
321
- });
322
- logger.debug(`Tag created: ${tagName}`);
323
- } catch (error) {
324
- logger.error(`Failed to create tag ${tagName}:`, error);
325
- throw error;
326
- }
327
- }
328
- createdTags.push(tagName);
329
351
  }
330
- logger.debug("Created Tags:", createdTags.join(", "));
331
- logger.success("Commit and tag completed!");
332
- await executeHook("success:commit-and-tag", internalConfig, dryRun ?? false);
333
- return createdTags;
334
- } catch (error) {
335
- logger.error("Error committing and tagging:", error);
336
- await executeHook("error:commit-and-tag", internalConfig, dryRun ?? false);
337
- throw error;
338
- }
339
- }
340
- async function pushCommitAndTags({ config, dryRun, logLevel, cwd }) {
341
- logger.start("Start push changes and tags");
342
- const command = config.release.gitTag ? "git push --follow-tags" : "git push";
343
- if (dryRun) {
344
- logger.info(`[dry-run] ${command}`);
345
- } else {
346
- logger.debug(`Executing: ${command}`);
347
- await execPromise(command, { noStderr: true, noStdout: true, logLevel, cwd });
348
352
  }
349
- logger.success("Pushing changes and tags completed!");
350
- }
351
- function getFirstCommit(cwd) {
352
- const result = execSync(
353
- "git rev-list --max-parents=0 HEAD",
354
- {
355
- cwd,
356
- encoding: "utf8"
357
- }
358
- );
359
- return result.trim();
360
- }
361
- function getCurrentGitBranch(cwd) {
362
- const result = execSync("git rev-parse --abbrev-ref HEAD", {
363
- cwd,
364
- encoding: "utf8"
353
+ const packagesArray = Array.from(packages.values());
354
+ const packagesWithCommits = packagesArray.filter((p) => p.commits.length > 0);
355
+ const expandedPackages = expandPackagesToBumpWithDependents({
356
+ allPackages: packagesArray,
357
+ packagesWithCommits
365
358
  });
366
- return result.trim();
359
+ for (const pkg of expandedPackages) {
360
+ packages.set(pkg.name, pkg);
361
+ }
362
+ for (const pkg of Array.from(packages.values())) {
363
+ const releaseType = getPackageReleaseType({
364
+ pkg,
365
+ config,
366
+ force
367
+ });
368
+ const newVersion = releaseType ? getPackageNewVersion({
369
+ name: pkg.name,
370
+ currentVersion: pkg.version,
371
+ releaseType,
372
+ preid: config.bump.preid,
373
+ suffix
374
+ }) : void 0;
375
+ const graduating = releaseType && isGraduating(pkg.version, releaseType) || isChangedPreid(pkg.version, config.bump.preid);
376
+ packages.set(pkg.name, {
377
+ ...pkg,
378
+ newVersion,
379
+ reason: pkg.reason || releaseType && graduating && "graduation" || void 0
380
+ });
381
+ }
382
+ const packagesToBump = Array.from(packages.values()).filter((p) => p.reason || force);
383
+ if (packagesToBump.length === 0) {
384
+ logger.debug("No packages to bump");
385
+ return [];
386
+ }
387
+ return packagesToBump;
367
388
  }
368
- function getCurrentGitRef(cwd) {
369
- const branch = getCurrentGitBranch(cwd);
370
- return branch || "HEAD";
389
+ function isAllowedCommit({
390
+ commit,
391
+ type,
392
+ changelog
393
+ }) {
394
+ if (commit.type === "chore" && ["deps", "release"].includes(commit.scope) && !commit.isBreaking) {
395
+ return false;
396
+ }
397
+ if (typeof type === "object") {
398
+ return !!type.semver || changelog && !!type.title;
399
+ }
400
+ if (typeof type === "boolean") {
401
+ return type;
402
+ }
403
+ return false;
371
404
  }
372
-
373
- async function generateMarkDown({
374
- commits,
375
- config,
405
+ async function getPackageCommits({
406
+ pkg,
376
407
  from,
377
408
  to,
378
- isFirstCommit
409
+ config,
410
+ changelog
379
411
  }) {
380
- const typeGroups = groupBy(commits, "type");
381
- const markdown = [];
382
- const breakingChanges = [];
383
- const updatedConfig = {
412
+ logger.debug(`Analyzing commits for ${pkg.name} since ${from} to ${to}`);
413
+ const changelogConfig = {
384
414
  ...config,
385
415
  from,
386
416
  to
387
417
  };
388
- const versionTitle = updatedConfig.to;
389
- const changelogTitle = `${updatedConfig.from}...${updatedConfig.to}`;
390
- markdown.push("", `## ${changelogTitle}`, "");
391
- if (updatedConfig.repo && updatedConfig.from && versionTitle) {
392
- const formattedCompareLink = formatCompareChanges(versionTitle, {
393
- ...updatedConfig,
394
- from: isFirstCommit ? getFirstCommit(updatedConfig.cwd) : updatedConfig.from
395
- });
396
- markdown.push(formattedCompareLink);
418
+ const rawCommits = await getGitDiff(from, to, changelogConfig.cwd);
419
+ const allCommits = parseCommits(rawCommits, changelogConfig);
420
+ const hasBreakingChanges = allCommits.some((commit) => commit.isBreaking);
421
+ logger.debug(`Has breaking changes: ${hasBreakingChanges}`);
422
+ const rootPackage = readPackageJson(changelogConfig.cwd);
423
+ if (!rootPackage) {
424
+ throw new Error("Failed to read root package.json");
397
425
  }
398
- for (const type in updatedConfig.types) {
399
- const group = typeGroups[type];
400
- if (!group || group.length === 0) {
401
- continue;
402
- }
403
- if (typeof updatedConfig.types[type] === "boolean") {
404
- continue;
426
+ const commits = allCommits.filter((commit) => {
427
+ const type = changelogConfig?.types[commit.type];
428
+ if (!isAllowedCommit({ commit, type, changelog })) {
429
+ return false;
405
430
  }
406
- markdown.push("", `### ${updatedConfig.types[type]?.title}`, "");
407
- for (const commit of group.reverse()) {
408
- const line = formatCommit(commit, updatedConfig);
409
- markdown.push(line);
410
- if (commit.isBreaking) {
411
- breakingChanges.push(line);
412
- }
431
+ if (pkg.path === changelogConfig.cwd || pkg.name === rootPackage.name) {
432
+ return true;
413
433
  }
434
+ const packageRelativePath = relative(changelogConfig.cwd, pkg.path);
435
+ const scopeMatches = commit.scope === pkg.name;
436
+ const bodyContainsPath = commit.body.includes(packageRelativePath);
437
+ return scopeMatches || bodyContainsPath;
438
+ });
439
+ logger.debug(`Found ${commits.length} commit(s) for ${pkg.name} from ${from} to ${to}`);
440
+ if (commits.length > 0) {
441
+ logger.debug(`${pkg.name}: ${commits.length} commit(s) found`);
442
+ } else {
443
+ logger.debug(`${pkg.name}: No commits found`);
414
444
  }
415
- if (breakingChanges.length > 0) {
416
- markdown.push("", "#### \u26A0\uFE0F Breaking Changes", "", ...breakingChanges);
445
+ return commits;
446
+ }
447
+ function hasLernaJson(rootDir) {
448
+ const lernaJsonPath = join(rootDir, "lerna.json");
449
+ return existsSync(lernaJsonPath);
450
+ }
451
+
452
+ async function executeHook(hook, config, dryRun, params) {
453
+ const hookInput = config.hooks?.[hook];
454
+ if (!hookInput) {
455
+ logger.debug(`Hook ${hook} not found`);
456
+ return;
417
457
  }
418
- const _authors = /* @__PURE__ */ new Map();
419
- for (const commit of commits) {
420
- if (!commit.author) {
421
- continue;
422
- }
423
- const name = formatName(commit.author.name);
424
- if (!name || name.includes("[bot]")) {
425
- continue;
426
- }
427
- if (updatedConfig.excludeAuthors && updatedConfig.excludeAuthors.some(
428
- (v) => name.includes(v) || commit.author.email?.includes(v)
429
- )) {
430
- continue;
431
- }
432
- if (_authors.has(name)) {
433
- const entry = _authors.get(name);
434
- entry?.email.add(commit.author.email);
435
- } else {
436
- _authors.set(name, { email: /* @__PURE__ */ new Set([commit.author.email]), name });
437
- }
458
+ if (typeof hookInput === "function") {
459
+ logger.info(`Executing hook ${hook}`);
460
+ const result = await hookInput(config, dryRun, params);
461
+ if (result)
462
+ logger.debug(`Hook ${hook} returned: ${result}`);
463
+ logger.info(`Hook ${hook} executed`);
464
+ return result;
438
465
  }
439
- if (updatedConfig.repo?.provider === "github") {
440
- await Promise.all(
441
- [..._authors.keys()].map(async (authorName) => {
442
- const meta = _authors.get(authorName);
443
- if (!meta) {
466
+ if (typeof hookInput === "string") {
467
+ logger.info(`Executing hook ${hook}`);
468
+ const result = await execPromise(hookInput, {
469
+ logLevel: config.logLevel,
470
+ cwd: config.cwd,
471
+ noStderr: true,
472
+ noStdout: true
473
+ });
474
+ if (result)
475
+ logger.debug(`Hook ${hook} returned: ${result}`);
476
+ logger.info(`Hook ${hook} executed`);
477
+ return result;
478
+ }
479
+ }
480
+ function isInCI() {
481
+ return Boolean(
482
+ 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"
483
+ );
484
+ }
485
+ function getCIName() {
486
+ if (process.env.GITHUB_ACTIONS === "true")
487
+ return "GitHub Actions";
488
+ if (process.env.GITLAB_CI === "true")
489
+ return "GitLab CI";
490
+ if (process.env.CIRCLECI === "true")
491
+ return "CircleCI";
492
+ if (process.env.TRAVIS === "true")
493
+ return "Travis CI";
494
+ if (process.env.JENKINS_HOME || process.env.JENKINS_URL)
495
+ return "Jenkins";
496
+ if (process.env.TF_BUILD === "True")
497
+ return "Azure Pipelines";
498
+ if (process.env.TEAMCITY_VERSION)
499
+ return "TeamCity";
500
+ if (process.env.BITBUCKET_BUILD_NUMBER)
501
+ return "Bitbucket Pipelines";
502
+ if (process.env.DRONE === "true")
503
+ return "Drone";
504
+ if (process.env.APPVEYOR)
505
+ return "AppVeyor";
506
+ if (process.env.BUILDKITE === "true")
507
+ return "Buildkite";
508
+ if (process.env.CODEBUILD_BUILD_ID)
509
+ return "AWS CodeBuild";
510
+ if (process.env.NETLIFY === "true")
511
+ return "Netlify";
512
+ if (process.env.VERCEL === "1")
513
+ return "Vercel";
514
+ if (process.env.HEROKU_TEST_RUN_ID)
515
+ return "Heroku CI";
516
+ if (process.env.BUDDY === "true")
517
+ return "Buddy";
518
+ if (process.env.SEMAPHORE === "true")
519
+ return "Semaphore";
520
+ if (process.env.CF_BUILD_ID)
521
+ return "Codefresh";
522
+ if (process.env.bamboo_buildKey)
523
+ return "Bamboo";
524
+ if (process.env.BUILD_ID && process.env.PROJECT_ID)
525
+ return "Google Cloud Build";
526
+ if (process.env.SCREWDRIVER === "true")
527
+ return "Screwdriver";
528
+ if (process.env.STRIDER === "true")
529
+ return "Strider";
530
+ if (process.env.CI === "true")
531
+ return "Unknown CI";
532
+ return null;
533
+ }
534
+ async function executeFormatCmd({
535
+ config,
536
+ dryRun
537
+ }) {
538
+ if (config.changelog?.formatCmd) {
539
+ logger.info("Running format command");
540
+ logger.debug(`Running format command: ${config.changelog.formatCmd}`);
541
+ try {
542
+ if (!dryRun) {
543
+ await execPromise(config.changelog.formatCmd, {
544
+ noStderr: true,
545
+ noStdout: true,
546
+ logLevel: config.logLevel,
547
+ cwd: config.cwd
548
+ });
549
+ logger.info("Format completed");
550
+ } else {
551
+ logger.log("[dry-run] exec format command: ", config.changelog.formatCmd);
552
+ }
553
+ } catch (error) {
554
+ throw new Error(`Format command failed: ${error}`);
555
+ }
556
+ } else {
557
+ logger.debug("No format command specified");
558
+ }
559
+ }
560
+ async function executeBuildCmd({
561
+ config,
562
+ dryRun
563
+ }) {
564
+ if (config.publish?.buildCmd) {
565
+ logger.info("Running build command");
566
+ logger.debug(`Running build command: ${config.publish.buildCmd}`);
567
+ if (!dryRun) {
568
+ await execPromise(config.publish.buildCmd, {
569
+ noStderr: true,
570
+ noStdout: true,
571
+ logLevel: config.logLevel,
572
+ cwd: config.cwd
573
+ });
574
+ logger.info("Build completed");
575
+ } else {
576
+ logger.info("[dry-run] exec build command: ", config.publish.buildCmd);
577
+ }
578
+ } else {
579
+ logger.debug("No build command specified");
580
+ }
581
+ }
582
+ function isBumpedPackage(pkg) {
583
+ return "oldVersion" in pkg && !!pkg.oldVersion;
584
+ }
585
+ async function getPackagesOrBumpedPackages({
586
+ config,
587
+ bumpResult,
588
+ suffix,
589
+ force
590
+ }) {
591
+ if (bumpResult?.bumpedPackages && bumpResult.bumpedPackages.length > 0) {
592
+ return bumpResult.bumpedPackages;
593
+ }
594
+ return await getPackages({
595
+ config,
596
+ patterns: config.monorepo?.packages,
597
+ suffix,
598
+ force
599
+ });
600
+ }
601
+
602
+ function getGitStatus(cwd) {
603
+ return execSync("git status --porcelain", {
604
+ cwd,
605
+ encoding: "utf8"
606
+ }).trim();
607
+ }
608
+ function checkGitStatusIfDirty() {
609
+ logger.debug("Checking git status");
610
+ const dirty = getGitStatus();
611
+ if (dirty) {
612
+ logger.debug("git status:", `
613
+ ${dirty.trim().split("\n").map((line) => line.trim()).join("\n")}`);
614
+ const error = `Git status is dirty!
615
+
616
+ Please commit or stash your changes before bumping or use --no-clean flag.
617
+
618
+ Unstaged files:
619
+
620
+ ${dirty.trim()}`;
621
+ throw new Error(error);
622
+ }
623
+ }
624
+ async function fetchGitTags(cwd) {
625
+ logger.debug("Fetching git tags from remote");
626
+ try {
627
+ await execPromise("git fetch --tags", { cwd, noStderr: true, noStdout: true, noSuccess: true });
628
+ logger.debug("Git tags fetched successfully");
629
+ } catch (error) {
630
+ logger.fail("Failed to fetch some git tags from remote (tags might already exist locally)", error);
631
+ logger.info("Continuing with local tags");
632
+ }
633
+ }
634
+ function detectGitProvider(cwd = process.cwd()) {
635
+ try {
636
+ const remoteUrl = execSync("git remote get-url origin", {
637
+ cwd,
638
+ encoding: "utf8"
639
+ }).trim();
640
+ if (remoteUrl.includes("github.com")) {
641
+ return "github";
642
+ }
643
+ if (remoteUrl.includes("gitlab.com") || remoteUrl.includes("gitlab")) {
644
+ return "gitlab";
645
+ }
646
+ return null;
647
+ } catch {
648
+ return null;
649
+ }
650
+ }
651
+ function parseGitRemoteUrl(remoteUrl) {
652
+ const sshRegex = /git@[\w.-]+:([\w.-]+)\/([\w.-]+?)(?:\.git)?$/;
653
+ const httpsRegex = /https?:\/\/[\w.-]+\/([\w.-]+)\/([\w.-]+?)(?:\.git)?$/;
654
+ const sshMatch = remoteUrl.match(sshRegex);
655
+ if (sshMatch) {
656
+ return {
657
+ owner: sshMatch[1],
658
+ repo: sshMatch[2]
659
+ };
660
+ }
661
+ const httpsMatch = remoteUrl.match(httpsRegex);
662
+ if (httpsMatch) {
663
+ return {
664
+ owner: httpsMatch[1],
665
+ repo: httpsMatch[2]
666
+ };
667
+ }
668
+ return null;
669
+ }
670
+ async function createCommitAndTags({
671
+ config,
672
+ noVerify,
673
+ bumpedPackages,
674
+ newVersion,
675
+ dryRun,
676
+ logLevel
677
+ }) {
678
+ const internalConfig = config || await loadRelizyConfig();
679
+ try {
680
+ await executeHook("before:commit-and-tag", internalConfig, dryRun ?? false);
681
+ const filePatternsToAdd = [
682
+ "package.json",
683
+ "lerna.json",
684
+ "CHANGELOG.md",
685
+ "**/CHANGELOG.md",
686
+ "**/package.json"
687
+ ];
688
+ logger.start("Start commit and tag");
689
+ logger.debug("Adding files to git staging area...");
690
+ for (const pattern of filePatternsToAdd) {
691
+ if (pattern === "lerna.json" && !hasLernaJson(internalConfig.cwd)) {
692
+ logger.verbose(`Skipping lerna.json as it doesn't exist`);
693
+ continue;
694
+ }
695
+ if ((pattern === "lerna.json" || pattern === "CHANGELOG.md") && !existsSync(join(internalConfig.cwd, pattern))) {
696
+ logger.verbose(`Skipping ${pattern} as it doesn't exist`);
697
+ continue;
698
+ }
699
+ if (dryRun) {
700
+ logger.info(`[dry-run] git add ${pattern}`);
701
+ continue;
702
+ }
703
+ try {
704
+ logger.debug(`git add ${pattern}`);
705
+ execSync(`git add ${pattern}`);
706
+ } catch {
707
+ }
708
+ }
709
+ const rootPackage = readPackageJson(internalConfig.cwd);
710
+ if (!rootPackage) {
711
+ throw new Error("Failed to read root package.json");
712
+ }
713
+ newVersion = newVersion || rootPackage.version;
714
+ const versionForMessage = internalConfig.monorepo?.versionMode === "independent" ? bumpedPackages?.map((pkg) => getIndependentTag({ name: pkg.name, version: pkg.newVersion || pkg.version })).join(", ") || "unknown" : newVersion || "unknown";
715
+ const commitMessage = internalConfig.templates.commitMessage?.replaceAll("{{newVersion}}", versionForMessage) || `chore(release): bump version to ${versionForMessage}`;
716
+ const noVerifyFlag = noVerify ? "--no-verify " : "";
717
+ logger.debug(`No verify: ${noVerify}`);
718
+ if (dryRun) {
719
+ logger.info(`[dry-run] git commit ${noVerifyFlag}-m "${commitMessage}"`);
720
+ } else {
721
+ logger.debug(`Executing: git commit ${noVerifyFlag}-m "${commitMessage}"`);
722
+ await execPromise(`git commit ${noVerifyFlag}-m "${commitMessage}"`, {
723
+ logLevel,
724
+ noStderr: true,
725
+ noStdout: true,
726
+ cwd: internalConfig.cwd
727
+ });
728
+ logger.success(`Committed: ${commitMessage}${noVerify ? " (--no-verify)" : ""}`);
729
+ }
730
+ const signTags = internalConfig.signTags ? "-s" : "";
731
+ logger.debug(`Sign tags: ${internalConfig.signTags}`);
732
+ const createdTags = [];
733
+ if (internalConfig.monorepo?.versionMode === "independent" && bumpedPackages && bumpedPackages.length > 0 && internalConfig.release.gitTag) {
734
+ logger.debug(`Creating ${bumpedPackages.length} independent package tags`);
735
+ for (const pkg of bumpedPackages) {
736
+ if (!pkg.newVersion) {
737
+ continue;
738
+ }
739
+ const tagName = getIndependentTag({ version: pkg.newVersion, name: pkg.name });
740
+ const tagMessage = internalConfig.templates?.tagMessage?.replaceAll("{{newVersion}}", pkg.newVersion) || tagName;
741
+ if (dryRun) {
742
+ logger.info(`[dry-run] git tag ${signTags} -a ${tagName} -m "${tagMessage}"`);
743
+ } else {
744
+ const cmd = `git tag ${signTags} -a ${tagName} -m "${tagMessage}"`;
745
+ logger.debug(`Executing: ${cmd}`);
746
+ try {
747
+ await execPromise(cmd, {
748
+ logLevel,
749
+ noStderr: true,
750
+ noStdout: true,
751
+ cwd: internalConfig.cwd
752
+ });
753
+ logger.debug(`Tag created: ${tagName}`);
754
+ } catch (error) {
755
+ logger.error(`Failed to create tag ${tagName}:`, error);
756
+ throw error;
757
+ }
758
+ }
759
+ createdTags.push(tagName);
760
+ }
761
+ logger.success(`Created ${createdTags.length} tags for independent packages, ${createdTags.join(", ")}`);
762
+ } else if (internalConfig.release.gitTag) {
763
+ const tagName = internalConfig.templates.tagBody?.replaceAll("{{newVersion}}", newVersion);
764
+ const tagMessage = internalConfig.templates?.tagMessage?.replaceAll("{{newVersion}}", newVersion) || tagName;
765
+ if (dryRun) {
766
+ logger.info(`[dry-run] git tag ${signTags} -a ${tagName} -m "${tagMessage}"`);
767
+ } else {
768
+ const cmd = `git tag ${signTags} -a ${tagName} -m "${tagMessage}"`;
769
+ logger.debug(`Executing: ${cmd}`);
770
+ try {
771
+ await execPromise(cmd, {
772
+ logLevel,
773
+ noStderr: true,
774
+ noStdout: true,
775
+ cwd: internalConfig.cwd
776
+ });
777
+ logger.debug(`Tag created: ${tagName}`);
778
+ } catch (error) {
779
+ logger.error(`Failed to create tag ${tagName}:`, error);
780
+ throw error;
781
+ }
782
+ }
783
+ createdTags.push(tagName);
784
+ }
785
+ logger.debug("Created Tags:", createdTags.join(", "));
786
+ logger.success("Commit and tag completed!");
787
+ await executeHook("success:commit-and-tag", internalConfig, dryRun ?? false);
788
+ return createdTags;
789
+ } catch (error) {
790
+ logger.error("Error committing and tagging:", error);
791
+ await executeHook("error:commit-and-tag", internalConfig, dryRun ?? false);
792
+ throw error;
793
+ }
794
+ }
795
+ async function pushCommitAndTags({ config, dryRun, logLevel, cwd }) {
796
+ logger.start("Start push changes and tags");
797
+ const command = config.release.gitTag ? "git push --follow-tags" : "git push";
798
+ if (dryRun) {
799
+ logger.info(`[dry-run] ${command}`);
800
+ } else {
801
+ logger.debug(`Executing: ${command}`);
802
+ await execPromise(command, { noStderr: true, noStdout: true, logLevel, cwd });
803
+ }
804
+ logger.success("Pushing changes and tags completed!");
805
+ }
806
+ function getFirstCommit(cwd) {
807
+ const result = execSync(
808
+ "git rev-list --max-parents=0 HEAD",
809
+ {
810
+ cwd,
811
+ encoding: "utf8"
812
+ }
813
+ );
814
+ return result.trim();
815
+ }
816
+ function getCurrentGitBranch(cwd) {
817
+ const result = execSync("git rev-parse --abbrev-ref HEAD", {
818
+ cwd,
819
+ encoding: "utf8"
820
+ });
821
+ return result.trim();
822
+ }
823
+ function getCurrentGitRef(cwd) {
824
+ const branch = getCurrentGitBranch(cwd);
825
+ return branch || "HEAD";
826
+ }
827
+
828
+ async function generateMarkDown({
829
+ commits,
830
+ config,
831
+ from,
832
+ to,
833
+ isFirstCommit
834
+ }) {
835
+ const typeGroups = groupBy(commits, "type");
836
+ const markdown = [];
837
+ const breakingChanges = [];
838
+ const updatedConfig = {
839
+ ...config,
840
+ from,
841
+ to
842
+ };
843
+ const versionTitle = updatedConfig.to;
844
+ const changelogTitle = `${updatedConfig.from}...${updatedConfig.to}`;
845
+ markdown.push("", `## ${changelogTitle}`, "");
846
+ if (updatedConfig.repo && updatedConfig.from && versionTitle) {
847
+ const formattedCompareLink = formatCompareChanges(versionTitle, {
848
+ ...updatedConfig,
849
+ from: isFirstCommit ? getFirstCommit(updatedConfig.cwd) : updatedConfig.from
850
+ });
851
+ markdown.push(formattedCompareLink);
852
+ }
853
+ for (const type in updatedConfig.types) {
854
+ const group = typeGroups[type];
855
+ if (!group || group.length === 0) {
856
+ continue;
857
+ }
858
+ if (typeof updatedConfig.types[type] === "boolean") {
859
+ continue;
860
+ }
861
+ markdown.push("", `### ${updatedConfig.types[type]?.title}`, "");
862
+ for (const commit of group.reverse()) {
863
+ const line = formatCommit(commit, updatedConfig);
864
+ markdown.push(line);
865
+ if (commit.isBreaking) {
866
+ breakingChanges.push(line);
867
+ }
868
+ }
869
+ }
870
+ if (breakingChanges.length > 0) {
871
+ markdown.push("", "#### \u26A0\uFE0F Breaking Changes", "", ...breakingChanges);
872
+ }
873
+ const _authors = /* @__PURE__ */ new Map();
874
+ for (const commit of commits) {
875
+ if (!commit.author) {
876
+ continue;
877
+ }
878
+ const name = formatName(commit.author.name);
879
+ if (!name || name.includes("[bot]")) {
880
+ continue;
881
+ }
882
+ if (updatedConfig.excludeAuthors && updatedConfig.excludeAuthors.some(
883
+ (v) => name.includes(v) || commit.author.email?.includes(v)
884
+ )) {
885
+ continue;
886
+ }
887
+ if (_authors.has(name)) {
888
+ const entry = _authors.get(name);
889
+ entry?.email.add(commit.author.email);
890
+ } else {
891
+ _authors.set(name, { email: /* @__PURE__ */ new Set([commit.author.email]), name });
892
+ }
893
+ }
894
+ if (updatedConfig.repo?.provider === "github") {
895
+ await Promise.all(
896
+ [..._authors.keys()].map(async (authorName) => {
897
+ const meta = _authors.get(authorName);
898
+ if (!meta) {
444
899
  return;
445
900
  }
446
901
  for (const data of [...meta.email, meta.name]) {
@@ -538,9 +993,8 @@ async function generateChangelog({
538
993
  if (isFirstCommit) {
539
994
  fromTag = config.monorepo?.versionMode === "independent" ? getIndependentTag({ version: "0.0.0", name: pkg.name }) : config.templates.tagBody.replace("{{newVersion}}", "0.0.0");
540
995
  }
541
- newVersion = newVersion || pkg.newVersion;
542
996
  let toTag = config.to;
543
- if (!toTag && newVersion) {
997
+ if (!toTag) {
544
998
  toTag = config.monorepo?.versionMode === "independent" ? getIndependentTag({ version: newVersion, name: pkg.name }) : config.templates.tagBody.replace("{{newVersion}}", newVersion);
545
999
  }
546
1000
  if (!toTag) {
@@ -658,7 +1112,8 @@ function getDefaultConfig() {
658
1112
  },
659
1113
  publish: {
660
1114
  private: false,
661
- args: []
1115
+ args: [],
1116
+ safetyCheck: false
662
1117
  },
663
1118
  tokens: {
664
1119
  gitlab: process$1.env.RELIZY_GITLAB_TOKEN || process$1.env.GITLAB_TOKEN || process$1.env.GITLAB_API_TOKEN || process$1.env.CI_JOB_TOKEN,
@@ -672,7 +1127,8 @@ function getDefaultConfig() {
672
1127
  push: true,
673
1128
  clean: true,
674
1129
  providerRelease: true,
675
- noVerify: false
1130
+ noVerify: false,
1131
+ gitTag: true
676
1132
  },
677
1133
  logLevel: "default",
678
1134
  safetyCheck: true
@@ -716,137 +1172,19 @@ async function loadRelizyConfig(options) {
716
1172
  });
717
1173
  if (!results._configFile) {
718
1174
  logger.debug(`No config file found with name "${configName}" - using standalone mode`);
719
- if (options?.configName) {
720
- logger.error(`No config file found with name "${configName}"`);
721
- process$1.exit(1);
722
- }
723
- }
724
- setupLogger(options?.overrides?.logLevel || results.config.logLevel);
725
- logger.verbose("User config:", formatJson(results.config.changelog));
726
- const resolvedConfig = await resolveConfig(results.config, cwd);
727
- logger.debug("Resolved config:", formatJson(resolvedConfig));
728
- return resolvedConfig;
729
- }
730
- function defineConfig(config) {
731
- return config;
732
- }
733
-
734
- function getPackageDependencies({
735
- packagePath,
736
- allPackageNames,
737
- dependencyTypes
738
- }) {
739
- const packageJsonPath = join(packagePath, "package.json");
740
- if (!existsSync(packageJsonPath)) {
741
- return [];
742
- }
743
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
744
- const deps = [];
745
- const allDeps = {
746
- ...dependencyTypes?.includes("dependencies") ? packageJson.dependencies : {},
747
- ...dependencyTypes?.includes("peerDependencies") ? packageJson.peerDependencies : {},
748
- ...dependencyTypes?.includes("devDependencies") ? packageJson.devDependencies : {}
749
- };
750
- for (const depName of Object.keys(allDeps)) {
751
- if (allPackageNames.has(depName)) {
752
- deps.push(depName);
753
- }
754
- }
755
- return deps;
756
- }
757
- function getDependentsOf({
758
- allPackages,
759
- packageName
760
- }) {
761
- return allPackages.filter(
762
- (pkg) => pkg.dependencies.includes(packageName)
763
- );
764
- }
765
- function expandPackagesToBumpWithDependents({
766
- allPackages,
767
- packagesWithCommits
768
- }) {
769
- const result = /* @__PURE__ */ new Map();
770
- logger.debug(`Expanding packages to bump: ${packagesWithCommits.length} packages with commits, ${allPackages.length} total packages`);
771
- for (const pkg of packagesWithCommits) {
772
- const packageToBump = {
773
- ...pkg,
774
- reason: "commits"
775
- };
776
- result.set(pkg.name, packageToBump);
777
- }
778
- const toProcess = [...packagesWithCommits.map((p) => p.name)];
779
- const processed = /* @__PURE__ */ new Set();
780
- while (toProcess.length > 0) {
781
- const currentPkgName = toProcess.shift();
782
- if (!currentPkgName || processed.has(currentPkgName)) {
783
- continue;
784
- }
785
- processed.add(currentPkgName);
786
- const dependents = getDependentsOf({
787
- packageName: currentPkgName,
788
- allPackages
789
- });
790
- for (const dependent of dependents) {
791
- if (!result.has(dependent.name)) {
792
- const currentChain = result.get(currentPkgName)?.dependencyChain || [];
793
- const chain = [...currentChain, currentPkgName];
794
- const packageBase = allPackages.find((p) => p.name === dependent.name);
795
- if (packageBase) {
796
- const packageToBump = {
797
- ...packageBase,
798
- reason: "dependency",
799
- dependencyChain: chain
800
- };
801
- result.set(dependent.name, packageToBump);
802
- toProcess.push(dependent.name);
803
- logger.debug(`${dependent.name} will be bumped (depends on ${chain.join(" \u2192 ")})`);
804
- }
805
- }
806
- }
807
- }
808
- return Array.from(result.values());
809
- }
810
- function topologicalSort(packages) {
811
- const sorted = [];
812
- const visited = /* @__PURE__ */ new Set();
813
- const visiting = /* @__PURE__ */ new Set();
814
- const packageMap = /* @__PURE__ */ new Map();
815
- for (const pkg of packages) {
816
- packageMap.set(pkg.name, pkg);
817
- }
818
- function visit(pkgName, path = []) {
819
- logger.debug(`Visiting ${pkgName}, path: ${path.join(" \u2192 ")}, visiting: ${Array.from(visiting).join(", ")}`);
820
- if (visiting.has(pkgName)) {
821
- const cycle = [...path, pkgName];
822
- logger.warn(`Circular dependency detected: ${cycle.join(" \u2192 ")}`);
823
- return;
824
- }
825
- if (visited.has(pkgName)) {
826
- logger.debug(`${pkgName} already visited globally, skipping`);
827
- return;
828
- }
829
- visiting.add(pkgName);
830
- logger.debug(`Added ${pkgName} to visiting set`);
831
- const pkg = packageMap.get(pkgName);
832
- if (!pkg) {
833
- logger.debug(`Package ${pkgName} not found in packageMap`);
834
- visiting.delete(pkgName);
835
- return;
836
- }
837
- logger.debug(`${pkgName} has dependencies: ${pkg.dependencies.join(", ")}`);
838
- for (const depName of pkg.dependencies) {
839
- visit(depName, [...path, pkgName]);
840
- }
841
- visiting.delete(pkgName);
842
- visited.add(pkgName);
843
- sorted.push(pkg);
844
- logger.debug(`Finished visiting ${pkgName}`);
845
- }
846
- for (const pkg of packages) {
847
- visit(pkg.name);
1175
+ if (options?.configName) {
1176
+ logger.error(`No config file found with name "${configName}"`);
1177
+ process$1.exit(1);
1178
+ }
848
1179
  }
849
- return sorted;
1180
+ setupLogger(options?.overrides?.logLevel || results.config.logLevel);
1181
+ logger.verbose("User config:", formatJson(results.config.changelog));
1182
+ const resolvedConfig = await resolveConfig(results.config, cwd);
1183
+ logger.debug("Resolved config:", formatJson(resolvedConfig));
1184
+ return resolvedConfig;
1185
+ }
1186
+ function defineConfig(config) {
1187
+ return config;
850
1188
  }
851
1189
 
852
1190
  async function githubIndependentMode({
@@ -864,10 +1202,10 @@ async function githubIndependentMode({
864
1202
  if (!config.tokens.github && !config.repo?.token) {
865
1203
  throw new Error("No GitHub token specified. Set GITHUB_TOKEN or GH_TOKEN environment variable.");
866
1204
  }
867
- const packages = bumpResult?.bumped && bumpResult?.bumpedPackages || await getPackages({
868
- suffix,
869
- patterns: config.monorepo?.packages,
1205
+ const packages = await getPackagesOrBumpedPackages({
870
1206
  config,
1207
+ bumpResult,
1208
+ suffix,
871
1209
  force
872
1210
  });
873
1211
  logger.info(`Creating ${packages.length} GitHub release(s)`);
@@ -941,12 +1279,13 @@ async function githubUnified({
941
1279
  if (!config.tokens.github && !config.repo?.token) {
942
1280
  throw new Error("No GitHub token specified. Set GITHUB_TOKEN or GH_TOKEN environment variable.");
943
1281
  }
944
- const to = config.to || config.templates.tagBody.replace("{{newVersion}}", bumpResult?.bumped && bumpResult.newVersion || rootPackage.version);
1282
+ const newVersion = bumpResult?.newVersion || rootPackage.version;
1283
+ const to = config.to || config.templates.tagBody.replace("{{newVersion}}", newVersion);
945
1284
  const changelog = await generateChangelog({
946
1285
  pkg: rootPackage,
947
1286
  config,
948
1287
  dryRun,
949
- newVersion: bumpResult?.newVersion || rootPackage.version
1288
+ newVersion
950
1289
  });
951
1290
  const releaseBody = changelog.split("\n").slice(2).join("\n");
952
1291
  const release = {
@@ -1006,10 +1345,14 @@ async function github(options) {
1006
1345
  });
1007
1346
  }
1008
1347
  const rootPackageBase = readPackageJson(config.cwd);
1348
+ if (!rootPackageBase) {
1349
+ throw new Error("Failed to read root package.json");
1350
+ }
1351
+ const newVersion = options.bumpResult?.newVersion || rootPackageBase.version;
1009
1352
  const { from, to } = await resolveTags({
1010
1353
  config,
1011
1354
  step: "provider-release",
1012
- newVersion: options.bumpResult?.newVersion || rootPackageBase.version,
1355
+ newVersion,
1013
1356
  pkg: rootPackageBase
1014
1357
  });
1015
1358
  const rootPackage = options.bumpResult?.rootPackage || await getRootPackage({
@@ -1101,9 +1444,9 @@ async function gitlabIndependentMode({
1101
1444
  force
1102
1445
  }) {
1103
1446
  logger.debug(`GitLab token: ${config.tokens.gitlab || config.repo?.token ? "\u2713 provided" : "\u2717 missing"}`);
1104
- const packages = bumpResult?.bumped && bumpResult?.bumpedPackages || await getPackages({
1105
- patterns: config.monorepo?.packages,
1447
+ const packages = await getPackagesOrBumpedPackages({
1106
1448
  config,
1449
+ bumpResult,
1107
1450
  suffix,
1108
1451
  force
1109
1452
  });
@@ -1174,12 +1517,13 @@ async function gitlabUnified({
1174
1517
  bumpResult
1175
1518
  }) {
1176
1519
  logger.debug(`GitLab token: ${config.tokens.gitlab || config.repo?.token ? "\u2713 provided" : "\u2717 missing"}`);
1177
- const to = config.templates.tagBody.replace("{{newVersion}}", rootPackage.newVersion || rootPackage.version);
1520
+ const newVersion = bumpResult?.newVersion || rootPackage.newVersion || rootPackage.version;
1521
+ const to = config.templates.tagBody.replace("{{newVersion}}", newVersion);
1178
1522
  const changelog = await generateChangelog({
1179
1523
  pkg: rootPackage,
1180
1524
  config,
1181
1525
  dryRun,
1182
- newVersion: bumpResult?.newVersion || rootPackage.version
1526
+ newVersion
1183
1527
  });
1184
1528
  const releaseBody = changelog.split("\n").slice(2).join("\n");
1185
1529
  logger.debug("Getting current branch...");
@@ -1216,7 +1560,7 @@ async function gitlabUnified({
1216
1560
  name: to,
1217
1561
  tag: to,
1218
1562
  version: to,
1219
- prerelease: isPrerelease(rootPackage.version)
1563
+ prerelease: isPrerelease(newVersion)
1220
1564
  }];
1221
1565
  }
1222
1566
  async function gitlab(options = {}) {
@@ -1245,10 +1589,14 @@ async function gitlab(options = {}) {
1245
1589
  });
1246
1590
  }
1247
1591
  const rootPackageBase = readPackageJson(config.cwd);
1592
+ if (!rootPackageBase) {
1593
+ throw new Error("Failed to read root package.json");
1594
+ }
1595
+ const newVersion = options.bumpResult?.newVersion || rootPackageBase.version;
1248
1596
  const { from, to } = await resolveTags({
1249
1597
  config,
1250
1598
  step: "provider-release",
1251
- newVersion: options.bumpResult?.newVersion || rootPackageBase.version,
1599
+ newVersion,
1252
1600
  pkg: rootPackageBase
1253
1601
  });
1254
1602
  const rootPackage = options.bumpResult?.rootPackage || await getRootPackage({
@@ -1259,7 +1607,7 @@ async function gitlab(options = {}) {
1259
1607
  from,
1260
1608
  to
1261
1609
  });
1262
- logger.debug(`Root package: ${getIndependentTag({ name: rootPackage.name, version: rootPackage.newVersion || rootPackage.version })}`);
1610
+ logger.debug(`Root package: ${getIndependentTag({ name: rootPackage.name, version: newVersion })}`);
1263
1611
  return await gitlabUnified({
1264
1612
  config,
1265
1613
  dryRun,
@@ -1272,6 +1620,12 @@ async function gitlab(options = {}) {
1272
1620
  }
1273
1621
  }
1274
1622
 
1623
+ function isGraduatingToStableBetweenVersion(version, newVersion) {
1624
+ const isSameBase = semver.major(version) === semver.major(newVersion) && semver.minor(version) === semver.minor(newVersion) && semver.patch(version) === semver.patch(newVersion);
1625
+ const fromPrerelease = semver.prerelease(version) !== null;
1626
+ const toStable = semver.prerelease(newVersion) === null;
1627
+ return isSameBase && fromPrerelease && toStable;
1628
+ }
1275
1629
  function determineSemverChange(commits, types) {
1276
1630
  let [hasMajor, hasMinor, hasPatch] = [false, false, false];
1277
1631
  for (const commit of commits) {
@@ -1402,26 +1756,27 @@ function determineReleaseType({
1402
1756
  }
1403
1757
  return handleExplicitReleaseType({ releaseType, currentVersion });
1404
1758
  }
1405
- function writeVersion(pkgPath, version, dryRun = false) {
1759
+ function writeVersion(pkgPath, newVersion, dryRun = false) {
1406
1760
  const packageJsonPath = join(pkgPath, "package.json");
1407
1761
  try {
1408
- logger.debug(`Writing ${version} to ${pkgPath}`);
1762
+ logger.debug(`Writing ${newVersion} to ${pkgPath}`);
1409
1763
  const content = readFileSync(packageJsonPath, "utf8");
1410
1764
  const packageJson = JSON.parse(content);
1411
1765
  const oldVersion = packageJson.version;
1412
- packageJson.version = version;
1766
+ packageJson.version = newVersion;
1413
1767
  if (dryRun) {
1414
- logger.info(`[dry-run] Updated ${packageJson.name}: ${oldVersion} \u2192 ${version}`);
1768
+ logger.info(`[dry-run] Updated ${packageJson.name}: ${oldVersion} \u2192 ${newVersion}`);
1415
1769
  return;
1416
1770
  }
1417
1771
  writeFileSync(packageJsonPath, `${formatJson(packageJson)}
1418
1772
  `, "utf8");
1419
- logger.info(`Updated ${packageJson.name}: ${oldVersion} \u2192 ${version}`);
1773
+ logger.info(`Updated ${packageJson.name}: ${oldVersion} \u2192 ${newVersion}`);
1420
1774
  } catch (error) {
1421
1775
  throw new Error(`Unable to write version to ${packageJsonPath}: ${error}`);
1422
1776
  }
1423
1777
  }
1424
1778
  function getPackageNewVersion({
1779
+ name,
1425
1780
  currentVersion,
1426
1781
  releaseType,
1427
1782
  preid,
@@ -1429,7 +1784,7 @@ function getPackageNewVersion({
1429
1784
  }) {
1430
1785
  let newVersion = semver.inc(currentVersion, releaseType, preid);
1431
1786
  if (!newVersion) {
1432
- throw new Error(`Unable to bump version "${currentVersion}" with release type "${releaseType}"
1787
+ throw new Error(`Unable to bump "${name}" version "${currentVersion}" with release type "${releaseType}"
1433
1788
 
1434
1789
  You should use an explicit release type (use flag: --major, --minor, --patch, --premajor, --preminor, --prepatch, --prerelease)`);
1435
1790
  }
@@ -1438,13 +1793,13 @@ You should use an explicit release type (use flag: --major, --minor, --patch, --
1438
1793
  }
1439
1794
  const isValidVersion = semver.gt(newVersion, currentVersion);
1440
1795
  if (!isValidVersion) {
1441
- throw new Error(`Unable to bump version "${currentVersion}" to "${newVersion}", new version is not greater than current version`);
1796
+ throw new Error(`Unable to bump "${name}" version "${currentVersion}" to "${newVersion}", new version is not greater than current version`);
1442
1797
  }
1443
1798
  if (isGraduating(currentVersion, releaseType)) {
1444
- logger.info(`Graduating from prerelease ${currentVersion} to stable ${newVersion}`);
1799
+ logger.info(`Graduating "${name}" from prerelease ${currentVersion} to stable ${newVersion}`);
1445
1800
  }
1446
1801
  if (isChangedPreid(currentVersion, preid)) {
1447
- logger.debug(`Graduating from ${getPreid(currentVersion)} to ${preid}`);
1802
+ logger.debug(`Graduating "${name}" from ${getPreid(currentVersion)} to ${preid}`);
1448
1803
  }
1449
1804
  return newVersion;
1450
1805
  }
@@ -1668,14 +2023,12 @@ async function confirmBump({
1668
2023
  });
1669
2024
  if (versionMode === "unified") {
1670
2025
  if (!newVersion) {
1671
- logger.error("Cannot confirm bump in unified mode without a new version");
1672
- process.exit(1);
2026
+ throw new Error("Cannot confirm bump in unified mode without a new version");
1673
2027
  }
1674
2028
  displayUnifiedModePackages({ packages, newVersion, force });
1675
2029
  } else if (versionMode === "selective") {
1676
2030
  if (!newVersion) {
1677
- logger.error("Cannot confirm bump in selective mode without a new version");
1678
- process.exit(1);
2031
+ throw new Error("Cannot confirm bump in selective mode without a new version");
1679
2032
  }
1680
2033
  displaySelectiveModePackages({ packages, newVersion, force });
1681
2034
  } else if (versionMode === "independent") {
@@ -1878,12 +2231,13 @@ async function resolveTags({
1878
2231
  const logLevel = config.logLevel;
1879
2232
  logger.debug(`[${versionMode}](${step}) Resolving tags`);
1880
2233
  const releaseType = config.bump.type;
2234
+ const graduating = typeof newVersion === "string" ? isGraduatingToStableBetweenVersion(pkg.version, newVersion) : isGraduating(pkg.version, releaseType);
1881
2235
  const from = await resolveFromTag({
1882
2236
  config,
1883
2237
  versionMode,
1884
2238
  step,
1885
2239
  packageName: pkg.name,
1886
- graduating: isGraduating(pkg.version, releaseType),
2240
+ graduating,
1887
2241
  logLevel
1888
2242
  });
1889
2243
  const to = resolveToTag({
@@ -1896,536 +2250,289 @@ async function resolveTags({
1896
2250
  logger.debug(`[${versionMode}](${step}) Using tags: ${from} \u2192 ${to}`);
1897
2251
  return { from, to };
1898
2252
  }
1899
-
1900
- let sessionOtp;
1901
- function detectPackageManager(cwd = process.cwd()) {
1902
- try {
1903
- const packageJsonPath = join(cwd, "package.json");
1904
- if (existsSync(packageJsonPath)) {
1905
- try {
1906
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
1907
- const pmField = packageJson.packageManager;
1908
- if (typeof pmField === "string") {
1909
- const pmName = pmField.split("@")[0];
1910
- if (["npm", "pnpm", "yarn", "bun"].includes(pmName)) {
1911
- logger.debug(`Detected package manager from package.json: ${pmName}`);
1912
- return pmName;
1913
- }
1914
- }
1915
- } catch (e) {
1916
- const errorString = e instanceof Error ? e.message : String(e);
1917
- logger.debug(`Failed to parse package.json: ${errorString}`);
1918
- }
1919
- }
1920
- const lockFiles = {
1921
- pnpm: "pnpm-lock.yaml",
1922
- yarn: "yarn.lock",
1923
- npm: "package-lock.json",
1924
- bun: "bun.lockb"
1925
- };
1926
- for (const [manager, file] of Object.entries(lockFiles)) {
1927
- if (existsSync(join(cwd, file))) {
1928
- logger.debug(`Detected package manager from lockfile: ${manager}`);
1929
- return manager;
1930
- }
1931
- }
1932
- const ua = process.env.npm_config_user_agent;
1933
- if (ua) {
1934
- const match = /(pnpm|yarn|npm|bun)/.exec(ua);
1935
- if (match) {
1936
- logger.debug(`Detected package manager from user agent: ${match[1]}`);
1937
- return match[1];
1938
- }
1939
- }
1940
- logger.debug("No package manager detected, defaulting to npm");
1941
- return "npm";
1942
- } catch (error) {
1943
- logger.fail(`Error detecting package manager: ${error}, defaulting to npm`);
1944
- return "npm";
1945
- }
1946
- }
1947
- function determinePublishTag(version, configTag) {
1948
- let tag = "latest";
1949
- if (configTag) {
1950
- tag = configTag;
1951
- }
1952
- if (isPrerelease(version) && !configTag) {
1953
- logger.warn('You are about to publish a "prerelease" version with the "latest" tag. To avoid mistake, the tag is set to "next"');
1954
- tag = "next";
1955
- }
1956
- if (isPrerelease(version) && configTag === "latest") {
1957
- logger.warn('Please note, you are about to publish a "prerelease" version with the "latest" tag.');
1958
- }
1959
- return tag;
1960
- }
1961
- function getPackagesToPublishInSelectiveMode(sortedPackages, rootVersion) {
1962
- const packagesToPublish = [];
1963
- for (const pkg of sortedPackages) {
1964
- const pkgJsonPath = join(pkg.path, "package.json");
1965
- const pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
1966
- if (pkgJson.version === rootVersion) {
1967
- packagesToPublish.push(pkg);
1968
- }
1969
- }
1970
- return packagesToPublish;
1971
- }
1972
- async function getPackagesToPublishInIndependentMode(sortedPackages, config) {
1973
- const packagesToPublish = [];
1974
- for (const pkg of sortedPackages) {
1975
- const { from, to } = await resolveTags({
1976
- config,
1977
- step: "publish",
1978
- pkg,
1979
- newVersion: pkg.version
1980
- });
1981
- if (pkg.commits.length > 0) {
1982
- packagesToPublish.push(pkg);
1983
- logger.debug(`${pkg.name}: ${pkg.commits.length} commit(s) since ${from} \u2192 ${to}`);
1984
- }
1985
- }
1986
- return packagesToPublish;
1987
- }
1988
- function isYarnBerry() {
1989
- return existsSync(path.join(process.cwd(), ".yarnrc.yml"));
1990
- }
1991
- function getCommandArgs({
1992
- packageManager,
1993
- tag,
1994
- config,
1995
- otp
1996
- }) {
1997
- const args = ["publish", "--tag", tag];
1998
- if (packageManager === "pnpm") {
1999
- args.push("--no-git-checks");
2000
- } else if (packageManager === "yarn") {
2001
- args.push("--non-interactive");
2002
- if (isYarnBerry())
2003
- args.push("--no-git-checks");
2004
- } else if (packageManager === "npm") {
2005
- args.push("--yes");
2006
- }
2007
- const registry = config.publish.registry;
2008
- if (registry) {
2009
- args.push("--registry", registry);
2010
- }
2011
- const access = config.publish.access;
2012
- if (access) {
2013
- args.push("--access", access);
2014
- }
2015
- const finalOtp = otp ?? sessionOtp ?? config.publish.otp;
2016
- if (finalOtp) {
2017
- args.push("--otp", finalOtp);
2018
- }
2019
- return args;
2020
- }
2021
- function isOtpError(error) {
2022
- if (typeof error !== "object" || error === null)
2023
- return false;
2024
- const errorMessage = "message" in error && typeof error.message === "string" ? error.message.toLowerCase() : "";
2025
- return errorMessage.includes("otp") || errorMessage.includes("one-time password") || errorMessage.includes("eotp");
2026
- }
2027
- function promptOtpWithTimeout(timeout = 9e4) {
2028
- return new Promise((resolve, reject) => {
2029
- const timer = setTimeout(() => {
2030
- reject(new Error("OTP input timeout"));
2031
- }, timeout);
2032
- input({
2033
- message: "This operation requires a one-time password (OTP). Please enter your OTP:"
2034
- }).then((otp) => {
2035
- clearTimeout(timer);
2036
- resolve(otp);
2037
- }).catch((error) => {
2038
- clearTimeout(timer);
2039
- reject(error);
2040
- });
2041
- });
2042
- }
2043
- async function handleOtpError() {
2044
- if (isInCI()) {
2045
- logger.error("OTP required but running in CI environment. Please provide OTP via config or `--otp` flag");
2046
- throw new Error("OTP required in CI environment");
2047
- }
2048
- logger.warn("Publish failed: OTP required");
2049
- try {
2050
- const otp = await promptOtpWithTimeout();
2051
- logger.debug("OTP received, retrying publish...");
2052
- return otp;
2053
- } catch (promptError) {
2054
- logger.error("Failed to get OTP:", promptError);
2055
- throw promptError;
2056
- }
2057
- }
2058
- async function executePublishCommand({
2059
- command,
2060
- packageNameAndVersion,
2061
- pkg,
2062
- config,
2063
- dryRun
2064
- }) {
2065
- logger.debug(`Executing publish command (${command}) in ${pkg.path}`);
2066
- if (dryRun) {
2067
- logger.info(`[dry-run] ${packageNameAndVersion}: Run ${command}`);
2068
- return;
2069
- }
2070
- const { stdout } = await execPromise(command, {
2071
- noStderr: true,
2072
- noStdout: true,
2073
- logLevel: config.logLevel,
2074
- cwd: pkg.path
2075
- });
2076
- if (stdout) {
2077
- logger.debug(stdout);
2078
- }
2079
- logger.info(`Published ${packageNameAndVersion}`);
2080
- }
2081
- async function publishPackage({
2082
- pkg,
2083
- config,
2084
- packageManager,
2085
- dryRun
2086
- }) {
2087
- const tag = determinePublishTag(pkg.version, config.publish.tag);
2088
- const packageNameAndVersion = getIndependentTag({ name: pkg.name, version: pkg.newVersion || pkg.version });
2089
- const baseCommand = packageManager === "yarn" && isYarnBerry() ? "yarn npm" : packageManager;
2090
- logger.debug(`Building publish command for ${pkg.name}`);
2091
- let dynamicOtp;
2092
- const maxAttempts = 2;
2093
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
2094
- try {
2095
- const args = getCommandArgs({
2096
- packageManager,
2097
- tag,
2098
- config,
2099
- otp: dynamicOtp
2100
- });
2101
- const command = `${baseCommand} ${args.join(" ")}`;
2102
- logger.debug(`Publishing ${packageNameAndVersion} with tag '${tag}' with command: ${command}`);
2103
- process.chdir(pkg.path);
2104
- await executePublishCommand({
2105
- command,
2106
- packageNameAndVersion,
2107
- pkg,
2108
- config,
2109
- dryRun
2110
- });
2111
- if (dynamicOtp && !sessionOtp) {
2112
- sessionOtp = dynamicOtp;
2113
- logger.debug("OTP stored for session");
2114
- }
2115
- return;
2116
- } catch (error) {
2117
- if (isOtpError(error) && attempt < maxAttempts - 1) {
2118
- dynamicOtp = await handleOtpError();
2119
- } else {
2120
- logger.error(`Failed to publish ${packageNameAndVersion}:`, error);
2121
- throw error;
2122
- }
2123
- } finally {
2124
- process.chdir(config.cwd);
2125
- }
2126
- }
2127
- }
2128
-
2129
- function readPackageJson(packagePath) {
2130
- const packageJsonPath = join(packagePath, "package.json");
2131
- if (!existsSync(packageJsonPath))
2132
- throw new Error(`package.json not found at ${packageJsonPath}`);
2133
- if (!statSync(packagePath).isDirectory())
2134
- throw new Error(`Not a directory: ${packagePath}`);
2135
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
2136
- if (!packageJson.name || !packageJson.version) {
2137
- throw new Error(`Invalid package.json at ${packagePath}`);
2138
- }
2139
- return {
2140
- name: packageJson.name,
2141
- version: packageJson.version,
2142
- private: packageJson.private || false,
2143
- path: packagePath
2144
- };
2145
- }
2146
- async function getRootPackage({
2147
- config,
2148
- force,
2149
- from,
2150
- to,
2151
- suffix,
2152
- changelog
2153
- }) {
2253
+
2254
+ let sessionOtp;
2255
+ function detectPackageManager(cwd = process.cwd()) {
2154
2256
  try {
2155
- const packageJson = readPackageJson(config.cwd);
2156
- const commits = await getPackageCommits({
2157
- pkg: packageJson,
2158
- from,
2159
- to,
2160
- config,
2161
- changelog
2162
- });
2163
- let newVersion;
2164
- if (config.monorepo?.versionMode !== "independent") {
2165
- const releaseType = determineReleaseType({
2166
- currentVersion: packageJson.version,
2167
- commits,
2168
- releaseType: config.bump.type,
2169
- preid: config.bump.preid,
2170
- types: config.types,
2171
- force
2172
- });
2173
- if (!releaseType) {
2174
- logger.fail("No commits require a version bump");
2175
- process.exit(1);
2257
+ const packageJsonPath = join(cwd, "package.json");
2258
+ if (existsSync(packageJsonPath)) {
2259
+ try {
2260
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
2261
+ const pmField = packageJson.packageManager;
2262
+ if (typeof pmField === "string") {
2263
+ const pmName = pmField.split("@")[0];
2264
+ if (["npm", "pnpm", "yarn", "bun"].includes(pmName)) {
2265
+ logger.debug(`Detected package manager from package.json: ${pmName}`);
2266
+ return pmName;
2267
+ }
2268
+ }
2269
+ } catch (e) {
2270
+ const errorString = e instanceof Error ? e.message : String(e);
2271
+ logger.debug(`Failed to parse package.json: ${errorString}`);
2176
2272
  }
2177
- newVersion = getPackageNewVersion({
2178
- currentVersion: packageJson.version,
2179
- releaseType,
2180
- preid: config.bump.preid,
2181
- suffix
2182
- });
2183
2273
  }
2184
- return {
2185
- ...packageJson,
2186
- path: config.cwd,
2187
- fromTag: from,
2188
- commits,
2189
- newVersion
2274
+ const lockFiles = {
2275
+ pnpm: "pnpm-lock.yaml",
2276
+ yarn: "yarn.lock",
2277
+ npm: "package-lock.json",
2278
+ bun: "bun.lockb"
2190
2279
  };
2280
+ for (const [manager, file] of Object.entries(lockFiles)) {
2281
+ if (existsSync(join(cwd, file))) {
2282
+ logger.debug(`Detected package manager from lockfile: ${manager}`);
2283
+ return manager;
2284
+ }
2285
+ }
2286
+ const ua = process.env.npm_config_user_agent;
2287
+ if (ua) {
2288
+ const match = /(pnpm|yarn|npm|bun)/.exec(ua);
2289
+ if (match) {
2290
+ logger.debug(`Detected package manager from user agent: ${match[1]}`);
2291
+ return match[1];
2292
+ }
2293
+ }
2294
+ logger.debug("No package manager detected, defaulting to npm");
2295
+ return "npm";
2191
2296
  } catch (error) {
2192
- const errorMessage = error instanceof Error ? error.message : String(error);
2193
- throw new Error(errorMessage);
2297
+ logger.fail(`Error detecting package manager: ${error}, defaulting to npm`);
2298
+ return "npm";
2194
2299
  }
2195
2300
  }
2196
- function readPackages({
2197
- cwd,
2198
- patterns,
2199
- ignorePackageNames
2200
- }) {
2201
- const packages = [];
2202
- const foundPaths = /* @__PURE__ */ new Set();
2203
- const patternsSet = new Set(patterns);
2204
- if (!patterns)
2205
- patternsSet.add(".");
2206
- logger.debug(`Read package.json files from patterns: ${patternsSet.values()}`);
2207
- for (const pattern of patternsSet) {
2208
- try {
2209
- const matches = fastGlob.sync(pattern, {
2210
- cwd,
2211
- onlyDirectories: true,
2212
- absolute: true,
2213
- ignore: ["**/node_modules/**", "**/dist/**", "**/.git/**"]
2214
- });
2215
- for (const matchPath of matches) {
2216
- if (foundPaths.has(matchPath))
2217
- continue;
2218
- const packageBase = readPackageJson(matchPath);
2219
- if (!packageBase || packageBase.private || ignorePackageNames?.includes(packageBase.name))
2220
- continue;
2221
- foundPaths.add(matchPath);
2222
- packages.push({
2223
- ...packageBase,
2224
- path: matchPath
2225
- });
2226
- }
2227
- } catch (error) {
2228
- logger.error(error);
2301
+ function determinePublishTag(version, configTag) {
2302
+ let tag = "latest";
2303
+ if (configTag) {
2304
+ tag = configTag;
2305
+ }
2306
+ if (isPrerelease(version) && !configTag) {
2307
+ logger.warn('You are about to publish a "prerelease" version with the "latest" tag. To avoid mistake, the tag is set to "next"');
2308
+ tag = "next";
2309
+ }
2310
+ if (isPrerelease(version) && configTag === "latest") {
2311
+ logger.warn('Please note, you are about to publish a "prerelease" version with the "latest" tag.');
2312
+ }
2313
+ return tag;
2314
+ }
2315
+ function getPackagesToPublishInSelectiveMode(sortedPackages, rootVersion) {
2316
+ const packagesToPublish = [];
2317
+ for (const pkg of sortedPackages) {
2318
+ const pkgJsonPath = join(pkg.path, "package.json");
2319
+ const pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
2320
+ if (pkgJson.version === rootVersion) {
2321
+ packagesToPublish.push(pkg);
2229
2322
  }
2230
2323
  }
2231
- return packages;
2324
+ return packagesToPublish;
2232
2325
  }
2233
- function getPackageReleaseType({
2234
- pkg,
2235
- config,
2236
- force
2237
- }) {
2238
- const releaseType = config.bump.type;
2239
- if (force) {
2240
- return determineReleaseType({
2241
- currentVersion: pkg.version,
2242
- commits: pkg.commits,
2243
- releaseType,
2244
- preid: config.bump.preid,
2245
- types: config.types,
2246
- force
2326
+ async function getPackagesToPublishInIndependentMode(sortedPackages, config) {
2327
+ const packagesToPublish = [];
2328
+ for (const pkg of sortedPackages) {
2329
+ const { from, to } = await resolveTags({
2330
+ config,
2331
+ step: "publish",
2332
+ pkg,
2333
+ newVersion: pkg.newVersion || pkg.version
2247
2334
  });
2335
+ if (pkg.commits.length > 0) {
2336
+ packagesToPublish.push(pkg);
2337
+ logger.debug(`${pkg.name}: ${pkg.commits.length} commit(s) since ${from} \u2192 ${to}`);
2338
+ }
2248
2339
  }
2249
- if (pkg.reason === "dependency") {
2250
- if (isStableReleaseType(releaseType))
2251
- return "patch";
2252
- if (isPrerelease(pkg.version))
2253
- return "prerelease";
2254
- return "prepatch";
2255
- }
2256
- return determineReleaseType({
2257
- currentVersion: pkg.version,
2258
- commits: pkg.commits,
2259
- releaseType,
2260
- preid: config.bump.preid,
2261
- types: config.types,
2262
- force
2263
- });
2340
+ return packagesToPublish;
2264
2341
  }
2265
- async function getPackages({
2266
- patterns,
2342
+ function isYarnBerry() {
2343
+ return existsSync(path.join(process.cwd(), ".yarnrc.yml"));
2344
+ }
2345
+ function getCommandArgs({
2346
+ packageManager,
2347
+ tag,
2267
2348
  config,
2268
- suffix,
2269
- force
2270
- }) {
2271
- const readedPackages = readPackages({
2272
- cwd: config.cwd,
2273
- patterns,
2274
- ignorePackageNames: config.monorepo?.ignorePackageNames
2275
- });
2276
- const packages = /* @__PURE__ */ new Map();
2277
- const foundPaths = /* @__PURE__ */ new Set();
2278
- const patternsSet = new Set(patterns);
2279
- if (!patterns)
2280
- patternsSet.add(".");
2281
- logger.debug(`Getting packages from patterns: ${patternsSet.values()}`);
2282
- for (const pattern of patternsSet) {
2283
- const matches = fastGlob.sync(pattern, {
2284
- cwd: config.cwd,
2285
- onlyDirectories: true,
2286
- absolute: true,
2287
- ignore: ["**/node_modules/**", "**/dist/**", "**/.git/**"]
2288
- });
2289
- for (const matchPath of matches) {
2290
- if (foundPaths.has(matchPath))
2291
- continue;
2292
- const packageBase = readPackageJson(matchPath);
2293
- if (packageBase.private) {
2294
- logger.debug(`${packageBase.name} is private and will be ignored`);
2295
- continue;
2296
- }
2297
- if (config.monorepo?.ignorePackageNames?.includes(packageBase.name)) {
2298
- logger.debug(`${packageBase.name} ignored by config (monorepo.ignorePackageNames)`);
2299
- continue;
2300
- }
2301
- if (!packageBase.version) {
2302
- logger.warn(`${packageBase.name} has no version and will be ignored`);
2303
- continue;
2304
- }
2305
- const { from, to } = await resolveTags({
2306
- config,
2307
- step: "bump",
2308
- pkg: packageBase,
2309
- newVersion: void 0
2310
- });
2311
- const commits = await getPackageCommits({
2312
- pkg: packageBase,
2313
- from,
2314
- to,
2315
- config,
2316
- changelog: false
2317
- });
2318
- foundPaths.add(matchPath);
2319
- const dependencies = getPackageDependencies({
2320
- packagePath: matchPath,
2321
- allPackageNames: new Set(readedPackages.map((p) => p.name)),
2322
- dependencyTypes: config.bump?.dependencyTypes
2323
- });
2324
- packages.set(packageBase.name, {
2325
- ...packageBase,
2326
- path: matchPath,
2327
- fromTag: from,
2328
- dependencies,
2329
- commits,
2330
- reason: commits.length > 0 ? "commits" : void 0,
2331
- dependencyChain: void 0,
2332
- newVersion: void 0
2333
- });
2349
+ otp,
2350
+ type,
2351
+ dryRun
2352
+ }) {
2353
+ const args = type === "publish" ? ["publish", "--tag", tag] : ["whoami"];
2354
+ const registry = config.publish.registry;
2355
+ if (registry) {
2356
+ args.push("--registry", registry);
2357
+ }
2358
+ const isPnpmOrNpm = packageManager === "pnpm" || packageManager === "npm";
2359
+ const publishToken = config.publish.token;
2360
+ if (publishToken) {
2361
+ if (!registry) {
2362
+ logger.warn("Publish token provided but no registry specified");
2363
+ } else if (!isPnpmOrNpm) {
2364
+ logger.warn("Publish token only supported for pnpm and npm");
2365
+ } else {
2366
+ const registryUrl = new URL(registry);
2367
+ const authTokenKey = `--//${registryUrl.host}${registryUrl.pathname}:_authToken=${publishToken}`;
2368
+ args.push(authTokenKey);
2334
2369
  }
2335
2370
  }
2336
- const packagesArray = Array.from(packages.values());
2337
- const packagesWithCommits = packagesArray.filter((p) => p.commits.length > 0);
2338
- const expandedPackages = expandPackagesToBumpWithDependents({
2339
- allPackages: packagesArray,
2340
- packagesWithCommits
2341
- });
2342
- for (const pkg of expandedPackages) {
2343
- packages.set(pkg.name, pkg);
2371
+ const finalOtp = otp ?? sessionOtp ?? config.publish.otp;
2372
+ if (finalOtp) {
2373
+ args.push("--otp", finalOtp);
2344
2374
  }
2345
- for (const pkg of Array.from(packages.values())) {
2346
- const releaseType = getPackageReleaseType({
2347
- pkg,
2348
- config,
2349
- force
2350
- });
2351
- const newVersion = releaseType ? getPackageNewVersion({
2352
- currentVersion: pkg.version,
2353
- releaseType,
2354
- preid: config.bump.preid,
2355
- suffix
2356
- }) : void 0;
2357
- const graduating = releaseType && isGraduating(pkg.version, releaseType) || isChangedPreid(pkg.version, config.bump.preid);
2358
- packages.set(pkg.name, {
2359
- ...pkg,
2360
- newVersion,
2361
- reason: pkg.reason || releaseType && graduating && "graduation" || void 0
2362
- });
2375
+ if (type === "auth") {
2376
+ return args;
2363
2377
  }
2364
- const packagesToBump = Array.from(packages.values()).filter((p) => p.reason || force);
2365
- if (packagesToBump.length === 0) {
2366
- logger.debug("No packages to bump");
2367
- return [];
2378
+ const access = config.publish.access;
2379
+ if (access) {
2380
+ args.push("--access", access);
2368
2381
  }
2369
- return packagesToBump;
2382
+ if (packageManager === "pnpm") {
2383
+ args.push("--no-git-checks");
2384
+ } else if (packageManager === "yarn") {
2385
+ args.push("--non-interactive");
2386
+ if (isYarnBerry())
2387
+ args.push("--no-git-checks");
2388
+ } else if (packageManager === "npm") {
2389
+ args.push("--yes");
2390
+ }
2391
+ if (dryRun) {
2392
+ args.push("--dry-run");
2393
+ }
2394
+ return args;
2370
2395
  }
2371
- function isAllowedCommit({
2372
- commit,
2373
- type,
2374
- changelog
2375
- }) {
2376
- if (commit.type === "chore" && ["deps", "release"].includes(commit.scope) && !commit.isBreaking) {
2396
+ function isOtpError(error) {
2397
+ if (typeof error !== "object" || error === null)
2377
2398
  return false;
2399
+ const errorMessage = "message" in error && typeof error.message === "string" ? error.message.toLowerCase() : "";
2400
+ return errorMessage.includes("otp") || errorMessage.includes("one-time password") || errorMessage.includes("eotp");
2401
+ }
2402
+ function promptOtpWithTimeout(timeout = 9e4) {
2403
+ return new Promise((resolve, reject) => {
2404
+ const timer = setTimeout(() => {
2405
+ reject(new Error("OTP input timeout"));
2406
+ }, timeout);
2407
+ input({
2408
+ message: "This operation requires a one-time password (OTP). Please enter your OTP:"
2409
+ }).then((otp) => {
2410
+ clearTimeout(timer);
2411
+ resolve(otp);
2412
+ }).catch((error) => {
2413
+ clearTimeout(timer);
2414
+ reject(error);
2415
+ });
2416
+ });
2417
+ }
2418
+ async function handleOtpError() {
2419
+ if (isInCI()) {
2420
+ logger.error("OTP required but running in CI environment. Please provide OTP via config or `--otp` flag");
2421
+ throw new Error("OTP required in CI environment");
2378
2422
  }
2379
- if (typeof type === "object") {
2380
- return !!type.semver || changelog && !!type.title;
2423
+ logger.warn("Publish failed: OTP required");
2424
+ try {
2425
+ const otp = await promptOtpWithTimeout();
2426
+ logger.debug("OTP received, retrying publish...");
2427
+ return otp;
2428
+ } catch (promptError) {
2429
+ logger.error("Failed to get OTP:", promptError);
2430
+ throw promptError;
2381
2431
  }
2382
- if (typeof type === "boolean") {
2383
- return type;
2432
+ }
2433
+ async function executePublishCommand({
2434
+ command,
2435
+ packageNameAndVersion,
2436
+ pkg,
2437
+ config,
2438
+ tag,
2439
+ dryRun,
2440
+ packageManager
2441
+ }) {
2442
+ logger.info(`${dryRun ? "[dry-run] " : ""}Publishing ${packageNameAndVersion} with tag "${tag}"`);
2443
+ const dryRunPublish = dryRun && packageManager !== "npm" && packageManager !== "yarn";
2444
+ if (dryRunPublish) {
2445
+ logger.info(`${dryRun ? "[dry-run] " : ""}Skipping actual publish for ${packageNameAndVersion}`);
2446
+ return;
2384
2447
  }
2385
- return false;
2448
+ const { stdout, stderr } = await execPromise(command, {
2449
+ noStderr: true,
2450
+ noStdout: true,
2451
+ noSuccess: true,
2452
+ logLevel: config.logLevel,
2453
+ cwd: pkg.path
2454
+ });
2455
+ logger.debug("Publish stdout:", stdout);
2456
+ logger.debug("Publish stderr:", stderr);
2386
2457
  }
2387
- async function getPackageCommits({
2458
+ function getAuthCommand({
2459
+ packageManager,
2460
+ config,
2461
+ otp
2462
+ }) {
2463
+ const args = getCommandArgs({
2464
+ packageManager,
2465
+ tag: void 0,
2466
+ config,
2467
+ otp,
2468
+ type: "auth"
2469
+ });
2470
+ return `${packageManager} ${args.join(" ")}`;
2471
+ }
2472
+ function getPublishCommand({
2473
+ packageManager,
2474
+ tag,
2475
+ config,
2476
+ otp,
2477
+ dryRun
2478
+ }) {
2479
+ const args = getCommandArgs({
2480
+ packageManager,
2481
+ tag,
2482
+ config,
2483
+ otp,
2484
+ dryRun,
2485
+ type: "publish"
2486
+ });
2487
+ const baseCommand = packageManager === "yarn" && isYarnBerry() ? "yarn npm" : packageManager;
2488
+ return `${baseCommand} ${args.join(" ")}`;
2489
+ }
2490
+ async function publishPackage({
2388
2491
  pkg,
2389
- from,
2390
- to,
2391
2492
  config,
2392
- changelog
2493
+ packageManager,
2494
+ dryRun
2393
2495
  }) {
2394
- logger.debug(`Analyzing commits for ${pkg.name} since ${from} to ${to}`);
2395
- const changelogConfig = {
2396
- ...config,
2397
- from,
2398
- to
2399
- };
2400
- const rawCommits = await getGitDiff(from, to, changelogConfig.cwd);
2401
- const allCommits = parseCommits(rawCommits, changelogConfig);
2402
- const hasBreakingChanges = allCommits.some((commit) => commit.isBreaking);
2403
- logger.debug(`Has breaking changes: ${hasBreakingChanges}`);
2404
- const rootPackage = readPackageJson(changelogConfig.cwd);
2405
- const commits = allCommits.filter((commit) => {
2406
- const type = changelogConfig?.types[commit.type];
2407
- if (!isAllowedCommit({ commit, type, changelog })) {
2408
- return false;
2409
- }
2410
- if (pkg.path === changelogConfig.cwd || pkg.name === rootPackage.name) {
2411
- return true;
2496
+ const tag = determinePublishTag(pkg.newVersion || pkg.version, config.publish.tag);
2497
+ const packageNameAndVersion = getIndependentTag({ name: pkg.name, version: pkg.newVersion || pkg.version });
2498
+ logger.debug(`Building publish command for ${pkg.name}`);
2499
+ let dynamicOtp;
2500
+ const maxAttempts = 2;
2501
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
2502
+ try {
2503
+ const command = getPublishCommand({
2504
+ packageManager,
2505
+ tag,
2506
+ config,
2507
+ otp: dynamicOtp,
2508
+ dryRun
2509
+ });
2510
+ process.chdir(pkg.path);
2511
+ await executePublishCommand({
2512
+ command,
2513
+ packageNameAndVersion,
2514
+ packageManager,
2515
+ pkg,
2516
+ config,
2517
+ dryRun,
2518
+ tag
2519
+ });
2520
+ if (dynamicOtp && !sessionOtp) {
2521
+ sessionOtp = dynamicOtp;
2522
+ logger.debug("OTP stored for session");
2523
+ }
2524
+ return;
2525
+ } catch (error) {
2526
+ if (isOtpError(error) && attempt < maxAttempts - 1) {
2527
+ dynamicOtp = await handleOtpError();
2528
+ } else {
2529
+ logger.error(`Failed to publish ${packageNameAndVersion}:`, error);
2530
+ throw error;
2531
+ }
2532
+ } finally {
2533
+ process.chdir(config.cwd);
2412
2534
  }
2413
- const packageRelativePath = relative(changelogConfig.cwd, pkg.path);
2414
- const scopeMatches = commit.scope === pkg.name;
2415
- const bodyContainsPath = commit.body.includes(packageRelativePath);
2416
- return scopeMatches || bodyContainsPath;
2417
- });
2418
- logger.debug(`Found ${commits.length} commit(s) for ${pkg.name} from ${from} to ${to}`);
2419
- if (commits.length > 0) {
2420
- logger.debug(`${pkg.name}: ${commits.length} commit(s) found`);
2421
- } else {
2422
- logger.debug(`${pkg.name}: No commits found`);
2423
2535
  }
2424
- return commits;
2425
- }
2426
- function hasLernaJson(rootDir) {
2427
- const lernaJsonPath = join(rootDir, "lerna.json");
2428
- return existsSync(lernaJsonPath);
2429
2536
  }
2430
2537
 
2431
2538
  async function bumpUnifiedMode({
@@ -2436,6 +2543,9 @@ async function bumpUnifiedMode({
2436
2543
  }) {
2437
2544
  logger.debug("Starting bump in unified mode");
2438
2545
  const rootPackageBase = readPackageJson(config.cwd);
2546
+ if (!rootPackageBase) {
2547
+ throw new Error("Failed to read root package.json");
2548
+ }
2439
2549
  const { from, to } = await resolveTags({
2440
2550
  config,
2441
2551
  step: "bump",
@@ -2515,6 +2625,9 @@ async function bumpSelectiveMode({
2515
2625
  }) {
2516
2626
  logger.debug("Starting bump in selective mode");
2517
2627
  const rootPackageBase = readPackageJson(config.cwd);
2628
+ if (!rootPackageBase) {
2629
+ throw new Error("Failed to read root package.json");
2630
+ }
2518
2631
  const { from, to } = await resolveTags({
2519
2632
  config,
2520
2633
  step: "bump",
@@ -2717,22 +2830,6 @@ async function bump(options = {}) {
2717
2830
  }
2718
2831
  }
2719
2832
 
2720
- async function getPackagesToGenerateChangelogFor({
2721
- config,
2722
- bumpResult,
2723
- suffix,
2724
- force
2725
- }) {
2726
- if (bumpResult?.bumpedPackages && bumpResult.bumpedPackages.length > 0) {
2727
- return bumpResult.bumpedPackages;
2728
- }
2729
- return await getPackages({
2730
- config,
2731
- patterns: config.monorepo?.packages,
2732
- suffix,
2733
- force
2734
- });
2735
- }
2736
2833
  async function generateIndependentRootChangelog({
2737
2834
  packages,
2738
2835
  config,
@@ -2764,10 +2861,13 @@ async function generateIndependentRootChangelog({
2764
2861
 
2765
2862
  ${packageChangelogs.join("\n\n")}`;
2766
2863
  logger.verbose(`Aggregated root changelog: ${aggregatedChangelog}`);
2767
- const rootPackage = readPackageJson(config.cwd);
2864
+ const rootPackageRead = readPackageJson(config.cwd);
2865
+ if (!rootPackageRead) {
2866
+ throw new Error("Failed to read root package.json");
2867
+ }
2768
2868
  writeChangelogToFile({
2769
2869
  cwd: config.cwd,
2770
- pkg: rootPackage,
2870
+ pkg: rootPackageRead,
2771
2871
  changelog: aggregatedChangelog,
2772
2872
  dryRun
2773
2873
  });
@@ -2786,10 +2886,14 @@ async function generateSimpleRootChangelog({
2786
2886
  }
2787
2887
  logger.debug("Generating simple root changelog");
2788
2888
  const rootPackageRead = readPackageJson(config.cwd);
2889
+ if (!rootPackageRead) {
2890
+ throw new Error("Failed to read root package.json");
2891
+ }
2892
+ const newVersion = bumpResult?.newVersion || rootPackageRead.version;
2789
2893
  const { from, to } = await resolveTags({
2790
2894
  config,
2791
2895
  step: "changelog",
2792
- newVersion: void 0,
2896
+ newVersion,
2793
2897
  pkg: rootPackageRead
2794
2898
  });
2795
2899
  const fromTag = bumpResult?.fromTag || from;
@@ -2802,7 +2906,6 @@ async function generateSimpleRootChangelog({
2802
2906
  to
2803
2907
  });
2804
2908
  logger.debug(`Generating ${rootPackage.name} changelog (${fromTag}...${to})`);
2805
- const newVersion = bumpResult?.newVersion || rootPackage.version;
2806
2909
  const rootChangelog = await generateChangelog({
2807
2910
  pkg: rootPackage,
2808
2911
  config,
@@ -2843,7 +2946,7 @@ async function changelog(options = {}) {
2843
2946
  logger.start("Start generating changelogs");
2844
2947
  if (config.changelog?.rootChangelog && config.monorepo) {
2845
2948
  if (config.monorepo.versionMode === "independent") {
2846
- const packages2 = await getPackagesToGenerateChangelogFor({
2949
+ const packages2 = await getPackagesOrBumpedPackages({
2847
2950
  config,
2848
2951
  bumpResult: options.bumpResult,
2849
2952
  suffix: options.suffix,
@@ -2867,27 +2970,28 @@ async function changelog(options = {}) {
2867
2970
  logger.debug("Skipping root changelog generation");
2868
2971
  }
2869
2972
  logger.debug("Generating package changelogs...");
2870
- const packages = options.bumpResult?.bumpedPackages ? options.bumpResult.bumpedPackages : await getPackages({
2973
+ const packages = await getPackagesOrBumpedPackages({
2871
2974
  config,
2872
- patterns: config.monorepo?.packages,
2975
+ bumpResult: options.bumpResult,
2873
2976
  suffix: options.suffix,
2874
2977
  force: options.force ?? false
2875
2978
  });
2876
2979
  logger.debug(`Processing ${packages.length} package(s)`);
2877
2980
  let generatedCount = 0;
2878
2981
  for await (const pkg of packages) {
2982
+ const newVersion = options.bumpResult?.bumpedPackages?.find((p) => p.name === pkg.name)?.newVersion || pkg.newVersion || pkg.version;
2879
2983
  const { from, to } = await resolveTags({
2880
2984
  config,
2881
2985
  step: "changelog",
2882
2986
  pkg,
2883
- newVersion: void 0
2987
+ newVersion
2884
2988
  });
2885
2989
  logger.debug(`Processing ${pkg.name} (${from}...${to})`);
2886
2990
  const changelog2 = await generateChangelog({
2887
2991
  pkg,
2888
2992
  config,
2889
2993
  dryRun,
2890
- newVersion: options.bumpResult?.bumpedPackages?.find((p) => p.name === pkg.name)?.newVersion
2994
+ newVersion
2891
2995
  });
2892
2996
  if (changelog2) {
2893
2997
  writeChangelogToFile({
@@ -2927,11 +3031,11 @@ function providerReleaseSafetyCheck({ config, provider }) {
2927
3031
  } else if (internalProvider === "gitlab") {
2928
3032
  token = config.tokens?.gitlab || config.repo?.token;
2929
3033
  } else {
2930
- logger.error(`Unsupported Git provider: ${internalProvider || "unknown"}`);
3034
+ logger.error(`[provider-release-safety-check] Unsupported Git provider: ${internalProvider || "unknown"}`);
2931
3035
  process.exit(1);
2932
3036
  }
2933
3037
  if (!token) {
2934
- logger.error(`No token provided for ${internalProvider || "unknown"} - The release will not be published - Please refer to the documentation: https://louismazel.github.io/relizy/guide/installation#environment-setup`);
3038
+ logger.error(`[provider-release-safety-check] No token provided for ${internalProvider || "unknown"} - The release will not be published - Please refer to the documentation: https://louismazel.github.io/relizy/guide/installation#environment-setup`);
2935
3039
  process.exit(1);
2936
3040
  }
2937
3041
  }
@@ -2998,6 +3102,40 @@ async function providerRelease(options = {}) {
2998
3102
  }
2999
3103
  }
3000
3104
 
3105
+ async function publishSafetyCheck({ config }) {
3106
+ logger.debug("[publish-safety-check] Running publish safety check");
3107
+ if (!config.safetyCheck || !config.release.publish || !config.publish.safetyCheck) {
3108
+ logger.debug("[publish-safety-check] Safety check disabled or publish disabled");
3109
+ return;
3110
+ }
3111
+ const packageManager = config.publish.packageManager || detectPackageManager(config.cwd);
3112
+ if (!packageManager) {
3113
+ logger.error("[publish-safety-check] Unable to detect package manager");
3114
+ process.exit(1);
3115
+ }
3116
+ const isPnpmOrNpm = packageManager === "pnpm" || packageManager === "npm";
3117
+ if (isPnpmOrNpm) {
3118
+ const authCommand = getAuthCommand({
3119
+ packageManager,
3120
+ config,
3121
+ otp: config.publish.otp
3122
+ });
3123
+ try {
3124
+ logger.debug("[publish-safety-check] Authenticating to package registry...");
3125
+ await execPromise(authCommand, {
3126
+ cwd: config.cwd,
3127
+ noStderr: true,
3128
+ noStdout: true,
3129
+ logLevel: config.logLevel,
3130
+ noSuccess: true
3131
+ });
3132
+ logger.info("[publish-safety-check] Successfully authenticated to package registry");
3133
+ } catch (error) {
3134
+ logger.error("[publish-safety-check] Failed to authenticate to package registry:", error);
3135
+ process.exit(1);
3136
+ }
3137
+ }
3138
+ }
3001
3139
  async function publish(options = {}) {
3002
3140
  const config = await loadRelizyConfig({
3003
3141
  configName: options.configName,
@@ -3008,14 +3146,16 @@ async function publish(options = {}) {
3008
3146
  otp: options.otp,
3009
3147
  registry: options.registry,
3010
3148
  tag: options.tag,
3011
- buildCmd: options.buildCmd
3149
+ buildCmd: options.buildCmd,
3150
+ token: options.token
3012
3151
  },
3013
- logLevel: options.logLevel
3152
+ logLevel: options.logLevel,
3153
+ safetyCheck: options.safetyCheck
3014
3154
  }
3015
3155
  });
3016
3156
  const dryRun = options.dryRun ?? false;
3017
3157
  logger.debug(`Dry run: ${dryRun}`);
3018
- const packageManager = detectPackageManager(process.cwd());
3158
+ const packageManager = config.publish.packageManager || detectPackageManager(config.cwd);
3019
3159
  logger.debug(`Package manager: ${packageManager}`);
3020
3160
  logger.info(`Version mode: ${config.monorepo?.versionMode || "standalone"}`);
3021
3161
  if (config.publish.registry) {
@@ -3026,11 +3166,15 @@ async function publish(options = {}) {
3026
3166
  }
3027
3167
  try {
3028
3168
  await executeHook("before:publish", config, dryRun);
3169
+ await publishSafetyCheck({ config });
3029
3170
  const rootPackage = readPackageJson(config.cwd);
3171
+ if (!rootPackage) {
3172
+ throw new Error("Failed to read root package.json");
3173
+ }
3030
3174
  logger.start("Start publishing packages");
3031
- const packages = options.bumpedPackages || await getPackages({
3175
+ const packages = await getPackagesOrBumpedPackages({
3032
3176
  config,
3033
- patterns: config.publish.packages ?? config.monorepo?.packages,
3177
+ bumpResult: options.bumpResult,
3034
3178
  suffix: options.suffix,
3035
3179
  force: options.force ?? false
3036
3180
  });
@@ -3114,7 +3258,8 @@ function getReleaseConfig(options = {}) {
3114
3258
  otp: options.otp,
3115
3259
  registry: options.registry,
3116
3260
  tag: options.tag,
3117
- buildCmd: options.buildCmd
3261
+ buildCmd: options.buildCmd,
3262
+ token: options.publishToken
3118
3263
  },
3119
3264
  release: {
3120
3265
  commit: options.commit,
@@ -3130,7 +3275,7 @@ function getReleaseConfig(options = {}) {
3130
3275
  }
3131
3276
  });
3132
3277
  }
3133
- function releaseSafetyCheck({
3278
+ async function releaseSafetyCheck({
3134
3279
  config,
3135
3280
  provider
3136
3281
  }) {
@@ -3138,6 +3283,7 @@ function releaseSafetyCheck({
3138
3283
  return;
3139
3284
  }
3140
3285
  providerReleaseSafetyCheck({ config, provider });
3286
+ await publishSafetyCheck({ config });
3141
3287
  }
3142
3288
  async function release(options = {}) {
3143
3289
  const dryRun = options.dryRun ?? false;
@@ -3147,7 +3293,7 @@ async function release(options = {}) {
3147
3293
  const config = await getReleaseConfig(options);
3148
3294
  logger.debug(`Version mode: ${config.monorepo?.versionMode || "standalone"}`);
3149
3295
  logger.debug(`Push: ${config.release.push}, Publish: ${config.release.publish}, Provider Release: ${config.release.providerRelease}`);
3150
- releaseSafetyCheck({ config, provider: options.provider });
3296
+ await releaseSafetyCheck({ config, provider: options.provider });
3151
3297
  try {
3152
3298
  await executeHook("before:release", config, dryRun);
3153
3299
  logger.box("Step 1/6: Bump versions");
@@ -3223,7 +3369,7 @@ async function release(options = {}) {
3223
3369
  tag: config.publish.tag,
3224
3370
  access: config.publish.access,
3225
3371
  otp: config.publish.otp,
3226
- bumpedPackages: bumpResult.bumpedPackages,
3372
+ bumpResult,
3227
3373
  dryRun,
3228
3374
  config,
3229
3375
  configName: options.configName,
@@ -3261,10 +3407,10 @@ async function release(options = {}) {
3261
3407
  logger.info("Skipping release (--no-provider-release)");
3262
3408
  }
3263
3409
  const publishedPackageCount = publishResponse?.publishedPackages.length ?? 0;
3264
- const versionDisplay = config.monorepo?.versionMode === "independent" ? `${bumpResult.bumpedPackages.length} packages bumped independently` : bumpResult.newVersion || readPackageJson(config.cwd).version;
3410
+ const versionDisplay = config.monorepo?.versionMode === "independent" ? `${bumpResult.bumpedPackages.length} packages bumped independently` : bumpResult.newVersion || readPackageJson(config.cwd)?.version;
3265
3411
  logger.box(`Release workflow completed!
3266
3412
 
3267
- Version: ${versionDisplay}
3413
+ Version: ${versionDisplay ?? "Unknown"}
3268
3414
  Tag(s): ${createdTags.length ? createdTags.join(", ") : "No"}
3269
3415
  Pushed: ${config.release.push ? "Yes" : "Disabled"}
3270
3416
  Published packages: ${config.release.publish ? publishedPackageCount : "Disabled"}
@@ -3278,4 +3424,4 @@ Git provider: ${provider}`);
3278
3424
  }
3279
3425
  }
3280
3426
 
3281
- 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 };
3427
+ export { getPackagesOrBumpedPackages as $, github as A, createGitlabRelease as B, gitlab as C, detectPackageManager as D, determinePublishTag as E, getPackagesToPublishInSelectiveMode as F, getPackagesToPublishInIndependentMode as G, getAuthCommand as H, publishPackage as I, readPackageJson as J, getRootPackage as K, readPackages as L, getPackages as M, getPackageCommits as N, hasLernaJson as O, getIndependentTag as P, getLastStableTag as Q, getLastTag as R, getLastRepoTag as S, getLastPackageTag as T, resolveTags as U, executeHook as V, isInCI as W, getCIName as X, executeFormatCmd as Y, executeBuildCmd as Z, isBumpedPackage as _, providerRelease as a, isGraduatingToStableBetweenVersion as a0, determineSemverChange as a1, determineReleaseType as a2, writeVersion as a3, getPackageNewVersion as a4, updateLernaVersion as a5, extractVersionFromPackageTag as a6, isPrerelease as a7, isStableReleaseType as a8, isPrereleaseReleaseType as a9, isGraduating as aa, getPreid as ab, isChangedPreid as ac, getBumpedPackageIndependently as ad, confirmBump as ae, getBumpedIndependentPackages as af, bump as b, changelog as c, publishSafetyCheck as d, publish as e, getDefaultConfig as f, generateChangelog as g, defineConfig as h, getPackageDependencies as i, getDependentsOf as j, expandPackagesToBumpWithDependents as k, loadRelizyConfig as l, getGitStatus as m, checkGitStatusIfDirty as n, fetchGitTags as o, providerReleaseSafetyCheck as p, detectGitProvider as q, release as r, parseGitRemoteUrl as s, topologicalSort as t, createCommitAndTags as u, pushCommitAndTags as v, writeChangelogToFile as w, getFirstCommit as x, getCurrentGitBranch as y, getCurrentGitRef as z };