create-packkit 0.1.2 → 0.2.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 +2 -1
- package/package.json +1 -1
- package/src/cli/args.js +1 -0
- package/src/core/features/bundler.js +1 -1
- package/src/core/features/index.js +2 -0
- package/src/core/features/meta.js +35 -3
- package/src/core/features/react.js +25 -0
- package/src/core/features/test.js +27 -3
- package/src/core/features/typescript.js +2 -1
- package/src/core/options.js +13 -0
- package/src/core/presets.js +8 -0
package/README.md
CHANGED
|
@@ -35,6 +35,7 @@ No install needed: **[danmat.github.io/create-packkit](https://danmat.github.io/
|
|
|
35
35
|
| **Language** | TypeScript (strict) · JavaScript (ESM) |
|
|
36
36
|
| **Module format** | ESM · CJS · dual (proper `exports` map) |
|
|
37
37
|
| **Target** | Library · CLI tool · both |
|
|
38
|
+
| **Framework** | None · **React** (component library — JSX, peer deps, jsdom tests) |
|
|
38
39
|
| **Bundler** | tsup · tsdown · unbuild · rollup · none (tsc) |
|
|
39
40
|
| **Tests** | Vitest · Jest · node:test · none (+ coverage) |
|
|
40
41
|
| **Lint/format** | ESLint + Prettier · Biome · oxlint · none |
|
|
@@ -47,7 +48,7 @@ No install needed: **[danmat.github.io/create-packkit](https://danmat.github.io/
|
|
|
47
48
|
|
|
48
49
|
## Presets
|
|
49
50
|
|
|
50
|
-
`ts-lib` · `js-lib` · `ts-cli` / `cli` · `minimal` · `full` — named bundles of the options above.
|
|
51
|
+
`ts-lib` · `js-lib` · `ts-cli` / `cli` · `react-lib` · `react-lib-js` · `oss` · `minimal` · `full` — named bundles of the options above. See the [roadmap](ROADMAP.md) for what's next.
|
|
51
52
|
|
|
52
53
|
## How it works
|
|
53
54
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-packkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Highly configurable scaffolder for modern npm packages and CLIs — pick your stack (TS/JS, bundler, tests, linter, CI, releases) from a CLI or a web configurator.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/cli/args.js
CHANGED
|
@@ -33,7 +33,7 @@ export default {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
// ---- build tooling ----
|
|
36
|
-
const entries = ['src/index.' + cfg.
|
|
36
|
+
const entries = ['src/index.' + cfg.srcExt];
|
|
37
37
|
if (cfg.hasCli) entries.push('src/cli.' + cfg.ext);
|
|
38
38
|
const formats = [cfg.hasEsm && 'esm', cfg.hasCjs && 'cjs'].filter(Boolean);
|
|
39
39
|
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import meta from './meta.js';
|
|
5
5
|
import bundler from './bundler.js';
|
|
6
6
|
import typescript from './typescript.js';
|
|
7
|
+
import react from './react.js';
|
|
7
8
|
import test from './test.js';
|
|
8
9
|
import lint from './lint.js';
|
|
9
10
|
import githooks from './githooks.js';
|
|
@@ -19,6 +20,7 @@ export default [
|
|
|
19
20
|
meta,
|
|
20
21
|
bundler,
|
|
21
22
|
typescript,
|
|
23
|
+
react,
|
|
22
24
|
test,
|
|
23
25
|
lint,
|
|
24
26
|
githooks,
|
|
@@ -24,8 +24,7 @@ export default {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
// Source entry point.
|
|
27
|
-
|
|
28
|
-
files[`src/index.${ext}`] = cfg.hasLibrary
|
|
27
|
+
files[`src/index.${cfg.srcExt}`] = cfg.hasLibrary
|
|
29
28
|
? libraryEntry(cfg)
|
|
30
29
|
: `// ${cfg.name}\n`;
|
|
31
30
|
|
|
@@ -43,6 +42,7 @@ export default {
|
|
|
43
42
|
};
|
|
44
43
|
|
|
45
44
|
function libraryEntry(cfg) {
|
|
45
|
+
if (cfg.isReact) return reactEntry(cfg);
|
|
46
46
|
if (cfg.isTs) {
|
|
47
47
|
return [
|
|
48
48
|
`/** Greet someone by name. */`,
|
|
@@ -65,6 +65,34 @@ function libraryEntry(cfg) {
|
|
|
65
65
|
].join('\n');
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
function reactEntry(cfg) {
|
|
69
|
+
if (cfg.isTs) {
|
|
70
|
+
return [
|
|
71
|
+
`export interface ButtonProps {`,
|
|
72
|
+
`\t/** Text shown inside the button. */`,
|
|
73
|
+
`\tlabel: string;`,
|
|
74
|
+
`\tonClick?: () => void;`,
|
|
75
|
+
`}`,
|
|
76
|
+
``,
|
|
77
|
+
`/** A tiny example component — replace me. */`,
|
|
78
|
+
`export function Button({ label, onClick }: ButtonProps) {`,
|
|
79
|
+
`\treturn <button onClick={onClick}>{label}</button>;`,
|
|
80
|
+
`}`,
|
|
81
|
+
``,
|
|
82
|
+
].join('\n');
|
|
83
|
+
}
|
|
84
|
+
return [
|
|
85
|
+
`/**`,
|
|
86
|
+
` * A tiny example component — replace me.`,
|
|
87
|
+
` * @param {{ label: string, onClick?: () => void }} props`,
|
|
88
|
+
` */`,
|
|
89
|
+
`export function Button({ label, onClick }) {`,
|
|
90
|
+
`\treturn <button onClick={onClick}>{label}</button>;`,
|
|
91
|
+
`}`,
|
|
92
|
+
``,
|
|
93
|
+
].join('\n');
|
|
94
|
+
}
|
|
95
|
+
|
|
68
96
|
function readme(cfg) {
|
|
69
97
|
const install = {
|
|
70
98
|
npm: `npm install ${cfg.name}`,
|
|
@@ -86,7 +114,11 @@ function readme(cfg) {
|
|
|
86
114
|
'',
|
|
87
115
|
];
|
|
88
116
|
|
|
89
|
-
if (cfg.hasLibrary) {
|
|
117
|
+
if (cfg.hasLibrary && cfg.isReact) {
|
|
118
|
+
lines.push('## Usage', '', '```' + (cfg.isTs ? 'tsx' : 'jsx'),
|
|
119
|
+
`import { Button } from '${cfg.name}';`, '',
|
|
120
|
+
`<Button label="Click me" onClick={() => alert('hi')} />`, '```', '');
|
|
121
|
+
} else if (cfg.hasLibrary) {
|
|
90
122
|
const imp = cfg.isTs || cfg.hasEsm ? `import { greet } from '${cfg.name}';` : `const { greet } = require('${cfg.name}');`;
|
|
91
123
|
lines.push('## Usage', '', '```' + (cfg.isTs ? 'ts' : 'js'), imp, '', `greet('world'); // "Hello, world!"`, '```', '');
|
|
92
124
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// React component-library support: react/react-dom as peers (so consumers bring
|
|
2
|
+
// their own), plus dev copies for building and testing. tsup/rollup externalize
|
|
3
|
+
// peerDependencies automatically, so React is never bundled into your package.
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
id: 'react',
|
|
7
|
+
active: (cfg) => cfg.isReact,
|
|
8
|
+
apply(cfg) {
|
|
9
|
+
const pkg = {
|
|
10
|
+
peerDependencies: {
|
|
11
|
+
react: '>=18',
|
|
12
|
+
'react-dom': '>=18',
|
|
13
|
+
},
|
|
14
|
+
devDependencies: {
|
|
15
|
+
react: '^18.3.0',
|
|
16
|
+
'react-dom': '^18.3.0',
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
if (cfg.isTs) {
|
|
20
|
+
pkg.devDependencies['@types/react'] = '^18.3.0';
|
|
21
|
+
pkg.devDependencies['@types/react-dom'] = '^18.3.0';
|
|
22
|
+
}
|
|
23
|
+
return { files: {}, pkg };
|
|
24
|
+
},
|
|
25
|
+
};
|
|
@@ -8,12 +8,16 @@ export default {
|
|
|
8
8
|
const pkg = { scripts: {}, devDependencies: {} };
|
|
9
9
|
const ext = cfg.ext;
|
|
10
10
|
|
|
11
|
+
const testExt = cfg.isReact ? cfg.srcExt : ext;
|
|
12
|
+
|
|
11
13
|
if (cfg.test === 'vitest') {
|
|
12
14
|
files[`vitest.config.${ext}`] = [
|
|
13
15
|
`import { defineConfig } from 'vitest/config';`,
|
|
14
16
|
``,
|
|
15
17
|
`export default defineConfig({`,
|
|
16
18
|
`\ttest: {`,
|
|
19
|
+
cfg.isReact ? `\t\tenvironment: 'jsdom',` : null,
|
|
20
|
+
cfg.isReact ? `\t\tglobals: true,` : null,
|
|
17
21
|
cfg.coverage ? `\t\tcoverage: { provider: 'v8', reporter: ['text', 'lcov'] },` : null,
|
|
18
22
|
`\t},`,
|
|
19
23
|
`});`,
|
|
@@ -22,11 +26,16 @@ export default {
|
|
|
22
26
|
pkg.scripts.test = 'vitest run';
|
|
23
27
|
pkg.scripts['test:watch'] = 'vitest';
|
|
24
28
|
pkg.devDependencies.vitest = '^2.0.0';
|
|
29
|
+
if (cfg.isReact) {
|
|
30
|
+
pkg.devDependencies.jsdom = '^25.0.0';
|
|
31
|
+
pkg.devDependencies['@testing-library/react'] = '^16.0.0';
|
|
32
|
+
pkg.devDependencies['@testing-library/dom'] = '^10.0.0';
|
|
33
|
+
}
|
|
25
34
|
if (cfg.coverage) {
|
|
26
35
|
pkg.scripts.coverage = 'vitest run --coverage';
|
|
27
36
|
pkg.devDependencies['@vitest/coverage-v8'] = '^2.0.0';
|
|
28
37
|
}
|
|
29
|
-
files[`src/index.test.${
|
|
38
|
+
files[`src/index.test.${testExt}`] = exampleTest('vitest', cfg);
|
|
30
39
|
} else if (cfg.test === 'jest') {
|
|
31
40
|
files['jest.config.js'] = jestConfig(cfg);
|
|
32
41
|
pkg.scripts.test = 'jest';
|
|
@@ -37,11 +46,11 @@ export default {
|
|
|
37
46
|
pkg.devDependencies['@types/jest'] = '^29.0.0';
|
|
38
47
|
}
|
|
39
48
|
if (cfg.coverage) pkg.scripts.coverage = 'jest --coverage';
|
|
40
|
-
files[`src/index.test.${
|
|
49
|
+
files[`src/index.test.${testExt}`] = exampleTest('jest', cfg);
|
|
41
50
|
} else if (cfg.test === 'node') {
|
|
42
51
|
pkg.scripts.test = cfg.isTs ? 'node --import tsx --test "src/**/*.test.ts"' : 'node --test';
|
|
43
52
|
if (cfg.isTs) pkg.devDependencies.tsx = '^4.0.0';
|
|
44
|
-
files[`src/index.test.${
|
|
53
|
+
files[`src/index.test.${testExt}`] = exampleTest('node', cfg);
|
|
45
54
|
}
|
|
46
55
|
|
|
47
56
|
return { files, pkg };
|
|
@@ -56,6 +65,21 @@ function importPath(runner, cfg) {
|
|
|
56
65
|
|
|
57
66
|
function exampleTest(runner, cfg) {
|
|
58
67
|
const imp = importPath(runner, cfg);
|
|
68
|
+
if (cfg.isReact) {
|
|
69
|
+
const api = runner === 'jest' ? '' : `import { describe, it, expect } from 'vitest';\n`;
|
|
70
|
+
return [
|
|
71
|
+
api + `import { render, screen } from '@testing-library/react';`,
|
|
72
|
+
`import { Button } from '${imp}';`,
|
|
73
|
+
``,
|
|
74
|
+
`describe('Button', () => {`,
|
|
75
|
+
`\tit('renders its label', () => {`,
|
|
76
|
+
`\t\trender(<Button label="Click me" />);`,
|
|
77
|
+
`\t\texpect(screen.getByText('Click me')).toBeDefined();`,
|
|
78
|
+
`\t});`,
|
|
79
|
+
`});`,
|
|
80
|
+
``,
|
|
81
|
+
].join('\n');
|
|
82
|
+
}
|
|
59
83
|
if (runner === 'node') {
|
|
60
84
|
return [
|
|
61
85
|
`import { test } from 'node:test';`,
|
|
@@ -9,7 +9,8 @@ export default {
|
|
|
9
9
|
target: 'ES2022',
|
|
10
10
|
module: 'ESNext',
|
|
11
11
|
moduleResolution: 'Bundler',
|
|
12
|
-
lib: ['ES2022'],
|
|
12
|
+
lib: cfg.isReact ? ['ES2022', 'DOM', 'DOM.Iterable'] : ['ES2022'],
|
|
13
|
+
...(cfg.isReact ? { jsx: 'react-jsx' } : {}),
|
|
13
14
|
strict: true,
|
|
14
15
|
noUncheckedIndexedAccess: true,
|
|
15
16
|
esModuleInterop: true,
|
package/src/core/options.js
CHANGED
|
@@ -35,6 +35,13 @@ export const OPTIONS = {
|
|
|
35
35
|
{ value: 'cli', label: 'CLI tool (ships a bin)' },
|
|
36
36
|
],
|
|
37
37
|
},
|
|
38
|
+
framework: {
|
|
39
|
+
group: 'core', type: 'select', label: 'Framework', default: 'none',
|
|
40
|
+
choices: [
|
|
41
|
+
{ value: 'none', label: 'None (plain package)' },
|
|
42
|
+
{ value: 'react', label: 'React (component library)' },
|
|
43
|
+
],
|
|
44
|
+
},
|
|
38
45
|
packageManager: {
|
|
39
46
|
group: 'core', type: 'select', label: 'Package manager', default: 'npm',
|
|
40
47
|
choices: [
|
|
@@ -183,8 +190,14 @@ export function normalizeConfig(input = {}) {
|
|
|
183
190
|
if (cfg.workflows.includes('codecov')) cfg.coverage = true;
|
|
184
191
|
// npm-publish + changesets are complementary; nothing to coerce, just noted.
|
|
185
192
|
|
|
193
|
+
cfg.isReact = cfg.framework === 'react';
|
|
194
|
+
// A React component library is a library; make sure it's targeted.
|
|
195
|
+
if (cfg.isReact && !cfg.target.includes('library')) cfg.target = ['library', ...cfg.target];
|
|
196
|
+
|
|
186
197
|
cfg.isTs = cfg.language === 'ts';
|
|
187
198
|
cfg.ext = cfg.isTs ? 'ts' : 'js';
|
|
199
|
+
// Source files carry JSX for React; config files keep .ts/.js.
|
|
200
|
+
cfg.srcExt = cfg.isReact ? (cfg.isTs ? 'tsx' : 'jsx') : cfg.ext;
|
|
188
201
|
cfg.hasLibrary = cfg.target.includes('library');
|
|
189
202
|
cfg.hasCli = cfg.target.includes('cli');
|
|
190
203
|
cfg.hasEsm = cfg.moduleFormat === 'esm' || cfg.moduleFormat === 'dual';
|
package/src/core/presets.js
CHANGED
|
@@ -6,6 +6,14 @@ export const PRESETS = {
|
|
|
6
6
|
'js-lib': { language: 'js', target: ['library'], moduleFormat: 'dual', bundler: 'tsup' },
|
|
7
7
|
'ts-cli': { language: 'ts', target: ['cli', 'library'], moduleFormat: 'esm' },
|
|
8
8
|
cli: { language: 'ts', target: ['cli', 'library'], moduleFormat: 'esm' },
|
|
9
|
+
'react-lib': { language: 'ts', framework: 'react', target: ['library'], moduleFormat: 'dual', test: 'vitest' },
|
|
10
|
+
'react-lib-js': { language: 'js', framework: 'react', target: ['library'], moduleFormat: 'dual', bundler: 'tsup', test: 'vitest' },
|
|
11
|
+
oss: {
|
|
12
|
+
language: 'ts', target: ['library'], moduleFormat: 'dual', bundler: 'tsup',
|
|
13
|
+
test: 'vitest', coverage: true, lint: 'eslint-prettier', gitHooks: 'simple-git-hooks',
|
|
14
|
+
release: 'changesets', workflows: ['ci', 'npm-publish', 'codeql', 'codecov'],
|
|
15
|
+
deps: 'renovate', community: true, agents: true, vscode: true,
|
|
16
|
+
},
|
|
9
17
|
minimal: {
|
|
10
18
|
language: 'ts', target: ['library'], moduleFormat: 'dual', bundler: 'tsup',
|
|
11
19
|
test: 'none', lint: 'none', gitHooks: 'none', release: 'none',
|