@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
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, Changesets |
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
- - [Changesets](./docs/toolchain/changesets.md) - Versioning and releases
70
+ - [Release Workflow](./docs/toolchain/release-workflow.md) - GitLab-flow-based versioning and releases
71
71
 
72
72
  ## Portability
73
73
 
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export declare function resolveRimrafBin(require: NodeRequire): string | null;
3
+ //# sourceMappingURL=clean.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=clean.test.d.ts.map
@@ -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,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=husky.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"husky.d.ts","sourceRoot":"","sources":["../../../src/bin/exec/husky.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=p.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"p.d.ts","sourceRoot":"","sources":["../../../src/bin/exec/p.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=s.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s.d.ts","sourceRoot":"","sources":["../../../src/bin/exec/s.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export declare function resolveTsxBin(require: NodeRequire): string | null;
3
+ //# sourceMappingURL=ts.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ts.test.d.ts.map
@@ -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,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=tsc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tsc.d.ts","sourceRoot":"","sources":["../../../src/bin/exec/tsc.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=hotfix.d.ts.map
@@ -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,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=release.d.ts.map
@@ -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,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=ship.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=utils.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.test.d.ts","sourceRoot":"","sources":["../../../src/bin/flow/utils.test.ts"],"names":[],"mappings":""}