create-packkit 1.0.0 → 1.1.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
@@ -53,6 +53,17 @@ No install needed: **[danmat.github.io/create-packkit](https://danmat.github.io/
53
53
 
54
54
  **Team profiles:** save a partial config as `packkit.config.json` (or any file) and reuse it with `npx create-packkit my-lib --from ./packkit.config.json` — flags still override the file.
55
55
 
56
+ ## For AI agents & automation
57
+
58
+ Packkit is safe to drive non-interactively — every option is a flag, so no prompts are needed. Agents can introspect the whole interface as JSON:
59
+
60
+ ```sh
61
+ npx create-packkit --schema # all options, presets, and aliases as JSON
62
+ npx create-packkit my-lib ts-lib --no-install --no-git # deterministic scaffold
63
+ ```
64
+
65
+ There's also an [`llms.txt`](llms.txt) (served at [danmat.github.io/create-packkit/llms.txt](https://danmat.github.io/create-packkit/llms.txt)) describing the commands for LLMs.
66
+
56
67
  ## How it works
57
68
 
58
69
  Packkit is a pure `config → { files }` **core** that runs in both Node and the browser:
package/llms.txt ADDED
@@ -0,0 +1,43 @@
1
+ # Packkit (create-packkit)
2
+
3
+ > A highly configurable, non-interactive scaffolder for modern npm packages, CLIs, HTTP services, and front-end apps (React/Vue/Svelte). Designed to be driven by humans OR automation/agents.
4
+
5
+ Packkit generates a complete, ready-to-ship project from a single deterministic command. It is safe for agents to call because every option is a flag — no prompts are required when a name and flags (or a preset) are given.
6
+
7
+ ## For agents: how to use it
8
+
9
+ Introspect the full interface (all options, presets, aliases) as JSON:
10
+
11
+ ```sh
12
+ npx create-packkit --schema
13
+ ```
14
+
15
+ Generate non-interactively (no prompts; `--no-install`/`--no-git` for a pure file scaffold):
16
+
17
+ ```sh
18
+ npx create-packkit <name> [preset] [--flags...]
19
+ ```
20
+
21
+ Examples:
22
+
23
+ ```sh
24
+ npx create-packkit my-lib ts-lib
25
+ npx create-packkit my-cli cli --pm pnpm
26
+ npx create-packkit ui react-lib --storybook
27
+ npx create-packkit api node-service
28
+ npx create-packkit web react-app
29
+ npx create-packkit lib my-lib --no-install --no-git
30
+ ```
31
+
32
+ ## Presets (and short aliases)
33
+
34
+ `ts-lib` (`lib`) · `js-lib` (`jslib`) · `ts-cli` / `cli` · `react-lib` (`rlib`) · `react-app` (`rapp`) · `vue-lib` (`vlib`) · `vue-app` (`vapp`) · `svelte-lib` (`slib`) · `svelte-app` (`sapp`) · `node-service` (`svc`) · `oss` · `minimal` · `full`
35
+
36
+ ## Key flags
37
+
38
+ `--language ts|js` · `--framework none|react|vue|svelte` · `--target library|cli|service|app` (repeatable) · `--module esm|cjs|dual` · `--bundler tsup|tsdown|unbuild|rollup|none` · `--minify` · `--test vitest|jest|node|none` · `--lint eslint-prettier|biome|oxlint|none` · `--hooks ...` · `--release changesets|release-it|np|none` · `--workflows ci|npm-publish|pages|codeql|codecov|stale` (repeatable) · `--pkg-checks` (publint + are-the-types-wrong) · `--knip` · `--jsr` · `--deps renovate|dependabot|none` · `--license MIT|Apache-2.0|ISC|none` · `--pm npm|pnpm|yarn|bun` · `--from <file>` (load a JSON profile) · `--no-install` · `--no-git`
39
+
40
+ ## Links
41
+ - Web configurator: https://danmat.github.io/create-packkit/
42
+ - Repo: https://github.com/DanMat/create-packkit
43
+ - npm: https://www.npmjs.com/package/create-packkit
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-packkit",
3
- "version": "1.0.0",
3
+ "version": "1.1.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": {
@@ -15,7 +15,8 @@
15
15
  "bin",
16
16
  "src",
17
17
  "README.md",
18
- "LICENSE"
18
+ "LICENSE",
19
+ "llms.txt"
19
20
  ],
20
21
  "engines": {
21
22
  "node": ">=18"
package/src/cli/args.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { parseArgs } from 'node:util';
2
- import { PRESET_NAMES } from '../core/presets.js';
2
+ import { resolvePreset } from '../core/presets.js';
3
3
 
4
4
  // Map friendly flag names to config keys for non-interactive use.
5
5
  const OVERRIDE_FLAGS = {
@@ -17,6 +17,7 @@ const OVERRIDE_FLAGS = {
17
17
  node: 'nodeVersion',
18
18
  author: 'author',
19
19
  description: 'description',
20
+ repo: 'repo',
20
21
  };
21
22
 
22
23
  // Boolean options that default ON — a --no-<flag> turns them off.
@@ -45,8 +46,12 @@ export function parseCliArgs(argv) {
45
46
  workflows: { type: 'string', multiple: true },
46
47
  minify: { type: 'boolean' },
47
48
  storybook: { type: 'boolean' },
49
+ 'pkg-checks': { type: 'boolean' },
50
+ knip: { type: 'boolean' },
51
+ jsr: { type: 'boolean' },
48
52
  help: { type: 'boolean', short: 'h' },
49
53
  version: { type: 'boolean', short: 'v' },
54
+ schema: { type: 'boolean' },
50
55
  ...Object.fromEntries(Object.keys(OVERRIDE_FLAGS).map((k) => [k, { type: 'string' }])),
51
56
  ...Object.fromEntries(Object.keys(NEGATABLE).map((k) => [k, { type: 'boolean' }])),
52
57
  },
@@ -55,7 +60,7 @@ export function parseCliArgs(argv) {
55
60
  // First positional may be a known preset; otherwise it's the project name.
56
61
  let preset = values.preset;
57
62
  const pos = [...positionals];
58
- if (!preset && pos.length && PRESET_NAMES.includes(pos[0])) preset = pos.shift();
63
+ if (!preset && pos.length && resolvePreset(pos[0])) preset = pos.shift();
59
64
  const name = values.name || pos[0];
60
65
 
61
66
  const overrides = {};
@@ -66,6 +71,9 @@ export function parseCliArgs(argv) {
66
71
  if (values.workflows) overrides.workflows = values.workflows;
67
72
  if (values.minify) overrides.minify = true;
68
73
  if (values.storybook) overrides.storybook = true;
74
+ if (values['pkg-checks']) overrides.pkgChecks = true;
75
+ if (values.knip) overrides.knip = true;
76
+ if (values.jsr) overrides.jsr = true;
69
77
  for (const [flag, key] of Object.entries(NEGATABLE)) {
70
78
  if (values[flag]) overrides[key] = false;
71
79
  }
@@ -81,6 +89,7 @@ export function parseCliArgs(argv) {
81
89
  git: !values['no-git'],
82
90
  help: !!values.help,
83
91
  version: !!values.version,
92
+ schema: !!values.schema,
84
93
  overrides,
85
94
  };
86
95
  }
package/src/cli/index.js CHANGED
@@ -2,7 +2,7 @@ import { resolve, basename } from 'node:path';
2
2
  import { readFileSync, existsSync } from 'node:fs';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import * as p from '@clack/prompts';
5
- import { generate, fromPreset, normalizeConfig, PRESET_NAMES } from '../core/index.js';
5
+ import { generate, fromPreset, normalizeConfig, PRESET_NAMES, OPTIONS, PRESET_INFO, PRESET_ALIASES } from '../core/index.js';
6
6
  import { parseCliArgs } from './args.js';
7
7
  import { runWizard } from './wizard.js';
8
8
  import { writeProject, dirIsEmptyOrMissing, gitInit, installDeps } from './write.js';
@@ -41,10 +41,14 @@ Options:
41
41
  --hooks <simple-git-hooks|husky|lefthook|none> --release <changesets|release-it|np|none>
42
42
  --workflows <ci|npm-publish|pages|codeql|codecov|stale> (repeatable)
43
43
  --deps <renovate|dependabot|none> --license <MIT|Apache-2.0|ISC|none>
44
+ --pkg-checks (publint + are-the-types-wrong) --knip --jsr
44
45
  --no-coverage --no-community --no-agents --no-vscode --no-editorconfig
46
+ --schema Print the full option/preset schema as JSON (for tools/agents)
45
47
  -h, --help Show this help
46
48
  -v, --version Show version
47
49
 
50
+ Preset shortcuts: lib, jslib, rlib, rapp, vlib, vapp, slib, sapp, svc
51
+
48
52
  Examples:
49
53
  npx packkit ts-lib my-lib
50
54
  npm create packkit@latest my-cli -- --preset cli
@@ -55,6 +59,10 @@ export async function run(argv = process.argv.slice(2)) {
55
59
  const args = parseCliArgs(argv);
56
60
  if (args.help) return void console.log(HELP);
57
61
  if (args.version) return void console.log(pkgVersion());
62
+ if (args.schema) {
63
+ // Machine-readable interface for tools/agents: every option, preset, and alias.
64
+ return void console.log(JSON.stringify({ version: pkgVersion(), options: OPTIONS, presets: PRESET_INFO, aliases: PRESET_ALIASES }, null, 2));
65
+ }
58
66
 
59
67
  const interactive = !args.preset && !args.yes && !args.from && process.stdout.isTTY;
60
68
 
@@ -20,16 +20,19 @@ export default {
20
20
  pkg.files = ['dist'];
21
21
  const esm = './dist/index.js';
22
22
  const cjs = './dist/index.cjs';
23
- const dts = './dist/index.d.ts';
23
+ const dtsEsm = './dist/index.d.ts';
24
+ // Dual builds emit a separate .d.cts so CJS consumers resolve the right
25
+ // types under the "require" condition (publint / are-the-types-wrong).
26
+ const dtsCjs = cfg.moduleFormat === 'dual' ? './dist/index.d.cts' : dtsEsm;
27
+
24
28
  const exp = {};
25
- if (cfg.isTs) exp.types = dts;
26
- if (cfg.hasEsm) exp.import = esm;
27
- if (cfg.hasCjs) exp.require = build ? cjs : './dist/index.cjs';
29
+ if (cfg.hasEsm) exp.import = cfg.isTs ? { types: dtsEsm, default: esm } : esm;
30
+ if (cfg.hasCjs) exp.require = cfg.isTs ? { types: dtsCjs, default: cjs } : cjs;
28
31
  pkg.exports = { '.': exp };
29
- if (cfg.hasCjs) pkg.main = exp.require;
30
- else pkg.main = esm;
32
+
33
+ pkg.main = cfg.hasCjs ? cjs : esm;
31
34
  if (cfg.hasEsm) pkg.module = esm;
32
- if (cfg.isTs) pkg.types = dts;
35
+ if (cfg.isTs) pkg.types = cfg.hasEsm ? dtsEsm : dtsCjs;
33
36
  }
34
37
 
35
38
  // ---- build tooling ----
@@ -0,0 +1,24 @@
1
+ // Package-correctness + hygiene checks (opt-in):
2
+ // - publint validates package.json shape (exports/main/module)
3
+ // - are-the-types-wrong verifies TS consumers resolve your .d.ts correctly
4
+ // - knip finds unused files, dependencies, and exports
5
+
6
+ export default {
7
+ id: 'checks',
8
+ active: (cfg) => cfg.pkgChecks || cfg.knip,
9
+ apply(cfg) {
10
+ const pkg = { scripts: {}, devDependencies: {} };
11
+
12
+ if (cfg.pkgChecks) {
13
+ pkg.scripts['check:pkg'] = 'publint && attw --pack';
14
+ pkg.devDependencies.publint = '^0.3.0';
15
+ pkg.devDependencies['@arethetypeswrong/cli'] = '^0.18.0';
16
+ }
17
+ if (cfg.knip) {
18
+ pkg.scripts.knip = 'knip';
19
+ pkg.devDependencies.knip = '^5.0.0';
20
+ }
21
+
22
+ return { files: {}, pkg };
23
+ },
24
+ };
@@ -12,6 +12,8 @@ import lint from './lint.js';
12
12
  import githooks from './githooks.js';
13
13
  import release from './release.js';
14
14
  import cli from './cli.js';
15
+ import checks from './checks.js';
16
+ import jsr from './jsr.js';
15
17
  import workflows from './workflows.js';
16
18
  import storybook from './storybook.js';
17
19
  import community from './community.js';
@@ -31,6 +33,8 @@ export default [
31
33
  githooks,
32
34
  release,
33
35
  cli,
36
+ checks,
37
+ jsr,
34
38
  workflows,
35
39
  storybook,
36
40
  community,
@@ -0,0 +1,42 @@
1
+ import { toJson } from '../render.js';
2
+
3
+ // JSR publishing (TypeScript-first, ESM-only registry with automatic provenance).
4
+ // Publishes source directly — no build step required. JSR names are scoped, so
5
+ // a non-scoped package gets an @scope placeholder to fill in.
6
+
7
+ export default {
8
+ id: 'jsr',
9
+ active: (cfg) => cfg.jsr,
10
+ apply(cfg) {
11
+ const name = cfg.name.startsWith('@') ? cfg.name : `@scope/${cfg.name}`;
12
+ return {
13
+ files: {
14
+ 'jsr.json': toJson({
15
+ name,
16
+ version: '0.0.0',
17
+ exports: `./src/index.${cfg.ext}`,
18
+ }),
19
+ '.github/workflows/jsr.yml': [
20
+ 'name: Publish to JSR',
21
+ 'on:',
22
+ ' push:',
23
+ ' tags: ["v*"]',
24
+ 'permissions:',
25
+ ' contents: read',
26
+ ' id-token: write',
27
+ 'jobs:',
28
+ ' publish:',
29
+ ' runs-on: ubuntu-latest',
30
+ ' steps:',
31
+ ' - uses: actions/checkout@v4',
32
+ ' - uses: actions/setup-node@v4',
33
+ " with:",
34
+ " node-version: '20'",
35
+ ' - run: npx jsr publish',
36
+ '',
37
+ ].join('\n'),
38
+ },
39
+ pkg: {},
40
+ };
41
+ },
42
+ };
@@ -16,6 +16,7 @@ export default {
16
16
 
17
17
  const kw = String(cfg.keywords || '').split(',').map((s) => s.trim()).filter(Boolean);
18
18
  if (kw.length) pkg.keywords = kw;
19
+ if (cfg.license !== 'none') pkg.license = cfg.license;
19
20
  if (cfg.author) pkg.author = cfg.author;
20
21
  if (cfg.repo) {
21
22
  pkg.repository = { type: 'git', url: `git+${cfg.repo.replace(/\.git$/, '')}.git` };
@@ -72,7 +72,9 @@ function ciWorkflow(cfg, codecov) {
72
72
  if (cfg.isTs) jobs.push(` - run: ${pmRun(cfg, 'typecheck')}`);
73
73
  if (cfg.lint !== 'none') jobs.push(` - run: ${pmRun(cfg, 'lint')}`);
74
74
  if (cfg.test !== 'none') jobs.push(` - run: ${pmRun(cfg, codecov ? 'coverage' : 'test')}`);
75
+ if (cfg.knip) jobs.push(` - run: ${pmRun(cfg, 'knip')}`);
75
76
  if (cfg.hasBuild) jobs.push(` - run: ${pmRun(cfg, 'build')}`);
77
+ if (cfg.pkgChecks) jobs.push(` - run: ${pmRun(cfg, 'check:pkg')}`); // after build (attw packs dist)
76
78
  const cov = codecov
77
79
  ? '\n - uses: codecov/codecov-action@v4\n with:\n token: ${{ secrets.CODECOV_TOKEN }}'
78
80
  : '';
@@ -85,6 +87,7 @@ function ciWorkflow(cfg, codecov) {
85
87
  'jobs:',
86
88
  ' ci:',
87
89
  ' runs-on: ubuntu-latest',
90
+ ' steps:',
88
91
  setupSteps(cfg),
89
92
  jobs.join('\n') + cov,
90
93
  '',
@@ -106,6 +109,7 @@ function releaseWorkflow(cfg) {
106
109
  'jobs:',
107
110
  ' release:',
108
111
  ' runs-on: ubuntu-latest',
112
+ ' steps:',
109
113
  setupSteps(cfg),
110
114
  ' - uses: changesets/action@v1',
111
115
  ' with:',
@@ -130,6 +134,7 @@ function releaseWorkflow(cfg) {
130
134
  'jobs:',
131
135
  ' publish:',
132
136
  ' runs-on: ubuntu-latest',
137
+ ' steps:',
133
138
  setupSteps(cfg),
134
139
  cfg.hasBuild ? ` - run: ${pmRun(cfg, 'build')}` : null,
135
140
  ' - run: npm publish --provenance --access public',
package/src/core/index.js CHANGED
@@ -6,15 +6,15 @@ import { normalizeConfig, OPTIONS, GROUPS, defaultConfig } from './options.js';
6
6
  import { deepMerge, toJson } from './render.js';
7
7
  import { finalizePackageJson } from './pkg.js';
8
8
  import features from './features/index.js';
9
- import { PRESETS, PRESET_NAMES, PRESET_INFO } from './presets.js';
9
+ import { PRESETS, PRESET_NAMES, PRESET_INFO, PRESET_ALIASES, resolvePreset } from './presets.js';
10
10
 
11
- export { OPTIONS, GROUPS, defaultConfig, normalizeConfig, PRESETS, PRESET_NAMES, PRESET_INFO };
11
+ export { OPTIONS, GROUPS, defaultConfig, normalizeConfig, PRESETS, PRESET_NAMES, PRESET_INFO, PRESET_ALIASES, resolvePreset };
12
12
 
13
- /** Apply a named preset over the defaults, returning a full config. */
13
+ /** Apply a named preset (or alias) over the defaults, returning a full config. */
14
14
  export function fromPreset(name, overrides = {}) {
15
- const preset = PRESETS[name];
16
- if (!preset) throw new Error(`Unknown preset "${name}". Known: ${PRESET_NAMES.join(', ')}`);
17
- return normalizeConfig({ ...preset, ...overrides });
15
+ const canonical = resolvePreset(name);
16
+ if (!canonical) throw new Error(`Unknown preset "${name}". Known: ${PRESET_NAMES.join(', ')}`);
17
+ return normalizeConfig({ ...PRESETS[canonical], ...overrides });
18
18
  }
19
19
 
20
20
  /** Turn a config into a complete set of files. */
@@ -91,6 +91,8 @@ export const OPTIONS = {
91
91
  },
92
92
  coverage: { group: 'quality', type: 'boolean', label: 'Coverage reporting', default: true },
93
93
  storybook: { group: 'quality', type: 'boolean', label: 'Storybook (component libraries)', default: false },
94
+ pkgChecks: { group: 'quality', type: 'boolean', label: 'Package checks (publint + are-the-types-wrong)', default: false },
95
+ knip: { group: 'quality', type: 'boolean', label: 'Knip (unused files / deps / exports)', default: false },
94
96
 
95
97
  // ---- lint / format ----
96
98
  lint: {
@@ -124,6 +126,7 @@ export const OPTIONS = {
124
126
  { value: 'none', label: 'None' },
125
127
  ],
126
128
  },
129
+ jsr: { group: 'release', type: 'boolean', label: 'Publish to JSR (TS-first registry)', default: false },
127
130
 
128
131
  // ---- github actions (configurable workflows) ----
129
132
  workflows: {
@@ -236,5 +239,11 @@ export function normalizeConfig(input = {}) {
236
239
 
237
240
  // Storybook only applies to component libraries.
238
241
  if (!cfg.hasFramework || cfg.hasApp || !cfg.hasLibrary) cfg.storybook = false;
242
+
243
+ cfg.publishable = (cfg.hasLibrary || cfg.hasCli) && !cfg.hasApp && !cfg.hasService;
244
+ // Package-correctness checks only make sense for a publishable package.
245
+ if (!cfg.publishable) cfg.pkgChecks = false;
246
+ // JSR is TypeScript-first, ESM, and for plain (non-framework) libraries.
247
+ if (!(cfg.isTs && cfg.hasLibrary && !cfg.hasFramework && !cfg.hasApp)) cfg.jsr = false;
239
248
  return cfg;
240
249
  }
@@ -23,6 +23,7 @@ export const PRESETS = {
23
23
  test: 'vitest', coverage: true, lint: 'eslint-prettier', gitHooks: 'simple-git-hooks',
24
24
  release: 'changesets', workflows: ['ci', 'npm-publish', 'codeql', 'codecov'],
25
25
  deps: 'renovate', community: true, agents: true, vscode: true,
26
+ pkgChecks: true, knip: true,
26
27
  },
27
28
  minimal: {
28
29
  language: 'ts', target: ['library'], moduleFormat: 'dual', bundler: 'tsup',
@@ -40,6 +41,27 @@ export const PRESETS = {
40
41
 
41
42
  export const PRESET_NAMES = Object.keys(PRESETS);
42
43
 
44
+ // Short shortcuts, e.g. `npx create-packkit lib my-lib`.
45
+ export const PRESET_ALIASES = {
46
+ lib: 'ts-lib',
47
+ jslib: 'js-lib',
48
+ rlib: 'react-lib',
49
+ rapp: 'react-app',
50
+ vlib: 'vue-lib',
51
+ vapp: 'vue-app',
52
+ slib: 'svelte-lib',
53
+ sapp: 'svelte-app',
54
+ svc: 'node-service',
55
+ service: 'node-service',
56
+ };
57
+
58
+ /** Resolve a preset name or alias to its canonical preset id (or undefined). */
59
+ export function resolvePreset(name) {
60
+ if (PRESETS[name]) return name;
61
+ if (PRESET_ALIASES[name]) return PRESET_ALIASES[name];
62
+ return undefined;
63
+ }
64
+
43
65
  // Short gists for the CLI help and the web configurator (tooltips + description).
44
66
  export const PRESET_INFO = {
45
67
  'ts-lib': 'TypeScript library — dual ESM/CJS, tsup, Vitest, ESLint.',