@eldrforge/kodrdriv 1.2.134 → 1.2.135

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 (72) hide show
  1. package/.cursor/rules/no-local-dependencies.md +6 -0
  2. package/dist/application.js +32 -42
  3. package/dist/application.js.map +1 -1
  4. package/dist/arguments.js +3 -3
  5. package/dist/arguments.js.map +1 -1
  6. package/dist/constants.js +5 -7
  7. package/dist/constants.js.map +1 -1
  8. package/dist/logging.js +4 -32
  9. package/dist/logging.js.map +1 -1
  10. package/dist/types.js +1 -0
  11. package/dist/types.js.map +1 -1
  12. package/package.json +13 -8
  13. package/dist/commands/audio-commit.js +0 -152
  14. package/dist/commands/audio-commit.js.map +0 -1
  15. package/dist/commands/audio-review.js +0 -274
  16. package/dist/commands/audio-review.js.map +0 -1
  17. package/dist/commands/clean.js +0 -49
  18. package/dist/commands/clean.js.map +0 -1
  19. package/dist/commands/commit.js +0 -680
  20. package/dist/commands/commit.js.map +0 -1
  21. package/dist/commands/development.js +0 -467
  22. package/dist/commands/development.js.map +0 -1
  23. package/dist/commands/link.js +0 -646
  24. package/dist/commands/link.js.map +0 -1
  25. package/dist/commands/precommit.js +0 -99
  26. package/dist/commands/precommit.js.map +0 -1
  27. package/dist/commands/publish.js +0 -1432
  28. package/dist/commands/publish.js.map +0 -1
  29. package/dist/commands/release.js +0 -376
  30. package/dist/commands/release.js.map +0 -1
  31. package/dist/commands/review.js +0 -733
  32. package/dist/commands/review.js.map +0 -1
  33. package/dist/commands/select-audio.js +0 -46
  34. package/dist/commands/select-audio.js.map +0 -1
  35. package/dist/commands/tree.js +0 -2363
  36. package/dist/commands/tree.js.map +0 -1
  37. package/dist/commands/unlink.js +0 -537
  38. package/dist/commands/unlink.js.map +0 -1
  39. package/dist/commands/updates.js +0 -211
  40. package/dist/commands/updates.js.map +0 -1
  41. package/dist/commands/versions.js +0 -221
  42. package/dist/commands/versions.js.map +0 -1
  43. package/dist/content/diff.js +0 -346
  44. package/dist/content/diff.js.map +0 -1
  45. package/dist/content/files.js +0 -190
  46. package/dist/content/files.js.map +0 -1
  47. package/dist/content/log.js +0 -72
  48. package/dist/content/log.js.map +0 -1
  49. package/dist/util/aiAdapter.js +0 -28
  50. package/dist/util/aiAdapter.js.map +0 -1
  51. package/dist/util/fileLock.js +0 -241
  52. package/dist/util/fileLock.js.map +0 -1
  53. package/dist/util/general.js +0 -379
  54. package/dist/util/general.js.map +0 -1
  55. package/dist/util/gitMutex.js +0 -161
  56. package/dist/util/gitMutex.js.map +0 -1
  57. package/dist/util/interactive.js +0 -32
  58. package/dist/util/interactive.js.map +0 -1
  59. package/dist/util/loggerAdapter.js +0 -41
  60. package/dist/util/loggerAdapter.js.map +0 -1
  61. package/dist/util/performance.js +0 -134
  62. package/dist/util/performance.js.map +0 -1
  63. package/dist/util/precommitOptimizations.js +0 -310
  64. package/dist/util/precommitOptimizations.js.map +0 -1
  65. package/dist/util/stopContext.js +0 -146
  66. package/dist/util/stopContext.js.map +0 -1
  67. package/dist/util/storageAdapter.js +0 -31
  68. package/dist/util/storageAdapter.js.map +0 -1
  69. package/dist/util/validation.js +0 -45
  70. package/dist/util/validation.js.map +0 -1
  71. package/dist/utils/branchState.js +0 -700
  72. package/dist/utils/branchState.js.map +0 -1
