create-packkit 2.3.0 → 2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-packkit",
3
- "version": "2.3.0",
3
+ "version": "2.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
@@ -15,6 +15,7 @@ const OVERRIDE_FLAGS = {
15
15
  license: 'license',
16
16
  pm: 'packageManager',
17
17
  node: 'nodeVersion',
18
+ server: 'serviceFramework',
18
19
  author: 'author',
19
20
  description: 'description',
20
21
  repo: 'repo',
package/src/cli/index.js CHANGED
@@ -39,6 +39,7 @@ Options:
39
39
  --pm <manager> npm | pnpm | yarn | bun
40
40
  --language <ts|js> --module <esm|cjs|dual> --framework <none|react|vue|svelte>
41
41
  --target <library|cli|service|app> (repeatable) --storybook
42
+ --server <hono|fastify|express> (HTTP service framework)
42
43
  --bundler <tsup|tsdown|unbuild|rollup|none> --minify
43
44
  --test <vitest|jest|node|none> --e2e (Playwright, for apps)
44
45
  --env (type-safe env validation, services/CLIs) --no-sourcemaps
package/src/cli/wizard.js CHANGED
@@ -36,6 +36,9 @@ export async function runWizard(seed = {}) {
36
36
  cfg.minify = bail(await p.confirm({ message: 'Minify the build output?', initialValue: false }));
37
37
  }
38
38
  cfg.test = bail(await p.select({ message: 'Test runner', options: asOptions('test'), initialValue: OPTIONS.test.default }));
39
+ if (cfg.target.includes('service')) {
40
+ cfg.serviceFramework = bail(await p.select({ message: 'Service framework', options: asOptions('serviceFramework'), initialValue: OPTIONS.serviceFramework.default }));
41
+ }
39
42
  if (cfg.target.includes('app')) {
40
43
  cfg.e2e = bail(await p.confirm({ message: 'Add Playwright end-to-end tests?', initialValue: false }));
41
44
  }
@@ -1,14 +1,16 @@
1
- // HTTP service target (Hono). Splits the app (testable) from the server entry
2
- // (which listens), adds start/dev scripts, and a Dockerfile.
1
+ // HTTP service target. Supports Hono (default), Fastify and Express. Splits the
2
+ // app (testable) from the server entry (which listens), adds start/dev scripts,
3
+ // and a production Dockerfile.
3
4
 
4
5
  export default {
5
6
  id: 'service',
6
7
  active: (cfg) => cfg.hasService,
7
8
  apply(cfg) {
8
9
  const ext = cfg.ext;
10
+ const fw = cfg.serviceFramework || 'hono';
9
11
  const files = {
10
- [`src/app.${ext}`]: appFile(cfg),
11
- [`src/index.${ext}`]: serverFile(cfg),
12
+ [`src/app.${ext}`]: appFile(cfg, fw),
13
+ [`src/index.${ext}`]: serverFile(cfg, fw),
12
14
  Dockerfile: dockerfile(cfg),
13
15
  '.dockerignore': ['node_modules', 'dist', 'coverage', '.git', '.github', '.env', '.env.*', '!.env.example', '*.log', 'Dockerfile', '.dockerignore', ''].join('\n'),
14
16
  };
@@ -20,15 +22,50 @@ export default {
20
22
  start: 'node dist/index.js',
21
23
  dev: cfg.isTs ? 'tsx watch src/index.ts' : 'node --watch src/index.js',
22
24
  },
23
- dependencies: { hono: '^4.5.0', '@hono/node-server': '^2.0.0' },
24
- ...(cfg.isTs ? { devDependencies: { tsx: '^4.0.0' } } : {}),
25
+ dependencies: deps(fw),
26
+ devDependencies: {
27
+ ...(cfg.isTs ? { tsx: '^4.0.0' } : {}),
28
+ ...(cfg.isTs ? typeDeps(fw) : {}),
29
+ },
25
30
  },
26
31
  };
27
32
  },
28
33
  };
29
34
 
