@regardio/dev 1.13.8 → 1.14.3

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 (113) hide show
  1. package/README.md +3 -3
  2. package/dist/bin/exec/clean.d.ts +3 -0
  3. package/dist/bin/exec/clean.d.ts.map +1 -0
  4. package/dist/bin/exec/clean.js +25 -0
  5. package/dist/bin/exec/clean.test.d.ts +2 -0
  6. package/dist/bin/exec/clean.test.d.ts.map +1 -0
  7. package/dist/bin/exec/clean.test.js +45 -0
  8. package/dist/bin/exec/husky.d.ts +3 -0
  9. package/dist/bin/exec/husky.d.ts.map +1 -0
  10. package/dist/bin/exec/p.d.ts +3 -0
  11. package/dist/bin/exec/p.d.ts.map +1 -0
  12. package/dist/bin/exec/s.d.ts +3 -0
  13. package/dist/bin/exec/s.d.ts.map +1 -0
  14. package/dist/bin/exec/ts.d.ts +3 -0
  15. package/dist/bin/exec/ts.d.ts.map +1 -0
  16. package/dist/bin/exec/ts.js +36 -0
  17. package/dist/bin/exec/ts.test.d.ts +2 -0
  18. package/dist/bin/exec/ts.test.d.ts.map +1 -0
  19. package/dist/bin/exec/ts.test.js +39 -0
  20. package/dist/bin/exec/tsc.d.ts +3 -0
  21. package/dist/bin/exec/tsc.d.ts.map +1 -0
  22. package/dist/bin/flow/hotfix.d.ts +3 -0
  23. package/dist/bin/flow/hotfix.d.ts.map +1 -0
  24. package/dist/bin/flow/hotfix.js +116 -0
  25. package/dist/bin/flow/release.d.ts +3 -0
  26. package/dist/bin/flow/release.d.ts.map +1 -0
  27. package/dist/bin/flow/release.js +68 -0
  28. package/dist/bin/flow/ship.d.ts +3 -0
  29. package/dist/bin/flow/ship.d.ts.map +1 -0
  30. package/dist/bin/flow/ship.js +104 -0
  31. package/dist/bin/flow/utils.d.ts +9 -0
  32. package/dist/bin/flow/utils.d.ts.map +1 -0
  33. package/dist/bin/flow/utils.js +63 -0
  34. package/dist/bin/flow/utils.test.d.ts +2 -0
  35. package/dist/bin/flow/utils.test.d.ts.map +1 -0
  36. package/dist/bin/flow/utils.test.js +127 -0
  37. package/dist/bin/lint/biome.d.ts +3 -0
  38. package/dist/bin/lint/biome.d.ts.map +1 -0
  39. package/dist/bin/lint/commit.d.ts +3 -0
  40. package/dist/bin/lint/commit.d.ts.map +1 -0
  41. package/dist/bin/lint/md.d.ts +3 -0
  42. package/dist/bin/lint/md.d.ts.map +1 -0
  43. package/dist/bin/lint/package.d.ts +4 -0
  44. package/dist/bin/lint/package.d.ts.map +1 -0
  45. package/dist/bin/lint/package.js +81 -0
  46. package/dist/bin/lint/package.test.d.ts +2 -0
  47. package/dist/bin/lint/package.test.d.ts.map +1 -0
  48. package/dist/bin/lint/package.test.js +65 -0
  49. package/package.json +21 -22
  50. package/src/bin/exec/clean.test.ts +63 -0
  51. package/src/bin/exec/clean.ts +36 -0
  52. package/src/bin/exec/ts.test.ts +54 -0
  53. package/src/bin/exec/ts.ts +52 -0
  54. package/src/bin/flow/hotfix.ts +210 -0
  55. package/src/bin/flow/release.ts +130 -0
  56. package/src/bin/flow/ship.ts +215 -0
  57. package/src/bin/flow/utils.test.ts +178 -0
  58. package/src/bin/flow/utils.ts +109 -0
  59. package/src/bin/lint/package.test.ts +83 -0
  60. package/src/bin/lint/package.ts +108 -0
  61. package/src/templates/release.yml +23 -17
  62. package/dist/bin/exec-clean.d.ts +0 -3
  63. package/dist/bin/exec-clean.d.ts.map +0 -1
  64. package/dist/bin/exec-clean.js +0 -18
  65. package/dist/bin/exec-husky.d.ts +0 -3
  66. package/dist/bin/exec-husky.d.ts.map +0 -1
  67. package/dist/bin/exec-p.d.ts +0 -3
  68. package/dist/bin/exec-p.d.ts.map +0 -1
  69. package/dist/bin/exec-s.d.ts +0 -3
  70. package/dist/bin/exec-s.d.ts.map +0 -1
  71. package/dist/bin/exec-ts.d.ts +0 -3
  72. package/dist/bin/exec-ts.d.ts.map +0 -1
  73. package/dist/bin/exec-ts.js +0 -28
  74. package/dist/bin/exec-tsc.d.ts +0 -3
  75. package/dist/bin/exec-tsc.d.ts.map +0 -1
  76. package/dist/bin/flow-changeset.d.ts +0 -3
  77. package/dist/bin/flow-changeset.d.ts.map +0 -1
  78. package/dist/bin/flow-changeset.js +0 -18
  79. package/dist/bin/flow-release.d.ts +0 -3
  80. package/dist/bin/flow-release.d.ts.map +0 -1
  81. package/dist/bin/flow-release.js +0 -115
  82. package/dist/bin/lint-biome.d.ts +0 -3
  83. package/dist/bin/lint-biome.d.ts.map +0 -1
  84. package/dist/bin/lint-commit.d.ts +0 -3
  85. package/dist/bin/lint-commit.d.ts.map +0 -1
  86. package/dist/bin/lint-md.d.ts +0 -3
  87. package/dist/bin/lint-md.d.ts.map +0 -1
  88. package/dist/bin/lint-package.d.ts +0 -3
  89. package/dist/bin/lint-package.d.ts.map +0 -1
  90. package/dist/bin/lint-package.js +0 -86
  91. package/dist/bin/lint-package.test.d.ts +0 -2
  92. package/dist/bin/lint-package.test.d.ts.map +0 -1
  93. package/dist/bin/lint-package.test.js +0 -111
  94. package/src/bin/exec-clean.ts +0 -24
  95. package/src/bin/exec-ts.ts +0 -39
  96. package/src/bin/flow-changeset.ts +0 -23
  97. package/src/bin/flow-release.ts +0 -185
  98. package/src/bin/lint-package.test.ts +0 -140
  99. package/src/bin/lint-package.ts +0 -114
  100. /package/dist/bin/{exec-husky.js → exec/husky.js} +0 -0
  101. /package/dist/bin/{exec-p.js → exec/p.js} +0 -0
  102. /package/dist/bin/{exec-s.js → exec/s.js} +0 -0
  103. /package/dist/bin/{exec-tsc.js → exec/tsc.js} +0 -0
  104. /package/dist/bin/{lint-biome.js → lint/biome.js} +0 -0
  105. /package/dist/bin/{lint-commit.js → lint/commit.js} +0 -0
  106. /package/dist/bin/{lint-md.js → lint/md.js} +0 -0
  107. /package/src/bin/{exec-husky.ts → exec/husky.ts} +0 -0
  108. /package/src/bin/{exec-p.ts → exec/p.ts} +0 -0
  109. /package/src/bin/{exec-s.ts → exec/s.ts} +0 -0
  110. /package/src/bin/{exec-tsc.ts → exec/tsc.ts} +0 -0
  111. /package/src/bin/{lint-biome.ts → lint/biome.ts} +0 -0
  112. /package/src/bin/{lint-commit.ts → lint/commit.ts} +0 -0
  113. /package/src/bin/{lint-md.ts → lint/md.ts} +0 -0
