@grunnverk/commands-publish 1.5.7-dev.20260131223731.750cd33 → 1.5.7-dev.20260131231507.728276

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.
package/dist/index.js CHANGED
@@ -4,13 +4,13 @@ import { createStorage, calculateTargetVersion, incrementPatchVersion as increme
4
4
  import { scanForPackageJsonFiles } from '@grunnverk/tree-core';
5
5
  import * as GitHub from '@grunnverk/github-tools';
6
6
  import { getOctokit } from '@grunnverk/github-tools';
7
- import fs, { readFile } from 'fs/promises';
7
+ import fs, { readFile, access } from 'fs/promises';
8
8
  import * as path from 'path';
9
9
  import path__default from 'path';
10
10
  import * as Commit from '@grunnverk/commands-git';
11
11
  import { Formatter } from '@kjerneverk/riotprompt';
12
12
  import 'dotenv/config';
13
- import { runAgenticRelease, requireTTY, generateReflectionReport, getUserChoice, STANDARD_CHOICES, getLLMFeedbackInEditor, editContentInEditor, createCompletionWithRetry, createReleasePrompt, runAgenticPublish, formatAgenticPublishResult } from '@grunnverk/ai-service';
13
+ import { runAgenticRelease, requireTTY, generateReflectionReport, getUserChoice, STANDARD_CHOICES, getLLMFeedbackInEditor, editContentInEditor, createCompletionWithRetry, createReleasePrompt } from '@grunnverk/ai-service';
14
14
 
15
15
  /**
16
16
  * Create retroactive working branch tags for past releases
@@ -537,6 +537,80 @@ import { runAgenticRelease, requireTTY, generateReflectionReport, getUserChoice,
537
537
  'examples/',
538
538
  'test-*/'
539
539
  ];