@@ -1,1432 +0,0 @@
1
- import path__default from 'path';
2
- import { execute as execute$2 } from './commit.js';
3
- import { hasStagedChanges } from '../content/diff.js';
4
- import { execute as execute$1 } from './release.js';
5
- import { getDryRunLogger, getLogger } from '../logging.js';
6
- import { run, localBranchExists, remoteBranchExists, runSecure, runWithDryRunSupport, safeJsonParse, validatePackageJson, validateGitRef, safeSyncBranchWithRemote, isBranchInSyncWithRemote } from '@eldrforge/git-tools';
7
- import * as GitHub from '@eldrforge/github-tools';
8
- import { createStorage, calculateTargetVersion } from '@eldrforge/shared';
9
- import { calculateBranchDependentVersion, checkIfTagExists, confirmVersionInteractively, getOutputPath } from '../util/general.js';
10
- import { DEFAULT_OUTPUT_DIRECTORY, KODRDRIV_DEFAULTS } from '../constants.js';
11
- import fs from 'fs/promises';
12
- import { runGitWithLock } from '../util/gitMutex.js';
13
- import { filterContent } from '../util/stopContext.js';
14
- import { runAgenticPublish, formatAgenticPublishResult } from '@eldrforge/ai-service';
15
-
16
- const scanNpmrcForEnvVars = async (storage)=>{
17
- const logger = getLogger();
18
- const npmrcPath = path__default.join(process.cwd(), '.npmrc');
19
- const envVars = [];
20
- if (await storage.exists(npmrcPath)) {
21
- try {
22
- const npmrcContent = await storage.readFile(npmrcPath, 'utf-8');
23
- // Match environment variable patterns like ${VAR_NAME} or $VAR_NAME
24
- const envVarMatches = npmrcContent.match(/\$\{([^}]+)\}|\$([A-Z_][A-Z0-9_]*)/g);
25
- if (envVarMatches) {
26
- for (const match of envVarMatches){
27
- // Extract variable name from ${VAR_NAME} or $VAR_NAME format
28
- const varName = match.replace(/\$\{|\}|\$/g, '');
29
- if (varName && !envVars.includes(varName)) {
30
- envVars.push(varName);
31
- }
32
- }
33
- }
34
- } catch (error) {
35
- logger.warn(`NPMRC_READ_FAILED: Unable to read .npmrc configuration file | Path: ${npmrcPath} | Error: ${error.message}`);
36
- logger.verbose('NPMRC_READ_IMPACT: Environment variable detection for publishing may be affected due to failed .npmrc read');
37
- }
38
- } else {
39
- logger.debug('NPMRC_NOT_FOUND: No .npmrc file present in current directory | Action: Skipping environment variable scan | Path: ' + npmrcPath);
40
- }
41
- return envVars;
42
- };
43
- /**
44
- * Checks if package-lock.json contains file: dependencies (from npm link)
45
- * and cleans them up if found by removing package-lock.json and regenerating it.
46
- */ const cleanupNpmLinkReferences = async (isDryRun)=>{
47
- const logger = getDryRunLogger(isDryRun);
48
- const packageLockPath = path__default.join(process.cwd(), 'package-lock.json');
49
- try {
50
- // Check if package-lock.json exists
51
- try {
52
- await fs.access(packageLockPath);
53
- } catch {
54
- // No package-lock.json, nothing to clean
55
- logger.verbose('PACKAGE_LOCK_NOT_FOUND: No package-lock.json file exists | Action: Skipping npm link cleanup | Path: ' + packageLockPath);
56
- return;
57
- }
58
- // Read and parse package-lock.json
59
- const packageLockContent = await fs.readFile(packageLockPath, 'utf-8');
60
- const packageLock = safeJsonParse(packageLockContent, packageLockPath);
61
- // Check for file: dependencies in the lockfile
62
- let hasFileReferences = false;
63
- // Check in packages (npm v7+)
64
- if (packageLock.packages) {
65
- for (const [pkgPath, pkgInfo] of Object.entries(packageLock.packages)){
66
- if (pkgInfo.resolved && typeof pkgInfo.resolved === 'string' && pkgInfo.resolved.startsWith('file:')) {
67
- // Check if it's a relative path (from npm link) rather than a workspace path
68
- const resolvedPath = pkgInfo.resolved.replace('file:', '');
69
- if (resolvedPath.startsWith('../') || resolvedPath.startsWith('./')) {
70
- hasFileReferences = true;
71
- logger.verbose(`NPM_LINK_DETECTED: Found npm link reference in packages section | Package: ${pkgPath} | Resolved: ${pkgInfo.resolved} | Type: relative_file_dependency`);
72
- break;
73
- }
74
- }
75
- }
76
- }
77
- // Check in dependencies (npm v6)
78
- if (!hasFileReferences && packageLock.dependencies) {
79
- for (const [pkgName, pkgInfo] of Object.entries(packageLock.dependencies)){
80
- if (pkgInfo.version && typeof pkgInfo.version === 'string' && pkgInfo.version.startsWith('file:')) {
81
- const versionPath = pkgInfo.version.replace('file:', '');
82
- if (versionPath.startsWith('../') || versionPath.startsWith('./')) {
83
- hasFileReferences = true;
84
- logger.verbose(`NPM_LINK_DETECTED: Found npm link reference in dependencies section | Package: ${pkgName} | Version: ${pkgInfo.version} | Type: relative_file_dependency`);
85
- break;
86
- }
87
- }
88
- }
89
- }
90
- if (hasFileReferences) {
91
- logger.info('NPM_LINK_CLEANUP_REQUIRED: Detected npm link references in package-lock.json | File: package-lock.json | Impact: Must be cleaned before publish');
92
- logger.info('NPM_LINK_CLEANUP_STARTING: Removing package-lock.json and regenerating clean version | Action: Remove file with relative dependencies');
93
- if (isDryRun) {
94
- logger.info('DRY_RUN_OPERATION: Would remove package-lock.json and regenerate it | Mode: dry-run | File: package-lock.json');
95
- } else {
96
- // Remove package-lock.json
97
- await fs.unlink(packageLockPath);
98
- logger.verbose('NPM_LINK_CLEANUP_FILE_REMOVED: Deleted package-lock.json containing npm link references | Path: ' + packageLockPath);
99
- // Regenerate clean package-lock.json
100
- logger.verbose('NPM_LOCK_REGENERATING: Executing npm install to regenerate package-lock.json from package.json | Command: npm install --package-lock-only --no-audit --no-fund');
101
- await runWithDryRunSupport('npm install --package-lock-only --no-audit --no-fund', isDryRun);
102
- logger.info('NPM_LOCK_REGENERATED: Successfully regenerated clean package-lock.json without link references | Path: ' + packageLockPath);
103
- }
104
- } else {
105
- logger.verbose('NPM_LINK_CHECK_CLEAN: No npm link references found in package-lock.json | Status: Ready for publish | File: ' + packageLockPath);
106
- }
107
- } catch (error) {
108
- // Log warning but don't fail - let npm update handle any issues
109
- logger.warn(`NPM_LINK_CHECK_FAILED: Unable to check or clean npm link references | Error: ${error.message} | Impact: Continuing with publish, npm will handle issues`);
110
- logger.verbose('PUBLISH_PROCESS_CONTINUING: Proceeding with publish workflow despite npm link check failure | Next: Standard npm publish validation');
111
- }
112
- };
113
- const validateEnvironmentVariables = (requiredEnvVars, isDryRun)=>{
114
- const logger = getDryRunLogger(isDryRun);
115
- const missingEnvVars = [];
116
- for (const envVar of requiredEnvVars){
117
- if (!process.env[envVar]) {
118
- missingEnvVars.push(envVar);
119
- }
120
- }
121
- if (missingEnvVars.length > 0) {
122
- if (isDryRun) {
123
- logger.warn(`ENV_VARS_MISSING: Required environment variables not set | Variables: ${missingEnvVars.join(', ')} | Mode: dry-run | Impact: Would fail in real publish`);
124
- } else {
125
- logger.error(`ENV_VARS_MISSING: Required environment variables not set | Variables: ${missingEnvVars.join(', ')} | Action: Must set before publish | Source: .npmrc configuration`);
126
- throw new Error(`Missing required environment variables: ${missingEnvVars.join(', ')}. Please set these environment variables before running publish.`);
127
- }
128
- }
129
- };
130
- const runPrechecks = async (runConfig, targetBranch)=>{
131
- var _runConfig_publish, _runConfig_publish1;
132
- const isDryRun = runConfig.dryRun || false;
133
- const logger = getDryRunLogger(isDryRun);
134
- const storage = createStorage();
135
- logger.info('PRECHECK_STARTING: Executing publish prechecks | Phase: validation | Target: ' + (targetBranch || 'default'));
136
- // Check if we're in a git repository
137
- try {
138
- if (isDryRun) {
139
- logger.info('PRECHECK_GIT_REPO: Would verify git repository | Mode: dry-run | Command: git rev-parse --git-dir');
140
- } else {
141
- await run('git rev-parse --git-dir');
142
- }
143
- } catch (error) {
144
- if (!isDryRun) {
145
- // Preserve the original error message to help with debugging
146
- const originalMessage = error.message || error.toString();
147
- throw new Error(`Not in a git repository or git command failed: ${originalMessage}. Please run this command from within a git repository.`);
148
- }
149
- }
150
- // Check for uncommitted changes
151
- logger.info('PRECHECK_GIT_STATUS: Checking for uncommitted changes | Command: git status --porcelain | Requirement: Clean working directory');
152
- try {
153
- if (isDryRun) {
154
- logger.info('PRECHECK_GIT_STATUS: Would verify clean working directory | Mode: dry-run | Command: git status --porcelain');
155
- } else {
156
- const { stdout } = await run('git status --porcelain');
157
- if (stdout.trim()) {
158
- throw new Error('Working directory has uncommitted changes. Please commit or stash your changes before running publish.');
159
- }
160
- }
161
- } catch (error) {
162
- if (!isDryRun) {
163
- // Preserve the original error message to help with debugging
164
- const originalMessage = error.message || error.toString();
165
- throw new Error(`Failed to check git status: ${originalMessage}. Please ensure you are in a valid git repository and try again.`);
166
- }
167
- }
168
- // Use the passed target branch or fallback to config/default
169
- const effectiveTargetBranch = targetBranch || ((_runConfig_publish = runConfig.publish) === null || _runConfig_publish === void 0 ? void 0 : _runConfig_publish.targetBranch) || 'main';
170
- // Check that we're not running from the target branch
171
- logger.info('PRECHECK_BRANCH: Verifying current branch is not target branch | Target: ' + effectiveTargetBranch + ' | Requirement: Must run from feature branch');
172
- if (isDryRun) {
173
- logger.info(`PRECHECK_BRANCH: Would verify current branch is not target branch | Mode: dry-run | Target: ${effectiveTargetBranch}`);
174
- } else {
175
- const currentBranch = await GitHub.getCurrentBranchName();
176
- if (currentBranch === effectiveTargetBranch) {
177
- throw new Error(`Cannot run publish from the target branch '${effectiveTargetBranch}'. Please switch to a different branch before running publish.`);
178
- }
179
- }
180
- // Check target branch sync with remote
181
- logger.info(`PRECHECK_BRANCH_SYNC: Checking target branch sync with remote | Branch: ${effectiveTargetBranch} | Remote: origin | Requirement: Branches must be synchronized`);
182
- if (isDryRun) {
183
- logger.info(`PRECHECK_BRANCH_SYNC: Would verify target branch is in sync with remote | Mode: dry-run | Branch: ${effectiveTargetBranch} | Remote: origin`);
184
- } else {
185
- // Only check if local target branch exists (it's okay if it doesn't exist locally)
186
- const targetBranchExists = await localBranchExists(effectiveTargetBranch);
187
- if (targetBranchExists) {
188
- const syncStatus = await isBranchInSyncWithRemote(effectiveTargetBranch);
189
- if (!syncStatus.inSync) {
190
- var _runConfig_publish2;
191
- logger.error(`BRANCH_SYNC_FAILED: Target branch not synchronized with remote | Branch: ${effectiveTargetBranch} | Status: out-of-sync | Impact: Cannot proceed with publish`);
192
- logger.error('');
193
- if (syncStatus.error) {
194
- logger.error(`BRANCH_SYNC_ERROR: ${syncStatus.error}`);
195
- } else if (syncStatus.localSha && syncStatus.remoteSha) {
196
- logger.error(`BRANCH_SYNC_DIVERGENCE: Local and remote commits differ | Local SHA: ${syncStatus.localSha.substring(0, 8)} | Remote SHA: ${syncStatus.remoteSha.substring(0, 8)}`);
197
- }
198
- // Check if agentic publish is enabled
199
- if ((_runConfig_publish2 = runConfig.publish) === null || _runConfig_publish2 === void 0 ? void 0 : _runConfig_publish2.agenticPublish) {
200
- logger.info('');
201
- logger.info('AGENTIC_PUBLISH_STARTING: Attempting automatic diagnosis and fix | Mode: agentic | Feature: AI-powered recovery');
202
- try {
203
- var _syncStatus_localSha, _syncStatus_remoteSha, _runConfig_publish3;
204
- const currentBranch = await GitHub.getCurrentBranchName();
205
- const agenticResult = await runAgenticPublish({
206
- targetBranch: effectiveTargetBranch,
207
- sourceBranch: currentBranch,
208
- issue: 'branch_sync',
209
- issueDetails: syncStatus.error || `Local SHA: ${(_syncStatus_localSha = syncStatus.localSha) === null || _syncStatus_localSha === void 0 ? void 0 : _syncStatus_localSha.substring(0, 8)}, Remote SHA: ${(_syncStatus_remoteSha = syncStatus.remoteSha) === null || _syncStatus_remoteSha === void 0 ? void 0 : _syncStatus_remoteSha.substring(0, 8)}`,
210
- workingDirectory: process.cwd(),
211
- maxIterations: ((_runConfig_publish3 = runConfig.publish) === null || _runConfig_publish3 === void 0 ? void 0 : _runConfig_publish3.agenticPublishMaxIterations) || 10,
212
- storage,
213
- logger,
214
- dryRun: runConfig.dryRun
215
- });
216
- // Display the formatted result
217
- const formattedResult = formatAgenticPublishResult(agenticResult);
218
- logger.info(formattedResult);
219
- if (agenticResult.success) {
220
- logger.info('AGENTIC_PUBLISH_SUCCESS: Issue resolved automatically | Status: ready-to-retry | Action: Re-running prechecks');
221
- // Re-run the sync check to verify it was fixed
222
- const reSyncStatus = await isBranchInSyncWithRemote(effectiveTargetBranch);
223
- if (reSyncStatus.inSync) {
224
- logger.info(`BRANCH_SYNC_VERIFIED: Target branch is now synchronized with remote | Branch: ${effectiveTargetBranch} | Status: in-sync`);
225
- return; // Continue with publish
226
- } else {
227
- logger.warn('AGENTIC_PUBLISH_VERIFICATION_FAILED: Branch still not in sync after agentic fix | Status: needs-attention');
228
- }
229
- }
230
- if (agenticResult.requiresManualIntervention) {
231
- throw new Error(`Target branch '${effectiveTargetBranch}' requires manual intervention. Please see the steps above.`);
232
- } else {
233
- throw new Error(`Agentic publish could not resolve the issue automatically. Please see the analysis above.`);
234
- }
235
- } catch (agenticError) {
236
- logger.warn(`AGENTIC_PUBLISH_FAILED: Agentic recovery failed | Error: ${agenticError.message} | Fallback: Manual steps`);
237
- // Fall through to manual steps
238
- }
239
- }
240
- logger.error('');
241
- logger.error('RESOLUTION_STEPS: Manual intervention required to sync branches:');
242
- logger.error(` Step 1: Switch to target branch | Command: git checkout ${effectiveTargetBranch}`);
243
- logger.error(` Step 2: Pull latest changes | Command: git pull origin ${effectiveTargetBranch}`);
244
- logger.error(' Step 3: Resolve merge conflicts if present');
245
- logger.error(' Step 4: Return to feature branch and retry publish');
246
- logger.error('');
247
- logger.error(`ALTERNATIVE_OPTION: Automatic sync available | Command: kodrdriv publish --sync-target | Branch: ${effectiveTargetBranch}`);
248
- logger.error(`ALTERNATIVE_OPTION_AI: AI-powered recovery available | Command: kodrdriv publish --agentic-publish | Branch: ${effectiveTargetBranch}`);
249
- throw new Error(`Target branch '${effectiveTargetBranch}' is not in sync with remote. Please sync the branch before running publish.`);
250
- } else {
251
- logger.info(`BRANCH_SYNC_VERIFIED: Target branch is synchronized with remote | Branch: ${effectiveTargetBranch} | Status: in-sync`);
252
- }
253
- } else {
254
- logger.info(`BRANCH_NOT_LOCAL: Target branch does not exist locally | Branch: ${effectiveTargetBranch} | Action: Will be created during publish process`);
255
- }
256
- }
257
- // Check GitHub Actions workflow configuration
258
- logger.info('PRECHECK_WORKFLOW: Checking GitHub Actions workflow configuration | Target: PR automation | Requirement: Workflows should trigger on pull requests');
259
- if (isDryRun) {
260
- logger.info('PRECHECK_WORKFLOW: Would check if GitHub Actions workflows are configured for pull requests | Mode: dry-run');
261
- } else {
262
- try {
263
- // TODO: Re-enable when checkWorkflowConfiguration is exported from github-tools
264
- // const workflowConfig = await GitHub.checkWorkflowConfiguration(effectiveTargetBranch);
265
- const workflowConfig = {
266
- hasWorkflows: true,
267
- hasPullRequestTriggers: true,
268
- workflowCount: 0,
269
- triggeredWorkflowNames: []
270
- };
271
- if (!workflowConfig.hasWorkflows) ; else if (!workflowConfig.hasPullRequestTriggers) ; else {
272
- logger.info(`WORKFLOW_CONFIGURED: Found workflows that will trigger on pull requests | Target Branch: ${effectiveTargetBranch} | Workflow Count: ${workflowConfig.triggeredWorkflowNames.length}`);
273
- for (const workflowName of workflowConfig.triggeredWorkflowNames){
274
- logger.info(`WORKFLOW_ACTIVE: ${workflowName} | Trigger: pull_request | Target: ${effectiveTargetBranch}`);
275
- }
276
- }
277
- } catch (error) {
278
- // Don't fail the precheck if we can't verify workflows
279
- // The wait logic will handle it later
280
- logger.debug(`WORKFLOW_CHECK_FAILED: Unable to verify workflow configuration | Error: ${error.message} | Impact: Will proceed with publish | Note: Wait logic will handle checks later`);
281
- }
282
- }
283
- // Check if prepublishOnly script exists in package.json
284
- logger.info('PRECHECK_PREPUBLISH: Checking for prepublishOnly script in package.json | Requirement: Must exist to run pre-flight checks | Expected: clean, lint, build, test');
285
- const packageJsonPath = path__default.join(process.cwd(), 'package.json');
286
- if (!await storage.exists(packageJsonPath)) {
287
- if (!isDryRun) {
288
- throw new Error('package.json not found in current directory.');
289
- } else {
290
- logger.warn('PACKAGE_JSON_NOT_FOUND: No package.json in current directory | Mode: dry-run | Impact: Cannot verify prepublishOnly script | Path: ' + packageJsonPath);
291
- }
292
- } else {
293
- var _packageJson_scripts;
294
- let packageJson;
295
- try {
296
- const packageJsonContents = await storage.readFile(packageJsonPath, 'utf-8');
297
- const parsed = safeJsonParse(packageJsonContents, packageJsonPath);
298
- packageJson = validatePackageJson(parsed, packageJsonPath);
299
- } catch (error) {
300
- if (!isDryRun) {
301
- throw new Error('Failed to parse package.json. Please ensure it contains valid JSON.');
302
- } else {
303
- logger.warn('PACKAGE_JSON_PARSE_FAILED: Unable to parse package.json | Mode: dry-run | Impact: Cannot verify prepublishOnly script | Path: ' + packageJsonPath + ' | Requirement: Valid JSON format');
304
- }
305
- }
306
- if (packageJson && !((_packageJson_scripts = packageJson.scripts) === null || _packageJson_scripts === void 0 ? void 0 : _packageJson_scripts.prepublishOnly)) {
307
- if (!isDryRun) {
308
- 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).');
309
- } else {
310
- logger.warn('PREPUBLISH_SCRIPT_MISSING: No prepublishOnly script found in package.json | Mode: dry-run | Requirement: Script must exist | Expected Tasks: clean, lint, build, test | Path: ' + packageJsonPath);
311
- }
312
- }
313
- }
314
- // Check required environment variables
315
- logger.verbose('PRECHECK_ENV_VARS: Checking required environment variables | Source: Configuration and .npmrc | Requirement: All required vars must be set');
316
- const coreRequiredEnvVars = ((_runConfig_publish1 = runConfig.publish) === null || _runConfig_publish1 === void 0 ? void 0 : _runConfig_publish1.requiredEnvVars) || [];
317
- const npmrcEnvVars = isDryRun ? [] : await scanNpmrcForEnvVars(storage); // Skip .npmrc scan in dry run
318
- const allRequiredEnvVars = [
319
- ...new Set([
320
- ...coreRequiredEnvVars,
321
- ...npmrcEnvVars
322
- ])
323
- ];
324
- if (allRequiredEnvVars.length > 0) {
325
- logger.verbose(`ENV_VARS_REQUIRED: Environment variables needed for publish | Variables: ${allRequiredEnvVars.join(', ')} | Count: ${allRequiredEnvVars.length} | Source: config + .npmrc`);
326
- validateEnvironmentVariables(allRequiredEnvVars, isDryRun);
327
- } else {
328
- logger.verbose('ENV_VARS_NONE: No required environment variables specified | Status: No validation needed | Source: config + .npmrc');
329
- }
330
- logger.info('PRECHECK_COMPLETE: All publish prechecks passed successfully | Status: Ready to proceed | Next: Execute publish workflow');
331
- };
332
- // Helper: deep-sort object keys for stable comparison
333
- const sortObjectKeys = (value)=>{
334
- if (Array.isArray(value)) {
335
- return value.map(sortObjectKeys);
336
- }
337
- if (value && typeof value === 'object') {
338
- const sorted = {};
339
- Object.keys(value).sort().forEach((key)=>{
340
- sorted[key] = sortObjectKeys(value[key]);
341
- });
342
- return sorted;
343
- }
344
- return value;
345
- };
346
- // Determine if there are substantive changes compared to the target branch (beyond just version bump)
347
- const isReleaseNecessaryComparedToTarget = async (targetBranch, isDryRun)=>{
348
- const logger = getDryRunLogger(isDryRun);
349
- // We compare current HEAD branch to the provided target branch
350
- const currentBranch = await GitHub.getCurrentBranchName();
351
- // Check if target branch exists before trying to compare
352
- try {
353
- // Validate target branch exists and is accessible
354
- await runSecure('git', [
355
- 'rev-parse',
356
- '--verify',
357
- targetBranch
358
- ]);
359
- } catch (error) {
360
- // Target branch doesn't exist or isn't accessible
361
- logger.verbose(`RELEASE_CHECK_NO_TARGET: Target branch does not exist or is not accessible | Branch: ${targetBranch} | Action: Proceeding with publish | Reason: First release to this branch`);
362
- return {
363
- necessary: true,
364
- reason: `Target branch '${targetBranch}' does not exist; first release to this branch`
365
- };
366
- }
367
- // If branches are identical, nothing to release
368
- const { stdout: namesStdout } = await runSecure('git', [
369
- 'diff',
370
- '--name-only',
371
- `${targetBranch}..${currentBranch}`
372
- ]);
373
- const changedFiles = namesStdout.split('\n').map((s)=>s.trim()).filter(Boolean);
374
- if (changedFiles.length === 0) {
375
- // No definitive signal; proceed with publish rather than skipping
376
- return {
377
- necessary: true,
378
- reason: 'No detectable changes via diff; proceeding conservatively'
379
- };
380
- }
381
- // If any files changed other than package.json or package-lock.json, a release is necessary
382
- const nonVersionFiles = changedFiles.filter((f)=>f !== 'package.json' && f !== 'package-lock.json');
383
- if (nonVersionFiles.length > 0) {
384
- return {
385
- necessary: true,
386
- reason: `Changed files beyond version bump: ${nonVersionFiles.join(', ')}`
387
- };
388
- }
389
- // Only package.json and/or package-lock.json changed. Verify package.json change is only the version field
390
- try {
391
- // Read package.json content from both branches
392
- const { stdout: basePkgStdout } = await runSecure('git', [
393
- 'show',
394
- `${targetBranch}:package.json`
395
- ]);
396
- const { stdout: headPkgStdout } = await runSecure('git', [
397
- 'show',
398
- `${currentBranch}:package.json`
399
- ]);
400
- const basePkg = validatePackageJson(safeJsonParse(basePkgStdout, `${targetBranch}:package.json`), `${targetBranch}:package.json`);
401
- const headPkg = validatePackageJson(safeJsonParse(headPkgStdout, `${currentBranch}:package.json`), `${currentBranch}:package.json`);
402
- const { version: _baseVersion, ...baseWithoutVersion } = basePkg;
403
- const { version: _headVersion, ...headWithoutVersion } = headPkg;
404
- const baseSorted = sortObjectKeys(baseWithoutVersion);
405
- const headSorted = sortObjectKeys(headWithoutVersion);
406
- const equalExceptVersion = JSON.stringify(baseSorted) === JSON.stringify(headSorted);
407
- if (equalExceptVersion) {
408
- const currentVersion = headPkg.version;
409
- const targetVersion = basePkg.version;
410
- return {
411
- necessary: false,
412
- reason: `No meaningful changes detected:\n • Current version: ${currentVersion}\n • Target branch version: ${targetVersion}\n • Only package.json version field differs\n\n To force republish: Add meaningful code changes or use --force (not yet implemented)`
413
- };
414
- }
415
- // Other fields changed inside package.json
416
- return {
417
- necessary: true,
418
- reason: 'package.json changes beyond version field'
419
- };
420
- } catch (error) {
421
- // Conservative: if we cannot prove it is only a version change, proceed with release
422
- logger.verbose(`RELEASE_CHECK_COMPARISON_FAILED: Unable to conclusively compare package.json changes | Error: ${error.message} | Action: Proceeding conservatively with publish | Reason: Cannot verify version-only change`);
423
- return {
424
- necessary: true,
425
- reason: 'Could not compare package.json safely'
426
- };
427
- }
428
- };
429
- const handleTargetBranchSyncRecovery = async (runConfig, targetBranch)=>{
430
- const isDryRun = runConfig.dryRun || false;
431
- const logger = getDryRunLogger(isDryRun);
432
- logger.info(`BRANCH_SYNC_ATTEMPTING: Initiating sync of target branch with remote | Branch: ${targetBranch} | Remote: origin | Operation: fetch + merge`);
433
- if (isDryRun) {
434
- logger.info(`BRANCH_SYNC_DRY_RUN: Would attempt to sync branch with remote | Mode: dry-run | Branch: ${targetBranch} | Remote: origin`);
435
- return;
436
- }
437
- const syncResult = await safeSyncBranchWithRemote(targetBranch);
438
- if (syncResult.success) {
439
- logger.info(`BRANCH_SYNC_SUCCESS: Successfully synchronized branch with remote | Branch: ${targetBranch} | Remote: origin | Status: in-sync`);
440
- logger.info('BRANCH_SYNC_NEXT_STEP: Ready to proceed with publish | Action: Re-run publish command | Branch: ' + targetBranch);
441
- } else if (syncResult.conflictResolutionRequired) {
442
- logger.error(`BRANCH_SYNC_CONFLICTS: Sync failed due to merge conflicts | Branch: ${targetBranch} | Status: conflicts-detected | Resolution: Manual intervention required`);
443
- logger.error('');
444
- logger.error('CONFLICT_RESOLUTION_STEPS: Manual conflict resolution required:');
445
- logger.error(` Step 1: Switch to target branch | Command: git checkout ${targetBranch}`);
446
- logger.error(` Step 2: Pull and resolve conflicts | Command: git pull origin ${targetBranch}`);
447
- logger.error(' Step 3: Commit resolved changes | Command: git commit');
448
- logger.error(' Step 4: Return to feature branch and retry | Command: kodrdriv publish');
449
- logger.error('');
450
- throw new Error(`Target branch '${targetBranch}' has conflicts that require manual resolution.`);
451
- } else {
452
- logger.error(`BRANCH_SYNC_FAILED: Sync operation failed | Branch: ${targetBranch} | Error: ${syncResult.error} | Remote: origin`);
453
- throw new Error(`Failed to sync target branch: ${syncResult.error}`);
454
- }
455
- };
456
- const execute = async (runConfig)=>{
457
- var _runConfig_publish, _runConfig_publish1, _runConfig_publish2, _runConfig_publish3;
458
- const isDryRun = runConfig.dryRun || false;
459
- const logger = getDryRunLogger(isDryRun);
460
- const storage = createStorage();
461
- // Get current branch for branch-dependent targeting
462
- let currentBranch;
463
- if (isDryRun) {
464
- currentBranch = 'mock-branch';
465
- } else {
466
- currentBranch = await GitHub.getCurrentBranchName();
467
- // Fetch latest remote information to avoid conflicts
468
- logger.info('GIT_FETCH_STARTING: Fetching latest remote information | Remote: origin | Purpose: Avoid conflicts during publish | Command: git fetch origin');
469
- try {
470
- await run('git fetch origin');
471
- logger.info('GIT_FETCH_SUCCESS: Successfully fetched latest remote information | Remote: origin | Status: up-to-date');
472
- } catch (error) {
473
- logger.warn(`GIT_FETCH_FAILED: Unable to fetch from remote | Remote: origin | Error: ${error.message} | Impact: May cause conflicts if remote has changes`);
474
- }
475
- // Sync current branch with remote to avoid conflicts
476
- logger.info(`CURRENT_BRANCH_SYNC: Synchronizing current branch with remote | Branch: ${currentBranch} | Remote: origin | Purpose: Avoid conflicts during publish`);
477
- try {
478
- const remoteExists = await run(`git ls-remote --exit-code --heads origin ${currentBranch}`).then(()=>true).catch(()=>false);
479
- if (remoteExists) {
480
- // Use explicit fetch+merge instead of pull to avoid git config conflicts
481
- await runGitWithLock(process.cwd(), async ()=>{
482
- await run(`git fetch origin ${currentBranch}`);
483
- await run(`git merge origin/${currentBranch} --no-edit`);
484
- }, `sync ${currentBranch}`);
485
- logger.info(`CURRENT_BRANCH_SYNCED: Successfully synchronized current branch with remote | Branch: ${currentBranch} | Remote: origin/${currentBranch} | Status: in-sync`);
486
- } else {
487
- logger.info(`REMOTE_BRANCH_NOT_FOUND: No remote branch exists | Branch: ${currentBranch} | Remote: origin | Action: Will be created on first push`);
488
- }
489
- } catch (error) {
490
- if (error.message && error.message.includes('CONFLICT')) {
491
- logger.error(`MERGE_CONFLICTS_DETECTED: Conflicts found when syncing current branch with remote | Branch: ${currentBranch} | Remote: origin/${currentBranch} | Status: conflicts-require-resolution`);
492
- logger.error(`CONFLICT_RESOLUTION_REQUIRED: Manual intervention needed to resolve conflicts and continue:`);
493
- logger.error(` Step 1: Resolve conflicts in affected files`);
494
- logger.error(` Step 2: Stage resolved files | Command: git add <resolved-files>`);
495
- logger.error(` Step 3: Commit resolution | Command: git commit`);
496
- logger.error(` Step 4: Retry publish | Command: kodrdriv publish`);
497
- throw new Error(`Merge conflicts detected when syncing ${currentBranch} with remote. Please resolve conflicts manually.`);
498
- } else {
499
- logger.warn(`CURRENT_BRANCH_SYNC_FAILED: Unable to sync current branch with remote | Branch: ${currentBranch} | Remote: origin/${currentBranch} | Error: ${error.message} | Impact: May cause issues during publish`);
500
- }
501
- }
502
- }
503
- // Determine target branch and version strategy based on branch configuration
504
- let targetBranch = ((_runConfig_publish = runConfig.publish) === null || _runConfig_publish === void 0 ? void 0 : _runConfig_publish.targetBranch) || 'main';
505
- let branchDependentVersioning = false;
506
- // Check for branches configuration
507
- if (runConfig.branches && runConfig.branches[currentBranch]) {
508
- branchDependentVersioning = true;
509
- const branchConfig = runConfig.branches[currentBranch];
510
- if (branchConfig.targetBranch) {
511
- targetBranch = branchConfig.targetBranch;
512
- }
513
- logger.info(`BRANCH_DEPENDENT_TARGETING: Branch-specific configuration active | Source: ${currentBranch} | Target: ${targetBranch} | Feature: Branch-dependent versioning and targeting`);
514
- logger.info(`BRANCH_CONFIGURATION_SOURCE: Current branch | Branch: ${currentBranch} | Type: source`);
515
- logger.info(`BRANCH_CONFIGURATION_TARGET: Target branch for publish | Branch: ${targetBranch} | Type: destination`);
516
- // Look at target branch config to show version strategy
517
- const targetBranchConfig = runConfig.branches[targetBranch];
518
- if (targetBranchConfig === null || targetBranchConfig === void 0 ? void 0 : targetBranchConfig.version) {
519
- const versionType = targetBranchConfig.version.type;
520
- const versionTag = targetBranchConfig.version.tag;
521
- const versionIncrement = targetBranchConfig.version.increment;
522
- logger.info(`VERSION_STRATEGY: Target branch version configuration | Branch: ${targetBranch} | Type: ${versionType} | Tag: ${versionTag || 'none'} | Increment: ${versionIncrement ? 'enabled' : 'disabled'}`);
523
- }
524
- } else {
525
- logger.debug(`BRANCH_TARGETING_DEFAULT: No branch-specific configuration found | Branch: ${currentBranch} | Action: Using default target | Target: ${targetBranch}`);
526
- }
527
- // Handle --sync-target flag
528
- if ((_runConfig_publish1 = runConfig.publish) === null || _runConfig_publish1 === void 0 ? void 0 : _runConfig_publish1.syncTarget) {
529
- await handleTargetBranchSyncRecovery(runConfig, targetBranch);
530
- return; // Exit after sync operation
531
- }
532
- // Check if target branch exists and create it if needed
533
- logger.info(`TARGET_BRANCH_CHECK: Verifying target branch existence | Branch: ${targetBranch} | Action: Create if missing | Source: Current HEAD`);
534
- if (isDryRun) {
535
- logger.info(`TARGET_BRANCH_CHECK: Would verify target branch exists and create if needed | Mode: dry-run | Branch: ${targetBranch}`);
536
- } else {
537
- const targetBranchExists = await localBranchExists(targetBranch);
538
- if (!targetBranchExists) {
539
- // Check if it exists on remote
540
- const remoteExists = await remoteBranchExists(targetBranch);
541
- if (remoteExists) {
542
- logger.info(`TARGET_BRANCH_TRACKING: Target branch exists on remote but not locally, tracking origin/${targetBranch} | Branch: ${targetBranch}`);
543
- try {
544
- await runGitWithLock(process.cwd(), async ()=>{
545
- // Create local branch tracking remote
546
- await runSecure('git', [
547
- 'branch',
548
- targetBranch,
549
- `origin/${targetBranch}`
550
- ]);
551
- logger.info(`TARGET_BRANCH_CREATED: Successfully created local tracking branch | Branch: ${targetBranch} | Source: origin/${targetBranch}`);
552
- }, `track target branch ${targetBranch}`);
553
- } catch (error) {
554
- throw new Error(`Failed to track target branch '${targetBranch}': ${error.message}`);
555
- }
556
- } else {
557
- logger.info(`TARGET_BRANCH_CREATING: Target branch does not exist locally or on remote, creating from current branch | Branch: ${targetBranch} | Source: HEAD | Remote: origin`);
558
- try {
559
- // Wrap git branch and push operations with lock
560
- await runGitWithLock(process.cwd(), async ()=>{
561
- // Create the target branch from the current HEAD
562
- await runSecure('git', [
563
- 'branch',
564
- targetBranch,
565
- 'HEAD'
566
- ]);
567
- logger.info(`TARGET_BRANCH_CREATED: Successfully created target branch locally | Branch: ${targetBranch} | Source: HEAD`);
568
- // Push the new branch to origin
569
- await runSecure('git', [
570
- 'push',
571
- 'origin',
572
- targetBranch
573
- ]);
574
- logger.info(`TARGET_BRANCH_PUSHED: Successfully pushed new target branch to remote | Branch: ${targetBranch} | Remote: origin/${targetBranch}`);
575
- }, `create and push target branch ${targetBranch}`);
576
- } catch (error) {
577
- throw new Error(`Failed to create target branch '${targetBranch}': ${error.message}`);
578
- }
579
- }
580
- } else {
581
- logger.info(`TARGET_BRANCH_EXISTS: Target branch already exists locally | Branch: ${targetBranch} | Status: ready`);
582
- }
583
- }
584
- // Run prechecks before starting any work
585
- await runPrechecks(runConfig, targetBranch);
586
- // Early check: determine if a release is necessary compared to target branch
587
- logger.info('RELEASE_NECESSITY_CHECK: Evaluating if release is required | Comparison: current branch vs target | Target: ' + targetBranch + ' | Purpose: Avoid unnecessary publishes');
588
- try {
589
- const necessity = await isReleaseNecessaryComparedToTarget(targetBranch, isDryRun);
590
- if (!necessity.necessary) {
591
- logger.info(`\nRELEASE_SKIPPED: No meaningful changes detected, skipping publish | Reason: ${necessity.reason} | Target: ${targetBranch}`);
592
- // Emit a machine-readable marker so tree mode can detect skip and avoid propagating versions
593
- // CRITICAL: Use console.log to write to stdout (logger.info goes to stderr via winston)
594
- // eslint-disable-next-line no-console
595
- console.log('KODRDRIV_PUBLISH_SKIPPED');
596
- return;
597
- } else {
598
- logger.verbose(`RELEASE_PROCEEDING: Meaningful changes detected, continuing with publish | Reason: ${necessity.reason} | Target: ${targetBranch}`);
599
- }
600
- } catch (error) {
601
- // On unexpected errors, proceed with publish to avoid false negatives blocking releases
602
- logger.verbose(`RELEASE_NECESSITY_CHECK_ERROR: Unable to determine release necessity | Error: ${error.message} | Action: Proceeding conservatively with publish | Rationale: Avoid blocking valid releases`);
603
- }
604
- logger.info('RELEASE_PROCESS_STARTING: Initiating release workflow | Target: ' + targetBranch + ' | Phase: dependency updates and version management');
605
- let pr = null;
606
- if (isDryRun) {
607
- logger.info('PR_CHECK: Would check for existing pull request | Mode: dry-run | Action: Skip PR lookup');
608
- logger.info('PR_ASSUMPTION: Assuming no existing PR found | Mode: dry-run | Purpose: Demo workflow');
609
- } else {
610
- const branchName = await GitHub.getCurrentBranchName();
611
- pr = await GitHub.findOpenPullRequestByHeadRef(branchName);
612
- }
613
- if (pr) {
614
- logger.info(`PR_FOUND: Existing pull request detected for current branch | URL: ${pr.html_url} | Status: open`);
615
- } else {
616
- var _runConfig_publish4, _runConfig_publish5, _runConfig_publish6, _runConfig_publish7, _runConfig_publish8, _runConfig_publish9, _runConfig_publish10, _runConfig_publish11, _runConfig_publish12, _releaseConfig_release;
617
- logger.info('PR_NOT_FOUND: No open pull request exists for current branch | Action: Starting new release publishing process | Next: Prepare dependencies and version');
618
- // STEP 1: Prepare for release (update dependencies and run prepublish checks) with NO version bump yet
619
- logger.verbose('RELEASE_PREP_STARTING: Preparing for release | Phase: dependency management | Action: Switch from workspace to remote dependencies | Version Bump: Not yet applied');
620
- // Clean up any npm link references before updating dependencies
621
- logger.verbose('NPM_LINK_CHECK: Scanning package-lock.json for npm link references | File: package-lock.json | Purpose: Remove development symlinks before publish');
622
- await cleanupNpmLinkReferences(isDryRun);
623
- // Update inter-project dependencies if --update-deps flag is present
624
- const updateDepsScope = (_runConfig_publish4 = runConfig.publish) === null || _runConfig_publish4 === void 0 ? void 0 : _runConfig_publish4.updateDeps;
625
- if (updateDepsScope) {
626
- logger.info(`INTER_PROJECT_DEPS_UPDATE: Updating inter-project dependencies | Scope: ${updateDepsScope} | Type: inter-project | Command: kodrdriv updates`);
627
- const Updates = await import('./updates.js');
628
- const updatesConfig = {
629
- ...runConfig,
630
- dryRun: isDryRun,
631
- updates: {
632
- scope: updateDepsScope,
633
- interProject: true
634
- }
635
- };
636
- await Updates.execute(updatesConfig);
637
- }
638
- logger.verbose('DEPS_UPDATE_REGISTRY: Updating dependencies to latest versions from npm registry | Source: registry | Target: package.json');
639
- const updatePatterns = (_runConfig_publish5 = runConfig.publish) === null || _runConfig_publish5 === void 0 ? void 0 : _runConfig_publish5.dependencyUpdatePatterns;
640
- if (updatePatterns && updatePatterns.length > 0) {
641
- logger.verbose(`DEPS_UPDATE_PATTERNS: Updating dependencies matching specified patterns | Patterns: ${updatePatterns.join(', ')} | Count: ${updatePatterns.length} | Command: npm update`);
642
- const patternsArg = updatePatterns.join(' ');
643
- await runWithDryRunSupport(`npm update ${patternsArg}`, isDryRun);
644
- } else {
645
- logger.verbose('DEPS_UPDATE_ALL: No dependency patterns specified, updating all dependencies | Scope: all | Command: npm update');
646
- await runWithDryRunSupport('npm update', isDryRun);
647
- }
648
- logger.info('PREPUBLISH_SCRIPT_RUNNING: Executing prepublishOnly script | Script: prepublishOnly | Purpose: Run pre-flight checks (clean, lint, build, test)');
649
- await runWithDryRunSupport('npm run prepublishOnly', isDryRun, {}, true); // Use inherited stdio
650
- // STEP 2: Commit dependency updates if any (still no version bump)
651
- logger.verbose('DEPS_STAGING: Staging dependency updates for commit | Files: package.json + package-lock.json | Command: git add | Note: Version bump not yet applied');
652
- // Check if package-lock.json exists before trying to stage it
653
- const packageLockExists = await storage.exists('package-lock.json');
654
- const filesToStage = packageLockExists ? 'package.json package-lock.json' : 'package.json';
655
- // Wrap git operations with repository lock to prevent .git/index.lock conflicts
656
- await runGitWithLock(process.cwd(), async ()=>{
657
- await runWithDryRunSupport(`git add ${filesToStage}`, isDryRun);
658
- }, 'stage dependency updates');
659
- logger.verbose('DEPS_COMMIT_CHECK: Checking for staged dependency updates | Command: git status | Purpose: Determine if commit needed');
660
- if (isDryRun) {
661
- logger.verbose('DEPS_COMMIT_DRY_RUN: Would create dependency update commit if changes are staged | Mode: dry-run');
662
- } else {
663
- if (await hasStagedChanges()) {
664
- logger.verbose('DEPS_COMMIT_CREATING: Staged dependency changes detected, creating commit | Files: ' + filesToStage + ' | Action: Execute commit command');
665
- // Commit also needs git lock
666
- await runGitWithLock(process.cwd(), async ()=>{
667
- await execute$2(runConfig);
668
- }, 'commit dependency updates');
669
- } else {
670
- logger.verbose('DEPS_COMMIT_SKIPPED: No dependency changes to commit | Files: ' + filesToStage + ' | Action: Skipping commit step');
671
- }
672
- }
673
- // STEP 3: Merge target branch into working branch (optional - now skipped by default since post-publish sync keeps branches in sync)
674
- const skipPreMerge = ((_runConfig_publish6 = runConfig.publish) === null || _runConfig_publish6 === void 0 ? void 0 : _runConfig_publish6.skipPrePublishMerge) !== false; // Default to true (skip)
675
- if (skipPreMerge) {
676
- logger.verbose(`PRE_MERGE_SKIPPED: Skipping pre-publish merge of target branch | Reason: Post-publish sync handles branch synchronization | Target: ${targetBranch} | Config: skipPrePublishMerge=true`);
677
- } else {
678
- logger.info(`PRE_MERGE_STARTING: Merging target branch into current branch | Target: ${targetBranch} | Purpose: Avoid version conflicts | Phase: pre-publish`);
679
- if (isDryRun) {
680
- logger.info(`Would merge ${targetBranch} into current branch`);
681
- } else {
682
- // Wrap entire merge process with git lock (involves fetch, merge, checkout, add, commit)
683
- await runGitWithLock(process.cwd(), async ()=>{
684
- // Fetch the latest target branch
685
- try {
686
- await run(`git fetch origin ${targetBranch}:${targetBranch}`);
687
- logger.info(`TARGET_BRANCH_FETCHED: Successfully fetched latest target branch | Branch: ${targetBranch} | Remote: origin/${targetBranch} | Purpose: Pre-merge sync`);
688
- } catch (fetchError) {
689
- logger.warn(`TARGET_BRANCH_FETCH_FAILED: Unable to fetch target branch | Branch: ${targetBranch} | Error: ${fetchError.message} | Impact: Proceeding without merge, PR may have conflicts`);
690
- logger.warn('MERGE_SKIPPED_NO_FETCH: Continuing without pre-merge | Reason: Target branch fetch failed | Impact: PR may require manual conflict resolution');
691
- }
692
- // Check if merge is needed (avoid unnecessary merge commits)
693
- try {
694
- const { stdout: mergeBase } = await run(`git merge-base HEAD ${targetBranch}`);
695
- const { stdout: targetCommit } = await run(`git rev-parse ${targetBranch}`);
696
- if (mergeBase.trim() === targetCommit.trim()) {
697
- logger.info(`MERGE_NOT_NEEDED: Current branch already up-to-date with target | Branch: ${targetBranch} | Status: in-sync | Action: Skipping merge`);
698
- } else {
699
- // Try to merge target branch into current branch
700
- let mergeSucceeded = false;
701
- try {
702
- await run(`git merge ${targetBranch} --no-edit -m "Merge ${targetBranch} to sync before version bump"`);
703
- logger.info(`MERGE_SUCCESS: Successfully merged target branch into current branch | Target: ${targetBranch} | Purpose: Sync before version bump`);
704
- mergeSucceeded = true;
705
- } catch (mergeError) {
706
- // If merge conflicts occur, check if they're only in version-related files
707
- const errorText = [
708
- mergeError.message || '',
709
- mergeError.stdout || '',
710
- mergeError.stderr || ''
711
- ].join(' ');
712
- if (errorText.includes('CONFLICT')) {
713
- logger.warn(`MERGE_CONFLICTS_DETECTED: Merge conflicts found, attempting automatic resolution | Target: ${targetBranch} | Strategy: Auto-resolve version files`);
714
- // Get list of conflicted files
715
- const { stdout: conflictedFiles } = await run('git diff --name-only --diff-filter=U');
716
- const conflicts = conflictedFiles.trim().split('\n').filter(Boolean);
717
- logger.verbose(`MERGE_CONFLICTS_LIST: Conflicted files detected | Files: ${conflicts.join(', ')} | Count: ${conflicts.length}`);
718
- // Check if conflicts are only in package.json and package-lock.json
719
- const versionFiles = [
720
- 'package.json',
721
- 'package-lock.json'
722
- ];
723
- const nonVersionConflicts = conflicts.filter((f)=>!versionFiles.includes(f));
724
- if (nonVersionConflicts.length > 0) {
725
- logger.error(`MERGE_AUTO_RESOLVE_FAILED: Cannot auto-resolve conflicts in non-version files | Files: ${nonVersionConflicts.join(', ')} | Count: ${nonVersionConflicts.length} | Resolution: Manual intervention required`);
726
- logger.error('');
727
- logger.error('CONFLICT_RESOLUTION_REQUIRED: Manual steps to resolve conflicts:');
728
- logger.error(' Step 1: Resolve conflicts in the files listed above');
729
- logger.error(' Step 2: Stage resolved files | Command: git add <resolved-files>');
730
- logger.error(' Step 3: Complete merge commit | Command: git commit');
731
- logger.error(' Step 4: Resume publish process | Command: kodrdriv publish');
732
- logger.error('');
733
- throw new Error(`Merge conflicts in non-version files. Please resolve manually.`);
734
- }
735
- // Auto-resolve version conflicts by accepting current branch versions
736
- // (keep our working branch's version, which is likely already updated)
737
- logger.info(`MERGE_AUTO_RESOLVING: Automatically resolving version conflicts | Strategy: Keep current branch versions | Files: ${versionFiles.join(', ')}`);
738
- for (const file of conflicts){
739
- if (versionFiles.includes(file)) {
740
- await run(`git checkout --ours ${file}`);
741
- await run(`git add ${file}`);
742
- logger.verbose(`MERGE_FILE_RESOLVED: Resolved file using current branch version | File: ${file} | Strategy: checkout --ours`);
743
- }
744
- }
745
- // Complete the merge
746
- await run(`git commit --no-edit -m "Merge ${targetBranch} to sync before version bump (auto-resolved version conflicts)"`);
747
- logger.info(`MERGE_AUTO_RESOLVE_SUCCESS: Successfully auto-resolved version conflicts and completed merge | Target: ${targetBranch} | Files: ${versionFiles.join(', ')}`);
748
- mergeSucceeded = true;
749
- } else {
750
- // Not a conflict error, re-throw
751
- throw mergeError;
752
- }
753
- }
754
- // Only run npm install if merge actually happened
755
- if (mergeSucceeded) {
756
- // Run npm install to update package-lock.json based on merged package.json
757
- logger.info('POST_MERGE_NPM_INSTALL: Running npm install after merge | Purpose: Update package-lock.json based on merged package.json | Command: npm install');
758
- await run('npm install');
759
- logger.info('POST_MERGE_NPM_COMPLETE: npm install completed successfully | Status: Dependencies synchronized');
760
- // Commit any changes from npm install (e.g., package-lock.json updates)
761
- const { stdout: mergeChangesStatus } = await run('git status --porcelain');
762
- if (mergeChangesStatus.trim()) {
763
- logger.verbose('POST_MERGE_CHANGES_DETECTED: Changes detected after npm install | Action: Staging for commit | Command: git add');
764
- // Check if package-lock.json exists before trying to stage it
765
- const packageLockExistsPostMerge = await storage.exists('package-lock.json');
766
- const filesToStagePostMerge = packageLockExistsPostMerge ? 'package.json package-lock.json' : 'package.json';
767
- await run(`git add ${filesToStagePostMerge}`);
768
- if (await hasStagedChanges()) {
769
- logger.verbose('POST_MERGE_COMMIT: Committing post-merge changes | Files: ' + filesToStagePostMerge + ' | Purpose: Finalize merge');
770
- await execute$2(runConfig);
771
- }
772
- }
773
- }
774
- }
775
- } catch (error) {
776
- // Only catch truly unexpected errors here
777
- logger.error(`MERGE_UNEXPECTED_ERROR: Unexpected error during merge process | Error: ${error.message} | Target: ${targetBranch} | Action: Aborting publish`);
778
- throw error;
779
- }
780
- }, `merge ${targetBranch} into current branch`);
781
- }
782
- }
783
- // STEP 4: Determine and set target version AFTER checks, dependency commit, and target branch merge
784
- logger.info('Determining target version...');
785
- let newVersion;
786
- if (isDryRun) {
787
- logger.info('Would determine target version and update package.json');
788
- newVersion = '1.0.0'; // Mock version for dry run
789
- } else {
790
- var _runConfig_publish13;
791
- const packageJsonContents = await storage.readFile('package.json', 'utf-8');
792
- const parsed = safeJsonParse(packageJsonContents, 'package.json');
793
- const packageJson = validatePackageJson(parsed, 'package.json');
794
- const currentVersion = packageJson.version;
795
- let proposedVersion;
796
- let finalTargetBranch = targetBranch;
797
- if (branchDependentVersioning && runConfig.branches) {
798
- // Use branch-dependent versioning logic
799
- const branchDependentResult = await calculateBranchDependentVersion(currentVersion, currentBranch, runConfig.branches, targetBranch);
800
- proposedVersion = branchDependentResult.version;
801
- finalTargetBranch = branchDependentResult.targetBranch;
802
- logger.info(`VERSION_BRANCH_DEPENDENT_CALCULATED: Branch-dependent version calculated | Current: ${currentVersion} | Proposed: ${proposedVersion} | Strategy: branch-dependent`);
803
- logger.info(`TARGET_BRANCH_FINAL: Final target branch determined | Branch: ${finalTargetBranch} | Source: branch-dependent config`);
804
- // Update targetBranch for the rest of the function
805
- targetBranch = finalTargetBranch;
806
- } else {
807
- var _runConfig_publish14;
808
- // Use existing logic for backward compatibility
809
- const targetVersionInput = ((_runConfig_publish14 = runConfig.publish) === null || _runConfig_publish14 === void 0 ? void 0 : _runConfig_publish14.targetVersion) || 'patch';
810
- proposedVersion = calculateTargetVersion(currentVersion, targetVersionInput);
811
- }
812
- const targetTagName = `v${proposedVersion}`;
813
- const tagExists = await checkIfTagExists(targetTagName);
814
- // Smart tag conflict handling
815
- if (tagExists) {
816
- const { getNpmPublishedVersion, getTagInfo } = await import('../util/general.js');
817
- logger.warn(`TAG_ALREADY_EXISTS: Tag already exists in repository | Tag: ${targetTagName} | Status: conflict | Action: Check npm registry`);
818
- // Check if this version is published on npm
819
- const npmVersion = await getNpmPublishedVersion(packageJson.name);
820
- const tagInfo = await getTagInfo(targetTagName);
821
- if (npmVersion === proposedVersion) {
822
- var _runConfig_publish15;
823
- // Version is already published on npm
824
- logger.info(`VERSION_ALREADY_PUBLISHED: Version already published on npm registry | Version: ${proposedVersion} | Status: published | Action: Skipping`);
825
- logger.info(`PUBLISH_SKIPPED_DUPLICATE: Skipping publish operation | Reason: Package already at target version | Version: ${proposedVersion}`);
826
- logger.info('');
827
- logger.info('REPUBLISH_OPTIONS: Options if you need to republish:');
828
- logger.info(` Option 1: Bump version | Command: npm version patch (or minor/major)`);
829
- logger.info(` Option 2: Re-run publish | Command: kodrdriv publish`);
830
- logger.info('');
831
- if ((_runConfig_publish15 = runConfig.publish) === null || _runConfig_publish15 === void 0 ? void 0 : _runConfig_publish15.skipAlreadyPublished) {
832
- logger.info('PUBLISH_SKIPPED_FLAG: Skipping package due to flag | Flag: --skip-already-published | Version: ' + proposedVersion + ' | Status: skipped');
833
- // Emit skip marker for tree mode detection
834
- // eslint-disable-next-line no-console
835
- console.log('KODRDRIV_PUBLISH_SKIPPED');
836
- return; // Exit without error
837
- } else {
838
- throw new Error(`Version ${proposedVersion} already published. Use --skip-already-published to continue.`);
839
- }
840
- } else {
841
- var _tagInfo_commit, _runConfig_publish16;
842
- // Tag exists but version not on npm - likely failed previous publish
843
- logger.warn('');
844
- logger.warn('PUBLISH_SITUATION_ANALYSIS: Analyzing publish conflict situation | Tag: ' + targetTagName + ' | npm: ' + (npmVersion || 'not published'));
845
- logger.warn(`PUBLISH_ANALYSIS_TAG_EXISTS: Tag exists locally | Tag: ${targetTagName} | Commit: ${tagInfo === null || tagInfo === void 0 ? void 0 : (_tagInfo_commit = tagInfo.commit) === null || _tagInfo_commit === void 0 ? void 0 : _tagInfo_commit.substring(0, 8)}`);
846
- logger.warn(`PUBLISH_ANALYSIS_NPM_STATUS: npm registry status | Version: ${npmVersion || 'not published'} | Status: ${npmVersion ? 'published' : 'missing'}`);
847
- logger.warn(`PUBLISH_ANALYSIS_CONCLUSION: Previous publish likely failed after tag creation | Reason: Tag exists but not on npm | Resolution: Recovery needed`);
848
- logger.warn('');
849
- logger.warn('PUBLISH_RECOVERY_OPTIONS: Recovery options available:');
850
- logger.warn(' OPTION_1_FORCE: Force republish by deleting tag | Command: kodrdriv publish --force-republish');
851
- logger.warn(' OPTION_2_BUMP: Skip version and bump | Command: npm version patch && kodrdriv publish');
852
- logger.warn(' OPTION_3_MANUAL: Manually delete tag:');
853
- logger.warn(` Command: git tag -d ${targetTagName}`);
854
- logger.warn(` Command: git push origin :refs/tags/${targetTagName}`);
855
- logger.warn('');
856
- if ((_runConfig_publish16 = runConfig.publish) === null || _runConfig_publish16 === void 0 ? void 0 : _runConfig_publish16.forceRepublish) {
857
- logger.info('PUBLISH_FORCE_REPUBLISH: Force republish mode enabled | Action: Deleting existing tag | Tag: ' + targetTagName + ' | Purpose: Allow republish');
858
- if (!isDryRun) {
859
- const { runSecure } = await import('@eldrforge/git-tools');
860
- // Delete local tag
861
- try {
862
- await runSecure('git', [
863
- 'tag',
864
- '-d',
865
- targetTagName
866
- ]);
867
- logger.info(`TAG_DELETED_LOCAL: Deleted local tag | Tag: ${targetTagName} | Status: removed-local`);
868
- } catch (error) {
869
- logger.debug(`Could not delete local tag: ${error.message}`);
870
- }
871
- // Delete remote tag
872
- try {
873
- await runSecure('git', [
874
- 'push',
875
- 'origin',
876
- `:refs/tags/${targetTagName}`
877
- ]);
878
- logger.info(`TAG_DELETED_REMOTE: Deleted remote tag | Tag: ${targetTagName} | Remote: origin | Status: removed-remote`);
879
- } catch (error) {
880
- logger.debug(`Could not delete remote tag: ${error.message}`);
881
- }
882
- logger.info('PUBLISH_TAG_CLEANUP_COMPLETE: Tag deleted successfully | Status: ready-for-publish | Next: Continue with publish workflow');
883
- } else {
884
- logger.info('Would delete tags and continue with publish');
885
- }
886
- } else {
887
- throw new Error(`Tag ${targetTagName} already exists. Use --force-republish to override.`);
888
- }
889
- }
890
- }
891
- if ((_runConfig_publish13 = runConfig.publish) === null || _runConfig_publish13 === void 0 ? void 0 : _runConfig_publish13.interactive) {
892
- var _runConfig_publish17;
893
- newVersion = await confirmVersionInteractively(currentVersion, proposedVersion, (_runConfig_publish17 = runConfig.publish) === null || _runConfig_publish17 === void 0 ? void 0 : _runConfig_publish17.targetVersion);
894
- const confirmedTagName = `v${newVersion}`;
895
- const confirmedTagExists = await checkIfTagExists(confirmedTagName);
896
- if (confirmedTagExists) {
897
- var _runConfig_publish18;
898
- const { getNpmPublishedVersion } = await import('../util/general.js');
899
- const npmVersion = await getNpmPublishedVersion(packageJson.name);
900
- if (npmVersion === newVersion) {
901
- throw new Error(`Tag ${confirmedTagName} already exists and version is published on npm. Please choose a different version.`);
902
- } else if (!((_runConfig_publish18 = runConfig.publish) === null || _runConfig_publish18 === void 0 ? void 0 : _runConfig_publish18.forceRepublish)) {
903
- throw new Error(`Tag ${confirmedTagName} already exists. Use --force-republish to override.`);
904
- }
905
- // If forceRepublish is set, we'll continue (tag will be deleted later)
906
- }
907
- } else {
908
- newVersion = proposedVersion;
909
- }
910
- logger.info(`Bumping version from ${currentVersion} to ${newVersion}`);
911
- packageJson.version = newVersion;
912
- await storage.writeFile('package.json', JSON.stringify(packageJson, null, 2) + '\n', 'utf-8');
913
- logger.info(`Version updated in package.json: ${newVersion}`);
914
- }
915
- // STEP 5: Commit version bump as a separate commit
916
- logger.verbose('Staging version bump for commit');
917
- // Check if package-lock.json exists before trying to stage it
918
- const packageLockExistsVersionBump = await storage.exists('package-lock.json');
919
- const filesToStageVersionBump = packageLockExistsVersionBump ? 'package.json package-lock.json' : 'package.json';
920
- // Wrap git operations with lock
921
- await runGitWithLock(process.cwd(), async ()=>{
922
- await runWithDryRunSupport(`git add ${filesToStageVersionBump}`, isDryRun);
923
- }, 'stage version bump');
924
- if (isDryRun) {
925
- logger.verbose('Would create version bump commit');
926
- } else {
927
- if (await hasStagedChanges()) {
928
- logger.verbose('Creating version bump commit...');
929
- await runGitWithLock(process.cwd(), async ()=>{
930
- await execute$2(runConfig);
931
- }, 'commit version bump');
932
- } else {
933
- logger.verbose('No version changes to commit.');
934
- }
935
- }
936
- logger.info('Generating release notes...');
937
- // Use the existing currentBranch variable for tag detection
938
- logger.debug(`Current branch for release notes: ${currentBranch}`);
939
- // Create a modified config for release notes generation that includes the publish --from, --interactive, and --from-main options
940
- const releaseConfig = {
941
- ...runConfig
942
- };
943
- releaseConfig.release = {
944
- ...runConfig.release,
945
- currentBranch: currentBranch,
946
- ...((_runConfig_publish7 = runConfig.publish) === null || _runConfig_publish7 === void 0 ? void 0 : _runConfig_publish7.from) && {
947
- from: runConfig.publish.from
948
- },
949
- ...((_runConfig_publish8 = runConfig.publish) === null || _runConfig_publish8 === void 0 ? void 0 : _runConfig_publish8.interactive) && {
950
- interactive: runConfig.publish.interactive
951
- },
952
- ...((_runConfig_publish9 = runConfig.publish) === null || _runConfig_publish9 === void 0 ? void 0 : _runConfig_publish9.fromMain) && {
953
- fromMain: runConfig.publish.fromMain
954
- }
955
- };
956
- if ((_runConfig_publish10 = runConfig.publish) === null || _runConfig_publish10 === void 0 ? void 0 : _runConfig_publish10.from) {
957
- logger.verbose(`Using custom 'from' reference for release notes: ${runConfig.publish.from}`);
958
- }
959
- if ((_runConfig_publish11 = runConfig.publish) === null || _runConfig_publish11 === void 0 ? void 0 : _runConfig_publish11.interactive) {
960
- logger.verbose('Interactive mode enabled for release notes generation');
961
- }
962
- if ((_runConfig_publish12 = runConfig.publish) === null || _runConfig_publish12 === void 0 ? void 0 : _runConfig_publish12.fromMain) {
963
- logger.verbose('Forcing comparison against main branch for release notes');
964
- }
965
- // Log self-reflection settings for debugging
966
- if ((_releaseConfig_release = releaseConfig.release) === null || _releaseConfig_release === void 0 ? void 0 : _releaseConfig_release.selfReflection) {
967
- logger.verbose('Self-reflection enabled for release notes generation');
968
- }
969
- const releaseSummary = await execute$1(releaseConfig);
970
- if (isDryRun) {
971
- logger.info('Would write release notes to RELEASE_NOTES.md and RELEASE_TITLE.md in output directory');
972
- } else {
973
- const outputDirectory = runConfig.outputDirectory || DEFAULT_OUTPUT_DIRECTORY;
974
- await storage.ensureDirectory(outputDirectory);
975
- const releaseNotesPath = getOutputPath(outputDirectory, 'RELEASE_NOTES.md');
976
- const releaseTitlePath = getOutputPath(outputDirectory, 'RELEASE_TITLE.md');
977
- await storage.writeFile(releaseNotesPath, releaseSummary.body, 'utf-8');
978
- await storage.writeFile(releaseTitlePath, releaseSummary.title, 'utf-8');
979
- logger.info(`Release notes and title generated and saved to ${releaseNotesPath} and ${releaseTitlePath}.`);
980
- }
981
- logger.info('Pushing to origin...');
982
- // Get current branch name and push explicitly to avoid pushing to wrong remote/branch
983
- const branchName = await GitHub.getCurrentBranchName();
984
- // Wrap git push with lock
985
- await runGitWithLock(process.cwd(), async ()=>{
986
- await runWithDryRunSupport(`git push origin ${branchName}`, isDryRun);
987
- }, `push ${branchName}`);
988
- logger.info('Creating pull request...');
989
- if (isDryRun) {
990
- logger.info('Would get commit title and create PR with GitHub API');
991
- pr = {
992
- number: 123,
993
- html_url: 'https://github.com/mock/repo/pull/123',
994
- labels: []
995
- };
996
- } else {
997
- const { stdout: rawCommitTitle } = await run('git log -1 --pretty=%B');
998
- // Apply stop-context filtering to PR title and body
999
- const commitTitle = filterContent(rawCommitTitle, runConfig.stopContext).filtered;
1000
- const prBody = filterContent('Automated release PR.', runConfig.stopContext).filtered;
1001
- pr = await GitHub.createPullRequest(commitTitle, prBody, branchName, targetBranch);
1002
- if (!pr) {
1003
- throw new Error('Failed to create pull request.');
1004
- }
1005
- logger.info(`Pull request created: ${pr.html_url} (${branchName} → ${targetBranch})`);
1006
- }
1007
- }
1008
- logger.info(`Waiting for PR #${pr.number} checks to complete...`);
1009
- if (!isDryRun) {
1010
- // Check if we already know from prechecks that no workflows will trigger
1011
- let shouldSkipWait = false;
1012
- try {
1013
- // TODO: Re-enable when checkWorkflowConfiguration is exported from github-tools
1014
- // const workflowConfig = await GitHub.checkWorkflowConfiguration(targetBranch);
1015
- const workflowConfig = {
1016
- hasWorkflows: true,
1017
- hasPullRequestTriggers: true,
1018
- workflowCount: 0,
1019
- triggeredWorkflowNames: []
1020
- };
1021
- if (!workflowConfig.hasWorkflows || !workflowConfig.hasPullRequestTriggers) ;
1022
- } catch (error) {
1023
- // If we can't verify, proceed with waiting to be safe
1024
- logger.debug(`Could not verify workflow configuration for wait skip: ${error.message}`);
1025
- }
1026
- if (!shouldSkipWait) {
1027
- var _runConfig_publish19, _runConfig_publish20, _runConfig_publish21;
1028
- // Configure timeout and user confirmation behavior
1029
- const timeout = ((_runConfig_publish19 = runConfig.publish) === null || _runConfig_publish19 === void 0 ? void 0 : _runConfig_publish19.checksTimeout) || KODRDRIV_DEFAULTS.publish.checksTimeout;
1030
- const senditMode = ((_runConfig_publish20 = runConfig.publish) === null || _runConfig_publish20 === void 0 ? void 0 : _runConfig_publish20.sendit) || false;
1031
- // sendit flag overrides skipUserConfirmation - if sendit is true, skip confirmation
1032
- const skipUserConfirmation = senditMode || ((_runConfig_publish21 = runConfig.publish) === null || _runConfig_publish21 === void 0 ? void 0 : _runConfig_publish21.skipUserConfirmation) || false;
1033
- await GitHub.waitForPullRequestChecks(pr.number, {
1034
- timeout,
1035
- skipUserConfirmation
1036
- });
1037
- }
1038
- }
1039
- const mergeMethod = ((_runConfig_publish2 = runConfig.publish) === null || _runConfig_publish2 === void 0 ? void 0 : _runConfig_publish2.mergeMethod) || 'squash';
1040
- if (isDryRun) {
1041
- logger.info(`Would merge PR #${pr.number} using ${mergeMethod} method`);
1042
- } else {
1043
- try {
1044
- await GitHub.mergePullRequest(pr.number, mergeMethod, false); // Don't delete branch
1045
- } catch (error) {
1046
- // Check if this is a merge conflict error
1047
- if (error.message && (error.message.includes('not mergeable') || error.message.includes('Pull Request is not mergeable') || error.message.includes('merge conflict'))) {
1048
- logger.error(`PR_MERGE_CONFLICTS: Pull request has merge conflicts | PR Number: ${pr.number} | Status: conflicts | Resolution: Manual intervention required`);
1049
- logger.error('');
1050
- logger.error('PR_CONFLICT_RESOLUTION: Steps to resolve conflicts:');
1051
- logger.error(` Step 1: Visit pull request | URL: ${pr.html_url}`);
1052
- logger.error(' Step 2: Resolve merge conflicts | Method: GitHub UI or local');
1053
- logger.error(' Step 3: Re-run publish command | Command: kodrdriv publish');
1054
- logger.error('');
1055
- logger.error('PR_AUTO_CONTINUE: Command will auto-detect existing PR | Behavior: Continues from where it left off | No re-creation needed');
1056
- throw new Error(`Merge conflicts detected in PR #${pr.number}. Please resolve conflicts and re-run the command.`);
1057
- } else {
1058
- // Re-throw other merge errors
1059
- throw error;
1060
- }
1061
- }
1062
- }
1063
- // Switch to target branch and pull latest changes
1064
- logger.info(`Checking out target branch: ${targetBranch}...`);
1065
- // Check for uncommitted changes and stash them if necessary
1066
- let hasStashedChanges = false;
1067
- if (!isDryRun) {
1068
- const { stdout: statusOutput } = await runSecure('git', [
1069
- 'status',
1070
- '--porcelain'
1071
- ]);
1072
- if (statusOutput.trim()) {
1073
- logger.info('PUBLISH_STASH_SAVING: Stashing uncommitted changes before checkout | Command: git stash push | Purpose: Protect changes during branch switch');
1074
- await runSecure('git', [
1075
- 'stash',
1076
- 'push',
1077
- '-m',
1078
- 'kodrdriv: stash before checkout target branch'
1079
- ]);
1080
- hasStashedChanges = true;
1081
- logger.info('PUBLISH_STASH_SUCCESS: Successfully stashed uncommitted changes | Status: saved | Name: kodrdriv stash');
1082
- }
1083
- }
1084
- try {
1085
- // Wrap git checkout and pull with lock
1086
- await runGitWithLock(process.cwd(), async ()=>{
1087
- await runWithDryRunSupport(`git checkout ${targetBranch}`, isDryRun);
1088
- }, `checkout ${targetBranch}`);
1089
- // Sync target branch with remote to avoid conflicts during PR creation
1090
- if (!isDryRun) {
1091
- logger.info(`PUBLISH_TARGET_SYNCING: Syncing target branch with remote | Branch: ${targetBranch} | Remote: origin | Purpose: Avoid PR conflicts`);
1092
- try {
1093
- const remoteExists = await run(`git ls-remote --exit-code --heads origin ${targetBranch}`).then(()=>true).catch(()=>false);
1094
- if (remoteExists) {
1095
- await runGitWithLock(process.cwd(), async ()=>{
1096
- await run(`git fetch origin ${targetBranch}`);
1097
- await run(`git merge origin/${targetBranch} --no-edit`);
1098
- }, `sync ${targetBranch}`);
1099
- logger.info(`PUBLISH_TARGET_SYNCED: Successfully synced target with remote | Branch: ${targetBranch} | Remote: origin | Status: in-sync`);
1100
- } else {
1101
- logger.info(`PUBLISH_TARGET_NO_REMOTE: No remote target branch found | Branch: ${targetBranch} | Remote: origin | Action: Will be created on first push`);
1102
- }
1103
- } catch (syncError) {
1104
- if (syncError.message && syncError.message.includes('CONFLICT')) {
1105
- logger.error(`PUBLISH_SYNC_CONFLICTS: Merge conflicts during target sync | Branch: ${targetBranch} | Remote: origin | Status: conflicts-detected`);
1106
- logger.error(`PUBLISH_SYNC_RESOLUTION: Manual conflict resolution steps:`);
1107
- logger.error(` Step 1: Checkout target | Command: git checkout ${targetBranch}`);
1108
- logger.error(` Step 2: Pull and merge | Command: git pull origin ${targetBranch}`);
1109
- logger.error(` Step 3: Resolve conflicts in files`);
1110
- logger.error(` Step 4: Stage resolved files | Command: git add <resolved-files>`);
1111
- logger.error(` Step 5: Complete merge | Command: git commit`);
1112
- logger.error(` Step 6: Return to branch | Command: git checkout ${currentBranch}`);
1113
- logger.error(` Step 7: Resume publish | Command: kodrdriv publish`);
1114
- throw syncError;
1115
- } else {
1116
- logger.warn(`PUBLISH_SYNC_WARNING: Could not sync target with remote | Branch: ${targetBranch} | Remote: origin | Error: ${syncError.message}`);
1117
- // Continue with publish process, but log the warning
1118
- }
1119
- }
1120
- } else {
1121
- logger.info(`Would sync ${targetBranch} with remote to avoid PR conflicts`);
1122
- }
1123
- } catch (error) {
1124
- // Check if this is a merge conflict or sync issue
1125
- if (!isDryRun && (error.message.includes('conflict') || error.message.includes('CONFLICT') || error.message.includes('diverged') || error.message.includes('non-fast-forward'))) {
1126
- logger.error(`PUBLISH_TARGET_SYNC_FAILED: Failed to sync target branch with remote | Branch: ${targetBranch} | Remote: origin | Impact: Cannot proceed safely`);
1127
- logger.error('');
1128
- logger.error('PUBLISH_SYNC_RECOVERY_OPTIONS: Available recovery options:');
1129
- logger.error(` OPTION_1_AUTO: Attempt automatic resolution | Command: kodrdriv publish --sync-target`);
1130
- logger.error(` OPTION_2_MANUAL: Manually resolve conflicts:`);
1131
- logger.error(` Step 1: Checkout target | Command: git checkout ${targetBranch}`);
1132
- logger.error(` Step 2: Pull from remote | Command: git pull origin ${targetBranch}`);
1133
- logger.error(` Step 3: Resolve conflicts and commit`);
1134
- logger.error(` Step 4: Re-run publish | Command: kodrdriv publish`);
1135
- logger.error('');
1136
- logger.error('PUBLISH_STOPPED_SAFETY: Publish process stopped | Reason: Prevent data loss | Status: safe-to-recover');
1137
- throw new Error(`Target branch '${targetBranch}' sync failed. Use recovery options above to resolve.`);
1138
- } else {
1139
- // Re-throw other errors
1140
- throw error;
1141
- }
1142
- }
1143
- // Restore stashed changes if we stashed them
1144
- if (hasStashedChanges) {
1145
- logger.info('PUBLISH_STASH_RESTORING: Restoring previously stashed changes | Command: git stash pop | Purpose: Restore working directory state');
1146
- try {
1147
- await runSecure('git', [
1148
- 'stash',
1149
- 'pop'
1150
- ]);
1151
- logger.info('PUBLISH_STASH_RESTORED: Successfully restored stashed changes | Status: restored | Stash: removed');
1152
- } catch (stashError) {
1153
- logger.warn(`PUBLISH_STASH_RESTORE_FAILED: Could not restore stashed changes | Error: ${stashError.message} | Impact: Changes still in stash`);
1154
- logger.warn('PUBLISH_STASH_AVAILABLE: Changes available in git stash | Command: git stash list | Purpose: View and restore manually');
1155
- }
1156
- }
1157
- // Now create and push the tag on the target branch
1158
- logger.info('Creating release tag...');
1159
- let tagName;
1160
- if (isDryRun) {
1161
- logger.info('Would read package.json version and create git tag');
1162
- tagName = 'v1.0.0'; // Mock version for dry run
1163
- } else {
1164
- const packageJsonContents = await storage.readFile('package.json', 'utf-8');
1165
- const { version } = safeJsonParse(packageJsonContents, 'package.json');
1166
- tagName = `v${version}`;
1167
- // Check if tag already exists locally
1168
- try {
1169
- // Validate tag name to prevent injection
1170
- if (!validateGitRef(tagName)) {
1171
- throw new Error(`Invalid tag name: ${tagName}`);
1172
- }
1173
- const { stdout } = await runSecure('git', [
1174
- 'tag',
1175
- '-l',
1176
- tagName
1177
- ]);
1178
- if (stdout.trim() === tagName) {
1179
- logger.info(`Tag ${tagName} already exists locally, skipping tag creation`);
1180
- } else {
1181
- await runGitWithLock(process.cwd(), async ()=>{
1182
- await runSecure('git', [
1183
- 'tag',
1184
- tagName
1185
- ]);
1186
- }, `create tag ${tagName}`);
1187
- logger.info(`Created local tag: ${tagName}`);
1188
- }
1189
- } catch (error) {
1190
- // If git tag -l fails, create the tag anyway
1191
- await runGitWithLock(process.cwd(), async ()=>{
1192
- await runSecure('git', [
1193
- 'tag',
1194
- tagName
1195
- ]);
1196
- }, `create tag ${tagName}`);
1197
- logger.info(`Created local tag: ${tagName}`);
1198
- }
1199
- // Check if tag exists on remote before pushing
1200
- let tagWasPushed = false;
1201
- try {
1202
- const { stdout } = await runSecure('git', [
1203
- 'ls-remote',
1204
- 'origin',
1205
- `refs/tags/${tagName}`
1206
- ]);
1207
- if (stdout.trim()) {
1208
- logger.info(`Tag ${tagName} already exists on remote, skipping push`);
1209
- } else {
1210
- await runGitWithLock(process.cwd(), async ()=>{
1211
- await runSecure('git', [
1212
- 'push',
1213
- 'origin',
1214
- tagName
1215
- ]);
1216
- }, `push tag ${tagName}`);
1217
- logger.info(`Pushed tag to remote: ${tagName}`);
1218
- tagWasPushed = true;
1219
- }
1220
- } catch (error) {
1221
- // If ls-remote fails, try to push anyway (might be a new remote)
1222
- try {
1223
- await runSecure('git', [
1224
- 'push',
1225
- 'origin',
1226
- tagName
1227
- ]);
1228
- logger.info(`Pushed tag to remote: ${tagName}`);
1229
- tagWasPushed = true;
1230
- } catch (pushError) {
1231
- if (pushError.message && pushError.message.includes('already exists')) {
1232
- logger.info(`Tag ${tagName} already exists on remote, continuing...`);
1233
- } else {
1234
- throw pushError;
1235
- }
1236
- }
1237
- }
1238
- // If we just pushed a new tag, wait for GitHub to process it
1239
- if (tagWasPushed) {
1240
- logger.verbose('Waiting for GitHub to process the pushed tag...');
1241
- await new Promise((resolve)=>setTimeout(resolve, 5000)); // 5 second delay
1242
- }
1243
- }
1244
- logger.info('Creating GitHub release...');
1245
- if (isDryRun) {
1246
- var _runConfig_publish22;
1247
- logger.info('Would read package.json version and create GitHub release with retry logic');
1248
- const milestonesEnabled = !((_runConfig_publish22 = runConfig.publish) === null || _runConfig_publish22 === void 0 ? void 0 : _runConfig_publish22.noMilestones);
1249
- if (milestonesEnabled) {
1250
- logger.info('Would close milestone for released version');
1251
- } else {
1252
- logger.info('Would skip milestone closure (--no-milestones)');
1253
- }
1254
- } else {
1255
- const outputDirectory = runConfig.outputDirectory || DEFAULT_OUTPUT_DIRECTORY;
1256
- const releaseNotesPath = getOutputPath(outputDirectory, 'RELEASE_NOTES.md');
1257
- const releaseTitlePath = getOutputPath(outputDirectory, 'RELEASE_TITLE.md');
1258
- const releaseNotesContent = await storage.readFile(releaseNotesPath, 'utf-8');
1259
- const releaseTitle = await storage.readFile(releaseTitlePath, 'utf-8');
1260
- // Create release with retry logic to handle GitHub tag processing delays
1261
- let retries = 3;
1262
- while(retries > 0){
1263
- try {
1264
- var _runConfig_publish23;
1265
- await GitHub.createRelease(tagName, releaseTitle, releaseNotesContent);
1266
- logger.info(`GitHub release created successfully for tag: ${tagName}`);
1267
- // Close milestone for this version if enabled
1268
- const milestonesEnabled = !((_runConfig_publish23 = runConfig.publish) === null || _runConfig_publish23 === void 0 ? void 0 : _runConfig_publish23.noMilestones);
1269
- if (milestonesEnabled) {
1270
- logger.info('PUBLISH_MILESTONE_CLOSING: Closing milestone for released version | Action: Close GitHub milestone | Purpose: Mark release complete');
1271
- const version = tagName.replace(/^v/, ''); // Remove 'v' prefix if present
1272
- await GitHub.closeMilestoneForVersion(version);
1273
- } else {
1274
- logger.debug('Milestone integration disabled via --no-milestones');
1275
- }
1276
- break; // Success - exit retry loop
1277
- } catch (error) {
1278
- // Check if this is a tag-not-found error that we can retry
1279
- const isTagNotFoundError = error.message && (error.message.includes('not found') || error.message.includes('does not exist') || error.message.includes('Reference does not exist'));
1280
- if (isTagNotFoundError && retries > 1) {
1281
- logger.verbose(`Tag ${tagName} not yet available on GitHub, retrying in 3 seconds... (${retries - 1} retries left)`);
1282
- await new Promise((resolve)=>setTimeout(resolve, 3000));
1283
- retries--;
1284
- } else if (isTagNotFoundError) {
1285
- // Tag not found error and we're out of retries
1286
- 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.`);
1287
- } else {
1288
- // Not a tag-not-found error - re-throw the original error
1289
- throw error;
1290
- }
1291
- }
1292
- }
1293
- }
1294
- // Wait for release workflows to complete (if enabled)
1295
- const waitForWorkflows = ((_runConfig_publish3 = runConfig.publish) === null || _runConfig_publish3 === void 0 ? void 0 : _runConfig_publish3.waitForReleaseWorkflows) !== false; // default to true
1296
- if (waitForWorkflows) {
1297
- logger.info('Waiting for release workflows...');
1298
- if (isDryRun) {
1299
- logger.info('Would monitor GitHub Actions workflows triggered by release');
1300
- } else {
1301
- var _runConfig_publish24, _runConfig_publish25, _runConfig_publish26, _runConfig_publish27;
1302
- const workflowTimeout = ((_runConfig_publish24 = runConfig.publish) === null || _runConfig_publish24 === void 0 ? void 0 : _runConfig_publish24.releaseWorkflowsTimeout) || KODRDRIV_DEFAULTS.publish.releaseWorkflowsTimeout;
1303
- const senditMode = ((_runConfig_publish25 = runConfig.publish) === null || _runConfig_publish25 === void 0 ? void 0 : _runConfig_publish25.sendit) || false;
1304
- const skipUserConfirmation = senditMode || ((_runConfig_publish26 = runConfig.publish) === null || _runConfig_publish26 === void 0 ? void 0 : _runConfig_publish26.skipUserConfirmation) || false;
1305
- // Get workflow names - either from config or auto-detect
1306
- let workflowNames = (_runConfig_publish27 = runConfig.publish) === null || _runConfig_publish27 === void 0 ? void 0 : _runConfig_publish27.releaseWorkflowNames;
1307
- if (!workflowNames || workflowNames.length === 0) {
1308
- logger.info('No specific workflow names configured, auto-detecting workflows triggered by release events...');
1309
- try {
1310
- workflowNames = await GitHub.getWorkflowsTriggeredByRelease();
1311
- if (workflowNames.length === 0) {
1312
- logger.info('No workflows found that are triggered by release events.');
1313
- } else {
1314
- logger.info(`Auto-detected release workflows: ${workflowNames.join(', ')}`);
1315
- }
1316
- } catch (error) {
1317
- logger.warn(`Failed to auto-detect release workflows: ${error.message}`);
1318
- workflowNames = undefined; // Fall back to monitoring all workflows
1319
- }
1320
- }
1321
- await GitHub.waitForReleaseWorkflows(tagName, {
1322
- timeout: workflowTimeout,
1323
- workflowNames,
1324
- skipUserConfirmation
1325
- });
1326
- }
1327
- } else {
1328
- logger.verbose('Skipping waiting for release workflows (disabled in config).');
1329
- }
1330
- // Switch back to source branch and sync with target
1331
- logger.info('');
1332
- logger.info(`PUBLISH_POST_SYNC: Syncing source branch with target after publish | Purpose: Keep branches synchronized | Strategy: Reset and force push`);
1333
- await runWithDryRunSupport(`git checkout ${currentBranch}`, isDryRun);
1334
- if (!isDryRun) {
1335
- // Sync target into source
1336
- // Note: With squash merging, fast-forward will fail because commit histories diverge
1337
- if (mergeMethod === 'squash') {
1338
- // For squash merges, reset to target branch to avoid conflicts
1339
- // The squash merge created a single commit on target that represents all source commits
1340
- logger.info(`Resetting ${currentBranch} to ${targetBranch} (squash merge)...`);
1341
- await run(`git reset --hard ${targetBranch}`);
1342
- logger.info(`PUBLISH_BRANCH_RESET: Reset source branch to target | Source: ${currentBranch} | Target: ${targetBranch} | Status: synchronized`);
1343
- // After squash merge and reset, we need to force push
1344
- // This is safe because we just merged to main and are syncing working branch
1345
- logger.info(`PUBLISH_FORCE_PUSHING: Force pushing synchronized branch | Branch: ${currentBranch} | Remote: origin | Purpose: Complete post-publish sync`);
1346
- try {
1347
- // Verify that remote working branch is ancestor of main (safety check)
1348
- try {
1349
- await run(`git fetch origin ${currentBranch}`);
1350
- await run(`git merge-base --is-ancestor origin/${currentBranch} ${targetBranch}`);
1351
- logger.verbose(`✓ Safety check passed: origin/${currentBranch} is ancestor of ${targetBranch}`);
1352
- } catch {
1353
- // Remote branch might not exist yet, or already in sync - both OK
1354
- logger.verbose(`Remote ${currentBranch} does not exist or is already synced`);
1355
- }
1356
- // Use --force-with-lease for safer force push
1357
- await run(`git push --force-with-lease origin ${currentBranch}`);
1358
- logger.info(`PUBLISH_FORCE_PUSH_SUCCESS: Successfully force pushed to remote | Branch: ${currentBranch} | Remote: origin | Status: synchronized`);
1359
- } catch (pushError) {
1360
- // If force push fails, provide helpful message
1361
- logger.warn(`PUBLISH_FORCE_PUSH_FAILED: Could not force push branch | Branch: ${currentBranch} | Remote: origin | Error: ${pushError.message}`);
1362
- logger.warn(`PUBLISH_MANUAL_PUSH_NEEDED: Manual force push required | Action: Push manually`);
1363
- logger.warn(`PUBLISH_MANUAL_PUSH_COMMAND: Force push command | Command: git push --force-with-lease origin ${currentBranch}`);
1364
- }
1365
- } else {
1366
- // For merge/rebase methods, try to merge target back into source
1367
- logger.info(`PUBLISH_MERGE_TARGET_BACK: Merging target back into source | Target: ${targetBranch} | Source: ${currentBranch} | Purpose: Sync branches after publish`);
1368
- // Try fast-forward first (works with merge/rebase methods)
1369
- // Use runSecure to avoid error output for expected failure
1370
- let fastForwardSucceeded = false;
1371
- try {
1372
- await runSecure('git', [
1373
- 'merge',
1374
- targetBranch,
1375
- '--ff-only'
1376
- ]);
1377
- fastForwardSucceeded = true;
1378
- logger.info(`PUBLISH_MERGE_FF_SUCCESS: Fast-forward merged target into source | Target: ${targetBranch} | Source: ${currentBranch} | Status: merged`);
1379
- } catch {
1380
- logger.verbose(`Fast-forward merge not possible, performing regular merge...`);
1381
- }
1382
- if (!fastForwardSucceeded) {
1383
- await run(`git merge ${targetBranch} --no-edit`);
1384
- logger.info(`PUBLISH_MERGE_SUCCESS: Merged target into source | Target: ${targetBranch} | Source: ${currentBranch} | Status: merged`);
1385
- }
1386
- }
1387
- // Determine version bump based on branch configuration
1388
- let versionCommand = 'prepatch'; // Default
1389
- let versionTag = 'dev'; // Default
1390
- if (branchDependentVersioning && runConfig.branches) {
1391
- const sourceBranchConfig = runConfig.branches[currentBranch];
1392
- if (sourceBranchConfig === null || sourceBranchConfig === void 0 ? void 0 : sourceBranchConfig.version) {
1393
- // Use configured version strategy for source branch
1394
- if (sourceBranchConfig.version.incrementLevel) {
1395
- versionCommand = `pre${sourceBranchConfig.version.incrementLevel}`;
1396
- }
1397
- if (sourceBranchConfig.version.tag) {
1398
- versionTag = sourceBranchConfig.version.tag;
1399
- }
1400
- }
1401
- }
1402
- // Bump to next development version
1403
- logger.info(`PUBLISH_DEV_VERSION_BUMPING: Bumping to next development version | Command: ${versionCommand} | Tag: ${versionTag} | Purpose: Prepare for next cycle`);
1404
- try {
1405
- const { stdout: newVersion } = await run(`npm version ${versionCommand} --preid=${versionTag}`);
1406
- logger.info(`PUBLISH_DEV_VERSION_BUMPED: Version bumped successfully | New Version: ${newVersion.trim()} | Type: development | Status: completed`);
1407
- } catch (versionError) {
1408
- logger.warn(`PUBLISH_DEV_VERSION_BUMP_FAILED: Failed to bump version | Error: ${versionError.message} | Impact: Version not updated`);
1409
- logger.warn('PUBLISH_MANUAL_VERSION_BUMP: Manual version bump may be needed | Action: Bump manually for next cycle | Command: npm version');
1410
- }
1411
- // Push updated source branch
1412
- logger.info(`PUBLISH_PUSH_SOURCE: Pushing updated source branch | Branch: ${currentBranch} | Remote: origin | Purpose: Push development version`);
1413
- try {
1414
- await runGitWithLock(process.cwd(), async ()=>{
1415
- await run(`git push origin ${currentBranch}`);
1416
- }, `push ${currentBranch}`);
1417
- logger.info(`PUBLISH_PUSH_SOURCE_SUCCESS: Pushed source branch successfully | Branch: ${currentBranch} | Remote: origin | Status: pushed`);
1418
- } catch (pushError) {
1419
- logger.warn(`PUBLISH_PUSH_SOURCE_FAILED: Failed to push source branch | Branch: ${currentBranch} | Error: ${pushError.message} | Impact: Need manual push`);
1420
- logger.warn(`PUBLISH_MANUAL_PUSH_COMMAND: Manual push command | Command: git push origin ${currentBranch}`);
1421
- }
1422
- } else {
1423
- logger.info(`PUBLISH_MERGE_DRY_RUN: Would merge target into source | Mode: dry-run | Target: ${targetBranch} | Source: ${currentBranch} | Strategy: ff-only`);
1424
- logger.info(`PUBLISH_VERSION_DRY_RUN: Would bump version to next development | Mode: dry-run | Action: Version bump`);
1425
- logger.info(`PUBLISH_PUSH_DRY_RUN: Would push source to remote | Mode: dry-run | Branch: ${currentBranch} | Remote: origin`);
1426
- }
1427
- logger.info('');
1428
- logger.info(`PUBLISH_COMPLETE: Publish workflow completed successfully | Branch: ${currentBranch} | Status: completed | Version: next-development`);
1429
- };
1430
-
1431
- export { execute };
1432
- //# sourceMappingURL=publish.js.map