@regardio/dev 1.16.2 → 1.17.0

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 CHANGED
@@ -27,7 +27,7 @@ The goal is code that's correct, consistent, and a pleasure to work with.
27
27
  | **Testing** | Vitest, Playwright, Testing Library |
28
28
  | **Build** | TypeScript, tsx, Vite |
29
29
  | **Workflow** | Husky, GitLab Flow |
30
- | **CLI utilities** | exec-clean, exec-p, exec-s, exec-ts, ship-staging, ship-production, ship-hotfix, lint-biome, lint-md, lint-package |
30
+ | **CLI utilities** | exec-clean, exec-p, exec-s, exec-tsc, ship-staging, ship-production, ship-hotfix, lint-biome, lint-md, lint-package |
31
31
 
32
32
  ## Quick Start
33
33
 
@@ -31,9 +31,10 @@ if (!branchExists('production')) {
31
31
  + ' git checkout -b production && git push -u origin production');
32
32
  process.exit(1);
33
33
  }
34
- const ahead = gitRead('log', 'origin/production..origin/staging', '--oneline');
34
+ git('pull', '--ff-only', 'origin', 'main');
35
+ const ahead = gitRead('log', 'origin/production..HEAD', '--oneline');
35
36
  if (!ahead) {
36
- console.error('staging is already in sync with production. Nothing to ship.');
37
+ console.error('main is already in sync with production. Nothing to ship.');
37
38
  process.exit(1);
38
39
  }
39
40
  console.log('\nCommits to be shipped to production:');
@@ -48,16 +49,12 @@ if (!confirm(`\nShip ${packageName} as a ${bumpType} release?`)) {
48
49
  console.log('Aborted.');
49
50
  process.exit(0);
50
51
  }
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...');
52
+ console.log('\nRunning quality checks on main...');
55
53
  try {
56
54
  runQualityChecks();
57
55
  }
58
56
  catch {
59
- console.error('\nQuality checks failed on staging. Fix issues before shipping.');
60
- git('checkout', 'main');
57
+ console.error('\nQuality checks failed on main. Fix issues before shipping.');
61
58
  process.exit(1);
62
59
  }
63
60
  console.log('✅ Quality checks passed');
@@ -86,19 +83,17 @@ catch {
86
83
  }
87
84
  git('add', '-A');
88
85
  git('commit', '-m', `chore(release): ${packageName}@${newVersion}`, '-m', changeBody);
89
- console.log('\nMerging staging into production...');
86
+ console.log('\nMerging main into production...');
90
87
  git('checkout', 'production');
91
88
  git('pull', '--ff-only', 'origin', 'production');
92
- git('merge', '--ff-only', 'staging');
89
+ git('merge', '--ff-only', 'main');
93
90
  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');
91
+ console.log('\nSyncing staging with production...');
99
92
  git('checkout', 'staging');
100
- git('merge', '--ff-only', 'main');
93
+ git('pull', '--ff-only', 'origin', 'staging');
94
+ git('merge', '--ff-only', 'production');
101
95
  git('push', 'origin', 'staging');
102
96
  git('checkout', 'main');
97
+ git('pull', '--ff-only', 'origin', 'main');
103
98
  console.log(`\n✅ Shipped ${packageName}@${newVersion} to production`);
104
99
  console.log('You are on main and ready to keep working.');
@@ -59,4 +59,5 @@ git('push', 'origin', 'staging');
59
59
  git('checkout', 'main');
60
60
  git('push', 'origin', 'main');
61
61
  console.log('\n✅ Changes deployed to staging');
62
- console.log('Run ship-production <patch|minor|major> when ready to promote to production.');
62
+ console.log('Run ship-production <patch|minor|major> when ready to ship to production.');
63
+ console.log('(Or ship directly from main to production without using ship-staging first.)');
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://www.schemastore.org/package.json",
3
3
  "name": "@regardio/dev",
4
- "version": "1.16.2",
4
+ "version": "1.17.0",
5
5
  "private": false,
6
6
  "description": "Regardio developer tooling for testing, linting, and build workflows",
7
7
  "keywords": [
@@ -59,7 +59,6 @@
59
59
  "exec-husky": "dist/bin/exec/husky.js",
60
60
  "exec-p": "dist/bin/exec/p.js",
61
61
  "exec-s": "dist/bin/exec/s.js",
62
- "exec-ts": "dist/bin/exec/ts.js",
63
62
  "exec-tsc": "dist/bin/exec/tsc.js",
64
63
  "lint-biome": "dist/bin/lint/biome.js",
65
64
  "lint-commit": "dist/bin/lint/commit.js",
@@ -91,28 +90,28 @@
91
90
  "typecheck": "tsc --noEmit"
92
91
  },
