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 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.1.2",
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
@@ -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',