@rshono/create 1.0.0-rc.1 → 1.0.0-rc.2

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 CHANGED
@@ -26,10 +26,10 @@ Six, each with a default, and each answerable by a flag instead:
26
26
  | Install dependencies? | yes | `--no-install` |
27
27
  | Initialize git? | yes, unless nested | `--no-git` |
28
28
 
29
- Plus `--formatter` and `--linter` to set either half of the quality preset on its own, `--force` to
30
- scaffold into a directory that is not empty, `--dry-run` to see the file list and write nothing, and `-y`
31
- to take the defaults for everything not given. **A non-interactive terminal implies `-y`**, so this is one
32
- command in CI or from an agent:
29
+ Plus `--formatter` and `--linter` to set either half of the quality preset on its own, `--pm` to override
30
+ the package manager that was detected, `--force` to scaffold into a directory that is not empty,
31
+ `--dry-run` to see the file list and write nothing, and `-y` to take the defaults for everything not
32
+ given. **A non-interactive terminal implies `-y`**, so this is one command in CI or from an agent:
33
33
 
34
34
  ```bash
35
35
  npm create @rshono@latest my-app -y --deploy cloudflare --tailwind --quality biome
@@ -104,9 +104,14 @@ delete to go back to plain CSS.
104
104
 
105
105
  ```bash
106
106
  pnpm --filter @rshono/create build # codegen, then one bundled dist/cli.mjs with no runtime deps
107
- pnpm --filter @rshono/create test # the plan matrix — fast, no I/O
107
+ pnpm --filter @rshono/create test # builds, then the plan matrix and the CLI seconds, no installs
108
108
  CREATE_RSHONO_E2E=1 pnpm --filter @rshono/create test # also: pack, install and build real apps
