@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.
- package/README.md +3 -3
- package/dist/bin/exec/clean.d.ts +3 -0
- package/dist/bin/exec/clean.d.ts.map +1 -0
- package/dist/bin/exec/clean.js +25 -0
- package/dist/bin/exec/clean.test.d.ts +2 -0
- package/dist/bin/exec/clean.test.d.ts.map +1 -0
- package/dist/bin/exec/clean.test.js +45 -0
- package/dist/bin/exec/husky.d.ts +3 -0
- package/dist/bin/exec/husky.d.ts.map +1 -0
- package/dist/bin/exec/p.d.ts +3 -0
- package/dist/bin/exec/p.d.ts.map +1 -0
- package/dist/bin/exec/s.d.ts +3 -0
- package/dist/bin/exec/s.d.ts.map +1 -0
- package/dist/bin/exec/ts.d.ts +3 -0
- package/dist/bin/exec/ts.d.ts.map +1 -0
- package/dist/bin/exec/ts.js +36 -0
- package/dist/bin/exec/ts.test.d.ts +2 -0
- package/dist/bin/exec/ts.test.d.ts.map +1 -0
- package/dist/bin/exec/ts.test.js +39 -0
- package/dist/bin/exec/tsc.d.ts +3 -0
- package/dist/bin/exec/tsc.d.ts.map +1 -0
- package/dist/bin/flow/hotfix.d.ts +3 -0
- package/dist/bin/flow/hotfix.d.ts.map +1 -0
- package/dist/bin/flow/hotfix.js +116 -0
- package/dist/bin/flow/release.d.ts +3 -0
- package/dist/bin/flow/release.d.ts.map +1 -0
- package/dist/bin/flow/release.js +68 -0
- package/dist/bin/flow/ship.d.ts +3 -0
- package/dist/bin/flow/ship.d.ts.map +1 -0
- package/dist/bin/flow/ship.js +104 -0
- package/dist/bin/flow/utils.d.ts +9 -0
- package/dist/bin/flow/utils.d.ts.map +1 -0
- package/dist/bin/flow/utils.js +63 -0
- package/dist/bin/flow/utils.test.d.ts +2 -0
- package/dist/bin/flow/utils.test.d.ts.map +1 -0
- package/dist/bin/flow/utils.test.js +127 -0
- package/dist/bin/lint/biome.d.ts +3 -0
- package/dist/bin/lint/biome.d.ts.map +1 -0
- package/dist/bin/lint/commit.d.ts +3 -0
- package/dist/bin/lint/commit.d.ts.map +1 -0
- package/dist/bin/lint/md.d.ts +3 -0
- package/dist/bin/lint/md.d.ts.map +1 -0
- package/dist/bin/lint/package.d.ts +4 -0
- package/dist/bin/lint/package.d.ts.map +1 -0
- package/dist/bin/lint/package.js +81 -0
- package/dist/bin/lint/package.test.d.ts +2 -0
- package/dist/bin/lint/package.test.d.ts.map +1 -0
- package/dist/bin/lint/package.test.js +65 -0
- package/package.json +21 -22
- package/src/bin/exec/clean.test.ts +63 -0
- package/src/bin/exec/clean.ts +36 -0
- package/src/bin/exec/ts.test.ts +54 -0
- package/src/bin/exec/ts.ts +52 -0
- package/src/bin/flow/hotfix.ts +210 -0
- package/src/bin/flow/release.ts +130 -0
- package/src/bin/flow/ship.ts +215 -0
- package/src/bin/flow/utils.test.ts +178 -0
- package/src/bin/flow/utils.ts +109 -0
- package/src/bin/lint/package.test.ts +83 -0
- package/src/bin/lint/package.ts +108 -0
- package/src/templates/release.yml +23 -17
- 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 -18
- package/dist/bin/exec-husky.d.ts +0 -3
- package/dist/bin/exec-husky.d.ts.map +0 -1
- package/dist/bin/exec-p.d.ts +0 -3
- package/dist/bin/exec-p.d.ts.map +0 -1
- package/dist/bin/exec-s.d.ts +0 -3
- package/dist/bin/exec-s.d.ts.map +0 -1
- package/dist/bin/exec-ts.d.ts +0 -3
- package/dist/bin/exec-ts.d.ts.map +0 -1
- package/dist/bin/exec-ts.js +0 -28
- package/dist/bin/exec-tsc.d.ts +0 -3
- package/dist/bin/exec-tsc.d.ts.map +0 -1
- package/dist/bin/flow-changeset.d.ts +0 -3
- package/dist/bin/flow-changeset.d.ts.map +0 -1
- package/dist/bin/flow-changeset.js +0 -18
- package/dist/bin/flow-release.d.ts +0 -3
- package/dist/bin/flow-release.d.ts.map +0 -1
- package/dist/bin/flow-release.js +0 -115
- package/dist/bin/lint-biome.d.ts +0 -3
- package/dist/bin/lint-biome.d.ts.map +0 -1
- package/dist/bin/lint-commit.d.ts +0 -3
- package/dist/bin/lint-commit.d.ts.map +0 -1
- package/dist/bin/lint-md.d.ts +0 -3
- package/dist/bin/lint-md.d.ts.map +0 -1
- package/dist/bin/lint-package.d.ts +0 -3
- package/dist/bin/lint-package.d.ts.map +0 -1
- package/dist/bin/lint-package.js +0 -86
- 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 -111
- package/src/bin/exec-clean.ts +0 -24
- package/src/bin/exec-ts.ts +0 -39
- package/src/bin/flow-changeset.ts +0 -23
- package/src/bin/flow-release.ts +0 -185
- package/src/bin/lint-package.test.ts +0 -140
- package/src/bin/lint-package.ts +0 -114
- /package/dist/bin/{exec-husky.js → exec/husky.js} +0 -0
- /package/dist/bin/{exec-p.js → exec/p.js} +0 -0
- /package/dist/bin/{exec-s.js → exec/s.js} +0 -0
- /package/dist/bin/{exec-tsc.js → exec/tsc.js} +0 -0
- /package/dist/bin/{lint-biome.js → lint/biome.js} +0 -0
- /package/dist/bin/{lint-commit.js → lint/commit.js} +0 -0
- /package/dist/bin/{lint-md.js → lint/md.js} +0 -0
- /package/src/bin/{exec-husky.ts → exec/husky.ts} +0 -0
- /package/src/bin/{exec-p.ts → exec/p.ts} +0 -0
- /package/src/bin/{exec-s.ts → exec/s.ts} +0 -0
- /package/src/bin/{exec-tsc.ts → exec/tsc.ts} +0 -0
- /package/src/bin/{lint-biome.ts → lint/biome.ts} +0 -0
- /package/src/bin/{lint-commit.ts → lint/commit.ts} +0 -0
- /package/src/bin/{lint-md.ts → lint/md.ts} +0 -0
package/README.md
CHANGED
|
@@ -26,8 +26,8 @@ The goal is code that's correct, consistent, and a pleasure to work with.
|
|
|
26
26
|
| **Linting** | Biome, Markdownlint, Commitlint |
|
|
27
27
|
| **Testing** | Vitest, Playwright, Testing Library |
|
|
28
28
|
| **Build** | TypeScript, tsx, Vite |
|
|
29
|
-
| **Workflow** | Husky,
|
|
30
|
-
| **CLI utilities** | exec-clean, exec-p, exec-s, exec-ts, flow-release, lint-biome, lint-md, lint-package |
|
|
29
|
+
| **Workflow** | Husky, GitLab Flow |
|
|
30
|
+
| **CLI utilities** | exec-clean, exec-p, exec-s, exec-ts, flow-release, flow-ship, flow-hotfix, lint-biome, lint-md, lint-package |
|
|
31
31
|
|
|
32
32
|
## Quick Start
|
|
33
33
|
|
|
@@ -67,7 +67,7 @@ Detailed documentation is organized by topic:
|
|
|
67
67
|
- [Commitlint](./docs/toolchain/commitlint.md) - Commit message validation
|
|
68
68
|
- [Markdownlint](./docs/toolchain/markdownlint.md) - Markdown quality
|
|
69
69
|
- [Husky](./docs/toolchain/husky.md) - Git hooks
|
|
70
|
-
- [
|
|
70
|
+
- [Release Workflow](./docs/toolchain/release-workflow.md) - GitLab-flow-based versioning and releases
|
|
71
71
|
|
|
72
72
|
## Portability
|
|
73
73
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clean.d.ts","sourceRoot":"","sources":["../../../src/bin/exec/clean.ts"],"names":[],"mappings":";AAcA,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,GAAG,IAAI,CAQpE"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
export function resolveRimrafBin(require) {
|
|
7
|
+
const pkgPath = require.resolve('rimraf/package.json');
|
|
8
|
+
const pkg = require(pkgPath);
|
|
9
|
+
const binRel = typeof pkg.bin === 'string' ? pkg.bin : pkg.bin?.rimraf;
|
|
10
|
+
if (!binRel)
|
|
11
|
+
return null;
|
|
12
|
+
const normalized = binRel.startsWith('./') ? binRel.slice(2) : binRel;
|
|
13
|
+
return path.join(path.dirname(pkgPath), normalized);
|
|
14
|
+
}
|
|
15
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
16
|
+
const require = createRequire(import.meta.url);
|
|
17
|
+
const bin = resolveRimrafBin(require);
|
|
18
|
+
if (!bin) {
|
|
19
|
+
console.error('Unable to locate rimraf binary from package.json bin field');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
const args = process.argv.slice(2);
|
|
23
|
+
const child = spawn(process.execPath, [bin, ...args], { stdio: 'inherit' });
|
|
24
|
+
child.on('exit', (code) => process.exit(code ?? 0));
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clean.test.d.ts","sourceRoot":"","sources":["../../../src/bin/exec/clean.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
import { resolveRimrafBin } from './clean.js';
|
|
5
|
+
function makeRequire(pkgPath, pkg) {
|
|
6
|
+
const req = (id) => (id === pkgPath ? pkg : undefined);
|
|
7
|
+
req.resolve = (_id) => pkgPath;
|
|
8
|
+
return req;
|
|
9
|
+
}
|
|
10
|
+
describe('resolveRimrafBin', () => {
|
|
11
|
+
it('resolves when bin is a plain string', () => {
|
|
12
|
+
const pkgPath = '/node_modules/rimraf/package.json';
|
|
13
|
+
const req = makeRequire(pkgPath, { bin: './dist/esm/bin.js' });
|
|
14
|
+
const result = resolveRimrafBin(req);
|
|
15
|
+
expect(result).toBe(path.join('/node_modules/rimraf', 'dist/esm/bin.js'));
|
|
16
|
+
});
|
|
17
|
+
it('strips leading ./ from the bin path', () => {
|
|
18
|
+
const pkgPath = '/node_modules/rimraf/package.json';
|
|
19
|
+
const req = makeRequire(pkgPath, { bin: './bin/rimraf' });
|
|
20
|
+
const result = resolveRimrafBin(req);
|
|
21
|
+
expect(result).toBe(path.join('/node_modules/rimraf', 'bin/rimraf'));
|
|
22
|
+
});
|
|
23
|
+
it('resolves when bin is an object with a rimraf key', () => {
|
|
24
|
+
const pkgPath = '/node_modules/rimraf/package.json';
|
|
25
|
+
const req = makeRequire(pkgPath, { bin: { rimraf: './dist/esm/bin.js' } });
|
|
26
|
+
const result = resolveRimrafBin(req);
|
|
27
|
+
expect(result).toBe(path.join('/node_modules/rimraf', 'dist/esm/bin.js'));
|
|
28
|
+
});
|
|
29
|
+
it('returns null when bin field is missing', () => {
|
|
30
|
+
const pkgPath = '/node_modules/rimraf/package.json';
|
|
31
|
+
const req = makeRequire(pkgPath, {});
|
|
32
|
+
expect(resolveRimrafBin(req)).toBeNull();
|
|
33
|
+
});
|
|
34
|
+
it('returns null when bin object has no rimraf key', () => {
|
|
35
|
+
const pkgPath = '/node_modules/rimraf/package.json';
|
|
36
|
+
const req = makeRequire(pkgPath, { bin: { other: './dist/other.js' } });
|
|
37
|
+
expect(resolveRimrafBin(req)).toBeNull();
|
|
38
|
+
});
|
|
39
|
+
it('resolves against the real rimraf package', () => {
|
|
40
|
+
const req = createRequire(import.meta.url);
|
|
41
|
+
const result = resolveRimrafBin(req);
|
|
42
|
+
expect(result).not.toBeNull();
|
|
43
|
+
expect(path.isAbsolute(result ?? '')).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"husky.d.ts","sourceRoot":"","sources":["../../../src/bin/exec/husky.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"p.d.ts","sourceRoot":"","sources":["../../../src/bin/exec/p.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s.d.ts","sourceRoot":"","sources":["../../../src/bin/exec/s.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ts.d.ts","sourceRoot":"","sources":["../../../src/bin/exec/ts.ts"],"names":[],"mappings":";AAcA,wBAAgB,aAAa,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,GAAG,IAAI,CAYjE"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
export function resolveTsxBin(require) {
|
|
7
|
+
const pkgPath = require.resolve('tsx/package.json');
|
|
8
|
+
const pkg = require(pkgPath);
|
|
9
|
+
const binRel = pkg.bin;
|
|
10
|
+
const binEntry = typeof binRel === 'string'
|
|
11
|
+
? binRel
|
|
12
|
+
: typeof binRel === 'object' && binRel !== null && 'tsx' in binRel
|
|
13
|
+
? binRel.tsx
|
|
14
|
+
: undefined;
|
|
15
|
+
if (!binEntry)
|
|
16
|
+
return null;
|
|
17
|
+
return path.join(path.dirname(pkgPath), binEntry);
|
|
18
|
+
}
|
|
19
|
+
if (fileURLToPath(import.meta.url) === path.resolve(process.argv[1] ?? '')) {
|
|
20
|
+
const args = process.argv.slice(2);
|
|
21
|
+
if (args.length === 0) {
|
|
22
|
+
console.error('Usage: exec-ts <script.ts> [args...]');
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
const [scriptArg, ...rest] = args;
|
|
26
|
+
const script = scriptArg ?? '';
|
|
27
|
+
const require = createRequire(import.meta.url);
|
|
28
|
+
const bin = resolveTsxBin(require);
|
|
29
|
+
if (!bin) {
|
|
30
|
+
console.error('Unable to locate tsx binary from package.json bin field');
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
const spawnOptions = { stdio: 'inherit' };
|
|
34
|
+
const child = spawn(process.execPath, [bin, script, ...rest], spawnOptions);
|
|
35
|
+
child.on('exit', (code) => process.exit(code ?? 0));
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ts.test.d.ts","sourceRoot":"","sources":["../../../src/bin/exec/ts.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
import { resolveTsxBin } from './ts.js';
|
|
5
|
+
function makeRequire(pkgPath, pkg) {
|
|
6
|
+
const req = (id) => (id === pkgPath ? pkg : undefined);
|
|
7
|
+
req.resolve = (_id) => pkgPath;
|
|
8
|
+
return req;
|
|
9
|
+
}
|
|
10
|
+
describe('resolveTsxBin', () => {
|
|
11
|
+
it('resolves when bin is a string', () => {
|
|
12
|
+
const pkgPath = '/node_modules/tsx/package.json';
|
|
13
|
+
const req = makeRequire(pkgPath, { bin: './dist/cli.mjs' });
|
|
14
|
+
const result = resolveTsxBin(req);
|
|
15
|
+
expect(result).toBe(path.join('/node_modules/tsx', 'dist/cli.mjs'));
|
|
16
|
+
});
|
|
17
|
+
it('resolves when bin is an object with a tsx key', () => {
|
|
18
|
+
const pkgPath = '/node_modules/tsx/package.json';
|
|
19
|
+
const req = makeRequire(pkgPath, { bin: { tsx: './dist/cli.mjs' } });
|
|
20
|
+
const result = resolveTsxBin(req);
|
|
21
|
+
expect(result).toBe(path.join('/node_modules/tsx', 'dist/cli.mjs'));
|
|
22
|
+
});
|
|
23
|
+
it('returns null when bin field is missing', () => {
|
|
24
|
+
const pkgPath = '/node_modules/tsx/package.json';
|
|
25
|
+
const req = makeRequire(pkgPath, {});
|
|
26
|
+
expect(resolveTsxBin(req)).toBeNull();
|
|
27
|
+
});
|
|
28
|
+
it('returns null when bin object has no tsx key', () => {
|
|
29
|
+
const pkgPath = '/node_modules/tsx/package.json';
|
|
30
|
+
const req = makeRequire(pkgPath, { bin: { other: './dist/other.js' } });
|
|
31
|
+
expect(resolveTsxBin(req)).toBeNull();
|
|
32
|
+
});
|
|
33
|
+
it('resolves against the real tsx package', () => {
|
|
34
|
+
const req = createRequire(import.meta.url);
|
|
35
|
+
const result = resolveTsxBin(req);
|
|
36
|
+
expect(result).not.toBeNull();
|
|
37
|
+
expect(path.isAbsolute(result ?? '')).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tsc.d.ts","sourceRoot":"","sources":["../../../src/bin/exec/tsc.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hotfix.d.ts","sourceRoot":"","sources":["../../../src/bin/flow/hotfix.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { branchExists, bumpVersion, git, gitRead, insertChangelog, runQualityChecks, runScript, } from './utils.js';
|
|
5
|
+
const subcommand = process.argv[2];
|
|
6
|
+
const subArgs = process.argv.slice(3);
|
|
7
|
+
if (subcommand === 'start') {
|
|
8
|
+
const name = subArgs[0];
|
|
9
|
+
if (!name) {
|
|
10
|
+
console.error('Usage: flow-hotfix start <name>');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
const hotfixBranch = `hotfix/${name}`;
|
|
14
|
+
const status = gitRead('status', '--porcelain');
|
|
15
|
+
if (status) {
|
|
16
|
+
console.error('Working directory has uncommitted changes. Commit or stash them first.');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
console.log('\nFetching latest state from origin...');
|
|
20
|
+
git('fetch', 'origin');
|
|
21
|
+
if (!branchExists('production')) {
|
|
22
|
+
console.error('Branch "production" does not exist. Create it first:\n'
|
|
23
|
+
+ ' git checkout -b production && git push -u origin production');
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
git('checkout', 'production');
|
|
27
|
+
git('pull', '--ff-only', 'origin', 'production');
|
|
28
|
+
git('checkout', '-b', hotfixBranch);
|
|
29
|
+
console.log(`\n✅ Hotfix branch "${hotfixBranch}" created from production.`);
|
|
30
|
+
console.log('Apply your fix, then run: flow-hotfix finish <patch|minor> "description"');
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
if (subcommand === 'finish') {
|
|
34
|
+
const bumpType = subArgs[0];
|
|
35
|
+
const message = subArgs.slice(1).join(' ');
|
|
36
|
+
if (!bumpType || !['patch', 'minor'].includes(bumpType)) {
|
|
37
|
+
console.error('Usage: flow-hotfix finish <patch|minor> "description"');
|
|
38
|
+
console.error('Hotfixes use patch or minor bumps only.');
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
if (!message) {
|
|
42
|
+
console.error('A description is required.');
|
|
43
|
+
console.error('Example: flow-hotfix finish patch "Fix critical auth bug"');
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
const currentBranch = gitRead('branch', '--show-current');
|
|
47
|
+
if (!currentBranch.startsWith('hotfix/')) {
|
|
48
|
+
console.error(`Must be on a hotfix/* branch. Currently on: ${currentBranch}`);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
const status = gitRead('status', '--porcelain');
|
|
52
|
+
if (status) {
|
|
53
|
+
console.error('Working directory has uncommitted changes. Commit or stash them first.');
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
console.log('\nRunning quality checks...');
|
|
57
|
+
try {
|
|
58
|
+
runQualityChecks();
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
console.error('\nQuality checks failed. Fix all issues before finishing the hotfix.');
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
console.log('✅ Quality checks passed');
|
|
65
|
+
const packageJsonPath = join(process.cwd(), 'package.json');
|
|
66
|
+
if (!existsSync(packageJsonPath)) {
|
|
67
|
+
console.error('No package.json found in current directory.');
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
71
|
+
const packageName = packageJson.name;
|
|
72
|
+
const oldVersion = packageJson.version;
|
|
73
|
+
const newVersion = bumpVersion(oldVersion, bumpType);
|
|
74
|
+
console.log(`\nBumping ${packageName}: ${oldVersion} → ${newVersion}`);
|
|
75
|
+
writeFileSync(packageJsonPath, `${JSON.stringify({ ...packageJson, version: newVersion }, null, 2)}\n`);
|
|
76
|
+
const changelogPath = join(process.cwd(), 'CHANGELOG.md');
|
|
77
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
78
|
+
insertChangelog(changelogPath, `## [${newVersion}] - ${today} (hotfix)\n\n${message}\n`);
|
|
79
|
+
try {
|
|
80
|
+
runScript('fix');
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
}
|
|
84
|
+
git('add', '-A');
|
|
85
|
+
git('commit', '-m', `chore(hotfix): ${packageName}@${newVersion}`, '-m', message);
|
|
86
|
+
console.log('\nFetching latest state from origin...');
|
|
87
|
+
git('fetch', 'origin');
|
|
88
|
+
console.log('\nMerging hotfix into production...');
|
|
89
|
+
git('checkout', 'production');
|
|
90
|
+
git('pull', '--ff-only', 'origin', 'production');
|
|
91
|
+
git('merge', '--no-ff', currentBranch, '-m', `chore(hotfix): merge ${currentBranch} into production`);
|
|
92
|
+
git('push', 'origin', 'production');
|
|
93
|
+
console.log('\nPropagating hotfix to staging...');
|
|
94
|
+
git('checkout', 'staging');
|
|
95
|
+
git('pull', '--ff-only', 'origin', 'staging');
|
|
96
|
+
git('merge', '--no-ff', 'production', '-m', 'chore(hotfix): merge production into staging');
|
|
97
|
+
git('push', 'origin', 'staging');
|
|
98
|
+
console.log('\nPropagating hotfix to main...');
|
|
99
|
+
git('checkout', 'main');
|
|
100
|
+
git('pull', '--ff-only', 'origin', 'main');
|
|
101
|
+
git('merge', '--no-ff', 'staging', '-m', 'chore(hotfix): merge staging into main');
|
|
102
|
+
git('push', 'origin', 'main');
|
|
103
|
+
git('branch', '-d', currentBranch);
|
|
104
|
+
try {
|
|
105
|
+
git('push', 'origin', '--delete', currentBranch);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
}
|
|
109
|
+
console.log(`\n✅ Hotfix ${packageName}@${newVersion} shipped to production → staging → main`);
|
|
110
|
+
console.log('You are on main and ready to keep working.');
|
|
111
|
+
process.exit(0);
|
|
112
|
+
}
|
|
113
|
+
console.error('Usage:');
|
|
114
|
+
console.error(' flow-hotfix start <name>');
|
|
115
|
+
console.error(' flow-hotfix finish <patch|minor> "description"');
|
|
116
|
+
process.exit(1);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"release.d.ts","sourceRoot":"","sources":["../../../src/bin/flow/release.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { branchExists, git, gitRead, runQualityChecks, runScript } from './utils.js';
|
|
5
|
+
const args = process.argv.slice(2);
|
|
6
|
+
const message = args.join(' ');
|
|
7
|
+
if (!message) {
|
|
8
|
+
console.error('Usage: flow-release "message"');
|
|
9
|
+
console.error('Example: flow-release "Add new vitest configs"');
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
const currentBranch = gitRead('branch', '--show-current');
|
|
13
|
+
if (currentBranch !== 'main') {
|
|
14
|
+
console.error(`Must be on the main branch to release. Currently on: ${currentBranch}`);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const status = gitRead('status', '--porcelain');
|
|
18
|
+
if (status) {
|
|
19
|
+
console.error('Working directory has uncommitted changes. Commit or stash them first.');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
console.log('\nFetching latest state from origin...');
|
|
23
|
+
git('fetch', 'origin');
|
|
24
|
+
git('pull', '--ff-only', 'origin', 'main');
|
|
25
|
+
const packageJsonPath = join(process.cwd(), 'package.json');
|
|
26
|
+
if (!existsSync(packageJsonPath)) {
|
|
27
|
+
console.error('No package.json found in current directory.');
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
31
|
+
const packageName = packageJson.name;
|
|
32
|
+
if (!packageName) {
|
|
33
|
+
console.error('No "name" field found in package.json.');
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
if (!branchExists('staging')) {
|
|
37
|
+
console.error('Branch "staging" does not exist locally or on origin. Create it first:\n'
|
|
38
|
+
+ ' git checkout -b staging && git push -u origin staging');
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
console.log('\nRunning quality checks...');
|
|
42
|
+
try {
|
|
43
|
+
runQualityChecks();
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
console.error('\nQuality checks failed. Fix all issues before releasing.');
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
console.log('✅ Quality checks passed');
|
|
50
|
+
try {
|
|
51
|
+
runScript('fix');
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
}
|
|
55
|
+
git('add', '-A');
|
|
56
|
+
const hasStagedChanges = gitRead('diff', '--cached', '--name-only') !== '';
|
|
57
|
+
if (hasStagedChanges) {
|
|
58
|
+
git('commit', '-m', `chore(staging): ${message}`);
|
|
59
|
+
}
|
|
60
|
+
console.log('\nMerging main into staging...');
|
|
61
|
+
git('checkout', 'staging');
|
|
62
|
+
git('merge', '--ff-only', 'main');
|
|
63
|
+
git('push', 'origin', 'staging');
|
|
64
|
+
git('checkout', 'main');
|
|
65
|
+
git('push', 'origin', 'main');
|
|
66
|
+
console.log('\n✅ Changes deployed to staging');
|
|
67
|
+
console.log(` "${message}"`);
|
|
68
|
+
console.log('Run flow-ship <patch|minor|major> when ready to promote to production.');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ship.d.ts","sourceRoot":"","sources":["../../../src/bin/flow/ship.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { branchExists, bumpVersion, confirm, git, gitRead, insertChangelog, runQualityChecks, runScript, } from './utils.js';
|
|
5
|
+
const args = process.argv.slice(2);
|
|
6
|
+
const bumpType = args[0];
|
|
7
|
+
if (!bumpType || !['patch', 'minor', 'major'].includes(bumpType)) {
|
|
8
|
+
console.error('Usage: flow-ship <patch|minor|major>');
|
|
9
|
+
console.error('Example: flow-ship minor');
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
const currentBranch = gitRead('branch', '--show-current');
|
|
13
|
+
if (currentBranch !== 'main') {
|
|
14
|
+
console.error(`Must be on the main branch to ship. Currently on: ${currentBranch}`);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const status = gitRead('status', '--porcelain');
|
|
18
|
+
if (status) {
|
|
19
|
+
console.error('Working directory has uncommitted changes. Commit or stash them first.');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
console.log('\nFetching latest state from origin...');
|
|
23
|
+
git('fetch', 'origin');
|
|
24
|
+
if (!branchExists('staging')) {
|
|
25
|
+
console.error('Branch "staging" does not exist. Create it first:\n'
|
|
26
|
+
+ ' git checkout -b staging && git push -u origin staging');
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
if (!branchExists('production')) {
|
|
30
|
+
console.error('Branch "production" does not exist. Create it first:\n'
|
|
31
|
+
+ ' git checkout -b production && git push -u origin production');
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
const ahead = gitRead('log', 'origin/production..origin/staging', '--oneline');
|
|
35
|
+
if (!ahead) {
|
|
36
|
+
console.error('staging is already in sync with production. Nothing to ship.');
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
console.log('\nCommits to be shipped to production:');
|
|
40
|
+
console.log(ahead);
|
|
41
|
+
const packageJsonPath = join(process.cwd(), 'package.json');
|
|
42
|
+
if (!existsSync(packageJsonPath)) {
|
|
43
|
+
console.error('No package.json found in current directory.');
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
const { name: packageName } = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
47
|
+
if (!confirm(`\nShip ${packageName} as a ${bumpType} release?`)) {
|
|
48
|
+
console.log('Aborted.');
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
51
|
+
console.log('\nChecking out staging for quality checks...');
|
|
52
|
+
git('checkout', 'staging');
|
|
53
|
+
git('pull', '--ff-only', 'origin', 'staging');
|
|
54
|
+
console.log('\nRunning quality checks on staging...');
|
|
55
|
+
try {
|
|
56
|
+
runQualityChecks();
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
console.error('\nQuality checks failed on staging. Fix issues before shipping.');
|
|
60
|
+
git('checkout', 'main');
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
console.log('✅ Quality checks passed');
|
|
64
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
65
|
+
const oldVersion = packageJson.version;
|
|
66
|
+
const newVersion = bumpVersion(oldVersion, bumpType);
|
|
67
|
+
console.log(`\nBumping ${packageName}: ${oldVersion} → ${newVersion}`);
|
|
68
|
+
writeFileSync(packageJsonPath, `${JSON.stringify({ ...packageJson, version: newVersion }, null, 2)}\n`);
|
|
69
|
+
const logOutput = gitRead('log', 'origin/production..HEAD', '--pretty=format:%s');
|
|
70
|
+
const changeLines = logOutput
|
|
71
|
+
? logOutput
|
|
72
|
+
.split('\n')
|
|
73
|
+
.map((s) => s.trim())
|
|
74
|
+
.filter((s) => s.length > 0)
|
|
75
|
+
: [];
|
|
76
|
+
const changeBody = changeLines.length > 0
|
|
77
|
+
? changeLines.map((c) => `- ${c.length > 98 ? `${c.slice(0, 95)}...` : c}`).join('\n')
|
|
78
|
+
: `${bumpType} release`;
|
|
79
|
+
const changelogPath = join(process.cwd(), 'CHANGELOG.md');
|
|
80
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
81
|
+
insertChangelog(changelogPath, `## [${newVersion}] - ${today}\n\n${changeBody}\n`);
|
|
82
|
+
try {
|
|
83
|
+
runScript('fix');
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
}
|
|
87
|
+
git('add', '-A');
|
|
88
|
+
git('commit', '-m', `chore(release): ${packageName}@${newVersion}`, '-m', changeBody);
|
|
89
|
+
console.log('\nMerging staging into production...');
|
|
90
|
+
git('checkout', 'production');
|
|
91
|
+
git('pull', '--ff-only', 'origin', 'production');
|
|
92
|
+
git('merge', '--ff-only', 'staging');
|
|
93
|
+
git('push', 'origin', 'production');
|
|
94
|
+
console.log('\nSyncing version commit back to main...');
|
|
95
|
+
git('checkout', 'main');
|
|
96
|
+
git('pull', '--ff-only', 'origin', 'main');
|
|
97
|
+
git('merge', '--ff-only', 'production');
|
|
98
|
+
git('push', 'origin', 'main');
|
|
99
|
+
git('checkout', 'staging');
|
|
100
|
+
git('merge', '--ff-only', 'main');
|
|
101
|
+
git('push', 'origin', 'staging');
|
|
102
|
+
git('checkout', 'main');
|
|
103
|
+
console.log(`\n✅ Shipped ${packageName}@${newVersion} to production`);
|
|
104
|
+
console.log('You are on main and ready to keep working.');
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const git: (...args: string[]) => void;
|
|
2
|
+
export declare const gitRead: (...args: string[]) => string;
|
|
3
|
+
export declare const runScript: (script: string) => void;
|
|
4
|
+
export declare const bumpVersion: (current: string, bump: string) => string;
|
|
5
|
+
export declare const insertChangelog: (changelogPath: string, entry: string) => void;
|
|
6
|
+
export declare const runQualityChecks: () => void;
|
|
7
|
+
export declare const branchExists: (name: string) => boolean;
|
|
8
|
+
export declare const confirm: (prompt: string, ttyPath?: string) => boolean;
|
|
9
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/bin/flow/utils.ts"],"names":[],"mappings":"AAeA,eAAO,MAAM,GAAG,GAAI,GAAG,MAAM,MAAM,EAAE,KAAG,IAGvC,CAAC;AAMF,eAAO,MAAM,OAAO,GAAI,GAAG,MAAM,MAAM,EAAE,KAAG,MACa,CAAC;AAM1D,eAAO,MAAM,SAAS,GAAI,QAAQ,MAAM,KAAG,IAG1C,CAAC;AAKF,eAAO,MAAM,WAAW,GAAI,SAAS,MAAM,EAAE,MAAM,MAAM,KAAG,MAS3D,CAAC;AAMF,eAAO,MAAM,eAAe,GAAI,eAAe,MAAM,EAAE,OAAO,MAAM,KAAG,IAetE,CAAC;AAKF,eAAO,MAAM,gBAAgB,QAAO,IAInC,CAAC;AAMF,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,KAAG,OAE0B,CAAC;AAOvE,eAAO,MAAM,OAAO,GAAI,QAAQ,MAAM,EAAE,gBAAoB,KAAG,OAe9D,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { execFileSync, execSync } from 'node:child_process';
|
|
2
|
+
import { closeSync, existsSync, openSync, readFileSync, readSync, writeFileSync } from 'node:fs';
|
|
3
|
+
export const git = (...args) => {
|
|
4
|
+
console.log(`$ git ${args.join(' ')}`);
|
|
5
|
+
execFileSync('git', args, { stdio: 'inherit' });
|
|
6
|
+
};
|
|
7
|
+
export const gitRead = (...args) => execFileSync('git', args, { encoding: 'utf-8' }).trim();
|
|
8
|
+
export const runScript = (script) => {
|
|
9
|
+
console.log(`$ pnpm ${script}`);
|
|
10
|
+
execSync(`pnpm ${script}`, { stdio: 'inherit' });
|
|
11
|
+
};
|
|
12
|
+
export const bumpVersion = (current, bump) => {
|
|
13
|
+
const parts = current.split('.').map(Number);
|
|
14
|
+
if (parts.length !== 3 || parts.some(Number.isNaN)) {
|
|
15
|
+
throw new Error(`Invalid semver: ${current}`);
|
|
16
|
+
}
|
|
17
|
+
const [major, minor, patch] = parts;
|
|
18
|
+
if (bump === 'major')
|
|
19
|
+
return `${major + 1}.0.0`;
|
|
20
|
+
if (bump === 'minor')
|
|
21
|
+
return `${major}.${minor + 1}.0`;
|
|
22
|
+
return `${major}.${minor}.${patch + 1}`;
|
|
23
|
+
};
|
|
24
|
+
export const insertChangelog = (changelogPath, entry) => {
|
|
25
|
+
if (existsSync(changelogPath)) {
|
|
26
|
+
const existing = readFileSync(changelogPath, 'utf-8');
|
|
27
|
+
const insertAt = existing.indexOf('\n## ');
|
|
28
|
+
if (insertAt === -1) {
|
|
29
|
+
writeFileSync(changelogPath, `${existing.trimEnd()}\n\n${entry}`);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
writeFileSync(changelogPath, `${existing.slice(0, insertAt)}\n\n${entry}${existing.slice(insertAt + 1)}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
writeFileSync(changelogPath, `# Changelog\n\n${entry}`);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
export const runQualityChecks = () => {
|
|
40
|
+
runScript('build');
|
|
41
|
+
runScript('typecheck');
|
|
42
|
+
runScript('report');
|
|
43
|
+
};
|
|
44
|
+
export const branchExists = (name) => gitRead('branch', '--list', name) !== ''
|
|
45
|
+
|| gitRead('branch', '--list', '--remotes', `origin/${name}`) !== '';
|
|
46
|
+
export const confirm = (prompt, ttyPath = '/dev/tty') => {
|
|
47
|
+
process.stdout.write(`${prompt} (y/N) `);
|
|
48
|
+
const buf = Buffer.alloc(1024);
|
|
49
|
+
let fd;
|
|
50
|
+
let shouldClose = false;
|
|
51
|
+
try {
|
|
52
|
+
fd = openSync(ttyPath, 'r');
|
|
53
|
+
shouldClose = true;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
fd = process.stdin.fd;
|
|
57
|
+
}
|
|
58
|
+
const bytesRead = readSync(fd, buf, 0, buf.length, null);
|
|
59
|
+
if (shouldClose)
|
|
60
|
+
closeSync(fd);
|
|
61
|
+
const answer = buf.slice(0, bytesRead).toString().trim();
|
|
62
|
+
return answer === 'y' || answer === 'Y';
|
|
63
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.test.d.ts","sourceRoot":"","sources":["../../../src/bin/flow/utils.test.ts"],"names":[],"mappings":""}
|