@eldrforge/kodrdriv 0.0.33 → 0.0.38

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 (69) hide show
  1. package/README.md +46 -69
  2. package/dist/application.js +146 -0
  3. package/dist/application.js.map +1 -0
  4. package/dist/arguments.js +22 -21
  5. package/dist/arguments.js.map +1 -1
  6. package/dist/commands/audio-commit.js +43 -21
  7. package/dist/commands/audio-commit.js.map +1 -1
  8. package/dist/commands/audio-review.js +46 -38
  9. package/dist/commands/audio-review.js.map +1 -1
  10. package/dist/commands/clean.js +28 -12
  11. package/dist/commands/clean.js.map +1 -1
  12. package/dist/commands/commit.js +132 -39
  13. package/dist/commands/commit.js.map +1 -1
  14. package/dist/commands/link.js +177 -159
  15. package/dist/commands/link.js.map +1 -1
  16. package/dist/commands/publish-tree.js +19 -6
  17. package/dist/commands/publish-tree.js.map +1 -1
  18. package/dist/commands/publish.js +152 -82
  19. package/dist/commands/publish.js.map +1 -1
  20. package/dist/commands/release.js +21 -16
  21. package/dist/commands/release.js.map +1 -1
  22. package/dist/commands/review.js +286 -60
  23. package/dist/commands/review.js.map +1 -1
  24. package/dist/commands/select-audio.js +25 -8
  25. package/dist/commands/select-audio.js.map +1 -1
  26. package/dist/commands/unlink.js +349 -159
  27. package/dist/commands/unlink.js.map +1 -1
  28. package/dist/constants.js +14 -5
  29. package/dist/constants.js.map +1 -1
  30. package/dist/content/diff.js +7 -5
  31. package/dist/content/diff.js.map +1 -1
  32. package/dist/content/log.js +4 -1
  33. package/dist/content/log.js.map +1 -1
  34. package/dist/error/CancellationError.js +9 -0
  35. package/dist/error/CancellationError.js.map +1 -0
  36. package/dist/error/CommandErrors.js +120 -0
  37. package/dist/error/CommandErrors.js.map +1 -0
  38. package/dist/logging.js +55 -12
  39. package/dist/logging.js.map +1 -1
  40. package/dist/main.js +6 -131
  41. package/dist/main.js.map +1 -1
  42. package/dist/prompt/commit.js +4 -0
  43. package/dist/prompt/commit.js.map +1 -1
  44. package/dist/prompt/instructions/commit.md +33 -24
  45. package/dist/prompt/instructions/release.md +39 -5
  46. package/dist/prompt/release.js +41 -1
  47. package/dist/prompt/release.js.map +1 -1
  48. package/dist/types.js +9 -2
  49. package/dist/types.js.map +1 -1
  50. package/dist/util/github.js +71 -4
  51. package/dist/util/github.js.map +1 -1
  52. package/dist/util/npmOptimizations.js +174 -0
  53. package/dist/util/npmOptimizations.js.map +1 -0
  54. package/dist/util/openai.js +4 -2
  55. package/dist/util/openai.js.map +1 -1
  56. package/dist/util/performance.js +202 -0
  57. package/dist/util/performance.js.map +1 -0
  58. package/dist/util/safety.js +166 -0
  59. package/dist/util/safety.js.map +1 -0
  60. package/dist/util/storage.js +10 -0
  61. package/dist/util/storage.js.map +1 -1
  62. package/dist/util/validation.js +81 -0
  63. package/dist/util/validation.js.map +1 -0
  64. package/package.json +19 -18
  65. package/packages/components/package.json +4 -0
  66. package/packages/tools/package.json +4 -0
  67. package/packages/utils/package.json +4 -0
  68. package/scripts/pre-commit-hook.sh +52 -0
  69. package/test-project/package.json +1 -0
@@ -4,14 +4,16 @@ import { hasStagedChanges } from '../content/diff.js';
4
4
  import { execute as execute$3 } from './release.js';
5
5
  import { execute as execute$4 } from './link.js';
6
6
  import { execute as execute$1 } from './unlink.js';
7
- import { getLogger } from '../logging.js';
7
+ import { getDryRunLogger, getLogger } from '../logging.js';
8
8
  import { runWithDryRunSupport, run } from '../util/child.js';
