@eldrforge/kodrdriv 0.0.51 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +2 -1
  2. package/dist/application.js +5 -3
  3. package/dist/application.js.map +1 -1
  4. package/dist/arguments.js +97 -70
  5. package/dist/arguments.js.map +1 -1
  6. package/dist/commands/audio-commit.js +7 -7
  7. package/dist/commands/audio-commit.js.map +1 -1
  8. package/dist/commands/audio-review.js +13 -13
  9. package/dist/commands/audio-review.js.map +1 -1
  10. package/dist/commands/clean.js +2 -2
  11. package/dist/commands/clean.js.map +1 -1
  12. package/dist/commands/commit.js +301 -36
  13. package/dist/commands/commit.js.map +1 -1
  14. package/dist/commands/link.js +7 -7
  15. package/dist/commands/link.js.map +1 -1
  16. package/dist/commands/publish.js +285 -306
  17. package/dist/commands/publish.js.map +1 -1
  18. package/dist/commands/release.js +171 -14
  19. package/dist/commands/release.js.map +1 -1
  20. package/dist/commands/review.js +52 -40
  21. package/dist/commands/review.js.map +1 -1
  22. package/dist/commands/select-audio.js +4 -4
  23. package/dist/commands/select-audio.js.map +1 -1
  24. package/dist/commands/tree.js +347 -34
  25. package/dist/commands/tree.js.map +1 -1
  26. package/dist/commands/unlink.js +5 -5
  27. package/dist/commands/unlink.js.map +1 -1
  28. package/dist/constants.js +28 -9
  29. package/dist/constants.js.map +1 -1
  30. package/dist/content/diff.js +122 -1
  31. package/dist/content/diff.js.map +1 -1
  32. package/dist/content/issues.js +17 -46
  33. package/dist/content/issues.js.map +1 -1
  34. package/dist/logging.js +3 -3
  35. package/dist/logging.js.map +1 -1
  36. package/dist/prompt/commit.js +2 -2
  37. package/dist/prompt/commit.js.map +1 -1
  38. package/dist/prompt/release.js +2 -2
  39. package/dist/prompt/release.js.map +1 -1
  40. package/dist/prompt/review.js +2 -2
  41. package/dist/prompt/review.js.map +1 -1
  42. package/dist/types.js +18 -5
  43. package/dist/types.js.map +1 -1
  44. package/dist/util/child.js +60 -4
  45. package/dist/util/child.js.map +1 -1
  46. package/dist/util/general.js +149 -13
  47. package/dist/util/general.js.map +1 -1
  48. package/dist/util/github.js +12 -8
  49. package/dist/util/github.js.map +1 -1
  50. package/dist/util/interactive.js +297 -0
  51. package/dist/util/interactive.js.map +1 -0
  52. package/dist/util/openai.js +87 -8
  53. package/dist/util/openai.js.map +1 -1
  54. package/dist/util/performance.js +8 -8
  55. package/dist/util/performance.js.map +1 -1
  56. package/dist/util/safety.js +4 -4
  57. package/dist/util/safety.js.map +1 -1
  58. package/dist/util/storage.js +2 -2
  59. package/dist/util/storage.js.map +1 -1
  60. package/package.json +6 -6
  61. package/test-increment.js +0 -0
@@ -1,20 +1,18 @@
1
- import path from 'path';
2
- import { execute as execute$2 } from './commit.js';
1
+ import path__default from 'path';
2
+ import { execute as execute$1 } from './commit.js';
3
3
  import { hasStagedChanges } from '../content/diff.js';
4
- import { execute as execute$3 } from './release.js';
5
- import { execute as execute$4 } from './link.js';
6
- import { execute as execute$1 } from './unlink.js';
4
+ import { execute as execute$2 } from './release.js';
7
5
  import { getDryRunLogger, getLogger } from '../logging.js';
8
6
  import { runWithDryRunSupport, run } from '../util/child.js';
9
7
  import { getCurrentBranchName, findOpenPullRequestByHeadRef, createPullRequest, waitForPullRequestChecks, mergePullRequest, createRelease, getWorkflowsTriggeredByRelease, waitForReleaseWorkflows } from '../util/github.js';
10
8
  import { create } from '../util/storage.js';
11
- import { incrementPatchVersion, getOutputPath } from '../util/general.js';
9
+ import { calculateTargetVersion, checkIfTagExists, confirmVersionInteractively, getOutputPath } from '../util/general.js';
12
10
  import { DEFAULT_OUTPUT_DIRECTORY } from '../constants.js';
13
11
  import { safeJsonParse, validatePackageJson } from '../util/validation.js';
14
12
 
