@regardio/dev 1.10.3 → 1.11.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 +1 -1
- package/dist/bin/flow-release.js +11 -0
- package/dist/bin/lint-package.d.ts +3 -0
- package/dist/bin/lint-package.d.ts.map +1 -0
- package/dist/bin/lint-package.js +86 -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 +111 -0
- package/dist/bin/post-build-exports.test.d.ts +2 -0
- package/dist/bin/post-build-exports.test.d.ts.map +1 -0
- package/dist/bin/post-build-exports.test.js +119 -0
- package/dist/vitest/node.d.ts +6 -0
- package/dist/vitest/node.d.ts.map +1 -1
- package/dist/vitest/node.js +10 -0
- package/dist/vitest/react.d.ts.map +1 -1
- package/dist/vitest/react.js +5 -0
- package/package.json +82 -73
- package/src/bin/flow-release.ts +12 -0
- package/src/bin/lint-package.test.ts +140 -0
- package/src/bin/lint-package.ts +114 -0
- package/src/bin/post-build-exports.test.ts +161 -0
- package/src/biome/preset.json +1 -0
- package/src/templates/release.yml +6 -0
- package/src/vitest/node.ts +15 -0
- package/src/vitest/react.ts +5 -0
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, Changesets |
|
|
30
|
-
| **CLI utilities** | exec-clean, exec-p, exec-s, exec-ts, flow-release, lint-biome, lint-md |
|
|
30
|
+
| **CLI utilities** | exec-clean, exec-p, exec-s, exec-ts, flow-release, lint-biome, lint-md, lint-package |
|
|
31
31
|
|
|
32
32
|
## Quick Start
|
|
33
33
|
|
package/dist/bin/flow-release.js
CHANGED
|
@@ -30,6 +30,17 @@ if (!packageName) {
|
|
|
30
30
|
process.exit(1);
|
|
31
31
|
}
|
|
32
32
|
console.log(`Releasing ${packageName} with ${bumpType} bump...`);
|
|
33
|
+
console.log('Running quality checks...');
|
|
34
|
+
try {
|
|
35
|
+
run('pnpm build');
|
|
36
|
+
run('pnpm typecheck');
|
|
37
|
+
run('pnpm report');
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
console.error('Quality checks failed. Fix issues before releasing.');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
console.log('✅ Quality checks passed');
|
|
33
44
|
try {
|
|
34
45
|
const status = runQuiet('git status --porcelain');
|
|
35
46
|
if (status) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lint-package.d.ts","sourceRoot":"","sources":["../../src/bin/lint-package.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { dirname, join, resolve } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const devRoot = resolve(__dirname, '../..');
|
|
8
|
+
const sortPkgBin = join(devRoot, 'node_modules/.bin/sort-package-json');
|
|
9
|
+
const sortPkgBinAlt = join(devRoot, 'node_modules/sort-package-json/cli.js');
|
|
10
|
+
let bin = '';
|
|
11
|
+
if (existsSync(sortPkgBin)) {
|
|
12
|
+
bin = sortPkgBin;
|
|
13
|
+
}
|
|
14
|
+
else if (existsSync(sortPkgBinAlt)) {
|
|
15
|
+
bin = `node ${sortPkgBinAlt}`;
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
bin = 'npx sort-package-json';
|
|
19
|
+
}
|
|
20
|
+
const args = process.argv.slice(2);
|
|
21
|
+
const fixMode = args.includes('--fix');
|
|
22
|
+
const files = args.filter((arg) => arg !== '--fix');
|
|
23
|
+
const targets = files.length > 0 ? files : ['package.json'];
|
|
24
|
+
try {
|
|
25
|
+
const checkFlag = fixMode ? '' : '--check';
|
|
26
|
+
execSync(`${bin} ${checkFlag} ${targets.join(' ')}`.trim(), { stdio: 'inherit' });
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
function fixExportsOrder(filePath, fix) {
|
|
32
|
+
const fullPath = resolve(process.cwd(), filePath);
|
|
33
|
+
if (!existsSync(fullPath))
|
|
34
|
+
return false;
|
|
35
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
36
|
+
const pkg = JSON.parse(content);
|
|
37
|
+
if (!pkg.exports || typeof pkg.exports !== 'object')
|
|
38
|
+
return false;
|
|
39
|
+
let modified = false;
|
|
40
|
+
function reorderConditions(obj) {
|
|
41
|
+
if (typeof obj !== 'object' || obj === null)
|
|
42
|
+
return obj;
|
|
43
|
+
if ('types' in obj && 'default' in obj) {
|
|
44
|
+
const keys = Object.keys(obj);
|
|
45
|
+
const typesIndex = keys.indexOf('types');
|
|
46
|
+
const defaultIndex = keys.indexOf('default');
|
|
47
|
+
if (defaultIndex < typesIndex) {
|
|
48
|
+
modified = true;
|
|
49
|
+
const reordered = {};
|
|
50
|
+
reordered.types = obj.types;
|
|
51
|
+
for (const key of keys) {
|
|
52
|
+
if (key !== 'types') {
|
|
53
|
+
reordered[key] = obj[key];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return reordered;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const result = {};
|
|
60
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
61
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
62
|
+
result[key] = reorderConditions(value);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
result[key] = value;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
pkg.exports = reorderConditions(pkg.exports);
|
|
71
|
+
if (modified && fix) {
|
|
72
|
+
writeFileSync(fullPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
73
|
+
}
|
|
74
|
+
return modified;
|
|
75
|
+
}
|
|
76
|
+
let hasExportsIssues = false;
|
|
77
|
+
for (const file of targets) {
|
|
78
|
+
const needsFix = fixExportsOrder(file, fixMode);
|
|
79
|
+
if (needsFix && !fixMode) {
|
|
80
|
+
console.error(`${file}: exports condition order is incorrect (types must come before default)`);
|
|
81
|
+
hasExportsIssues = true;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (hasExportsIssues) {
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lint-package.test.d.ts","sourceRoot":"","sources":["../../src/bin/lint-package.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
function reorderConditions(obj) {
|
|
3
|
+
function processObject(o) {
|
|
4
|
+
if (typeof o !== 'object' || o === null)
|
|
5
|
+
return o;
|
|
6
|
+
const processed = {};
|
|
7
|
+
for (const [key, value] of Object.entries(o)) {
|
|
8
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
9
|
+
processed[key] = processObject(value);
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
processed[key] = value;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
if ('types' in processed && 'default' in processed) {
|
|
16
|
+
const keys = Object.keys(processed);
|
|
17
|
+
const typesIndex = keys.indexOf('types');
|
|
18
|
+
const defaultIndex = keys.indexOf('default');
|
|
19
|
+
if (defaultIndex < typesIndex) {
|
|
20
|
+
const reordered = {};
|
|
21
|
+
reordered.types = processed.types;
|
|
22
|
+
for (const key of keys) {
|
|
23
|
+
if (key !== 'types') {
|
|
24
|
+
reordered[key] = processed[key];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return reordered;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return processed;
|
|
31
|
+
}
|
|
32
|
+
return processObject(obj);
|
|
33
|
+
}
|
|
34
|
+
describe('lint-package', () => {
|
|
35
|
+
describe('reorderConditions', () => {
|
|
36
|
+
it('should reorder types before default when default comes first', () => {
|
|
37
|
+
const input = {
|
|
38
|
+
'./foo': {
|
|
39
|
+
default: './dist/foo.js',
|
|
40
|
+
types: './dist/foo.d.ts',
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
const result = reorderConditions(input);
|
|
44
|
+
expect(Object.keys(result['./foo'])).toEqual(['types', 'default']);
|
|
45
|
+
});
|
|
46
|
+
it('should not modify when types already comes before default', () => {
|
|
47
|
+
const input = {
|
|
48
|
+
'./foo': {
|
|
49
|
+
default: './dist/foo.js',
|
|
50
|
+
types: './dist/foo.d.ts',
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
const result = reorderConditions(input);
|
|
54
|
+
expect(Object.keys(result['./foo'])).toEqual(['types', 'default']);
|
|
55
|
+
});
|
|
56
|
+
it('should handle multiple exports with mixed order', () => {
|
|
57
|
+
const input = {
|
|
58
|
+
'./a': {
|
|
59
|
+
default: './dist/a.js',
|
|
60
|
+
types: './dist/a.d.ts',
|
|
61
|
+
},
|
|
62
|
+
'./b': {
|
|
63
|
+
default: './dist/b.js',
|
|
64
|
+
types: './dist/b.d.ts',
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
const result = reorderConditions(input);
|
|
68
|
+
expect(Object.keys(result['./a'])[0]).toBe('types');
|
|
69
|
+
expect(Object.keys(result['./b'])[0]).toBe('types');
|
|
70
|
+
});
|
|
71
|
+
it('should preserve other keys after types', () => {
|
|
72
|
+
const input = {
|
|
73
|
+
'./foo': {
|
|
74
|
+
default: './dist/foo.js',
|
|
75
|
+
import: './dist/foo.mjs',
|
|
76
|
+
require: './dist/foo.cjs',
|
|
77
|
+
types: './dist/foo.d.ts',
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
const result = reorderConditions(input);
|
|
81
|
+
const keys = Object.keys(result['./foo']);
|
|
82
|
+
expect(keys[0]).toBe('types');
|
|
83
|
+
expect(keys.slice(1)).toEqual(['default', 'import', 'require']);
|
|
84
|
+
});
|
|
85
|
+
it('should handle exports without types or default', () => {
|
|
86
|
+
const input = {
|
|
87
|
+
'./styles.css': './dist/styles.css',
|
|
88
|
+
};
|
|
89
|
+
const result = reorderConditions(input);
|
|
90
|
+
expect(result).toEqual(input);
|
|
91
|
+
});
|
|
92
|
+
it('should handle deeply nested condition objects', () => {
|
|
93
|
+
const input = {
|
|
94
|
+
'./foo': {
|
|
95
|
+
browser: {
|
|
96
|
+
default: './dist/foo.browser.js',
|
|
97
|
+
types: './dist/foo.browser.d.ts',
|
|
98
|
+
},
|
|
99
|
+
node: {
|
|
100
|
+
default: './dist/foo.node.js',
|
|
101
|
+
types: './dist/foo.node.d.ts',
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
const result = reorderConditions(input);
|
|
106
|
+
const foo = result['./foo'];
|
|
107
|
+
expect(Object.keys(foo.node)[0]).toBe('types');
|
|
108
|
+
expect(Object.keys(foo.browser)[0]).toBe('types');
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"post-build-exports.test.d.ts","sourceRoot":"","sources":["../../src/bin/post-build-exports.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
function parseArgs(args) {
|
|
3
|
+
const result = {
|
|
4
|
+
dist: 'dist',
|
|
5
|
+
prefix: './',
|
|
6
|
+
preserve: [],
|
|
7
|
+
strip: '',
|
|
8
|
+
};
|
|
9
|
+
for (let i = 0; i < args.length; i++) {
|
|
10
|
+
const arg = args[i];
|
|
11
|
+
const next = args[i + 1];
|
|
12
|
+
if (arg === '--dist' && next) {
|
|
13
|
+
result.dist = next;
|
|
14
|
+
i++;
|
|
15
|
+
}
|
|
16
|
+
else if (arg === '--preserve' && next) {
|
|
17
|
+
result.preserve = next.split(',').map((p) => p.trim());
|
|
18
|
+
i++;
|
|
19
|
+
}
|
|
20
|
+
else if (arg === '--prefix' && next) {
|
|
21
|
+
result.prefix = next;
|
|
22
|
+
i++;
|
|
23
|
+
}
|
|
24
|
+
else if (arg === '--strip' && next) {
|
|
25
|
+
result.strip = next;
|
|
26
|
+
i++;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
function generateExportName(jsPath, strip) {
|
|
32
|
+
let exportPath = jsPath.replace(/\.js$/, '');
|
|
33
|
+
if (strip && exportPath.startsWith(strip)) {
|
|
34
|
+
exportPath = exportPath.slice(strip.length);
|
|
35
|
+
if (exportPath.startsWith('/')) {
|
|
36
|
+
exportPath = exportPath.slice(1);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (exportPath.endsWith('/index')) {
|
|
40
|
+
exportPath = exportPath.slice(0, -6);
|
|
41
|
+
}
|
|
42
|
+
return `./${exportPath}`;
|
|
43
|
+
}
|
|
44
|
+
describe('post-build-exports', () => {
|
|
45
|
+
describe('parseArgs', () => {
|
|
46
|
+
it('should return defaults when no args provided', () => {
|
|
47
|
+
const result = parseArgs([]);
|
|
48
|
+
expect(result).toEqual({
|
|
49
|
+
dist: 'dist',
|
|
50
|
+
prefix: './',
|
|
51
|
+
preserve: [],
|
|
52
|
+
strip: '',
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
it('should parse --dist option', () => {
|
|
56
|
+
const result = parseArgs(['--dist', 'build']);
|
|
57
|
+
expect(result.dist).toBe('build');
|
|
58
|
+
});
|
|
59
|
+
it('should parse --preserve option with single value', () => {
|
|
60
|
+
const result = parseArgs(['--preserve', './tailwind.css']);
|
|
61
|
+
expect(result.preserve).toEqual(['./tailwind.css']);
|
|
62
|
+
});
|
|
63
|
+
it('should parse --preserve option with multiple comma-separated values', () => {
|
|
64
|
+
const result = parseArgs(['--preserve', './a.css, ./b.css, ./c.css']);
|
|
65
|
+
expect(result.preserve).toEqual(['./a.css', './b.css', './c.css']);
|
|
66
|
+
});
|
|
67
|
+
it('should parse --prefix option', () => {
|
|
68
|
+
const result = parseArgs(['--prefix', './lib/']);
|
|
69
|
+
expect(result.prefix).toBe('./lib/');
|
|
70
|
+
});
|
|
71
|
+
it('should parse --strip option', () => {
|
|
72
|
+
const result = parseArgs(['--strip', 'generated']);
|
|
73
|
+
expect(result.strip).toBe('generated');
|
|
74
|
+
});
|
|
75
|
+
it('should parse multiple options together', () => {
|
|
76
|
+
const result = parseArgs([
|
|
77
|
+
'--dist',
|
|
78
|
+
'output',
|
|
79
|
+
'--preserve',
|
|
80
|
+
'./styles.css',
|
|
81
|
+
'--strip',
|
|
82
|
+
'src',
|
|
83
|
+
]);
|
|
84
|
+
expect(result).toEqual({
|
|
85
|
+
dist: 'output',
|
|
86
|
+
prefix: './',
|
|
87
|
+
preserve: ['./styles.css'],
|
|
88
|
+
strip: 'src',
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
it('should ignore unknown options', () => {
|
|
92
|
+
const result = parseArgs(['--unknown', 'value', '--dist', 'build']);
|
|
93
|
+
expect(result.dist).toBe('build');
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
describe('generateExportName', () => {
|
|
97
|
+
it('should convert .js path to export name', () => {
|
|
98
|
+
expect(generateExportName('foo.js', '')).toBe('./foo');
|
|
99
|
+
});
|
|
100
|
+
it('should handle nested paths', () => {
|
|
101
|
+
expect(generateExportName('utils/helpers.js', '')).toBe('./utils/helpers');
|
|
102
|
+
});
|
|
103
|
+
it('should strip /index suffix', () => {
|
|
104
|
+
expect(generateExportName('components/button/index.js', '')).toBe('./components/button');
|
|
105
|
+
});
|
|
106
|
+
it('should apply strip prefix', () => {
|
|
107
|
+
expect(generateExportName('generated/icons/arrow.js', 'generated')).toBe('./icons/arrow');
|
|
108
|
+
});
|
|
109
|
+
it('should handle strip with leading slash', () => {
|
|
110
|
+
expect(generateExportName('generated/icons/arrow.js', 'generated/')).toBe('./icons/arrow');
|
|
111
|
+
});
|
|
112
|
+
it('should handle index.js at root', () => {
|
|
113
|
+
expect(generateExportName('index.js', '')).toBe('./index');
|
|
114
|
+
});
|
|
115
|
+
it('should handle deeply nested index files', () => {
|
|
116
|
+
expect(generateExportName('a/b/c/index.js', '')).toBe('./a/b/c');
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
});
|
package/dist/vitest/node.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
import type { TestUserConfig } from 'vitest/node';
|
|
2
|
+
export declare const coverageThresholds: {
|
|
3
|
+
branches: number;
|
|
4
|
+
functions: number;
|
|
5
|
+
lines: number;
|
|
6
|
+
statements: number;
|
|
7
|
+
};
|
|
2
8
|
export declare const vitestNodeConfig: TestUserConfig;
|
|
3
9
|
//# sourceMappingURL=node.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/vitest/node.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAMlD,eAAO,MAAM,gBAAgB,EAAE,
|
|
1
|
+
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/vitest/node.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAMlD,eAAO,MAAM,kBAAkB;;;;;CAK9B,CAAC;AAMF,eAAO,MAAM,gBAAgB,EAAE,cAS9B,CAAC"}
|
package/dist/vitest/node.js
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
|
+
export const coverageThresholds = {
|
|
2
|
+
branches: 80,
|
|
3
|
+
functions: 80,
|
|
4
|
+
lines: 80,
|
|
5
|
+
statements: 80,
|
|
6
|
+
};
|
|
1
7
|
export const vitestNodeConfig = {
|
|
8
|
+
coverage: {
|
|
9
|
+
provider: 'v8',
|
|
10
|
+
thresholds: coverageThresholds,
|
|
11
|
+
},
|
|
2
12
|
environment: 'node',
|
|
3
13
|
exclude: ['node_modules', 'dist', 'build', '.turbo', '.react-router'],
|
|
4
14
|
globals: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../../src/vitest/react.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../../src/vitest/react.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AASlD,eAAO,MAAM,iBAAiB,EAAE,cAU/B,CAAC"}
|
package/dist/vitest/react.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
+
import { coverageThresholds } from './node';
|
|
1
2
|
export const vitestReactConfig = {
|
|
3
|
+
coverage: {
|
|
4
|
+
provider: 'v8',
|
|
5
|
+
thresholds: coverageThresholds,
|
|
6
|
+
},
|
|
2
7
|
environment: 'jsdom',
|
|
3
8
|
exclude: ['node_modules', 'dist', 'build', '.turbo', '.react-router'],
|
|
4
9
|
globals: true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,60 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://www.schemastore.org/package.json",
|
|
3
|
+
"name": "@regardio/dev",
|
|
4
|
+
"version": "1.11.1",
|
|
5
|
+
"private": false,
|
|
6
|
+
"description": "Regardio developer tooling for testing, linting, and build workflows",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"biome",
|
|
9
|
+
"changesets",
|
|
10
|
+
"commitlint",
|
|
11
|
+
"dev",
|
|
12
|
+
"husky",
|
|
13
|
+
"linting",
|
|
14
|
+
"markdownlint",
|
|
15
|
+
"playwright",
|
|
16
|
+
"testing",
|
|
17
|
+
"tooling",
|
|
18
|
+
"typescript",
|
|
19
|
+
"vitest"
|
|
20
|
+
],
|
|
21
|
+
"homepage": "https://github.com/regardio/dev/blob/main/README.md",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/regardio/dev/issues"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/regardio/dev.git"
|
|
28
|
+
},
|
|
29
|
+
"license": "MIT",
|
|
3
30
|
"author": "Bernd Matzner <bernd.matzner@regard.io>",
|
|
31
|
+
"sideEffects": false,
|
|
32
|
+
"type": "module",
|
|
33
|
+
"exports": {
|
|
34
|
+
"./biome": "./src/biome/preset.json",
|
|
35
|
+
"./commitlint": "./src/commitlint/commitlint.cjs",
|
|
36
|
+
"./markdownlint": "./src/markdownlint/markdownlint.json",
|
|
37
|
+
"./markdownlint-cli2": "./src/markdownlint/markdownlint-cli2.jsonc",
|
|
38
|
+
"./playwright": {
|
|
39
|
+
"types": "./dist/playwright/index.d.ts",
|
|
40
|
+
"default": "./dist/playwright/index.js"
|
|
41
|
+
},
|
|
42
|
+
"./testing/setup-react": {
|
|
43
|
+
"types": "./dist/testing/setup-react.d.ts",
|
|
44
|
+
"default": "./dist/testing/setup-react.js"
|
|
45
|
+
},
|
|
46
|
+
"./typescript/base.json": "./src/typescript/base.json",
|
|
47
|
+
"./typescript/build.json": "./src/typescript/build.json",
|
|
48
|
+
"./typescript/react.json": "./src/typescript/react.json",
|
|
49
|
+
"./vitest/node": {
|
|
50
|
+
"types": "./dist/vitest/node.d.ts",
|
|
51
|
+
"default": "./dist/vitest/node.js"
|
|
52
|
+
},
|
|
53
|
+
"./vitest/react": {
|
|
54
|
+
"types": "./dist/vitest/react.d.ts",
|
|
55
|
+
"default": "./dist/vitest/react.js"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
4
58
|
"bin": {
|
|
5
59
|
"exec-clean": "dist/bin/exec-clean.js",
|
|
6
60
|
"exec-husky": "dist/bin/exec-husky.js",
|
|
@@ -13,10 +67,30 @@
|
|
|
13
67
|
"lint-biome": "dist/bin/lint-biome.js",
|
|
14
68
|
"lint-commit": "dist/bin/lint-commit.js",
|
|
15
69
|
"lint-md": "dist/bin/lint-md.js",
|
|
70
|
+
"lint-package": "dist/bin/lint-package.js",
|
|
16
71
|
"post-build-exports": "dist/bin/post-build-exports.js"
|
|
17
72
|
},
|
|
18
|
-
"
|
|
19
|
-
"
|
|
73
|
+
"files": [
|
|
74
|
+
"dist",
|
|
75
|
+
"src"
|
|
76
|
+
],
|
|
77
|
+
"scripts": {
|
|
78
|
+
"build": "tsc -p tsconfig.build.json",
|
|
79
|
+
"clean": "tsx src/bin/exec-clean.ts .turbo dist",
|
|
80
|
+
"fix": "run-p fix:*",
|
|
81
|
+
"fix:biome": "biome check --write --unsafe .",
|
|
82
|
+
"fix:md": "markdownlint-cli2 --fix \"**/*.md\" \"**/*.mdx\" \"!**/node_modules/**\" \"!**/dist/**\"",
|
|
83
|
+
"fix:pkg": "tsx src/bin/lint-package.ts",
|
|
84
|
+
"lint": "run-p lint:*",
|
|
85
|
+
"lint:biome": "biome check .",
|
|
86
|
+
"lint:md": "markdownlint-cli2 \"**/*.md\" \"**/*.mdx\" \"!**/node_modules/**\" \"!**/dist/**\"",
|
|
87
|
+
"prepare": "husky",
|
|
88
|
+
"release": "tsx src/bin/flow-release.ts",
|
|
89
|
+
"report": "vitest run --coverage",
|
|
90
|
+
"test": "run-p test:*",
|
|
91
|
+
"test:unit": "vitest run",
|
|
92
|
+
"typecheck": "tsc --noEmit",
|
|
93
|
+
"version": "tsx src/bin/flow-changeset.ts version"
|
|
20
94
|
},
|
|
21
95
|
"dependencies": {
|
|
22
96
|
"@biomejs/biome": "2.3.11",
|
|
@@ -29,6 +103,7 @@
|
|
|
29
103
|
"@testing-library/react": "16.3.1",
|
|
30
104
|
"@total-typescript/ts-reset": "0.6.1",
|
|
31
105
|
"@types/node": "25.0.3",
|
|
106
|
+
"@vitest/coverage-v8": "4.0.16",
|
|
32
107
|
"@vitest/ui": "4.0.16",
|
|
33
108
|
"husky": "9.1.7",
|
|
34
109
|
"jsdom": "27.4.0",
|
|
@@ -36,58 +111,12 @@
|
|
|
36
111
|
"npm-run-all": "4.1.5",
|
|
37
112
|
"postcss": "8.5.6",
|
|
38
113
|
"rimraf": "6.1.2",
|
|
114
|
+
"sort-package-json": "3.6.0",
|
|
39
115
|
"tsx": "4.21.0",
|
|
40
116
|
"typescript": "5.9.3",
|
|
41
117
|
"vite": "7.3.1",
|
|
42
118
|
"vitest": "4.0.16"
|
|
43
119
|
},
|
|
44
|
-
"description": "Regardio developer tooling for testing, linting, and build workflows",
|
|
45
|
-
"engines": {
|
|
46
|
-
"node": ">=18"
|
|
47
|
-
},
|
|
48
|
-
"exports": {
|
|
49
|
-
"./biome": "./src/biome/preset.json",
|
|
50
|
-
"./commitlint": "./src/commitlint/commitlint.cjs",
|
|
51
|
-
"./markdownlint": "./src/markdownlint/markdownlint.json",
|
|
52
|
-
"./markdownlint-cli2": "./src/markdownlint/markdownlint-cli2.jsonc",
|
|
53
|
-
"./playwright": {
|
|
54
|
-
"default": "./dist/playwright/index.js",
|
|
55
|
-
"types": "./dist/playwright/index.d.ts"
|
|
56
|
-
},
|
|
57
|
-
"./testing/setup-react": {
|
|
58
|
-
"default": "./dist/testing/setup-react.js",
|
|
59
|
-
"types": "./dist/testing/setup-react.d.ts"
|
|
60
|
-
},
|
|
61
|
-
"./typescript/base.json": "./src/typescript/base.json",
|
|
62
|
-
"./typescript/build.json": "./src/typescript/build.json",
|
|
63
|
-
"./typescript/react.json": "./src/typescript/react.json",
|
|
64
|
-
"./vitest/node": {
|
|
65
|
-
"default": "./dist/vitest/node.js",
|
|
66
|
-
"types": "./dist/vitest/node.d.ts"
|
|
67
|
-
},
|
|
68
|
-
"./vitest/react": {
|
|
69
|
-
"default": "./dist/vitest/react.js",
|
|
70
|
-
"types": "./dist/vitest/react.d.ts"
|
|
71
|
-
}
|
|
72
|
-
},
|
|
73
|
-
"files": ["dist", "src"],
|
|
74
|
-
"homepage": "https://github.com/regardio/dev/blob/main/README.md",
|
|
75
|
-
"keywords": [
|
|
76
|
-
"biome",
|
|
77
|
-
"changesets",
|
|
78
|
-
"commitlint",
|
|
79
|
-
"dev",
|
|
80
|
-
"husky",
|
|
81
|
-
"linting",
|
|
82
|
-
"markdownlint",
|
|
83
|
-
"playwright",
|
|
84
|
-
"testing",
|
|
85
|
-
"tooling",
|
|
86
|
-
"typescript",
|
|
87
|
-
"vitest"
|
|
88
|
-
],
|
|
89
|
-
"license": "MIT",
|
|
90
|
-
"name": "@regardio/dev",
|
|
91
120
|
"peerDependencies": {
|
|
92
121
|
"postcss": "8.4"
|
|
93
122
|
},
|
|
@@ -96,30 +125,10 @@
|
|
|
96
125
|
"optional": true
|
|
97
126
|
}
|
|
98
127
|
},
|
|
99
|
-
"
|
|
128
|
+
"engines": {
|
|
129
|
+
"node": ">=18"
|
|
130
|
+
},
|
|
100
131
|
"publishConfig": {
|
|
101
132
|
"access": "public"
|
|
102
|
-
}
|
|
103
|
-
"repository": {
|
|
104
|
-
"type": "git",
|
|
105
|
-
"url": "git+https://github.com/regardio/dev.git"
|
|
106
|
-
},
|
|
107
|
-
"scripts": {
|
|
108
|
-
"build": "pnpm exec tsc -p tsconfig.build.json",
|
|
109
|
-
"clean": "pnpm exec tsx src/bin/exec-clean.ts .turbo dist",
|
|
110
|
-
"fix": "exec-p fix:*",
|
|
111
|
-
"fix:biome": "biome check --write --unsafe .",
|
|
112
|
-
"fix:md": "markdownlint-cli2 --fix \"**/*.md\" \"**/*.mdx\" \"!**/node_modules/**\" \"!**/dist/**\"",
|
|
113
|
-
"lint": "run-p lint:*",
|
|
114
|
-
"lint:biome": "biome check .",
|
|
115
|
-
"lint:md": "markdownlint-cli2 \"**/*.md\" \"**/*.mdx\" \"!**/node_modules/**\" \"!**/dist/**\"",
|
|
116
|
-
"prepare": "husky",
|
|
117
|
-
"release": "pnpm exec tsx src/bin/flow-release.ts",
|
|
118
|
-
"test": "run-p test:*",
|
|
119
|
-
"test:unit": "vitest run",
|
|
120
|
-
"version": "flow-changeset version"
|
|
121
|
-
},
|
|
122
|
-
"sideEffects": false,
|
|
123
|
-
"type": "module",
|
|
124
|
-
"version": "1.10.3"
|
|
133
|
+
}
|
|
125
134
|
}
|
package/src/bin/flow-release.ts
CHANGED
|
@@ -70,6 +70,18 @@ if (!packageName) {
|
|
|
70
70
|
|
|
71
71
|
console.log(`Releasing ${packageName} with ${bumpType} bump...`);
|
|
72
72
|
|
|
73
|
+
// Run quality checks before release
|
|
74
|
+
console.log('Running quality checks...');
|
|
75
|
+
try {
|
|
76
|
+
run('pnpm build');
|
|
77
|
+
run('pnpm typecheck');
|
|
78
|
+
run('pnpm report');
|
|
79
|
+
} catch {
|
|
80
|
+
console.error('Quality checks failed. Fix issues before releasing.');
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
console.log('✅ Quality checks passed');
|
|
84
|
+
|
|
73
85
|
// Ensure we're in a clean git state
|
|
74
86
|
try {
|
|
75
87
|
const status = runQuiet('git status --porcelain');
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Reorder exports conditions: types must come before default for TypeScript.
|
|
5
|
+
* Extracted from lint-package.ts for testing.
|
|
6
|
+
*/
|
|
7
|
+
function reorderConditions(obj: Record<string, unknown>): Record<string, unknown> {
|
|
8
|
+
function processObject(o: Record<string, unknown>): Record<string, unknown> {
|
|
9
|
+
if (typeof o !== 'object' || o === null) return o;
|
|
10
|
+
|
|
11
|
+
// First, recursively process all nested objects
|
|
12
|
+
const processed: Record<string, unknown> = {};
|
|
13
|
+
for (const [key, value] of Object.entries(o)) {
|
|
14
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
15
|
+
processed[key] = processObject(value as Record<string, unknown>);
|
|
16
|
+
} else {
|
|
17
|
+
processed[key] = value;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Then check if this object has both 'types' and 'default' keys
|
|
22
|
+
if ('types' in processed && 'default' in processed) {
|
|
23
|
+
const keys = Object.keys(processed);
|
|
24
|
+
const typesIndex = keys.indexOf('types');
|
|
25
|
+
const defaultIndex = keys.indexOf('default');
|
|
26
|
+
|
|
27
|
+
// If default comes before types, reorder
|
|
28
|
+
if (defaultIndex < typesIndex) {
|
|
29
|
+
const reordered: Record<string, unknown> = {};
|
|
30
|
+
reordered.types = processed.types;
|
|
31
|
+
for (const key of keys) {
|
|
32
|
+
if (key !== 'types') {
|
|
33
|
+
reordered[key] = processed[key];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return reordered;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return processed;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return processObject(obj);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe('lint-package', () => {
|
|
47
|
+
describe('reorderConditions', () => {
|
|
48
|
+
it('should reorder types before default when default comes first', () => {
|
|
49
|
+
const input = {
|
|
50
|
+
'./foo': {
|
|
51
|
+
default: './dist/foo.js',
|
|
52
|
+
types: './dist/foo.d.ts',
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const result = reorderConditions(input);
|
|
57
|
+
|
|
58
|
+
expect(Object.keys(result['./foo'] as Record<string, unknown>)).toEqual(['types', 'default']);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should not modify when types already comes before default', () => {
|
|
62
|
+
const input = {
|
|
63
|
+
'./foo': {
|
|
64
|
+
default: './dist/foo.js',
|
|
65
|
+
types: './dist/foo.d.ts',
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const result = reorderConditions(input);
|
|
70
|
+
|
|
71
|
+
expect(Object.keys(result['./foo'] as Record<string, unknown>)).toEqual(['types', 'default']);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should handle multiple exports with mixed order', () => {
|
|
75
|
+
const input = {
|
|
76
|
+
'./a': {
|
|
77
|
+
default: './dist/a.js',
|
|
78
|
+
types: './dist/a.d.ts',
|
|
79
|
+
},
|
|
80
|
+
'./b': {
|
|
81
|
+
default: './dist/b.js',
|
|
82
|
+
types: './dist/b.d.ts',
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const result = reorderConditions(input);
|
|
87
|
+
|
|
88
|
+
expect(Object.keys(result['./a'] as Record<string, unknown>)[0]).toBe('types');
|
|
89
|
+
expect(Object.keys(result['./b'] as Record<string, unknown>)[0]).toBe('types');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should preserve other keys after types', () => {
|
|
93
|
+
const input = {
|
|
94
|
+
'./foo': {
|
|
95
|
+
default: './dist/foo.js',
|
|
96
|
+
import: './dist/foo.mjs',
|
|
97
|
+
require: './dist/foo.cjs',
|
|
98
|
+
types: './dist/foo.d.ts',
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const result = reorderConditions(input);
|
|
103
|
+
const keys = Object.keys(result['./foo'] as Record<string, unknown>);
|
|
104
|
+
|
|
105
|
+
expect(keys[0]).toBe('types');
|
|
106
|
+
expect(keys.slice(1)).toEqual(['default', 'import', 'require']);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should handle exports without types or default', () => {
|
|
110
|
+
const input = {
|
|
111
|
+
'./styles.css': './dist/styles.css',
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const result = reorderConditions(input);
|
|
115
|
+
|
|
116
|
+
expect(result).toEqual(input);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should handle deeply nested condition objects', () => {
|
|
120
|
+
const input = {
|
|
121
|
+
'./foo': {
|
|
122
|
+
browser: {
|
|
123
|
+
default: './dist/foo.browser.js',
|
|
124
|
+
types: './dist/foo.browser.d.ts',
|
|
125
|
+
},
|
|
126
|
+
node: {
|
|
127
|
+
default: './dist/foo.node.js',
|
|
128
|
+
types: './dist/foo.node.d.ts',
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const result = reorderConditions(input);
|
|
134
|
+
const foo = result['./foo'] as Record<string, Record<string, unknown>>;
|
|
135
|
+
|
|
136
|
+
expect(Object.keys(foo.node as object)[0]).toBe('types');
|
|
137
|
+
expect(Object.keys(foo.browser as object)[0]).toBe('types');
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
});
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Sorts package.json files using sort-package-json and fixes
|
|
4
|
+
* exports condition order (types must come before default for TypeScript).
|
|
5
|
+
*/
|
|
6
|
+
import { execSync } from 'node:child_process';
|
|
7
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
8
|
+
import { dirname, join, resolve } from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const devRoot = resolve(__dirname, '../..');
|
|
13
|
+
|
|
14
|
+
// Find sort-package-json binary
|
|
15
|
+
const sortPkgBin = join(devRoot, 'node_modules/.bin/sort-package-json');
|
|
16
|
+
const sortPkgBinAlt = join(devRoot, 'node_modules/sort-package-json/cli.js');
|
|
17
|
+
|
|
18
|
+
let bin = '';
|
|
19
|
+
if (existsSync(sortPkgBin)) {
|
|
20
|
+
bin = sortPkgBin;
|
|
21
|
+
} else if (existsSync(sortPkgBinAlt)) {
|
|
22
|
+
bin = `node ${sortPkgBinAlt}`;
|
|
23
|
+
} else {
|
|
24
|
+
bin = 'npx sort-package-json';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Get args passed to this script
|
|
28
|
+
const args = process.argv.slice(2);
|
|
29
|
+
const fixMode = args.includes('--fix');
|
|
30
|
+
const files = args.filter((arg) => arg !== '--fix');
|
|
31
|
+
const targets = files.length > 0 ? files : ['package.json'];
|
|
32
|
+
|
|
33
|
+
// Run sort-package-json
|
|
34
|
+
try {
|
|
35
|
+
const checkFlag = fixMode ? '' : '--check';
|
|
36
|
+
execSync(`${bin} ${checkFlag} ${targets.join(' ')}`.trim(), { stdio: 'inherit' });
|
|
37
|
+
} catch {
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Fix exports condition order: types must come before default for TypeScript.
|
|
43
|
+
* See: https://www.typescriptlang.org/docs/handbook/esm-node.html
|
|
44
|
+
* Returns true if the file needs changes.
|
|
45
|
+
*/
|
|
46
|
+
function fixExportsOrder(filePath: string, fix: boolean): boolean {
|
|
47
|
+
const fullPath = resolve(process.cwd(), filePath);
|
|
48
|
+
if (!existsSync(fullPath)) return false;
|
|
49
|
+
|
|
50
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
51
|
+
const pkg = JSON.parse(content) as Record<string, unknown>;
|
|
52
|
+
|
|
53
|
+
if (!pkg.exports || typeof pkg.exports !== 'object') return false;
|
|
54
|
+
|
|
55
|
+
let modified = false;
|
|
56
|
+
|
|
57
|
+
function reorderConditions(obj: Record<string, unknown>): Record<string, unknown> {
|
|
58
|
+
if (typeof obj !== 'object' || obj === null) return obj;
|
|
59
|
+
|
|
60
|
+
// Check if this object has both 'types' and 'default' keys
|
|
61
|
+
if ('types' in obj && 'default' in obj) {
|
|
62
|
+
const keys = Object.keys(obj);
|
|
63
|
+
const typesIndex = keys.indexOf('types');
|
|
64
|
+
const defaultIndex = keys.indexOf('default');
|
|
65
|
+
|
|
66
|
+
// If default comes before types, reorder
|
|
67
|
+
if (defaultIndex < typesIndex) {
|
|
68
|
+
modified = true;
|
|
69
|
+
const reordered: Record<string, unknown> = {};
|
|
70
|
+
// Put types first, then all other keys in original order
|
|
71
|
+
reordered.types = obj.types;
|
|
72
|
+
for (const key of keys) {
|
|
73
|
+
if (key !== 'types') {
|
|
74
|
+
reordered[key] = obj[key];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return reordered;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Recursively process nested objects
|
|
82
|
+
const result: Record<string, unknown> = {};
|
|
83
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
84
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
85
|
+
result[key] = reorderConditions(value as Record<string, unknown>);
|
|
86
|
+
} else {
|
|
87
|
+
result[key] = value;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
pkg.exports = reorderConditions(pkg.exports as Record<string, unknown>);
|
|
94
|
+
|
|
95
|
+
if (modified && fix) {
|
|
96
|
+
writeFileSync(fullPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return modified;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Fix exports order in each file
|
|
103
|
+
let hasExportsIssues = false;
|
|
104
|
+
for (const file of targets) {
|
|
105
|
+
const needsFix = fixExportsOrder(file, fixMode);
|
|
106
|
+
if (needsFix && !fixMode) {
|
|
107
|
+
console.error(`${file}: exports condition order is incorrect (types must come before default)`);
|
|
108
|
+
hasExportsIssues = true;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (hasExportsIssues) {
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Parse command line arguments for post-build-exports.
|
|
5
|
+
* Extracted from post-build-exports.ts for testing.
|
|
6
|
+
*/
|
|
7
|
+
function parseArgs(args: string[]): {
|
|
8
|
+
dist: string;
|
|
9
|
+
preserve: string[];
|
|
10
|
+
prefix: string;
|
|
11
|
+
strip: string;
|
|
12
|
+
} {
|
|
13
|
+
const result = {
|
|
14
|
+
dist: 'dist',
|
|
15
|
+
prefix: './',
|
|
16
|
+
preserve: [] as string[],
|
|
17
|
+
strip: '',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
for (let i = 0; i < args.length; i++) {
|
|
21
|
+
const arg = args[i];
|
|
22
|
+
const next = args[i + 1];
|
|
23
|
+
|
|
24
|
+
if (arg === '--dist' && next) {
|
|
25
|
+
result.dist = next;
|
|
26
|
+
i++;
|
|
27
|
+
} else if (arg === '--preserve' && next) {
|
|
28
|
+
result.preserve = next.split(',').map((p) => p.trim());
|
|
29
|
+
i++;
|
|
30
|
+
} else if (arg === '--prefix' && next) {
|
|
31
|
+
result.prefix = next;
|
|
32
|
+
i++;
|
|
33
|
+
} else if (arg === '--strip' && next) {
|
|
34
|
+
result.strip = next;
|
|
35
|
+
i++;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Generate export name from JS file path.
|
|
44
|
+
* Extracted from post-build-exports.ts for testing.
|
|
45
|
+
*/
|
|
46
|
+
function generateExportName(jsPath: string, strip: string): string {
|
|
47
|
+
let exportPath = jsPath.replace(/\.js$/, '');
|
|
48
|
+
|
|
49
|
+
if (strip && exportPath.startsWith(strip)) {
|
|
50
|
+
exportPath = exportPath.slice(strip.length);
|
|
51
|
+
if (exportPath.startsWith('/')) {
|
|
52
|
+
exportPath = exportPath.slice(1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Strip /index suffix for cleaner exports
|
|
57
|
+
if (exportPath.endsWith('/index')) {
|
|
58
|
+
exportPath = exportPath.slice(0, -6);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return `./${exportPath}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
describe('post-build-exports', () => {
|
|
65
|
+
describe('parseArgs', () => {
|
|
66
|
+
it('should return defaults when no args provided', () => {
|
|
67
|
+
const result = parseArgs([]);
|
|
68
|
+
|
|
69
|
+
expect(result).toEqual({
|
|
70
|
+
dist: 'dist',
|
|
71
|
+
prefix: './',
|
|
72
|
+
preserve: [],
|
|
73
|
+
strip: '',
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should parse --dist option', () => {
|
|
78
|
+
const result = parseArgs(['--dist', 'build']);
|
|
79
|
+
|
|
80
|
+
expect(result.dist).toBe('build');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should parse --preserve option with single value', () => {
|
|
84
|
+
const result = parseArgs(['--preserve', './tailwind.css']);
|
|
85
|
+
|
|
86
|
+
expect(result.preserve).toEqual(['./tailwind.css']);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should parse --preserve option with multiple comma-separated values', () => {
|
|
90
|
+
const result = parseArgs(['--preserve', './a.css, ./b.css, ./c.css']);
|
|
91
|
+
|
|
92
|
+
expect(result.preserve).toEqual(['./a.css', './b.css', './c.css']);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should parse --prefix option', () => {
|
|
96
|
+
const result = parseArgs(['--prefix', './lib/']);
|
|
97
|
+
|
|
98
|
+
expect(result.prefix).toBe('./lib/');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should parse --strip option', () => {
|
|
102
|
+
const result = parseArgs(['--strip', 'generated']);
|
|
103
|
+
|
|
104
|
+
expect(result.strip).toBe('generated');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should parse multiple options together', () => {
|
|
108
|
+
const result = parseArgs([
|
|
109
|
+
'--dist',
|
|
110
|
+
'output',
|
|
111
|
+
'--preserve',
|
|
112
|
+
'./styles.css',
|
|
113
|
+
'--strip',
|
|
114
|
+
'src',
|
|
115
|
+
]);
|
|
116
|
+
|
|
117
|
+
expect(result).toEqual({
|
|
118
|
+
dist: 'output',
|
|
119
|
+
prefix: './',
|
|
120
|
+
preserve: ['./styles.css'],
|
|
121
|
+
strip: 'src',
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should ignore unknown options', () => {
|
|
126
|
+
const result = parseArgs(['--unknown', 'value', '--dist', 'build']);
|
|
127
|
+
|
|
128
|
+
expect(result.dist).toBe('build');
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe('generateExportName', () => {
|
|
133
|
+
it('should convert .js path to export name', () => {
|
|
134
|
+
expect(generateExportName('foo.js', '')).toBe('./foo');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should handle nested paths', () => {
|
|
138
|
+
expect(generateExportName('utils/helpers.js', '')).toBe('./utils/helpers');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should strip /index suffix', () => {
|
|
142
|
+
expect(generateExportName('components/button/index.js', '')).toBe('./components/button');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should apply strip prefix', () => {
|
|
146
|
+
expect(generateExportName('generated/icons/arrow.js', 'generated')).toBe('./icons/arrow');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should handle strip with leading slash', () => {
|
|
150
|
+
expect(generateExportName('generated/icons/arrow.js', 'generated/')).toBe('./icons/arrow');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should handle index.js at root', () => {
|
|
154
|
+
expect(generateExportName('index.js', '')).toBe('./index');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should handle deeply nested index files', () => {
|
|
158
|
+
expect(generateExportName('a/b/c/index.js', '')).toBe('./a/b/c');
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
});
|
package/src/biome/preset.json
CHANGED
package/src/vitest/node.ts
CHANGED
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
import type { TestUserConfig } from 'vitest/node';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Coverage thresholds for library packages.
|
|
5
|
+
* These ensure adequate test coverage before publishing.
|
|
6
|
+
*/
|
|
7
|
+
export const coverageThresholds = {
|
|
8
|
+
branches: 80,
|
|
9
|
+
functions: 80,
|
|
10
|
+
lines: 80,
|
|
11
|
+
statements: 80,
|
|
12
|
+
};
|
|
13
|
+
|
|
3
14
|
/**
|
|
4
15
|
* Base Vitest configuration for Node.js packages.
|
|
5
16
|
* Use with defineConfig() in your vitest.config.ts
|
|
6
17
|
*/
|
|
7
18
|
export const vitestNodeConfig: TestUserConfig = {
|
|
19
|
+
coverage: {
|
|
20
|
+
provider: 'v8',
|
|
21
|
+
thresholds: coverageThresholds,
|
|
22
|
+
},
|
|
8
23
|
environment: 'node',
|
|
9
24
|
exclude: ['node_modules', 'dist', 'build', '.turbo', '.react-router'],
|
|
10
25
|
globals: true,
|
package/src/vitest/react.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { TestUserConfig } from 'vitest/node';
|
|
2
|
+
import { coverageThresholds } from './node';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Vitest configuration for React packages with jsdom environment.
|
|
@@ -7,6 +8,10 @@ import type { TestUserConfig } from 'vitest/node';
|
|
|
7
8
|
* Requires a setup file that imports '@testing-library/jest-dom/vitest'
|
|
8
9
|
*/
|
|
9
10
|
export const vitestReactConfig: TestUserConfig = {
|
|
11
|
+
coverage: {
|
|
12
|
+
provider: 'v8',
|
|
13
|
+
thresholds: coverageThresholds,
|
|
14
|
+
},
|
|
10
15
|
environment: 'jsdom',
|
|
11
16
|
exclude: ['node_modules', 'dist', 'build', '.turbo', '.react-router'],
|
|
12
17
|
globals: true,
|