109
109
  ```
110
110
 
111
+ `plan.test.mjs` is the fast one — the whole matrix of answers in memory, no directory touched.
112
+ `cli.test.mjs` spawns the real bin against temp directories, which is where argument parsing and the
113
+ refusal to overwrite somebody's files are checked. `e2e.test.mjs` is the opt-in one, and the only one that
114
+ installs anything.
115
+
111
116
  `@clack/prompts` (MIT) is bundled rather than depended on, so `npm create` downloads one tarball before it
112
117
  can ask its first question.
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
@@ -32,16 +32,16 @@ var __webpack_exports__ = {};
32
32
  __webpack_require__.d(__webpack_exports__, {
33
33
  bp: () => (/* reexport */ DEPLOY_TARGET_NAMES),
34
34
  ml: () => (/* reexport */ (/* inlined export .ESLINT_TYPESCRIPT */"~6.0.3")),
35
+ wC: () => (/* reexport */ FORMATTER_NAMES),
35
36
  e7: () => (/* reexport */ FRAMEWORK_DEPS),
37
+ At: () => (/* reexport */ LINTER_NAMES),
36
38
  Ab: () => (/* reexport */ PACKAGE_MANAGERS),
37
39
  pm: () => (/* reexport */ QUALITY_PRESETS),
38
40
  s_: () => (/* reexport */ RSHONO_RANGE),
39
41
  RM: () => (/* reexport */ TOOL_VERSIONS),
40
- Jh: () => (/* reexport */ buildPackageJson),
42
+ dh: () => (/* reexport */ conflictingEntries),
41
43
  P8: () => (/* reexport */ deployHint),
42
44
  Lv: () => (/* reexport */ detectPackageManager),
43
- WL: () => (/* reexport */ inspectTarget),
44
- p1: () => (/* reexport */ isDeployTarget),
45
45
  eW: () => (/* reexport */ isValidPackageName),
46
46
  VZ: () => (/* reexport */ packageManager),
47
47
  et: () => (/* reexport */ plan_plan),
@@ -79,7 +79,11 @@ __webpack_require__.d(__webpack_exports__, {
79
79
 
80
80
  ;// CONCATENATED MODULE: ./src/generated/framework.ts
81
81
  // GENERATED by scripts/codegen.mjs from packages/core — do not edit. Run `pnpm --filter @rshono/create codegen`.
82
- /** The framework release a scaffolded app is pinned to: this package and rshono ship together. */ const RSHONO_VERSION = '1.0.0-rc.1';
82
+ /** The framework release a scaffolded app is pinned to: this package and rshono ship together. */ const RSHONO_VERSION = '1.0.0-rc.2';
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.1.0';
83
87
  /**
84
88
  * The dependency versions rshono is tested against, copied from its own manifest. Exact where it is
85
89
  * exact — React's RSC internals are coupled across builds, and a generated app has no workspace
@@ -125,7 +129,7 @@ __webpack_require__.d(__webpack_exports__, {
125
129
 
126
130
  ;// CONCATENATED MODULE: ./src/versions.ts
127
131
 
128
-
132
+ /** Passed straight through, so everything generated from the framework reaches the rest of the package here. */
129
133
  /** The framework range a scaffolded app gets. The two packages are released together, so this is ours. */ const RSHONO_RANGE = `^${RSHONO_VERSION}`;
130
134
  /**
131
135
  * Versions for the optional tooling the features can add — the one place in this package where a
@@ -176,8 +180,11 @@ __webpack_require__.d(__webpack_exports__, {
176
180
  * Deliberately thin. The framework already knows how to arrange its own output for every platform, and
177
181
  * `rshono build` writes the one platform config that has to exist (`wrangler.jsonc`, with the
178
182
  * `compatibility_date` of the day it ran) if the project has none. Generating a second copy here would
179
- * be a copy that goes stale. So a target contributes a `deploy` script, the CLI it needs locally, and
180
- * the build artefacts its platform leaves in the project.
183
+ * be a copy that goes stale.
184
+ *
185
+ * So a target contributes only what is true of it: the command that runs or ships the build, the CLI
186
+ * that command needs installed locally, the directories its platform leaves behind for `.gitignore`, and
187
+ * a note for the step no command covers. Several contribute just one of those.
181
188
  */ const DEPLOY_FEATURES = {
182
189
  // Where a Node build goes from here is a Dockerfile or a process manager, neither of which this can
183
190
  // guess — so the target contributes only the command that runs what was built.
@@ -260,8 +267,10 @@ function deployFeature(target) {
260
267
  * deduplicates by `id`, so `formatter: 'biome', linter: 'biome'` contributes one set of files, one
261
268
  * dependency and one pair of scripts.
262
269
  *
263
- * Each tool brings its own `check` script alongside `format`/`lint`, because the writing half and the
264
- * CI half want different exit-code behaviour: `format` rewrites files, `check` fails instead.
270
+ * A formatter brings `format:check` beside `format`, because the writing half and the CI half want
271
+ * different exit-code behaviour: `format` rewrites files, `format:check` fails instead. A linter brings
272
+ * `lint:fix` beside `lint`, for the same reason in the other direction — `lint` is already the failing
273
+ * one. Biome adds a `check` of its own, which is the pair of them in a single pass.
265
274
  */ const PRETTIER = {
266
275
  id: 'prettier',
267
276
  overlays: [
@@ -416,6 +425,22 @@ function stylingFeature(styling) {
416
425
 
417
426
  ;// CONCATENATED MODULE: ./src/options.ts
418
427
 
428
+ /*
429
+ * The names each option accepts, spelled once. The types below are derived from them, the CLI validates
430
+ * its flags against them and prints them in `--help`, and `pm.ts` recognises a package manager by them
431
+ * — so a name added here reaches all three without a second list to remember.
432
+ */ const FORMATTER_NAMES = [
433
+ 'prettier',
434
+ 'biome',
435
+ 'oxfmt',
436
+ 'none'
437
+ ];
438
+ const LINTER_NAMES = [
439
+ 'oxlint',
440
+ 'eslint',
441
+ 'biome',
442
+ 'none'
443
+ ];
419
444
  const PACKAGE_MANAGERS = [
420
445
  'npm',
421
446
  'pnpm',
@@ -426,9 +451,6 @@ const DEPLOY_TARGET_NAMES = DEPLOY_TARGETS.map((target)=>target.name);
426
451
  function deployHint(name) {
427
452
  return DEPLOY_TARGETS.find((target)=>target.name === name)?.hint ?? '';
428
453
  }
429
- function isDeployTarget(value) {
430
- return DEPLOY_TARGET_NAMES.includes(value);
431
- }
432
454
  const QUALITY_PRESETS = [
433
455
  {
434
456
  id: 'prettier-oxlint',
@@ -486,11 +508,16 @@ const QUALITY_PRESETS = [
486
508
  return /^(?:@[a-z\d\-*~][a-z\d\-*._~]*\/)?[a-z\d\-~][a-z\d\-._~]*$/.test(name) && name.length <= 214;
487
509
  }
488
510
 
511
+ ;// CONCATENATED MODULE: external "node:fs"
512
+
513
+ ;// CONCATENATED MODULE: external "node:path"
514
+
489
515
  ;// CONCATENATED MODULE: ./src/pkg.ts
490
516
 
491
517
  /**
492
- * The scripts every app gets. `start` is not among them: it is the *Node* launcher, and the deploy
493
- * features each contribute the command their own platform runs what was built with.
518
+ * The scripts every app gets. `start` is not among them, because it means something different per
519
+ * platform: the three targets that run the build themselves node, bun, deno each contribute their
520
+ * own, and a platform target contributes a `deploy` instead, where its platform has one command to give.
494
521
  */ const BASE_SCRIPTS = {
495
522
  dev: 'rshono dev',
496
523
  build: 'rshono build',
@@ -574,10 +601,9 @@ function sorted(record) {
574
601
  version: '0.1.0',
575
602
  private: true,
576
603
  type: 'module',
577
- // The floor the framework declares. Stated here too so a CI image or a contributor on an older
578
- // Node finds out from their package manager rather than from a stack trace.
604
+ // Generated from the framework's own manifest, so the app's floor cannot drift below rshono's.
579
605
  engines: {
580
- node: '>=22.1.0'
606
+ node: NODE_ENGINE
581
607
  },
582
608
  scripts,
583
609
  dependencies: sorted(dependencies),
@@ -593,25 +619,25 @@ function sorted(record) {
593
619
  return `${JSON.stringify(ordered, null, 2)}\n`;
594
620
  }
595
621
 
596
- ;// CONCATENATED MODULE: external "node:fs"
597
-
598
- ;// CONCATENATED MODULE: external "node:path"
599
-
600
622
  ;// CONCATENATED MODULE: ./src/render.ts
601
623
 
602
- const TOKEN_PATTERN = /__[A-Z][A-Z\d_]*__/g;
624
+ /**
625
+ * `{{NAME}}`, and not the `__NAME__` this used to be. A template is a real file that real tools run
626
+ * over, and in markdown `__NAME__` *is* strong emphasis — Prettier rewrites it to `**NAME**`, which
627
+ * turns a token into literal text that no substitution will ever match again. `{{…}}` means nothing to
628
+ * any of the formats these templates are written in.
629
+ */ const TOKEN_PATTERN = /\{\{[A-Z][A-Z\d_]*\}\}/g;
603
630
  function tokensFor(answers, pm) {
604
631
  return {
605
- __PROJECT_NAME__: answers.packageName,
606
- __DEPLOY_TARGET__: answers.deploy,
607
- __DEPLOY_HINT__: deployHint(answers.deploy),
608
- __PM__: pm.name,
609
- __PM_RUN__: pm.run
632
+ '{{PROJECT_NAME}}': answers.packageName,
633
+ '{{DEPLOY_TARGET}}': answers.deploy,
634
+ '{{DEPLOY_HINT}}': deployHint(answers.deploy),
635
+ '{{PM_RUN}}': pm.run
610
636
  };
611
637
  }
612
638
  /**
613
639
  * Substitutes tokens, and throws on one it doesn't know — a typo in a template would otherwise ship a
614
- * literal `__PORJECT_NAME__` into somebody's new app, which no test of the generator's logic would
640
+ * literal `{{PORJECT_NAME}}` into somebody's new app, which no test of the generator's logic would
615
641
  * catch.
616
642
  */ function render(contents, tokens, source) {
617
643
  return contents.replace(TOKEN_PATTERN, (token)=>{
@@ -702,6 +728,7 @@ function readTemplateDir(dir) {
702
728
 
703
729
  ;// CONCATENATED MODULE: ./src/pm.ts
704
730
 
731
+
705
732
  const INSTALL = {
706
733
  npm: [
707
734
  'install'
@@ -721,7 +748,7 @@ const RUN = {
721
748
  bun: 'bun'
722
749
  };
723
750
  function isKnown(name) {
724
- return name === 'npm' || name === 'pnpm' || name === 'yarn' || name === 'bun';
751
+ return PACKAGE_MANAGERS.includes(name);
725
752
  }
726
753
  function packageManager(name, version) {
727
754
  return {
@@ -778,15 +805,20 @@ function run(pm, args, cwd) {
778
805
  '.vscode',
779
806
  'Thumbs.db'
780
807
  ]);
781
- function inspectTarget(dir) {
782
- if (!existsSync(dir)) return {
783
- exists: false,
784
- conflicts: []
785
- };
786
- return {
787
- exists: true,
788
- conflicts: readdirSync(dir).filter((entry)=>!IGNORED_ENTRIES.has(entry))
789
- };
808
+ /**
809
+ * What is already at the target path, which is what decides whether scaffolding into it is safe: the
810
+ * entries already there, ignoring the ones a fresh clone or an editor leaves behind.
811
+ *
812
+ * A path that does not exist yet is no conflict. A path that exists and is *not* a directory is not
813
+ * something `--force` should be able to write into, so it throws rather than reporting an empty list —
814
+ * otherwise `create-rshono README.md` gets as far as `mkdir` before failing on a raw ENOTDIR.
815
+ */ function conflictingEntries(dir) {
816
+ const stats = statSync(dir, {
817
+ throwIfNoEntry: false
818
+ });
819
+ if (!stats) return [];
820
+ if (!stats.isDirectory()) throw new Error(`${dir} already exists and is not a directory.`);
821
+ return readdirSync(dir).filter((entry)=>!IGNORED_ENTRIES.has(entry));
790
822
  }
791
823
  /**
792
824
  * Writes the plan. Directories are created as needed, and files are written with the plan's own
@@ -808,7 +840,16 @@ function inspectTarget(dir) {
808
840
  /**
809
841
  * The generator without the CLI around it: answers in, the exact set of files out. This is what the
810
842
  * test suite asserts on — the whole matrix of options, in memory, with no directory to clean up — and
811
- * what another tool would call to scaffold an app itself.
843
+ * what another tool would call to scaffold an app itself:
844
+ *
845
+ * ```ts
846
+ * import { packageManager, plan, writePlan } from '@rshono/create';
847
+ *
848
+ * const answers = { packageName: 'my-app', deploy: 'node', styling: 'css', formatter: 'prettier', linter: 'oxlint' };
849
+ * writePlan(plan(answers, packageManager('npm')), './my-app');
850
+ * ```
851
+ *
852
+ * `package.json` is assembled inside `plan`, so nothing here needs to be composed by hand.
812
853
  *
813
854
  * @packageDocumentation
814
855
  */
@@ -818,23 +859,22 @@ function inspectTarget(dir) {
818
859
 
819
860
 
820
861
 
821
-
822
862
  var __webpack_exports__DEPLOY_TARGET_NAMES = __webpack_exports__.bp;
823
863
  var __webpack_exports__ESLINT_TYPESCRIPT = __webpack_exports__.ml;
864
+ var __webpack_exports__FORMATTER_NAMES = __webpack_exports__.wC;
824
865
  var __webpack_exports__FRAMEWORK_DEPS = __webpack_exports__.e7;
866
+ var __webpack_exports__LINTER_NAMES = __webpack_exports__.At;
825
867
  var __webpack_exports__PACKAGE_MANAGERS = __webpack_exports__.Ab;
826
868
  var __webpack_exports__QUALITY_PRESETS = __webpack_exports__.pm;
827
869
  var __webpack_exports__RSHONO_RANGE = __webpack_exports__.s_;
828
870
  var __webpack_exports__TOOL_VERSIONS = __webpack_exports__.RM;
829
- var __webpack_exports__buildPackageJson = __webpack_exports__.Jh;
871
+ var __webpack_exports__conflictingEntries = __webpack_exports__.dh;
830
872
  var __webpack_exports__deployHint = __webpack_exports__.P8;
831
873
  var __webpack_exports__detectPackageManager = __webpack_exports__.Lv;
832
- var __webpack_exports__inspectTarget = __webpack_exports__.WL;
833
- var __webpack_exports__isDeployTarget = __webpack_exports__.p1;
834
874
  var __webpack_exports__isValidPackageName = __webpack_exports__.eW;
835
875
  var __webpack_exports__packageManager = __webpack_exports__.VZ;
836
876
  var __webpack_exports__plan = __webpack_exports__.et;
837
877
  var __webpack_exports__selectFeatures = __webpack_exports__.N7;
838
878
  var __webpack_exports__toPackageName = __webpack_exports__.PQ;
839
879
  var __webpack_exports__writePlan = __webpack_exports__.pD;
840
- export { __webpack_exports__DEPLOY_TARGET_NAMES as DEPLOY_TARGET_NAMES, __webpack_exports__ESLINT_TYPESCRIPT as ESLINT_TYPESCRIPT, __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 };
880
+ 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 };
package/dist/cli.mjs CHANGED
@@ -6,7 +6,7 @@ import { parseArgs, styleText as external_node_util_styleText } from "node:util"
6
6
  import node_process, { stdin as external_node_process_stdin, stdout as external_node_process_stdout } from "node:process";
7
7
  import node_readline from "node:readline";
8
8
  import "node:tty";
9
- import { existsSync as external_node_fs_existsSync, mkdirSync, readFileSync, readdirSync as external_node_fs_readdirSync, writeFileSync } from "node:fs";
9
+ import { mkdirSync, readFileSync, readdirSync as external_node_fs_readdirSync, statSync, writeFileSync } from "node:fs";
10
10
  import { basename, dirname as external_node_path_dirname, join as external_node_path_join, posix, relative as external_node_path_relative, resolve, sep } from "node:path";
11
11
  import { spawnSync } from "node:child_process";
12
12
  var __webpack_modules__ = ({
@@ -2943,7 +2943,11 @@ function hasGit(cwd) {
2943
2943
 
2944
2944
  ;// CONCATENATED MODULE: ./src/generated/framework.ts
2945
2945
  // GENERATED by scripts/codegen.mjs from packages/core — do not edit. Run `pnpm --filter @rshono/create codegen`.
2946
- /** The framework release a scaffolded app is pinned to: this package and rshono ship together. */ const RSHONO_VERSION = '1.0.0-rc.1';
2946
+ /** The framework release a scaffolded app is pinned to: this package and rshono ship together. */ const RSHONO_VERSION = '1.0.0-rc.2';
2947
+ /**
2948
+ * The Node range rshono itself declares, restated in every scaffolded app's `engines` — so a CI image
2949
+ * or a contributor on an older Node hears it from their package manager rather than from a stack trace.
2950
+ */ const NODE_ENGINE = '>=22.1.0';
2947
2951
  /**
2948
2952
  * The dependency versions rshono is tested against, copied from its own manifest. Exact where it is
2949
2953
  * exact — React's RSC internals are coupled across builds, and a generated app has no workspace
@@ -2989,19 +2993,32 @@ function hasGit(cwd) {
2989
2993
 
2990
2994
  ;// CONCATENATED MODULE: ./src/options.ts
2991
2995
 
2992
- const PACKAGE_MANAGERS = (/* unused pure expression or super */ null && ([
2996
+ /*
2997
+ * The names each option accepts, spelled once. The types below are derived from them, the CLI validates
2998
+ * its flags against them and prints them in `--help`, and `pm.ts` recognises a package manager by them
2999
+ * — so a name added here reaches all three without a second list to remember.
3000
+ */ const FORMATTER_NAMES = [
3001
+ 'prettier',
3002
+ 'biome',
3003
+ 'oxfmt',
3004
+ 'none'
3005
+ ];
3006
+ const LINTER_NAMES = [
3007
+ 'oxlint',
3008
+ 'eslint',
3009
+ 'biome',
3010
+ 'none'
3011
+ ];
3012
+ const PACKAGE_MANAGERS = [
2993
3013
  'npm',
2994
3014
  'pnpm',
2995
3015
  'yarn',
2996
3016
  'bun'
2997
- ]));
3017
+ ];
2998
3018
  const DEPLOY_TARGET_NAMES = DEPLOY_TARGETS.map((target)=>target.name);
2999
3019
  function deployHint(name) {
3000
3020
  return DEPLOY_TARGETS.find((target)=>target.name === name)?.hint ?? '';
3001
3021
  }
3002
- function isDeployTarget(value) {
3003
- return DEPLOY_TARGET_NAMES.includes(value);
3004
- }
3005
3022
  const QUALITY_PRESETS = [
3006
3023
  {
3007
3024
  id: 'prettier-oxlint',
@@ -3088,7 +3105,7 @@ const QUALITY_PRESETS = [
3088
3105
 
3089
3106
  ;// CONCATENATED MODULE: ./src/versions.ts
3090
3107
 
3091
-
3108
+ /** Passed straight through, so everything generated from the framework reaches the rest of the package here. */
3092
3109
  /** The framework range a scaffolded app gets. The two packages are released together, so this is ours. */ const RSHONO_RANGE = `^${RSHONO_VERSION}`;
3093
3110
  /**
3094
3111
  * Versions for the optional tooling the features can add — the one place in this package where a
@@ -3139,8 +3156,11 @@ const QUALITY_PRESETS = [
3139
3156
  * Deliberately thin. The framework already knows how to arrange its own output for every platform, and
3140
3157
  * `rshono build` writes the one platform config that has to exist (`wrangler.jsonc`, with the
3141
3158
  * `compatibility_date` of the day it ran) if the project has none. Generating a second copy here would
3142
- * be a copy that goes stale. So a target contributes a `deploy` script, the CLI it needs locally, and
3143
- * the build artefacts its platform leaves in the project.
3159
+ * be a copy that goes stale.
3160
+ *
3161
+ * So a target contributes only what is true of it: the command that runs or ships the build, the CLI
3162
+ * that command needs installed locally, the directories its platform leaves behind for `.gitignore`, and
3163
+ * a note for the step no command covers. Several contribute just one of those.
3144
3164
  */ const DEPLOY_FEATURES = {
3145
3165
  // Where a Node build goes from here is a Dockerfile or a process manager, neither of which this can
3146
3166
  // guess — so the target contributes only the command that runs what was built.
@@ -3223,8 +3243,10 @@ function deployFeature(target) {
3223
3243
  * deduplicates by `id`, so `formatter: 'biome', linter: 'biome'` contributes one set of files, one
3224
3244
  * dependency and one pair of scripts.
3225
3245
  *
3226
- * Each tool brings its own `check` script alongside `format`/`lint`, because the writing half and the
3227
- * CI half want different exit-code behaviour: `format` rewrites files, `check` fails instead.
3246
+ * A formatter brings `format:check` beside `format`, because the writing half and the CI half want
3247
+ * different exit-code behaviour: `format` rewrites files, `format:check` fails instead. A linter brings
3248
+ * `lint:fix` beside `lint`, for the same reason in the other direction — `lint` is already the failing
3249
+ * one. Biome adds a `check` of its own, which is the pair of them in a single pass.
3228
3250
  */ const PRETTIER = {
3229
3251
  id: 'prettier',
3230
3252
  overlays: [
@@ -3380,8 +3402,9 @@ function stylingFeature(styling) {
3380
3402
  ;// CONCATENATED MODULE: ./src/pkg.ts
3381
3403
 
3382
3404
  /**
3383
- * The scripts every app gets. `start` is not among them: it is the *Node* launcher, and the deploy
3384
- * features each contribute the command their own platform runs what was built with.
3405
+ * The scripts every app gets. `start` is not among them, because it means something different per
3406
+ * platform: the three targets that run the build themselves node, bun, deno each contribute their
3407
+ * own, and a platform target contributes a `deploy` instead, where its platform has one command to give.
3385
3408
  */ const BASE_SCRIPTS = {
3386
3409
  dev: 'rshono dev',
3387
3410
  build: 'rshono build',
@@ -3465,10 +3488,9 @@ function sorted(record) {
3465
3488
  version: '0.1.0',
3466
3489
  private: true,
3467
3490
  type: 'module',
3468
- // The floor the framework declares. Stated here too so a CI image or a contributor on an older
3469
- // Node finds out from their package manager rather than from a stack trace.
3491
+ // Generated from the framework's own manifest, so the app's floor cannot drift below rshono's.
3470
3492
  engines: {
3471
- node: '>=22.1.0'
3493
+ node: NODE_ENGINE
3472
3494
  },
3473
3495
  scripts,
3474
3496
  dependencies: sorted(dependencies),
@@ -3486,19 +3508,23 @@ function sorted(record) {
3486
3508
 
3487
3509
  ;// CONCATENATED MODULE: ./src/render.ts
3488
3510
 
3489
- const TOKEN_PATTERN = /__[A-Z][A-Z\d_]*__/g;
3511
+ /**
3512
+ * `{{NAME}}`, and not the `__NAME__` this used to be. A template is a real file that real tools run
3513
+ * over, and in markdown `__NAME__` *is* strong emphasis — Prettier rewrites it to `**NAME**`, which
3514
+ * turns a token into literal text that no substitution will ever match again. `{{…}}` means nothing to
3515
+ * any of the formats these templates are written in.
3516
+ */ const TOKEN_PATTERN = /\{\{[A-Z][A-Z\d_]*\}\}/g;
3490
3517
  function tokensFor(answers, pm) {
3491
3518
  return {
3492
- __PROJECT_NAME__: answers.packageName,
3493
- __DEPLOY_TARGET__: answers.deploy,
3494
- __DEPLOY_HINT__: deployHint(answers.deploy),
3495
- __PM__: pm.name,
3496
- __PM_RUN__: pm.run
3519
+ '{{PROJECT_NAME}}': answers.packageName,
3520
+ '{{DEPLOY_TARGET}}': answers.deploy,
3521
+ '{{DEPLOY_HINT}}': deployHint(answers.deploy),
3522
+ '{{PM_RUN}}': pm.run
3497
3523
  };
3498
3524
  }
3499
3525
  /**
3500
3526
  * Substitutes tokens, and throws on one it doesn't know — a typo in a template would otherwise ship a
3501
- * literal `__PORJECT_NAME__` into somebody's new app, which no test of the generator's logic would
3527
+ * literal `{{PORJECT_NAME}}` into somebody's new app, which no test of the generator's logic would
3502
3528
  * catch.
3503
3529
  */ function render(contents, tokens, source) {
3504
3530
  return contents.replace(TOKEN_PATTERN, (token)=>{
@@ -3587,6 +3613,7 @@ function readTemplateDir(dir) {
3587
3613
 
3588
3614
  ;// CONCATENATED MODULE: ./src/pm.ts
3589
3615
 
3616
+
3590
3617
  const INSTALL = {
3591
3618
  npm: [
3592
3619
  'install'
@@ -3606,7 +3633,7 @@ const RUN = {
3606
3633
  bun: 'bun'
3607
3634
  };
3608
3635
  function isKnown(name) {
3609
- return name === 'npm' || name === 'pnpm' || name === 'yarn' || name === 'bun';
3636
+ return PACKAGE_MANAGERS.includes(name);
3610
3637
  }
3611
3638
  function packageManager(name, version) {
3612
3639
  return {
@@ -3700,15 +3727,20 @@ function run(pm, args, cwd) {
3700
3727
  '.vscode',
3701
3728
  'Thumbs.db'
3702
3729
  ]);
3703
- function inspectTarget(dir) {
3704
- if (!external_node_fs_existsSync(dir)) return {
3705
- exists: false,
3706
- conflicts: []
3707
- };
3708
- return {
3709
- exists: true,
3710
- conflicts: external_node_fs_readdirSync(dir).filter((entry)=>!IGNORED_ENTRIES.has(entry))
3711
- };
3730
+ /**
3731
+ * What is already at the target path, which is what decides whether scaffolding into it is safe: the
3732
+ * entries already there, ignoring the ones a fresh clone or an editor leaves behind.
3733
+ *
3734
+ * A path that does not exist yet is no conflict. A path that exists and is *not* a directory is not
3735
+ * something `--force` should be able to write into, so it throws rather than reporting an empty list —
3736
+ * otherwise `create-rshono README.md` gets as far as `mkdir` before failing on a raw ENOTDIR.
3737
+ */ function conflictingEntries(dir) {
3738
+ const stats = statSync(dir, {
3739
+ throwIfNoEntry: false
3740
+ });
3741
+ if (!stats) return [];
3742
+ if (!stats.isDirectory()) throw new Error(`${dir} already exists and is not a directory.`);
3743
+ return external_node_fs_readdirSync(dir).filter((entry)=>!IGNORED_ENTRIES.has(entry));
3712
3744
  }
3713
3745
  /**
3714
3746
  * Writes the plan. Directories are created as needed, and files are written with the plan's own
@@ -3736,8 +3768,9 @@ function inspectTarget(dir) {
3736
3768
 
3737
3769
 
3738
3770
 
3771
+
3739
3772
  const DEFAULT_DIRECTORY = 'my-rshono-app';
3740
- const HELP = `create-rshono — scaffold a new rshono app
3773
+ /** Every accepted name comes from `options.ts`, so the help cannot promise one the validation refuses. */ const HELP = `create-rshono — scaffold a new rshono app
3741
3774
 
3742
3775
  Usage:
3743
3776
  npm create @rshono@latest [directory] [options]
@@ -3747,9 +3780,9 @@ Options:
3747
3780
  -d, --deploy <target> ${DEPLOY_TARGET_NAMES.join(' | ')}
3748
3781
  --tailwind Tailwind CSS (--no-tailwind for plain CSS)
3749
3782
  --quality <preset> ${QUALITY_PRESETS.map((preset)=>preset.id).join(' | ')}
3750
- --formatter <name> prettier | biome | oxfmt | none (overrides --quality)
3751
- --linter <name> oxlint | eslint | biome | none (overrides --quality; eslint pins TypeScript 6)
3752
- --pm <name> npm | pnpm | yarn | bun (default: whatever ran this)
3783
+ --formatter <name> ${FORMATTER_NAMES.join(' | ')} (overrides --quality)
3784
+ --linter <name> ${LINTER_NAMES.join(' | ')} (overrides --quality; eslint pins TypeScript 6)
3785
+ --pm <name> ${PACKAGE_MANAGERS.join(' | ')} (default: whatever ran this)
3753
3786
  --no-install write the files and stop
3754
3787
  --no-git do not initialize a repository
3755
3788
  --force scaffold into a directory that is not empty
@@ -3777,93 +3810,94 @@ function fail(message) {
3777
3810
  if (off) return false;
3778
3811
  return undefined;
3779
3812
  }
3780
- async function main() {
3781
- const { values, positionals } = parseArgs({
3782
- options: {
3783
- yes: {
3784
- type: 'boolean',
3785
- short: 'y'
3786
- },
3787
- deploy: {
3788
- type: 'string',
3789
- short: 'd'
3790
- },
3791
- tailwind: {
3792
- type: 'boolean'
3793
- },
3794
- 'no-tailwind': {
3795
- type: 'boolean'
3796
- },
3797
- quality: {
3798
- type: 'string'
3799
- },
3800
- formatter: {
3801
- type: 'string'
3802
- },
3803
- linter: {
3804
- type: 'string'
3805
- },
3806
- pm: {
3807
- type: 'string'
3808
- },
3809
- install: {
3810
- type: 'boolean'
3811
- },
3812
- 'no-install': {
3813
- type: 'boolean'
3814
- },
3815
- git: {
3816
- type: 'boolean'
3817
- },
3818
- 'no-git': {
3819
- type: 'boolean'
3820
- },
3821
- force: {
3822
- type: 'boolean'
3823
- },
3824
- 'dry-run': {
3825
- type: 'boolean'
3826
- },
3827
- help: {
3828
- type: 'boolean',
3829
- short: 'h'
3813
+ /**
3814
+ * `parseArgs` rejects an unknown flag with a message that names it but nothing else — so a mistyped
3815
+ * `--tailwnid` reads as a wall of text about positional arguments. Pointing at `--help` is the whole
3816
+ * addition.
3817
+ */ function parse() {
3818
+ try {
3819
+ return parseArgs({
3820
+ options: {
3821
+ yes: {
3822
+ type: 'boolean',
3823
+ short: 'y'
3824
+ },
3825
+ deploy: {
3826
+ type: 'string',
3827
+ short: 'd'
3828
+ },
3829
+ tailwind: {
3830
+ type: 'boolean'
3831
+ },
3832
+ 'no-tailwind': {
3833
+ type: 'boolean'
3834
+ },
3835
+ quality: {
3836
+ type: 'string'
3837
+ },
3838
+ formatter: {
3839
+ type: 'string'
3840
+ },
3841
+ linter: {
3842
+ type: 'string'
3843
+ },
3844
+ pm: {
3845
+ type: 'string'
3846
+ },
3847
+ install: {
3848
+ type: 'boolean'
3849
+ },
3850
+ 'no-install': {
3851
+ type: 'boolean'
3852
+ },
3853
+ git: {
3854
+ type: 'boolean'
3855
+ },
3856
+ 'no-git': {
3857
+ type: 'boolean'
3858
+ },
3859
+ force: {
3860
+ type: 'boolean'
3861
+ },
3862
+ 'dry-run': {
3863
+ type: 'boolean'
3864
+ },
3865
+ help: {
3866
+ type: 'boolean',
3867
+ short: 'h'
3868
+ },
3869
+ version: {
3870
+ type: 'boolean',
3871
+ short: 'v'
3872
+ }
3830
3873
  },
3831
- version: {
3832
- type: 'boolean',
3833
- short: 'v'
3834
- }
3835
- },
3836
- allowPositionals: true
3837
- });
3874
+ allowPositionals: true
3875
+ });
3876
+ } catch (error) {
3877
+ fail(`${error instanceof Error ? error.message : String(error)}\n\nRun with --help to see the options.`);
3878
+ }
3879
+ }
3880
+ async function main() {
3881
+ const { values, positionals } = parse();
3838
3882
  if (values.help) return console.log(HELP);
3839
- if (values.version) return console.log("1.0.0-rc.1");
3840
- // A pipe, a CI job or an agent gets the defaults rather than a prompt nothing can answer.
3841
- const interactive = Boolean(process.stdout.isTTY) && !values.yes;
3842
- const pmFlag = oneOf(values.pm, [
3843
- 'npm',
3844
- 'pnpm',
3845
- 'yarn',
3846
- 'bun'
3847
- ], 'pm');
3883
+ if (values.version) return console.log("1.0.0-rc.2");
3884
+ /*
3885
+ * A pipe, a CI job or an agent gets the defaults rather than a prompt nothing can answer. Both streams
3886
+ * have to be a terminal: the prompts draw on stdout but *read from stdin*, so `echo | npm create` with
3887
+ * stdout still attached would otherwise ask a question with nothing behind the keyboard.
3888
+ */ const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY) && !values.yes;
3889
+ const pmFlag = oneOf(values.pm, PACKAGE_MANAGERS, 'pm');
3848
3890
  const pm = pmFlag ? packageManager(pmFlag) : detectPackageManager();
3849
3891
  const deployFlag = oneOf(values.deploy, DEPLOY_TARGET_NAMES, 'deploy');
3850
- const formatterFlag = oneOf(values.formatter, [
3851
- 'prettier',
3852
- 'biome',
3853
- 'oxfmt',
3854
- 'none'
3855
- ], 'formatter');
3856
- const linterFlag = oneOf(values.linter, [
3857
- 'oxlint',
3858
- 'eslint',
3859
- 'biome',
3860
- 'none'
3861
- ], 'linter');
3892
+ const formatterFlag = oneOf(values.formatter, FORMATTER_NAMES, 'formatter');
3893
+ const linterFlag = oneOf(values.linter, LINTER_NAMES, 'linter');
3862
3894
  const qualityFlag = oneOf(values.quality, QUALITY_PRESETS.map((preset)=>preset.id), 'quality');
3863
3895
  const tailwindFlag = tristate(values.tailwind, values['no-tailwind'], 'tailwind');
3864
3896
  const installFlag = tristate(values.install, values['no-install'], 'install');
3865
3897
  const gitFlag = tristate(values.git, values['no-git'], 'git');
3866
- intro(`create-rshono · rshono ${"1.0.0-rc.1"}`);
3898
+ // The framework version, not this package's: it is the one the app will be pinned to, and the one
3899
+ // worth reading here. `--version` reports create-rshono's own.
3900
+ intro(`create-rshono · rshono ${RSHONO_VERSION}`);
3867
3901
  // ── Where ───────────────────────────────────────────────────────────────────────────────────────
3868
3902
  let directory = positionals[0];
3869
3903
  if (!directory) {
@@ -3877,7 +3911,7 @@ async function main() {
3877
3911
  const targetDir = resolve(process.cwd(), directory);
3878
3912
  const packageName = toPackageName(directory === '.' ? basename(targetDir) : directory);
3879
3913
  if (!packageName || !isValidPackageName(packageName)) fail(`"${directory}" does not give a usable npm package name.`);
3880
- const conflicts = inspectTarget(targetDir).conflicts;
3914
+ const conflicts = conflictingEntries(targetDir);
3881
3915
  if (conflicts.length > 0 && !values.force) {
3882
3916
  const where = directory === '.' ? 'this directory' : `"${directory}"`;
3883
3917
  const listed = `${conflicts.slice(0, 3).join(', ')}${conflicts.length > 3 ? ', …' : ''}`;
@@ -3903,7 +3937,7 @@ async function main() {
3903
3937
  }))
3904
3938
  }));
3905
3939
  }
3906
- let styling = tailwindFlag === undefined ? 'css' : tailwindFlag ? 'tailwind' : 'css';
3940
+ let styling = tailwindFlag ? 'tailwind' : 'css';
3907
3941
  if (tailwindFlag === undefined && interactive) {
3908
3942
  styling = unwrap(await dist_select({
3909
3943
  message: 'Styling?',
@@ -3953,9 +3987,10 @@ async function main() {
3953
3987
  initialValue: true
3954
3988
  }));
3955
3989
  }
3956
- let git = gitFlag ?? !isInsideRepo(process.cwd());
3990
+ // Asked once, not once per use: this shells out to `git rev-parse`.
3991
+ const nested = isInsideRepo(process.cwd());
3992
+ let git = gitFlag ?? !nested;
3957
3993
  if (gitFlag === undefined && interactive) {
3958
- const nested = isInsideRepo(process.cwd());
3959
3994
  git = unwrap(await dist_confirm({
3960
3995
  message: nested ? 'Initialize a git repository? (this is already inside one)' : 'Initialize a git repository?',
3961
3996
  initialValue: !nested
@@ -3963,14 +3998,10 @@ async function main() {
3963
3998
  }
3964
3999
  const answers = {
3965
4000
  packageName,
3966
- targetDir,
3967
4001
  deploy,
3968
4002
  styling,
3969
4003
  formatter,
3970
- linter,
3971
- packageManager: pm.name,
3972
- install,
3973
- git
4004
+ linter
3974
4005
  };
3975
4006
  // ── Plan, then write ────────────────────────────────────────────────────────────────────────────
3976
4007
  const plan = plan_plan(answers, pm);
@@ -3987,14 +4018,17 @@ async function main() {
3987
4018
  if (install) {
3988
4019
  log.step(`Installing dependencies with ${pm.name}…`);
3989
4020
  installed = runInstall(pm, targetDir);
3990
- if (!installed) log.warn(`${pm.name} install failed — run it yourself and the rest will work.`);
4021
+ if (!installed) log.warn(`${pm.name} install failed — the files are all written, so run it yourself in the project.`);
3991
4022
  }
3992
4023
  /*
3993
4024
  * Format the scaffold with the tool it was scaffolded with, so a fresh project passes its own
3994
4025
  * `format:check` instead of reporting a diff nobody made. Needs the install, since the formatter is a
3995
4026
  * devDependency — hence the skip, rather than a failure, when there is none.
3996
- */ if (installed && formatter !== 'none') {
3997
- if (!runScript(pm, 'format', targetDir)) log.warn(`\`${pm.run} format\` failed the files are fine, the formatter is not.`);
4027
+ *
4028
+ * Whether there is a `format` script is the plan's answer, not the formatter answer's: `--formatter
4029
+ * none --linter biome` has no formatter and a `format` script all the same, because Biome brings one.
4030
+ */ if (installed && plan.features.some((feature)=>feature.scripts?.format)) {
4031
+ if (!runScript(pm, 'format', targetDir)) log.warn(`\`${pm.run} format\` failed — the scaffold is written, just not formatted.`);
3998
4032
  }
