@regardio/dev 1.24.0 → 2.0.1
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/README.md +2 -2
- package/dist/bin/ship/hotfix.bin.mjs +140 -0
- package/dist/bin/ship/production.bin.mjs +120 -0
- package/dist/bin/ship/staging.bin.mjs +70 -0
- package/dist/bin/ship/utils-BQ-JZ2D5.mjs +45 -0
- package/dist/playwright/index.d.mts +24 -0
- package/dist/playwright/index.mjs +61 -0
- package/dist/vitest/node.d.mts +22 -0
- package/dist/vitest/node.mjs +28 -0
- package/dist/vitest/react.d.mts +22 -0
- package/dist/vitest/react.mjs +28 -0
- package/docs/en/README.md +95 -0
- package/docs/en/agents.md +57 -0
- package/docs/en/standards/api.md +324 -0
- package/docs/en/standards/coding.md +144 -0
- package/docs/en/standards/commits.md +111 -0
- package/docs/en/standards/documentation.md +173 -0
- package/docs/en/standards/naming.md +180 -0
- package/docs/en/standards/principles.md +84 -0
- package/docs/en/standards/react.md +246 -0
- package/docs/en/standards/sql.md +258 -0
- package/docs/en/standards/testing.md +139 -0
- package/docs/en/standards/writing.md +119 -0
- package/docs/en/tools/biome.md +89 -0
- package/docs/en/tools/commitlint.md +92 -0
- package/docs/en/tools/dependencies.md +116 -0
- package/docs/en/tools/husky.md +90 -0
- package/docs/en/tools/markdownlint.md +84 -0
- package/docs/en/tools/playwright.md +117 -0
- package/docs/en/tools/releases.md +242 -0
- package/docs/en/tools/typescript.md +89 -0
- package/docs/en/tools/vitest.md +146 -0
- package/package.json +58 -70
- package/src/biome/preset.json +3 -0
- package/templates/changeset/README.md +14 -0
- package/templates/changeset/config.json +11 -0
- package/templates/github/release.yml +77 -0
- package/dist/bin/exec/clean.d.ts +0 -3
- package/dist/bin/exec/clean.d.ts.map +0 -1
- package/dist/bin/exec/clean.js +0 -25
- package/dist/bin/exec/clean.test.d.ts +0 -2
- package/dist/bin/exec/clean.test.d.ts.map +0 -1
- package/dist/bin/exec/clean.test.js +0 -45
- package/dist/bin/exec/husky.d.ts +0 -3
- package/dist/bin/exec/husky.d.ts.map +0 -1
- package/dist/bin/exec/husky.js +0 -9
- package/dist/bin/exec/p.d.ts +0 -3
- package/dist/bin/exec/p.d.ts.map +0 -1
- package/dist/bin/exec/p.js +0 -8
- package/dist/bin/exec/s.d.ts +0 -3
- package/dist/bin/exec/s.d.ts.map +0 -1
- package/dist/bin/exec/s.js +0 -8
- package/dist/bin/exec/tsc.d.ts +0 -3
- package/dist/bin/exec/tsc.d.ts.map +0 -1
- package/dist/bin/exec/tsc.js +0 -8
- package/dist/bin/lint/biome.d.ts +0 -3
- package/dist/bin/lint/biome.d.ts.map +0 -1
- package/dist/bin/lint/biome.js +0 -8
- package/dist/bin/lint/commit.d.ts +0 -3
- package/dist/bin/lint/commit.d.ts.map +0 -1
- package/dist/bin/lint/commit.js +0 -8
- package/dist/bin/lint/md.d.ts +0 -3
- package/dist/bin/lint/md.d.ts.map +0 -1
- package/dist/bin/lint/md.js +0 -16
- package/dist/bin/lint/package.d.ts +0 -4
- package/dist/bin/lint/package.d.ts.map +0 -1
- package/dist/bin/lint/package.js +0 -81
- package/dist/bin/lint/package.test.d.ts +0 -2
- package/dist/bin/lint/package.test.d.ts.map +0 -1
- package/dist/bin/lint/package.test.js +0 -65
- package/dist/bin/ship/hotfix.d.ts +0 -3
- package/dist/bin/ship/hotfix.d.ts.map +0 -1
- package/dist/bin/ship/hotfix.js +0 -141
- package/dist/bin/ship/production.d.ts +0 -3
- package/dist/bin/ship/production.d.ts.map +0 -1
- package/dist/bin/ship/production.js +0 -124
- package/dist/bin/ship/staging.d.ts +0 -3
- package/dist/bin/ship/staging.d.ts.map +0 -1
- package/dist/bin/ship/staging.js +0 -51
- package/dist/bin/ship/utils.d.ts +0 -9
- package/dist/bin/ship/utils.d.ts.map +0 -1
- package/dist/bin/ship/utils.js +0 -63
- package/dist/bin/ship/utils.test.d.ts +0 -2
- package/dist/bin/ship/utils.test.d.ts.map +0 -1
- package/dist/bin/ship/utils.test.js +0 -127
- package/dist/config.test.d.ts +0 -2
- package/dist/config.test.d.ts.map +0 -1
- package/dist/config.test.js +0 -101
- package/dist/playwright/index.d.ts +0 -10
- package/dist/playwright/index.d.ts.map +0 -1
- package/dist/playwright/index.js +0 -42
- package/dist/playwright/index.test.d.ts +0 -2
- package/dist/playwright/index.test.d.ts.map +0 -1
- package/dist/playwright/index.test.js +0 -55
- package/dist/testing/setup-react.d.ts +0 -2
- package/dist/testing/setup-react.d.ts.map +0 -1
- package/dist/testing/setup-react.js +0 -1
- package/dist/vitest/node.d.ts +0 -22
- package/dist/vitest/node.d.ts.map +0 -1
- package/dist/vitest/node.js +0 -16
- package/dist/vitest/react.d.ts +0 -17
- package/dist/vitest/react.d.ts.map +0 -1
- package/dist/vitest/react.js +0 -12
- package/src/bin/exec/clean.test.ts +0 -63
- package/src/bin/exec/clean.ts +0 -36
- package/src/bin/exec/husky.ts +0 -14
- package/src/bin/exec/p.ts +0 -13
- package/src/bin/exec/s.ts +0 -13
- package/src/bin/exec/tsc.ts +0 -13
- package/src/bin/lint/biome.ts +0 -13
- package/src/bin/lint/commit.ts +0 -13
- package/src/bin/lint/md.ts +0 -28
- package/src/bin/lint/package.test.ts +0 -83
- package/src/bin/lint/package.ts +0 -108
- package/src/bin/ship/hotfix.ts +0 -241
- package/src/bin/ship/production.ts +0 -240
- package/src/bin/ship/staging.ts +0 -108
- package/src/bin/ship/utils.test.ts +0 -178
- package/src/bin/ship/utils.ts +0 -109
- package/src/config.test.ts +0 -129
- package/src/markdownlint/markdownlint-cli2.jsonc +0 -9
- package/src/playwright/index.test.ts +0 -73
- package/src/playwright/index.ts +0 -63
- package/src/templates/release.yml +0 -128
- package/src/testing/setup-react.ts +0 -8
- package/src/vitest/node.ts +0 -25
- package/src/vitest/react.ts +0 -19
- /package/{src → templates}/sqlfluff/setup.cfg +0 -0
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* ship-production: Version and promote main to production following the GitLab workflow.
|
|
4
|
-
*
|
|
5
|
-
* Usage: ship-production <patch|minor|major>
|
|
6
|
-
*
|
|
7
|
-
* GitLab workflow:
|
|
8
|
-
* main → (version bump commit) → production → staging → main
|
|
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 main is ahead of production (there is something to ship)
|
|
16
|
-
* 3. Runs quality checks on main
|
|
17
|
-
* 4. Bumps the version in package.json
|
|
18
|
-
* 5. Collects change descriptions from git log between production and main
|
|
19
|
-
* 6. Updates CHANGELOG.md
|
|
20
|
-
* 7. Commits the version bump on main
|
|
21
|
-
* 8. Merges main into production (fast-forward) and pushes
|
|
22
|
-
* 9. Merges production into staging to keep it in sync
|
|
23
|
-
* 10. Merges production back into main to ensure consistency
|
|
24
|
-
* 11. Returns to main
|
|
25
|
-
*/
|
|
26
|
-
import { execSync } from 'node:child_process';
|
|
27
|
-
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
28
|
-
import { join } from 'node:path';
|
|
29
|
-
|
|
30
|
-
import {
|
|
31
|
-
branchExists,
|
|
32
|
-
bumpVersion,
|
|
33
|
-
confirm,
|
|
34
|
-
git,
|
|
35
|
-
gitRead,
|
|
36
|
-
insertChangelog,
|
|
37
|
-
runQualityChecks,
|
|
38
|
-
runScript,
|
|
39
|
-
} from './utils.js';
|
|
40
|
-
|
|
41
|
-
const args = process.argv.slice(2);
|
|
42
|
-
const bumpType = args[0];
|
|
43
|
-
|
|
44
|
-
if (!bumpType || !['patch', 'minor', 'major'].includes(bumpType)) {
|
|
45
|
-
console.error('Usage: ship-production <patch|minor|major>');
|
|
46
|
-
console.error('Example: ship-production minor');
|
|
47
|
-
process.exit(1);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// ---------------------------------------------------------------------------
|
|
51
|
-
// Guard: must be on main
|
|
52
|
-
// ---------------------------------------------------------------------------
|
|
53
|
-
const currentBranch = gitRead('branch', '--show-current');
|
|
54
|
-
if (currentBranch !== 'main') {
|
|
55
|
-
console.error(`Must be on the main branch to ship. Currently on: ${currentBranch}`);
|
|
56
|
-
process.exit(1);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Guard: working tree must be clean
|
|
60
|
-
const status = gitRead('status', '--porcelain');
|
|
61
|
-
if (status) {
|
|
62
|
-
console.error('Working directory has uncommitted changes. Commit or stash them first.');
|
|
63
|
-
process.exit(1);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// ---------------------------------------------------------------------------
|
|
67
|
-
// Fetch and verify both branches exist
|
|
68
|
-
// ---------------------------------------------------------------------------
|
|
69
|
-
console.log('\nFetching latest state from origin...');
|
|
70
|
-
git('fetch', 'origin');
|
|
71
|
-
|
|
72
|
-
if (!branchExists('staging')) {
|
|
73
|
-
console.error(
|
|
74
|
-
'Branch "staging" does not exist. Create it first:\n'
|
|
75
|
-
+ ' git checkout -b staging && git push -u origin staging',
|
|
76
|
-
);
|
|
77
|
-
process.exit(1);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (!branchExists('production')) {
|
|
81
|
-
console.error(
|
|
82
|
-
'Branch "production" does not exist. Create it first:\n'
|
|
83
|
-
+ ' git checkout -b production && git push -u origin production',
|
|
84
|
-
);
|
|
85
|
-
process.exit(1);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// ---------------------------------------------------------------------------
|
|
89
|
-
// Verify main has commits not yet in production
|
|
90
|
-
// ---------------------------------------------------------------------------
|
|
91
|
-
git('pull', '--ff-only', 'origin', 'main');
|
|
92
|
-
const ahead = gitRead('log', 'origin/production..HEAD', '--oneline');
|
|
93
|
-
if (!ahead) {
|
|
94
|
-
console.error('main is already in sync with production. Nothing to ship.');
|
|
95
|
-
process.exit(1);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
console.log('\nCommits to be shipped to production:');
|
|
99
|
-
console.log(ahead);
|
|
100
|
-
|
|
101
|
-
// ---------------------------------------------------------------------------
|
|
102
|
-
// Read package name for the confirmation prompt
|
|
103
|
-
// ---------------------------------------------------------------------------
|
|
104
|
-
const packageJsonPath = join(process.cwd(), 'package.json');
|
|
105
|
-
if (!existsSync(packageJsonPath)) {
|
|
106
|
-
console.error('No package.json found in current directory.');
|
|
107
|
-
process.exit(1);
|
|
108
|
-
}
|
|
109
|
-
const { name: packageName } = JSON.parse(readFileSync(packageJsonPath, 'utf-8')) as {
|
|
110
|
-
name: string;
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
if (!confirm(`\nShip ${packageName} as a ${bumpType} release?`)) {
|
|
114
|
-
console.log('Aborted.');
|
|
115
|
-
process.exit(0);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// ---------------------------------------------------------------------------
|
|
119
|
-
// Quality checks on main
|
|
120
|
-
// ---------------------------------------------------------------------------
|
|
121
|
-
console.log('\nRunning quality checks on main...');
|
|
122
|
-
try {
|
|
123
|
-
runQualityChecks();
|
|
124
|
-
} catch {
|
|
125
|
-
console.error('\nQuality checks failed on main. Fix issues before shipping.');
|
|
126
|
-
process.exit(1);
|
|
127
|
-
}
|
|
128
|
-
console.log('✅ Quality checks passed');
|
|
129
|
-
|
|
130
|
-
// ---------------------------------------------------------------------------
|
|
131
|
-
// Read version from main package.json and bump
|
|
132
|
-
// ---------------------------------------------------------------------------
|
|
133
|
-
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')) as {
|
|
134
|
-
name: string;
|
|
135
|
-
version: string;
|
|
136
|
-
};
|
|
137
|
-
const oldVersion = packageJson.version;
|
|
138
|
-
const newVersion = bumpVersion(oldVersion, bumpType);
|
|
139
|
-
|
|
140
|
-
console.log(`\nBumping ${packageName}: ${oldVersion} → ${newVersion}`);
|
|
141
|
-
|
|
142
|
-
writeFileSync(
|
|
143
|
-
packageJsonPath,
|
|
144
|
-
`${JSON.stringify({ ...packageJson, version: newVersion }, null, 2)}\n`,
|
|
145
|
-
);
|
|
146
|
-
|
|
147
|
-
// ---------------------------------------------------------------------------
|
|
148
|
-
// Derive change descriptions from git log between production and staging
|
|
149
|
-
// ---------------------------------------------------------------------------
|
|
150
|
-
const logOutput = gitRead('log', 'origin/production..HEAD', '--pretty=format:%s');
|
|
151
|
-
const changeLines = logOutput
|
|
152
|
-
? logOutput
|
|
153
|
-
.split('\n')
|
|
154
|
-
.map((s) => s.trim())
|
|
155
|
-
.filter((s) => s.length > 0)
|
|
156
|
-
: [];
|
|
157
|
-
|
|
158
|
-
const changeBody =
|
|
159
|
-
changeLines.length > 0
|
|
160
|
-
? changeLines.map((c) => `- ${c.length > 98 ? `${c.slice(0, 95)}...` : c}`).join('\n')
|
|
161
|
-
: `${bumpType} release`;
|
|
162
|
-
|
|
163
|
-
// ---------------------------------------------------------------------------
|
|
164
|
-
// Update CHANGELOG.md
|
|
165
|
-
// ---------------------------------------------------------------------------
|
|
166
|
-
const changelogPath = join(process.cwd(), 'CHANGELOG.md');
|
|
167
|
-
const today = new Date().toISOString().slice(0, 10);
|
|
168
|
-
insertChangelog(changelogPath, `## [${newVersion}] - ${today}\n\n${changeBody}\n`);
|
|
169
|
-
|
|
170
|
-
// ---------------------------------------------------------------------------
|
|
171
|
-
// Fix formatting of modified files (package.json, CHANGELOG.md)
|
|
172
|
-
// ---------------------------------------------------------------------------
|
|
173
|
-
try {
|
|
174
|
-
runScript('fix:pkg');
|
|
175
|
-
} catch {
|
|
176
|
-
// fix:pkg may not exist
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Format modified files
|
|
180
|
-
try {
|
|
181
|
-
git('add', '-A');
|
|
182
|
-
const changedFiles = gitRead('diff', '--cached', '--name-only').split('\n').filter(Boolean);
|
|
183
|
-
|
|
184
|
-
// Format JSON files with biome
|
|
185
|
-
for (const file of changedFiles) {
|
|
186
|
-
if (file.endsWith('.json')) {
|
|
187
|
-
try {
|
|
188
|
-
execSync(`npx biome check --write ${file}`, { cwd: process.cwd(), stdio: 'inherit' });
|
|
189
|
-
} catch {
|
|
190
|
-
// File might not need formatting or biome not available
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Format markdown files with markdownlint
|
|
196
|
-
for (const file of changedFiles) {
|
|
197
|
-
if (file.endsWith('.md')) {
|
|
198
|
-
try {
|
|
199
|
-
execSync(`npx markdownlint-cli2 --fix ${file}`, { cwd: process.cwd(), stdio: 'inherit' });
|
|
200
|
-
} catch {
|
|
201
|
-
// File might not need formatting or markdownlint not available
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
} catch {
|
|
206
|
-
// Formatters not available
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// ---------------------------------------------------------------------------
|
|
210
|
-
// Commit version bump on main
|
|
211
|
-
// ---------------------------------------------------------------------------
|
|
212
|
-
git('add', '-A');
|
|
213
|
-
git('commit', '-m', `chore(release): ${packageName}@${newVersion}`, '-m', changeBody);
|
|
214
|
-
|
|
215
|
-
// ---------------------------------------------------------------------------
|
|
216
|
-
// Merge main → production
|
|
217
|
-
// ---------------------------------------------------------------------------
|
|
218
|
-
console.log('\nMerging main into production...');
|
|
219
|
-
git('checkout', 'production');
|
|
220
|
-
git('pull', '--ff-only', 'origin', 'production');
|
|
221
|
-
git('merge', '--ff-only', 'main');
|
|
222
|
-
git('push', 'origin', 'production');
|
|
223
|
-
|
|
224
|
-
// ---------------------------------------------------------------------------
|
|
225
|
-
// Sync staging with production
|
|
226
|
-
// ---------------------------------------------------------------------------
|
|
227
|
-
console.log('\nSyncing staging with production...');
|
|
228
|
-
git('checkout', 'staging');
|
|
229
|
-
git('pull', '--ff-only', 'origin', 'staging');
|
|
230
|
-
git('merge', '--ff-only', 'production');
|
|
231
|
-
git('push', 'origin', 'staging');
|
|
232
|
-
|
|
233
|
-
// ---------------------------------------------------------------------------
|
|
234
|
-
// Return to main and push
|
|
235
|
-
// ---------------------------------------------------------------------------
|
|
236
|
-
git('checkout', 'main');
|
|
237
|
-
git('push', 'origin', 'main');
|
|
238
|
-
|
|
239
|
-
console.log(`\n✅ Shipped ${packageName}@${newVersion} to production`);
|
|
240
|
-
console.log('You are on main and ready to keep working.');
|
package/src/bin/ship/staging.ts
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* ship-staging: Deploy changes to staging following the GitLab workflow.
|
|
4
|
-
*
|
|
5
|
-
* Usage: ship-staging [message]
|
|
6
|
-
*
|
|
7
|
-
* GitLab workflow:
|
|
8
|
-
* main → staging (staging deploy, no version bump yet)
|
|
9
|
-
*
|
|
10
|
-
* This script is OPTIONAL. You can ship directly to production using ship-production,
|
|
11
|
-
* which will automatically sync staging afterward.
|
|
12
|
-
*
|
|
13
|
-
* Use ship-staging when you want to test changes in a staging environment before
|
|
14
|
-
* shipping to production. Otherwise, skip this step and use ship-production directly.
|
|
15
|
-
*
|
|
16
|
-
* Versioning happens at ship time (ship-production), not here.
|
|
17
|
-
* This keeps version numbers reserved for production-verified code.
|
|
18
|
-
*
|
|
19
|
-
* This script:
|
|
20
|
-
* 1. Ensures the current branch is main and the working tree is clean
|
|
21
|
-
* 2. Pulls latest main from origin
|
|
22
|
-
* 3. Runs quality checks locally (build, lint, typecheck, tests)
|
|
23
|
-
* 4. Merges main into staging (fast-forward) and pushes
|
|
24
|
-
* 5. Pushes main and returns so work can continue
|
|
25
|
-
*/
|
|
26
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
27
|
-
import { join } from 'node:path';
|
|
28
|
-
|
|
29
|
-
import { branchExists, git, gitRead, runQualityChecks } from './utils.js';
|
|
30
|
-
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
|
-
// Guard: must be on main
|
|
33
|
-
// ---------------------------------------------------------------------------
|
|
34
|
-
const currentBranch = gitRead('branch', '--show-current');
|
|
35
|
-
if (currentBranch !== 'main') {
|
|
36
|
-
console.error(`Must be on the main branch to release. Currently on: ${currentBranch}`);
|
|
37
|
-
process.exit(1);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Guard: working tree must be clean
|
|
41
|
-
const status = gitRead('status', '--porcelain');
|
|
42
|
-
if (status) {
|
|
43
|
-
console.error('Working directory has uncommitted changes. Commit or stash them first.');
|
|
44
|
-
process.exit(1);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// ---------------------------------------------------------------------------
|
|
48
|
-
// Sync with origin before doing anything
|
|
49
|
-
// ---------------------------------------------------------------------------
|
|
50
|
-
console.log('\nFetching latest state from origin...');
|
|
51
|
-
git('fetch', 'origin');
|
|
52
|
-
git('pull', '--ff-only', 'origin', 'main');
|
|
53
|
-
|
|
54
|
-
// ---------------------------------------------------------------------------
|
|
55
|
-
// Read package.json
|
|
56
|
-
// ---------------------------------------------------------------------------
|
|
57
|
-
const packageJsonPath = join(process.cwd(), 'package.json');
|
|
58
|
-
if (!existsSync(packageJsonPath)) {
|
|
59
|
-
console.error('No package.json found in current directory.');
|
|
60
|
-
process.exit(1);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')) as { name: string };
|
|
64
|
-
const packageName = packageJson.name;
|
|
65
|
-
|
|
66
|
-
if (!packageName) {
|
|
67
|
-
console.error('No "name" field found in package.json.');
|
|
68
|
-
process.exit(1);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Guard: staging branch must exist
|
|
72
|
-
if (!branchExists('staging')) {
|
|
73
|
-
console.error(
|
|
74
|
-
'Branch "staging" does not exist locally or on origin. Create it first:\n'
|
|
75
|
-
+ ' git checkout -b staging && git push -u origin staging',
|
|
76
|
-
);
|
|
77
|
-
process.exit(1);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// ---------------------------------------------------------------------------
|
|
81
|
-
// Quality checks — no broken code may be committed
|
|
82
|
-
// ---------------------------------------------------------------------------
|
|
83
|
-
console.log('\nRunning quality checks...');
|
|
84
|
-
try {
|
|
85
|
-
runQualityChecks();
|
|
86
|
-
} catch {
|
|
87
|
-
console.error('\nQuality checks failed. Fix all issues before releasing.');
|
|
88
|
-
process.exit(1);
|
|
89
|
-
}
|
|
90
|
-
console.log('✅ Quality checks passed');
|
|
91
|
-
|
|
92
|
-
// ---------------------------------------------------------------------------
|
|
93
|
-
// Merge into staging
|
|
94
|
-
// ---------------------------------------------------------------------------
|
|
95
|
-
console.log('\nMerging main into staging...');
|
|
96
|
-
git('checkout', 'staging');
|
|
97
|
-
git('merge', '--ff-only', 'main');
|
|
98
|
-
git('push', 'origin', 'staging');
|
|
99
|
-
|
|
100
|
-
// ---------------------------------------------------------------------------
|
|
101
|
-
// Return to main and sync
|
|
102
|
-
// ---------------------------------------------------------------------------
|
|
103
|
-
git('checkout', 'main');
|
|
104
|
-
git('push', 'origin', 'main');
|
|
105
|
-
|
|
106
|
-
console.log('\n✅ Changes deployed to staging');
|
|
107
|
-
console.log('Run ship-production <patch|minor|major> when ready to ship to production.');
|
|
108
|
-
console.log('(Or ship directly from main to production without using ship-staging first.)');
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { tmpdir } from 'node:os';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
-
|
|
6
|
-
import { bumpVersion, confirm, insertChangelog } from './utils.js';
|
|
7
|
-
|
|
8
|
-
// ---------------------------------------------------------------------------
|
|
9
|
-
// bumpVersion
|
|
10
|
-
// ---------------------------------------------------------------------------
|
|
11
|
-
|
|
12
|
-
describe('bumpVersion', () => {
|
|
13
|
-
it('bumps patch', () => {
|
|
14
|
-
expect(bumpVersion('1.2.3', 'patch')).toBe('1.2.4');
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('bumps minor and resets patch', () => {
|
|
18
|
-
expect(bumpVersion('1.2.3', 'minor')).toBe('1.3.0');
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('bumps major and resets minor + patch', () => {
|
|
22
|
-
expect(bumpVersion('1.2.3', 'major')).toBe('2.0.0');
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('handles zero components', () => {
|
|
26
|
-
expect(bumpVersion('0.0.0', 'patch')).toBe('0.0.1');
|
|
27
|
-
expect(bumpVersion('0.0.0', 'minor')).toBe('0.1.0');
|
|
28
|
-
expect(bumpVersion('0.0.0', 'major')).toBe('1.0.0');
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('defaults to patch for unknown bump type', () => {
|
|
32
|
-
expect(bumpVersion('1.2.3', 'unknown')).toBe('1.2.4');
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('throws for invalid semver', () => {
|
|
36
|
-
expect(() => bumpVersion('not-semver', 'patch')).toThrow('Invalid semver');
|
|
37
|
-
expect(() => bumpVersion('1.2', 'patch')).toThrow('Invalid semver');
|
|
38
|
-
expect(() => bumpVersion('1.2.x', 'patch')).toThrow('Invalid semver');
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
// ---------------------------------------------------------------------------
|
|
43
|
-
// insertChangelog
|
|
44
|
-
// ---------------------------------------------------------------------------
|
|
45
|
-
|
|
46
|
-
describe('insertChangelog', () => {
|
|
47
|
-
let tmpDir: string;
|
|
48
|
-
let changelogPath: string;
|
|
49
|
-
|
|
50
|
-
beforeEach(() => {
|
|
51
|
-
tmpDir = join(tmpdir(), `flow-utils-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
52
|
-
mkdirSync(tmpDir, { recursive: true });
|
|
53
|
-
changelogPath = join(tmpDir, 'CHANGELOG.md');
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
afterEach(() => {
|
|
57
|
-
rmSync(tmpDir, { force: true, recursive: true });
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('creates a new file when none exists', () => {
|
|
61
|
-
insertChangelog(changelogPath, '## [1.0.0] - 2025-01-01\n\n- initial release\n');
|
|
62
|
-
|
|
63
|
-
expect(existsSync(changelogPath)).toBe(true);
|
|
64
|
-
const content = readFileSync(changelogPath, 'utf-8');
|
|
65
|
-
expect(content).toContain('# Changelog');
|
|
66
|
-
expect(content).toContain('## [1.0.0] - 2025-01-01');
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('inserts before the first existing ## section', () => {
|
|
70
|
-
writeFileSync(changelogPath, '# Changelog\n\n## [1.0.0] - 2025-01-01\n\n- old entry\n');
|
|
71
|
-
|
|
72
|
-
insertChangelog(changelogPath, '## [1.1.0] - 2025-02-01\n\n- new entry\n');
|
|
73
|
-
|
|
74
|
-
const content = readFileSync(changelogPath, 'utf-8');
|
|
75
|
-
const newIdx = content.indexOf('## [1.1.0]');
|
|
76
|
-
const oldIdx = content.indexOf('## [1.0.0]');
|
|
77
|
-
expect(newIdx).toBeLessThan(oldIdx);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('appends when no ## section exists yet', () => {
|
|
81
|
-
writeFileSync(changelogPath, '# Changelog\n');
|
|
82
|
-
|
|
83
|
-
insertChangelog(changelogPath, '## [1.0.0] - 2025-01-01\n\n- initial\n');
|
|
84
|
-
|
|
85
|
-
const content = readFileSync(changelogPath, 'utf-8');
|
|
86
|
-
expect(content).toContain('## [1.0.0]');
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('preserves existing entries when inserting', () => {
|
|
90
|
-
writeFileSync(changelogPath, '# Changelog\n\n## [1.0.0] - 2025-01-01\n\n- old entry\n');
|
|
91
|
-
|
|
92
|
-
insertChangelog(changelogPath, '## [1.1.0] - 2025-02-01\n\n- new entry\n');
|
|
93
|
-
|
|
94
|
-
const content = readFileSync(changelogPath, 'utf-8');
|
|
95
|
-
expect(content).toContain('## [1.0.0]');
|
|
96
|
-
expect(content).toContain('- old entry');
|
|
97
|
-
expect(content).toContain('## [1.1.0]');
|
|
98
|
-
expect(content).toContain('- new entry');
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('appends when file exists but has no title line', () => {
|
|
102
|
-
writeFileSync(changelogPath, '## [1.0.0] - 2025-01-01\n\n- old entry\n');
|
|
103
|
-
|
|
104
|
-
insertChangelog(changelogPath, '## [1.1.0] - 2025-02-01\n\n- new entry\n');
|
|
105
|
-
|
|
106
|
-
const content = readFileSync(changelogPath, 'utf-8');
|
|
107
|
-
expect(content).toContain('## [1.0.0]');
|
|
108
|
-
expect(content).toContain('## [1.1.0]');
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('handles multiple existing entries in the correct order', () => {
|
|
112
|
-
writeFileSync(
|
|
113
|
-
changelogPath,
|
|
114
|
-
'# Changelog\n\n## [1.1.0] - 2025-02-01\n\n- second\n\n## [1.0.0] - 2025-01-01\n\n- first\n',
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
insertChangelog(changelogPath, '## [1.2.0] - 2025-03-01\n\n- third\n');
|
|
118
|
-
|
|
119
|
-
const content = readFileSync(changelogPath, 'utf-8');
|
|
120
|
-
const idx120 = content.indexOf('## [1.2.0]');
|
|
121
|
-
const idx110 = content.indexOf('## [1.1.0]');
|
|
122
|
-
const idx100 = content.indexOf('## [1.0.0]');
|
|
123
|
-
expect(idx120).toBeLessThan(idx110);
|
|
124
|
-
expect(idx110).toBeLessThan(idx100);
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
// ---------------------------------------------------------------------------
|
|
129
|
-
// confirm
|
|
130
|
-
// ---------------------------------------------------------------------------
|
|
131
|
-
|
|
132
|
-
describe('confirm', () => {
|
|
133
|
-
let tmpDir: string;
|
|
134
|
-
let inputFile: string;
|
|
135
|
-
|
|
136
|
-
beforeEach(() => {
|
|
137
|
-
tmpDir = join(tmpdir(), `confirm-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
138
|
-
mkdirSync(tmpDir, { recursive: true });
|
|
139
|
-
inputFile = join(tmpDir, 'input');
|
|
140
|
-
vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
afterEach(() => {
|
|
144
|
-
rmSync(tmpDir, { force: true, recursive: true });
|
|
145
|
-
vi.restoreAllMocks();
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it('returns true for "y"', () => {
|
|
149
|
-
writeFileSync(inputFile, 'y\n');
|
|
150
|
-
expect(confirm('Continue?', inputFile)).toBe(true);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it('returns true for "Y"', () => {
|
|
154
|
-
writeFileSync(inputFile, 'Y\n');
|
|
155
|
-
expect(confirm('Continue?', inputFile)).toBe(true);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it('returns false for "n"', () => {
|
|
159
|
-
writeFileSync(inputFile, 'n\n');
|
|
160
|
-
expect(confirm('Continue?', inputFile)).toBe(false);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
it('returns false for empty input', () => {
|
|
164
|
-
writeFileSync(inputFile, '\n');
|
|
165
|
-
expect(confirm('Continue?', inputFile)).toBe(false);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it('returns false for any other input', () => {
|
|
169
|
-
writeFileSync(inputFile, 'yes\n');
|
|
170
|
-
expect(confirm('Continue?', inputFile)).toBe(false);
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
it('writes the prompt to stdout', () => {
|
|
174
|
-
writeFileSync(inputFile, 'y\n');
|
|
175
|
-
confirm('Ship it?', inputFile);
|
|
176
|
-
expect(process.stdout.write).toHaveBeenCalledWith('Ship it? (y/N) ');
|
|
177
|
-
});
|
|
178
|
-
});
|
package/src/bin/ship/utils.ts
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared utilities for ship-staging, ship-production, and ship-hotfix.
|
|
3
|
-
*
|
|
4
|
-
* Git commands use execFileSync (not a shell string) so user-provided
|
|
5
|
-
* strings such as commit messages are never interpolated by the shell.
|
|
6
|
-
* pnpm script invocations use execSync via shell since script names are
|
|
7
|
-
* developer-controlled and pnpm itself is resolved through PATH.
|
|
8
|
-
*/
|
|
9
|
-
import { execFileSync, execSync } from 'node:child_process';
|
|
10
|
-
import { closeSync, existsSync, openSync, readFileSync, readSync, writeFileSync } from 'node:fs';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Run a git command with the given arguments.
|
|
14
|
-
* Output is inherited (visible to the user).
|
|
15
|
-
*/
|
|
16
|
-
export const git = (...args: string[]): void => {
|
|
17
|
-
console.log(`$ git ${args.join(' ')}`);
|
|
18
|
-
execFileSync('git', args, { stdio: 'inherit' });
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Run a git command and return trimmed stdout.
|
|
23
|
-
* Stderr is suppressed; throws on non-zero exit.
|
|
24
|
-
*/
|
|
25
|
-
export const gitRead = (...args: string[]): string =>
|
|
26
|
-
execFileSync('git', args, { encoding: 'utf-8' }).trim();
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Run a pnpm script via the shell.
|
|
30
|
-
* Only pass developer-controlled script names — never user input.
|
|
31
|
-
*/
|
|
32
|
-
export const runScript = (script: string): void => {
|
|
33
|
-
console.log(`$ pnpm ${script}`);
|
|
34
|
-
execSync(`pnpm ${script}`, { stdio: 'inherit' });
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Bump a semver string by the given increment type.
|
|
39
|
-
*/
|
|
40
|
-
export const bumpVersion = (current: string, bump: string): string => {
|
|
41
|
-
const parts = current.split('.').map(Number);
|
|
42
|
-
if (parts.length !== 3 || parts.some(Number.isNaN)) {
|
|
43
|
-
throw new Error(`Invalid semver: ${current}`);
|
|
44
|
-
}
|
|
45
|
-
const [major, minor, patch] = parts as [number, number, number];
|
|
46
|
-
if (bump === 'major') return `${major + 1}.0.0`;
|
|
47
|
-
if (bump === 'minor') return `${major}.${minor + 1}.0`;
|
|
48
|
-
return `${major}.${minor}.${patch + 1}`;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Insert a new changelog entry after the title line of CHANGELOG.md.
|
|
53
|
-
* Creates the file if it does not exist.
|
|
54
|
-
*/
|
|
55
|
-
export const insertChangelog = (changelogPath: string, entry: string): void => {
|
|
56
|
-
if (existsSync(changelogPath)) {
|
|
57
|
-
const existing = readFileSync(changelogPath, 'utf-8');
|
|
58
|
-
const insertAt = existing.indexOf('\n## ');
|
|
59
|
-
if (insertAt === -1) {
|
|
60
|
-
writeFileSync(changelogPath, `${existing.trimEnd()}\n\n${entry}`);
|
|
61
|
-
} else {
|
|
62
|
-
writeFileSync(
|
|
63
|
-
changelogPath,
|
|
64
|
-
`${existing.slice(0, insertAt)}\n\n${entry}${existing.slice(insertAt + 1)}`,
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
} else {
|
|
68
|
-
writeFileSync(changelogPath, `# Changelog\n\n${entry}`);
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Run quality checks. Throws if any step fails.
|
|
74
|
-
*/
|
|
75
|
-
export const runQualityChecks = (): void => {
|
|
76
|
-
runScript('build');
|
|
77
|
-
runScript('typecheck');
|
|
78
|
-
runScript('test');
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Check whether a branch exists locally or on origin.
|
|
83
|
-
* Requires a prior `git fetch` to have up-to-date remote refs.
|
|
84
|
-
*/
|
|
85
|
-
export const branchExists = (name: string): boolean =>
|
|
86
|
-
gitRead('branch', '--list', name) !== ''
|
|
87
|
-
|| gitRead('branch', '--list', '--remotes', `origin/${name}`) !== '';
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Prompt the user for confirmation and return true only if they type "y" or "Y".
|
|
91
|
-
* Defaults to "no" on empty input, so pressing Enter alone aborts.
|
|
92
|
-
* The ttyPath parameter is injectable for testing.
|
|
93
|
-
*/
|
|
94
|
-
export const confirm = (prompt: string, ttyPath = '/dev/tty'): boolean => {
|
|
95
|
-
process.stdout.write(`${prompt} (y/N) `);
|
|
96
|
-
const buf = Buffer.alloc(1024);
|
|
97
|
-
let fd: number;
|
|
98
|
-
let shouldClose = false;
|
|
99
|
-
try {
|
|
100
|
-
fd = openSync(ttyPath, 'r');
|
|
101
|
-
shouldClose = true;
|
|
102
|
-
} catch {
|
|
103
|
-
fd = process.stdin.fd;
|
|
104
|
-
}
|
|
105
|
-
const bytesRead = readSync(fd, buf, 0, buf.length, null);
|
|
106
|
-
if (shouldClose) closeSync(fd);
|
|
107
|
-
const answer = buf.slice(0, bytesRead).toString().trim();
|
|
108
|
-
return answer === 'y' || answer === 'Y';
|
|
109
|
-
};
|