9
9
  import { getCurrentBranchName, findOpenPullRequestByHeadRef, createPullRequest, waitForPullRequestChecks, mergePullRequest, createRelease, getWorkflowsTriggeredByRelease, waitForReleaseWorkflows } from '../util/github.js';
10
10
  import { create } from '../util/storage.js';
11
11
  import { incrementPatchVersion, getOutputPath } from '../util/general.js';
12
12
  import { DEFAULT_OUTPUT_DIRECTORY } from '../constants.js';
13
+ import { safeJsonParse, validatePackageJson } from '../util/validation.js';
13
14
 
14
15
  const scanNpmrcForEnvVars = async (storage)=>{
16
+ const logger = getLogger();
15
17
  const npmrcPath = path.join(process.cwd(), '.npmrc');
16
18
  const envVars = [];
17
19
  if (await storage.exists(npmrcPath)) {
@@ -28,15 +30,17 @@ const scanNpmrcForEnvVars = async (storage)=>{
28
30
  }
29
31
  }
30
32
  }
31
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
32
33
  } catch (error) {
33
- // If we can't read .npmrc, that's okay - just continue
34
+ logger.warn(`Failed to read .npmrc file at ${npmrcPath}: ${error.message}`);
35
+ logger.verbose('This may affect environment variable detection for publishing');
34
36
  }
37
+ } else {
38
+ logger.debug('.npmrc file not found, skipping environment variable scan');
35
39
  }
36
40
  return envVars;
37
41
  };
