relizy 0.1.0 → 0.2.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.
@@ -2,14 +2,14 @@ import { logger, execPromise } from '@maz-ui/node';
2
2
  import { execSync } from 'node:child_process';
3
3
  import { existsSync, readFileSync, writeFileSync, statSync } from 'node:fs';
4
4
  import path, { join } from 'node:path';
5
- import { formatCompareChanges, formatReference, resolveRepoConfig, getRepoConfig, createGithubRelease, getGitDiff, parseCommits, determineSemverChange } from 'changelogen';
6
- import fastGlob from 'fast-glob';
7
- import { confirm } from '@inquirer/prompts';
8
- import { upperFirst, formatJson } from '@maz-ui/utils';
9
- import * as semver from 'semver';
10
5
  import process$1 from 'node:process';
6
+ import { upperFirst, formatJson } from '@maz-ui/utils';
11
7
  import { setupDotenv, loadConfig } from 'c12';
8
+ import { formatCompareChanges, formatReference, resolveRepoConfig, getRepoConfig, createGithubRelease, getGitDiff, parseCommits, determineSemverChange } from 'changelogen';
12
9
  import { defu } from 'defu';
10
+ import fastGlob from 'fast-glob';
11
+ import { confirm, input } from '@inquirer/prompts';
12
+ import * as semver from 'semver';
13
13
  import { convert } from 'convert-gitmoji';
14
14
  import { fetch as fetch$1 } from 'node-fetch-native';
15
15
 
@@ -110,7 +110,8 @@ function getCommitBody(commit) {
110
110
  return false;
111
111
  }
112
112
  const isFileLine = /^[AMDRC]\s+/.test(trimmedLine);
113
- return !isFileLine;
113
+ const R100 = /R100\s+/.test(trimmedLine);
114
+ return !isFileLine && !R100;
114
115
  });