3999
4033
  if (git) {
4000
4034
  if (!hasGit(targetDir)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rshono/create",
3
- "version": "1.0.0-rc.1",
3
+ "version": "1.0.0-rc.2",
4
4
  "description": "Scaffold a new rshono app — Hono + Rspack + React Server Components",
5
5
  "author": "Lasse <lasse@lassetange.com> (https://www.lassetange.com)",
6
6
  "license": "ISC",
@@ -1,11 +1,11 @@
1
- # **PROJECT_NAME**
1
+ # {{PROJECT_NAME}}
2
2
 
3
3
  A [rshono](https://github.com/rshono/rshono) app — [Hono](https://hono.dev) + [Rspack](https://rspack.rs) + [React Server Components](https://react.dev/reference/rsc/server-components).
4
4
 
5
5
  ```bash
6
- __PM_RUN__ dev # dev server with HMR, http://localhost:3000
7
- __PM_RUN__ build # production build for __DEPLOY_TARGET__
8
- __PM_RUN__ typecheck # tsc --noEmit
6
+ {{PM_RUN}} dev # dev server with HMR, http://localhost:3000
7
+ {{PM_RUN}} build # production build for {{DEPLOY_TARGET}}
8
+ {{PM_RUN}} typecheck # tsc --noEmit
9
9
  ```
10
10
 
11
11
  `package.json` has the rest, including whatever your formatter and linter added.
@@ -35,7 +35,7 @@ compiles to `undefined` rather than shipping. `src/lib/env.ts` is where both sid
35
35
 
36
36
  ## Deploying
37
37
 
38
- This app is built for `__DEPLOY_TARGET__`: after `__PM_RUN__ build`, **DEPLOY_HINT**.
38
+ This app is built for `{{DEPLOY_TARGET}}`: after `{{PM_RUN}} build`, {{DEPLOY_HINT}}.
39
39
 
40
40
  Change `deploy` in `rshono.config.ts` to target somewhere else, or build for one place without editing the
41
41
  file: `rshono build --deploy vercel`, or `RSHONO_DEPLOY=vercel` in CI. `dev` always runs the Node dev
@@ -6,4 +6,4 @@
6
6
  # Only `PUBLIC_`-prefixed variables are compiled into the browser bundle. Anything else is server-only:
7
7
  # a stray read of it in client code compiles to `undefined` rather than shipping.
8
8
 
9
- PUBLIC_APP_NAME="__PROJECT_NAME__"
9
+ PUBLIC_APP_NAME="{{PROJECT_NAME}}"
@@ -10,7 +10,7 @@ import { defineConfig } from '@rshono/core';
10
10
  */
11
11
  export default defineConfig({
12
12
  /** Where `build` targets. Overridable per build with `--deploy` or `RSHONO_DEPLOY`. */
13
- deploy: '__DEPLOY_TARGET__',
13
+ deploy: '{{DEPLOY_TARGET}}',
14
14
 
15
15
  // The public origin, baked into prerendered pages' absolute URLs. Set it if you use `render: 'static'`
16
16
  // and build canonical tags, `og:url` or absolute links — there is no request to read a Host from.
@@ -9,7 +9,7 @@
9
9
 
10
10
  /** Safe anywhere: `PUBLIC_` variables are the ones compiled into the browser bundle. */
11
11
  export const publicEnv = {
12
- appName: process.env.PUBLIC_APP_NAME ?? '__PROJECT_NAME__',
12
+ appName: process.env.PUBLIC_APP_NAME ?? '{{PROJECT_NAME}}',
13
13
  };
14
14
 
15
15
  /**
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://biomejs.dev/schemas/2.5.6/schema.json",
3
- "files": { "includes": ["**", "!dist", "!wrangler.jsonc"] },
3
+ "files": { "includes": ["**", "!dist", "!.wrangler", "!.vercel", "!.netlify", "!wrangler.jsonc"] },
4
4
  "formatter": { "indentStyle": "space", "indentWidth": 2, "lineWidth": 140 },
5
5
  "javascript": { "formatter": { "quoteStyle": "single", "jsxQuoteStyle": "double" } },
6
6
  "linter": { "enabled": true, "rules": { "preset": "recommended" } }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://biomejs.dev/schemas/2.5.6/schema.json",
3
- "files": { "includes": ["**", "!dist", "!wrangler.jsonc", "!**/*.css"] },
3
+ "files": { "includes": ["**", "!dist", "!.wrangler", "!.vercel", "!.netlify", "!wrangler.jsonc", "!**/*.css"] },
4
4
  "formatter": { "indentStyle": "space", "indentWidth": 2, "lineWidth": 140 },
5
5
  "javascript": { "formatter": { "quoteStyle": "single", "jsxQuoteStyle": "double" } },
6
6
  "linter": { "enabled": true, "rules": { "preset": "recommended" } }
@@ -3,5 +3,5 @@
3
3
  "printWidth": 140,
4
4
  "singleQuote": true,
5
5
  "trailingComma": "all",
6
- "ignorePatterns": ["wrangler.jsonc"]
6
+ "ignorePatterns": ["dist", ".wrangler", ".vercel", ".netlify", "wrangler.jsonc"]
7
7
  }
@@ -3,5 +3,5 @@
3
3
  "categories": {
4
4
  "correctness": "error"
5
5
  },
6
- "ignorePatterns": ["dist"]
6
+ "ignorePatterns": ["dist", ".wrangler", ".vercel", ".netlify"]
7
7
  }
@@ -1,10 +1,15 @@
1
1
  node_modules/
2
- dist/
3
2
  pnpm-lock.yaml
4
3
  package-lock.json
5
4
  yarn.lock
6
5
  bun.lock
7
6
 
7
+ # Build output: `rshono build`'s own, and whatever the deploy target assembles beside it.
8
+ dist/
9
+ .wrangler/
10
+ .vercel/
11
+ .netlify/
12
+
8
13
  # Written by `rshono build` for the Cloudflare target, in the shape the build decided. Yours to edit —
9
14
  # just not the formatter's to reflow on every build.
10
15
  wrangler.jsonc
@@ -10,7 +10,7 @@ import { defineConfig } from '@rshono/core';
10
10
  */
11
11
  export default defineConfig({
12
12
  /** Where `build` targets. Overridable per build with `--deploy` or `RSHONO_DEPLOY`. */
13
- deploy: '__DEPLOY_TARGET__',
13
+ deploy: '{{DEPLOY_TARGET}}',
14
14
 
15
15
  // The public origin, baked into prerendered pages' absolute URLs. Set it if you use `render: 'static'`
16
16
  // and build canonical tags, `og:url` or absolute links — there is no request to read a Host from.