93
92
  "dependencies": {
94
- "@biomejs/biome": "2.4.5",
95
- "@commitlint/cli": "20.4.2",
96
- "@commitlint/config-conventional": "20.4.2",
93
+ "@biomejs/biome": "2.4.7",
94
+ "@commitlint/cli": "20.5.0",
95
+ "@commitlint/config-conventional": "20.5.0",
97
96
  "@playwright/test": "1.58.2",
98
97
  "@testing-library/jest-dom": "6.9.1",
99
98
  "@testing-library/react": "16.3.2",
100
99
  "@total-typescript/ts-reset": "0.6.1",
101
- "@types/node": "25.3.3",
102
- "@vitest/coverage-v8": "4.0.18",
103
- "@vitest/ui": "4.0.18",
100
+ "@types/node": "25.5.0",
101
+ "@vitest/coverage-v8": "4.1.0",
102
+ "@vitest/ui": "4.1.0",
104
103
  "husky": "9.1.7",
105
- "jsdom": "28.1.0",
104
+ "jsdom": "29.0.0",
106
105
  "markdownlint-cli2": "0.21.0",
107
106
  "npm-run-all": "4.1.5",
108
- "postcss": "8.5.6",
107
+ "postcss": "8.5.8",
109
108
  "rimraf": "6.1.3",
110
109
  "rollup": "4.59.0",
111
110
  "sort-package-json": "3.6.1",
112
111
  "tsx": "4.21.0",
113
112
  "typescript": "5.9.3",
114
- "vite": "7.3.1",
115
- "vitest": "4.0.18"
113
+ "vite": "8.0.0",
114
+ "vitest": "4.1.0"
116
115
  },
