create-packkit 0.3.1 → 0.4.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
@@ -34,7 +34,7 @@ No install needed: **[danmat.github.io/create-packkit](https://danmat.github.io/
34
34
  |---|---|
35
35
  | **Language** | TypeScript (strict) · JavaScript (ESM) |
36
36
  | **Module format** | ESM · CJS · dual (proper `exports` map) |
37
- | **Target** | Library · CLI tool · both |
37
+ | **Target** | Library · CLI tool · **HTTP service (Hono)** · any combination |
38
38
  | **Framework** | None · **React** (component library — JSX, peer deps, jsdom tests) |
39
39
  | **Bundler** | tsup · tsdown · unbuild · rollup · none (tsc) · optional **minify** |
40
40
  | **Tests** | Vitest · Jest · node:test · none (+ coverage) |
@@ -48,7 +48,7 @@ No install needed: **[danmat.github.io/create-packkit](https://danmat.github.io/
48
48
 
49
49
  ## Presets
50
50
 
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
+ `ts-lib` · `js-lib` · `ts-cli` / `cli` · `react-lib` · `react-lib-js` · `node-service` · `oss` · `minimal` · `full` — named bundles of the options above. See the [roadmap](ROADMAP.md) for what's next.
52
52
 
53
53
  ## How it works
54
54
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-packkit",
3
- "version": "0.3.1",
3
+ "version": "0.4.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
@@ -11,6 +11,7 @@ const OVERRIDE_FLAGS = {
11
11
  lint: 'lint',
12
12
  hooks: 'gitHooks',
13
13
  release: 'release',
14
+ deps: 'deps',
14
15
  license: 'license',
15
16
  pm: 'packageManager',
16
17
  node: 'nodeVersion',
@@ -18,6 +19,15 @@ const OVERRIDE_FLAGS = {
18
19
  description: 'description',
19
20
  };
20
21
 
22
+ // Boolean options that default ON — a --no-<flag> turns them off.
23
+ const NEGATABLE = {
24
+ 'no-coverage': 'coverage',
25
+ 'no-community': 'community',
26
+ 'no-agents': 'agents',
27
+ 'no-vscode': 'vscode',
28
+ 'no-editorconfig': 'editorconfig',
29
+ };
30
+
21
31
  export function parseCliArgs(argv) {
22
32
  const { values, positionals } = parseArgs({
23
33
  args: argv,
@@ -31,9 +41,11 @@ export function parseCliArgs(argv) {
31
41
  'no-git': { type: 'boolean' },
32
42
  minify: { type: 'boolean' },
33
43
  target: { type: 'string', multiple: true },
44
+ workflows: { type: 'string', multiple: true },
34
45
  help: { type: 'boolean', short: 'h' },
35
46
  version: { type: 'boolean', short: 'v' },
36
47
  ...Object.fromEntries(Object.keys(OVERRIDE_FLAGS).map((k) => [k, { type: 'string' }])),
48
+ ...Object.fromEntries(Object.keys(NEGATABLE).map((k) => [k, { type: 'boolean' }])),
37
49
  },
38
50
  });
39
51
 
@@ -48,7 +60,11 @@ export function parseCliArgs(argv) {
48
60
  if (values[flag] != null) overrides[key] = values[flag];
49
61
  }
50
62
  if (values.target) overrides.target = values.target;
63
+ if (values.workflows) overrides.workflows = values.workflows;
51
64
  if (values.minify) overrides.minify = true;
65
+ for (const [flag, key] of Object.entries(NEGATABLE)) {
66
+ if (values[flag]) overrides[key] = false;
67
+ }
52
68
  if (name) overrides.name = name;
53
69
 
54
70
  return {
package/src/cli/index.js CHANGED
@@ -33,9 +33,14 @@ Options:
33
33
  --no-install Skip dependency install
34
34
  --no-git Skip git init
35
35
  --pm <manager> npm | pnpm | yarn | bun
36
- --language <ts|js> --module <esm|cjs|dual> --bundler <tsup|tsdown|unbuild|rollup|none>
36
+ --language <ts|js> --module <esm|cjs|dual> --framework <none|react>
37
+ --target <library|cli|service> (repeatable)
38
+ --bundler <tsup|tsdown|unbuild|rollup|none> --minify
37
39
  --test <vitest|jest|node|none> --lint <eslint-prettier|biome|oxlint|none>
38
- --license <MIT|Apache-2.0|ISC|none>
40
+ --hooks <simple-git-hooks|husky|lefthook|none> --release <changesets|release-it|np|none>
41
+ --workflows <ci|npm-publish|pages|codeql|codecov|stale> (repeatable)
42
+ --deps <renovate|dependabot|none> --license <MIT|Apache-2.0|ISC|none>
43
+ --no-coverage --no-community --no-agents --no-vscode --no-editorconfig
39
44
  -h, --help Show this help
40
45
  -v, --version Show version
41
46
 
@@ -5,6 +5,7 @@ import meta from './meta.js';
5
5
  import bundler from './bundler.js';
6
6
  import typescript from './typescript.js';
7
7
  import react from './react.js';
8
+ import service from './service.js';
8
9
  import test from './test.js';
9
10
  import lint from './lint.js';
10
11
  import githooks from './githooks.js';
@@ -21,6 +22,7 @@ export default [
21
22
  bundler,
22
23
  typescript,
23
24
  react,
25
+ service,
24
26
  test,
25
27
  lint,
26
28
  githooks,
@@ -0,0 +1,79 @@
1
+ // HTTP service target (Hono). Splits the app (testable) from the server entry
2
+ // (which listens), adds start/dev scripts, and a Dockerfile.
3
+
4
+ export default {
5
+ id: 'service',
6
+ active: (cfg) => cfg.hasService,
7
+ apply(cfg) {
8
+ const ext = cfg.ext;
9
+ const files = {
10
+ [`src/app.${ext}`]: appFile(cfg),
11
+ [`src/index.${ext}`]: serverFile(cfg),
12
+ Dockerfile: dockerfile(cfg),
13
+ '.dockerignore': 'node_modules\ndist\n.git\n.env\n',
14
+ };
15
+ return {
16
+ files,
17
+ pkg: {
18
+ private: true,
19
+ scripts: {
20
+ start: 'node dist/index.js',
21
+ dev: cfg.isTs ? 'tsx watch src/index.ts' : 'node --watch src/index.js',
22
+ },
23
+ dependencies: { hono: '^4.5.0', '@hono/node-server': '^1.12.0' },
24
+ ...(cfg.isTs ? { devDependencies: { tsx: '^4.0.0' } } : {}),
25
+ },
26
+ };
27
+ },
28
+ };
29
+
30
+ function appFile(cfg) {
31
+ const t = cfg.isTs;
32
+ return [
33
+ `import { Hono } from 'hono';`,
34
+ ``,
35
+ `export const app = new Hono();`,
36
+ ``,
37
+ `app.get('/', (c) => c.json({ ok: true, service: '${cfg.name}' }));`,
38
+ `app.get('/health', (c) => c.text('ok'));`,
39
+ ``,
40
+ ].join('\n');
41
+ }
42
+
43
+ function serverFile(cfg) {
44
+ return [
45
+ `import { serve } from '@hono/node-server';`,
46
+ `import { app } from './app${cfg.isTs ? '.js' : '.js'}';`,
47
+ ``,
48
+ `const port = Number(process.env.PORT) || 3000;`,
49
+ `serve({ fetch: app.fetch, port }, (info) => {`,
50
+ `\tconsole.log(\`Listening on http://localhost:\${info.port}\`);`,
51
+ `});`,
52
+ ``,
53
+ ].join('\n');
54
+ }
55
+
56
+ function dockerfile(cfg) {
57
+ const node = cfg.nodeVersion;
58
+ const pm = cfg.packageManager;
59
+ const install = pm === 'npm' ? 'npm ci' : `${pm} install --frozen-lockfile`;
60
+ const build = pm === 'npm' ? 'npm run build' : `${pm} run build`;
61
+ return [
62
+ `FROM node:${node}-slim AS build`,
63
+ `WORKDIR /app`,
64
+ `COPY package*.json ./`,
65
+ `RUN ${install}`,
66
+ `COPY . .`,
67
+ `RUN ${build}`,
68
+ ``,
69
+ `FROM node:${node}-slim`,
70
+ `WORKDIR /app`,
71
+ `ENV NODE_ENV=production`,
72
+ `COPY --from=build /app/node_modules ./node_modules`,
73
+ `COPY --from=build /app/dist ./dist`,
74
+ `COPY package.json ./`,
75
+ `EXPOSE 3000`,
76
+ `CMD ["node", "dist/index.js"]`,
77
+ ``,
78
+ ].join('\n');
79
+ }
@@ -65,6 +65,20 @@ function importPath(runner, cfg) {
65
65
 
66
66
  function exampleTest(runner, cfg) {
67
67
  const imp = importPath(runner, cfg);
68
+ if (cfg.hasService) {
69
+ const api = runner === 'jest' ? '' : `import { describe, it, expect } from 'vitest';\n`;
70
+ return [
71
+ api + `import { app } from './app.js';`,
72
+ ``,
73
+ `describe('app', () => {`,
74
+ `\tit('responds on /', async () => {`,
75
+ `\t\tconst res = await app.request('/');`,
76
+ `\t\texpect(res.status).toBe(200);`,
77
+ `\t});`,
78
+ `});`,
79
+ ``,
80
+ ].join('\n');
81
+ }
68
82
  if (cfg.isReact) {
69
83
  const api = runner === 'jest' ? '' : `import { describe, it, expect } from 'vitest';\n`;
70
84
  return [
package/src/core/index.js CHANGED
@@ -6,9 +6,9 @@ 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 } from './presets.js';
9
+ import { PRESETS, PRESET_NAMES, PRESET_INFO } from './presets.js';
10
10
 
11
- export { OPTIONS, GROUPS, defaultConfig, normalizeConfig, PRESETS, PRESET_NAMES };
11
+ export { OPTIONS, GROUPS, defaultConfig, normalizeConfig, PRESETS, PRESET_NAMES, PRESET_INFO };
12
12
 
13
13
  /** Apply a named preset over the defaults, returning a full config. */
14
14
  export function fromPreset(name, overrides = {}) {
@@ -33,6 +33,7 @@ export const OPTIONS = {
33
33
  choices: [
34
34
  { value: 'library', label: 'Library (importable package)' },
35
35
  { value: 'cli', label: 'CLI tool (ships a bin)' },
36
+ { value: 'service', label: 'HTTP service (Hono)' },
36
37
  ],
37
38
  },
38
39
  framework: {
@@ -205,6 +206,7 @@ export function normalizeConfig(input = {}) {
205
206
  cfg.srcExt = cfg.isReact ? (cfg.isTs ? 'tsx' : 'jsx') : cfg.ext;
206
207
  cfg.hasLibrary = cfg.target.includes('library');
207
208
  cfg.hasCli = cfg.target.includes('cli');
209
+ cfg.hasService = cfg.target.includes('service');
208
210
  cfg.hasEsm = cfg.moduleFormat === 'esm' || cfg.moduleFormat === 'dual';
209
211
  cfg.hasCjs = cfg.moduleFormat === 'cjs' || cfg.moduleFormat === 'dual';
210
212
  return cfg;
@@ -8,6 +8,11 @@ export const PRESETS = {
8
8
  cli: { language: 'ts', target: ['cli', 'library'], moduleFormat: 'esm' },
9
9
  'react-lib': { language: 'ts', framework: 'react', target: ['library'], moduleFormat: 'dual', test: 'vitest' },
10
10
  'react-lib-js': { language: 'js', framework: 'react', target: ['library'], moduleFormat: 'dual', bundler: 'tsup', test: 'vitest' },
11
+ 'node-service': {
12
+ language: 'ts', target: ['service'], moduleFormat: 'esm', bundler: 'tsup',
13
+ test: 'vitest', lint: 'eslint-prettier', gitHooks: 'simple-git-hooks',
14
+ release: 'none', workflows: ['ci'], deps: 'renovate', agents: true, vscode: true,
15
+ },
11
16
  oss: {
12
17
  language: 'ts', target: ['library'], moduleFormat: 'dual', bundler: 'tsup',
13
18
  test: 'vitest', coverage: true, lint: 'eslint-prettier', gitHooks: 'simple-git-hooks',
@@ -29,3 +34,17 @@ export const PRESETS = {
29
34
  };
30
35
 
31
36
  export const PRESET_NAMES = Object.keys(PRESETS);
37
+
38
+ // Short gists for the CLI help and the web configurator (tooltips + description).
39
+ export const PRESET_INFO = {
40
+ 'ts-lib': 'TypeScript library — dual ESM/CJS, tsup, Vitest, ESLint.',
41
+ 'js-lib': 'JavaScript (ESM) library — tsup, Vitest, ESLint.',
42
+ 'ts-cli': 'TypeScript CLI + library — ESM, ships a bin.',
43
+ cli: 'TypeScript CLI tool — ESM, ships a bin.',
44
+ 'react-lib': 'React component library (TS) — JSX, peer deps, jsdom tests.',
45
+ 'react-lib-js': 'React component library (JS) — JSX, peer deps, jsdom tests.',
46
+ 'node-service': 'Node HTTP service (Hono) — tsx dev, tsup build, Dockerfile.',
47
+ oss: 'Full open-source library — coverage, CodeQL, Codecov, Renovate, Changesets.',
48
+ minimal: 'Bare TS library — tsup only, no tests/lint/CI.',
49
+ full: 'Everything on — library + CLI, all workflows and extras.',
50
+ };