@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 +10 -5
- package/dist/api.mjs +85 -45
- package/dist/cli.mjs +163 -129
- package/package.json +1 -1
- package/templates/base/README.md +5 -5
- package/templates/base/_env +1 -1
- package/templates/base/rshono.config.ts +1 -1
- package/templates/base/src/lib/env.ts +1 -1
- package/templates/biome/biome.json +1 -1
- package/templates/biome-tailwind/biome.json +1 -1
- package/templates/oxfmt/_oxfmtrc.json +1 -1
- package/templates/oxlint/_oxlintrc.json +1 -1
- package/templates/prettier/_prettierignore +6 -1
- package/templates/tailwind/rshono.config.ts +1 -1
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, `--
|
|
30
|
-
|
|
31
|
-
to
|
|
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 —
|
|
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 {
|
|
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
|
-
|
|
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.
|
|
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.
|
|
180
|
-
*
|
|
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
|
-
*
|
|
264
|
-
*
|
|
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
|
|
493
|
-
*
|
|
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
|
-
//
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
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 `
|
|
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
|
|
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
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
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
|
|
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,
|
|
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 {
|
|
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.
|
|
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
|
-
|
|
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.
|
|
3143
|
-
*
|
|
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
|
-
*
|
|
3227
|
-
*
|
|
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
|
|
3384
|
-
*
|
|
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
|
-
//
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
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 `
|
|
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
|
|
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
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
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>
|
|
3751
|
-
--linter <name>
|
|
3752
|
-
--pm <name>
|
|
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
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
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
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
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.
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
3997
|
-
|
|
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
package/templates/base/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
#
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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 `
|
|
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
|
package/templates/base/_env
CHANGED
|
@@ -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: '
|
|
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 ?? '
|
|
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" } }
|
|
@@ -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: '
|
|
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.
|