117
116
  "peerDependencies": {
118
117
  "postcss": "8.4"
@@ -1,26 +1,26 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * ship-production: Version and promote staging to production following the GitLab workflow.
3
+ * ship-production: Version and promote main to production following the GitLab workflow.
4
4
  *
5
5
  * Usage: ship-production <patch|minor|major>
6
6
  *
7
7
  * GitLab workflow:
8
- * staging → (version bump commit) → production
8
+ * main → (version bump commit) → production → staging → main
9
9
  *
10
10
  * Versioning is intentionally deferred to this step so that version numbers
11
11
  * only ever correspond to production-verified code.
12
12
  *
13
13
  * This script:
14
14
  * 1. Ensures the current branch is main and the working tree is clean
15
- * 2. Verifies staging is ahead of production (there is something to ship)
16
- * 3. Runs quality checks on the staging branch
15
+ * 2. Verifies main is ahead of production (there is something to ship)
16
+ * 3. Runs quality checks on main
17
17
  * 4. Bumps the version in package.json
18
- * 5. Collects change descriptions from git log between production and staging
18
+ * 5. Collects change descriptions from git log between production and main
19
19
  * 6. Updates CHANGELOG.md
20
- * 7. Commits the version bump on staging
21
- * 8. Merges staging into production (fast-forward) and pushes
22
- * 9. Merges production back into main to carry the version commit forward
23
- * 10. Syncs staging with main so the next ship-staging can ff-merge cleanly
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
24
  * 11. Returns to main
25
25
  */
26
26
  import { existsSync, readFileSync, writeFileSync } from 'node:fs';
@@ -85,11 +85,12 @@ if (!branchExists('production')) {
85
85
  }
86
86
 
87
87
  // ---------------------------------------------------------------------------
88
- // Verify staging has commits not yet in production
88
+ // Verify main has commits not yet in production
89
89
  // ---------------------------------------------------------------------------
90
- const ahead = gitRead('log', 'origin/production..origin/staging', '--oneline');
90
+ git('pull', '--ff-only', 'origin', 'main');
91
+ const ahead = gitRead('log', 'origin/production..HEAD', '--oneline');
91
92
  if (!ahead) {
92
- console.error('staging is already in sync with production. Nothing to ship.');
93
+ console.error('main is already in sync with production. Nothing to ship.');
93
94
  process.exit(1);
94
95
  }
95
96
 
@@ -114,24 +115,19 @@ if (!confirm(`\nShip ${packageName} as a ${bumpType} release?`)) {
114
115
  }
115
116
 
116
117
  // ---------------------------------------------------------------------------
117
- // Quality checks on staging
118
+ // Quality checks on main
118
119
  // ---------------------------------------------------------------------------
119
- console.log('\nChecking out staging for quality checks...');
120
- git('checkout', 'staging');
121
- git('pull', '--ff-only', 'origin', 'staging');
122
-
123
- console.log('\nRunning quality checks on staging...');
120
+ console.log('\nRunning quality checks on main...');
124
121
  try {
125
122
  runQualityChecks();
126
123
  } catch {
127
- console.error('\nQuality checks failed on staging. Fix issues before shipping.');
128
- git('checkout', 'main');
124
+ console.error('\nQuality checks failed on main. Fix issues before shipping.');
129
125
  process.exit(1);
130
126
  }
131
127
  console.log('✅ Quality checks passed');
132
128
 
133
129
  // ---------------------------------------------------------------------------
134
- // Read version from staging package.json and bump
130
+ // Read version from main package.json and bump
135
131
  // ---------------------------------------------------------------------------
136
132
  const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')) as {
137
133
  name: string;
@@ -180,36 +176,34 @@ try {
180
176
  }
181
177
 
182
178
  // ---------------------------------------------------------------------------
183
- // Commit version bump on staging
179
+ // Commit version bump on main
184
180
  // ---------------------------------------------------------------------------
185
181
  git('add', '-A');
186
182
  git('commit', '-m', `chore(release): ${packageName}@${newVersion}`, '-m', changeBody);
187
183
 
188
184
  // ---------------------------------------------------------------------------
189
- // Merge staging → production
185
+ // Merge main → production
190
186
  // ---------------------------------------------------------------------------
191
- console.log('\nMerging staging into production...');
187
+ console.log('\nMerging main into production...');
192
188
  git('checkout', 'production');
193
189
  git('pull', '--ff-only', 'origin', 'production');
194
- git('merge', '--ff-only', 'staging');
190
+ git('merge', '--ff-only', 'main');
195
191
  git('push', 'origin', 'production');
196
192
 
197
193
  // ---------------------------------------------------------------------------
198
- // Bring version commit back to main
194
+ // Sync staging with production
199
195
  // ---------------------------------------------------------------------------
200
- console.log('\nSyncing version commit back to main...');
201
- git('checkout', 'main');
202
- git('pull', '--ff-only', 'origin', 'main');
196
+ console.log('\nSyncing staging with production...');
197
+ git('checkout', 'staging');
198
+ git('pull', '--ff-only', 'origin', 'staging');
203
199
  git('merge', '--ff-only', 'production');
204
- git('push', 'origin', 'main');
200
+ git('push', 'origin', 'staging');
205
201
 
206
202
  // ---------------------------------------------------------------------------
207
- // Sync staging with main so the next flow-release can ff-merge cleanly
203
+ // Return to main and sync
208
204
  // ---------------------------------------------------------------------------
209
- git('checkout', 'staging');
210
- git('merge', '--ff-only', 'main');
211
- git('push', 'origin', 'staging');
212
205
  git('checkout', 'main');
206
+ git('pull', '--ff-only', 'origin', 'main');
213
207
 
214
208
  console.log(`\n✅ Shipped ${packageName}@${newVersion} to production`);
215
209
  console.log('You are on main and ready to keep working.');
@@ -7,6 +7,12 @@
7
7
  * GitLab workflow:
8
8
  * main → staging (staging deploy, no version bump yet)
9
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
+ *
10
16
  * Versioning happens at ship time (ship-production), not here.
11
17
  * This keeps version numbers reserved for production-verified code.
12
18
  *
@@ -120,4 +126,5 @@ git('checkout', 'main');
120
126
  git('push', 'origin', 'main');
121
127
 
122
128
  console.log('\n✅ Changes deployed to staging');
123
- console.log('Run ship-production <patch|minor|major> when ready to promote to production.');
129
+ console.log('Run ship-production <patch|minor|major> when ready to ship to production.');
130
+ console.log('(Or ship directly from main to production without using ship-staging first.)');
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
- export declare function resolveTsxBin(require: NodeRequire): string | null;
3
- //# sourceMappingURL=ts.d.ts.map
@@ -1 +0,0 @@
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"}
@@ -1,36 +0,0 @@
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
- }
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=ts.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ts.test.d.ts","sourceRoot":"","sources":["../../../src/bin/exec/ts.test.ts"],"names":[],"mappings":""}
@@ -1,39 +0,0 @@
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
- });
@@ -1,54 +0,0 @@
1
- import { createRequire } from 'node:module';
2
- import path from 'node:path';
3
- import { describe, expect, it } from 'vitest';
4
-
5
- import { resolveTsxBin } from './ts.js';
6
-
7
- function makeRequire(pkgPath: string, pkg: unknown): NodeRequire {
8
- const req = (id: string): unknown => (id === pkgPath ? pkg : undefined);
9
- req.resolve = (_id: string) => pkgPath;
10
- return req as unknown as NodeRequire;
11
- }
12
-
13
- describe('resolveTsxBin', () => {
14
- it('resolves when bin is a string', () => {
15
- const pkgPath = '/node_modules/tsx/package.json';
16
- const req = makeRequire(pkgPath, { bin: './dist/cli.mjs' });
17
-
18
- const result = resolveTsxBin(req);
19
-
20
- expect(result).toBe(path.join('/node_modules/tsx', 'dist/cli.mjs'));
21
- });
22
-
23
- it('resolves when bin is an object with a tsx key', () => {
24
- const pkgPath = '/node_modules/tsx/package.json';
25
- const req = makeRequire(pkgPath, { bin: { tsx: './dist/cli.mjs' } });
26
-
27
- const result = resolveTsxBin(req);
28
-
29
- expect(result).toBe(path.join('/node_modules/tsx', 'dist/cli.mjs'));
30
- });
31
-
32
- it('returns null when bin field is missing', () => {
33
- const pkgPath = '/node_modules/tsx/package.json';
34
- const req = makeRequire(pkgPath, {});
35
-
36
- expect(resolveTsxBin(req)).toBeNull();
37
- });
38
-
39
- it('returns null when bin object has no tsx key', () => {
40
- const pkgPath = '/node_modules/tsx/package.json';
41
- const req = makeRequire(pkgPath, { bin: { other: './dist/other.js' } });
42
-
43
- expect(resolveTsxBin(req)).toBeNull();
44
- });
45
-
46
- it('resolves against the real tsx package', () => {
47
- const req = createRequire(import.meta.url);
48
-
49
- const result = resolveTsxBin(req);
50
-
51
- expect(result).not.toBeNull();
52
- expect(path.isAbsolute(result ?? '')).toBe(true);
53
- });
54
- });
@@ -1,52 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * exec-ts: Run a local TypeScript file via tsx with TS support.
4
- * Usage: exec-ts path/to/script.ts [args...]
5
- */
6
- import { type SpawnOptions, spawn } from 'node:child_process';
7
- import { createRequire } from 'node:module';
8
- import path from 'node:path';
9
- import { fileURLToPath } from 'node:url';
10
-
11
- /**
12
- * Resolve the absolute path to the tsx binary from its package.json bin field.
13
- * Returns the resolved path, or null if it cannot be determined.
14
- */
15
- export function resolveTsxBin(require: NodeRequire): string | null {
16
- const pkgPath = require.resolve('tsx/package.json');
17
- const pkg = require(pkgPath) as { bin?: unknown };
18
- const binRel = pkg.bin;
19
- const binEntry: string | undefined =
20
- typeof binRel === 'string'
21
- ? binRel
22
- : typeof binRel === 'object' && binRel !== null && 'tsx' in binRel
23
- ? (binRel as Record<string, string>).tsx
24
- : undefined;
25
- if (!binEntry) return null;
26
- return path.join(path.dirname(pkgPath), binEntry);
27
- }
28
-
29
- // ---------------------------------------------------------------------------
30
- // CLI entry point — only runs when executed directly
31
- // ---------------------------------------------------------------------------
32
- if (fileURLToPath(import.meta.url) === path.resolve(process.argv[1] ?? '')) {
33
- const args = process.argv.slice(2);
34
- if (args.length === 0) {
35
- console.error('Usage: exec-ts <script.ts> [args...]');
36
- process.exit(1);
37
- }
38
-
39
- const [scriptArg, ...rest] = args;
40
- const script = scriptArg ?? '';
41
-
42
- const require = createRequire(import.meta.url);
43
- const bin = resolveTsxBin(require);
44
- if (!bin) {
45
- console.error('Unable to locate tsx binary from package.json bin field');
46
- process.exit(1);
47
- }
48
-
49
- const spawnOptions: SpawnOptions = { stdio: 'inherit' };
50
- const child = spawn(process.execPath, [bin, script, ...rest], spawnOptions);
51
- child.on('exit', (code: number | null) => process.exit(code ?? 0));
52
- }