15
13
  const scanNpmrcForEnvVars = async (storage)=>{
16
14
  const logger = getLogger();
17
- const npmrcPath = path.join(process.cwd(), '.npmrc');
15
+ const npmrcPath = path__default.join(process.cwd(), '.npmrc');
18
16
  const envVars = [];
19
17
  if (await storage.exists(npmrcPath)) {
20
18
  try {
@@ -57,7 +55,7 @@ const validateEnvironmentVariables = (requiredEnvVars, isDryRun)=>{
57
55
  }
58
56
  };
59
57
  const runPrechecks = async (runConfig)=>{
60
- var _runConfig_publish;
58
+ var _runConfig_publish, _runConfig_publish1;
61
59
  const isDryRun = runConfig.dryRun || false;
62
60
  const logger = getDryRunLogger(isDryRun);
63
61
  const storage = create({
@@ -96,19 +94,20 @@ const runPrechecks = async (runConfig)=>{
96
94
  throw new Error(`Failed to check git status: ${originalMessage}. Please ensure you are in a valid git repository and try again.`);
97
95
  }
98
96
  }
99
- // Check if we're on a release branch
97
+ // Check that we're not running from the target branch
100
98
  logger.info('Checking current branch...');
99
+ const targetBranch = ((_runConfig_publish = runConfig.publish) === null || _runConfig_publish === void 0 ? void 0 : _runConfig_publish.targetBranch) || 'main';
101
100
  if (isDryRun) {
102
- logger.info('Would verify current branch is a release branch (starts with "release/")');
101
+ logger.info(`Would verify current branch is not the target branch (${targetBranch})`);
103
102
  } else {
104
103
  const currentBranch = await getCurrentBranchName();
105
- if (!currentBranch.startsWith('release/')) {
106
- throw new Error(`Current branch '${currentBranch}' is not a release branch. Please switch to a release branch (e.g., release/1.0.0) before running publish.`);
104
+ if (currentBranch === targetBranch) {
105
+ throw new Error(`Cannot run publish from the target branch '${targetBranch}'. Please switch to a different branch before running publish.`);
107
106
  }
108
107
  }
109
108
  // Check if prepublishOnly script exists in package.json
110
109
  logger.info('Checking for prepublishOnly script...');
111
- const packageJsonPath = path.join(process.cwd(), 'package.json');
110
+ const packageJsonPath = path__default.join(process.cwd(), 'package.json');
112
111
  if (!await storage.exists(packageJsonPath)) {
113
112
  if (!isDryRun) {
114
113
  throw new Error('package.json not found in current directory.');
@@ -139,7 +138,7 @@ const runPrechecks = async (runConfig)=>{
139
138
  }
140
139
  // Check required environment variables
141
140
  logger.verbose('Checking required environment variables...');
142
- const coreRequiredEnvVars = ((_runConfig_publish = runConfig.publish) === null || _runConfig_publish === void 0 ? void 0 : _runConfig_publish.requiredEnvVars) || [];
141
+ const coreRequiredEnvVars = ((_runConfig_publish1 = runConfig.publish) === null || _runConfig_publish1 === void 0 ? void 0 : _runConfig_publish1.requiredEnvVars) || [];
143
142
  const npmrcEnvVars = isDryRun ? [] : await scanNpmrcForEnvVars(storage); // Skip .npmrc scan in dry run
144
143
  const allRequiredEnvVars = [
145
144
  ...new Set([
@@ -156,342 +155,322 @@ const runPrechecks = async (runConfig)=>{
156
155
  logger.info('All prechecks passed.');
157
156
  };
158
157
  const execute = async (runConfig)=>{
158
+ var _runConfig_publish, _runConfig_publish1, _runConfig_publish2;
159
159
  const isDryRun = runConfig.dryRun || false;
160
160
  const logger = getDryRunLogger(isDryRun);
161
161
  const storage = create({
162
162
  log: logger.info
163
163
  });
164
- // Track whether the publish process completed successfully
165
- let publishCompleted = false;
166
- // Track whether we've unlinked packages (and thus need to restore them)
167
- let packagesUnlinked = false;
168
164
  // Run prechecks before starting any work
169
165
  await runPrechecks(runConfig);
170
166
  logger.info('Starting release process...');
171
- try {
172
- var _runConfig_publish, _runConfig_publish1, _runConfig_publish2;
173
- // Unlink all workspace packages before starting (if enabled)
174
- const shouldUnlink = ((_runConfig_publish = runConfig.publish) === null || _runConfig_publish === void 0 ? void 0 : _runConfig_publish.unlinkWorkspacePackages) !== false; // default to true
175
- if (shouldUnlink) {
176
- logger.verbose('Unlinking workspace packages...');
177
- await execute$1(runConfig);
178
- packagesUnlinked = true;
179
- } else {
180
- logger.verbose('Skipping unlink workspace packages (disabled in config).');
181
- }
182
- let pr = null;
167
+ let pr = null;
168
+ if (isDryRun) {
169
+ logger.info('Would check for existing pull request');
170
+ logger.info('Assuming no existing PR found for demo purposes');
171
+ } else {
172
+ const branchName = await getCurrentBranchName();
173
+ pr = await findOpenPullRequestByHeadRef(branchName);
174
+ }
175
+ if (pr) {
176
+ logger.info(`Found existing pull request for branch: ${pr.html_url}`);
177
+ } else {
178
+ var _runConfig_publish3, _runConfig_publish4, _runConfig_publish5;
179
+ logger.info('No open pull request found, starting new release publishing process...');
180
+ // STEP 1: Determine and set target version FIRST (before any commits)
181
+ logger.info('Determining target version...');
182
+ let newVersion;
183
183
  if (isDryRun) {
184
- logger.info('Would check for existing pull request');
185
- logger.info('Assuming no existing PR found for demo purposes');
186
- } else {
187
- const branchName = await getCurrentBranchName();
188
- pr = await findOpenPullRequestByHeadRef(branchName);
189
- }
190
- if (pr) {
191
- logger.info(`Found existing pull request for branch: ${pr.html_url}`);
184
+ logger.info('Would determine target version and update package.json');
185
+ newVersion = '1.0.0'; // Mock version for dry run
192
186
  } else {
193
- var _runConfig_publish3;
194
- logger.info('No open pull request found, starting new release publishing process...');
195
- // 1. Prepare for release
196
- logger.verbose('Preparing for release: switching from workspace to remote dependencies.');
197
- logger.verbose('Updating dependencies to latest versions from registry');
198
- const updatePatterns = (_runConfig_publish3 = runConfig.publish) === null || _runConfig_publish3 === void 0 ? void 0 : _runConfig_publish3.dependencyUpdatePatterns;
199
- if (updatePatterns && updatePatterns.length > 0) {
200
- logger.verbose(`Updating dependencies matching patterns: ${updatePatterns.join(', ')}`);
201
- const patternsArg = updatePatterns.join(' ');
202
- await runWithDryRunSupport(`npm update ${patternsArg}`, isDryRun);
203
- } else {
204
- logger.verbose('No dependency update patterns specified, updating all dependencies');
205
- await runWithDryRunSupport('npm update', isDryRun);
187
+ var _runConfig_publish6, _runConfig_publish7;
188
+ const packageJsonContents = await storage.readFile('package.json', 'utf-8');
189
+ const parsed = safeJsonParse(packageJsonContents, 'package.json');
190
+ const packageJson = validatePackageJson(parsed, 'package.json');
191
+ const currentVersion = packageJson.version;
192
+ // Determine target version based on --targetVersion option
193
+ const targetVersionInput = ((_runConfig_publish6 = runConfig.publish) === null || _runConfig_publish6 === void 0 ? void 0 : _runConfig_publish6.targetVersion) || 'patch';
194
+ const proposedVersion = calculateTargetVersion(currentVersion, targetVersionInput);
195
+ // Check if target tag already exists
196
+ const targetTagName = `v${proposedVersion}`;
197
+ const tagExists = await checkIfTagExists(targetTagName);
198
+ if (tagExists) {
199
+ throw new Error(`Tag ${targetTagName} already exists. Please choose a different version or delete the existing tag.`);
206
200
  }
207
- logger.verbose('Staging changes for release commit');
208
- await runWithDryRunSupport('git add package.json package-lock.json', isDryRun);
209
- logger.info('Running prepublishOnly script...');
210
- await runWithDryRunSupport('npm run prepublishOnly', isDryRun);
211
- logger.verbose('Checking for staged changes...');
212
- if (isDryRun) {
213
- logger.verbose('Assuming staged changes exist for demo purposes');
214
- logger.verbose('Would create commit...');
215
- await execute$2(runConfig);
216
- } else {
217
- if (await hasStagedChanges()) {
218
- logger.verbose('Staged changes found, creating commit...');
219
- await execute$2(runConfig);
220
- } else {
221
- logger.verbose('No changes to commit, skipping commit.');
201
+ // Interactive confirmation if --interactive flag is set
202
+ if ((_runConfig_publish7 = runConfig.publish) === null || _runConfig_publish7 === void 0 ? void 0 : _runConfig_publish7.interactive) {
203
+ newVersion = await confirmVersionInteractively(currentVersion, proposedVersion, targetVersionInput);
204
+ // Re-check if the confirmed version's tag exists (in case user entered custom version)
205
+ const confirmedTagName = `v${newVersion}`;
206
+ const confirmedTagExists = await checkIfTagExists(confirmedTagName);
207
+ if (confirmedTagExists) {
208
+ throw new Error(`Tag ${confirmedTagName} already exists. Please choose a different version or delete the existing tag.`);
222
209
  }
223
- }
224
- logger.info('Bumping version...');
225
- // Manually increment version without creating a tag
226
- if (isDryRun) {
227
- logger.info('Would manually increment patch version in package.json and commit');
228
- } else {
229
- const packageJsonContents = await storage.readFile('package.json', 'utf-8');
230
- const parsed = safeJsonParse(packageJsonContents, 'package.json');
231
- const packageJson = validatePackageJson(parsed, 'package.json');
232
- const currentVersion = packageJson.version;
233
- const newVersion = incrementPatchVersion(currentVersion);
234
- packageJson.version = newVersion;
235
- await storage.writeFile('package.json', JSON.stringify(packageJson, null, 2) + '\n', 'utf-8');
236
- logger.info(`Version bumped from ${currentVersion} to ${newVersion}`);
237
- // Stage and commit the version change
238
- await run('git add package.json');
239
- await run(`git commit -m "chore: bump version to ${newVersion}"`);
240
- logger.info(`Version change committed: ${newVersion}`);
241
- }
242
- logger.info('Generating release notes...');
243
- const releaseSummary = await execute$3(runConfig);
244
- if (isDryRun) {
245
- logger.info('Would write release notes to RELEASE_NOTES.md and RELEASE_TITLE.md in output directory');
246
210
  } else {
247
- const outputDirectory = runConfig.outputDirectory || DEFAULT_OUTPUT_DIRECTORY;
248
- await storage.ensureDirectory(outputDirectory);
249
- const releaseNotesPath = getOutputPath(outputDirectory, 'RELEASE_NOTES.md');
250
- const releaseTitlePath = getOutputPath(outputDirectory, 'RELEASE_TITLE.md');
251
- await storage.writeFile(releaseNotesPath, releaseSummary.body, 'utf-8');
252
- await storage.writeFile(releaseTitlePath, releaseSummary.title, 'utf-8');
253
- logger.info(`Release notes and title generated and saved to ${releaseNotesPath} and ${releaseTitlePath}.`);
254
- }
255
- logger.info('Pushing to origin...');
256
- // Get current branch name and push explicitly to avoid pushing to wrong remote/branch
257
- const currentBranch = await getCurrentBranchName();
258
- await runWithDryRunSupport(`git push origin ${currentBranch}`, isDryRun);
259
- logger.info('Creating pull request...');
260
- if (isDryRun) {
261
- logger.info('Would get commit title and create PR with GitHub API');
262
- pr = {
263
- number: 123,
264
- html_url: 'https://github.com/mock/repo/pull/123',
265
- labels: []
266
- };
267
- } else {
268
- const { stdout: commitTitle } = await run('git log -1 --pretty=%B');
269
- pr = await createPullRequest(commitTitle, 'Automated release PR.', await getCurrentBranchName());
270
- if (!pr) {
271
- throw new Error('Failed to create pull request.');
272
- }
273
- logger.info(`Pull request created: ${pr.html_url}`);
211
+ newVersion = proposedVersion;
274
212
  }
213
+ logger.info(`Bumping version from ${currentVersion} to ${newVersion}`);
214
+ // Update package.json with the new version BEFORE any other operations
215
+ packageJson.version = newVersion;
216
+ await storage.writeFile('package.json', JSON.stringify(packageJson, null, 2) + '\n', 'utf-8');
217
+ logger.info(`Version updated in package.json: ${newVersion}`);
275
218
  }
276
- logger.info(`Waiting for PR #${pr.number} checks to complete...`);
277
- if (!isDryRun) {
278
- var _runConfig_publish4, _runConfig_publish5, _runConfig_publish6;
279
- // Configure timeout and user confirmation behavior
280
- const timeout = ((_runConfig_publish4 = runConfig.publish) === null || _runConfig_publish4 === void 0 ? void 0 : _runConfig_publish4.checksTimeout) || 300000; // 5 minutes default
281
- const senditMode = ((_runConfig_publish5 = runConfig.publish) === null || _runConfig_publish5 === void 0 ? void 0 : _runConfig_publish5.sendit) || false;
282
- // sendit flag overrides skipUserConfirmation - if sendit is true, skip confirmation
283
- const skipUserConfirmation = senditMode || ((_runConfig_publish6 = runConfig.publish) === null || _runConfig_publish6 === void 0 ? void 0 : _runConfig_publish6.skipUserConfirmation) || false;
284
- await waitForPullRequestChecks(pr.number, {
285
- timeout,
286
- skipUserConfirmation
287
- });
288
- }
289
- const mergeMethod = ((_runConfig_publish1 = runConfig.publish) === null || _runConfig_publish1 === void 0 ? void 0 : _runConfig_publish1.mergeMethod) || 'squash';
290
- if (isDryRun) {
291
- logger.info(`Would merge PR #${pr.number} using ${mergeMethod} method`);
219
+ // STEP 2: Prepare for release (with correct version now in package.json)
220
+ logger.verbose('Preparing for release: switching from workspace to remote dependencies.');
221
+ logger.verbose('Updating dependencies to latest versions from registry');
222
+ const updatePatterns = (_runConfig_publish3 = runConfig.publish) === null || _runConfig_publish3 === void 0 ? void 0 : _runConfig_publish3.dependencyUpdatePatterns;
223
+ if (updatePatterns && updatePatterns.length > 0) {
224
+ logger.verbose(`Updating dependencies matching patterns: ${updatePatterns.join(', ')}`);
225
+ const patternsArg = updatePatterns.join(' ');
226
+ await runWithDryRunSupport(`npm update ${patternsArg}`, isDryRun);
292
227
  } else {
293
- try {
294
- await mergePullRequest(pr.number, mergeMethod);
295
- } catch (error) {
296
- // Check if this is a merge conflict error
297
- if (error.message && (error.message.includes('not mergeable') || error.message.includes('Pull Request is not mergeable') || error.message.includes('merge conflict'))) {
298
- logger.error(`❌ Pull Request #${pr.number} has merge conflicts that need to be resolved.`);
299
- logger.error('');
300
- logger.error('📋 To resolve this issue:');
301
- logger.error(` 1. Visit the Pull Request: ${pr.html_url}`);
302
- logger.error(' 2. Resolve the merge conflicts through GitHub\'s web interface or locally');
303
- logger.error(' 3. Once conflicts are resolved, re-run the publish command');
304
- logger.error('');
305
- logger.error('💡 The command will automatically detect the existing PR and continue from where it left off.');
306
- throw new Error(`Merge conflicts detected in PR #${pr.number}. Please resolve conflicts and re-run the command.`);
307
- } else {
308
- // Re-throw other merge errors
309
- throw error;
310
- }
311
- }
228
+ logger.verbose('No dependency update patterns specified, updating all dependencies');
229
+ await runWithDryRunSupport('npm update', isDryRun);
312
230
  }
313
- logger.info('Checking out main branch...');
314
- await runWithDryRunSupport('git checkout main', isDryRun);
315
- await runWithDryRunSupport('git pull origin main', isDryRun);
316
- // Now create and push the tag on the main branch
317
- logger.info('Creating release tag...');
318
- let tagName;
231
+ logger.info('Running prepublishOnly script...');
232
+ await runWithDryRunSupport('npm run prepublishOnly', isDryRun, {}, true); // Use inherited stdio
233
+ // STEP 3: Stage all changes (version bump + dependencies + any build artifacts)
234
+ logger.verbose('Staging all changes for release commit');
235
+ await runWithDryRunSupport('git add package.json package-lock.json', isDryRun);
236
+ logger.verbose('Checking for staged changes...');
319
237
  if (isDryRun) {
320
- logger.info('Would read package.json version and create git tag');
321
- tagName = 'v1.0.0'; // Mock version for dry run
238
+ logger.verbose('Assuming staged changes exist for demo purposes');
239
+ logger.verbose('Would create commit...');
240
+ await execute$1(runConfig);
322
241
  } else {
323
- const packageJsonContents = await storage.readFile('package.json', 'utf-8');
324
- const { version } = JSON.parse(packageJsonContents);
325
- tagName = `v${version}`;
326
- // Check if tag already exists locally
327
- try {
328
- const { stdout } = await run(`git tag -l ${tagName}`);
329
- if (stdout.trim() === tagName) {
330
- logger.info(`Tag ${tagName} already exists locally, skipping tag creation`);
331
- } else {
332
- await run(`git tag ${tagName}`);
333
- logger.info(`Created local tag: ${tagName}`);
334
- }
335
- } catch (error) {
336
- // If git tag -l fails, create the tag anyway
337
- await run(`git tag ${tagName}`);
338
- logger.info(`Created local tag: ${tagName}`);
242
+ if (await hasStagedChanges()) {
243
+ logger.verbose('Staged changes found, creating commit...');
244
+ await execute$1(runConfig);
245
+ } else {
246
+ logger.verbose('No changes to commit, skipping commit.');
339
247
  }
340
- // Check if tag exists on remote before pushing
341
- let tagWasPushed = false;
342
- try {
343
- const { stdout } = await run(`git ls-remote origin refs/tags/${tagName}`);
344
- if (stdout.trim()) {
345
- logger.info(`Tag ${tagName} already exists on remote, skipping push`);
346
- } else {
347
- await run(`git push origin ${tagName}`);
348
- logger.info(`Pushed tag to remote: ${tagName}`);
349
- tagWasPushed = true;
350
- }
351
- } catch (error) {
352
- // If ls-remote fails, try to push anyway (might be a new remote)
353
- try {
354
- await run(`git push origin ${tagName}`);
355
- logger.info(`Pushed tag to remote: ${tagName}`);
356
- tagWasPushed = true;
357
- } catch (pushError) {
358
- if (pushError.message && pushError.message.includes('already exists')) {
359
- logger.info(`Tag ${tagName} already exists on remote, continuing...`);
360
- } else {
361
- throw pushError;
362
- }
248
+ }
249
+ logger.info('Generating release notes...');
250
+ // Create a modified config for release notes generation that includes the publish --from and --interactive options
251
+ const releaseConfig = {
252
+ ...runConfig
253
+ };
254
+ if (((_runConfig_publish4 = runConfig.publish) === null || _runConfig_publish4 === void 0 ? void 0 : _runConfig_publish4.from) || ((_runConfig_publish5 = runConfig.publish) === null || _runConfig_publish5 === void 0 ? void 0 : _runConfig_publish5.interactive)) {
255
+ // Pass the publish --from and --interactive options to the release config
256
+ releaseConfig.release = {
257
+ ...runConfig.release,
258
+ ...runConfig.publish.from && {
259
+ from: runConfig.publish.from
260
+ },
261
+ ...runConfig.publish.interactive && {
262
+ interactive: runConfig.publish.interactive
363
263
  }
264
+ };
265
+ if (runConfig.publish.from) {
266
+ logger.verbose(`Using custom 'from' reference for release notes: ${runConfig.publish.from}`);
364
267
  }
365
- // If we just pushed a new tag, wait for GitHub to process it
366
- if (tagWasPushed) {
367
- logger.verbose('Waiting for GitHub to process the pushed tag...');
368
- await new Promise((resolve)=>setTimeout(resolve, 5000)); // 5 second delay
268
+ if (runConfig.publish.interactive) {
269
+ logger.verbose('Interactive mode enabled for release notes generation');
369
270
  }
370
271
  }
371
- logger.info('Creating GitHub release...');
272
+ const releaseSummary = await execute$2(releaseConfig);
372
273
  if (isDryRun) {
373
- logger.info('Would read package.json version and create GitHub release with retry logic');
274
+ logger.info('Would write release notes to RELEASE_NOTES.md and RELEASE_TITLE.md in output directory');
374
275
  } else {
375
276
  const outputDirectory = runConfig.outputDirectory || DEFAULT_OUTPUT_DIRECTORY;
277
+ await storage.ensureDirectory(outputDirectory);
376
278
  const releaseNotesPath = getOutputPath(outputDirectory, 'RELEASE_NOTES.md');
377
279
  const releaseTitlePath = getOutputPath(outputDirectory, 'RELEASE_TITLE.md');
378
- const releaseNotesContent = await storage.readFile(releaseNotesPath, 'utf-8');
379
- const releaseTitle = await storage.readFile(releaseTitlePath, 'utf-8');
380
- // Create release with retry logic to handle GitHub tag processing delays
381
- let retries = 3;
382
- while(retries > 0){
383
- try {
384
- await createRelease(tagName, releaseTitle, releaseNotesContent);
385
- logger.info(`GitHub release created successfully for tag: ${tagName}`);
386
- break; // Success - exit retry loop
387
- } catch (error) {
388
- // Check if this is a tag-not-found error that we can retry
389
- const isTagNotFoundError = error.message && (error.message.includes('not found') || error.message.includes('does not exist') || error.message.includes('Reference does not exist'));
390
- if (isTagNotFoundError && retries > 1) {
391
- logger.verbose(`Tag ${tagName} not yet available on GitHub, retrying in 3 seconds... (${retries - 1} retries left)`);
392
- await new Promise((resolve)=>setTimeout(resolve, 3000));
393
- retries--;
394
- } else if (isTagNotFoundError) {
395
- // Tag not found error and we're out of retries
396
- throw new Error(`Tag ${tagName} was not found on GitHub after ${3 - retries + 1} attempts. This may indicate a problem with tag creation or GitHub synchronization.`);
397
- } else {
398
- // Not a tag-not-found error - re-throw the original error
399
- throw error;
400
- }
401
- }
280
+ await storage.writeFile(releaseNotesPath, releaseSummary.body, 'utf-8');
281
+ await storage.writeFile(releaseTitlePath, releaseSummary.title, 'utf-8');
282
+ logger.info(`Release notes and title generated and saved to ${releaseNotesPath} and ${releaseTitlePath}.`);
283
+ }
284
+ logger.info('Pushing to origin...');
285
+ // Get current branch name and push explicitly to avoid pushing to wrong remote/branch
286
+ const currentBranch = await getCurrentBranchName();
287
+ await runWithDryRunSupport(`git push origin ${currentBranch}`, isDryRun);
288
+ logger.info('Creating pull request...');
289
+ if (isDryRun) {
290
+ logger.info('Would get commit title and create PR with GitHub API');
291
+ pr = {
292
+ number: 123,
293
+ html_url: 'https://github.com/mock/repo/pull/123',
294
+ labels: []
295
+ };
296
+ } else {
297
+ const { stdout: commitTitle } = await run('git log -1 --pretty=%B');
298
+ pr = await createPullRequest(commitTitle, 'Automated release PR.', await getCurrentBranchName());
299
+ if (!pr) {
300
+ throw new Error('Failed to create pull request.');
402
301
  }
302
+ logger.info(`Pull request created: ${pr.html_url}`);
403
303
  }
404
- // Wait for release workflows to complete (if enabled)
405
- const waitForWorkflows = ((_runConfig_publish2 = runConfig.publish) === null || _runConfig_publish2 === void 0 ? void 0 : _runConfig_publish2.waitForReleaseWorkflows) !== false; // default to true
406
- if (waitForWorkflows) {
407
- logger.info('Waiting for release workflows...');
408
- if (isDryRun) {
409
- logger.info('Would monitor GitHub Actions workflows triggered by release');
304
+ }
305
+ logger.info(`Waiting for PR #${pr.number} checks to complete...`);
306
+ if (!isDryRun) {
307
+ var _runConfig_publish8, _runConfig_publish9, _runConfig_publish10;
308
+ // Configure timeout and user confirmation behavior
309
+ const timeout = ((_runConfig_publish8 = runConfig.publish) === null || _runConfig_publish8 === void 0 ? void 0 : _runConfig_publish8.checksTimeout) || 300000; // 5 minutes default
310
+ const senditMode = ((_runConfig_publish9 = runConfig.publish) === null || _runConfig_publish9 === void 0 ? void 0 : _runConfig_publish9.sendit) || false;
311
+ // sendit flag overrides skipUserConfirmation - if sendit is true, skip confirmation
312
+ const skipUserConfirmation = senditMode || ((_runConfig_publish10 = runConfig.publish) === null || _runConfig_publish10 === void 0 ? void 0 : _runConfig_publish10.skipUserConfirmation) || false;
313
+ await waitForPullRequestChecks(pr.number, {
314
+ timeout,
315
+ skipUserConfirmation
316
+ });
317
+ }
318
+ const mergeMethod = ((_runConfig_publish = runConfig.publish) === null || _runConfig_publish === void 0 ? void 0 : _runConfig_publish.mergeMethod) || 'squash';
319
+ if (isDryRun) {
320
+ logger.info(`Would merge PR #${pr.number} using ${mergeMethod} method`);
321
+ } else {
322
+ try {
323
+ await mergePullRequest(pr.number, mergeMethod, false); // Don't delete branch
324
+ } catch (error) {
325
+ // Check if this is a merge conflict error
326
+ if (error.message && (error.message.includes('not mergeable') || error.message.includes('Pull Request is not mergeable') || error.message.includes('merge conflict'))) {
327
+ logger.error(`❌ Pull Request #${pr.number} has merge conflicts that need to be resolved.`);
328
+ logger.error('');
329
+ logger.error('📋 To resolve this issue:');
330
+ logger.error(` 1. Visit the Pull Request: ${pr.html_url}`);
331
+ logger.error(' 2. Resolve the merge conflicts through GitHub\'s web interface or locally');
332
+ logger.error(' 3. Once conflicts are resolved, re-run the publish command');
333
+ logger.error('');
334
+ logger.error('💡 The command will automatically detect the existing PR and continue from where it left off.');
335
+ throw new Error(`Merge conflicts detected in PR #${pr.number}. Please resolve conflicts and re-run the command.`);
410
336
  } else {
411
- var _runConfig_publish7, _runConfig_publish8, _runConfig_publish9, _runConfig_publish10;
412
- const workflowTimeout = ((_runConfig_publish7 = runConfig.publish) === null || _runConfig_publish7 === void 0 ? void 0 : _runConfig_publish7.releaseWorkflowsTimeout) || 600000; // 10 minutes default
413
- const senditMode = ((_runConfig_publish8 = runConfig.publish) === null || _runConfig_publish8 === void 0 ? void 0 : _runConfig_publish8.sendit) || false;
414
- const skipUserConfirmation = senditMode || ((_runConfig_publish9 = runConfig.publish) === null || _runConfig_publish9 === void 0 ? void 0 : _runConfig_publish9.skipUserConfirmation) || false;
415
- // Get workflow names - either from config or auto-detect
416
- let workflowNames = (_runConfig_publish10 = runConfig.publish) === null || _runConfig_publish10 === void 0 ? void 0 : _runConfig_publish10.releaseWorkflowNames;
417
- if (!workflowNames || workflowNames.length === 0) {
418
- logger.info('No specific workflow names configured, auto-detecting workflows triggered by release events...');
419
- try {
420
- workflowNames = await getWorkflowsTriggeredByRelease();
421
- if (workflowNames.length === 0) {
422
- logger.info('No workflows found that are triggered by release events.');
423
- } else {
424
- logger.info(`Auto-detected release workflows: ${workflowNames.join(', ')}`);
425
- }
426
- } catch (error) {
427
- logger.warn(`Failed to auto-detect release workflows: ${error.message}`);
428
- workflowNames = undefined; // Fall back to monitoring all workflows
429
- }
430
- }
431
- await waitForReleaseWorkflows(tagName, {
432
- timeout: workflowTimeout,
433
- workflowNames,
434
- skipUserConfirmation
435
- });
337
+ // Re-throw other merge errors
338
+ throw error;
436
339
  }
437
- } else {
438
- logger.verbose('Skipping waiting for release workflows (disabled in config).');
439
340
  }
440
- logger.info('Creating new release branch...');
441
- if (isDryRun) {
442
- logger.info('Would create next release branch (e.g., release/1.0.1) and push to origin');
443
- } else {
444
- const packageJsonContents = await storage.readFile('package.json', 'utf-8');
445
- const { version } = JSON.parse(packageJsonContents);
446
- const nextVersion = incrementPatchVersion(version);
447
- const newBranchName = `release/${nextVersion}`;
448
- // Check if branch already exists locally
449
- let branchExists = false;
450
- try {
451
- await run(`git show-ref --verify --quiet refs/heads/${newBranchName}`);
452
- branchExists = true;
453
- } catch {
454
- // Branch doesn't exist locally
455
- branchExists = false;
341
+ }
342
+ logger.info('Checking out main branch...');
343
+ await runWithDryRunSupport('git checkout main', isDryRun);
344
+ await runWithDryRunSupport('git pull origin main', isDryRun);
345
+ // Now create and push the tag on the main branch
346
+ logger.info('Creating release tag...');
347
+ let tagName;
348
+ if (isDryRun) {
349
+ logger.info('Would read package.json version and create git tag');
350
+ tagName = 'v1.0.0'; // Mock version for dry run
351
+ } else {
352
+ const packageJsonContents = await storage.readFile('package.json', 'utf-8');
353
+ const { version } = JSON.parse(packageJsonContents);
354
+ tagName = `v${version}`;
355
+ // Check if tag already exists locally
356
+ try {
357
+ const { stdout } = await run(`git tag -l ${tagName}`);
358
+ if (stdout.trim() === tagName) {
359
+ logger.info(`Tag ${tagName} already exists locally, skipping tag creation`);
360
+ } else {
361
+ await run(`git tag ${tagName}`);
362
+ logger.info(`Created local tag: ${tagName}`);
456
363
  }
457
- if (branchExists) {
458
- // Branch exists, switch to it
459
- await run(`git checkout ${newBranchName}`);
460
- logger.info(`Switched to existing branch ${newBranchName}`);
364
+ } catch (error) {
365
+ // If git tag -l fails, create the tag anyway
366
+ await run(`git tag ${tagName}`);
367
+ logger.info(`Created local tag: ${tagName}`);
368
+ }
369
+ // Check if tag exists on remote before pushing
370
+ let tagWasPushed = false;
371
+ try {
372
+ const { stdout } = await run(`git ls-remote origin refs/tags/${tagName}`);
373
+ if (stdout.trim()) {
374
+ logger.info(`Tag ${tagName} already exists on remote, skipping push`);
461
375
  } else {
462
- // Branch doesn't exist, create it
463
- await run(`git checkout -b ${newBranchName}`);
464
- logger.info(`Created new branch ${newBranchName}`);
376
+ await run(`git push origin ${tagName}`);
377
+ logger.info(`Pushed tag to remote: ${tagName}`);
378
+ tagWasPushed = true;
465
379
  }
466
- // Check if branch exists on remote before pushing
467
- let remoteExists = false;
380
+ } catch (error) {
381
+ // If ls-remote fails, try to push anyway (might be a new remote)
468
382
  try {
469
- const { stdout } = await run(`git ls-remote origin refs/heads/${newBranchName}`);
470
- remoteExists = stdout.trim() !== '';
471
- } catch {
472
- // Assume remote doesn't exist if ls-remote fails
473
- remoteExists = false;
383
+ await run(`git push origin ${tagName}`);
384
+ logger.info(`Pushed tag to remote: ${tagName}`);
385
+ tagWasPushed = true;
386
+ } catch (pushError) {
387
+ if (pushError.message && pushError.message.includes('already exists')) {
388
+ logger.info(`Tag ${tagName} already exists on remote, continuing...`);
389
+ } else {
390
+ throw pushError;
391
+ }
474
392
  }
475
- if (remoteExists) {
476
- logger.info(`Branch ${newBranchName} already exists on remote, skipping push`);
477
- } else {
478
- await run(`git push -u origin ${newBranchName}`);
479
- logger.info(`Branch ${newBranchName} pushed to origin.`);
393
+ }
394
+ // If we just pushed a new tag, wait for GitHub to process it
395
+ if (tagWasPushed) {
396
+ logger.verbose('Waiting for GitHub to process the pushed tag...');
397
+ await new Promise((resolve)=>setTimeout(resolve, 5000)); // 5 second delay
398
+ }
399
+ }
400
+ logger.info('Creating GitHub release...');
401
+ if (isDryRun) {
402
+ logger.info('Would read package.json version and create GitHub release with retry logic');
403
+ } else {
404
+ const outputDirectory = runConfig.outputDirectory || DEFAULT_OUTPUT_DIRECTORY;
405
+ const releaseNotesPath = getOutputPath(outputDirectory, 'RELEASE_NOTES.md');
406
+ const releaseTitlePath = getOutputPath(outputDirectory, 'RELEASE_TITLE.md');
407
+ const releaseNotesContent = await storage.readFile(releaseNotesPath, 'utf-8');
408
+ const releaseTitle = await storage.readFile(releaseTitlePath, 'utf-8');
409
+ // Create release with retry logic to handle GitHub tag processing delays
410
+ let retries = 3;
411
+ while(retries > 0){
412
+ try {
413
+ await createRelease(tagName, releaseTitle, releaseNotesContent);
414
+ logger.info(`GitHub release created successfully for tag: ${tagName}`);
415
+ break; // Success - exit retry loop
416
+ } catch (error) {
417
+ // Check if this is a tag-not-found error that we can retry
418
+ const isTagNotFoundError = error.message && (error.message.includes('not found') || error.message.includes('does not exist') || error.message.includes('Reference does not exist'));
419
+ if (isTagNotFoundError && retries > 1) {
420
+ logger.verbose(`Tag ${tagName} not yet available on GitHub, retrying in 3 seconds... (${retries - 1} retries left)`);
421
+ await new Promise((resolve)=>setTimeout(resolve, 3000));
422
+ retries--;
423
+ } else if (isTagNotFoundError) {
424
+ // Tag not found error and we're out of retries
425
+ throw new Error(`Tag ${tagName} was not found on GitHub after ${3 - retries + 1} attempts. This may indicate a problem with tag creation or GitHub synchronization.`);
426
+ } else {
427
+ // Not a tag-not-found error - re-throw the original error
428
+ throw error;
429
+ }
480
430
  }
481
431
  }
482
- logger.info('Preparation complete.');
483
- publishCompleted = true; // Mark as completed only if we reach this point
484
- } finally{
485
- var _runConfig_publish11;
486
- // Link packages if linking is enabled, regardless of whether we unlinked them
487
- const shouldLink = ((_runConfig_publish11 = runConfig.publish) === null || _runConfig_publish11 === void 0 ? void 0 : _runConfig_publish11.linkWorkspacePackages) !== false; // default to true
488
- if (shouldLink) {
489
- logger.verbose('Ensuring linked packages are properly set up...');
490
- await execute$4(runConfig);
432
+ }
433
+ // Wait for release workflows to complete (if enabled)
434
+ const waitForWorkflows = ((_runConfig_publish1 = runConfig.publish) === null || _runConfig_publish1 === void 0 ? void 0 : _runConfig_publish1.waitForReleaseWorkflows) !== false; // default to true
435
+ if (waitForWorkflows) {
436
+ logger.info('Waiting for release workflows...');
437
+ if (isDryRun) {
438
+ logger.info('Would monitor GitHub Actions workflows triggered by release');
491
439
  } else {
492
- logger.verbose('Skipping link packages (disabled in config).');
440
+ var _runConfig_publish11, _runConfig_publish12, _runConfig_publish13, _runConfig_publish14;
441
+ const workflowTimeout = ((_runConfig_publish11 = runConfig.publish) === null || _runConfig_publish11 === void 0 ? void 0 : _runConfig_publish11.releaseWorkflowsTimeout) || 600000; // 10 minutes default
442
+ const senditMode = ((_runConfig_publish12 = runConfig.publish) === null || _runConfig_publish12 === void 0 ? void 0 : _runConfig_publish12.sendit) || false;
443
+ const skipUserConfirmation = senditMode || ((_runConfig_publish13 = runConfig.publish) === null || _runConfig_publish13 === void 0 ? void 0 : _runConfig_publish13.skipUserConfirmation) || false;
444
+ // Get workflow names - either from config or auto-detect
445
+ let workflowNames = (_runConfig_publish14 = runConfig.publish) === null || _runConfig_publish14 === void 0 ? void 0 : _runConfig_publish14.releaseWorkflowNames;
446
+ if (!workflowNames || workflowNames.length === 0) {
447
+ logger.info('No specific workflow names configured, auto-detecting workflows triggered by release events...');
448
+ try {
449
+ workflowNames = await getWorkflowsTriggeredByRelease();
450
+ if (workflowNames.length === 0) {
451
+ logger.info('No workflows found that are triggered by release events.');
452
+ } else {
453
+ logger.info(`Auto-detected release workflows: ${workflowNames.join(', ')}`);
454
+ }
455
+ } catch (error) {
456
+ logger.warn(`Failed to auto-detect release workflows: ${error.message}`);
457
+ workflowNames = undefined; // Fall back to monitoring all workflows
458
+ }
459
+ }
460
+ await waitForReleaseWorkflows(tagName, {
461
+ timeout: workflowTimeout,
462
+ workflowNames,
463
+ skipUserConfirmation
464
+ });
493
465
  }
466
+ } else {
467
+ logger.verbose('Skipping waiting for release workflows (disabled in config).');
494
468
  }
469
+ // Switch to target branch
470
+ const targetBranch = ((_runConfig_publish2 = runConfig.publish) === null || _runConfig_publish2 === void 0 ? void 0 : _runConfig_publish2.targetBranch) || 'main';
471
+ logger.info(`Switching to target branch: ${targetBranch}`);
472
+ await runWithDryRunSupport(`git checkout ${targetBranch}`, isDryRun);
473
+ logger.info('Publish process complete.');
495
474
  };
496
475
 
497
476
  export { execute };