@rshono/create 1.0.0-rc.0 → 1.0.0-rc.10
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 +83 -53
- package/bin/create-rshono.mjs +5 -3
- package/dist/api.mjs +181 -105
- package/dist/cli.mjs +250 -197
- package/package.json +11 -12
- package/templates/base/AGENTS.md +5 -0
- package/templates/base/CLAUDE.md +1 -0
- package/templates/base/README.md +6 -10
- package/templates/base/_env +2 -5
- package/templates/base/public/favicon.svg +2 -1
- package/templates/base/rshono.config.ts +1 -31
- package/templates/base/src/components/404.tsx +0 -1
- package/templates/base/src/components/500.tsx +0 -6
- package/templates/base/src/components/home.tsx +6 -23
- package/templates/base/src/components/layout.tsx +4 -11
- package/templates/base/src/routes.ts +1 -26
- package/templates/base/src/server.ts +20 -35
- package/templates/base/src/styles.css +1 -64
- package/templates/biome/biome.json +2 -1
- package/templates/biome-tailwind/biome.json +1 -1
- package/templates/eslint/eslint.config.mjs +16 -0
- package/templates/oxfmt/_oxfmtrc.json +1 -1
- package/templates/oxlint/_oxlintrc.json +1 -1
- package/templates/prettier/_prettierignore +6 -1
- package/templates/tailwind/postcss.config.mjs +0 -4
- package/templates/tailwind/rshono.config.ts +2 -38
- package/templates/tailwind/src/components/home.tsx +6 -23
- package/templates/tailwind/src/components/layout.tsx +4 -11
- package/templates/tailwind/src/styles.css +0 -25
- package/templates/base/src/actions.ts +0 -19
- package/templates/base/src/components/greet-form.tsx +0 -27
- package/templates/base/src/lib/env.ts +0 -26
package/dist/api.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {fileURLToPath as __rspack_fileURLToPath} from "node:url";
|
|
3
3
|
import {dirname as __rspack_dirname} from "node:path";
|
|
4
4
|
var __rspack_import_meta_dirname__ = __rspack_dirname(__rspack_fileURLToPath(import.meta.url));
|
|
5
|
-
import {
|
|
5
|
+
import { mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { dirname, join, posix, sep } from "node:path";
|
|
7
7
|
import "node:child_process";
|
|
8
8
|
// The require scope
|
|
@@ -31,18 +31,19 @@ var __webpack_exports__ = {};
|
|
|
31
31
|
// EXPORTS
|
|
32
32
|
__webpack_require__.d(__webpack_exports__, {
|
|
33
33
|
bp: () => (/* reexport */ DEPLOY_TARGET_NAMES),
|
|
34
|
+
ml: () => (/* reexport */ (/* inlined export .ESLINT_TYPESCRIPT */"~6.0.3")),
|
|
35
|
+
wC: () => (/* reexport */ FORMATTER_NAMES),
|
|
34
36
|
e7: () => (/* reexport */ FRAMEWORK_DEPS),
|
|
37
|
+
At: () => (/* reexport */ LINTER_NAMES),
|
|
35
38
|
Ab: () => (/* reexport */ PACKAGE_MANAGERS),
|
|
36
39
|
pm: () => (/* reexport */ QUALITY_PRESETS),
|
|
37
40
|
s_: () => (/* reexport */ RSHONO_RANGE),
|
|
38
41
|
RM: () => (/* reexport */ TOOL_VERSIONS),
|
|
39
|
-
|
|
42
|
+
dh: () => (/* reexport */ conflictingEntries),
|
|
40
43
|
P8: () => (/* reexport */ deployHint),
|
|
41
44
|
Lv: () => (/* reexport */ detectPackageManager),
|
|
42
|
-
WL: () => (/* reexport */ inspectTarget),
|
|
43
|
-
p1: () => (/* reexport */ isDeployTarget),
|
|
44
45
|
eW: () => (/* reexport */ isValidPackageName),
|
|
45
|
-
|
|
46
|
+
VZ: () => (/* reexport */ packageManager),
|
|
46
47
|
et: () => (/* reexport */ plan_plan),
|
|
47
48
|
N7: () => (/* reexport */ selectFeatures),
|
|
48
49
|
PQ: () => (/* reexport */ toPackageName),
|
|
@@ -78,7 +79,11 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
78
79
|
|
|
79
80
|
;// CONCATENATED MODULE: ./src/generated/framework.ts
|
|
80
81
|
// GENERATED by scripts/codegen.mjs from packages/core — do not edit. Run `pnpm --filter @rshono/create codegen`.
|
|
81
|
-
/** The framework release a scaffolded app is pinned to: this package and rshono ship together. */ const RSHONO_VERSION = '1.0.0-rc.
|
|
82
|
+
/** The framework release a scaffolded app is pinned to: this package and rshono ship together. */ const RSHONO_VERSION = '1.0.0-rc.10';
|
|
83
|
+
/**
|
|
84
|
+
* The Node range rshono itself declares, restated in every scaffolded app's `engines` — so a CI image
|
|
85
|
+
* or a contributor on an older Node hears it from their package manager rather than from a stack trace.
|
|
86
|
+
*/ const NODE_ENGINE = '>=22.18.0';
|
|
82
87
|
/**
|
|
83
88
|
* The dependency versions rshono is tested against, copied from its own manifest. Exact where it is
|
|
84
89
|
* exact — React's RSC internals are coupled across builds, and a generated app has no workspace
|
|
@@ -100,22 +105,10 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
100
105
|
name: 'cloudflare',
|
|
101
106
|
hint: 'deploy with `wrangler deploy`'
|
|
102
107
|
},
|
|
103
|
-
{
|
|
104
|
-
name: 'bun',
|
|
105
|
-
hint: 'run `bun dist/server/main.mjs`'
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
name: 'deno',
|
|
109
|
-
hint: 'run `deno serve -A dist/server/main.mjs`'
|
|
110
|
-
},
|
|
111
108
|
{
|
|
112
109
|
name: 'vercel',
|
|
113
110
|
hint: 'deploy with `vercel deploy --prebuilt`'
|
|
114
111
|
},
|
|
115
|
-
{
|
|
116
|
-
name: 'netlify',
|
|
117
|
-
hint: 'deploy with `netlify deploy --build=false --dir=.netlify/publish`'
|
|
118
|
-
},
|
|
119
112
|
{
|
|
120
113
|
name: 'aws-lambda',
|
|
121
114
|
hint: 'zip dist/ with the handler at dist/server/main.mjs'
|
|
@@ -124,7 +117,7 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
124
117
|
|
|
125
118
|
;// CONCATENATED MODULE: ./src/versions.ts
|
|
126
119
|
|
|
127
|
-
|
|
120
|
+
/** Passed straight through, so everything generated from the framework reaches the rest of the package here. */
|
|
128
121
|
/** The framework range a scaffolded app gets. The two packages are released together, so this is ours. */ const RSHONO_RANGE = `^${RSHONO_VERSION}`;
|
|
129
122
|
/**
|
|
130
123
|
* Versions for the optional tooling the features can add — the one place in this package where a
|
|
@@ -145,20 +138,36 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
145
138
|
'@biomejs/biome': '^2.5.6',
|
|
146
139
|
oxlint: '^1.76.0',
|
|
147
140
|
oxfmt: '^0.61.0',
|
|
141
|
+
eslint: '^10.8.0',
|
|
142
|
+
// ESLint's own recommended JavaScript rules, which typescript-eslint layers on top of rather than
|
|
143
|
+
// replaces, and the rules of hooks — the one class of React mistake no type checker sees.
|
|
144
|
+
'@eslint/js': '^10.0.1',
|
|
145
|
+
'typescript-eslint': '^8.65.0',
|
|
146
|
+
'eslint-plugin-react-hooks': '^7.1.1',
|
|
148
147
|
wrangler: '^4.115.0'
|
|
149
148
|
};
|
|
149
|
+
/**
|
|
150
|
+
* The TypeScript an ESLint app pins, in place of the framework's own — the one deliberate exception to
|
|
151
|
+
* {@link FRAMEWORK_DEPS}.
|
|
152
|
+
*
|
|
153
|
+
* typescript-eslint reads TypeScript's compiler API directly rather than through a stable interface,
|
|
154
|
+
* so it accepts `typescript >=4.8.4 <6.1.0` and nothing above; `~6.0.3` is the newest that satisfies
|
|
155
|
+
* it. rshono's declarations compile the same under either, which is what makes this an app's business
|
|
156
|
+
* and not the framework's.
|
|
157
|
+
*
|
|
158
|
+
* When upstream widens the range, this constant and the ESLint feature's use of it are what to delete.
|
|
159
|
+
*/ const ESLINT_TYPESCRIPT = '~6.0.3';
|
|
150
160
|
|
|
151
161
|
;// CONCATENATED MODULE: ./src/features/deploy.ts
|
|
152
162
|
|
|
153
163
|
/**
|
|
154
|
-
* What a deploy target adds beyond the `deploy` line in `rshono.config.ts
|
|
155
|
-
* to read, and the template's to carry.
|
|
164
|
+
* What a deploy target adds beyond the `deploy` line in `rshono.config.ts`, which the template carries.
|
|
156
165
|
*
|
|
157
|
-
* Deliberately thin
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
* the
|
|
166
|
+
* Deliberately thin: the framework arranges its own output for every platform, and `rshono build`
|
|
167
|
+
* writes the one platform config that has to exist (`wrangler.jsonc`, dated the day it ran) if the
|
|
168
|
+
* project has none — a second copy generated here would only go stale. So a target contributes the
|
|
169
|
+
* command that ships the build, the CLI that command needs, the directories to gitignore, and a note
|
|
170
|
+
* for the step no command covers. Several contribute just one of those.
|
|
162
171
|
*/ const DEPLOY_FEATURES = {
|
|
163
172
|
// Where a Node build goes from here is a Dockerfile or a process manager, neither of which this can
|
|
164
173
|
// guess — so the target contributes only the command that runs what was built.
|
|
@@ -168,25 +177,18 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
168
177
|
start: 'rshono start'
|
|
169
178
|
}
|
|
170
179
|
},
|
|
171
|
-
// `rshono start` is the Node target's launcher and refuses a build made for another platform, so
|
|
172
|
-
// these two get the command their own runtime uses under the same script name.
|
|
173
|
-
bun: {
|
|
174
|
-
id: 'deploy-bun',
|
|
175
|
-
scripts: {
|
|
176
|
-
start: 'bun dist/server/main.mjs'
|
|
177
|
-
}
|
|
178
|
-
},
|
|
179
|
-
deno: {
|
|
180
|
-
id: 'deploy-deno',
|
|
181
|
-
scripts: {
|
|
182
|
-
start: 'deno serve -A dist/server/main.mjs'
|
|
183
|
-
}
|
|
184
|
-
},
|
|
185
180
|
cloudflare: {
|
|
186
181
|
id: 'deploy-cloudflare',
|
|
187
182
|
devDependencies: {
|
|
188
183
|
wrangler: TOOL_VERSIONS.wrangler
|
|
189
184
|
},
|
|
185
|
+
// The only two install scripts a scaffolded app can end up with, and wrangler brings both. Each
|
|
186
|
+
// one merely picks the platform binary out of the optional dependency that already carries it, so
|
|
187
|
+
// neither needs to run — `workerd --version` and `esbuild --version` both answer without it.
|
|
188
|
+
allowBuilds: {
|
|
189
|
+
esbuild: false,
|
|
190
|
+
workerd: false
|
|
191
|
+
},
|
|
190
192
|
scripts: {
|
|
191
193
|
deploy: 'rshono build && wrangler deploy'
|
|
192
194
|
},
|
|
@@ -209,15 +211,6 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
209
211
|
'--prebuilt uploads what rshono build assembled; the platform must not rebuild it.'
|
|
210
212
|
]
|
|
211
213
|
},
|
|
212
|
-
netlify: {
|
|
213
|
-
id: 'deploy-netlify',
|
|
214
|
-
scripts: {
|
|
215
|
-
deploy: 'rshono build && netlify deploy --build=false --dir=.netlify/publish'
|
|
216
|
-
},
|
|
217
|
-
gitignore: [
|
|
218
|
-
'.netlify/'
|
|
219
|
-
]
|
|
220
|
-
},
|
|
221
214
|
'aws-lambda': {
|
|
222
215
|
id: 'deploy-aws-lambda',
|
|
223
216
|
notes: [
|
|
@@ -236,8 +229,10 @@ function deployFeature(target) {
|
|
|
236
229
|
* deduplicates by `id`, so `formatter: 'biome', linter: 'biome'` contributes one set of files, one
|
|
237
230
|
* dependency and one pair of scripts.
|
|
238
231
|
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
232
|
+
* A formatter brings `format:check` beside `format`, because the writing half and the CI half want
|
|
233
|
+
* different exit-code behaviour: `format` rewrites files, `format:check` fails instead. A linter brings
|
|
234
|
+
* `lint:fix` beside `lint`, for the same reason in the other direction — `lint` is already the failing
|
|
235
|
+
* one. Biome adds a `check` of its own, which is the pair of them in a single pass.
|
|
241
236
|
*/ const PRETTIER = {
|
|
242
237
|
id: 'prettier',
|
|
243
238
|
overlays: [
|
|
@@ -277,6 +272,29 @@ const OXLINT = {
|
|
|
277
272
|
'lint:fix': 'oxlint --fix'
|
|
278
273
|
}
|
|
279
274
|
};
|
|
275
|
+
/**
|
|
276
|
+
* The one feature that changes a dependency the framework otherwise decides: typescript-eslint cannot
|
|
277
|
+
* be installed alongside the TypeScript rshono is tested against, so an ESLint app pins the newest one
|
|
278
|
+
* its peer range accepts (see {@link ESLINT_TYPESCRIPT}). Its rules are type-aware — the reason to
|
|
279
|
+
* reach for ESLint over a syntax-only linter — so the config it ships hands the parser the whole
|
|
280
|
+
* program rather than linting file by file.
|
|
281
|
+
*/ const ESLINT = {
|
|
282
|
+
id: 'eslint',
|
|
283
|
+
overlays: [
|
|
284
|
+
'eslint'
|
|
285
|
+
],
|
|
286
|
+
devDependencies: {
|
|
287
|
+
eslint: TOOL_VERSIONS.eslint,
|
|
288
|
+
'@eslint/js': TOOL_VERSIONS["@eslint/js"],
|
|
289
|
+
'typescript-eslint': TOOL_VERSIONS["typescript-eslint"],
|
|
290
|
+
'eslint-plugin-react-hooks': TOOL_VERSIONS["eslint-plugin-react-hooks"],
|
|
291
|
+
typescript: (/* inlined export .ESLINT_TYPESCRIPT */"~6.0.3")
|
|
292
|
+
},
|
|
293
|
+
scripts: {
|
|
294
|
+
lint: 'eslint .',
|
|
295
|
+
'lint:fix': 'eslint . --fix'
|
|
296
|
+
}
|
|
297
|
+
};
|
|
280
298
|
const BIOME = {
|
|
281
299
|
id: 'biome',
|
|
282
300
|
overlays: [
|
|
@@ -301,6 +319,7 @@ const FORMATTERS = {
|
|
|
301
319
|
};
|
|
302
320
|
const LINTERS = {
|
|
303
321
|
oxlint: OXLINT,
|
|
322
|
+
eslint: ESLINT,
|
|
304
323
|
biome: BIOME,
|
|
305
324
|
none: null
|
|
306
325
|
};
|
|
@@ -314,14 +333,13 @@ function linterFeature(linter) {
|
|
|
314
333
|
;// CONCATENATED MODULE: ./src/features/styling.ts
|
|
315
334
|
|
|
316
335
|
/**
|
|
317
|
-
* Tailwind is a PostCSS plugin and nothing more, which is the whole of this feature: four packages,
|
|
318
|
-
* an overlay carrying
|
|
319
|
-
*
|
|
320
|
-
*
|
|
336
|
+
* Tailwind is a PostCSS plugin and nothing more, which is the whole of this feature: four packages,
|
|
337
|
+
* plus an overlay carrying `postcss.config.mjs`, an `rshono.config.ts` whose `rspack` hook puts
|
|
338
|
+
* postcss-loader in front of the CSS parser, a Tailwind entry stylesheet, and the two views rewritten
|
|
339
|
+
* in utilities.
|
|
321
340
|
*
|
|
322
341
|
* `postcss` and `postcss-loader` are the app's dependencies rather than the framework's — rshono
|
|
323
|
-
* compiles CSS natively
|
|
324
|
-
* install one.
|
|
342
|
+
* compiles CSS natively, so an app that does not want a plugin chain does not install one.
|
|
325
343
|
*/ const TAILWIND = {
|
|
326
344
|
id: 'tailwind',
|
|
327
345
|
overlays: [
|
|
@@ -367,6 +385,22 @@ function stylingFeature(styling) {
|
|
|
367
385
|
|
|
368
386
|
;// CONCATENATED MODULE: ./src/options.ts
|
|
369
387
|
|
|
388
|
+
/*
|
|
389
|
+
* The names each option accepts, spelled once. The types below are derived from them, the CLI validates
|
|
390
|
+
* its flags against them and prints them in `--help`, and `pm.ts` recognises a package manager by them
|
|
391
|
+
* — so a name added here reaches all three without a second list to remember.
|
|
392
|
+
*/ const FORMATTER_NAMES = [
|
|
393
|
+
'prettier',
|
|
394
|
+
'biome',
|
|
395
|
+
'oxfmt',
|
|
396
|
+
'none'
|
|
397
|
+
];
|
|
398
|
+
const LINTER_NAMES = [
|
|
399
|
+
'oxlint',
|
|
400
|
+
'eslint',
|
|
401
|
+
'biome',
|
|
402
|
+
'none'
|
|
403
|
+
];
|
|
370
404
|
const PACKAGE_MANAGERS = [
|
|
371
405
|
'npm',
|
|
372
406
|
'pnpm',
|
|
@@ -377,9 +411,6 @@ const DEPLOY_TARGET_NAMES = DEPLOY_TARGETS.map((target)=>target.name);
|
|
|
377
411
|
function deployHint(name) {
|
|
378
412
|
return DEPLOY_TARGETS.find((target)=>target.name === name)?.hint ?? '';
|
|
379
413
|
}
|
|
380
|
-
function isDeployTarget(value) {
|
|
381
|
-
return DEPLOY_TARGET_NAMES.includes(value);
|
|
382
|
-
}
|
|
383
414
|
const QUALITY_PRESETS = [
|
|
384
415
|
{
|
|
385
416
|
id: 'prettier-oxlint',
|
|
@@ -388,6 +419,13 @@ const QUALITY_PRESETS = [
|
|
|
388
419
|
formatter: 'prettier',
|
|
389
420
|
linter: 'oxlint'
|
|
390
421
|
},
|
|
422
|
+
{
|
|
423
|
+
id: 'prettier-eslint',
|
|
424
|
+
label: 'Prettier + ESLint',
|
|
425
|
+
hint: 'type-aware rules — pins TypeScript 6, which is all typescript-eslint accepts',
|
|
426
|
+
formatter: 'prettier',
|
|
427
|
+
linter: 'eslint'
|
|
428
|
+
},
|
|
391
429
|
{
|
|
392
430
|
id: 'biome',
|
|
393
431
|
label: 'Biome',
|
|
@@ -430,11 +468,16 @@ const QUALITY_PRESETS = [
|
|
|
430
468
|
return /^(?:@[a-z\d\-*~][a-z\d\-*._~]*\/)?[a-z\d\-~][a-z\d\-._~]*$/.test(name) && name.length <= 214;
|
|
431
469
|
}
|
|
432
470
|
|
|
471
|
+
;// CONCATENATED MODULE: external "node:fs"
|
|
472
|
+
|
|
473
|
+
;// CONCATENATED MODULE: external "node:path"
|
|
474
|
+
|
|
433
475
|
;// CONCATENATED MODULE: ./src/pkg.ts
|
|
434
476
|
|
|
435
477
|
/**
|
|
436
|
-
* The scripts every app gets. `start` is not among them
|
|
437
|
-
*
|
|
478
|
+
* The scripts every app gets. `start` is not among them, because it means something different per
|
|
479
|
+
* platform: `node` is the one target that runs the build itself, so it contributes its own, and a
|
|
480
|
+
* platform target contributes a `deploy` instead, where its platform has one command to give.
|
|
438
481
|
*/ const BASE_SCRIPTS = {
|
|
439
482
|
dev: 'rshono dev',
|
|
440
483
|
build: 'rshono build',
|
|
@@ -454,6 +497,30 @@ const QUALITY_PRESETS = [
|
|
|
454
497
|
function sorted(record) {
|
|
455
498
|
return Object.fromEntries(Object.entries(record).sort(([a], [b])=>a < b ? -1 : 1));
|
|
456
499
|
}
|
|
500
|
+
/**
|
|
501
|
+
* pnpm's settings for the new app, written only when a feature has something to put in them. `null`
|
|
502
|
+
* means there is nothing to say, so no file is written.
|
|
503
|
+
*
|
|
504
|
+
* It exists for one field, `allowBuilds`. pnpm fails an install — and every `pnpm dev` after it —
|
|
505
|
+
* until the project has said whether a dependency's install script should run. Nothing rshono itself
|
|
506
|
+
* installs has one, so most apps get no file; the packages that do (wrangler's esbuild and workerd)
|
|
507
|
+
* declare their answer on the feature that brings them.
|
|
508
|
+
*
|
|
509
|
+
* A file rather than a `pnpm` key in `package.json`, which pnpm 11 no longer reads.
|
|
510
|
+
*/ function buildPnpmSettings(features) {
|
|
511
|
+
const allowBuilds = {};
|
|
512
|
+
for (const feature of features)Object.assign(allowBuilds, feature.allowBuilds);
|
|
513
|
+
const entries = Object.entries(sorted(allowBuilds));
|
|
514
|
+
if (entries.length === 0) return null;
|
|
515
|
+
return [
|
|
516
|
+
'# Which dependencies may run an install script. pnpm runs none it has not been told about, and',
|
|
517
|
+
'# fails the install rather than skip one quietly — so anything added later belongs here too.',
|
|
518
|
+
'# `false` means the script was looked at: these ship their real binary as an optional dependency.',
|
|
519
|
+
'allowBuilds:',
|
|
520
|
+
...entries.map(([name, allowed])=>` ${name}: ${allowed}`),
|
|
521
|
+
''
|
|
522
|
+
].join('\n');
|
|
523
|
+
}
|
|
457
524
|
/**
|
|
458
525
|
* Assembles `package.json` from the answers and whatever the selected features contribute.
|
|
459
526
|
*
|
|
@@ -485,10 +552,9 @@ function sorted(record) {
|
|
|
485
552
|
version: '0.1.0',
|
|
486
553
|
private: true,
|
|
487
554
|
type: 'module',
|
|
488
|
-
//
|
|
489
|
-
// Node finds out from their package manager rather than from a stack trace.
|
|
555
|
+
// Generated from the framework's own manifest, so the app's floor cannot drift below rshono's.
|
|
490
556
|
engines: {
|
|
491
|
-
node:
|
|
557
|
+
node: NODE_ENGINE
|
|
492
558
|
},
|
|
493
559
|
scripts,
|
|
494
560
|
dependencies: sorted(dependencies),
|
|
@@ -504,25 +570,24 @@ function sorted(record) {
|
|
|
504
570
|
return `${JSON.stringify(ordered, null, 2)}\n`;
|
|
505
571
|
}
|
|
506
572
|
|
|
507
|
-
;// CONCATENATED MODULE: external "node:fs"
|
|
508
|
-
|
|
509
|
-
;// CONCATENATED MODULE: external "node:path"
|
|
510
|
-
|
|
511
573
|
;// CONCATENATED MODULE: ./src/render.ts
|
|
512
574
|
|
|
513
|
-
|
|
575
|
+
/**
|
|
576
|
+
* `{{NAME}}`, deliberately not `__NAME__`: templates are real files that real tools run over, and in
|
|
577
|
+
* markdown `__NAME__` *is* strong emphasis — Prettier rewrites it to `**NAME**` and the token stops
|
|
578
|
+
* matching. `{{…}}` means nothing to any format these templates are written in.
|
|
579
|
+
*/ const TOKEN_PATTERN = /\{\{[A-Z][A-Z\d_]*\}\}/g;
|
|
514
580
|
function tokensFor(answers, pm) {
|
|
515
581
|
return {
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
__PM_RUN__: pm.run
|
|
582
|
+
'{{PROJECT_NAME}}': answers.packageName,
|
|
583
|
+
'{{DEPLOY_TARGET}}': answers.deploy,
|
|
584
|
+
'{{DEPLOY_HINT}}': deployHint(answers.deploy),
|
|
585
|
+
'{{PM_RUN}}': pm.run
|
|
521
586
|
};
|
|
522
587
|
}
|
|
523
588
|
/**
|
|
524
589
|
* Substitutes tokens, and throws on one it doesn't know — a typo in a template would otherwise ship a
|
|
525
|
-
* literal `
|
|
590
|
+
* literal `{{PORJECT_NAME}}` into somebody's new app, which no test of the generator's logic would
|
|
526
591
|
* catch.
|
|
527
592
|
*/ function render(contents, tokens, source) {
|
|
528
593
|
return contents.replace(TOKEN_PATTERN, (token)=>{
|
|
@@ -555,10 +620,9 @@ function readTemplateDir(dir) {
|
|
|
555
620
|
/**
|
|
556
621
|
* `_gitignore` → `.gitignore`, and so on for every dotfile.
|
|
557
622
|
*
|
|
558
|
-
* npm strips a literal `.gitignore` out of a published tarball, so a template cannot
|
|
559
|
-
*
|
|
560
|
-
*
|
|
561
|
-
* fix. It applies to the basename only, so `src/lib/_x.ts` is a dotfile but `templates/_x/y.ts` is not.
|
|
623
|
+
* npm strips a literal `.gitignore` out of a published tarball, so a template cannot contain one — it
|
|
624
|
+
* would exist in the repo, pass every local test, and be missing from the package everybody installs.
|
|
625
|
+
* Applies to the basename only, so `src/lib/_x.ts` is a dotfile but `templates/_x/y.ts` is not.
|
|
562
626
|
*/ function undotted(path) {
|
|
563
627
|
const segments = path.split(posix.sep);
|
|
564
628
|
const name = segments.pop();
|
|
@@ -598,6 +662,9 @@ function readTemplateDir(dir) {
|
|
|
598
662
|
const gitignore = files.get('.gitignore');
|
|
599
663
|
if (gitignore) files.set('.gitignore', appendGitignore(gitignore, features));
|
|
600
664
|
files.set('package.json', buildPackageJson(answers, features, pm));
|
|
665
|
+
// Only for pnpm, and only when a feature brought an install script to answer for — see `buildPnpmSettings`.
|
|
666
|
+
const pnpmSettings = pm.name === 'pnpm' ? buildPnpmSettings(features) : null;
|
|
667
|
+
if (pnpmSettings) files.set('pnpm-workspace.yaml', pnpmSettings);
|
|
601
668
|
return {
|
|
602
669
|
// Sorted, so both the write order and a test's snapshot are stable.
|
|
603
670
|
files: new Map([
|
|
@@ -612,6 +679,7 @@ function readTemplateDir(dir) {
|
|
|
612
679
|
|
|
613
680
|
;// CONCATENATED MODULE: ./src/pm.ts
|
|
614
681
|
|
|
682
|
+
|
|
615
683
|
const INSTALL = {
|
|
616
684
|
npm: [
|
|
617
685
|
'install'
|
|
@@ -631,7 +699,7 @@ const RUN = {
|
|
|
631
699
|
bun: 'bun'
|
|
632
700
|
};
|
|
633
701
|
function isKnown(name) {
|
|
634
|
-
return name
|
|
702
|
+
return PACKAGE_MANAGERS.includes(name);
|
|
635
703
|
}
|
|
636
704
|
function packageManager(name, version) {
|
|
637
705
|
return {
|
|
@@ -642,8 +710,8 @@ function packageManager(name, version) {
|
|
|
642
710
|
};
|
|
643
711
|
}
|
|
644
712
|
/**
|
|
645
|
-
* Which package manager invoked us. Every one of them sets `npm_config_user_agent`
|
|
646
|
-
* spawns — `pnpm/11.9.0 npm/? node/v22.14.0 darwin arm64` — so `
|
|
713
|
+
* Which package manager invoked us. Every one of them sets `npm_config_user_agent` on the process it
|
|
714
|
+
* spawns — `pnpm/11.9.0 npm/? node/v22.14.0 darwin arm64` — so `pnx @rshono/create` scaffolds a pnpm
|
|
647
715
|
* project without asking, and the exact version comes along for the `packageManager` field.
|
|
648
716
|
*
|
|
649
717
|
* Falls back to npm, which is also what a bare `node bin/create-rshono.mjs` gets.
|
|
@@ -660,12 +728,6 @@ function packageManager(name, version) {
|
|
|
660
728
|
*/ function runInstall(pm, cwd) {
|
|
661
729
|
return run(pm, pm.install, cwd);
|
|
662
730
|
}
|
|
663
|
-
/** `<pm> run <script>` — the one form every package manager accepts, Yarn v1 included. */ function runScript(pm, script, cwd) {
|
|
664
|
-
return run(pm, [
|
|
665
|
-
'run',
|
|
666
|
-
script
|
|
667
|
-
], cwd);
|
|
668
|
-
}
|
|
669
731
|
function run(pm, args, cwd) {
|
|
670
732
|
const result = spawnSync(pm.name, args, {
|
|
671
733
|
cwd,
|
|
@@ -688,15 +750,20 @@ function run(pm, args, cwd) {
|
|
|
688
750
|
'.vscode',
|
|
689
751
|
'Thumbs.db'
|
|
690
752
|
]);
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
753
|
+
/**
|
|
754
|
+
* What is already at the target path, ignoring the entries a fresh clone or an editor leaves behind —
|
|
755
|
+
* which is what decides whether scaffolding into it is safe.
|
|
756
|
+
*
|
|
757
|
+
* A path that does not exist yet is no conflict. A path that exists and is *not* a directory throws
|
|
758
|
+
* rather than reporting an empty list, since `--force` should not write into one either — otherwise
|
|
759
|
+
* `create-rshono README.md` gets as far as `mkdir` before failing on a raw ENOTDIR.
|
|
760
|
+
*/ function conflictingEntries(dir) {
|
|
761
|
+
const stats = statSync(dir, {
|
|
762
|
+
throwIfNoEntry: false
|
|
763
|
+
});
|
|
764
|
+
if (!stats) return [];
|
|
765
|
+
if (!stats.isDirectory()) throw new Error(`${dir} already exists and is not a directory.`);
|
|
766
|
+
return readdirSync(dir).filter((entry)=>!IGNORED_ENTRIES.has(entry));
|
|
700
767
|
}
|
|
701
768
|
/**
|
|
702
769
|
* Writes the plan. Directories are created as needed, and files are written with the plan's own
|
|
@@ -718,7 +785,16 @@ function inspectTarget(dir) {
|
|
|
718
785
|
/**
|
|
719
786
|
* The generator without the CLI around it: answers in, the exact set of files out. This is what the
|
|
720
787
|
* test suite asserts on — the whole matrix of options, in memory, with no directory to clean up — and
|
|
721
|
-
* what another tool would call to scaffold an app itself
|
|
788
|
+
* what another tool would call to scaffold an app itself:
|
|
789
|
+
*
|
|
790
|
+
* ```ts
|
|
791
|
+
* import { packageManager, plan, writePlan } from '@rshono/create';
|
|
792
|
+
*
|
|
793
|
+
* const answers = { packageName: 'my-app', deploy: 'node', styling: 'css', formatter: 'prettier', linter: 'oxlint' };
|
|
794
|
+
* writePlan(plan(answers, packageManager('npm')), './my-app');
|
|
795
|
+
* ```
|
|
796
|
+
*
|
|
797
|
+
* `package.json` is assembled inside `plan`, so nothing here needs to be composed by hand.
|
|
722
798
|
*
|
|
723
799
|
* @packageDocumentation
|
|
724
800
|
*/
|
|
@@ -728,22 +804,22 @@ function inspectTarget(dir) {
|
|
|
728
804
|
|
|
729
805
|
|
|
730
806
|
|
|
731
|
-
|
|
732
807
|
var __webpack_exports__DEPLOY_TARGET_NAMES = __webpack_exports__.bp;
|
|
808
|
+
var __webpack_exports__ESLINT_TYPESCRIPT = __webpack_exports__.ml;
|
|
809
|
+
var __webpack_exports__FORMATTER_NAMES = __webpack_exports__.wC;
|
|
733
810
|
var __webpack_exports__FRAMEWORK_DEPS = __webpack_exports__.e7;
|
|
811
|
+
var __webpack_exports__LINTER_NAMES = __webpack_exports__.At;
|
|
734
812
|
var __webpack_exports__PACKAGE_MANAGERS = __webpack_exports__.Ab;
|
|
735
813
|
var __webpack_exports__QUALITY_PRESETS = __webpack_exports__.pm;
|
|
736
814
|
var __webpack_exports__RSHONO_RANGE = __webpack_exports__.s_;
|
|
737
815
|
var __webpack_exports__TOOL_VERSIONS = __webpack_exports__.RM;
|
|
738
|
-
var
|
|
816
|
+
var __webpack_exports__conflictingEntries = __webpack_exports__.dh;
|
|
739
817
|
var __webpack_exports__deployHint = __webpack_exports__.P8;
|
|
740
818
|
var __webpack_exports__detectPackageManager = __webpack_exports__.Lv;
|
|
741
|
-
var __webpack_exports__inspectTarget = __webpack_exports__.WL;
|
|
742
|
-
var __webpack_exports__isDeployTarget = __webpack_exports__.p1;
|
|
743
819
|
var __webpack_exports__isValidPackageName = __webpack_exports__.eW;
|
|
744
|
-
var __webpack_exports__packageManager = __webpack_exports__.
|
|
820
|
+
var __webpack_exports__packageManager = __webpack_exports__.VZ;
|
|
745
821
|
var __webpack_exports__plan = __webpack_exports__.et;
|
|
746
822
|
var __webpack_exports__selectFeatures = __webpack_exports__.N7;
|
|
747
823
|
var __webpack_exports__toPackageName = __webpack_exports__.PQ;
|
|
748
824
|
var __webpack_exports__writePlan = __webpack_exports__.pD;
|
|
749
|
-
export { __webpack_exports__DEPLOY_TARGET_NAMES as DEPLOY_TARGET_NAMES, __webpack_exports__FRAMEWORK_DEPS as FRAMEWORK_DEPS, __webpack_exports__PACKAGE_MANAGERS as PACKAGE_MANAGERS, __webpack_exports__QUALITY_PRESETS as QUALITY_PRESETS, __webpack_exports__RSHONO_RANGE as RSHONO_RANGE, __webpack_exports__TOOL_VERSIONS as TOOL_VERSIONS,
|
|
825
|
+
export { __webpack_exports__DEPLOY_TARGET_NAMES as DEPLOY_TARGET_NAMES, __webpack_exports__ESLINT_TYPESCRIPT as ESLINT_TYPESCRIPT, __webpack_exports__FORMATTER_NAMES as FORMATTER_NAMES, __webpack_exports__FRAMEWORK_DEPS as FRAMEWORK_DEPS, __webpack_exports__LINTER_NAMES as LINTER_NAMES, __webpack_exports__PACKAGE_MANAGERS as PACKAGE_MANAGERS, __webpack_exports__QUALITY_PRESETS as QUALITY_PRESETS, __webpack_exports__RSHONO_RANGE as RSHONO_RANGE, __webpack_exports__TOOL_VERSIONS as TOOL_VERSIONS, __webpack_exports__conflictingEntries as conflictingEntries, __webpack_exports__deployHint as deployHint, __webpack_exports__detectPackageManager as detectPackageManager, __webpack_exports__isValidPackageName as isValidPackageName, __webpack_exports__packageManager as packageManager, __webpack_exports__plan as plan, __webpack_exports__selectFeatures as selectFeatures, __webpack_exports__toPackageName as toPackageName, __webpack_exports__writePlan as writePlan };
|