create-packkit 0.1.1 → 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 +8 -7
- package/package.json +2 -3
- 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
|
@@ -13,20 +13,20 @@ Most scaffolders lock you into one stack, one language, and the terminal. Packki
|
|
|
13
13
|
```sh
|
|
14
14
|
# interactive wizard
|
|
15
15
|
npm create packkit@latest
|
|
16
|
-
# or
|
|
17
|
-
npx packkit
|
|
16
|
+
# or with npx
|
|
17
|
+
npx create-packkit
|
|
18
18
|
|
|
19
19
|
# skip the wizard with a preset
|
|
20
|
-
npx packkit ts-lib my-lib
|
|
21
|
-
npx packkit cli my-tool
|
|
22
|
-
npx packkit --preset full my-pkg --pm pnpm
|
|
20
|
+
npx create-packkit ts-lib my-lib
|
|
21
|
+
npx create-packkit cli my-tool
|
|
22
|
+
npx create-packkit --preset full my-pkg --pm pnpm
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
Then `cd`, and you already have a working project — `build`, `test`, and `lint` all pass out of the box.
|
|
26
26
|
|
|
27
27
|
## Or configure it on the web
|
|
28
28
|
|
|
29
|
-
No install needed: **[danmat.github.io/create-packkit](https://danmat.github.io/create-packkit/)** — tick the options, preview the file tree, and **download a zip** (or copy the equivalent `npx packkit` command). Everything runs in your browser.
|
|
29
|
+
No install needed: **[danmat.github.io/create-packkit](https://danmat.github.io/create-packkit/)** — tick the options, preview the file tree, and **download a zip** (or copy the equivalent `npx create-packkit` command). Everything runs in your browser.
|
|
30
30
|
|
|
31
31
|
## What you can pick
|
|
32
32
|
|
|
@@ -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,11 +1,10 @@
|
|
|
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": {
|
|
7
|
-
"create-packkit": "bin/cli.js"
|
|
8
|
-
"packkit": "bin/cli.js"
|
|
7
|
+
"create-packkit": "bin/cli.js"
|
|
9
8
|
},
|
|
10
9
|
"exports": {
|
|
11
10
|
".": "./src/core/index.js",
|
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',
|