38
42
  const validateEnvironmentVariables = (requiredEnvVars, isDryRun)=>{
39
- const logger = getLogger();
43
+ const logger = getDryRunLogger(isDryRun);
40
44
  const missingEnvVars = [];
41
45
  for (const envVar of requiredEnvVars){
42
46
  if (!process.env[envVar]) {
@@ -45,7 +49,7 @@ const validateEnvironmentVariables = (requiredEnvVars, isDryRun)=>{
45
49
  }
46
50
  if (missingEnvVars.length > 0) {
47
51
  if (isDryRun) {
48
- logger.warn(`DRY RUN: Missing required environment variables: ${missingEnvVars.join(', ')}`);
52
+ logger.warn(`Missing required environment variables: ${missingEnvVars.join(', ')}`);
49
53
  } else {
50
54
  logger.error(`Missing required environment variables: ${missingEnvVars.join(', ')}`);
51
55
  throw new Error(`Missing required environment variables: ${missingEnvVars.join(', ')}. Please set these environment variables before running publish.`);
@@ -54,46 +58,44 @@ const validateEnvironmentVariables = (requiredEnvVars, isDryRun)=>{
54
58
  };
55
59
  const runPrechecks = async (runConfig)=>{
56
60
  var _runConfig_publish;
57
- const logger = getLogger();
61
+ const isDryRun = runConfig.dryRun || false;
62
+ const logger = getDryRunLogger(isDryRun);
58
63
  const storage = create({
59
64
  log: logger.info
60
65
  });
61
- const isDryRun = runConfig.dryRun || false;
62
- logger.info(isDryRun ? 'DRY RUN: Running prechecks...' : 'Running prechecks...');
66
+ logger.info('Running prechecks...');
63
67
  // Check if we're in a git repository
64
68
  try {
65
69
  if (isDryRun) {
66
- logger.info('DRY RUN: Would check git repository with: git rev-parse --git-dir');
70
+ logger.info('Would check git repository with: git rev-parse --git-dir');
67
71
  } else {
68
72
  await run('git rev-parse --git-dir');
69
73
  }
70
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
71
74
  } catch (error) {
72
75
  if (!isDryRun) {
73
76
  throw new Error('Not in a git repository. Please run this command from within a git repository.');
74
77
  }
75
78
  }
76
79
  // Check for uncommitted changes
77
- logger.info(isDryRun ? 'DRY RUN: Would check for uncommitted changes...' : 'Checking for uncommitted changes...');
80
+ logger.info('Checking for uncommitted changes...');
78
81
  try {
79
82
  if (isDryRun) {
80
- logger.info('DRY RUN: Would check git status with: git status --porcelain');
83
+ logger.info('Would check git status with: git status --porcelain');
81
84
  } else {
82
85
  const { stdout } = await run('git status --porcelain');
83
86
  if (stdout.trim()) {
84
87
  throw new Error('Working directory has uncommitted changes. Please commit or stash your changes before running publish.');
85
88
  }
86
89
  }
87
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
88
90
  } catch (error) {
89
91
  if (!isDryRun) {
90
92
  throw new Error('Failed to check git status. Please ensure you are in a valid git repository.');
91
93
  }
92
94
  }
93
95
  // Check if we're on a release branch
94
- logger.info(isDryRun ? 'DRY RUN: Would check current branch...' : 'Checking current branch...');
96
+ logger.info('Checking current branch...');
95
97
  if (isDryRun) {
96
- logger.info('DRY RUN: Would verify current branch is a release branch (starts with "release/")');
98
+ logger.info('Would verify current branch is a release branch (starts with "release/")');
97
99
  } else {
98
100
  const currentBranch = await getCurrentBranchName();
99
101
  if (!currentBranch.startsWith('release/')) {
@@ -101,38 +103,38 @@ const runPrechecks = async (runConfig)=>{
101
103
  }
102
104
  }
103
105
  // Check if prepublishOnly script exists in package.json
104
- logger.info(isDryRun ? 'DRY RUN: Would check for prepublishOnly script...' : 'Checking for prepublishOnly script...');
106
+ logger.info('Checking for prepublishOnly script...');
105
107
  const packageJsonPath = path.join(process.cwd(), 'package.json');
106
108
  if (!await storage.exists(packageJsonPath)) {
107
109
  if (!isDryRun) {
108
110
  throw new Error('package.json not found in current directory.');
109
111
  } else {
110
- logger.warn('DRY RUN: package.json not found in current directory.');
112
+ logger.warn('package.json not found in current directory.');
111
113
  }
112
114
  } else {
113
115
  var _packageJson_scripts;
114
116
  let packageJson;
115
117
  try {
116
118
  const packageJsonContents = await storage.readFile(packageJsonPath, 'utf-8');
117
- packageJson = JSON.parse(packageJsonContents);
118
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
119
+ const parsed = safeJsonParse(packageJsonContents, packageJsonPath);
120
+ packageJson = validatePackageJson(parsed, packageJsonPath);
119
121
  } catch (error) {
120
122
  if (!isDryRun) {
121
123
  throw new Error('Failed to parse package.json. Please ensure it contains valid JSON.');
122
124
  } else {
123
- logger.warn('DRY RUN: Failed to parse package.json. Please ensure it contains valid JSON.');
125
+ logger.warn('Failed to parse package.json. Please ensure it contains valid JSON.');
124
126
  }
125
127
  }
126
128
  if (packageJson && !((_packageJson_scripts = packageJson.scripts) === null || _packageJson_scripts === void 0 ? void 0 : _packageJson_scripts.prepublishOnly)) {
127
129
  if (!isDryRun) {
128
130
  throw new Error('prepublishOnly script is required in package.json but was not found. Please add a prepublishOnly script that runs your pre-flight checks (e.g., clean, lint, build, test).');
129
131
  } else {
130
- logger.warn('DRY RUN: prepublishOnly script is required in package.json but was not found.');
132
+ logger.warn('prepublishOnly script is required in package.json but was not found.');
131
133
  }
132
134
  }
133
135
  }
134
136
  // Check required environment variables
135
- logger.verbose(isDryRun ? 'DRY RUN: Would check required environment variables...' : 'Checking required environment variables...');
137
+ logger.verbose('Checking required environment variables...');
136
138
  const coreRequiredEnvVars = ((_runConfig_publish = runConfig.publish) === null || _runConfig_publish === void 0 ? void 0 : _runConfig_publish.requiredEnvVars) || [];
137
139
  const npmrcEnvVars = isDryRun ? [] : await scanNpmrcForEnvVars(storage); // Skip .npmrc scan in dry run
138
140
  const allRequiredEnvVars = [
@@ -142,65 +144,67 @@ const runPrechecks = async (runConfig)=>{
142
144
  ])
143
145
  ];
144
146
  if (allRequiredEnvVars.length > 0) {
145
- logger.verbose(`${isDryRun ? 'DRY RUN: ' : ''}Required environment variables: ${allRequiredEnvVars.join(', ')}`);
147
+ logger.verbose(`Required environment variables: ${allRequiredEnvVars.join(', ')}`);
146
148
  validateEnvironmentVariables(allRequiredEnvVars, isDryRun);
147
149
  } else {
148
- logger.verbose(isDryRun ? 'DRY RUN: No required environment variables specified.' : 'No required environment variables specified.');
150
+ logger.verbose('No required environment variables specified.');
149
151
  }
150
- logger.info(isDryRun ? 'DRY RUN: All prechecks would pass.' : 'All prechecks passed.');
152
+ logger.info('All prechecks passed.');
151
153
  };
152
154
  const execute = async (runConfig)=>{
153
- const logger = getLogger();
155
+ const isDryRun = runConfig.dryRun || false;
156
+ const logger = getDryRunLogger(isDryRun);
154
157
  const storage = create({
155
158
  log: logger.info
156
159
  });
157
- const isDryRun = runConfig.dryRun || false;
160
+ // Track whether the publish process completed successfully
161
+ let publishCompleted = false;
158
162
  // Run prechecks before starting any work
159
163
  await runPrechecks(runConfig);
160
- logger.info(isDryRun ? 'DRY RUN: Would start release process...' : 'Starting release process...');
164
+ logger.info('Starting release process...');
161
165
  try {
162
166
  var _runConfig_publish, _runConfig_publish1, _runConfig_publish2;
163
167
  // Unlink all workspace packages before starting (if enabled)
164
168
  const shouldUnlink = ((_runConfig_publish = runConfig.publish) === null || _runConfig_publish === void 0 ? void 0 : _runConfig_publish.unlinkWorkspacePackages) !== false; // default to true
165
169
  if (shouldUnlink) {
166
- logger.verbose(isDryRun ? 'DRY RUN: Would unlink workspace packages...' : 'Unlinking workspace packages...');
170
+ logger.verbose('Unlinking workspace packages...');
167
171
  await execute$1(runConfig);
168
172
  } else {
169
- logger.verbose(isDryRun ? 'DRY RUN: Would skip unlink workspace packages (disabled in config).' : 'Skipping unlink workspace packages (disabled in config).');
173
+ logger.verbose('Skipping unlink workspace packages (disabled in config).');
170
174
  }
171
175
  let pr = null;
172
176
  if (isDryRun) {
173
- logger.info('DRY RUN: Would check for existing pull request');
174
- logger.info('DRY RUN: Assuming no existing PR found for demo purposes');
177
+ logger.info('Would check for existing pull request');
178
+ logger.info('Assuming no existing PR found for demo purposes');
175
179
  } else {
176
180
  const branchName = await getCurrentBranchName();
177
181
  pr = await findOpenPullRequestByHeadRef(branchName);
178
182
  }
179
183
  if (pr) {
180
- logger.info(`${isDryRun ? 'DRY RUN: ' : ''}Found existing pull request for branch: ${pr.html_url}`);
184
+ logger.info(`Found existing pull request for branch: ${pr.html_url}`);
181
185
  } else {
182
186
  var _runConfig_publish3;
183
- logger.info(isDryRun ? 'DRY RUN: No open pull request found, would start new release publishing process...' : 'No open pull request found, starting new release publishing process...');
187
+ logger.info('No open pull request found, starting new release publishing process...');
184
188
  // 1. Prepare for release
185
- logger.verbose(isDryRun ? 'DRY RUN: Would prepare for release: switching from workspace to remote dependencies.' : 'Preparing for release: switching from workspace to remote dependencies.');
186
- logger.verbose(isDryRun ? 'DRY RUN: Would update dependencies to latest versions from registry' : 'Updating dependencies to latest versions from registry');
189
+ logger.verbose('Preparing for release: switching from workspace to remote dependencies.');
190
+ logger.verbose('Updating dependencies to latest versions from registry');
187
191
  const updatePatterns = (_runConfig_publish3 = runConfig.publish) === null || _runConfig_publish3 === void 0 ? void 0 : _runConfig_publish3.dependencyUpdatePatterns;
188
192
  if (updatePatterns && updatePatterns.length > 0) {
189
- logger.verbose(`${isDryRun ? 'DRY RUN: ' : ''}Updating dependencies matching patterns: ${updatePatterns.join(', ')}`);
193
+ logger.verbose(`Updating dependencies matching patterns: ${updatePatterns.join(', ')}`);
190
194
  const patternsArg = updatePatterns.join(' ');
191
- await runWithDryRunSupport(`pnpm update --latest ${patternsArg}`, isDryRun);
195
+ await runWithDryRunSupport(`npm update ${patternsArg}`, isDryRun);
192
196
  } else {
193
- logger.verbose(isDryRun ? 'DRY RUN: No dependency update patterns specified, would update all dependencies' : 'No dependency update patterns specified, updating all dependencies');
194
- await runWithDryRunSupport('pnpm update --latest', isDryRun);
197
+ logger.verbose('No dependency update patterns specified, updating all dependencies');
198
+ await runWithDryRunSupport('npm update', isDryRun);
195
199
  }
196
- logger.verbose(isDryRun ? 'DRY RUN: Would stage changes for release commit' : 'Staging changes for release commit');
197
- await runWithDryRunSupport('git add package.json pnpm-lock.yaml', isDryRun);
198
- logger.info(isDryRun ? 'DRY RUN: Would run prepublishOnly script...' : 'Running prepublishOnly script...');
199
- await runWithDryRunSupport('pnpm run prepublishOnly', isDryRun);
200
- logger.verbose(isDryRun ? 'DRY RUN: Would check for staged changes...' : 'Checking for staged changes...');
200
+ logger.verbose('Staging changes for release commit');
201
+ await runWithDryRunSupport('git add package.json package-lock.json', isDryRun);
202
+ logger.info('Running prepublishOnly script...');
203
+ await runWithDryRunSupport('npm run prepublishOnly', isDryRun);
204
+ logger.verbose('Checking for staged changes...');
201
205
  if (isDryRun) {
202
- logger.verbose('DRY RUN: Assuming staged changes exist for demo purposes');
203
- logger.verbose('DRY RUN: Would create commit...');
206
+ logger.verbose('Assuming staged changes exist for demo purposes');
207
+ logger.verbose('Would create commit...');
204
208
  await execute$2(runConfig);
205
209
  } else {
206
210
  if (await hasStagedChanges()) {
@@ -210,13 +214,14 @@ const execute = async (runConfig)=>{
210
214
  logger.verbose('No changes to commit, skipping commit.');
211
215
  }
212
216
  }
213
- logger.info(isDryRun ? 'DRY RUN: Would bump version...' : 'Bumping version...');
217
+ logger.info('Bumping version...');
214
218
  // Manually increment version without creating a tag
215
219
  if (isDryRun) {
216
- logger.info('DRY RUN: Would manually increment patch version in package.json and commit');
220
+ logger.info('Would manually increment patch version in package.json and commit');
217
221
  } else {
218
222
  const packageJsonContents = await storage.readFile('package.json', 'utf-8');
219
- const packageJson = JSON.parse(packageJsonContents);
223
+ const parsed = safeJsonParse(packageJsonContents, 'package.json');
224
+ const packageJson = validatePackageJson(parsed, 'package.json');
220
225
  const currentVersion = packageJson.version;
221
226
  const newVersion = incrementPatchVersion(currentVersion);
222
227
  packageJson.version = newVersion;
@@ -227,10 +232,10 @@ const execute = async (runConfig)=>{
227
232
  await run(`git commit -m "chore: bump version to ${newVersion}"`);
228
233
  logger.info(`Version change committed: ${newVersion}`);
229
234
  }
230
- logger.info(isDryRun ? 'DRY RUN: Would generate release notes...' : 'Generating release notes...');
235
+ logger.info('Generating release notes...');
231
236
  const releaseSummary = await execute$3(runConfig);
232
237
  if (isDryRun) {
233
- logger.info('DRY RUN: Would write release notes to RELEASE_NOTES.md and RELEASE_TITLE.md in output directory');
238
+ logger.info('Would write release notes to RELEASE_NOTES.md and RELEASE_TITLE.md in output directory');
234
239
  } else {
235
240
  const outputDirectory = runConfig.outputDirectory || DEFAULT_OUTPUT_DIRECTORY;
236
241
  await storage.ensureDirectory(outputDirectory);
@@ -240,11 +245,13 @@ const execute = async (runConfig)=>{
240
245
  await storage.writeFile(releaseTitlePath, releaseSummary.title, 'utf-8');
241
246
  logger.info(`Release notes and title generated and saved to ${releaseNotesPath} and ${releaseTitlePath}.`);
242
247
  }
243
- logger.info(isDryRun ? 'DRY RUN: Would push to origin...' : 'Pushing to origin...');
244
- await runWithDryRunSupport('git push', isDryRun);
245
- logger.info(isDryRun ? 'DRY RUN: Would create pull request...' : 'Creating pull request...');
248
+ logger.info('Pushing to origin...');
249
+ // Get current branch name and push explicitly to avoid pushing to wrong remote/branch
250
+ const currentBranch = await getCurrentBranchName();
251
+ await runWithDryRunSupport(`git push origin ${currentBranch}`, isDryRun);
252
+ logger.info('Creating pull request...');
246
253
  if (isDryRun) {
247
- logger.info('DRY RUN: Would get commit title and create PR with GitHub API');
254
+ logger.info('Would get commit title and create PR with GitHub API');
248
255
  pr = {
249
256
  number: 123,
250
257
  html_url: 'https://github.com/mock/repo/pull/123',
@@ -259,7 +266,7 @@ const execute = async (runConfig)=>{
259
266
  logger.info(`Pull request created: ${pr.html_url}`);
260
267
  }
261
268
  }
262
- logger.info(`${isDryRun ? 'DRY RUN: Would wait for' : 'Waiting for'} PR #${pr.number} checks to complete...`);
269
+ logger.info(`Waiting for PR #${pr.number} checks to complete...`);
263
270
  if (!isDryRun) {
264
271
  var _runConfig_publish4, _runConfig_publish5, _runConfig_publish6;
265
272
  // Configure timeout and user confirmation behavior
@@ -274,21 +281,23 @@ const execute = async (runConfig)=>{
274
281
  }
275
282
  const mergeMethod = ((_runConfig_publish1 = runConfig.publish) === null || _runConfig_publish1 === void 0 ? void 0 : _runConfig_publish1.mergeMethod) || 'squash';
276
283
  if (isDryRun) {
277
- logger.info(`DRY RUN: Would merge PR #${pr.number} using ${mergeMethod} method`);
284
+ logger.info(`Would merge PR #${pr.number} using ${mergeMethod} method`);
278
285
  } else {
279
286
  await mergePullRequest(pr.number, mergeMethod);
280
287
  }
281
- logger.info(isDryRun ? 'DRY RUN: Would checkout main branch...' : 'Checking out main branch...');
288
+ logger.info('Checking out main branch...');
282
289
  await runWithDryRunSupport('git checkout main', isDryRun);
283
290
  await runWithDryRunSupport('git pull origin main', isDryRun);
284
291
  // Now create and push the tag on the main branch
285
- logger.info(isDryRun ? 'DRY RUN: Would create release tag...' : 'Creating release tag...');
292
+ logger.info('Creating release tag...');
293
+ let tagName;
286
294
  if (isDryRun) {
287
- logger.info('DRY RUN: Would read package.json version and create git tag');
295
+ logger.info('Would read package.json version and create git tag');
296
+ tagName = 'v1.0.0'; // Mock version for dry run
288
297
  } else {
289
298
  const packageJsonContents = await storage.readFile('package.json', 'utf-8');
290
299
  const { version } = JSON.parse(packageJsonContents);
291
- const tagName = `v${version}`;
300
+ tagName = `v${version}`;
292
301
  // Check if tag already exists locally
293
302
  try {
294
303
  const { stdout } = await run(`git tag -l ${tagName}`);
@@ -304,6 +313,7 @@ const execute = async (runConfig)=>{
304
313
  logger.info(`Created local tag: ${tagName}`);
305
314
  }
306
315
  // Check if tag exists on remote before pushing
316
+ let tagWasPushed = false;
307
317
  try {
308
318
  const { stdout } = await run(`git ls-remote origin refs/tags/${tagName}`);
309
319
  if (stdout.trim()) {
@@ -311,12 +321,14 @@ const execute = async (runConfig)=>{
311
321
  } else {
312
322
  await run(`git push origin ${tagName}`);
313
323
  logger.info(`Pushed tag to remote: ${tagName}`);
324
+ tagWasPushed = true;
314
325
  }
315
326
  } catch (error) {
316
327
  // If ls-remote fails, try to push anyway (might be a new remote)
317
328
  try {
318
329
  await run(`git push origin ${tagName}`);
319
330
  logger.info(`Pushed tag to remote: ${tagName}`);
331
+ tagWasPushed = true;
320
332
  } catch (pushError) {
321
333
  if (pushError.message && pushError.message.includes('already exists')) {
322
334
  logger.info(`Tag ${tagName} already exists on remote, continuing...`);
@@ -325,29 +337,51 @@ const execute = async (runConfig)=>{
325
337
  }
326
338
  }
327
339
  }
340
+ // If we just pushed a new tag, wait for GitHub to process it
341
+ if (tagWasPushed) {
342
+ logger.verbose('Waiting for GitHub to process the pushed tag...');
343
+ await new Promise((resolve)=>setTimeout(resolve, 5000)); // 5 second delay
344
+ }
328
345
  }
329
- logger.info(isDryRun ? 'DRY RUN: Would create GitHub release...' : 'Creating GitHub release...');
330
- let tagName;
346
+ logger.info('Creating GitHub release...');
331
347
  if (isDryRun) {
332
- logger.info('DRY RUN: Would read package.json version and create GitHub release');
333
- tagName = 'v1.0.0'; // Mock version for dry run
348
+ logger.info('Would read package.json version and create GitHub release with retry logic');
334
349
  } else {
335
- const packageJsonContents = await storage.readFile('package.json', 'utf-8');
336
- const { version } = JSON.parse(packageJsonContents);
337
- tagName = `v${version}`;
338
350
  const outputDirectory = runConfig.outputDirectory || DEFAULT_OUTPUT_DIRECTORY;
339
351
  const releaseNotesPath = getOutputPath(outputDirectory, 'RELEASE_NOTES.md');
340
352
  const releaseTitlePath = getOutputPath(outputDirectory, 'RELEASE_TITLE.md');
341
353
  const releaseNotesContent = await storage.readFile(releaseNotesPath, 'utf-8');
342
354
  const releaseTitle = await storage.readFile(releaseTitlePath, 'utf-8');
343
- await createRelease(tagName, releaseTitle, releaseNotesContent);
355
+ // Create release with retry logic to handle GitHub tag processing delays
356
+ let retries = 3;
357
+ while(retries > 0){
358
+ try {
359
+ await createRelease(tagName, releaseTitle, releaseNotesContent);
360
+ logger.info(`GitHub release created successfully for tag: ${tagName}`);
361
+ break; // Success - exit retry loop
362
+ } catch (error) {
363
+ // Check if this is a tag-not-found error that we can retry
364
+ const isTagNotFoundError = error.message && (error.message.includes('not found') || error.message.includes('does not exist') || error.message.includes('Reference does not exist'));
365
+ if (isTagNotFoundError && retries > 1) {
366
+ logger.verbose(`Tag ${tagName} not yet available on GitHub, retrying in 3 seconds... (${retries - 1} retries left)`);
367
+ await new Promise((resolve)=>setTimeout(resolve, 3000));
368
+ retries--;
369
+ } else if (isTagNotFoundError) {
370
+ // Tag not found error and we're out of retries
371
+ 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.`);
372
+ } else {
373
+ // Not a tag-not-found error - re-throw the original error
374
+ throw error;
375
+ }
376
+ }
377
+ }
344
378
  }
345
379
  // Wait for release workflows to complete (if enabled)
346
380
  const waitForWorkflows = ((_runConfig_publish2 = runConfig.publish) === null || _runConfig_publish2 === void 0 ? void 0 : _runConfig_publish2.waitForReleaseWorkflows) !== false; // default to true
347
381
  if (waitForWorkflows) {
348
- logger.info(isDryRun ? 'DRY RUN: Would wait for release workflows...' : 'Waiting for release workflows...');
382
+ logger.info('Waiting for release workflows...');
349
383
  if (isDryRun) {
350
- logger.info('DRY RUN: Would monitor GitHub Actions workflows triggered by release');
384
+ logger.info('Would monitor GitHub Actions workflows triggered by release');
351
385
  } else {
352
386
  var _runConfig_publish7, _runConfig_publish8, _runConfig_publish9, _runConfig_publish10;
353
387
  const workflowTimeout = ((_runConfig_publish7 = runConfig.publish) === null || _runConfig_publish7 === void 0 ? void 0 : _runConfig_publish7.releaseWorkflowsTimeout) || 600000; // 10 minutes default
@@ -376,30 +410,66 @@ const execute = async (runConfig)=>{
376
410
  });
377
411
  }
378
412
  } else {
379
- logger.verbose(isDryRun ? 'DRY RUN: Would skip waiting for release workflows (disabled in config).' : 'Skipping waiting for release workflows (disabled in config).');
413
+ logger.verbose('Skipping waiting for release workflows (disabled in config).');
380
414
  }
381
- logger.info(isDryRun ? 'DRY RUN: Would create new release branch...' : 'Creating new release branch...');
415
+ logger.info('Creating new release branch...');
382
416
  if (isDryRun) {
383
- logger.info('DRY RUN: Would create next release branch (e.g., release/1.0.1) and push to origin');
417
+ logger.info('Would create next release branch (e.g., release/1.0.1) and push to origin');
384
418
  } else {
385
419
  const packageJsonContents = await storage.readFile('package.json', 'utf-8');
386
420
  const { version } = JSON.parse(packageJsonContents);
387
421
  const nextVersion = incrementPatchVersion(version);
388
422
  const newBranchName = `release/${nextVersion}`;
389
- await run(`git checkout -b ${newBranchName}`);
390
- await run(`git push -u origin ${newBranchName}`);
391
- logger.info(`Branch ${newBranchName} created and pushed to origin.`);
423
+ // Check if branch already exists locally
424
+ let branchExists = false;
425
+ try {
426
+ await run(`git show-ref --verify --quiet refs/heads/${newBranchName}`);
427
+ branchExists = true;
428
+ } catch {
429
+ // Branch doesn't exist locally
430
+ branchExists = false;
431
+ }
432
+ if (branchExists) {
433
+ // Branch exists, switch to it
434
+ await run(`git checkout ${newBranchName}`);
435
+ logger.info(`Switched to existing branch ${newBranchName}`);
436
+ } else {
437
+ // Branch doesn't exist, create it
438
+ await run(`git checkout -b ${newBranchName}`);
439
+ logger.info(`Created new branch ${newBranchName}`);
440
+ }
441
+ // Check if branch exists on remote before pushing
442
+ let remoteExists = false;
443
+ try {
444
+ const { stdout } = await run(`git ls-remote origin refs/heads/${newBranchName}`);
445
+ remoteExists = stdout.trim() !== '';
446
+ } catch {
447
+ // Assume remote doesn't exist if ls-remote fails
448
+ remoteExists = false;
449
+ }
450
+ if (remoteExists) {
451
+ logger.info(`Branch ${newBranchName} already exists on remote, skipping push`);
452
+ } else {
453
+ await run(`git push -u origin ${newBranchName}`);
454
+ logger.info(`Branch ${newBranchName} pushed to origin.`);
455
+ }
392
456
  }
393
- logger.info(isDryRun ? 'DRY RUN: Preparation would be complete.' : 'Preparation complete.');
457
+ logger.info('Preparation complete.');
458
+ publishCompleted = true; // Mark as completed only if we reach this point
394
459
  } finally{
395
460
  var _runConfig_publish11;
396
- // Restore linked packages (if enabled)
461
+ // Always restore linked packages if enabled, regardless of success/failure
462
+ // This ensures we don't leave the repository with file: dependencies
397
463
  const shouldLink = ((_runConfig_publish11 = runConfig.publish) === null || _runConfig_publish11 === void 0 ? void 0 : _runConfig_publish11.linkWorkspacePackages) !== false; // default to true
398
464
  if (shouldLink) {
399
- logger.verbose(isDryRun ? 'DRY RUN: Would restore linked packages...' : 'Restoring linked packages...');
465
+ if (publishCompleted) {
466
+ logger.verbose('Restoring linked packages after successful publish...');
467
+ } else {
468
+ logger.verbose('Restoring linked packages after failed publish to avoid leaving file: dependencies...');
469
+ }
400
470
  await execute$4(runConfig);
401
471
  } else {
402
- logger.verbose(isDryRun ? 'DRY RUN: Would skip restore linked packages (disabled in config).' : 'Skipping restore linked packages (disabled in config).');
472
+ logger.verbose('Skipping restore linked packages (disabled in config).');
403
473
  }
404
474
  }
405
475
  };