30
- function appFile(cfg) {
31
- const t = cfg.isTs;
35
+ function deps(fw) {
36
+ if (fw === 'fastify') return { fastify: '^5.0.0' };
37
+ if (fw === 'express') return { express: '^5.0.0' };
38
+ return { hono: '^4.5.0', '@hono/node-server': '^2.0.0' };
39
+ }
40
+
41
+ function typeDeps(fw) {
42
+ if (fw === 'express') return { '@types/express': '^5.0.0' };
43
+ return {};
44
+ }
45
+
46
+ function appFile(cfg, fw) {
47
+ if (fw === 'fastify') {
48
+ return [
49
+ `import Fastify from 'fastify';`,
50
+ ``,
51
+ `export const app = Fastify();`,
52
+ ``,
53
+ `app.get('/', async () => ({ ok: true, service: '${cfg.name}' }));`,
54
+ `app.get('/health', async () => 'ok');`,
55
+ ``,
56
+ ].join('\n');
57
+ }
58
+ if (fw === 'express') {
59
+ return [
60
+ `import express from 'express';`,
61
+ ``,
62
+ `export const app = express();`,
63
+ ``,
64
+ `app.get('/', (_req, res) => res.json({ ok: true, service: '${cfg.name}' }));`,
65
+ `app.get('/health', (_req, res) => res.send('ok'));`,
66
+ ``,
67
+ ].join('\n');
68
+ }
32
69
  return [
33
70
  `import { Hono } from 'hono';`,
34
71
  ``,
@@ -40,13 +77,43 @@ function appFile(cfg) {
40
77
  ].join('\n');
41
78
  }
42
79
 
43
- function serverFile(cfg) {
80
+ function serverFile(cfg, fw) {
81
+ const port = cfg.env ? 'env.PORT' : 'Number(process.env.PORT) || 3000';
82
+ const envImport = cfg.env ? `import { env } from './env.js';` : null;
83
+
84
+ if (fw === 'fastify') {
85
+ return [
86
+ `import { app } from './app.js';`,
87
+ envImport,
88
+ ``,
89
+ `const port = ${port};`,
90
+ `app.listen({ port, host: '0.0.0.0' }).then((url) => {`,
91
+ `\tconsole.log(\`Listening on \${url}\`);`,
92
+ `}).catch((err) => {`,
93
+ `\tapp.log.error(err);`,
94
+ `\tprocess.exit(1);`,
95
+ `});`,
96
+ ``,
97
+ ].filter((l) => l !== null).join('\n');
98
+ }
99
+ if (fw === 'express') {
100
+ return [
101
+ `import { app } from './app.js';`,
102
+ envImport,
103
+ ``,
104
+ `const port = ${port};`,
105
+ `app.listen(port, () => {`,
106
+ `\tconsole.log(\`Listening on http://localhost:\${port}\`);`,
107
+ `});`,
108
+ ``,
109
+ ].filter((l) => l !== null).join('\n');
110
+ }
44
111
  return [
45
112
  `import { serve } from '@hono/node-server';`,
46
113
  `import { app } from './app.js';`,
47
- cfg.env ? `import { env } from './env.js';` : null,
114
+ envImport,
48
115
  ``,
49
- `const port = ${cfg.env ? 'env.PORT' : 'Number(process.env.PORT) || 3000'};`,
116
+ `const port = ${port};`,
50
117
  `serve({ fetch: app.fetch, port }, (info) => {`,
51
118
  `\tconsole.log(\`Listening on http://localhost:\${info.port}\`);`,
52
119
  `});`,
@@ -68,6 +68,12 @@ export default {
68
68
  files[`src/index.test.${testExt}`] = exampleTest('node', cfg);
69
69
  }
70
70
 
71
+ // Express services test through supertest (no built-in inject).
72
+ if (cfg.hasService && cfg.serviceFramework === 'express') {
73
+ pkg.devDependencies.supertest = '^7.0.0';
74
+ if (cfg.isTs) pkg.devDependencies['@types/supertest'] = '^6.0.0';
75
+ }
76
+
71
77
  return { files, pkg };
72
78
  },
73
79
  };
@@ -82,13 +88,28 @@ function exampleTest(runner, cfg) {
82
88
  const imp = importPath(runner, cfg);
83
89
  if (cfg.hasService) {
84
90
  const api = runner === 'jest' ? '' : `import { describe, it, expect } from 'vitest';\n`;
91
+ const fw = cfg.serviceFramework || 'hono';
92
+ let imports, call, statusProp;
93
+ if (fw === 'fastify') {
94
+ imports = `import { app } from './app.js';`;
95
+ call = `await app.inject({ method: 'GET', url: '/' })`;
96
+ statusProp = 'statusCode';
97
+ } else if (fw === 'express') {
98
+ imports = `import request from 'supertest';\nimport { app } from './app.js';`;
99
+ call = `await request(app).get('/')`;
100
+ statusProp = 'status';
101
+ } else {
102
+ imports = `import { app } from './app.js';`;
103
+ call = `await app.request('/')`;
104
+ statusProp = 'status';
105
+ }
85
106
  return [
86
- api + `import { app } from './app.js';`,
107
+ api + imports,
87
108
  ``,
88
109
  `describe('app', () => {`,
89
110
  `\tit('responds on /', async () => {`,
90
- `\t\tconst res = await app.request('/');`,
91
- `\t\texpect(res.status).toBe(200);`,
111
+ `\t\tconst res = ${call};`,
112
+ `\t\texpect(res.${statusProp}).toBe(200);`,
92
113
  `\t});`,
93
114
  `});`,
94
115
  ``,
@@ -34,6 +34,14 @@ export const OPTIONS = {
34
34
  { value: 'cjs', label: 'CommonJS only' },
35
35
  ],
36
36
  },
37
+ serviceFramework: {
38
+ group: 'core', type: 'select', label: 'Service framework (HTTP service)', default: 'hono',
39
+ choices: [
40
+ { value: 'hono', label: 'Hono (fast, web-standard)' },
41
+ { value: 'fastify', label: 'Fastify' },
42
+ { value: 'express', label: 'Express' },
43
+ ],
44
+ },
37
45
  target: {
38
46
  group: 'core', type: 'multiselect', label: 'What are you building?', default: ['library'],
39
47
  choices: [