@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/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 { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
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
- Jh: () => (/* reexport */ buildPackageJson),
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
- ml: () => (/* reexport */ packageManager),
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.0';
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` — which is the build's job
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. 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.
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
- * 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.
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, 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.
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 and has no PostCSS in it, so an app that does not want a plugin chain does not
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: it is the *Node* launcher, and the deploy
437
- * features each contribute the command their own platform runs what was built with.
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
- // 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.
555
+ // Generated from the framework's own manifest, so the app's floor cannot drift below rshono's.
490
556
  engines: {
491
- node: '>=22.1.0'
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
- const TOKEN_PATTERN = /__[A-Z][A-Z\d_]*__/g;
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
- __PROJECT_NAME__: answers.packageName,
517
- __DEPLOY_TARGET__: answers.deploy,
518
- __DEPLOY_HINT__: deployHint(answers.deploy),
519
- __PM__: pm.name,
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 `__PORJECT_NAME__` into somebody's new app, which no test of the generator's logic would
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 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.
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 === 'npm' || name === 'pnpm' || name === 'yarn' || name === 'bun';
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` for the process it
646
- * spawns — `pnpm/11.9.0 npm/? node/v22.14.0 darwin arm64` — so `pnpm create @rshono` scaffolds a pnpm
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
- 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
- };
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 __webpack_exports__buildPackageJson = __webpack_exports__.Jh;
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__.ml;
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, __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 };
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 };