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 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 the short alias
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.1.1",
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
@@ -5,6 +5,7 @@ import { PRESET_NAMES } from '../core/presets.js';
5
5
  const OVERRIDE_FLAGS = {
6
6
  language: 'language',
7
7
  module: 'moduleFormat',
8
+ framework: 'framework',
8
9
  bundler: 'bundler',
9
10
  test: 'test',
10
11
  lint: 'lint',
@@ -33,7 +33,7 @@ export default {
33
33
  }
34
34
 
35
35
  // ---- build tooling ----
36
- const entries = ['src/index.' + cfg.ext];
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
- const ext = cfg.ext;
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.${ext}`] = exampleTest('vitest', cfg);
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.${ext}`] = exampleTest('jest', cfg);
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.${ext}`] = exampleTest('node', cfg);
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,
@@ -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';
@@ -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',