@@ -0,0 +1,210 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * flow-hotfix: Manage hotfix branches based on production code.
4
+ *
5
+ * Usage:
6
+ * flow-hotfix start <name> - Create hotfix/<name> from production
7
+ * flow-hotfix finish <patch|minor> "description" - Finish and propagate the hotfix
8
+ *
9
+ * GitLab workflow:
10
+ * production → hotfix/<name> → production → staging → main
11
+ *
12
+ * start:
13
+ * 1. Fetches origin, checks out production, creates hotfix/<name>
14
+ *
15
+ * finish:
16
+ * 1. Ensures you are on a hotfix/* branch with a clean working tree
17
+ * 2. Runs quality checks
18
+ * 3. Bumps version (patch or minor) and updates CHANGELOG.md
19
+ * 4. Commits, then merges into production
20
+ * 5. Merges production into staging
21
+ * 6. Merges staging into main
22
+ * 7. Pushes all three branches, deletes the hotfix branch
23
+ * 8. Checks out main
24
+ */
25
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
26
+ import { join } from 'node:path';
27
+
28
+ import {
29
+ branchExists,
30
+ bumpVersion,
31
+ git,
32
+ gitRead,
33
+ insertChangelog,
34
+ runQualityChecks,
35
+ runScript,
36
+ } from './utils.js';
37
+
38
+ const subcommand = process.argv[2];
39
+ const subArgs = process.argv.slice(3);
40
+
41
+ // ---------------------------------------------------------------------------
42
+ // flow-hotfix start <name>
43
+ // ---------------------------------------------------------------------------
44
+ if (subcommand === 'start') {
45
+ const name = subArgs[0];
46
+ if (!name) {
47
+ console.error('Usage: flow-hotfix start <name>');
48
+ process.exit(1);
49
+ }
50
+
51
+ const hotfixBranch = `hotfix/${name}`;
52
+
53
+ const status = gitRead('status', '--porcelain');
54
+ if (status) {
55
+ console.error('Working directory has uncommitted changes. Commit or stash them first.');
56
+ process.exit(1);
57
+ }
58
+
59
+ console.log('\nFetching latest state from origin...');
60
+ git('fetch', 'origin');
61
+
62
+ if (!branchExists('production')) {
63
+ console.error(
64
+ 'Branch "production" does not exist. Create it first:\n'
65
+ + ' git checkout -b production && git push -u origin production',
66
+ );
67
+ process.exit(1);
68
+ }
69
+
70
+ git('checkout', 'production');
71
+ git('pull', '--ff-only', 'origin', 'production');
72
+ git('checkout', '-b', hotfixBranch);
73
+
74
+ console.log(`\n✅ Hotfix branch "${hotfixBranch}" created from production.`);
75
+ console.log('Apply your fix, then run: flow-hotfix finish <patch|minor> "description"');
76
+ process.exit(0);
77
+ }
78
+
79
+ // ---------------------------------------------------------------------------
80
+ // flow-hotfix finish <patch|minor> "description"
81
+ // ---------------------------------------------------------------------------
82
+ if (subcommand === 'finish') {
83
+ const bumpType = subArgs[0];
84
+ const message = subArgs.slice(1).join(' ');
85
+
86
+ if (!bumpType || !['patch', 'minor'].includes(bumpType)) {
87
+ console.error('Usage: flow-hotfix finish <patch|minor> "description"');
88
+ console.error('Hotfixes use patch or minor bumps only.');
89
+ process.exit(1);
90
+ }
91
+
92
+ if (!message) {
93
+ console.error('A description is required.');
94
+ console.error('Example: flow-hotfix finish patch "Fix critical auth bug"');
95
+ process.exit(1);
96
+ }
97
+
98
+ // Guard: must be on a hotfix/* branch
99
+ const currentBranch = gitRead('branch', '--show-current');
100
+ if (!currentBranch.startsWith('hotfix/')) {
101
+ console.error(`Must be on a hotfix/* branch. Currently on: ${currentBranch}`);
102
+ process.exit(1);
103
+ }
104
+
105
+ // Guard: working tree must be clean
106
+ const status = gitRead('status', '--porcelain');
107
+ if (status) {
108
+ console.error('Working directory has uncommitted changes. Commit or stash them first.');
109
+ process.exit(1);
110
+ }
111
+
112
+ // Quality checks
113
+ console.log('\nRunning quality checks...');
114
+ try {
115
+ runQualityChecks();
116
+ } catch {
117
+ console.error('\nQuality checks failed. Fix all issues before finishing the hotfix.');
118
+ process.exit(1);
119
+ }
120
+ console.log('✅ Quality checks passed');
121
+
122
+ // Read package.json
123
+ const packageJsonPath = join(process.cwd(), 'package.json');
124
+ if (!existsSync(packageJsonPath)) {
125
+ console.error('No package.json found in current directory.');
126
+ process.exit(1);
127
+ }
128
+
129
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')) as {
130
+ name: string;
131
+ version: string;
132
+ };
133
+ const packageName = packageJson.name;
134
+ const oldVersion = packageJson.version;
135
+ const newVersion = bumpVersion(oldVersion, bumpType);
136
+
137
+ console.log(`\nBumping ${packageName}: ${oldVersion} → ${newVersion}`);
138
+
139
+ writeFileSync(
140
+ packageJsonPath,
141
+ `${JSON.stringify({ ...packageJson, version: newVersion }, null, 2)}\n`,
142
+ );
143
+
144
+ // Update CHANGELOG.md
145
+ const changelogPath = join(process.cwd(), 'CHANGELOG.md');
146
+ const today = new Date().toISOString().slice(0, 10);
147
+ insertChangelog(changelogPath, `## [${newVersion}] - ${today} (hotfix)\n\n${message}\n`);
148
+
149
+ // Fix formatting if available
150
+ try {
151
+ runScript('fix');
152
+ } catch {
153
+ // fix may not exist in all packages
154
+ }
155
+
156
+ // Commit
157
+ git('add', '-A');
158
+ git('commit', '-m', `chore(hotfix): ${packageName}@${newVersion}`, '-m', message);
159
+
160
+ // Fetch before merging
161
+ console.log('\nFetching latest state from origin...');
162
+ git('fetch', 'origin');
163
+
164
+ // Merge hotfix → production
165
+ console.log('\nMerging hotfix into production...');
166
+ git('checkout', 'production');
167
+ git('pull', '--ff-only', 'origin', 'production');
168
+ git(
169
+ 'merge',
170
+ '--no-ff',
171
+ currentBranch,
172
+ '-m',
173
+ `chore(hotfix): merge ${currentBranch} into production`,
174
+ );
175
+ git('push', 'origin', 'production');
176
+
177
+ // Merge production → staging
178
+ console.log('\nPropagating hotfix to staging...');
179
+ git('checkout', 'staging');
180
+ git('pull', '--ff-only', 'origin', 'staging');
181
+ git('merge', '--no-ff', 'production', '-m', 'chore(hotfix): merge production into staging');
182
+ git('push', 'origin', 'staging');
183
+
184
+ // Merge staging → main
185
+ console.log('\nPropagating hotfix to main...');
186
+ git('checkout', 'main');
187
+ git('pull', '--ff-only', 'origin', 'main');
188
+ git('merge', '--no-ff', 'staging', '-m', 'chore(hotfix): merge staging into main');
189
+ git('push', 'origin', 'main');
190
+
191
+ // Delete hotfix branch
192
+ git('branch', '-d', currentBranch);
193
+ try {
194
+ git('push', 'origin', '--delete', currentBranch);
195
+ } catch {
196
+ // Remote branch may not exist if it was never pushed
197
+ }
198
+
199
+ console.log(`\n✅ Hotfix ${packageName}@${newVersion} shipped to production → staging → main`);
200
+ console.log('You are on main and ready to keep working.');
201
+ process.exit(0);
202
+ }
203
+
204
+ // ---------------------------------------------------------------------------
205
+ // Unknown subcommand
206
+ // ---------------------------------------------------------------------------
207
+ console.error('Usage:');
208
+ console.error(' flow-hotfix start <name>');
209
+ console.error(' flow-hotfix finish <patch|minor> "description"');
210
+ process.exit(1);
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * flow-release: Deploy changes to staging following the GitLab workflow.
4
+ *
5
+ * Usage: flow-release "message"
6
+ *
7
+ * GitLab workflow:
8
+ * main → staging (staging deploy, no version bump yet)
9
+ *
10
+ * Versioning happens at ship time (flow-ship), not here.
11
+ * This keeps version numbers reserved for production-verified code.
12
+ *
13
+ * This script:
14
+ * 1. Ensures the current branch is main and the working tree is clean
15
+ * 2. Pulls latest main from origin
16
+ * 3. Runs quality checks locally (build, typecheck, tests)
17
+ * 4. Runs fix — commits formatting output (if any) with a commitlint-compliant message
18
+ * 5. Merges main into staging (fast-forward) and pushes
19
+ * 6. Pushes main and returns so work can continue
20
+ */
21
+ import { existsSync, readFileSync } from 'node:fs';
22
+ import { join } from 'node:path';
23
+
24
+ import { branchExists, git, gitRead, runQualityChecks, runScript } from './utils.js';
25
+
26
+ const args = process.argv.slice(2);
27
+ const message = args.join(' ');
28
+
29
+ if (!message) {
30
+ console.error('Usage: flow-release "message"');
31
+ console.error('Example: flow-release "Add new vitest configs"');
32
+ process.exit(1);
33
+ }
34
+
35
+ // ---------------------------------------------------------------------------
36
+ // Guard: must be on main
37
+ // ---------------------------------------------------------------------------
38
+ const currentBranch = gitRead('branch', '--show-current');
39
+ if (currentBranch !== 'main') {
40
+ console.error(`Must be on the main branch to release. Currently on: ${currentBranch}`);
41
+ process.exit(1);
42
+ }
43
+
44
+ // Guard: working tree must be clean
45
+ const status = gitRead('status', '--porcelain');
46
+ if (status) {
47
+ console.error('Working directory has uncommitted changes. Commit or stash them first.');
48
+ process.exit(1);
49
+ }
50
+
51
+ // ---------------------------------------------------------------------------
52
+ // Sync with origin before doing anything
53
+ // ---------------------------------------------------------------------------
54
+ console.log('\nFetching latest state from origin...');
55
+ git('fetch', 'origin');
56
+ git('pull', '--ff-only', 'origin', 'main');
57
+
58
+ // ---------------------------------------------------------------------------
59
+ // Read package.json
60
+ // ---------------------------------------------------------------------------
61
+ const packageJsonPath = join(process.cwd(), 'package.json');
62
+ if (!existsSync(packageJsonPath)) {
63
+ console.error('No package.json found in current directory.');
64
+ process.exit(1);
65
+ }
66
+
67
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')) as { name: string };
68
+ const packageName = packageJson.name;
69
+
70
+ if (!packageName) {
71
+ console.error('No "name" field found in package.json.');
72
+ process.exit(1);
73
+ }
74
+
75
+ // Guard: staging branch must exist
76
+ if (!branchExists('staging')) {
77
+ console.error(
78
+ 'Branch "staging" does not exist locally or on origin. Create it first:\n'
79
+ + ' git checkout -b staging && git push -u origin staging',
80
+ );
81
+ process.exit(1);
82
+ }
83
+
84
+ // ---------------------------------------------------------------------------
85
+ // Quality checks — no broken code may be committed
86
+ // ---------------------------------------------------------------------------
87
+ console.log('\nRunning quality checks...');
88
+ try {
89
+ runQualityChecks();
90
+ } catch {
91
+ console.error('\nQuality checks failed. Fix all issues before releasing.');
92
+ process.exit(1);
93
+ }
94
+ console.log('✅ Quality checks passed');
95
+
96
+ // ---------------------------------------------------------------------------
97
+ // Fix formatting if available
98
+ // ---------------------------------------------------------------------------
99
+ try {
100
+ runScript('fix');
101
+ } catch {
102
+ // fix may not exist in all packages
103
+ }
104
+
105
+ // ---------------------------------------------------------------------------
106
+ // Commit formatting fixes (if any)
107
+ // ---------------------------------------------------------------------------
108
+ git('add', '-A');
109
+ const hasStagedChanges = gitRead('diff', '--cached', '--name-only') !== '';
110
+ if (hasStagedChanges) {
111
+ git('commit', '-m', `chore(staging): ${message}`);
112
+ }
113
+
114
+ // ---------------------------------------------------------------------------
115
+ // Merge into staging
116
+ // ---------------------------------------------------------------------------
117
+ console.log('\nMerging main into staging...');
118
+ git('checkout', 'staging');
119
+ git('merge', '--ff-only', 'main');
120
+ git('push', 'origin', 'staging');
121
+
122
+ // ---------------------------------------------------------------------------
123
+ // Return to main and sync
124
+ // ---------------------------------------------------------------------------
125
+ git('checkout', 'main');
126
+ git('push', 'origin', 'main');
127
+
128
+ console.log('\n✅ Changes deployed to staging');
129
+ console.log(` "${message}"`);
130
+ console.log('Run flow-ship <patch|minor|major> when ready to promote to production.');
@@ -0,0 +1,215 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * flow-ship: Version and promote staging to production following the GitLab workflow.
4
+ *
5
+ * Usage: flow-ship <patch|minor|major>
6
+ *
7
+ * GitLab workflow:
8
+ * staging → (version bump commit) → production
9
+ *
10
+ * Versioning is intentionally deferred to this step so that version numbers
11
+ * only ever correspond to production-verified code.
12
+ *
13
+ * This script:
14
+ * 1. Ensures the current branch is main and the working tree is clean
15
+ * 2. Verifies staging is ahead of production (there is something to ship)
16
+ * 3. Runs quality checks on the staging branch
17
+ * 4. Bumps the version in package.json
18
+ * 5. Collects change descriptions from git log between production and staging
19
+ * 6. Updates CHANGELOG.md
20
+ * 7. Commits the version bump on staging
21
+ * 8. Merges staging into production (fast-forward) and pushes
22
+ * 9. Merges production back into main to carry the version commit forward
23
+ * 10. Syncs staging with main so the next flow-release can ff-merge cleanly
24
+ * 11. Returns to main
25
+ */
26
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
27
+ import { join } from 'node:path';
28
+
29
+ import {
30
+ branchExists,
31
+ bumpVersion,
32
+ confirm,
33
+ git,
34
+ gitRead,
35
+ insertChangelog,
36
+ runQualityChecks,
37
+ runScript,
38
+ } from './utils.js';
39
+
40
+ const args = process.argv.slice(2);
41
+ const bumpType = args[0];
42
+
43
+ if (!bumpType || !['patch', 'minor', 'major'].includes(bumpType)) {
44
+ console.error('Usage: flow-ship <patch|minor|major>');
45
+ console.error('Example: flow-ship minor');
46
+ process.exit(1);
47
+ }
48
+
49
+ // ---------------------------------------------------------------------------
50
+ // Guard: must be on main
51
+ // ---------------------------------------------------------------------------
52
+ const currentBranch = gitRead('branch', '--show-current');
53
+ if (currentBranch !== 'main') {
54
+ console.error(`Must be on the main branch to ship. Currently on: ${currentBranch}`);
55
+ process.exit(1);
56
+ }
57
+
58
+ // Guard: working tree must be clean
59
+ const status = gitRead('status', '--porcelain');
60
+ if (status) {
61
+ console.error('Working directory has uncommitted changes. Commit or stash them first.');
62
+ process.exit(1);
63
+ }
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // Fetch and verify both branches exist
67
+ // ---------------------------------------------------------------------------
68
+ console.log('\nFetching latest state from origin...');
69
+ git('fetch', 'origin');
70
+
71
+ if (!branchExists('staging')) {
72
+ console.error(
73
+ 'Branch "staging" does not exist. Create it first:\n'
74
+ + ' git checkout -b staging && git push -u origin staging',
75
+ );
76
+ process.exit(1);
77
+ }
78
+
79
+ if (!branchExists('production')) {
80
+ console.error(
81
+ 'Branch "production" does not exist. Create it first:\n'
82
+ + ' git checkout -b production && git push -u origin production',
83
+ );
84
+ process.exit(1);
85
+ }
86
+
87
+ // ---------------------------------------------------------------------------
88
+ // Verify staging has commits not yet in production
89
+ // ---------------------------------------------------------------------------
90
+ const ahead = gitRead('log', 'origin/production..origin/staging', '--oneline');
91
+ if (!ahead) {
92
+ console.error('staging is already in sync with production. Nothing to ship.');
93
+ process.exit(1);
94
+ }
95
+
96
+ console.log('\nCommits to be shipped to production:');
97
+ console.log(ahead);
98
+
99
+ // ---------------------------------------------------------------------------
100
+ // Read package name for the confirmation prompt
101
+ // ---------------------------------------------------------------------------
102
+ const packageJsonPath = join(process.cwd(), 'package.json');
103
+ if (!existsSync(packageJsonPath)) {
104
+ console.error('No package.json found in current directory.');
105
+ process.exit(1);
106
+ }
107
+ const { name: packageName } = JSON.parse(readFileSync(packageJsonPath, 'utf-8')) as {
108
+ name: string;
109
+ };
110
+
111
+ if (!confirm(`\nShip ${packageName} as a ${bumpType} release?`)) {
112
+ console.log('Aborted.');
113
+ process.exit(0);
114
+ }
115
+
116
+ // ---------------------------------------------------------------------------
117
+ // Quality checks on staging
118
+ // ---------------------------------------------------------------------------
119
+ console.log('\nChecking out staging for quality checks...');
120
+ git('checkout', 'staging');
121
+ git('pull', '--ff-only', 'origin', 'staging');
122
+
123
+ console.log('\nRunning quality checks on staging...');
124
+ try {
125
+ runQualityChecks();
126
+ } catch {
127
+ console.error('\nQuality checks failed on staging. Fix issues before shipping.');
128
+ git('checkout', 'main');
129
+ process.exit(1);
130
+ }
131
+ console.log('✅ Quality checks passed');
132
+
133
+ // ---------------------------------------------------------------------------
134
+ // Read version from staging package.json and bump
135
+ // ---------------------------------------------------------------------------
136
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')) as {
137
+ name: string;
138
+ version: string;
139
+ };
140
+ const oldVersion = packageJson.version;
141
+ const newVersion = bumpVersion(oldVersion, bumpType);
142
+
143
+ console.log(`\nBumping ${packageName}: ${oldVersion} → ${newVersion}`);
144
+
145
+ writeFileSync(
146
+ packageJsonPath,
147
+ `${JSON.stringify({ ...packageJson, version: newVersion }, null, 2)}\n`,
148
+ );
149
+
150
+ // ---------------------------------------------------------------------------
151
+ // Derive change descriptions from git log between production and staging
152
+ // ---------------------------------------------------------------------------
153
+ const logOutput = gitRead('log', 'origin/production..HEAD', '--pretty=format:%s');
154
+ const changeLines = logOutput
155
+ ? logOutput
156
+ .split('\n')
157
+ .map((s) => s.trim())
158
+ .filter((s) => s.length > 0)
159
+ : [];
160
+
161
+ const changeBody =
162
+ changeLines.length > 0
163
+ ? changeLines.map((c) => `- ${c.length > 98 ? `${c.slice(0, 95)}...` : c}`).join('\n')
164
+ : `${bumpType} release`;
165
+
166
+ // ---------------------------------------------------------------------------
167
+ // Update CHANGELOG.md
168
+ // ---------------------------------------------------------------------------
169
+ const changelogPath = join(process.cwd(), 'CHANGELOG.md');
170
+ const today = new Date().toISOString().slice(0, 10);
171
+ insertChangelog(changelogPath, `## [${newVersion}] - ${today}\n\n${changeBody}\n`);
172
+
173
+ // ---------------------------------------------------------------------------
174
+ // Fix formatting if available
175
+ // ---------------------------------------------------------------------------
176
+ try {
177
+ runScript('fix');
178
+ } catch {
179
+ // fix may not exist in all packages
180
+ }
181
+
182
+ // ---------------------------------------------------------------------------
183
+ // Commit version bump on staging
184
+ // ---------------------------------------------------------------------------
185
+ git('add', '-A');
186
+ git('commit', '-m', `chore(release): ${packageName}@${newVersion}`, '-m', changeBody);
187
+
188
+ // ---------------------------------------------------------------------------
189
+ // Merge staging → production
190
+ // ---------------------------------------------------------------------------
191
+ console.log('\nMerging staging into production...');
192
+ git('checkout', 'production');
193
+ git('pull', '--ff-only', 'origin', 'production');
194
+ git('merge', '--ff-only', 'staging');
195
+ git('push', 'origin', 'production');
196
+
197
+ // ---------------------------------------------------------------------------
198
+ // Bring version commit back to main
199
+ // ---------------------------------------------------------------------------
200
+ console.log('\nSyncing version commit back to main...');
201
+ git('checkout', 'main');
202
+ git('pull', '--ff-only', 'origin', 'main');
203
+ git('merge', '--ff-only', 'production');
204
+ git('push', 'origin', 'main');
205
+
206
+ // ---------------------------------------------------------------------------
207
+ // Sync staging with main so the next flow-release can ff-merge cleanly
208
+ // ---------------------------------------------------------------------------
209
+ git('checkout', 'staging');
210
+ git('merge', '--ff-only', 'main');
211
+ git('push', 'origin', 'staging');
212
+ git('checkout', 'main');
213
+
214
+ console.log(`\n✅ Shipped ${packageName}@${newVersion} to production`);
215
+ console.log('You are on main and ready to keep working.');