115
116
  if (contentLines.length === 0) {
116
117
  return "";
@@ -148,6 +149,138 @@ function groupBy(items, key) {
148
149
  return groups;
149
150
  }
150
151
 
152
+ async function executeHook(hook, config, dryRun, params) {
153
+ const hookInput = config.hooks?.[hook];
154
+ if (!hookInput) {
155
+ logger.debug(`Hook ${hook} not found`);
156
+ return;
157
+ }
158
+ if (typeof hookInput === "function") {
159
+ logger.info(`Executing hook ${hook}`);
160
+ const result = await hookInput(config, dryRun, params);
161
+ if (result)
162
+ logger.debug(`Hook ${hook} returned: ${result}`);
163
+ logger.info(`Hook ${hook} executed`);
164
+ return result;
165
+ }
166
+ if (typeof hookInput === "string") {
167
+ logger.info(`Executing hook ${hook}`);
168
+ const result = await execPromise(hookInput, {
169
+ logLevel: config.logLevel,
170
+ cwd: config.cwd,
171
+ noStderr: true,
172
+ noStdout: true
173
+ });
174
+ if (result)
175
+ logger.debug(`Hook ${hook} returned: ${result}`);
176
+ logger.info(`Hook ${hook} executed`);
177
+ return result;
178
+ }
179
+ }
180
+ function isInCI() {
181
+ return Boolean(
182
+ 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"
183
+ );
184
+ }
185
+ function getCIName() {
186
+ if (process.env.GITHUB_ACTIONS === "true")
187
+ return "GitHub Actions";
188
+ if (process.env.GITLAB_CI === "true")
189
+ return "GitLab CI";
190
+ if (process.env.CIRCLECI === "true")
191
+ return "CircleCI";
192
+ if (process.env.TRAVIS === "true")
193
+ return "Travis CI";
194
+ if (process.env.JENKINS_HOME || process.env.JENKINS_URL)
195
+ return "Jenkins";
196
+ if (process.env.TF_BUILD === "True")
197
+ return "Azure Pipelines";
198
+ if (process.env.TEAMCITY_VERSION)
199
+ return "TeamCity";
200
+ if (process.env.BITBUCKET_BUILD_NUMBER)
201
+ return "Bitbucket Pipelines";
202
+ if (process.env.DRONE === "true")
203
+ return "Drone";
204
+ if (process.env.APPVEYOR)
205
+ return "AppVeyor";
206
+ if (process.env.BUILDKITE === "true")
207
+ return "Buildkite";
208
+ if (process.env.CODEBUILD_BUILD_ID)
209
+ return "AWS CodeBuild";
210
+ if (process.env.NETLIFY === "true")
211
+ return "Netlify";
212
+ if (process.env.VERCEL === "1")
213
+ return "Vercel";
214
+ if (process.env.HEROKU_TEST_RUN_ID)
215
+ return "Heroku CI";
216
+ if (process.env.BUDDY === "true")
217
+ return "Buddy";
218
+ if (process.env.SEMAPHORE === "true")
219
+ return "Semaphore";
220
+ if (process.env.CF_BUILD_ID)
221
+ return "Codefresh";
222
+ if (process.env.bamboo_buildKey)
223
+ return "Bamboo";
224
+ if (process.env.BUILD_ID && process.env.PROJECT_ID)
225
+ return "Google Cloud Build";
226
+ if (process.env.SCREWDRIVER === "true")
227
+ return "Screwdriver";
228
+ if (process.env.STRIDER === "true")
229
+ return "Strider";
230
+ if (process.env.CI === "true")
231
+ return "Unknown CI";
232
+ return null;
233
+ }
234
+ async function executeFormatCmd({
235
+ config,
236
+ dryRun
237
+ }) {
238
+ if (config.changelog?.formatCmd) {
239
+ logger.info("Running format command");
240
+ logger.debug(`Running format command: ${config.changelog.formatCmd}`);
241
+ try {
242
+ if (!dryRun) {
243
+ await execPromise(config.changelog.formatCmd, {
244
+ noStderr: true,
245
+ noStdout: true,
246
+ logLevel: config.logLevel,
247
+ cwd: config.cwd
248
+ });
249
+ logger.info("Format completed");
250
+ } else {
251
+ logger.log("[dry-run] exec format command: ", config.changelog.formatCmd);
252
+ }
253
+ } catch (error) {
254
+ logger.error("Format command failed:", error);
255
+ process.exit(1);
256
+ }
257
+ } else {
258
+ logger.debug("No format command specified");
259
+ }
260
+ }
261
+ async function executeBuildCmd({
262
+ config,
263
+ dryRun
264
+ }) {
265
+ if (config.publish?.buildCmd) {
266
+ logger.info("Running build command");
267
+ logger.debug(`Running build command: ${config.publish.buildCmd}`);
268
+ if (!dryRun) {
269
+ await execPromise(config.publish.buildCmd, {
270
+ noStderr: true,
271
+ noStdout: true,
272
+ logLevel: config.logLevel,
273
+ cwd: config.cwd
274
+ });
275
+ logger.info("Build completed");
276
+ } else {
277
+ logger.log("[dry-run] exec build command: ", config.publish.buildCmd);
278
+ }
279
+ } else {
280
+ logger.debug("No build command specified");
281
+ }
282
+ }
283
+
151
284
  function fromTagIsFirstCommit(fromTag, cwd) {
152
285
  return fromTag === getFirstCommit(cwd);
153
286
  }
@@ -164,21 +297,27 @@ async function generateChangelog({
164
297
  fromTag = config.monorepo?.versionMode === "independent" ? `${pkg.name}@0.0.0` : config.templates.tagBody.replace("{{newVersion}}", "0.0.0");
165
298
  }
166
299
  const toTag = config.to || (config.monorepo?.versionMode === "independent" ? `${pkg.name}@${pkg.version}` : config.templates.tagBody.replace("{{newVersion}}", pkg.version));
300
+ logger.debug(`Generating changelog for ${pkg.name} - from ${fromTag} to ${toTag}`);
167
301
  try {
168
- logger.debug(`Generating changelog for ${pkg.name} - from ${fromTag} to ${toTag}`);
169
302
  config = {
170
303
  ...config,
171
304
  from: fromTag,
172
305
  to: toTag
173
306
  };
174
- let changelog = await generateMarkDown(commits, config);
175
- logger.verbose(`Output changelog for ${pkg.name}:
176
- ${changelog}`);
307
+ const generatedChangelog = await generateMarkDown(commits, config);
308
+ let changelog = generatedChangelog;
177
309
  if (commits.length === 0) {
178
310
  changelog = `${changelog}
179
311
 
180
312
  ${config.templates.emptyChangelogContent}`;
181
313
  }
314
+ const changelogResult = await executeHook("generate:changelog", config, dryRun, {
315
+ commits,
316
+ changelog
317
+ });
318
+ changelog = changelogResult || changelog;
319
+ logger.verbose(`Output changelog for ${pkg.name}:
320
+ ${changelog}`);
182
321
  logger.debug(`Changelog generated for ${pkg.name} (${commits.length} commits)`);
183
322
  logger.verbose(`Final changelog for ${pkg.name}:
184
323
 
@@ -224,55 +363,6 @@ ${existingChangelog}`;
224
363
  logger.info(`Changelog updated for ${pkg.name}`);
225
364
  }
226
365
  }
227
- async function executeFormatCmd({
228
- config,
229
- dryRun
230
- }) {
231
- if (config.changelog?.formatCmd) {
232
- logger.info("Running format command");
233
- logger.debug(`Running format command: ${config.changelog.formatCmd}`);
234
- try {
235
- if (!dryRun) {
236
- await execPromise(config.changelog.formatCmd, {
237
- noStderr: true,
238
- noStdout: true,
239
- logLevel: config.logLevel,
240
- cwd: config.cwd
241
- });
242
- logger.info("Format completed");
243
- } else {
244
- logger.log("[dry-run] exec format command: ", config.changelog.formatCmd);
245
- }
246
- } catch (error) {
247
- logger.error("Format command failed:", error);
248
- process.exit(1);
249
- }
250
- } else {
251
- logger.debug("No format command specified");
252
- }
253
- }
254
- async function executeBuildCmd({
255
- config,
256
- dryRun
257
- }) {
258
- if (config.publish?.buildCmd) {
259
- logger.info("Running build command");
260
- logger.debug(`Running build command: ${config.publish.buildCmd}`);
261
- if (!dryRun) {
262
- await execPromise(config.publish.buildCmd, {
263
- noStderr: true,
264
- noStdout: true,
265
- logLevel: config.logLevel,
266
- cwd: config.cwd
267
- });
268
- logger.info("Build completed");
269
- } else {
270
- logger.log("[dry-run] exec build command: ", config.publish.buildCmd);
271
- }
272
- } else {
273
- logger.debug("No build command specified");
274
- }
275
- }
276
366
 
277
367
  function getDefaultConfig() {
278
368
  return {
@@ -314,8 +404,8 @@ function getDefaultConfig() {
314
404
  args: []
315
405
  },
316
406
  tokens: {
317
- gitlab: process$1.env.RELIZY_TOKENS_GITLAB || process$1.env.GITLAB_TOKEN || process$1.env.GITLAB_API_TOKEN || process$1.env.CI_JOB_TOKEN,
318
- github: process$1.env.RELIZY_TOKENS_GITHUB || process$1.env.GITHUB_TOKEN || process$1.env.GH_TOKEN
407
+ gitlab: process$1.env.RELIZY_GITLAB_TOKEN || process$1.env.GITLAB_TOKEN || process$1.env.GITLAB_API_TOKEN || process$1.env.CI_JOB_TOKEN,
408
+ github: process$1.env.RELIZY_GITHUB_TOKEN || process$1.env.GITHUB_TOKEN || process$1.env.GH_TOKEN
319
409
  },
320
410
  scopeMap: {},
321
411
  release: {
@@ -324,10 +414,11 @@ function getDefaultConfig() {
324
414
  changelog: true,
325
415
  push: true,
326
416
  clean: true,
327
- release: true,
417
+ providerRelease: true,
328
418
  noVerify: false
329
419
  },
330
- logLevel: "default"
420
+ logLevel: "default",
421
+ safetyCheck: true
331
422
  };
332
423
  }
333
424
  function setupLogger(logLevel) {
@@ -353,12 +444,12 @@ async function resolveConfig(config, cwd) {
353
444
  }
354
445
  return config;
355
446
  }
356
- async function loadMonorepoConfig({ baseConfig, overrides, configName = "relizy" }) {
357
- const cwd = overrides?.cwd ?? process$1.cwd();
358
- configName ??= "relizy";
447
+ async function loadRelizyConfig(options) {
448
+ const cwd = options?.overrides?.cwd ?? process$1.cwd();
449
+ const configName = options?.configName ?? "relizy";
359
450
  await setupDotenv({ cwd });
360
451
  const defaultConfig = getDefaultConfig();
361
- const overridesConfig = defu(overrides, baseConfig);
452
+ const overridesConfig = defu(options?.overrides, options?.baseConfig);
362
453
  const results = await loadConfig({
363
454
  cwd,
364
455
  name: configName,
@@ -367,10 +458,13 @@ async function loadMonorepoConfig({ baseConfig, overrides, configName = "relizy"
367
458
  overrides: overridesConfig
368
459
  });
369
460
  if (!results._configFile) {
370
- logger.error(`No config file found with name "${configName}"`);
371
- process$1.exit(1);
461
+ logger.debug(`No config file found with name "${configName}" - using standalone mode`);
462
+ if (options?.configName) {
463
+ logger.error(`No config file found with name "${configName}"`);
464
+ process$1.exit(1);
465
+ }
372
466
  }
373
- setupLogger(overrides?.logLevel || results.config.logLevel);
467
+ setupLogger(options?.overrides?.logLevel || results.config.logLevel);
374
468
  logger.verbose("User config:", formatJson(results.config.changelog));
375
469
  const resolvedConfig = await resolveConfig(results.config, cwd);
376
470
  logger.debug("Resolved config:", formatJson(resolvedConfig));
@@ -499,7 +593,7 @@ function checkGitStatusIfDirty() {
499
593
  if (dirty) {
500
594
  logger.debug("git status:", `
501
595
  ${dirty.trim().split("\n").map((line) => line.trim()).join("\n")}`);
502
- throw new Error("Working directory is not clean");
596
+ throw new Error(dirty);
503
597
  }
504
598
  }
505
599
  async function fetchGitTags(cwd) {
@@ -555,61 +649,88 @@ async function createCommitAndTags({
555
649
  newVersion,
556
650
  dryRun,
557
651
  logLevel
558
- }) {
559
- const filePatternsToAdd = [
560
- "package.json",
561
- "lerna.json",
562
- "CHANGELOG.md",
563
- "**/CHANGELOG.md",
564
- "**/package.json"
565
- ];
566
- logger.start("Start commit and tag");
567
- logger.debug("Adding files to git staging area...");
568
- for (const pattern of filePatternsToAdd) {
569
- if (pattern === "lerna.json" && !hasLernaJson(config.cwd)) {
570
- logger.verbose(`Skipping lerna.json as it doesn't exist`);
571
- continue;
572
- }
573
- if ((pattern === "lerna.json" || pattern === "CHANGELOG.md") && !existsSync(join(config.cwd, pattern))) {
574
- logger.verbose(`Skipping ${pattern} as it doesn't exist`);
575
- continue;
652
+ } = {}) {
653
+ const internalConfig = config || await loadRelizyConfig();
654
+ try {
655
+ await executeHook("before:commit-and-tag", internalConfig, dryRun ?? false);
656
+ const filePatternsToAdd = [
657
+ "package.json",
658
+ "lerna.json",
659
+ "CHANGELOG.md",
660
+ "**/CHANGELOG.md",
661
+ "**/package.json"
662
+ ];
663
+ logger.start("Start commit and tag");
664
+ logger.debug("Adding files to git staging area...");
665
+ for (const pattern of filePatternsToAdd) {
666
+ if (pattern === "lerna.json" && !hasLernaJson(internalConfig.cwd)) {
667
+ logger.verbose(`Skipping lerna.json as it doesn't exist`);
668
+ continue;
669
+ }
670
+ if ((pattern === "lerna.json" || pattern === "CHANGELOG.md") && !existsSync(join(internalConfig.cwd, pattern))) {
671
+ logger.verbose(`Skipping ${pattern} as it doesn't exist`);
672
+ continue;
673
+ }
674
+ if (dryRun) {
675
+ logger.info(`[dry-run] git add ${pattern}`);
676
+ continue;
677
+ }
678
+ try {
679
+ logger.debug(`git add ${pattern}`);
680
+ execSync(`git add ${pattern}`);
681
+ } catch {
682
+ }
576
683
  }
684
+ const rootPackage = getRootPackage(internalConfig.cwd);
685
+ newVersion = newVersion || rootPackage.version;
686
+ const versionForMessage = internalConfig.monorepo?.versionMode === "independent" ? bumpedPackages?.map((pkg) => `${pkg.name}@${pkg.version}`).join(", ") || "unknown" : newVersion || "unknown";
687
+ const commitMessage = internalConfig.templates.commitMessage?.replaceAll("{{newVersion}}", versionForMessage) || `chore(release): bump version to ${versionForMessage}`;
688
+ const noVerifyFlag = noVerify ? "--no-verify " : "";
689
+ logger.debug(`No verify: ${noVerify}`);
577
690
  if (dryRun) {
578
- logger.info(`[dry-run] git add ${pattern}`);
579
- continue;
580
- }
581
- try {
582
- logger.debug(`git add ${pattern}`);
583
- execSync(`git add ${pattern}`);
584
- } catch {
585
- }
586
- }
587
- const rootPackage = getRootPackage(config.cwd);
588
- newVersion = newVersion || rootPackage.version;
589
- const versionForMessage = config.monorepo?.versionMode === "independent" ? bumpedPackages?.map((pkg) => `${pkg.name}@${pkg.version}`).join(", ") || "unknown" : newVersion || "unknown";
590
- const commitMessage = config.templates.commitMessage?.replaceAll("{{newVersion}}", versionForMessage) || `chore(release): bump version to ${versionForMessage}`;
591
- const noVerifyFlag = noVerify ? "--no-verify " : "";
592
- logger.debug(`No verify: ${noVerify}`);
593
- if (dryRun) {
594
- logger.info(`[dry-run] git commit ${noVerifyFlag}-m "${commitMessage}"`);
595
- } else {
596
- logger.debug(`Executing: git commit ${noVerifyFlag}-m "${commitMessage}"`);
597
- await execPromise(`git commit ${noVerifyFlag}-m "${commitMessage}"`, {
598
- logLevel,
599
- noStderr: true,
600
- noStdout: true,
601
- cwd: config.cwd
602
- });
603
- logger.success(`Committed: ${commitMessage}${noVerify ? " (--no-verify)" : ""}`);
604
- }
605
- const signTags = config.signTags ? "-s" : "";
606
- logger.debug(`Sign tags: ${config.signTags}`);
607
- const createdTags = [];
608
- if (config.monorepo?.versionMode === "independent" && bumpedPackages && bumpedPackages.length > 0) {
609
- logger.debug(`Creating ${bumpedPackages.length} independent package tags`);
610
- for (const pkg of bumpedPackages) {
611
- const tagName = `${pkg.name}@${pkg.version}`;
612
- const tagMessage = config.templates?.tagMessage?.replaceAll("{{newVersion}}", pkg.version || "") || tagName;
691
+ logger.info(`[dry-run] git commit ${noVerifyFlag}-m "${commitMessage}"`);
692
+ } else {
693
+ logger.debug(`Executing: git commit ${noVerifyFlag}-m "${commitMessage}"`);
694
+ await execPromise(`git commit ${noVerifyFlag}-m "${commitMessage}"`, {
695
+ logLevel,
696
+ noStderr: true,
697
+ noStdout: true,
698
+ cwd: internalConfig.cwd
699
+ });
700
+ logger.success(`Committed: ${commitMessage}${noVerify ? " (--no-verify)" : ""}`);
701
+ }
702
+ const signTags = internalConfig.signTags ? "-s" : "";
703
+ logger.debug(`Sign tags: ${internalConfig.signTags}`);
704
+ const createdTags = [];
705
+ if (internalConfig.monorepo?.versionMode === "independent" && bumpedPackages && bumpedPackages.length > 0) {
706
+ logger.debug(`Creating ${bumpedPackages.length} independent package tags`);
707
+ for (const pkg of bumpedPackages) {
708
+ const tagName = `${pkg.name}@${pkg.version}`;
709
+ const tagMessage = internalConfig.templates?.tagMessage?.replaceAll("{{newVersion}}", pkg.version || "") || tagName;
710
+ if (dryRun) {
711
+ logger.info(`[dry-run] git tag ${signTags} -a ${tagName} -m "${tagMessage}"`);
712
+ } else {
713
+ const cmd = `git tag ${signTags} -a ${tagName} -m "${tagMessage}"`;
714
+ logger.debug(`Executing: ${cmd}`);
715
+ try {
716
+ await execPromise(cmd, {
717
+ logLevel,
718
+ noStderr: true,
719
+ noStdout: true,
720
+ cwd: internalConfig.cwd
721
+ });
722
+ logger.debug(`Tag created: ${tagName}`);
723
+ } catch (error) {
724
+ logger.error(`Failed to create tag ${tagName}:`, error);
725
+ throw error;
726
+ }
727
+ }
728
+ createdTags.push(tagName);
729
+ }
730
+ logger.success(`Created ${createdTags.length} tags for independent packages, ${createdTags.join(", ")}`);
731
+ } else {
732
+ const tagName = internalConfig.templates.tagBody?.replaceAll("{{newVersion}}", newVersion);
733
+ const tagMessage = internalConfig.templates?.tagMessage?.replaceAll("{{newVersion}}", newVersion) || tagName;
613
734
  if (dryRun) {
614
735
  logger.info(`[dry-run] git tag ${signTags} -a ${tagName} -m "${tagMessage}"`);
615
736
  } else {
@@ -620,7 +741,7 @@ async function createCommitAndTags({
620
741
  logLevel,
621
742
  noStderr: true,
622
743
  noStdout: true,
623
- cwd: config.cwd
744
+ cwd: internalConfig.cwd
624
745
  });
625
746
  logger.debug(`Tag created: ${tagName}`);
626
747
  } catch (error) {
@@ -630,33 +751,15 @@ async function createCommitAndTags({
630
751
  }
631
752
  createdTags.push(tagName);
632
753
  }
633
- logger.success(`Created ${createdTags.length} tags for independent packages, ${createdTags.join(", ")}`);
634
- } else {
635
- const tagName = config.templates.tagBody?.replaceAll("{{newVersion}}", newVersion);
636
- const tagMessage = config.templates?.tagMessage?.replaceAll("{{newVersion}}", newVersion) || tagName;
637
- if (dryRun) {
638
- logger.info(`[dry-run] git tag ${signTags} -a ${tagName} -m "${tagMessage}"`);
639
- } else {
640
- const cmd = `git tag ${signTags} -a ${tagName} -m "${tagMessage}"`;
641
- logger.debug(`Executing: ${cmd}`);
642
- try {
643
- await execPromise(cmd, {
644
- logLevel,
645
- noStderr: true,
646
- noStdout: true,
647
- cwd: config.cwd
648
- });
649
- logger.debug(`Tag created: ${tagName}`);
650
- } catch (error) {
651
- logger.error(`Failed to create tag ${tagName}:`, error);
652
- throw error;
653
- }
654
- }
655
- createdTags.push(tagName);
754
+ logger.debug("Created Tags:", createdTags.join(", "));
755
+ logger.success("Commit and tag completed!");
756
+ await executeHook("after:commit-and-tag", internalConfig, dryRun ?? false);
757
+ return createdTags;
758
+ } catch (error) {
759
+ logger.error("Error committing and tagging:", error);
760
+ await executeHook("error:commit-and-tag", internalConfig, dryRun ?? false);
761
+ throw error;
656
762
  }
657
- logger.debug("Created Tags:", createdTags.join(", "));
658
- logger.success("Commit and tag completed!");
659
- return createdTags;
660
763
  }
661
764
  async function pushCommitAndTags({ dryRun, logLevel, cwd }) {
662
765
  logger.start("Start push changes and tags");
@@ -666,7 +769,7 @@ async function pushCommitAndTags({ dryRun, logLevel, cwd }) {
666
769
  logger.debug("Executing: git push --follow-tags");
667
770
  await execPromise("git push --follow-tags", { noStderr: true, noStdout: true, logLevel, cwd });
668
771
  }
669
- logger.success("End push changes and tags");
772
+ logger.success("Pushing changes and tags completed!");
670
773
  }
671
774
  function getFirstCommit(cwd) {
672
775
  const result = execSync(
@@ -699,8 +802,8 @@ async function githubIndependentMode({
699
802
  if (!repoConfig) {
700
803
  throw new Error("No repository configuration found. Please check your changelog config.");
701
804
  }
702
- logger.debug(`GitHub token: ${config.tokens.github ? "\u2713 provided" : "\u2717 missing"}`);
703
- if (!config.tokens.github) {
805
+ logger.debug(`GitHub token: ${config.tokens.github || config.repo?.token ? "\u2713 provided" : "\u2717 missing"}`);
806
+ if (!config.tokens.github && !config.repo?.token) {
704
807
  throw new Error("No GitHub token specified. Set GITHUB_TOKEN or GH_TOKEN environment variable.");
705
808
  }
706
809
  const packages = bumpedPackages || getPackages({
@@ -783,8 +886,8 @@ async function githubUnified({
783
886
  if (!repoConfig) {
784
887
  throw new Error("No repository configuration found. Please check your changelog config.");
785
888
  }
786
- logger.debug(`GitHub token: ${config.tokens.github ? "\u2713 provided" : "\u2717 missing"}`);
787
- if (!config.tokens.github) {
889
+ logger.debug(`GitHub token: ${config.tokens.github || config.repo?.token ? "\u2713 provided" : "\u2717 missing"}`);
890
+ if (!config.tokens.github && !config.repo?.token) {
788
891
  throw new Error("No GitHub token specified. Set GITHUB_TOKEN or GH_TOKEN environment variable.");
789
892
  }
790
893
  const to = config.templates.tagBody.replace("{{newVersion}}", rootPackage.version);
@@ -838,10 +941,9 @@ async function githubUnified({
838
941
  }
839
942
  async function github(options = {}) {
840
943
  try {
841
- logger.start("Start publishing GitHub release");
842
944
  const dryRun = options.dryRun ?? false;
843
945
  logger.debug(`Dry run: ${dryRun}`);
844
- const config = await loadMonorepoConfig({
946
+ const config = await loadRelizyConfig({
845
947
  configName: options.configName,
846
948
  baseConfig: options.config,
847
949
  overrides: {
@@ -849,7 +951,7 @@ async function github(options = {}) {
849
951
  to: options.to,
850
952
  logLevel: options.logLevel,
851
953
  tokens: {
852
- github: options.token || process.env.CHANGELOGEN_TOKENS_GITHUB || process.env.GITHUB_TOKEN || process.env.GH_TOKEN
954
+ github: options.token
853
955
  }
854
956
  }
855
957
  });
@@ -879,7 +981,7 @@ async function createGitlabRelease({
879
981
  release,
880
982
  dryRun
881
983
  }) {
882
- const token = config.tokens.gitlab || process.env.GITLAB_TOKEN || process.env.CI_JOB_TOKEN;
984
+ const token = config.tokens.gitlab || config.repo?.token;
883
985
  if (!token && !dryRun) {
884
986
  throw new Error(
885
987
  "No GitLab token found. Set GITLAB_TOKEN or CI_JOB_TOKEN environment variable or configure tokens.gitlab"
@@ -940,7 +1042,7 @@ async function gitlabIndependentMode({
940
1042
  dryRun,
941
1043
  bumpedPackages
942
1044
  }) {
943
- logger.debug(`GitLab token: ${config.tokens.gitlab ? "\u2713 provided" : "\u2717 missing"}`);
1045
+ logger.debug(`GitLab token: ${config.tokens.gitlab || config.repo?.token ? "\u2713 provided" : "\u2717 missing"}`);
944
1046
  const packages = bumpedPackages || getPackages({
945
1047
  cwd: config.cwd,
946
1048
  patterns: config.monorepo?.packages,
@@ -1020,7 +1122,7 @@ async function gitlabUnified({
1020
1122
  fromTag,
1021
1123
  oldVersion
1022
1124
  }) {
1023
- logger.debug(`GitLab token: ${config.tokens.gitlab ? "\u2713 provided" : "\u2717 missing"}`);
1125
+ logger.debug(`GitLab token: ${config.tokens.gitlab || config.repo?.token ? "\u2713 provided" : "\u2717 missing"}`);
1024
1126
  const to = config.templates.tagBody.replace("{{newVersion}}", rootPackage.version);
1025
1127
  const commits = await getPackageCommits({
1026
1128
  pkg: rootPackage,
@@ -1077,10 +1179,9 @@ async function gitlabUnified({
1077
1179
  }
1078
1180
  async function gitlab(options = {}) {
1079
1181
  try {
1080
- logger.start("Start publishing GitLab release");
1081
1182
  const dryRun = options.dryRun ?? false;
1082
1183
  logger.debug(`Dry run: ${dryRun}`);
1083
- const config = await loadMonorepoConfig({
1184
+ const config = await loadRelizyConfig({
1084
1185
  configName: options.configName,
1085
1186
  baseConfig: options.config,
1086
1187
  overrides: {
@@ -1088,7 +1189,7 @@ async function gitlab(options = {}) {
1088
1189
  to: options.to,
1089
1190
  logLevel: options.logLevel,
1090
1191
  tokens: {
1091
- gitlab: options.token || process.env.CHANGELOGEN_TOKENS_GITLAB || process.env.GITLAB_TOKEN || process.env.GITLAB_API_TOKEN || process.env.CI_JOB_TOKEN
1192
+ gitlab: options.token
1092
1193
  }
1093
1194
  }
1094
1195
  });
@@ -1976,6 +2077,7 @@ async function resolveTags({
1976
2077
  return tags;
1977
2078
  }
1978
2079
 
2080
+ let sessionOtp;
1979
2081
  function detectPackageManager(cwd = process.cwd()) {
1980
2082
  try {
1981
2083
  const packageJsonPath = join(cwd, "package.json");
@@ -2078,7 +2180,8 @@ function isYarnBerry() {
2078
2180
  function getCommandArgs({
2079
2181
  packageManager,
2080
2182
  tag,
2081
- config
2183
+ config,
2184
+ otp
2082
2185
  }) {
2083
2186
  const args = ["publish", "--tag", tag];
2084
2187
  if (packageManager === "pnpm") {
@@ -2098,12 +2201,72 @@ function getCommandArgs({
2098
2201
  if (access) {
2099
2202
  args.push("--access", access);
2100
2203
  }
2101
- const otp = config.publish.otp;
2102
- if (otp) {
2103
- args.push("--otp", otp);
2204
+ const finalOtp = otp ?? sessionOtp ?? config.publish.otp;
2205
+ if (finalOtp) {
2206
+ args.push("--otp", finalOtp);
2104
2207
  }
2105
2208
  return args;
2106
2209
  }
2210
+ function isOtpError(error) {
2211
+ if (typeof error !== "object" || error === null)
2212
+ return false;
2213
+ const errorMessage = "message" in error && typeof error.message === "string" ? error.message.toLowerCase() : "";
2214
+ return errorMessage.includes("otp") || errorMessage.includes("one-time password") || errorMessage.includes("eotp");
2215
+ }
2216
+ function promptOtpWithTimeout(timeout = 9e4) {
2217
+ return new Promise((resolve, reject) => {
2218
+ const timer = setTimeout(() => {
2219
+ reject(new Error("OTP input timeout"));
2220
+ }, timeout);
2221
+ input({
2222
+ message: "This operation requires a one-time password (OTP). Please enter your OTP:"
2223
+ }).then((otp) => {
2224
+ clearTimeout(timer);
2225
+ resolve(otp);
2226
+ }).catch((error) => {
2227
+ clearTimeout(timer);
2228
+ reject(error);
2229
+ });
2230
+ });
2231
+ }
2232
+ async function handleOtpError() {
2233
+ if (process.env.CI) {
2234
+ logger.error("OTP required but running in CI environment. Please provide OTP via config.");
2235
+ throw new Error("OTP required in CI environment");
2236
+ }
2237
+ logger.warn("Publish failed: OTP required");
2238
+ try {
2239
+ const otp = await promptOtpWithTimeout();
2240
+ logger.debug("OTP received, retrying publish...");
2241
+ return otp;
2242
+ } catch (promptError) {
2243
+ logger.error("Failed to get OTP:", promptError);
2244
+ throw promptError;
2245
+ }
2246
+ }
2247
+ async function executePublishCommand({
2248
+ command,
2249
+ packageNameAndVersion,
2250
+ pkg,
2251
+ config,
2252
+ dryRun
2253
+ }) {
2254
+ logger.debug(`Executing publish command (${command}) in ${pkg.path}`);
2255
+ if (dryRun) {
2256
+ logger.info(`[dry-run] ${packageNameAndVersion}: Run ${command}`);
2257
+ return;
2258
+ }
2259
+ const { stdout } = await execPromise(command, {
2260
+ noStderr: true,
2261
+ noStdout: true,
2262
+ logLevel: config.logLevel,
2263
+ cwd: pkg.path
2264
+ });
2265
+ if (stdout) {
2266
+ logger.debug(stdout);
2267
+ }
2268
+ logger.info(`Published ${packageNameAndVersion}`);
2269
+ }
2107
2270
  async function publishPackage({
2108
2271
  pkg,
2109
2272
  config,
@@ -2111,40 +2274,44 @@ async function publishPackage({
2111
2274
  dryRun
2112
2275
  }) {
2113
2276
  const tag = determinePublishTag(pkg.version, config.publish.tag);
2114
- logger.debug(`Building publish command for ${pkg.name}`);
2115
- const args = getCommandArgs({
2116
- packageManager,
2117
- tag,
2118
- config
2119
- });
2120
- const baseCommand = packageManager === "yarn" && isYarnBerry() ? "yarn npm" : packageManager;
2121
- const command = `${baseCommand} ${args.join(" ")}`;
2122
2277
  const packageNameAndVersion = `${pkg.name}@${pkg.version}`;
2123
- logger.debug(`Publishing ${packageNameAndVersion} with tag '${tag}' with command: ${command}`);
2124
- try {
2125
- process.chdir(pkg.path);
2126
- logger.debug(`Executing publish command (${command}) in ${pkg.path}`);
2127
- if (dryRun) {
2128
- logger.info(`[dry-run] ${packageNameAndVersion}: Run ${command}`);
2129
- } else {
2130
- const { stdout } = await execPromise(command, {
2131
- noStderr: true,
2132
- noStdout: true,
2133
- logLevel: config.logLevel,
2134
- cwd: pkg.path
2278
+ const baseCommand = packageManager === "yarn" && isYarnBerry() ? "yarn npm" : packageManager;
2279
+ logger.debug(`Building publish command for ${pkg.name}`);
2280
+ let dynamicOtp;
2281
+ const maxAttempts = 2;
2282
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
2283
+ try {
2284
+ const args = getCommandArgs({
2285
+ packageManager,
2286
+ tag,
2287
+ config,
2288
+ otp: dynamicOtp
2289
+ });
2290
+ const command = `${baseCommand} ${args.join(" ")}`;
2291
+ logger.debug(`Publishing ${packageNameAndVersion} with tag '${tag}' with command: ${command}`);
2292
+ process.chdir(pkg.path);
2293
+ await executePublishCommand({
2294
+ command,
2295
+ packageNameAndVersion,
2296
+ pkg,
2297
+ config,
2298
+ dryRun
2135
2299
  });
2136
- if (stdout) {
2137
- logger.debug(stdout);
2300
+ if (dynamicOtp && !sessionOtp) {
2301
+ sessionOtp = dynamicOtp;
2302
+ logger.debug("OTP stored for session");
2138
2303
  }
2304
+ return;
2305
+ } catch (error) {
2306
+ if (isOtpError(error) && attempt < maxAttempts - 1) {
2307
+ dynamicOtp = await handleOtpError();
2308
+ } else {
2309
+ logger.error(`Failed to publish ${packageNameAndVersion}:`, error);
2310
+ throw error;
2311
+ }
2312
+ } finally {
2313
+ process.chdir(config.cwd);
2139
2314
  }
2140
- if (!dryRun) {
2141
- logger.info(`Published ${packageNameAndVersion}`);
2142
- }
2143
- } catch (error) {
2144
- logger.error(`Failed to publish ${packageNameAndVersion}:`, error);
2145
- throw error;
2146
- } finally {
2147
- process.chdir(config.cwd);
2148
2315
  }
2149
2316
  }
2150
2317
 
@@ -2393,30 +2560,31 @@ async function bumpSelectiveMode({
2393
2560
  };
2394
2561
  }
2395
2562
  async function bump(options = {}) {
2563
+ const config = await loadRelizyConfig({
2564
+ configName: options.configName,
2565
+ baseConfig: options.config,
2566
+ overrides: {
2567
+ bump: {
2568
+ yes: options.yes,
2569
+ type: options.type,
2570
+ clean: options.clean,
2571
+ preid: options.preid
2572
+ },
2573
+ logLevel: options.logLevel
2574
+ }
2575
+ });
2576
+ const dryRun = options.dryRun ?? false;
2577
+ logger.debug(`Dry run: ${dryRun}`);
2578
+ const force = options.force ?? false;
2579
+ logger.debug(`Bump forced: ${force}`);
2396
2580
  try {
2581
+ await executeHook("before:bump", config, dryRun);
2397
2582
  logger.start("Start bumping versions");
2398
- const dryRun = options.dryRun ?? false;
2399
- logger.debug(`Dry run: ${dryRun}`);
2400
- const force = options.force ?? false;
2401
- logger.debug(`Bump forced: ${force}`);
2402
- const config = await loadMonorepoConfig({
2403
- configName: options.configName,
2404
- baseConfig: options.config,
2405
- overrides: {
2406
- bump: {
2407
- yes: options.yes,
2408
- type: options.type,
2409
- clean: options.clean,
2410
- preid: options.preid
2411
- },
2412
- logLevel: options.logLevel
2413
- }
2414
- });
2415
2583
  if (config.bump.clean && config.release.clean) {
2416
2584
  try {
2417
2585
  checkGitStatusIfDirty();
2418
- } catch {
2419
- logger.error("Git status is dirty, please commit or stash your changes before bumping or use --no-clean flag");
2586
+ } catch (error) {
2587
+ logger.error("Git status is dirty, please commit or stash your changes before bumping or use --no-clean flag", error);
2420
2588
  process.exit(1);
2421
2589
  }
2422
2590
  }
@@ -2449,9 +2617,11 @@ async function bump(options = {}) {
2449
2617
  } else {
2450
2618
  logger.fail("No packages to bump, no commits found");
2451
2619
  }
2620
+ await executeHook("after:bump", config, dryRun);
2452
2621
  return result;
2453
2622
  } catch (error) {
2454
2623
  logger.error("Error bumping versions:", error);
2624
+ await executeHook("error:bump", config, dryRun);
2455
2625
  throw error;
2456
2626
  }
2457
2627
  }
@@ -2577,24 +2747,25 @@ async function generateSimpleRootChangelog({
2577
2747
  }
2578
2748
  }
2579
2749
  async function changelog(options = {}) {
2750
+ const config = await loadRelizyConfig({
2751
+ configName: options.configName,
2752
+ baseConfig: options.config,
2753
+ overrides: {
2754
+ from: options.from,
2755
+ to: options.to,
2756
+ logLevel: options.logLevel,
2757
+ changelog: {
2758
+ rootChangelog: options.rootChangelog,
2759
+ formatCmd: options.formatCmd
2760
+ }
2761
+ }
2762
+ });
2763
+ const dryRun = options.dryRun ?? false;
2764
+ logger.debug(`Dry run: ${dryRun}`);
2765
+ logger.info(`Version mode: ${config.monorepo?.versionMode || "standalone"}`);
2580
2766
  try {
2767
+ await executeHook("before:changelog", config, dryRun);
2581
2768
  logger.start("Start generating changelogs");
2582
- const dryRun = options.dryRun ?? false;
2583
- logger.debug(`Dry run: ${dryRun}`);
2584
- const config = await loadMonorepoConfig({
2585
- configName: options.configName,
2586
- baseConfig: options.config,
2587
- overrides: {
2588
- from: options.from,
2589
- to: options.to,
2590
- logLevel: options.logLevel,
2591
- changelog: {
2592
- rootChangelog: options.rootChangelog,
2593
- formatCmd: options.formatCmd
2594
- }
2595
- }
2596
- });
2597
- logger.info(`Version mode: ${config.monorepo?.versionMode || "standalone"}`);
2598
2769
  const packages = getPackagesToGenerateChangelogFor({
2599
2770
  config,
2600
2771
  bumpedPackages: options.bumpedPackages
@@ -2660,16 +2831,35 @@ async function changelog(options = {}) {
2660
2831
  dryRun
2661
2832
  });
2662
2833
  logger.success(`${dryRun ? "[dry run] " : ""}Changelog generation completed!`);
2834
+ await executeHook("after:changelog", config, dryRun);
2663
2835
  } catch (error) {
2664
2836
  logger.error("Error generating changelogs:", error);
2837
+ await executeHook("error:changelog", config, dryRun);
2665
2838
  throw error;
2666
2839
  }
2667
2840
  }
2668
2841
 
2842
+ function providerReleaseSafetyCheck({ config, provider }) {
2843
+ if (!config.safetyCheck || !config.release.providerRelease) {
2844
+ return;
2845
+ }
2846
+ const internalProvider = provider || config.repo?.provider || detectGitProvider();
2847
+ let token;
2848
+ if (internalProvider === "github") {
2849
+ token = config.tokens?.github || config.repo?.token;
2850
+ } else if (internalProvider === "gitlab") {
2851
+ token = config.tokens?.gitlab || config.repo?.token;
2852
+ } else {
2853
+ logger.error(`Unsupported Git provider: ${internalProvider || "unknown"}`);
2854
+ process.exit(1);
2855
+ }
2856
+ if (!token) {
2857
+ 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`);
2858
+ process.exit(1);
2859
+ }
2860
+ }
2669
2861
  async function providerRelease(options = {}) {
2670
- const dryRun = options.dryRun ?? false;
2671
- logger.debug(`Dry run: ${dryRun}`);
2672
- const config = await loadMonorepoConfig({
2862
+ const config = await loadRelizyConfig({
2673
2863
  configName: options.configName,
2674
2864
  baseConfig: options.config,
2675
2865
  overrides: {
@@ -2679,68 +2869,82 @@ async function providerRelease(options = {}) {
2679
2869
  github: options.token,
2680
2870
  gitlab: options.token
2681
2871
  },
2682
- logLevel: options.logLevel
2872
+ logLevel: options.logLevel,
2873
+ safetyCheck: options.safetyCheck
2683
2874
  }
2684
2875
  });
2876
+ const dryRun = options.dryRun ?? false;
2877
+ logger.debug(`Dry run: ${dryRun}`);
2685
2878
  logger.info(`Version mode: ${config.monorepo?.versionMode || "standalone"}`);
2686
- const detectedProvider = options.provider || detectGitProvider();
2687
- if (!detectedProvider) {
2688
- logger.warn("Unable to detect Git provider. Skipping release publication.");
2689
- throw new Error("Unable to detect Git provider");
2690
- } else {
2691
- logger.info(
2692
- options.provider ? `Using Git provider: ${options.provider}` : `Detected Git provider: ${detectedProvider}`
2693
- );
2694
- }
2695
- let postedReleases = [];
2696
- const payload = {
2697
- from: config.from,
2698
- dryRun,
2699
- config,
2700
- logLevel: config.logLevel,
2701
- bumpResult: options.bumpResult
2702
- };
2703
- if (detectedProvider === "github") {
2704
- postedReleases = await github(payload);
2705
- } else if (detectedProvider === "gitlab") {
2706
- postedReleases = await gitlab(payload);
2707
- } else {
2708
- logger.warn(`Unsupported Git provider: ${detectedProvider}`);
2879
+ try {
2880
+ await executeHook("before:provider-release", config, dryRun);
2881
+ const detectedProvider = options.provider || detectGitProvider();
2882
+ providerReleaseSafetyCheck({ config, provider: detectedProvider });
2883
+ logger.start("Start provider release");
2884
+ if (!detectedProvider) {
2885
+ logger.warn("Unable to detect Git provider. Skipping release publication.");
2886
+ throw new Error("Unable to detect Git provider");
2887
+ } else {
2888
+ logger.info(
2889
+ options.provider ? `Using Git provider: ${options.provider}` : `Detected Git provider: ${detectedProvider}`
2890
+ );
2891
+ }
2892
+ let postedReleases = [];
2893
+ const payload = {
2894
+ from: config.from,
2895
+ dryRun,
2896
+ config,
2897
+ logLevel: config.logLevel,
2898
+ bumpResult: options.bumpResult
2899
+ };
2900
+ if (detectedProvider === "github") {
2901
+ postedReleases = await github(payload);
2902
+ } else if (detectedProvider === "gitlab") {
2903
+ postedReleases = await gitlab(payload);
2904
+ } else {
2905
+ logger.warn(`Unsupported Git provider: ${detectedProvider}`);
2906
+ }
2907
+ await executeHook("after:provider-release", config, dryRun);
2908
+ return {
2909
+ detectedProvider,
2910
+ postedReleases
2911
+ };
2912
+ } catch (error) {
2913
+ logger.error("Error publishing releases:", error);
2914
+ await executeHook("error:provider-release", config, dryRun);
2915
+ throw error;
2709
2916
  }
2710
- return {
2711
- detectedProvider,
2712
- postedReleases
2713
- };
2714
2917
  }
2715
2918
 
2716
2919
  async function publish(options = {}) {
2920
+ const config = await loadRelizyConfig({
2921
+ configName: options.configName,
2922
+ baseConfig: options.config,
2923
+ overrides: {
2924
+ publish: {
2925
+ access: options.access,
2926
+ otp: options.otp,
2927
+ registry: options.registry,
2928
+ tag: options.tag,
2929
+ buildCmd: options.buildCmd
2930
+ },
2931
+ logLevel: options.logLevel
2932
+ }
2933
+ });
2934
+ const dryRun = options.dryRun ?? false;
2935
+ logger.debug(`Dry run: ${dryRun}`);
2936
+ const packageManager = detectPackageManager(process.cwd());
2937
+ logger.debug(`Package manager: ${packageManager}`);
2938
+ logger.info(`Version mode: ${config.monorepo?.versionMode || "standalone"}`);
2939
+ if (config.publish.registry) {
2940
+ logger.debug(`Registry: ${config.publish.registry}`);
2941
+ }
2942
+ if (config.publish.tag) {
2943
+ logger.debug(`Tag: ${config.publish.tag}`);
2944
+ }
2717
2945
  try {
2946
+ await executeHook("before:publish", config, dryRun);
2718
2947
  logger.start("Start publishing packages");
2719
- const dryRun = options.dryRun ?? false;
2720
- logger.debug(`Dry run: ${dryRun}`);
2721
- const packageManager = detectPackageManager(process.cwd());
2722
- logger.debug(`Package manager: ${packageManager}`);
2723
- const config = await loadMonorepoConfig({
2724
- configName: options.configName,
2725
- baseConfig: options.config,
2726
- overrides: {
2727
- publish: {
2728
- access: options.access,
2729
- otp: options.otp,
2730
- registry: options.registry,
2731
- tag: options.tag,
2732
- buildCmd: options.buildCmd
2733
- },
2734
- logLevel: options.logLevel
2735
- }
2736
- });
2737
- logger.info(`Version mode: ${config.monorepo?.versionMode || "standalone"}`);
2738
- if (config.publish.registry) {
2739
- logger.debug(`Registry: ${config.publish.registry}`);
2740
- }
2741
- if (config.publish.tag) {
2742
- logger.debug(`Tag: ${config.publish.tag}`);
2743
- }
2744
2948
  const packages = options.bumpedPackages || getPackages({
2745
2949
  cwd: config.cwd,
2746
2950
  patterns: config.publish.packages ?? config.monorepo?.packages,
@@ -2788,18 +2992,20 @@ async function publish(options = {}) {
2788
2992
  if (!dryRun) {
2789
2993
  logger.info("Package(s) have been published to npm registry");
2790
2994
  }
2791
- logger.success("Publishing completed!");
2995
+ logger.success("Publishing packages completed!");
2996
+ await executeHook("after:publish", config, dryRun);
2792
2997
  return {
2793
2998
  publishedPackages
2794
2999
  };
2795
3000
  } catch (error) {
2796
3001
  logger.error("Error publishing packages:", error);
3002
+ await executeHook("error:publish", config, dryRun);
2797
3003
  throw error;
2798
3004
  }
2799
3005
  }
2800
3006
 
2801
3007
  function getReleaseConfig(options = {}) {
2802
- return loadMonorepoConfig({
3008
+ return loadRelizyConfig({
2803
3009
  configName: options.configName,
2804
3010
  overrides: {
2805
3011
  logLevel: options.logLevel,
@@ -2832,21 +3038,33 @@ function getReleaseConfig(options = {}) {
2832
3038
  push: options.push,
2833
3039
  publish: options.publish,
2834
3040
  noVerify: options.noVerify,
2835
- release: options.release,
3041
+ providerRelease: options.providerRelease,
2836
3042
  clean: options.clean
2837
- }
3043
+ },
3044
+ safetyCheck: options.safetyCheck
2838
3045
  }
2839
3046
  });
2840
3047
  }
3048
+ function releaseSafetyCheck({
3049
+ config,
3050
+ provider
3051
+ }) {
3052
+ if (!config.safetyCheck) {
3053
+ return;
3054
+ }
3055
+ providerReleaseSafetyCheck({ config, provider });
3056
+ }
2841
3057
  async function release(options = {}) {
3058
+ const dryRun = options.dryRun ?? false;
3059
+ logger.debug(`Dry run: ${dryRun}`);
3060
+ const force = options.force ?? false;
3061
+ logger.debug(`Force bump: ${force}`);
3062
+ const config = await getReleaseConfig(options);
3063
+ logger.debug(`Version mode: ${config.monorepo?.versionMode || "standalone"}`);
3064
+ logger.debug(`Push: ${config.release.push}, Publish: ${config.release.publish}, Provider Release: ${config.release.providerRelease}`);
3065
+ releaseSafetyCheck({ config, provider: options.provider });
2842
3066
  try {
2843
- const dryRun = options.dryRun ?? false;
2844
- logger.debug(`Dry run: ${dryRun}`);
2845
- const force = options.force ?? false;
2846
- logger.debug(`Force bump: ${force}`);
2847
- const config = await getReleaseConfig(options);
2848
- logger.debug(`Version mode: ${config.monorepo?.versionMode || "standalone"}`);
2849
- logger.debug(`Push: ${config.release.push}, Publish: ${config.release.publish}, Release: ${config.release.release}`);
3067
+ await executeHook("before:release", config, dryRun);
2850
3068
  logger.box("Step 1/6: Bump versions");
2851
3069
  const bumpResult = await bump({
2852
3070
  type: config.bump.type,
@@ -2893,7 +3111,14 @@ async function release(options = {}) {
2893
3111
  }
2894
3112
  logger.box("Step 4/6: Push changes and tags");
2895
3113
  if (config.release.push && config.release.commit) {
2896
- await pushCommitAndTags({ dryRun, logLevel: config.logLevel, cwd: config.cwd });
3114
+ await executeHook("before:push", config, dryRun);
3115
+ try {
3116
+ await pushCommitAndTags({ dryRun, logLevel: config.logLevel, cwd: config.cwd });
3117
+ await executeHook("after:push", config, dryRun);
3118
+ } catch (error) {
3119
+ await executeHook("error:push", config, dryRun);
3120
+ throw error;
3121
+ }
2897
3122
  } else {
2898
3123
  logger.info("Skipping push (--no-push or --no-commit)");
2899
3124
  }
@@ -2916,7 +3141,7 @@ async function release(options = {}) {
2916
3141
  let provider = config.repo?.provider;
2917
3142
  let postedReleases = [];
2918
3143
  logger.box("Step 6/6: Publish Git release");
2919
- if (config.release.release) {
3144
+ if (config.release.providerRelease) {
2920
3145
  logger.debug(`Provider from config: ${provider}`);
2921
3146
  try {
2922
3147
  const response = await providerRelease({
@@ -2933,7 +3158,7 @@ async function release(options = {}) {
2933
3158
  logger.error("Error during release publication:", error);
2934
3159
  }
2935
3160
  } else {
2936
- logger.info("Skipping release (--no-release)");
3161
+ logger.info("Skipping release (--no-provider-release)");
2937
3162
  }
2938
3163
  const publishedPackageCount = publishResponse?.publishedPackages.length ?? 0;
2939
3164
  const versionDisplay = config.monorepo?.versionMode === "independent" ? `${bumpResult.bumpedPackages.length} packages bumped independently` : bumpResult.newVersion || getRootPackage(config.cwd).version;
@@ -2943,12 +3168,14 @@ Version: ${versionDisplay}
2943
3168
  Tag(s): ${createdTags.length ? createdTags.join(", ") : "No"}
2944
3169
  Pushed: ${config.release.push ? "Yes" : "Disabled"}
2945
3170
  Published packages: ${config.release.publish ? publishedPackageCount : "Disabled"}
2946
- Published release: ${config.release.release ? postedReleases.length : "Disabled"}
3171
+ Published release: ${config.release.providerRelease ? postedReleases.length : "Disabled"}
2947
3172
  Git provider: ${provider}`);
3173
+ await executeHook("after:release", config, dryRun);
2948
3174
  } catch (error) {
3175
+ await executeHook("error:release", config, dryRun);
2949
3176
  logger.error("Error during release workflow:", error);
2950
3177
  throw error;
2951
3178
  }
2952
3179
  }
2953
3180
 
2954
- export { isChangedPreid as $, getCurrentGitRef as A, github as B, createGitlabRelease as C, gitlab as D, getPackages as E, getPackageCommits as F, getRootPackage as G, hasLernaJson as H, getPackageToBump as I, detectPackageManager as J, determinePublishTag as K, getPackagesToPublishInSelectiveMode as L, getPackagesToPublishInIndependentMode as M, publishPackage as N, getLastRepoTag as O, getLastPackageTag as P, resolveTags as Q, determineReleaseType as R, writeVersion as S, bumpPackageVersion as T, updateLernaVersion as U, extractVersionFromPackageTag as V, isPrerelease as W, isStableReleaseType as X, isPrereleaseReleaseType as Y, isGraduating as Z, getPreid as _, publish as a, bumpPackageIndependently as a0, confirmBump as a1, findPackagesWithCommitsAndCalculateVersions as a2, bumpIndependentPackages as a3, bump as b, changelog as c, executeBuildCmd as d, executeFormatCmd as e, getDefaultConfig as f, generateChangelog as g, defineConfig as h, getPackageDependencies as i, getPackagesWithDependencies as j, getDependentsOf as k, loadMonorepoConfig as l, expandPackagesToBumpWithDependents as m, getGitStatus as n, checkGitStatusIfDirty as o, providerRelease as p, fetchGitTags as q, release as r, detectGitProvider as s, topologicalSort as t, parseGitRemoteUrl as u, createCommitAndTags as v, writeChangelogToFile as w, pushCommitAndTags as x, getFirstCommit as y, getCurrentGitBranch as z };
3181
+ export { isStableReleaseType as $, github as A, createGitlabRelease as B, gitlab as C, getPackages as D, getPackageCommits as E, getRootPackage as F, hasLernaJson as G, getPackageToBump as H, detectPackageManager as I, determinePublishTag as J, getPackagesToPublishInSelectiveMode as K, getPackagesToPublishInIndependentMode as L, publishPackage as M, getLastRepoTag as N, getLastPackageTag as O, resolveTags as P, executeHook as Q, isInCI as R, getCIName as S, executeFormatCmd as T, executeBuildCmd as U, determineReleaseType as V, writeVersion as W, bumpPackageVersion as X, updateLernaVersion as Y, extractVersionFromPackageTag as Z, isPrerelease as _, providerRelease as a, isPrereleaseReleaseType as a0, isGraduating as a1, getPreid as a2, isChangedPreid as a3, bumpPackageIndependently as a4, confirmBump as a5, findPackagesWithCommitsAndCalculateVersions as a6, bumpIndependentPackages as a7, bump as b, changelog as c, publish as d, getDefaultConfig as e, defineConfig as f, generateChangelog as g, getPackageDependencies as h, getPackagesWithDependencies 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 };