create-packkit 2.2.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 +1 -1
- package/src/cli/args.js +3 -0
- package/src/cli/index.js +2 -0
- package/src/cli/wizard.js +3 -0
- package/src/core/features/index.js +2 -0
- package/src/core/features/service.js +78 -11
- package/src/core/features/sizelimit.js +23 -0
- package/src/core/features/test.js +24 -3
- package/src/core/features/workflows.js +1 -0
- package/src/core/options.js +11 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-packkit",
|
|
3
|
-
"version": "2.
|
|
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',
|
|
@@ -54,6 +55,7 @@ export function parseCliArgs(argv) {
|
|
|
54
55
|
canary: { type: 'boolean' },
|
|
55
56
|
'pkg-checks': { type: 'boolean' },
|
|
56
57
|
knip: { type: 'boolean' },
|
|
58
|
+
'size-limit': { type: 'boolean' },
|
|
57
59
|
jsr: { type: 'boolean' },
|
|
58
60
|
help: { type: 'boolean', short: 'h' },
|
|
59
61
|
version: { type: 'boolean', short: 'v' },
|
|
@@ -83,6 +85,7 @@ export function parseCliArgs(argv) {
|
|
|
83
85
|
if (values.canary) overrides.canary = true;
|
|
84
86
|
if (values['pkg-checks']) overrides.pkgChecks = true;
|
|
85
87
|
if (values.knip) overrides.knip = true;
|
|
88
|
+
if (values['size-limit']) overrides.sizeLimit = true;
|
|
86
89
|
if (values.jsr) overrides.jsr = true;
|
|
87
90
|
for (const [flag, key] of Object.entries(NEGATABLE)) {
|
|
88
91
|
if (values[flag]) overrides[key] = false;
|
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
|
|
@@ -47,6 +48,7 @@ Options:
|
|
|
47
48
|
--workflows <ci|npm-publish|pages|codeql|codecov|stale> (repeatable)
|
|
48
49
|
--deps <renovate|dependabot|none> --license <MIT|Apache-2.0|ISC|none>
|
|
49
50
|
--pkg-checks (publint + are-the-types-wrong) --knip --jsr --canary
|
|
51
|
+
--size-limit (bundle-size budget, libraries)
|
|
50
52
|
--no-coverage --no-community --no-agents --no-vscode --no-editorconfig
|
|
51
53
|
--schema Print the full option/preset schema as JSON (for tools/agents)
|
|
52
54
|
-h, --help Show this help
|
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
|
}
|
|
@@ -15,6 +15,7 @@ import githooks from './githooks.js';
|
|
|
15
15
|
import release from './release.js';
|
|
16
16
|
import cli from './cli.js';
|
|
17
17
|
import checks from './checks.js';
|
|
18
|
+
import sizelimit from './sizelimit.js';
|
|
18
19
|
import jsr from './jsr.js';
|
|
19
20
|
import workflows from './workflows.js';
|
|
20
21
|
import storybook from './storybook.js';
|
|
@@ -38,6 +39,7 @@ export default [
|
|
|
38
39
|
release,
|
|
39
40
|
cli,
|
|
40
41
|
checks,
|
|
42
|
+
sizelimit,
|
|
41
43
|
jsr,
|
|
42
44
|
workflows,
|
|
43
45
|
storybook,
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
// HTTP service target
|
|
2
|
-
// (which listens), adds start/dev scripts,
|
|
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:
|
|
24
|
-
|
|
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
|
|
31
|
-
|
|
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
|
-
|
|
114
|
+
envImport,
|
|
48
115
|
``,
|
|
49
|
-
`const port = ${
|
|
116
|
+
`const port = ${port};`,
|
|
50
117
|
`serve({ fetch: app.fetch, port }, (info) => {`,
|
|
51
118
|
`\tconsole.log(\`Listening on http://localhost:\${info.port}\`);`,
|
|
52
119
|
`});`,
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// size-limit: a bundle-size budget for published libraries. Measures the
|
|
2
|
+
// brotli-compressed size of the built entry and fails when it exceeds the
|
|
3
|
+
// limit — cheap insurance against a dependency silently bloating the package.
|
|
4
|
+
import { toJson } from '../render.js';
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
id: 'sizelimit',
|
|
8
|
+
active: (cfg) => cfg.sizeLimit,
|
|
9
|
+
apply(cfg) {
|
|
10
|
+
return {
|
|
11
|
+
files: {
|
|
12
|
+
'.size-limit.json': toJson([{ name: cfg.name, path: 'dist/index.js', limit: '10 kB' }]),
|
|
13
|
+
},
|
|
14
|
+
pkg: {
|
|
15
|
+
scripts: { size: 'size-limit' },
|
|
16
|
+
devDependencies: {
|
|
17
|
+
'size-limit': '^11.0.0',
|
|
18
|
+
'@size-limit/preset-small-lib': '^11.0.0',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
},
|
|
23
|
+
};
|
|
@@ -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 +
|
|
107
|
+
api + imports,
|
|
87
108
|
``,
|
|
88
109
|
`describe('app', () => {`,
|
|
89
110
|
`\tit('responds on /', async () => {`,
|
|
90
|
-
`\t\tconst res =
|
|
91
|
-
`\t\texpect(res
|
|
111
|
+
`\t\tconst res = ${call};`,
|
|
112
|
+
`\t\texpect(res.${statusProp}).toBe(200);`,
|
|
92
113
|
`\t});`,
|
|
93
114
|
`});`,
|
|
94
115
|
``,
|
|
@@ -80,6 +80,7 @@ function ciWorkflow(cfg, codecov) {
|
|
|
80
80
|
if (cfg.test !== 'none') jobs.push(` - run: ${pmRun(cfg, codecov ? 'coverage' : 'test')}`);
|
|
81
81
|
if (cfg.knip) jobs.push(` - run: ${pmRun(cfg, 'knip')}`);
|
|
82
82
|
if (cfg.hasBuild) jobs.push(` - run: ${pmRun(cfg, 'build')}`);
|
|
83
|
+
if (cfg.sizeLimit) jobs.push(` - run: ${pmRun(cfg, 'size')}`); // after build (measures dist)
|
|
83
84
|
if (cfg.pkgChecks) jobs.push(` - run: ${pmRun(cfg, 'check:pkg')}`); // after build (attw packs dist)
|
|
84
85
|
const cov = codecov
|
|
85
86
|
? '\n - uses: codecov/codecov-action@v4\n with:\n token: ${{ secrets.CODECOV_TOKEN }}'
|
package/src/core/options.js
CHANGED
|
@@ -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: [
|
|
@@ -102,6 +110,7 @@ export const OPTIONS = {
|
|
|
102
110
|
canary: { group: 'release', type: 'boolean', label: 'Snapshot / canary release workflow (Changesets)', default: false },
|
|
103
111
|
pkgChecks: { group: 'quality', type: 'boolean', label: 'Package checks (publint + are-the-types-wrong)', default: false },
|
|
104
112
|
knip: { group: 'quality', type: 'boolean', label: 'Knip (unused files / deps / exports)', default: false },
|
|
113
|
+
sizeLimit: { group: 'quality', type: 'boolean', label: 'size-limit (bundle-size budget, libraries)', default: false },
|
|
105
114
|
|
|
106
115
|
// ---- lint / format ----
|
|
107
116
|
lint: {
|
|
@@ -260,6 +269,8 @@ export function normalizeConfig(input = {}) {
|
|
|
260
269
|
if (!cfg.publishable) cfg.pkgChecks = false;
|
|
261
270
|
// Sourcemaps + shipped source only matter for a published package.
|
|
262
271
|
if (!cfg.publishable) cfg.sourcemaps = false;
|
|
272
|
+
// A bundle-size budget needs a published package with a built entry.
|
|
273
|
+
if (!(cfg.publishable && cfg.hasBuild)) cfg.sizeLimit = false;
|
|
263
274
|
// Env validation is for server-side runtimes (services / CLIs), not libs/apps.
|
|
264
275
|
if (!(cfg.hasService || cfg.hasCli)) cfg.env = false;
|
|
265
276
|
// Canary snapshots ride on the Changesets flow.
|