540
+ /**
541
+ * Checks if .gitignore contains required patterns to prevent publishing
542
+ * development artifacts and sensitive files.
543
+ */ async function checkGitignorePatterns$1(directory, checks) {
544
+ const logger = getLogger();
545
+ const gitignorePath = path.join(directory, '.gitignore');
546
+ // Required patterns that must be present in .gitignore
547
+ const requiredPatterns = [
548
+ 'node_modules',
549
+ 'dist',
550
+ 'package-lock.json',
551
+ '.env',
552
+ 'output/',
553
+ 'coverage',
554
+ '.kodrdriv*'
555
+ ];
556
+ // Check if .gitignore exists
557
+ try {
558
+ await access(gitignorePath);
559
+ } catch {
560
+ checks.gitignore.passed = false;
561
+ checks.gitignore.issues.push('.gitignore file not found');
562
+ checks.gitignore.issues.push(`Create .gitignore with patterns: ${requiredPatterns.join(', ')}`);
563
+ return;
564
+ }
565
+ // Read .gitignore content
566
+ let gitignoreContent;
567
+ try {
568
+ gitignoreContent = await readFile(gitignorePath, 'utf-8');
569
+ } catch (error) {
570
+ checks.gitignore.passed = false;
571
+ checks.gitignore.issues.push(`Failed to read .gitignore: ${error.message}`);
572
+ return;
573
+ }
574
+ // Parse .gitignore into lines, ignoring comments and empty lines
575
+ const gitignoreLines = gitignoreContent.split('\n').map((line)=>line.trim()).filter((line)=>line && !line.startsWith('#'));
576
+ // Check for missing patterns
577
+ const missingPatterns = [];
578
+ for (const pattern of requiredPatterns){
579
+ // Check if the pattern exists in any of the gitignore lines
580
+ const found = gitignoreLines.some((line)=>{
581
+ // Exact match
582
+ if (line === pattern) return true;
583
+ // Pattern with wildcard - check if the pattern or any matching line exists
584
+ if (pattern.includes('*')) {
585
+ const basePattern = pattern.replace('*', '');
586
+ // Accept exact wildcard pattern, base pattern, or any line starting with base
587
+ return line === pattern || line === basePattern || line.startsWith(basePattern);
588
+ }
589
+ // Pattern with trailing slash - check both with and without slash
590
+ if (pattern.endsWith('/')) {
591
+ const basePattern = pattern.slice(0, -1);
592
+ return line === basePattern || line === pattern || line.startsWith(pattern);
593
+ }
594
+ // Line with trailing slash - check if it matches the pattern (e.g., "node_modules/" matches "node_modules")
595
+ if (line.endsWith('/')) {
596
+ const lineBase = line.slice(0, -1);
597
+ return lineBase === pattern || line.startsWith(pattern + '/');
598
+ }
599
+ return false;
600
+ });
601
+ if (!found) {
602
+ missingPatterns.push(pattern);
603
+ }
604
+ }
605
+ // Report missing patterns (relaxed check - allow variations)
606
+ const criticalMissing = missingPatterns.filter((p)=>!p.includes('coverage') && p !== 'package-lock.json');
607
+ if (criticalMissing.length > 0) {
608
+ checks.gitignore.passed = false;
609
+ checks.gitignore.issues.push(`Missing required patterns: ${criticalMissing.join(', ')}`);
610
+ checks.gitignore.issues.push('These patterns prevent committing build artifacts and sensitive files');
611
+ }
612
+ logger.debug(`Gitignore check: ${checks.gitignore.passed ? 'passed' : 'failed'}`);
613
+ }
540
614
  /**
541
615
  * Execute check-development command
542
616
  */ async function execute$2(config) {
@@ -564,6 +638,10 @@ import { runAgenticRelease, requireTTY, generateReflectionReport, getUserChoice,
564
638
  const isTree = packageJsonFiles.length > 1;
565
639
  logger.info(`Detected ${isTree ? 'tree' : 'single package'} with ${packageJsonFiles.length} package(s)`);
566
640
  const checks = {
641
+ gitignore: {
642
+ passed: true,
643
+ issues: []
644
+ },
567
645
  branch: {
568
646
  passed: true,
569
647
  issues: []
@@ -600,6 +678,8 @@ import { runAgenticRelease, requireTTY, generateReflectionReport, getUserChoice,
600
678
  const packagesToCheck = isTree ? packageJsonFiles : [
601
679
  path.join(directory, 'package.json')
602
680
  ];
681
+ // Check .gitignore patterns (required for publish to succeed)
682
+ await checkGitignorePatterns$1(directory, checks);
603
683
  // Build a set of all local package names for link status checking
604
684
  const localPackageNames = new Set();
605
685
  for (const pkgJsonPath of packagesToCheck){
@@ -683,7 +763,7 @@ import { runAgenticRelease, requireTTY, generateReflectionReport, getUserChoice,
683
763
  // Ignore - there might not be a merge to abort if it was a fast-forward
684
764
  }
685
765
  } catch (mergeError) {
686
- var _mergeError_message, _mergeError_stderr;
766
+ var _mergeError_message, _mergeError_stderr, _mergeError_stdout;
687
767
  // Abort any partial merge
688
768
  try {
689
769
  await run('git merge --abort', {
@@ -694,7 +774,7 @@ import { runAgenticRelease, requireTTY, generateReflectionReport, getUserChoice,
694
774
  // Ignore abort errors
695
775
  }
696
776
  // If merge failed, there are likely conflicts
697
- if (((_mergeError_message = mergeError.message) === null || _mergeError_message === void 0 ? void 0 : _mergeError_message.includes('CONFLICT')) || ((_mergeError_stderr = mergeError.stderr) === null || _mergeError_stderr === void 0 ? void 0 : _mergeError_stderr.includes('CONFLICT'))) {
777
+ if (((_mergeError_message = mergeError.message) === null || _mergeError_message === void 0 ? void 0 : _mergeError_message.includes('CONFLICT')) || ((_mergeError_stderr = mergeError.stderr) === null || _mergeError_stderr === void 0 ? void 0 : _mergeError_stderr.includes('CONFLICT')) || ((_mergeError_stdout = mergeError.stdout) === null || _mergeError_stdout === void 0 ? void 0 : _mergeError_stdout.includes('CONFLICT'))) {
698
778
  checks.mergeConflicts.passed = false;
699
779
  checks.mergeConflicts.issues.push(`${pkgName}: Merge conflicts detected with ${targetBranch} branch`);
700
780
  } else {
@@ -841,7 +921,7 @@ import { runAgenticRelease, requireTTY, generateReflectionReport, getUserChoice,
841
921
  // Build summary - linkStatus and releaseWorkflow are not included in allPassed (recommendations)
842
922
  // mergeConflicts is ALWAYS checked (critical for preventing post-merge failures)
843
923
  // openPRs is only checked when validateRelease is true
844
- const allPassed = checks.branch.passed && checks.remoteSync.passed && checks.mergeConflicts.passed && checks.devVersion.passed && (validateRelease ? checks.openPRs.passed : true);
924
+ const allPassed = checks.gitignore.passed && checks.branch.passed && checks.remoteSync.passed && checks.mergeConflicts.passed && checks.devVersion.passed && (validateRelease ? checks.openPRs.passed : true);
845
925
  const hasWarnings = checks.linkStatus.warnings.length > 0 || checks.mergeConflicts.warnings.length > 0 || checks.openPRs.warnings.length > 0 || checks.releaseWorkflow.warnings.length > 0;
846
926
  // Log results
847
927
  let summary = `\n${'='.repeat(60)}\n`;
@@ -852,6 +932,7 @@ import { runAgenticRelease, requireTTY, generateReflectionReport, getUserChoice,
852
932
  if (allPassed) {
853
933
  summary += `✅ Status: READY FOR DEVELOPMENT\n\n`;
854
934
  summary += `All required checks passed:\n`;
935
+ summary += ` ✓ Gitignore patterns\n`;
855
936
  summary += ` ✓ Branch status\n`;
856
937
  summary += ` ✓ Remote sync\n`;
857
938
  summary += ` ✓ No merge conflicts with main\n`;
@@ -864,6 +945,11 @@ import { runAgenticRelease, requireTTY, generateReflectionReport, getUserChoice,
864
945
  }
865
946
  } else {
866
947
  summary += `⚠️ Status: NOT READY\n\n`;
948
+ if (!checks.gitignore.passed) {
949
+ summary += `❌ Gitignore Issues:\n`;
950
+ checks.gitignore.issues.forEach((issue)=>summary += ` - ${issue}\n`);
951
+ summary += `\n`;
952
+ }
867
953
  if (!checks.branch.passed) {
868
954
  summary += `❌ Branch Issues:\n`;
869
955
  checks.branch.issues.forEach((issue)=>summary += ` - ${issue}\n`);
@@ -878,6 +964,13 @@ import { runAgenticRelease, requireTTY, generateReflectionReport, getUserChoice,
878
964
  summary += `❌ Merge Conflict Issues:\n`;
879
965
  checks.mergeConflicts.issues.forEach((issue)=>summary += ` - ${issue}\n`);
880
966
  summary += `\n`;
967
+ summary += ` To resolve merge conflicts:\n`;
968
+ summary += ` 1. Fetch latest: git fetch origin main\n`;
969
+ summary += ` 2. Merge main into your branch: git merge origin/main\n`;
970
+ summary += ` 3. Resolve conflicts in your editor\n`;
971
+ summary += ` 4. Commit the merge: git add . && git commit\n`;
972
+ summary += ` 5. Run check-development again to verify\n`;
973
+ summary += `\n`;
881
974
  }
882
975
  if (!checks.devVersion.passed) {
883
976
  summary += `❌ Dev Version Issues:\n`;
@@ -1536,7 +1629,6 @@ const runPrechecks = async (runConfig, targetBranch)=>{
1536
1629
  if (targetBranchExists) {
1537
1630
  const syncStatus = await isBranchInSyncWithRemote(effectiveTargetBranch);
1538
1631
  if (!syncStatus.inSync) {
1539
- var _runConfig_publish2;
1540
1632
  logger.error(`BRANCH_SYNC_FAILED: Target branch not synchronized with remote | Branch: ${effectiveTargetBranch} | Status: out-of-sync | Impact: Cannot proceed with publish`);
1541
1633
  logger.error('');
1542
1634
  if (syncStatus.error) {
@@ -1544,48 +1636,6 @@ const runPrechecks = async (runConfig, targetBranch)=>{
1544
1636
  } else if (syncStatus.localSha && syncStatus.remoteSha) {
1545
1637
  logger.error(`BRANCH_SYNC_DIVERGENCE: Local and remote commits differ | Local SHA: ${syncStatus.localSha.substring(0, 8)} | Remote SHA: ${syncStatus.remoteSha.substring(0, 8)}`);
1546
1638
  }
1547
- // Check if agentic publish is enabled
1548
- if ((_runConfig_publish2 = runConfig.publish) === null || _runConfig_publish2 === void 0 ? void 0 : _runConfig_publish2.agenticPublish) {
1549
- logger.info('');
1550
- logger.info('AGENTIC_PUBLISH_STARTING: Attempting automatic diagnosis and fix | Mode: agentic | Feature: AI-powered recovery');
1551
- try {
1552
- var _syncStatus_localSha, _syncStatus_remoteSha, _runConfig_publish3;
1553
- const currentBranch = await GitHub.getCurrentBranchName();
1554
- const agenticResult = await runAgenticPublish({
1555
- targetBranch: effectiveTargetBranch,
1556
- sourceBranch: currentBranch,
1557
- issue: 'branch_sync',
1558
- 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)}`,
1559
- workingDirectory: process.cwd(),
1560
- maxIterations: ((_runConfig_publish3 = runConfig.publish) === null || _runConfig_publish3 === void 0 ? void 0 : _runConfig_publish3.agenticPublishMaxIterations) || 10,
1561
- storage,
1562
- logger,
1563
- dryRun: runConfig.dryRun
1564
- });
1565
- // Display the formatted result
1566
- const formattedResult = formatAgenticPublishResult(agenticResult);
1567
- logger.info(formattedResult);
1568
- if (agenticResult.success) {
1569
- logger.info('AGENTIC_PUBLISH_SUCCESS: Issue resolved automatically | Status: ready-to-retry | Action: Re-running prechecks');
1570
- // Re-run the sync check to verify it was fixed
1571
- const reSyncStatus = await isBranchInSyncWithRemote(effectiveTargetBranch);
1572
- if (reSyncStatus.inSync) {
1573
- logger.info(`BRANCH_SYNC_VERIFIED: Target branch is now synchronized with remote | Branch: ${effectiveTargetBranch} | Status: in-sync`);
1574
- return; // Continue with publish
1575
- } else {
1576
- logger.warn('AGENTIC_PUBLISH_VERIFICATION_FAILED: Branch still not in sync after agentic fix | Status: needs-attention');
1577
- }
1578
- }
1579
- if (agenticResult.requiresManualIntervention) {
1580
- throw new Error(`Target branch '${effectiveTargetBranch}' requires manual intervention. Please see the steps above.`);
1581
- } else {
1582
- throw new Error(`Agentic publish could not resolve the issue automatically. Please see the analysis above.`);
1583
- }
1584
- } catch (agenticError) {
1585
- logger.warn(`AGENTIC_PUBLISH_FAILED: Agentic recovery failed | Error: ${agenticError.message} | Fallback: Manual steps`);
1586
- // Fall through to manual steps
1587
- }
1588
- }
1589
1639
  logger.error('');
1590
1640
  logger.error('RESOLUTION_STEPS: Manual intervention required to sync branches:');
1591
1641
  logger.error(` Step 1: Switch to target branch | Command: git checkout ${effectiveTargetBranch}`);
@@ -1594,7 +1644,6 @@ const runPrechecks = async (runConfig, targetBranch)=>{
1594
1644
  logger.error(' Step 4: Return to feature branch and retry publish');
1595
1645
  logger.error('');
1596
1646
  logger.error(`ALTERNATIVE_OPTION: Automatic sync available | Command: kodrdriv publish --sync-target | Branch: ${effectiveTargetBranch}`);
1597
- logger.error(`ALTERNATIVE_OPTION_AI: AI-powered recovery available | Command: kodrdriv publish --agentic-publish | Branch: ${effectiveTargetBranch}`);
1598
1647
  throw new Error(`Target branch '${effectiveTargetBranch}' is not in sync with remote. Please sync the branch before running publish.`);
1599
1648
  } else {
1600
1649
  logger.info(`BRANCH_SYNC_VERIFIED: Target branch is synchronized with remote | Branch: ${effectiveTargetBranch} | Status: in-sync`);
@@ -1972,8 +2021,19 @@ const execute = async (runConfig)=>{
1972
2021
  }
1973
2022
  if (pr) {
1974
2023
  logger.info(`PR_FOUND: Existing pull request detected for current branch | URL: ${pr.html_url} | Status: open`);
2024
+ // Even when PR exists, we need to determine the version for tagging later
2025
+ logger.info('VERSION_DETERMINATION: Determining version from current package.json | Purpose: Tag creation after merge');
2026
+ if (!isDryRun) {
2027
+ const packageJsonContents = await storage.readFile('package.json', 'utf-8');
2028
+ const parsed = safeJsonParse(packageJsonContents, 'package.json');
2029
+ const packageJson = validatePackageJson(parsed, 'package.json');
2030
+ newVersion = packageJson.version;
2031
+ logger.info(`VERSION_DETECTED: Version determined from package.json | Version: ${newVersion} | Source: current working branch`);
2032
+ } else {
2033
+ newVersion = '1.0.0'; // Mock version for dry run
2034
+ }
1975
2035
  } else {
1976
- var _runConfig_publish4, _runConfig_publish5, _runConfig_publish6, _runConfig_publish7, _runConfig_publish8, _runConfig_publish9, _runConfig_publish10, _runConfig_publish11, _runConfig_publish12, _runConfig_publish13, _releaseConfig_release;
2036
+ var _runConfig_publish4, _runConfig_publish5, _runConfig_publish6, _runConfig_publish7, _runConfig_publish8, _runConfig_publish9, _runConfig_publish10, _runConfig_publish11, _runConfig_publish12, _releaseConfig_release;
1977
2037
  logger.info('PR_NOT_FOUND: No open pull request exists for current branch | Action: Starting new release publishing process | Next: Prepare dependencies and version');
1978
2038
  // STEP 1: Prepare for release (update dependencies and run prepublish checks) with NO version bump yet
1979
2039
  logger.verbose('RELEASE_PREP_STARTING: Preparing for release | Phase: dependency management | Action: Switch from workspace to remote dependencies | Version Bump: Not yet applied');
@@ -2033,121 +2093,20 @@ const execute = async (runConfig)=>{
2033
2093
  logger.verbose('DEPS_COMMIT_SKIPPED: No dependency changes to commit | Files: ' + filesToStage + ' | Action: Skipping commit step');
2034
2094
  }
2035
2095
  }
2036
- // STEP 3: Merge target branch into working branch (optional - now skipped by default since post-publish sync keeps branches in sync)
2037
- const skipPreMerge = ((_runConfig_publish7 = runConfig.publish) === null || _runConfig_publish7 === void 0 ? void 0 : _runConfig_publish7.skipPrePublishMerge) !== false; // Default to true (skip)
2038
- if (skipPreMerge) {
2039
- logger.verbose(`PRE_MERGE_SKIPPED: Skipping pre-publish merge of target branch | Reason: Post-publish sync handles branch synchronization | Target: ${targetBranch} | Config: skipPrePublishMerge=true`);
2040
- } else {
2041
- logger.info(`PRE_MERGE_STARTING: Merging target branch into current branch | Target: ${targetBranch} | Purpose: Avoid version conflicts | Phase: pre-publish`);
2042
- if (isDryRun) {
2043
- logger.info(`Would merge ${targetBranch} into current branch`);
2044
- } else {
2045
- // Wrap entire merge process with git lock (involves fetch, merge, checkout, add, commit)
2046
- await runGitWithLock(process.cwd(), async ()=>{
2047
- // Fetch the latest target branch
2048
- try {
2049
- await run(`git fetch origin ${targetBranch}:${targetBranch}`);
2050
- logger.info(`TARGET_BRANCH_FETCHED: Successfully fetched latest target branch | Branch: ${targetBranch} | Remote: origin/${targetBranch} | Purpose: Pre-merge sync`);
2051
- } catch (fetchError) {
2052
- logger.warn(`TARGET_BRANCH_FETCH_FAILED: Unable to fetch target branch | Branch: ${targetBranch} | Error: ${fetchError.message} | Impact: Proceeding without merge, PR may have conflicts`);
2053
- logger.warn('MERGE_SKIPPED_NO_FETCH: Continuing without pre-merge | Reason: Target branch fetch failed | Impact: PR may require manual conflict resolution');
2054
- }
2055
- // Check if merge is needed (avoid unnecessary merge commits)
2056
- try {
2057
- const { stdout: mergeBase } = await run(`git merge-base HEAD ${targetBranch}`);
2058
- const { stdout: targetCommit } = await run(`git rev-parse ${targetBranch}`);
2059
- if (mergeBase.trim() === targetCommit.trim()) {
2060
- logger.info(`MERGE_NOT_NEEDED: Current branch already up-to-date with target | Branch: ${targetBranch} | Status: in-sync | Action: Skipping merge`);
2061
- } else {
2062
- // Try to merge target branch into current branch
2063
- let mergeSucceeded = false;
2064
- try {
2065
- await run(`git merge ${targetBranch} --no-edit -m "Merge ${targetBranch} to sync before version bump"`);
2066
- logger.info(`MERGE_SUCCESS: Successfully merged target branch into current branch | Target: ${targetBranch} | Purpose: Sync before version bump`);
2067
- mergeSucceeded = true;
2068
- } catch (mergeError) {
2069
- // If merge conflicts occur, check if they're only in version-related files
2070
- const errorText = [
2071
- mergeError.message || '',
2072
- mergeError.stdout || '',
2073
- mergeError.stderr || ''
2074
- ].join(' ');
2075
- if (errorText.includes('CONFLICT')) {
2076
- logger.warn(`MERGE_CONFLICTS_DETECTED: Merge conflicts found, attempting automatic resolution | Target: ${targetBranch} | Strategy: Auto-resolve version files`);
2077
- // Get list of conflicted files
2078
- const { stdout: conflictedFiles } = await run('git diff --name-only --diff-filter=U');
2079
- const conflicts = conflictedFiles.trim().split('\n').filter(Boolean);
2080
- logger.verbose(`MERGE_CONFLICTS_LIST: Conflicted files detected | Files: ${conflicts.join(', ')} | Count: ${conflicts.length}`);
2081
- // Check if conflicts are only in package.json (package-lock.json is gitignored)
2082
- const versionFiles = [
2083
- 'package.json'
2084
- ];
2085
- const nonVersionConflicts = conflicts.filter((f)=>!versionFiles.includes(f));
2086
- if (nonVersionConflicts.length > 0) {
2087
- logger.error(`MERGE_AUTO_RESOLVE_FAILED: Cannot auto-resolve conflicts in non-version files | Files: ${nonVersionConflicts.join(', ')} | Count: ${nonVersionConflicts.length} | Resolution: Manual intervention required`);
2088
- logger.error('');
2089
- logger.error('CONFLICT_RESOLUTION_REQUIRED: Manual steps to resolve conflicts:');
2090
- logger.error(' Step 1: Resolve conflicts in the files listed above');
2091
- logger.error(' Step 2: Stage resolved files | Command: git add <resolved-files>');
2092
- logger.error(' Step 3: Complete merge commit | Command: git commit');
2093
- logger.error(' Step 4: Resume publish process | Command: kodrdriv publish');
2094
- logger.error('');
2095
- throw new Error(`Merge conflicts in non-version files. Please resolve manually.`);
2096
- }
2097
- // Auto-resolve version conflicts by accepting current branch versions
2098
- // (keep our working branch's version, which is likely already updated)
2099
- logger.info(`MERGE_AUTO_RESOLVING: Automatically resolving version conflicts | Strategy: Keep current branch versions | Files: ${versionFiles.join(', ')}`);
2100
- for (const file of conflicts){
2101
- if (versionFiles.includes(file)) {
2102
- await run(`git checkout --ours ${file}`);
2103
- await run(`git add ${file}`);
2104
- logger.verbose(`MERGE_FILE_RESOLVED: Resolved file using current branch version | File: ${file} | Strategy: checkout --ours`);
2105
- }
2106
- }
2107
- // Complete the merge
2108
- await run(`git commit --no-edit -m "Merge ${targetBranch} to sync before version bump (auto-resolved version conflicts)"`);
2109
- logger.info(`MERGE_AUTO_RESOLVE_SUCCESS: Successfully auto-resolved version conflicts and completed merge | Target: ${targetBranch} | Files: ${versionFiles.join(', ')}`);
2110
- mergeSucceeded = true;
2111
- } else {
2112
- // Not a conflict error, re-throw
2113
- throw mergeError;
2114
- }
2115
- }
2116
- // Only run npm install if merge actually happened
2117
- if (mergeSucceeded) {
2118
- // Run npm install to update package-lock.json based on merged package.json
2119
- logger.info('POST_MERGE_NPM_INSTALL: Running npm install after merge | Purpose: Update package-lock.json based on merged package.json | Command: npm install');
2120
- await run('npm install');
2121
- logger.info('POST_MERGE_NPM_COMPLETE: npm install completed successfully | Status: Dependencies synchronized');
2122
- // Commit any changes from npm install (e.g., package-lock.json updates)
2123
- const { stdout: mergeChangesStatus } = await run('git status --porcelain');
2124
- if (mergeChangesStatus.trim()) {
2125
- logger.verbose('POST_MERGE_CHANGES_DETECTED: Changes detected after npm install | Action: Staging for commit | Command: git add');
2126
- // Skip package-lock.json as it's in .gitignore to avoid private registry refs
2127
- const filesToStagePostMerge = 'package.json';
2128
- await run(`git add ${filesToStagePostMerge}`);
2129
- if (await Diff.hasStagedChanges()) {
2130
- logger.verbose('POST_MERGE_COMMIT: Committing post-merge changes | Files: ' + filesToStagePostMerge + ' | Purpose: Finalize merge');
2131
- await Commit.commit(runConfig);
2132
- }
2133
- }
2134
- }
2135
- }
2136
- } catch (error) {
2137
- // Only catch truly unexpected errors here
2138
- logger.error(`MERGE_UNEXPECTED_ERROR: Unexpected error during merge process | Error: ${error.message} | Target: ${targetBranch} | Action: Aborting publish`);
2139
- throw error;
2140
- }
2141
- }, `merge ${targetBranch} into current branch`);
2142
- }
2143
- }
2096
+ // STEP 3: Pre-merge target into working (REMOVED)
2097
+ // This step was previously used to merge target branch into working before creating PR.
2098
+ // It's no longer needed because:
2099
+ // 1. check-development already ensures branches are in sync
2100
+ // 2. Post-publish sync handles branch synchronization
2101
+ // 3. Squash merge workflow makes pre-merge unnecessary
2102
+ // If branches have diverged, the PR will show conflicts that need manual resolution.
2144
2103
  // STEP 4: Determine and set target version AFTER checks, dependency commit, and target branch merge
2145
2104
  logger.info('Determining target version...');
2146
2105
  if (isDryRun) {
2147
2106
  logger.info('Would determine target version and update package.json');
2148
2107
  newVersion = '1.0.0'; // Mock version for dry run
2149
2108
  } else {
2150
- var _runConfig_publish14;
2109
+ var _runConfig_publish13;
2151
2110
  const packageJsonContents = await storage.readFile('package.json', 'utf-8');
2152
2111
  const parsed = safeJsonParse(packageJsonContents, 'package.json');
2153
2112
  const packageJson = validatePackageJson(parsed, 'package.json');
@@ -2164,9 +2123,9 @@ const execute = async (runConfig)=>{
2164
2123
  // Update targetBranch for the rest of the function
2165
2124
  targetBranch = finalTargetBranch;
2166
2125
  } else {
2167
- var _runConfig_publish15;
2126
+ var _runConfig_publish14;
2168
2127
  // Use existing logic for backward compatibility
2169
- const targetVersionInput = ((_runConfig_publish15 = runConfig.publish) === null || _runConfig_publish15 === void 0 ? void 0 : _runConfig_publish15.targetVersion) || 'patch';
2128
+ const targetVersionInput = ((_runConfig_publish14 = runConfig.publish) === null || _runConfig_publish14 === void 0 ? void 0 : _runConfig_publish14.targetVersion) || 'patch';
2170
2129
  proposedVersion = calculateTargetVersion(currentVersion, targetVersionInput);
2171
2130
  }
2172
2131
  const targetTagName = `v${proposedVersion}`;
@@ -2179,7 +2138,7 @@ const execute = async (runConfig)=>{
2179
2138
  const npmVersion = await getNpmPublishedVersion(packageJson.name);
2180
2139
  const tagInfo = await getTagInfo(targetTagName);
2181
2140
  if (npmVersion === proposedVersion) {
2182
- var _runConfig_publish16;
2141
+ var _runConfig_publish15;
2183
2142
  // Version is already published on npm
2184
2143
  logger.info(`VERSION_ALREADY_PUBLISHED: Version already published on npm registry | Version: ${proposedVersion} | Status: published | Action: Skipping`);
2185
2144
  logger.info(`PUBLISH_SKIPPED_DUPLICATE: Skipping publish operation | Reason: Package already at target version | Version: ${proposedVersion}`);
@@ -2188,7 +2147,7 @@ const execute = async (runConfig)=>{
2188
2147
  logger.info(` Option 1: Bump version | Command: npm version patch (or minor/major)`);
2189
2148
  logger.info(` Option 2: Re-run publish | Command: kodrdriv publish`);
2190
2149
  logger.info('');
2191
- if ((_runConfig_publish16 = runConfig.publish) === null || _runConfig_publish16 === void 0 ? void 0 : _runConfig_publish16.skipAlreadyPublished) {
2150
+ if ((_runConfig_publish15 = runConfig.publish) === null || _runConfig_publish15 === void 0 ? void 0 : _runConfig_publish15.skipAlreadyPublished) {
2192
2151
  logger.info('PUBLISH_SKIPPED_FLAG: Skipping package due to flag | Flag: --skip-already-published | Version: ' + proposedVersion + ' | Status: skipped');
2193
2152
  // Emit skip marker for tree mode detection with reason
2194
2153
  // eslint-disable-next-line no-console
@@ -2200,7 +2159,7 @@ const execute = async (runConfig)=>{
2200
2159
  throw new Error(`Version ${proposedVersion} already published. Use --skip-already-published to continue.`);
2201
2160
  }
2202
2161
  } else {
2203
- var _tagInfo_commit, _runConfig_publish17;
2162
+ var _tagInfo_commit, _runConfig_publish16;
2204
2163
  // Tag exists but version not on npm - likely failed previous publish
2205
2164
  logger.warn('');
2206
2165
  logger.warn('PUBLISH_SITUATION_ANALYSIS: Analyzing publish conflict situation | Tag: ' + targetTagName + ' | npm: ' + (npmVersion || 'not published'));
@@ -2215,7 +2174,7 @@ const execute = async (runConfig)=>{
2215
2174
  logger.warn(` Command: git tag -d ${targetTagName}`);
2216
2175
  logger.warn(` Command: git push origin :refs/tags/${targetTagName}`);
2217
2176
  logger.warn('');
2218
- if ((_runConfig_publish17 = runConfig.publish) === null || _runConfig_publish17 === void 0 ? void 0 : _runConfig_publish17.forceRepublish) {
2177
+ if ((_runConfig_publish16 = runConfig.publish) === null || _runConfig_publish16 === void 0 ? void 0 : _runConfig_publish16.forceRepublish) {
2219
2178
  logger.info('PUBLISH_FORCE_REPUBLISH: Force republish mode enabled | Action: Deleting existing tag | Tag: ' + targetTagName + ' | Purpose: Allow republish');
2220
2179
  if (!isDryRun) {
2221
2180
  const { runSecure } = await import('@grunnverk/git-tools');
@@ -2250,18 +2209,18 @@ const execute = async (runConfig)=>{
2250
2209
  }
2251
2210
  }
2252
2211
  }
2253
- if ((_runConfig_publish14 = runConfig.publish) === null || _runConfig_publish14 === void 0 ? void 0 : _runConfig_publish14.interactive) {
2254
- var _runConfig_publish18;
2255
- newVersion = await confirmVersionInteractively(currentVersion, proposedVersion, (_runConfig_publish18 = runConfig.publish) === null || _runConfig_publish18 === void 0 ? void 0 : _runConfig_publish18.targetVersion);
2212
+ if ((_runConfig_publish13 = runConfig.publish) === null || _runConfig_publish13 === void 0 ? void 0 : _runConfig_publish13.interactive) {
2213
+ var _runConfig_publish17;
2214
+ newVersion = await confirmVersionInteractively(currentVersion, proposedVersion, (_runConfig_publish17 = runConfig.publish) === null || _runConfig_publish17 === void 0 ? void 0 : _runConfig_publish17.targetVersion);
2256
2215
  const confirmedTagName = `v${newVersion}`;
2257
2216
  const confirmedTagExists = await checkIfTagExists(confirmedTagName);
2258
2217
  if (confirmedTagExists) {
2259
- var _runConfig_publish19;
2218
+ var _runConfig_publish18;
2260
2219
  const { getNpmPublishedVersion } = await import('@grunnverk/core');
2261
2220
  const npmVersion = await getNpmPublishedVersion(packageJson.name);
2262
2221
  if (npmVersion === newVersion) {
2263
2222
  throw new Error(`Tag ${confirmedTagName} already exists and version is published on npm. Please choose a different version.`);
2264
- } else if (!((_runConfig_publish19 = runConfig.publish) === null || _runConfig_publish19 === void 0 ? void 0 : _runConfig_publish19.forceRepublish)) {
2223
+ } else if (!((_runConfig_publish18 = runConfig.publish) === null || _runConfig_publish18 === void 0 ? void 0 : _runConfig_publish18.forceRepublish)) {
2265
2224
  throw new Error(`Tag ${confirmedTagName} already exists. Use --force-republish to override.`);
2266
2225
  }
2267
2226
  // If forceRepublish is set, we'll continue (tag will be deleted later)
@@ -2305,23 +2264,23 @@ const execute = async (runConfig)=>{
2305
2264
  ...runConfig.release,
2306
2265
  currentBranch: currentBranch,
2307
2266
  version: newVersion,
2308
- ...((_runConfig_publish8 = runConfig.publish) === null || _runConfig_publish8 === void 0 ? void 0 : _runConfig_publish8.from) && {
2267
+ ...((_runConfig_publish7 = runConfig.publish) === null || _runConfig_publish7 === void 0 ? void 0 : _runConfig_publish7.from) && {
2309
2268
  from: runConfig.publish.from
2310
2269
  },
2311
- ...((_runConfig_publish9 = runConfig.publish) === null || _runConfig_publish9 === void 0 ? void 0 : _runConfig_publish9.interactive) && {
2270
+ ...((_runConfig_publish8 = runConfig.publish) === null || _runConfig_publish8 === void 0 ? void 0 : _runConfig_publish8.interactive) && {
2312
2271
  interactive: runConfig.publish.interactive
2313
2272
  },
2314
- ...((_runConfig_publish10 = runConfig.publish) === null || _runConfig_publish10 === void 0 ? void 0 : _runConfig_publish10.fromMain) && {
2273
+ ...((_runConfig_publish9 = runConfig.publish) === null || _runConfig_publish9 === void 0 ? void 0 : _runConfig_publish9.fromMain) && {
2315
2274
  fromMain: runConfig.publish.fromMain
2316
2275
  }
2317
2276
  };
2318
- if ((_runConfig_publish11 = runConfig.publish) === null || _runConfig_publish11 === void 0 ? void 0 : _runConfig_publish11.from) {
2277
+ if ((_runConfig_publish10 = runConfig.publish) === null || _runConfig_publish10 === void 0 ? void 0 : _runConfig_publish10.from) {
2319
2278
  logger.verbose(`Using custom 'from' reference for release notes: ${runConfig.publish.from}`);
2320
2279
  }
2321
- if ((_runConfig_publish12 = runConfig.publish) === null || _runConfig_publish12 === void 0 ? void 0 : _runConfig_publish12.interactive) {
2280
+ if ((_runConfig_publish11 = runConfig.publish) === null || _runConfig_publish11 === void 0 ? void 0 : _runConfig_publish11.interactive) {
2322
2281
  logger.verbose('Interactive mode enabled for release notes generation');
2323
2282
  }
2324
- if ((_runConfig_publish13 = runConfig.publish) === null || _runConfig_publish13 === void 0 ? void 0 : _runConfig_publish13.fromMain) {
2283
+ if ((_runConfig_publish12 = runConfig.publish) === null || _runConfig_publish12 === void 0 ? void 0 : _runConfig_publish12.fromMain) {
2325
2284
  logger.verbose('Forcing comparison against main branch for release notes');
2326
2285
  }
2327
2286
  // Log self-reflection settings for debugging
@@ -2386,12 +2345,12 @@ const execute = async (runConfig)=>{
2386
2345
  logger.debug(`Could not verify workflow configuration for wait skip: ${error.message}`);
2387
2346
  }
2388
2347
  if (!shouldSkipWait) {
2389
- var _runConfig_publish20, _runConfig_publish21, _runConfig_publish22;
2348
+ var _runConfig_publish19, _runConfig_publish20, _runConfig_publish21;
2390
2349
  // Configure timeout and user confirmation behavior
2391
- const timeout = ((_runConfig_publish20 = runConfig.publish) === null || _runConfig_publish20 === void 0 ? void 0 : _runConfig_publish20.checksTimeout) || KODRDRIV_DEFAULTS.publish.checksTimeout;
2392
- const senditMode = ((_runConfig_publish21 = runConfig.publish) === null || _runConfig_publish21 === void 0 ? void 0 : _runConfig_publish21.sendit) || false;
2350
+ const timeout = ((_runConfig_publish19 = runConfig.publish) === null || _runConfig_publish19 === void 0 ? void 0 : _runConfig_publish19.checksTimeout) || KODRDRIV_DEFAULTS.publish.checksTimeout;
2351
+ const senditMode = ((_runConfig_publish20 = runConfig.publish) === null || _runConfig_publish20 === void 0 ? void 0 : _runConfig_publish20.sendit) || false;
2393
2352
  // sendit flag overrides skipUserConfirmation - if sendit is true, skip confirmation
2394
- const skipUserConfirmation = senditMode || ((_runConfig_publish22 = runConfig.publish) === null || _runConfig_publish22 === void 0 ? void 0 : _runConfig_publish22.skipUserConfirmation) || false;
2353
+ const skipUserConfirmation = senditMode || ((_runConfig_publish21 = runConfig.publish) === null || _runConfig_publish21 === void 0 ? void 0 : _runConfig_publish21.skipUserConfirmation) || false;
2395
2354
  await GitHub.waitForPullRequestChecks(pr.number, {
2396
2355
  timeout,
2397
2356
  skipUserConfirmation
@@ -2516,50 +2475,15 @@ const execute = async (runConfig)=>{
2516
2475
  logger.warn('PUBLISH_STASH_AVAILABLE: Changes available in git stash | Command: git stash list | Purpose: View and restore manually');
2517
2476
  }
2518
2477
  }
2519
- // Update package.json on target branch to release version and commit
2520
- // This ensures the tag points to a commit with the correct release version
2478
+ // Read the version from target branch for tag creation
2479
+ // After squash merge, this should match the version we set on the working branch
2521
2480
  if (!isDryRun) {
2522
- logger.info(`PUBLISH_VERSION_UPDATE_TARGET: Updating package.json on target branch to release version | Version: ${newVersion} | Branch: ${targetBranch}`);
2523
- // Read current package.json on target branch
2524
2481
  const targetPackageJsonContents = await storage.readFile('package.json', 'utf-8');
2525
2482
  const targetPackageJson = safeJsonParse(targetPackageJsonContents, 'package.json');
2526
- // Check if version update is needed
2527
- if (targetPackageJson.version !== newVersion) {
2528
- logger.info(`PUBLISH_VERSION_MISMATCH: Version mismatch detected on target | Current: ${targetPackageJson.version} | Expected: ${newVersion} | Action: Updating`);
2529
- // Update version in package.json
2530
- targetPackageJson.version = newVersion;
2531
- await storage.writeFile('package.json', JSON.stringify(targetPackageJson, null, 2) + '\n', 'utf-8');
2532
- logger.info(`PUBLISH_VERSION_UPDATED: Updated package.json version on target branch | Version: ${newVersion} | Branch: ${targetBranch}`);
2533
- // Stage and commit the version update
2534
- await runGitWithLock(process.cwd(), async ()=>{
2535
- await runSecure('git', [
2536
- 'add',
2537
- 'package.json'
2538
- ]);
2539
- }, 'stage version update on target');
2540
- // Check if there are staged changes before committing
2541
- if (await Diff.hasStagedChanges()) {
2542
- logger.info('PUBLISH_VERSION_COMMITTING: Committing version update to target branch | Purpose: Ensure tag points to correct version');
2543
- await runGitWithLock(process.cwd(), async ()=>{
2544
- await Commit.commit(runConfig);
2545
- }, 'commit version update on target');
2546
- logger.info('PUBLISH_VERSION_COMMITTED: Version update committed successfully');
2547
- // Push the version update to remote
2548
- logger.info(`PUBLISH_VERSION_PUSHING: Pushing version update to remote | Branch: ${targetBranch} | Remote: origin`);
2549
- await runGitWithLock(process.cwd(), async ()=>{
2550
- await runSecure('git', [
2551
- 'push',
2552
- 'origin',
2553
- targetBranch
2554
- ]);
2555
- }, `push version update to ${targetBranch}`);
2556
- logger.info('PUBLISH_VERSION_PUSHED: Version update pushed to remote successfully');
2557
- } else {
2558
- logger.verbose('PUBLISH_VERSION_NO_CHANGES: No changes to commit (version already correct)');
2559
- }
2560
- } else {
2561
- logger.info(`PUBLISH_VERSION_CORRECT: Version already correct on target branch | Version: ${newVersion} | Branch: ${targetBranch}`);
2562
- }
2483
+ const targetVersion = targetPackageJson.version;
2484
+ logger.info(`PUBLISH_VERSION_TARGET: Version on target branch after merge | Version: ${targetVersion} | Branch: ${targetBranch}`);
2485
+ // Use the version from target branch for tagging (should match newVersion from working branch)
2486
+ newVersion = targetVersion;
2563
2487
  }
2564
2488
  // Now create and push the tag on the target branch
2565
2489
  logger.info('Creating release tag...');
@@ -2650,9 +2574,9 @@ const execute = async (runConfig)=>{
2650
2574
  }
2651
2575
  logger.info('Creating GitHub release...');
2652
2576
  if (isDryRun) {
2653
- var _runConfig_publish23;
2577
+ var _runConfig_publish22;
2654
2578
  logger.info('Would read package.json version and create GitHub release with retry logic');
2655
- const milestonesEnabled = !((_runConfig_publish23 = runConfig.publish) === null || _runConfig_publish23 === void 0 ? void 0 : _runConfig_publish23.noMilestones);
2579
+ const milestonesEnabled = !((_runConfig_publish22 = runConfig.publish) === null || _runConfig_publish22 === void 0 ? void 0 : _runConfig_publish22.noMilestones);
2656
2580
  if (milestonesEnabled) {
2657
2581
  logger.info('Would close milestone for released version');
2658
2582
  } else {
@@ -2668,11 +2592,11 @@ const execute = async (runConfig)=>{
2668
2592
  let retries = 3;
2669
2593
  while(retries > 0){
2670
2594
  try {
2671
- var _runConfig_publish24;
2595
+ var _runConfig_publish23;
2672
2596
  await GitHub.createRelease(tagName, releaseTitle, releaseNotesContent);
2673
2597
  logger.info(`GitHub release created successfully for tag: ${tagName}`);
2674
2598
  // Close milestone for this version if enabled
2675
- const milestonesEnabled = !((_runConfig_publish24 = runConfig.publish) === null || _runConfig_publish24 === void 0 ? void 0 : _runConfig_publish24.noMilestones);
2599
+ const milestonesEnabled = !((_runConfig_publish23 = runConfig.publish) === null || _runConfig_publish23 === void 0 ? void 0 : _runConfig_publish23.noMilestones);
2676
2600
  if (milestonesEnabled) {
2677
2601
  logger.info('PUBLISH_MILESTONE_CLOSING: Closing milestone for released version | Action: Close GitHub milestone | Purpose: Mark release complete');
2678
2602
  const version = tagName.replace(/^v/, ''); // Remove 'v' prefix if present
@@ -2705,12 +2629,12 @@ const execute = async (runConfig)=>{
2705
2629
  if (isDryRun) {
2706
2630
  logger.info('Would monitor GitHub Actions workflows triggered by release');
2707
2631
  } else {
2708
- var _runConfig_publish25, _runConfig_publish26, _runConfig_publish27, _runConfig_publish28;
2709
- const workflowTimeout = ((_runConfig_publish25 = runConfig.publish) === null || _runConfig_publish25 === void 0 ? void 0 : _runConfig_publish25.releaseWorkflowsTimeout) || KODRDRIV_DEFAULTS.publish.releaseWorkflowsTimeout;
2710
- const senditMode = ((_runConfig_publish26 = runConfig.publish) === null || _runConfig_publish26 === void 0 ? void 0 : _runConfig_publish26.sendit) || false;
2711
- const skipUserConfirmation = senditMode || ((_runConfig_publish27 = runConfig.publish) === null || _runConfig_publish27 === void 0 ? void 0 : _runConfig_publish27.skipUserConfirmation) || false;
2632
+ var _runConfig_publish24, _runConfig_publish25, _runConfig_publish26, _runConfig_publish27;
2633
+ const workflowTimeout = ((_runConfig_publish24 = runConfig.publish) === null || _runConfig_publish24 === void 0 ? void 0 : _runConfig_publish24.releaseWorkflowsTimeout) || KODRDRIV_DEFAULTS.publish.releaseWorkflowsTimeout;
2634
+ const senditMode = ((_runConfig_publish25 = runConfig.publish) === null || _runConfig_publish25 === void 0 ? void 0 : _runConfig_publish25.sendit) || false;
2635
+ const skipUserConfirmation = senditMode || ((_runConfig_publish26 = runConfig.publish) === null || _runConfig_publish26 === void 0 ? void 0 : _runConfig_publish26.skipUserConfirmation) || false;
2712
2636
  // Get workflow names - either from config or auto-detect
2713
- let workflowNames = (_runConfig_publish28 = runConfig.publish) === null || _runConfig_publish28 === void 0 ? void 0 : _runConfig_publish28.releaseWorkflowNames;
2637
+ let workflowNames = (_runConfig_publish27 = runConfig.publish) === null || _runConfig_publish27 === void 0 ? void 0 : _runConfig_publish27.releaseWorkflowNames;
2714
2638
  if (!workflowNames || workflowNames.length === 0) {
2715
2639
  logger.info('No specific workflow names configured, auto-detecting workflows triggered by release events...');
2716
2640
  try {
@@ -2753,8 +2677,12 @@ const execute = async (runConfig)=>{
2753
2677
  try {
2754
2678
  // Verify that remote working branch is ancestor of main (safety check)
2755
2679
  try {
2756
- await run(`git fetch origin ${currentBranch}`);
2757
- await run(`git merge-base --is-ancestor origin/${currentBranch} ${targetBranch}`);
2680
+ await run(`git fetch origin ${currentBranch}`, {
2681
+ suppressErrorLogging: true
2682
+ });
2683
+ await run(`git merge-base --is-ancestor origin/${currentBranch} ${targetBranch}`, {
2684
+ suppressErrorLogging: true
2685
+ });
2758
2686
  logger.verbose(`✓ Safety check passed: origin/${currentBranch} is ancestor of ${targetBranch}`);
2759
2687
  } catch {
2760
2688
  // Remote branch might not exist yet, or already in sync - both OK