create-packkit 0.5.0 → 1.0.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 +3 -1
- package/package.json +1 -1
- package/src/cli/args.js +2 -0
- package/src/cli/index.js +51 -5
- package/src/core/features/meta.js +22 -6
- package/src/core/features/workflows.js +6 -6
- package/src/core/presets.js +4 -0
package/README.md
CHANGED
|
@@ -49,7 +49,9 @@ No install needed: **[danmat.github.io/create-packkit](https://danmat.github.io/
|
|
|
49
49
|
|
|
50
50
|
## Presets
|
|
51
51
|
|
|
52
|
-
`ts-lib` · `js-lib` · `ts-cli` / `cli` · `react-lib` · `react-lib-js` · `react-app` · `vue-lib` · `svelte-lib` · `node-service` · `oss` · `minimal` · `full` — named bundles of the options above. See the [roadmap](ROADMAP.md) for what's next.
|
|
52
|
+
`ts-lib` · `js-lib` · `ts-cli` / `cli` · `react-lib` · `react-lib-js` · `react-app` · `vue-lib` · `vue-app` · `svelte-lib` · `svelte-app` · `node-service` · `oss` · `minimal` · `full` — named bundles of the options above. See the [roadmap](ROADMAP.md) for what's next.
|
|
53
|
+
|
|
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.
|
|
53
55
|
|
|
54
56
|
## How it works
|
|
55
57
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-packkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.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
|
@@ -34,6 +34,7 @@ export function parseCliArgs(argv) {
|
|
|
34
34
|
allowPositionals: true,
|
|
35
35
|
options: {
|
|
36
36
|
preset: { type: 'string' },
|
|
37
|
+
from: { type: 'string' },
|
|
37
38
|
name: { type: 'string' },
|
|
38
39
|
yes: { type: 'boolean', short: 'y' },
|
|
39
40
|
here: { type: 'boolean' },
|
|
@@ -72,6 +73,7 @@ export function parseCliArgs(argv) {
|
|
|
72
73
|
|
|
73
74
|
return {
|
|
74
75
|
preset,
|
|
76
|
+
from: values.from,
|
|
75
77
|
name,
|
|
76
78
|
here: !!values.here,
|
|
77
79
|
yes: !!values.yes,
|
package/src/cli/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resolve, basename } from 'node:path';
|
|
2
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import * as p from '@clack/prompts';
|
|
5
5
|
import { generate, fromPreset, normalizeConfig, PRESET_NAMES } from '../core/index.js';
|
|
@@ -27,6 +27,7 @@ Presets: ${PRESET_NAMES.join(', ')}
|
|
|
27
27
|
|
|
28
28
|
Options:
|
|
29
29
|
--preset <name> Use a preset (skips the wizard)
|
|
30
|
+
--from <file> Load defaults from a JSON profile (or packkit.config.json)
|
|
30
31
|
--name <name> Package name
|
|
31
32
|
--here Scaffold into the current directory
|
|
32
33
|
-y, --yes Accept defaults / preset, no prompts
|
|
@@ -55,16 +56,20 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
55
56
|
if (args.help) return void console.log(HELP);
|
|
56
57
|
if (args.version) return void console.log(pkgVersion());
|
|
57
58
|
|
|
58
|
-
const interactive = !args.preset && !args.yes && process.stdout.isTTY;
|
|
59
|
+
const interactive = !args.preset && !args.yes && !args.from && process.stdout.isTTY;
|
|
60
|
+
|
|
61
|
+
// Precedence: preset < profile file < CLI flags.
|
|
62
|
+
const profile = loadProfile(args);
|
|
63
|
+
const seed = { ...profile, ...args.overrides };
|
|
59
64
|
|
|
60
65
|
let config;
|
|
61
66
|
if (interactive) {
|
|
62
67
|
p.intro('📦 Packkit');
|
|
63
|
-
config = normalizeConfig(await runWizard(
|
|
68
|
+
config = normalizeConfig(await runWizard(seed));
|
|
64
69
|
} else if (args.preset) {
|
|
65
|
-
config = fromPreset(args.preset,
|
|
70
|
+
config = fromPreset(args.preset, seed);
|
|
66
71
|
} else {
|
|
67
|
-
config = normalizeConfig(
|
|
72
|
+
config = normalizeConfig(seed);
|
|
68
73
|
}
|
|
69
74
|
|
|
70
75
|
config.gitInit = args.git;
|
|
@@ -110,8 +115,49 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
110
115
|
console.log(done);
|
|
111
116
|
if (next.length) console.log('\nNext steps:\n ' + next.join('\n '));
|
|
112
117
|
}
|
|
118
|
+
|
|
119
|
+
const latest = await checkForUpdate(pkgVersion());
|
|
120
|
+
if (latest) console.log(`\n↑ A newer create-packkit is available: ${latest} (you have ${pkgVersion()}). Use @latest to update.`);
|
|
113
121
|
}
|
|
114
122
|
|
|
115
123
|
function runWord(config) {
|
|
116
124
|
return config.packageManager === 'npm' ? 'npm run' : config.packageManager;
|
|
117
125
|
}
|
|
126
|
+
|
|
127
|
+
// Load a partial config from --from <file>, or a packkit.config.json in cwd.
|
|
128
|
+
function loadProfile(args) {
|
|
129
|
+
const path = args.from || (existsSync('packkit.config.json') ? 'packkit.config.json' : null);
|
|
130
|
+
if (!path) return {};
|
|
131
|
+
try {
|
|
132
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
133
|
+
} catch (err) {
|
|
134
|
+
console.error(`Could not read profile "${path}": ${err instanceof Error ? err.message : err}`);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Best-effort update check — TTY only, short timeout, never throws or blocks.
|
|
140
|
+
async function checkForUpdate(current) {
|
|
141
|
+
try {
|
|
142
|
+
if (process.env.CI || process.env.NO_UPDATE_NOTIFIER || !process.stdout.isTTY) return null;
|
|
143
|
+
const ctrl = new AbortController();
|
|
144
|
+
const timer = setTimeout(() => ctrl.abort(), 1500);
|
|
145
|
+
const res = await fetch('https://registry.npmjs.org/create-packkit/latest', { signal: ctrl.signal });
|
|
146
|
+
clearTimeout(timer);
|
|
147
|
+
if (!res.ok) return null;
|
|
148
|
+
const { version } = await res.json();
|
|
149
|
+
return version && isNewer(version, current) ? version : null;
|
|
150
|
+
} catch {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function isNewer(latest, current) {
|
|
156
|
+
const a = latest.split('.').map(Number);
|
|
157
|
+
const b = current.split('.').map(Number);
|
|
158
|
+
for (let i = 0; i < 3; i++) {
|
|
159
|
+
if ((a[i] || 0) > (b[i] || 0)) return true;
|
|
160
|
+
if ((a[i] || 0) < (b[i] || 0)) return false;
|
|
161
|
+
}
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
@@ -77,6 +77,23 @@ function run(cfg, script) {
|
|
|
77
77
|
return cfg.packageManager === 'npm' ? `npm run ${script}` : `${cfg.packageManager} ${script}`;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
function makeBadges(cfg) {
|
|
81
|
+
const badges = [];
|
|
82
|
+
const publishable = (cfg.hasLibrary || cfg.hasCli) && !cfg.hasApp && !cfg.hasService;
|
|
83
|
+
if (publishable) {
|
|
84
|
+
const enc = encodeURIComponent(cfg.name);
|
|
85
|
+
badges.push(`[](https://www.npmjs.com/package/${cfg.name})`);
|
|
86
|
+
}
|
|
87
|
+
const repo = cfg.repo ? cfg.repo.replace(/\.git$/, '') : '';
|
|
88
|
+
if (repo && (cfg.workflows || []).includes('ci')) {
|
|
89
|
+
badges.push(`[](${repo}/actions/workflows/ci.yml)`);
|
|
90
|
+
}
|
|
91
|
+
if (cfg.license !== 'none') {
|
|
92
|
+
badges.push(`[}-blue.svg)](LICENSE)`);
|
|
93
|
+
}
|
|
94
|
+
return badges.join(' ');
|
|
95
|
+
}
|
|
96
|
+
|
|
80
97
|
function readme(cfg) {
|
|
81
98
|
const install = {
|
|
82
99
|
npm: `npm install ${cfg.name}`,
|
|
@@ -90,14 +107,13 @@ function readme(cfg) {
|
|
|
90
107
|
'',
|
|
91
108
|
cfg.description || '_A modern package scaffolded with [Packkit](https://danmat.github.io/create-packkit/)._',
|
|
92
109
|
'',
|
|
93
|
-
'## Install',
|
|
94
|
-
'',
|
|
95
|
-
'```sh',
|
|
96
|
-
install,
|
|
97
|
-
'```',
|
|
98
|
-
'',
|
|
99
110
|
];
|
|
100
111
|
|
|
112
|
+
const badges = makeBadges(cfg);
|
|
113
|
+
if (badges) lines.push(badges, '');
|
|
114
|
+
|
|
115
|
+
lines.push('## Install', '', '```sh', install, '```', '');
|
|
116
|
+
|
|
101
117
|
if (cfg.hasApp) {
|
|
102
118
|
lines.push('## Develop', '', '```sh', run(cfg, 'dev') + ' # start the dev server', run(cfg, 'build') + ' # production build', '```', '');
|
|
103
119
|
} else if (cfg.hasLibrary && cfg.isReact) {
|
|
@@ -140,8 +140,12 @@ function releaseWorkflow(cfg) {
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
function pagesWorkflow(cfg) {
|
|
143
|
+
// Storybook projects deploy the built catalog; everything else serves ./docs.
|
|
144
|
+
const build = cfg.storybook
|
|
145
|
+
? [setupSteps(cfg), ` - run: ${pmRun(cfg, 'build-storybook')}`, ' - uses: actions/configure-pages@v5', ' - uses: actions/upload-pages-artifact@v3', ' with:', ' path: ./storybook-static']
|
|
146
|
+
: [' - uses: actions/checkout@v4', ' - uses: actions/configure-pages@v5', ' - uses: actions/upload-pages-artifact@v3', ' with:', ' path: ./docs'];
|
|
143
147
|
return [
|
|
144
|
-
|
|
148
|
+
`name: Deploy ${cfg.storybook ? 'Storybook' : 'Pages'}`,
|
|
145
149
|
'on:',
|
|
146
150
|
' push:',
|
|
147
151
|
' branches: [main]',
|
|
@@ -159,11 +163,7 @@ function pagesWorkflow(cfg) {
|
|
|
159
163
|
' url: ${{ steps.deployment.outputs.page_url }}',
|
|
160
164
|
' runs-on: ubuntu-latest',
|
|
161
165
|
' steps:',
|
|
162
|
-
'
|
|
163
|
-
' - uses: actions/configure-pages@v5',
|
|
164
|
-
' - uses: actions/upload-pages-artifact@v3',
|
|
165
|
-
' with:',
|
|
166
|
-
' path: ./docs',
|
|
166
|
+
build.join('\n'),
|
|
167
167
|
' - id: deployment',
|
|
168
168
|
' uses: actions/deploy-pages@v4',
|
|
169
169
|
'',
|
package/src/core/presets.js
CHANGED
|
@@ -10,7 +10,9 @@ export const PRESETS = {
|
|
|
10
10
|
'react-lib-js': { language: 'js', framework: 'react', target: ['library'], moduleFormat: 'dual', bundler: 'tsup', test: 'vitest' },
|
|
11
11
|
'react-app': { language: 'ts', framework: 'react', target: ['app'], test: 'vitest', release: 'none', workflows: ['ci'] },
|
|
12
12
|
'vue-lib': { language: 'ts', framework: 'vue', target: ['library'], test: 'vitest' },
|
|
13
|
+
'vue-app': { language: 'ts', framework: 'vue', target: ['app'], test: 'vitest', release: 'none', workflows: ['ci'] },
|
|
13
14
|
'svelte-lib': { language: 'ts', framework: 'svelte', target: ['library'], test: 'vitest' },
|
|
15
|
+
'svelte-app': { language: 'ts', framework: 'svelte', target: ['app'], test: 'vitest', release: 'none', workflows: ['ci'] },
|
|
14
16
|
'node-service': {
|
|
15
17
|
language: 'ts', target: ['service'], moduleFormat: 'esm', bundler: 'tsup',
|
|
16
18
|
test: 'vitest', lint: 'eslint-prettier', gitHooks: 'simple-git-hooks',
|
|
@@ -48,7 +50,9 @@ export const PRESET_INFO = {
|
|
|
48
50
|
'react-lib-js': 'React component library (JS) — JSX, peer deps, jsdom tests.',
|
|
49
51
|
'react-app': 'React SPA — Vite dev server, build, Testing Library.',
|
|
50
52
|
'vue-lib': 'Vue component library — Vite lib build (SFCs), dual + types.',
|
|
53
|
+
'vue-app': 'Vue SPA — Vite dev server, build, Testing Library.',
|
|
51
54
|
'svelte-lib': 'Svelte component library — ships source, peer svelte, jsdom tests.',
|
|
55
|
+
'svelte-app': 'Svelte SPA — Vite dev server, build, Testing Library.',
|
|
52
56
|
'node-service': 'Node HTTP service (Hono) — tsx dev, tsup build, Dockerfile.',
|
|
53
57
|
oss: 'Full open-source library — coverage, CodeQL, Codecov, Renovate, Changesets.',
|
|
54
58
|
minimal: 'Bare TS library — tsup only, no tests/lint/CI.',
|