@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.
Files changed (34) hide show
  1. package/README.md +105 -0
  2. package/bin/create-rshono.mjs +29 -0
  3. package/dist/api.mjs +749 -0
  4. package/dist/cli.mjs +3930 -0
  5. package/package.json +59 -0
  6. package/templates/base/README.md +42 -0
  7. package/templates/base/_env +9 -0
  8. package/templates/base/_gitignore +8 -0
  9. package/templates/base/public/favicon.svg +1 -0
  10. package/templates/base/public/robots.txt +2 -0
  11. package/templates/base/rshono.config.ts +35 -0
  12. package/templates/base/src/actions.ts +19 -0
  13. package/templates/base/src/components/404.tsx +17 -0
  14. package/templates/base/src/components/500.tsx +21 -0
  15. package/templates/base/src/components/greet-form.tsx +27 -0
  16. package/templates/base/src/components/home.tsx +50 -0
  17. package/templates/base/src/components/layout.tsx +45 -0
  18. package/templates/base/src/env.d.ts +2 -0
  19. package/templates/base/src/lib/env.ts +26 -0
  20. package/templates/base/src/routes.ts +32 -0
  21. package/templates/base/src/server.ts +59 -0
  22. package/templates/base/src/styles.css +178 -0
  23. package/templates/base/tsconfig.json +20 -0
  24. package/templates/biome/biome.json +7 -0
  25. package/templates/biome-tailwind/biome.json +7 -0
  26. package/templates/oxfmt/_oxfmtrc.json +7 -0
  27. package/templates/oxlint/_oxlintrc.json +7 -0
  28. package/templates/prettier/_prettierignore +10 -0
  29. package/templates/prettier/_prettierrc.json +7 -0
  30. package/templates/tailwind/postcss.config.mjs +9 -0
  31. package/templates/tailwind/rshono.config.ts +45 -0
  32. package/templates/tailwind/src/components/home.tsx +50 -0
  33. package/templates/tailwind/src/components/layout.tsx +45 -0
  34. 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 };