@rshono/create 1.0.0-rc.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 +105 -0
- package/bin/create-rshono.mjs +29 -0
- package/dist/api.mjs +749 -0
- package/dist/cli.mjs +3930 -0
- package/package.json +59 -0
- package/templates/base/README.md +42 -0
- package/templates/base/_env +9 -0
- package/templates/base/_gitignore +8 -0
- package/templates/base/public/favicon.svg +1 -0
- package/templates/base/public/robots.txt +2 -0
- package/templates/base/rshono.config.ts +35 -0
- package/templates/base/src/actions.ts +19 -0
- package/templates/base/src/components/404.tsx +17 -0
- package/templates/base/src/components/500.tsx +21 -0
- package/templates/base/src/components/greet-form.tsx +27 -0
- package/templates/base/src/components/home.tsx +50 -0
- package/templates/base/src/components/layout.tsx +45 -0
- package/templates/base/src/env.d.ts +2 -0
- package/templates/base/src/lib/env.ts +26 -0
- package/templates/base/src/routes.ts +32 -0
- package/templates/base/src/server.ts +59 -0
- package/templates/base/src/styles.css +178 -0
- package/templates/base/tsconfig.json +20 -0
- package/templates/biome/biome.json +7 -0
- package/templates/biome-tailwind/biome.json +7 -0
- package/templates/oxfmt/_oxfmtrc.json +7 -0
- package/templates/oxlint/_oxlintrc.json +7 -0
- package/templates/prettier/_prettierignore +10 -0
- package/templates/prettier/_prettierrc.json +7 -0
- package/templates/tailwind/postcss.config.mjs +9 -0
- package/templates/tailwind/rshono.config.ts +45 -0
- package/templates/tailwind/src/components/home.tsx +50 -0
- package/templates/tailwind/src/components/layout.tsx +45 -0
- package/templates/tailwind/src/styles.css +40 -0
package/dist/api.mjs
ADDED
|
@@ -0,0 +1,749 @@
|
|
|
1
|
+
// Bundled by rspack — edit src/ and rebuild. Includes @clack/prompts (MIT).
|
|
2
|
+
import {fileURLToPath as __rspack_fileURLToPath} from "node:url";
|
|
3
|
+
import {dirname as __rspack_dirname} from "node:path";
|
|
4
|
+
var __rspack_import_meta_dirname__ = __rspack_dirname(__rspack_fileURLToPath(import.meta.url));
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { dirname, join, posix, sep } from "node:path";
|
|
7
|
+
import "node:child_process";
|
|
8
|
+
// The require scope
|
|
9
|
+
var __webpack_require__ = {};
|
|
10
|
+
|
|
11
|
+
// webpack/runtime/define_property_getters
|
|
12
|
+
(() => {
|
|
13
|
+
__webpack_require__.d = (exports, getters, values) => {
|
|
14
|
+
var define = (defs, kind) => {
|
|
15
|
+
for(var key in defs) {
|
|
16
|
+
if(__webpack_require__.o(defs, key) && !__webpack_require__.o(exports, key)) {
|
|
17
|
+
Object.defineProperty(exports, key, { enumerable: true, [kind]: defs[key] });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
define(getters, "get");
|
|
22
|
+
define(values, "value");
|
|
23
|
+
};
|
|
24
|
+
})();
|
|
25
|
+
// webpack/runtime/has_own_property
|
|
26
|
+
(() => {
|
|
27
|
+
__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
|
|
28
|
+
})();
|
|
29
|
+
var __webpack_exports__ = {};
|
|
30
|
+
|
|
31
|
+
// EXPORTS
|
|
32
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
33
|
+
bp: () => (/* reexport */ DEPLOY_TARGET_NAMES),
|
|
34
|
+
e7: () => (/* reexport */ FRAMEWORK_DEPS),
|
|
35
|
+
Ab: () => (/* reexport */ PACKAGE_MANAGERS),
|
|
36
|
+
pm: () => (/* reexport */ QUALITY_PRESETS),
|
|
37
|
+
s_: () => (/* reexport */ RSHONO_RANGE),
|
|
38
|
+
RM: () => (/* reexport */ TOOL_VERSIONS),
|
|
39
|
+
Jh: () => (/* reexport */ buildPackageJson),
|
|
40
|
+
P8: () => (/* reexport */ deployHint),
|
|
41
|
+
Lv: () => (/* reexport */ detectPackageManager),
|
|
42
|
+
WL: () => (/* reexport */ inspectTarget),
|
|
43
|
+
p1: () => (/* reexport */ isDeployTarget),
|
|
44
|
+
eW: () => (/* reexport */ isValidPackageName),
|
|
45
|
+
ml: () => (/* reexport */ packageManager),
|
|
46
|
+
et: () => (/* reexport */ plan_plan),
|
|
47
|
+
N7: () => (/* reexport */ selectFeatures),
|
|
48
|
+
PQ: () => (/* reexport */ toPackageName),
|
|
49
|
+
pD: () => (/* reexport */ writePlan)
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
;// CONCATENATED MODULE: ./src/features/combinations.ts
|
|
53
|
+
/**
|
|
54
|
+
* Biome's CSS parser rejects Tailwind's syntax — `@apply` and a `@layer` block are parse errors, not
|
|
55
|
+
* unknown-at-rule warnings — so a project with both needs Biome pointed away from stylesheets. The
|
|
56
|
+
* overlay is a second `biome.json` listed after the first, which is all "later overlay wins" means.
|
|
57
|
+
*
|
|
58
|
+
* Narrow on purpose: the alternative was excluding CSS for everybody, which would quietly give up
|
|
59
|
+
* formatting the plain-CSS template that Biome handles perfectly well.
|
|
60
|
+
*/ const BIOME_TAILWIND = {
|
|
61
|
+
id: 'biome-tailwind',
|
|
62
|
+
overlays: [
|
|
63
|
+
'biome-tailwind'
|
|
64
|
+
]
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Features that exist because of how two answers *combine*, rather than because of either one alone.
|
|
68
|
+
* Applied after the per-axis features, so an overlay here has the last word on a file.
|
|
69
|
+
*
|
|
70
|
+
* Keep this list short. A combination that needs more than a file swap is usually a sign that the two
|
|
71
|
+
* options should not both be offered.
|
|
72
|
+
*/ function combinationFeatures(answers) {
|
|
73
|
+
const usesBiome = answers.formatter === 'biome' || answers.linter === 'biome';
|
|
74
|
+
return usesBiome && answers.styling === 'tailwind' ? [
|
|
75
|
+
BIOME_TAILWIND
|
|
76
|
+
] : [];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
;// CONCATENATED MODULE: ./src/generated/framework.ts
|
|
80
|
+
// 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.0';
|
|
82
|
+
/**
|
|
83
|
+
* The dependency versions rshono is tested against, copied from its own manifest. Exact where it is
|
|
84
|
+
* exact — React's RSC internals are coupled across builds, and a generated app has no workspace
|
|
85
|
+
* overrides to fall back on.
|
|
86
|
+
*/ const FRAMEWORK_DEPS = {
|
|
87
|
+
hono: '^4.12.31',
|
|
88
|
+
react: '19.2.8',
|
|
89
|
+
'react-dom': '19.2.8',
|
|
90
|
+
typescript: '^7.0.2',
|
|
91
|
+
'@types/node': '^26.1.1',
|
|
92
|
+
'@types/react': '^19.2.17'
|
|
93
|
+
};
|
|
94
|
+
/** Every `deploy` target the installed framework knows, with the command that ships what it built. */ const DEPLOY_TARGETS = [
|
|
95
|
+
{
|
|
96
|
+
name: 'node',
|
|
97
|
+
hint: 'run `rshono start`'
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: 'cloudflare',
|
|
101
|
+
hint: 'deploy with `wrangler deploy`'
|
|
102
|
+
},
|
|
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
|
+
{
|
|
112
|
+
name: 'vercel',
|
|
113
|
+
hint: 'deploy with `vercel deploy --prebuilt`'
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: 'netlify',
|
|
117
|
+
hint: 'deploy with `netlify deploy --build=false --dir=.netlify/publish`'
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: 'aws-lambda',
|
|
121
|
+
hint: 'zip dist/ with the handler at dist/server/main.mjs'
|
|
122
|
+
}
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
;// CONCATENATED MODULE: ./src/versions.ts
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
/** The framework range a scaffolded app gets. The two packages are released together, so this is ours. */ const RSHONO_RANGE = `^${RSHONO_VERSION}`;
|
|
129
|
+
/**
|
|
130
|
+
* Versions for the optional tooling the features can add — the one place in this package where a
|
|
131
|
+
* dependency range is typed out by hand, because none of these are things the framework itself
|
|
132
|
+
* declares. Everything a *scaffolded app* needs to run rshono comes from {@link FRAMEWORK_DEPS},
|
|
133
|
+
* which is generated from rshono's own manifest and must not be edited here.
|
|
134
|
+
*
|
|
135
|
+
* Ranges are caret, not exact: these are the app's own dev tools, and a scaffold made six months from
|
|
136
|
+
* now should pick up their patch releases rather than pinning whatever was current the day this file
|
|
137
|
+
* was last touched.
|
|
138
|
+
*/ const TOOL_VERSIONS = {
|
|
139
|
+
tailwindcss: '^4.3.3',
|
|
140
|
+
'@tailwindcss/postcss': '^4.3.3',
|
|
141
|
+
// The pass Tailwind runs in, installed with it rather than with the framework.
|
|
142
|
+
postcss: '^8.5.23',
|
|
143
|
+
'postcss-loader': '^8.2.1',
|
|
144
|
+
prettier: '^3.9.6',
|
|
145
|
+
'@biomejs/biome': '^2.5.6',
|
|
146
|
+
oxlint: '^1.76.0',
|
|
147
|
+
oxfmt: '^0.61.0',
|
|
148
|
+
wrangler: '^4.115.0'
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
;// CONCATENATED MODULE: ./src/features/deploy.ts
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* What a deploy target adds beyond the `deploy` line in `rshono.config.ts` — which is the build's job
|
|
155
|
+
* to read, and the template's to carry.
|
|
156
|
+
*
|
|
157
|
+
* Deliberately thin. The framework already knows how to arrange its own output for every platform, and
|
|
158
|
+
* `rshono build` writes the one platform config that has to exist (`wrangler.jsonc`, with the
|
|
159
|
+
* `compatibility_date` of the day it ran) if the project has none. Generating a second copy here would
|
|
160
|
+
* be a copy that goes stale. So a target contributes a `deploy` script, the CLI it needs locally, and
|
|
161
|
+
* the build artefacts its platform leaves in the project.
|
|
162
|
+
*/ const DEPLOY_FEATURES = {
|
|
163
|
+
// Where a Node build goes from here is a Dockerfile or a process manager, neither of which this can
|
|
164
|
+
// guess — so the target contributes only the command that runs what was built.
|
|
165
|
+
node: {
|
|
166
|
+
id: 'deploy-node',
|
|
167
|
+
scripts: {
|
|
168
|
+
start: 'rshono start'
|
|
169
|
+
}
|
|
170
|
+
},
|
|
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
|
+
cloudflare: {
|
|
186
|
+
id: 'deploy-cloudflare',
|
|
187
|
+
devDependencies: {
|
|
188
|
+
wrangler: TOOL_VERSIONS.wrangler
|
|
189
|
+
},
|
|
190
|
+
scripts: {
|
|
191
|
+
deploy: 'rshono build && wrangler deploy'
|
|
192
|
+
},
|
|
193
|
+
gitignore: [
|
|
194
|
+
'.wrangler/'
|
|
195
|
+
],
|
|
196
|
+
notes: [
|
|
197
|
+
'The first build writes wrangler.jsonc — yours to edit after that.'
|
|
198
|
+
]
|
|
199
|
+
},
|
|
200
|
+
vercel: {
|
|
201
|
+
id: 'deploy-vercel',
|
|
202
|
+
scripts: {
|
|
203
|
+
deploy: 'rshono build && vercel deploy --prebuilt'
|
|
204
|
+
},
|
|
205
|
+
gitignore: [
|
|
206
|
+
'.vercel/'
|
|
207
|
+
],
|
|
208
|
+
notes: [
|
|
209
|
+
'--prebuilt uploads what rshono build assembled; the platform must not rebuild it.'
|
|
210
|
+
]
|
|
211
|
+
},
|
|
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
|
+
'aws-lambda': {
|
|
222
|
+
id: 'deploy-aws-lambda',
|
|
223
|
+
notes: [
|
|
224
|
+
'Use a Function URL in RESPONSE_STREAM mode — a buffered invoke mode drops the streaming.'
|
|
225
|
+
]
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
function deployFeature(target) {
|
|
229
|
+
return DEPLOY_FEATURES[target];
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
;// CONCATENATED MODULE: ./src/features/quality.ts
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* The formatter and linter features. Biome answers to both slots and appears once — `selectFeatures`
|
|
236
|
+
* deduplicates by `id`, so `formatter: 'biome', linter: 'biome'` contributes one set of files, one
|
|
237
|
+
* dependency and one pair of scripts.
|
|
238
|
+
*
|
|
239
|
+
* Each tool brings its own `check` script alongside `format`/`lint`, because the writing half and the
|
|
240
|
+
* CI half want different exit-code behaviour: `format` rewrites files, `check` fails instead.
|
|
241
|
+
*/ const PRETTIER = {
|
|
242
|
+
id: 'prettier',
|
|
243
|
+
overlays: [
|
|
244
|
+
'prettier'
|
|
245
|
+
],
|
|
246
|
+
devDependencies: {
|
|
247
|
+
prettier: TOOL_VERSIONS.prettier
|
|
248
|
+
},
|
|
249
|
+
scripts: {
|
|
250
|
+
format: 'prettier --write .',
|
|
251
|
+
'format:check': 'prettier --check .'
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
const OXFMT = {
|
|
255
|
+
id: 'oxfmt',
|
|
256
|
+
overlays: [
|
|
257
|
+
'oxfmt'
|
|
258
|
+
],
|
|
259
|
+
devDependencies: {
|
|
260
|
+
oxfmt: TOOL_VERSIONS.oxfmt
|
|
261
|
+
},
|
|
262
|
+
scripts: {
|
|
263
|
+
format: 'oxfmt .',
|
|
264
|
+
'format:check': 'oxfmt --check .'
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
const OXLINT = {
|
|
268
|
+
id: 'oxlint',
|
|
269
|
+
overlays: [
|
|
270
|
+
'oxlint'
|
|
271
|
+
],
|
|
272
|
+
devDependencies: {
|
|
273
|
+
oxlint: TOOL_VERSIONS.oxlint
|
|
274
|
+
},
|
|
275
|
+
scripts: {
|
|
276
|
+
lint: 'oxlint',
|
|
277
|
+
'lint:fix': 'oxlint --fix'
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
const BIOME = {
|
|
281
|
+
id: 'biome',
|
|
282
|
+
overlays: [
|
|
283
|
+
'biome'
|
|
284
|
+
],
|
|
285
|
+
devDependencies: {
|
|
286
|
+
'@biomejs/biome': TOOL_VERSIONS["@biomejs/biome"]
|
|
287
|
+
},
|
|
288
|
+
scripts: {
|
|
289
|
+
format: 'biome format --write .',
|
|
290
|
+
'format:check': 'biome format .',
|
|
291
|
+
lint: 'biome lint .',
|
|
292
|
+
'lint:fix': 'biome lint --write .',
|
|
293
|
+
check: 'biome check .'
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
const FORMATTERS = {
|
|
297
|
+
prettier: PRETTIER,
|
|
298
|
+
oxfmt: OXFMT,
|
|
299
|
+
biome: BIOME,
|
|
300
|
+
none: null
|
|
301
|
+
};
|
|
302
|
+
const LINTERS = {
|
|
303
|
+
oxlint: OXLINT,
|
|
304
|
+
biome: BIOME,
|
|
305
|
+
none: null
|
|
306
|
+
};
|
|
307
|
+
function formatterFeature(formatter) {
|
|
308
|
+
return FORMATTERS[formatter];
|
|
309
|
+
}
|
|
310
|
+
function linterFeature(linter) {
|
|
311
|
+
return LINTERS[linter];
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
;// CONCATENATED MODULE: ./src/features/styling.ts
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Tailwind is a PostCSS plugin and nothing more, which is the whole of this feature: four packages, and
|
|
318
|
+
* an overlay carrying the `postcss.config.mjs` naming the plugin, an `rshono.config.ts` whose `rspack`
|
|
319
|
+
* hook puts postcss-loader in front of the CSS parser, a Tailwind entry stylesheet, and the two views
|
|
320
|
+
* written in utilities instead of classes of their own.
|
|
321
|
+
*
|
|
322
|
+
* `postcss` and `postcss-loader` are the app's dependencies rather than the framework's — rshono
|
|
323
|
+
* compiles CSS natively and has no PostCSS in it, so an app that does not want a plugin chain does not
|
|
324
|
+
* install one.
|
|
325
|
+
*/ const TAILWIND = {
|
|
326
|
+
id: 'tailwind',
|
|
327
|
+
overlays: [
|
|
328
|
+
'tailwind'
|
|
329
|
+
],
|
|
330
|
+
devDependencies: {
|
|
331
|
+
tailwindcss: TOOL_VERSIONS.tailwindcss,
|
|
332
|
+
'@tailwindcss/postcss': TOOL_VERSIONS["@tailwindcss/postcss"],
|
|
333
|
+
postcss: TOOL_VERSIONS.postcss,
|
|
334
|
+
'postcss-loader': TOOL_VERSIONS["postcss-loader"]
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
function stylingFeature(styling) {
|
|
338
|
+
return styling === 'tailwind' ? TAILWIND : null;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
;// CONCATENATED MODULE: ./src/features/index.ts
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* The features a set of answers selects, in application order — so an overlay listed later wins a file
|
|
348
|
+
* both of them ship. Deduplicated by `id`, which is what lets one feature answer two questions (Biome
|
|
349
|
+
* is both the formatter and the linter) without contributing twice.
|
|
350
|
+
*/ function selectFeatures(answers) {
|
|
351
|
+
const selected = [
|
|
352
|
+
deployFeature(answers.deploy),
|
|
353
|
+
stylingFeature(answers.styling),
|
|
354
|
+
formatterFeature(answers.formatter),
|
|
355
|
+
linterFeature(answers.linter),
|
|
356
|
+
...combinationFeatures(answers)
|
|
357
|
+
];
|
|
358
|
+
const features = [];
|
|
359
|
+
const seen = new Set();
|
|
360
|
+
for (const feature of selected){
|
|
361
|
+
if (!feature || seen.has(feature.id)) continue;
|
|
362
|
+
seen.add(feature.id);
|
|
363
|
+
features.push(feature);
|
|
364
|
+
}
|
|
365
|
+
return features;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
;// CONCATENATED MODULE: ./src/options.ts
|
|
369
|
+
|
|
370
|
+
const PACKAGE_MANAGERS = [
|
|
371
|
+
'npm',
|
|
372
|
+
'pnpm',
|
|
373
|
+
'yarn',
|
|
374
|
+
'bun'
|
|
375
|
+
];
|
|
376
|
+
const DEPLOY_TARGET_NAMES = DEPLOY_TARGETS.map((target)=>target.name);
|
|
377
|
+
function deployHint(name) {
|
|
378
|
+
return DEPLOY_TARGETS.find((target)=>target.name === name)?.hint ?? '';
|
|
379
|
+
}
|
|
380
|
+
function isDeployTarget(value) {
|
|
381
|
+
return DEPLOY_TARGET_NAMES.includes(value);
|
|
382
|
+
}
|
|
383
|
+
const QUALITY_PRESETS = [
|
|
384
|
+
{
|
|
385
|
+
id: 'prettier-oxlint',
|
|
386
|
+
label: 'Prettier + oxlint',
|
|
387
|
+
hint: 'the conventional formatter, with a fast linter',
|
|
388
|
+
formatter: 'prettier',
|
|
389
|
+
linter: 'oxlint'
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
id: 'biome',
|
|
393
|
+
label: 'Biome',
|
|
394
|
+
hint: 'formatter and linter in one tool',
|
|
395
|
+
formatter: 'biome',
|
|
396
|
+
linter: 'biome'
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
id: 'oxc',
|
|
400
|
+
label: 'oxfmt + oxlint',
|
|
401
|
+
hint: 'the oxc toolchain — fastest, newest',
|
|
402
|
+
formatter: 'oxfmt',
|
|
403
|
+
linter: 'oxlint'
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
id: 'none',
|
|
407
|
+
label: 'None',
|
|
408
|
+
hint: 'add your own later',
|
|
409
|
+
formatter: 'none',
|
|
410
|
+
linter: 'none'
|
|
411
|
+
}
|
|
412
|
+
];
|
|
413
|
+
/**
|
|
414
|
+
* Turns whatever the user typed into a name npm will accept, or returns `null` when nothing usable is
|
|
415
|
+
* left. Lowercasing and replacing runs of invalid characters covers the ordinary cases (`My App`,
|
|
416
|
+
* `my_app`); a scoped name is kept intact, since `@scope/name` is legal.
|
|
417
|
+
*/ function toPackageName(input) {
|
|
418
|
+
const trimmed = input.trim().replace(/^\.\/+/, '').replace(/\/+$/, '');
|
|
419
|
+
if (!trimmed || trimmed === '.') return null;
|
|
420
|
+
// A scoped name is a name, not a path: `@scope/pkg` stays whole rather than becoming `pkg`. Anything
|
|
421
|
+
// else is a path, and the last segment is the one that names the project.
|
|
422
|
+
const scoped = /^@[^\\/]+[\\/][^\\/]+$/.test(trimmed);
|
|
423
|
+
const base = scoped ? trimmed : trimmed.split(/[\\/]/).filter(Boolean).pop() ?? '';
|
|
424
|
+
if (!base) return null;
|
|
425
|
+
const name = base.toLowerCase().replace(/[\\/]/g, '/').replace(/[^a-z\d\-._~/@]+/g, '-').replace(/^-+/, '').replace(/-+$/, '');
|
|
426
|
+
if (!name || name === '@' || !scoped && name.startsWith('.')) return null;
|
|
427
|
+
return name.slice(0, 214);
|
|
428
|
+
}
|
|
429
|
+
/** npm's own rule, narrowed to what we ever generate: no uppercase, no leading dot or underscore. */ function isValidPackageName(name) {
|
|
430
|
+
return /^(?:@[a-z\d\-*~][a-z\d\-*._~]*\/)?[a-z\d\-~][a-z\d\-._~]*$/.test(name) && name.length <= 214;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
;// CONCATENATED MODULE: ./src/pkg.ts
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* The scripts every app gets. `start` is not among them: it is the *Node* launcher, and the deploy
|
|
437
|
+
* features each contribute the command their own platform runs what was built with.
|
|
438
|
+
*/ const BASE_SCRIPTS = {
|
|
439
|
+
dev: 'rshono dev',
|
|
440
|
+
build: 'rshono build',
|
|
441
|
+
typecheck: 'tsc --noEmit'
|
|
442
|
+
};
|
|
443
|
+
/** Field order in the emitted file — the conventional reading order, and stable so snapshots are too. */ const FIELD_ORDER = [
|
|
444
|
+
'name',
|
|
445
|
+
'version',
|
|
446
|
+
'private',
|
|
447
|
+
'type',
|
|
448
|
+
'engines',
|
|
449
|
+
'packageManager',
|
|
450
|
+
'scripts',
|
|
451
|
+
'dependencies',
|
|
452
|
+
'devDependencies'
|
|
453
|
+
];
|
|
454
|
+
function sorted(record) {
|
|
455
|
+
return Object.fromEntries(Object.entries(record).sort(([a], [b])=>a < b ? -1 : 1));
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Assembles `package.json` from the answers and whatever the selected features contribute.
|
|
459
|
+
*
|
|
460
|
+
* Dependencies are sorted by name and scripts are left in contribution order (the base ones, then each
|
|
461
|
+
* feature's, in the order features were selected) — so two runs with the same answers produce byte-
|
|
462
|
+
* identical output, which is what makes the generated manifest snapshot-testable.
|
|
463
|
+
*/ function buildPackageJson(answers, features, pm) {
|
|
464
|
+
const scripts = {
|
|
465
|
+
...BASE_SCRIPTS
|
|
466
|
+
};
|
|
467
|
+
const dependencies = {
|
|
468
|
+
'@rshono/core': RSHONO_RANGE,
|
|
469
|
+
hono: FRAMEWORK_DEPS.hono,
|
|
470
|
+
react: FRAMEWORK_DEPS.react,
|
|
471
|
+
'react-dom': FRAMEWORK_DEPS["react-dom"]
|
|
472
|
+
};
|
|
473
|
+
const devDependencies = {
|
|
474
|
+
'@types/node': FRAMEWORK_DEPS["@types/node"],
|
|
475
|
+
'@types/react': FRAMEWORK_DEPS["@types/react"],
|
|
476
|
+
typescript: FRAMEWORK_DEPS.typescript
|
|
477
|
+
};
|
|
478
|
+
for (const feature of features){
|
|
479
|
+
Object.assign(scripts, feature.scripts);
|
|
480
|
+
Object.assign(dependencies, feature.dependencies);
|
|
481
|
+
Object.assign(devDependencies, feature.devDependencies);
|
|
482
|
+
}
|
|
483
|
+
const manifest = {
|
|
484
|
+
name: answers.packageName,
|
|
485
|
+
version: '0.1.0',
|
|
486
|
+
private: true,
|
|
487
|
+
type: 'module',
|
|
488
|
+
// The floor the framework declares. Stated here too so a CI image or a contributor on an older
|
|
489
|
+
// Node finds out from their package manager rather than from a stack trace.
|
|
490
|
+
engines: {
|
|
491
|
+
node: '>=22.1.0'
|
|
492
|
+
},
|
|
493
|
+
scripts,
|
|
494
|
+
dependencies: sorted(dependencies),
|
|
495
|
+
devDependencies: sorted(devDependencies)
|
|
496
|
+
};
|
|
497
|
+
// Only when the environment told us the exact version: `packageManager` pins the tool for Corepack,
|
|
498
|
+
// and a guess at the version is worse than leaving the field out.
|
|
499
|
+
if (pm.version) manifest.packageManager = `${pm.name}@${pm.version}`;
|
|
500
|
+
const ordered = Object.fromEntries(FIELD_ORDER.filter((field)=>field in manifest).map((field)=>[
|
|
501
|
+
field,
|
|
502
|
+
manifest[field]
|
|
503
|
+
]));
|
|
504
|
+
return `${JSON.stringify(ordered, null, 2)}\n`;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
;// CONCATENATED MODULE: external "node:fs"
|
|
508
|
+
|
|
509
|
+
;// CONCATENATED MODULE: external "node:path"
|
|
510
|
+
|
|
511
|
+
;// CONCATENATED MODULE: ./src/render.ts
|
|
512
|
+
|
|
513
|
+
const TOKEN_PATTERN = /__[A-Z][A-Z\d_]*__/g;
|
|
514
|
+
function tokensFor(answers, pm) {
|
|
515
|
+
return {
|
|
516
|
+
__PROJECT_NAME__: answers.packageName,
|
|
517
|
+
__DEPLOY_TARGET__: answers.deploy,
|
|
518
|
+
__DEPLOY_HINT__: deployHint(answers.deploy),
|
|
519
|
+
__PM__: pm.name,
|
|
520
|
+
__PM_RUN__: pm.run
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Substitutes tokens, and throws on one it doesn't know — a typo in a template would otherwise ship a
|
|
525
|
+
* literal `__PORJECT_NAME__` into somebody's new app, which no test of the generator's logic would
|
|
526
|
+
* catch.
|
|
527
|
+
*/ function render(contents, tokens, source) {
|
|
528
|
+
return contents.replace(TOKEN_PATTERN, (token)=>{
|
|
529
|
+
const value = tokens[token];
|
|
530
|
+
if (value === undefined) throw new Error(`[create-rshono] ${source} uses unknown template token ${token}`);
|
|
531
|
+
return value;
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
;// CONCATENATED MODULE: ./src/plan.ts
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
/** `dist/cli.mjs` and `templates/` are siblings in the published package, as they are in the repo. */ const TEMPLATES_DIR = join(__rspack_import_meta_dirname__, '..', 'templates');
|
|
542
|
+
function readTemplateDir(dir) {
|
|
543
|
+
const files = new Map();
|
|
544
|
+
for (const entry of readdirSync(dir, {
|
|
545
|
+
recursive: true,
|
|
546
|
+
withFileTypes: true
|
|
547
|
+
})){
|
|
548
|
+
if (!entry.isFile()) continue;
|
|
549
|
+
const absolute = join(entry.parentPath, entry.name);
|
|
550
|
+
const relative = absolute.slice(dir.length + 1).split(sep).join(posix.sep);
|
|
551
|
+
files.set(relative, readFileSync(absolute, 'utf8'));
|
|
552
|
+
}
|
|
553
|
+
return files;
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* `_gitignore` → `.gitignore`, and so on for every dotfile.
|
|
557
|
+
*
|
|
558
|
+
* npm strips a literal `.gitignore` out of a published tarball, so a template cannot simply contain
|
|
559
|
+
* one — the file would exist in the repo, pass every local test, and be missing from the package
|
|
560
|
+
* everybody actually installs. Naming them with an underscore and renaming here is the long-standing
|
|
561
|
+
* fix. It applies to the basename only, so `src/lib/_x.ts` is a dotfile but `templates/_x/y.ts` is not.
|
|
562
|
+
*/ function undotted(path) {
|
|
563
|
+
const segments = path.split(posix.sep);
|
|
564
|
+
const name = segments.pop();
|
|
565
|
+
return [
|
|
566
|
+
...segments,
|
|
567
|
+
name.startsWith('_') ? `.${name.slice(1)}` : name
|
|
568
|
+
].join(posix.sep);
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* A feature's `.gitignore` lines, appended under a heading naming it — so somebody reading the file six
|
|
572
|
+
* months later can tell why `.wrangler/` is in there.
|
|
573
|
+
*/ function appendGitignore(existing, features) {
|
|
574
|
+
const additions = features.filter((feature)=>feature.gitignore?.length);
|
|
575
|
+
if (additions.length === 0) return existing;
|
|
576
|
+
const blocks = additions.map((feature)=>`\n# ${feature.id}\n${feature.gitignore.join('\n')}\n`);
|
|
577
|
+
return existing + blocks.join('');
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Turns answers into the exact set of files to write, without touching the target directory — the
|
|
581
|
+
* decisions and the I/O are separated so the whole matrix of answers can be asserted on in a test, and
|
|
582
|
+
* so `--dry-run` is the same code path minus the last step.
|
|
583
|
+
*/ function plan_plan(answers, pm) {
|
|
584
|
+
const features = selectFeatures(answers);
|
|
585
|
+
const tokens = tokensFor(answers, pm);
|
|
586
|
+
const raw = readTemplateDir(join(TEMPLATES_DIR, 'base'));
|
|
587
|
+
for (const feature of features){
|
|
588
|
+
for (const overlay of feature.overlays ?? []){
|
|
589
|
+
for (const [path, contents] of readTemplateDir(join(TEMPLATES_DIR, overlay))){
|
|
590
|
+
raw.set(path, contents);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
const files = new Map();
|
|
595
|
+
for (const [path, contents] of raw){
|
|
596
|
+
files.set(undotted(path), render(contents, tokens, path));
|
|
597
|
+
}
|
|
598
|
+
const gitignore = files.get('.gitignore');
|
|
599
|
+
if (gitignore) files.set('.gitignore', appendGitignore(gitignore, features));
|
|
600
|
+
files.set('package.json', buildPackageJson(answers, features, pm));
|
|
601
|
+
return {
|
|
602
|
+
// Sorted, so both the write order and a test's snapshot are stable.
|
|
603
|
+
files: new Map([
|
|
604
|
+
...files
|
|
605
|
+
].sort(([a], [b])=>a < b ? -1 : 1)),
|
|
606
|
+
features,
|
|
607
|
+
notes: features.flatMap((feature)=>feature.notes ?? [])
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
;// CONCATENATED MODULE: external "node:child_process"
|
|
612
|
+
|
|
613
|
+
;// CONCATENATED MODULE: ./src/pm.ts
|
|
614
|
+
|
|
615
|
+
const INSTALL = {
|
|
616
|
+
npm: [
|
|
617
|
+
'install'
|
|
618
|
+
],
|
|
619
|
+
pnpm: [
|
|
620
|
+
'install'
|
|
621
|
+
],
|
|
622
|
+
yarn: [],
|
|
623
|
+
bun: [
|
|
624
|
+
'install'
|
|
625
|
+
]
|
|
626
|
+
};
|
|
627
|
+
const RUN = {
|
|
628
|
+
npm: 'npm run',
|
|
629
|
+
pnpm: 'pnpm',
|
|
630
|
+
yarn: 'yarn',
|
|
631
|
+
bun: 'bun'
|
|
632
|
+
};
|
|
633
|
+
function isKnown(name) {
|
|
634
|
+
return name === 'npm' || name === 'pnpm' || name === 'yarn' || name === 'bun';
|
|
635
|
+
}
|
|
636
|
+
function packageManager(name, version) {
|
|
637
|
+
return {
|
|
638
|
+
name,
|
|
639
|
+
version,
|
|
640
|
+
install: INSTALL[name],
|
|
641
|
+
run: RUN[name]
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Which package manager invoked us. Every one of them sets `npm_config_user_agent` for the process it
|
|
646
|
+
* spawns — `pnpm/11.9.0 npm/? node/v22.14.0 darwin arm64` — so `pnpm create @rshono` scaffolds a pnpm
|
|
647
|
+
* project without asking, and the exact version comes along for the `packageManager` field.
|
|
648
|
+
*
|
|
649
|
+
* Falls back to npm, which is also what a bare `node bin/create-rshono.mjs` gets.
|
|
650
|
+
*/ function detectPackageManager(userAgent = process.env.npm_config_user_agent) {
|
|
651
|
+
const [spec] = (userAgent ?? '').split(' ');
|
|
652
|
+
const [name, version] = (spec ?? '').split('/');
|
|
653
|
+
if (name && isKnown(name)) return packageManager(name, version && /^\d+\.\d+\.\d+/.test(version) ? version : undefined);
|
|
654
|
+
return packageManager('npm');
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Runs the install, streaming its output. `shell: true` on Windows because npm, pnpm and yarn are all
|
|
658
|
+
* `.cmd` shims there, which `spawn` cannot execute directly — and with a shell involved the arguments
|
|
659
|
+
* are all fixed strings from the tables above, never anything the user typed.
|
|
660
|
+
*/ function runInstall(pm, cwd) {
|
|
661
|
+
return run(pm, pm.install, cwd);
|
|
662
|
+
}
|
|
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
|
+
function run(pm, args, cwd) {
|
|
670
|
+
const result = spawnSync(pm.name, args, {
|
|
671
|
+
cwd,
|
|
672
|
+
stdio: 'inherit',
|
|
673
|
+
shell: process.platform === 'win32'
|
|
674
|
+
});
|
|
675
|
+
return result.status === 0;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
;// CONCATENATED MODULE: ./src/write.ts
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Files that do not make a directory "occupied". A user who ran `git init` or opened the folder in an
|
|
683
|
+
* editor before scaffolding has not put anything in it that we would overwrite.
|
|
684
|
+
*/ const IGNORED_ENTRIES = new Set([
|
|
685
|
+
'.git',
|
|
686
|
+
'.DS_Store',
|
|
687
|
+
'.idea',
|
|
688
|
+
'.vscode',
|
|
689
|
+
'Thumbs.db'
|
|
690
|
+
]);
|
|
691
|
+
function inspectTarget(dir) {
|
|
692
|
+
if (!existsSync(dir)) return {
|
|
693
|
+
exists: false,
|
|
694
|
+
conflicts: []
|
|
695
|
+
};
|
|
696
|
+
return {
|
|
697
|
+
exists: true,
|
|
698
|
+
conflicts: readdirSync(dir).filter((entry)=>!IGNORED_ENTRIES.has(entry))
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Writes the plan. Directories are created as needed, and files are written with the plan's own
|
|
703
|
+
* ordering so a failure part-way through leaves something a person can make sense of.
|
|
704
|
+
*/ function writePlan(plan, targetDir) {
|
|
705
|
+
mkdirSync(targetDir, {
|
|
706
|
+
recursive: true
|
|
707
|
+
});
|
|
708
|
+
for (const [path, contents] of plan.files){
|
|
709
|
+
const absolute = join(targetDir, path);
|
|
710
|
+
mkdirSync(dirname(absolute), {
|
|
711
|
+
recursive: true
|
|
712
|
+
});
|
|
713
|
+
writeFileSync(absolute, contents);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
;// CONCATENATED MODULE: ./src/api.ts
|
|
718
|
+
/**
|
|
719
|
+
* The generator without the CLI around it: answers in, the exact set of files out. This is what the
|
|
720
|
+
* 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.
|
|
722
|
+
*
|
|
723
|
+
* @packageDocumentation
|
|
724
|
+
*/
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
|
|
732
|
+
var __webpack_exports__DEPLOY_TARGET_NAMES = __webpack_exports__.bp;
|
|
733
|
+
var __webpack_exports__FRAMEWORK_DEPS = __webpack_exports__.e7;
|
|
734
|
+
var __webpack_exports__PACKAGE_MANAGERS = __webpack_exports__.Ab;
|
|
735
|
+
var __webpack_exports__QUALITY_PRESETS = __webpack_exports__.pm;
|
|
736
|
+
var __webpack_exports__RSHONO_RANGE = __webpack_exports__.s_;
|
|
737
|
+
var __webpack_exports__TOOL_VERSIONS = __webpack_exports__.RM;
|
|
738
|
+
var __webpack_exports__buildPackageJson = __webpack_exports__.Jh;
|
|
739
|
+
var __webpack_exports__deployHint = __webpack_exports__.P8;
|
|
740
|
+
var __webpack_exports__detectPackageManager = __webpack_exports__.Lv;
|
|
741
|
+
var __webpack_exports__inspectTarget = __webpack_exports__.WL;
|
|
742
|
+
var __webpack_exports__isDeployTarget = __webpack_exports__.p1;
|
|
743
|
+
var __webpack_exports__isValidPackageName = __webpack_exports__.eW;
|
|
744
|
+
var __webpack_exports__packageManager = __webpack_exports__.ml;
|
|
745
|
+
var __webpack_exports__plan = __webpack_exports__.et;
|
|
746
|
+
var __webpack_exports__selectFeatures = __webpack_exports__.N7;
|
|
747
|
+
var __webpack_exports__toPackageName = __webpack_exports__.PQ;
|
|
748
|
+
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, __webpack_exports__buildPackageJson as buildPackageJson, __webpack_exports__deployHint as deployHint, __webpack_exports__detectPackageManager as detectPackageManager, __webpack_exports__inspectTarget as inspectTarget, __webpack_exports__isDeployTarget as isDeployTarget, __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 };
|