@rshono/create 1.0.0-rc.1 → 1.0.0-rc.3
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 +30 -16
- package/bin/create-rshono.mjs +0 -0
- package/dist/api.mjs +88 -47
- package/dist/cli.mjs +168 -133
- package/package.json +10 -11
- package/templates/base/README.md +5 -5
- package/templates/base/_env +1 -1
- package/templates/base/public/favicon.svg +2 -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
|
@@ -1,17 +1,26 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/rshono/rshono/main/logo.svg" alt="" width="72" height="72" />
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<h1 align="center">@rshono/create</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
Scaffolds a new <a href="https://github.com/rshono/rshono">rshono</a> app — Hono + Rspack + React Server Components.
|
|
9
|
+
</p>
|
|
4
10
|
|
|
5
11
|
```bash
|
|
6
|
-
|
|
7
|
-
|
|
12
|
+
npx @rshono/create@latest my-app
|
|
13
|
+
pnx @rshono/create my-app
|
|
14
|
+
bunx @rshono/create my-app
|
|
8
15
|
yarn create @rshono my-app
|
|
9
|
-
bun create @rshono my-app
|
|
10
16
|
```
|
|
11
17
|
|
|
12
|
-
The package manager that ran it is the one the project gets: it is read from `npm_config_user_agent`,
|
|
13
|
-
for the install, written into `packageManager` for Corepack,
|
|
14
|
-
prints. Nothing asks you which one you meant.
|
|
18
|
+
The package manager that ran it is the one the project gets: it is read from `npm_config_user_agent`, which
|
|
19
|
+
every one of these runners sets, and it is used for the install, written into `packageManager` for Corepack,
|
|
20
|
+
and used in every command the closing summary prints. Nothing asks you which one you meant.
|
|
21
|
+
|
|
22
|
+
Yarn is the odd row out because `yarn dlx` is Berry-only, while `yarn create` works in either — and `pnx`
|
|
23
|
+
arrived in pnpm 10.16, before which it is `pnpm dlx`.
|
|
15
24
|
|
|
16
25
|
## The questions
|
|
17
26
|
|
|
@@ -26,13 +35,13 @@ Six, each with a default, and each answerable by a flag instead:
|
|
|
26
35
|
| Install dependencies? | yes | `--no-install` |
|
|
27
36
|
| Initialize git? | yes, unless nested | `--no-git` |
|
|
28
37
|
|
|
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:
|
|
38
|
+
Plus `--formatter` and `--linter` to set either half of the quality preset on its own, `--pm` to override
|
|
39
|
+
the package manager that was detected, `--force` to scaffold into a directory that is not empty,
|
|
40
|
+
`--dry-run` to see the file list and write nothing, and `-y` to take the defaults for everything not
|
|
41
|
+
given. **A non-interactive terminal implies `-y`**, so this is one command in CI or from an agent:
|
|
33
42
|
|
|
34
43
|
```bash
|
|
35
|
-
|
|
44
|
+
npx @rshono/create@latest my-app -y --deploy cloudflare --tailwind --quality biome
|
|
36
45
|
```
|
|
37
46
|
|
|
38
47
|
The deploy targets, and the deploy command each one prints, are generated from the framework's own
|
|
@@ -104,9 +113,14 @@ delete to go back to plain CSS.
|
|
|
104
113
|
|
|
105
114
|
```bash
|
|
106
115
|
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 —
|
|
116
|
+
pnpm --filter @rshono/create test # builds, then the plan matrix and the CLI — seconds, no installs
|
|
108
117
|
CREATE_RSHONO_E2E=1 pnpm --filter @rshono/create test # also: pack, install and build real apps
|
|
109
118
|
```
|
|
110
119
|
|
|
111
|
-
|
|
112
|
-
|
|
120
|
+
`plan.test.mjs` is the fast one — the whole matrix of answers in memory, no directory touched.
|
|
121
|
+
`cli.test.mjs` spawns the real bin against temp directories, which is where argument parsing and the
|
|
122
|
+
refusal to overwrite somebody's files are checked. `e2e.test.mjs` is the opt-in one, and the only one that
|
|
123
|
+
installs anything.
|
|
124
|
+
|
|
125
|
+
`@clack/prompts` (MIT) is bundled rather than depended on, so `npx @rshono/create` downloads one tarball
|
|
126
|
+
before it can ask its first question.
|
package/bin/create-rshono.mjs
CHANGED
|
File without changes
|
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.3';
|
|
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 {
|
|
@@ -733,8 +760,9 @@ function packageManager(name, version) {
|
|
|
733
760
|
}
|
|
734
761
|
/**
|
|
735
762
|
* Which package manager invoked us. Every one of them sets `npm_config_user_agent` for the process it
|
|
736
|
-
* spawns — `pnpm/11.9.0 npm/? node/v22.14.0 darwin arm64` — so
|
|
737
|
-
* project without asking, and the exact version comes along for the
|
|
763
|
+
* spawns, from its runner as much as from `create` — `pnpm/11.9.0 npm/? node/v22.14.0 darwin arm64` — so
|
|
764
|
+
* `pnx @rshono/create` scaffolds a pnpm project without asking, and the exact version comes along for the
|
|
765
|
+
* `packageManager` field.
|
|
738
766
|
*
|
|
739
767
|
* Falls back to npm, which is also what a bare `node bin/create-rshono.mjs` gets.
|
|
740
768
|
*/ function detectPackageManager(userAgent = process.env.npm_config_user_agent) {
|
|
@@ -778,15 +806,20 @@ function run(pm, args, cwd) {
|
|
|
778
806
|
'.vscode',
|
|
779
807
|
'Thumbs.db'
|
|
780
808
|
]);
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
809
|
+
/**
|
|
810
|
+
* What is already at the target path, which is what decides whether scaffolding into it is safe: the
|
|
811
|
+
* entries already there, ignoring the ones a fresh clone or an editor leaves behind.
|
|
812
|
+
*
|
|
813
|
+
* A path that does not exist yet is no conflict. A path that exists and is *not* a directory is not
|
|
814
|
+
* something `--force` should be able to write into, so it throws rather than reporting an empty list —
|
|
815
|
+
* otherwise `create-rshono README.md` gets as far as `mkdir` before failing on a raw ENOTDIR.
|
|
816
|
+
*/ function conflictingEntries(dir) {
|
|
817
|
+
const stats = statSync(dir, {
|
|
818
|
+
throwIfNoEntry: false
|
|
819
|
+
});
|
|
820
|
+
if (!stats) return [];
|
|
821
|
+
if (!stats.isDirectory()) throw new Error(`${dir} already exists and is not a directory.`);
|
|
822
|
+
return readdirSync(dir).filter((entry)=>!IGNORED_ENTRIES.has(entry));
|
|
790
823
|
}
|
|
791
824
|
/**
|
|
792
825
|
* Writes the plan. Directories are created as needed, and files are written with the plan's own
|
|
@@ -808,7 +841,16 @@ function inspectTarget(dir) {
|
|
|
808
841
|
/**
|
|
809
842
|
* The generator without the CLI around it: answers in, the exact set of files out. This is what the
|
|
810
843
|
* 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
|
|
844
|
+
* what another tool would call to scaffold an app itself:
|
|
845
|
+
*
|
|
846
|
+
* ```ts
|
|
847
|
+
* import { packageManager, plan, writePlan } from '@rshono/create';
|
|
848
|
+
*
|
|
849
|
+
* const answers = { packageName: 'my-app', deploy: 'node', styling: 'css', formatter: 'prettier', linter: 'oxlint' };
|
|
850
|
+
* writePlan(plan(answers, packageManager('npm')), './my-app');
|
|
851
|
+
* ```
|
|
852
|
+
*
|
|
853
|
+
* `package.json` is assembled inside `plan`, so nothing here needs to be composed by hand.
|
|
812
854
|
*
|
|
813
855
|
* @packageDocumentation
|
|
814
856
|
*/
|
|
@@ -818,23 +860,22 @@ function inspectTarget(dir) {
|
|
|
818
860
|
|
|
819
861
|
|
|
820
862
|
|
|
821
|
-
|
|
822
863
|
var __webpack_exports__DEPLOY_TARGET_NAMES = __webpack_exports__.bp;
|
|
823
864
|
var __webpack_exports__ESLINT_TYPESCRIPT = __webpack_exports__.ml;
|
|
865
|
+
var __webpack_exports__FORMATTER_NAMES = __webpack_exports__.wC;
|
|
824
866
|
var __webpack_exports__FRAMEWORK_DEPS = __webpack_exports__.e7;
|
|
867
|
+
var __webpack_exports__LINTER_NAMES = __webpack_exports__.At;
|
|
825
868
|
var __webpack_exports__PACKAGE_MANAGERS = __webpack_exports__.Ab;
|
|
826
869
|
var __webpack_exports__QUALITY_PRESETS = __webpack_exports__.pm;
|
|
827
870
|
var __webpack_exports__RSHONO_RANGE = __webpack_exports__.s_;
|
|
828
871
|
var __webpack_exports__TOOL_VERSIONS = __webpack_exports__.RM;
|
|
829
|
-
var
|
|
872
|
+
var __webpack_exports__conflictingEntries = __webpack_exports__.dh;
|
|
830
873
|
var __webpack_exports__deployHint = __webpack_exports__.P8;
|
|
831
874
|
var __webpack_exports__detectPackageManager = __webpack_exports__.Lv;
|
|
832
|
-
var __webpack_exports__inspectTarget = __webpack_exports__.WL;
|
|
833
|
-
var __webpack_exports__isDeployTarget = __webpack_exports__.p1;
|
|
834
875
|
var __webpack_exports__isValidPackageName = __webpack_exports__.eW;
|
|
835
876
|
var __webpack_exports__packageManager = __webpack_exports__.VZ;
|
|
836
877
|
var __webpack_exports__plan = __webpack_exports__.et;
|
|
837
878
|
var __webpack_exports__selectFeatures = __webpack_exports__.N7;
|
|
838
879
|
var __webpack_exports__toPackageName = __webpack_exports__.PQ;
|
|
839
880
|
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,
|
|
881
|
+
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.3';
|
|
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 {
|
|
@@ -3618,8 +3645,9 @@ function packageManager(name, version) {
|
|
|
3618
3645
|
}
|
|
3619
3646
|
/**
|
|
3620
3647
|
* Which package manager invoked us. Every one of them sets `npm_config_user_agent` for the process it
|
|
3621
|
-
* spawns — `pnpm/11.9.0 npm/? node/v22.14.0 darwin arm64` — so
|
|
3622
|
-
* project without asking, and the exact version comes along for the
|
|
3648
|
+
* spawns, from its runner as much as from `create` — `pnpm/11.9.0 npm/? node/v22.14.0 darwin arm64` — so
|
|
3649
|
+
* `pnx @rshono/create` scaffolds a pnpm project without asking, and the exact version comes along for the
|
|
3650
|
+
* `packageManager` field.
|
|
3623
3651
|
*
|
|
3624
3652
|
* Falls back to npm, which is also what a bare `node bin/create-rshono.mjs` gets.
|
|
3625
3653
|
*/ function detectPackageManager(userAgent = process.env.npm_config_user_agent) {
|
|
@@ -3700,15 +3728,20 @@ function run(pm, args, cwd) {
|
|
|
3700
3728
|
'.vscode',
|
|
3701
3729
|
'Thumbs.db'
|
|
3702
3730
|
]);
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3731
|
+
/**
|
|
3732
|
+
* What is already at the target path, which is what decides whether scaffolding into it is safe: the
|
|
3733
|
+
* entries already there, ignoring the ones a fresh clone or an editor leaves behind.
|
|
3734
|
+
*
|
|
3735
|
+
* A path that does not exist yet is no conflict. A path that exists and is *not* a directory is not
|
|
3736
|
+
* something `--force` should be able to write into, so it throws rather than reporting an empty list —
|
|
3737
|
+
* otherwise `create-rshono README.md` gets as far as `mkdir` before failing on a raw ENOTDIR.
|
|
3738
|
+
*/ function conflictingEntries(dir) {
|
|
3739
|
+
const stats = statSync(dir, {
|
|
3740
|
+
throwIfNoEntry: false
|
|
3741
|
+
});
|
|
3742
|
+
if (!stats) return [];
|
|
3743
|
+
if (!stats.isDirectory()) throw new Error(`${dir} already exists and is not a directory.`);
|
|
3744
|
+
return external_node_fs_readdirSync(dir).filter((entry)=>!IGNORED_ENTRIES.has(entry));
|
|
3712
3745
|
}
|
|
3713
3746
|
/**
|
|
3714
3747
|
* Writes the plan. Directories are created as needed, and files are written with the plan's own
|
|
@@ -3736,20 +3769,21 @@ function inspectTarget(dir) {
|
|
|
3736
3769
|
|
|
3737
3770
|
|
|
3738
3771
|
|
|
3772
|
+
|
|
3739
3773
|
const DEFAULT_DIRECTORY = 'my-rshono-app';
|
|
3740
|
-
const HELP = `create-rshono — scaffold a new rshono app
|
|
3774
|
+
/** 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
3775
|
|
|
3742
3776
|
Usage:
|
|
3743
|
-
|
|
3777
|
+
npx @rshono/create@latest [directory] [options]
|
|
3744
3778
|
|
|
3745
3779
|
Options:
|
|
3746
3780
|
-y, --yes accept the default for every question not given as a flag
|
|
3747
3781
|
-d, --deploy <target> ${DEPLOY_TARGET_NAMES.join(' | ')}
|
|
3748
3782
|
--tailwind Tailwind CSS (--no-tailwind for plain CSS)
|
|
3749
3783
|
--quality <preset> ${QUALITY_PRESETS.map((preset)=>preset.id).join(' | ')}
|
|
3750
|
-
--formatter <name>
|
|
3751
|
-
--linter <name>
|
|
3752
|
-
--pm <name>
|
|
3784
|
+
--formatter <name> ${FORMATTER_NAMES.join(' | ')} (overrides --quality)
|
|
3785
|
+
--linter <name> ${LINTER_NAMES.join(' | ')} (overrides --quality; eslint pins TypeScript 6)
|
|
3786
|
+
--pm <name> ${PACKAGE_MANAGERS.join(' | ')} (default: whatever ran this)
|
|
3753
3787
|
--no-install write the files and stop
|
|
3754
3788
|
--no-git do not initialize a repository
|
|
3755
3789
|
--force scaffold into a directory that is not empty
|
|
@@ -3760,7 +3794,7 @@ Options:
|
|
|
3760
3794
|
Every question can be answered by a flag, and a non-interactive terminal implies --yes — so one command
|
|
3761
3795
|
scaffolds without prompting:
|
|
3762
3796
|
|
|
3763
|
-
|
|
3797
|
+
npx @rshono/create@latest my-app -y --deploy cloudflare --tailwind --quality biome
|
|
3764
3798
|
`;
|
|
3765
3799
|
function fail(message) {
|
|
3766
3800
|
log.error(message);
|
|
@@ -3777,93 +3811,94 @@ function fail(message) {
|
|
|
3777
3811
|
if (off) return false;
|
|
3778
3812
|
return undefined;
|
|
3779
3813
|
}
|
|
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
|
-
|
|
3814
|
+
/**
|
|
3815
|
+
* `parseArgs` rejects an unknown flag with a message that names it but nothing else — so a mistyped
|
|
3816
|
+
* `--tailwnid` reads as a wall of text about positional arguments. Pointing at `--help` is the whole
|
|
3817
|
+
* addition.
|
|
3818
|
+
*/ function parse() {
|
|
3819
|
+
try {
|
|
3820
|
+
return parseArgs({
|
|
3821
|
+
options: {
|
|
3822
|
+
yes: {
|
|
3823
|
+
type: 'boolean',
|
|
3824
|
+
short: 'y'
|
|
3825
|
+
},
|
|
3826
|
+
deploy: {
|
|
3827
|
+
type: 'string',
|
|
3828
|
+
short: 'd'
|
|
3829
|
+
},
|
|
3830
|
+
tailwind: {
|
|
3831
|
+
type: 'boolean'
|
|
3832
|
+
},
|
|
3833
|
+
'no-tailwind': {
|
|
3834
|
+
type: 'boolean'
|
|
3835
|
+
},
|
|
3836
|
+
quality: {
|
|
3837
|
+
type: 'string'
|
|
3838
|
+
},
|
|
3839
|
+
formatter: {
|
|
3840
|
+
type: 'string'
|
|
3841
|
+
},
|
|
3842
|
+
linter: {
|
|
3843
|
+
type: 'string'
|
|
3844
|
+
},
|
|
3845
|
+
pm: {
|
|
3846
|
+
type: 'string'
|
|
3847
|
+
},
|
|
3848
|
+
install: {
|
|
3849
|
+
type: 'boolean'
|
|
3850
|
+
},
|
|
3851
|
+
'no-install': {
|
|
3852
|
+
type: 'boolean'
|
|
3853
|
+
},
|
|
3854
|
+
git: {
|
|
3855
|
+
type: 'boolean'
|
|
3856
|
+
},
|
|
3857
|
+
'no-git': {
|
|
3858
|
+
type: 'boolean'
|
|
3859
|
+
},
|
|
3860
|
+
force: {
|
|
3861
|
+
type: 'boolean'
|
|
3862
|
+
},
|
|
3863
|
+
'dry-run': {
|
|
3864
|
+
type: 'boolean'
|
|
3865
|
+
},
|
|
3866
|
+
help: {
|
|
3867
|
+
type: 'boolean',
|
|
3868
|
+
short: 'h'
|
|
3869
|
+
},
|
|
3870
|
+
version: {
|
|
3871
|
+
type: 'boolean',
|
|
3872
|
+
short: 'v'
|
|
3873
|
+
}
|
|
3830
3874
|
},
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3875
|
+
allowPositionals: true
|
|
3876
|
+
});
|
|
3877
|
+
} catch (error) {
|
|
3878
|
+
fail(`${error instanceof Error ? error.message : String(error)}\n\nRun with --help to see the options.`);
|
|
3879
|
+
}
|
|
3880
|
+
}
|
|
3881
|
+
async function main() {
|
|
3882
|
+
const { values, positionals } = parse();
|
|
3838
3883
|
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');
|
|
3884
|
+
if (values.version) return console.log("1.0.0-rc.3");
|
|
3885
|
+
/*
|
|
3886
|
+
* A pipe, a CI job or an agent gets the defaults rather than a prompt nothing can answer. Both streams
|
|
3887
|
+
* have to be a terminal: the prompts draw on stdout but *read from stdin*, so `echo | npx @rshono/create`
|
|
3888
|
+
* with stdout still attached would otherwise ask a question with nothing behind the keyboard.
|
|
3889
|
+
*/ const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY) && !values.yes;
|
|
3890
|
+
const pmFlag = oneOf(values.pm, PACKAGE_MANAGERS, 'pm');
|
|
3848
3891
|
const pm = pmFlag ? packageManager(pmFlag) : detectPackageManager();
|
|
3849
3892
|
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');
|
|
3893
|
+
const formatterFlag = oneOf(values.formatter, FORMATTER_NAMES, 'formatter');
|
|
3894
|
+
const linterFlag = oneOf(values.linter, LINTER_NAMES, 'linter');
|
|
3862
3895
|
const qualityFlag = oneOf(values.quality, QUALITY_PRESETS.map((preset)=>preset.id), 'quality');
|
|
3863
3896
|
const tailwindFlag = tristate(values.tailwind, values['no-tailwind'], 'tailwind');
|
|
3864
3897
|
const installFlag = tristate(values.install, values['no-install'], 'install');
|
|
3865
3898
|
const gitFlag = tristate(values.git, values['no-git'], 'git');
|
|
3866
|
-
|
|
3899
|
+
// The framework version, not this package's: it is the one the app will be pinned to, and the one
|
|
3900
|
+
// worth reading here. `--version` reports create-rshono's own.
|
|
3901
|
+
intro(`create-rshono · rshono ${RSHONO_VERSION}`);
|
|
3867
3902
|
// ── Where ───────────────────────────────────────────────────────────────────────────────────────
|
|
3868
3903
|
let directory = positionals[0];
|
|
3869
3904
|
if (!directory) {
|
|
@@ -3877,7 +3912,7 @@ async function main() {
|
|
|
3877
3912
|
const targetDir = resolve(process.cwd(), directory);
|
|
3878
3913
|
const packageName = toPackageName(directory === '.' ? basename(targetDir) : directory);
|
|
3879
3914
|
if (!packageName || !isValidPackageName(packageName)) fail(`"${directory}" does not give a usable npm package name.`);
|
|
3880
|
-
const conflicts =
|
|
3915
|
+
const conflicts = conflictingEntries(targetDir);
|
|
3881
3916
|
if (conflicts.length > 0 && !values.force) {
|
|
3882
3917
|
const where = directory === '.' ? 'this directory' : `"${directory}"`;
|
|
3883
3918
|
const listed = `${conflicts.slice(0, 3).join(', ')}${conflicts.length > 3 ? ', …' : ''}`;
|
|
@@ -3903,7 +3938,7 @@ async function main() {
|
|
|
3903
3938
|
}))
|
|
3904
3939
|
}));
|
|
3905
3940
|
}
|
|
3906
|
-
let styling = tailwindFlag
|
|
3941
|
+
let styling = tailwindFlag ? 'tailwind' : 'css';
|
|
3907
3942
|
if (tailwindFlag === undefined && interactive) {
|
|
3908
3943
|
styling = unwrap(await dist_select({
|
|
3909
3944
|
message: 'Styling?',
|
|
@@ -3953,9 +3988,10 @@ async function main() {
|
|
|
3953
3988
|
initialValue: true
|
|
3954
3989
|
}));
|
|
3955
3990
|
}
|
|
3956
|
-
|
|
3991
|
+
// Asked once, not once per use: this shells out to `git rev-parse`.
|
|
3992
|
+
const nested = isInsideRepo(process.cwd());
|
|
3993
|
+
let git = gitFlag ?? !nested;
|
|
3957
3994
|
if (gitFlag === undefined && interactive) {
|
|
3958
|
-
const nested = isInsideRepo(process.cwd());
|
|
3959
3995
|
git = unwrap(await dist_confirm({
|
|
3960
3996
|
message: nested ? 'Initialize a git repository? (this is already inside one)' : 'Initialize a git repository?',
|
|
3961
3997
|
initialValue: !nested
|
|
@@ -3963,14 +3999,10 @@ async function main() {
|
|
|
3963
3999
|
}
|
|
3964
4000
|
const answers = {
|
|
3965
4001
|
packageName,
|
|
3966
|
-
targetDir,
|
|
3967
4002
|
deploy,
|
|
3968
4003
|
styling,
|
|
3969
4004
|
formatter,
|
|
3970
|
-
linter
|
|
3971
|
-
packageManager: pm.name,
|
|
3972
|
-
install,
|
|
3973
|
-
git
|
|
4005
|
+
linter
|
|
3974
4006
|
};
|
|
3975
4007
|
// ── Plan, then write ────────────────────────────────────────────────────────────────────────────
|
|
3976
4008
|
const plan = plan_plan(answers, pm);
|
|
@@ -3987,14 +4019,17 @@ async function main() {
|
|
|
3987
4019
|
if (install) {
|
|
3988
4020
|
log.step(`Installing dependencies with ${pm.name}…`);
|
|
3989
4021
|
installed = runInstall(pm, targetDir);
|
|
3990
|
-
if (!installed) log.warn(`${pm.name} install failed — run it yourself
|
|
4022
|
+
if (!installed) log.warn(`${pm.name} install failed — the files are all written, so run it yourself in the project.`);
|
|
3991
4023
|
}
|
|
3992
4024
|
/*
|
|
3993
4025
|
* Format the scaffold with the tool it was scaffolded with, so a fresh project passes its own
|
|
3994
4026
|
* `format:check` instead of reporting a diff nobody made. Needs the install, since the formatter is a
|
|
3995
4027
|
* devDependency — hence the skip, rather than a failure, when there is none.
|
|
3996
|
-
|
|
3997
|
-
|
|
4028
|
+
*
|
|
4029
|
+
* Whether there is a `format` script is the plan's answer, not the formatter answer's: `--formatter
|
|
4030
|
+
* none --linter biome` has no formatter and a `format` script all the same, because Biome brings one.
|
|
4031
|
+
*/ if (installed && plan.features.some((feature)=>feature.scripts?.format)) {
|
|
4032
|
+
if (!runScript(pm, 'format', targetDir)) log.warn(`\`${pm.run} format\` failed — the scaffold is written, just not formatted.`);
|
|
3998
4033
|
}
|
|
3999
4034
|
if (git) {
|
|
4000
4035
|
if (!hasGit(targetDir)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rshono/create",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.3",
|
|
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",
|
|
@@ -41,19 +41,18 @@
|
|
|
41
41
|
"publishConfig": {
|
|
42
42
|
"access": "public"
|
|
43
43
|
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@clack/prompts": "^1.7.0",
|
|
46
|
+
"@rspack/core": "2.1.5",
|
|
47
|
+
"@types/node": "^26.1.1",
|
|
48
|
+
"typescript": "^7.0.2",
|
|
49
|
+
"@rshono/core": "1.0.0-rc.3"
|
|
50
|
+
},
|
|
44
51
|
"scripts": {
|
|
45
52
|
"build": "node scripts/codegen.mjs && node scripts/build.mjs",
|
|
46
53
|
"clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
47
54
|
"codegen": "node scripts/codegen.mjs",
|
|
48
55
|
"typecheck": "node scripts/codegen.mjs && tsc --noEmit",
|
|
49
|
-
"test": "npm run build && node --test --test-concurrency=1 \"test/*.test.mjs\""
|
|
50
|
-
"prepack": "npm run build"
|
|
51
|
-
},
|
|
52
|
-
"devDependencies": {
|
|
53
|
-
"@clack/prompts": "^1.7.0",
|
|
54
|
-
"@rspack/core": "2.1.5",
|
|
55
|
-
"@types/node": "^26.1.1",
|
|
56
|
-
"@rshono/core": "workspace:*",
|
|
57
|
-
"typescript": "^7.0.2"
|
|
56
|
+
"test": "npm run build && node --test --test-concurrency=1 \"test/*.test.mjs\""
|
|
58
57
|
}
|
|
59
|
-
}
|
|
58
|
+
}
|
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
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
<!-- The rshono mark, as a placeholder — replace it with your own and the tab is yours. -->
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"><title>rshono</title><path fill="#087EA4" d="M12.4365 0.2520325c0.062375 -0.0080215 0.11745 0.0077075 0.16515 0.0471875 1.755075 2.142025 3.40665 4.359855 4.954725 6.65348 1.14615 1.725625 2.121325 3.550175 2.92565 5.4738 1.2845 3.426025 0.616 6.40675 -2.005475 8.9421 -2.293725 1.938575 -4.936225 2.693575 -7.927575 2.265025 -3.57555 -0.729025 -6.00575 -2.7974 -7.290525 -6.205225 -0.33465 -1.109425 -0.44475 -2.241925 -0.3303 -3.397525 0.19055 -1.9891 0.662425 -3.9081 1.415625 -5.7569 0.31385 -0.75435 0.722825 -1.4464 1.2269 -2.076275 0.411225 0.4898 0.80445 0.993175 1.179675 1.510025 0.17375 0.181625 0.354625 0.35465 0.542675 0.51905C8.728325 5.378325 10.44285 2.7201 12.4365 0.2520325Z"/><path fill="#61DAFB" d="M12.10625 4.07425c1.73145 2.008325 3.296525 4.1475 4.695175 6.41755 0.438525 0.75115 0.800275 1.537625 1.085325 2.3594 0.593825 2.336175 -0.043225 4.26305 -1.9111 5.7805 -1.80655 1.2712 -3.788425 1.6487 -5.945675 1.132525 -2.326325 -0.721875 -3.671175 -2.286975 -4.034575 -4.6952 -0.088175 -0.7593 -0.009525 -1.4986 0.23595 -2.217825 0.35005 -0.888875 0.774725 -1.73825 1.274075 -2.54815 0.471875 -0.6921 0.94375 -1.38415 1.415625 -2.07625 1.071925 -1.378375 2.13365 -2.762525 3.1852 -4.15255Z"/></svg>
|
|
@@ -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.
|