@rshono/create 1.0.0-rc.0 → 1.0.0-rc.1
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 +12 -5
- package/dist/api.mjs +95 -4
- package/dist/cli.mjs +96 -6
- package/package.json +1 -1
- package/templates/base/src/actions.ts +4 -1
- package/templates/eslint/eslint.config.mjs +31 -0
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ Six, each with a default, and each answerable by a flag instead:
|
|
|
22
22
|
| Where should it go? | `my-rshono-app` | first positional argument, or `.` |
|
|
23
23
|
| Where is it deployed? | `node` | `--deploy node\|bun\|deno\|cloudflare\|vercel\|netlify\|aws-lambda` |
|
|
24
24
|
| Styling | plain CSS | `--tailwind` / `--no-tailwind` |
|
|
25
|
-
| Formatting & linting | Prettier + oxlint | `--quality prettier-oxlint\|biome\|oxc\|none`
|
|
25
|
+
| Formatting & linting | Prettier + oxlint | `--quality prettier-oxlint\|prettier-eslint\|biome\|oxc\|none` |
|
|
26
26
|
| Install dependencies? | yes | `--no-install` |
|
|
27
27
|
| Initialize git? | yes, unless nested | `--no-git` |
|
|
28
28
|
|
|
@@ -51,6 +51,7 @@ src/actions.ts a 'use server' action, called from a form that works wit
|
|
|
51
51
|
src/components/ layout, home, greet-form ('use client'), 404, 500
|
|
52
52
|
src/lib/env.ts both sides of the PUBLIC_ boundary in one place
|
|
53
53
|
src/styles.css element-level CSS, or the Tailwind entry
|
|
54
|
+
pnpm-workspace.yaml pnpm only: which dependency install scripts this app runs (none of them)
|
|
54
55
|
```
|
|
55
56
|
|
|
56
57
|
Then, if the dependencies were installed, the scaffold is run through its own formatter — so a fresh
|
|
@@ -83,10 +84,16 @@ import { plan, writePlan } from '@rshono/create';
|
|
|
83
84
|
|
|
84
85
|
## Two things worth knowing
|
|
85
86
|
|
|
86
|
-
**
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
**The ESLint preset pins TypeScript 6.** Linting TypeScript with ESLint means `typescript-eslint`, which
|
|
88
|
+
reads the compiler API directly rather than through a stable interface, so its peer range is
|
|
89
|
+
`typescript >=4.8.4 <6.1.0` — below the TypeScript rshono is built and tested against. An app that chooses
|
|
90
|
+
ESLint therefore gets `typescript ~6.0.3`, the newest that range allows: `npm install` would otherwise fail
|
|
91
|
+
outright on the conflict, and forcing past it hands you a linter running against a compiler API it was never
|
|
92
|
+
built for. The framework's declarations compile identically under either version, which is what makes this
|
|
93
|
+
the app's pin and not the framework's — every other preset leaves TypeScript alone. What you trade for
|
|
94
|
+
type-aware rules is a compiler one major behind, and the JavaScript implementation rather than the native
|
|
95
|
+
one, so `typecheck` on a large app is several times slower. When upstream widens the range, the pin in
|
|
96
|
+
`features/quality.ts` is the only thing to delete.
|
|
90
97
|
|
|
91
98
|
**Tailwind is four packages, a `postcss.config.mjs` and one rule in `rshono.config.ts`.** rshono compiles
|
|
92
99
|
CSS natively and has no PostCSS in it — that is deliberate, and it means an app that wants a plugin chain
|
package/dist/api.mjs
CHANGED
|
@@ -31,6 +31,7 @@ var __webpack_exports__ = {};
|
|
|
31
31
|
// EXPORTS
|
|
32
32
|
__webpack_require__.d(__webpack_exports__, {
|
|
33
33
|
bp: () => (/* reexport */ DEPLOY_TARGET_NAMES),
|
|
34
|
+
ml: () => (/* reexport */ (/* inlined export .ESLINT_TYPESCRIPT */"~6.0.3")),
|
|
34
35
|
e7: () => (/* reexport */ FRAMEWORK_DEPS),
|
|
35
36
|
Ab: () => (/* reexport */ PACKAGE_MANAGERS),
|
|
36
37
|
pm: () => (/* reexport */ QUALITY_PRESETS),
|
|
@@ -42,7 +43,7 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
42
43
|
WL: () => (/* reexport */ inspectTarget),
|
|
43
44
|
p1: () => (/* reexport */ isDeployTarget),
|
|
44
45
|
eW: () => (/* reexport */ isValidPackageName),
|
|
45
|
-
|
|
46
|
+
VZ: () => (/* reexport */ packageManager),
|
|
46
47
|
et: () => (/* reexport */ plan_plan),
|
|
47
48
|
N7: () => (/* reexport */ selectFeatures),
|
|
48
49
|
PQ: () => (/* reexport */ toPackageName),
|
|
@@ -78,7 +79,7 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
78
79
|
|
|
79
80
|
;// CONCATENATED MODULE: ./src/generated/framework.ts
|
|
80
81
|
// GENERATED by scripts/codegen.mjs from packages/core — do not edit. Run `pnpm --filter @rshono/create codegen`.
|
|
81
|
-
/** The framework release a scaffolded app is pinned to: this package and rshono ship together. */ const RSHONO_VERSION = '1.0.0-rc.
|
|
82
|
+
/** The framework release a scaffolded app is pinned to: this package and rshono ship together. */ const RSHONO_VERSION = '1.0.0-rc.1';
|
|
82
83
|
/**
|
|
83
84
|
* The dependency versions rshono is tested against, copied from its own manifest. Exact where it is
|
|
84
85
|
* exact — React's RSC internals are coupled across builds, and a generated app has no workspace
|
|
@@ -145,8 +146,26 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
145
146
|
'@biomejs/biome': '^2.5.6',
|
|
146
147
|
oxlint: '^1.76.0',
|
|
147
148
|
oxfmt: '^0.61.0',
|
|
149
|
+
eslint: '^10.8.0',
|
|
150
|
+
// ESLint's own recommended JavaScript rules, which typescript-eslint layers on top of rather than
|
|
151
|
+
// replaces, and the rules of hooks — the one class of React mistake no type checker sees.
|
|
152
|
+
'@eslint/js': '^10.0.1',
|
|
153
|
+
'typescript-eslint': '^8.65.0',
|
|
154
|
+
'eslint-plugin-react-hooks': '^7.1.1',
|
|
148
155
|
wrangler: '^4.115.0'
|
|
149
156
|
};
|
|
157
|
+
/**
|
|
158
|
+
* The TypeScript an ESLint app pins, in place of the framework's own — the one deliberate exception to
|
|
159
|
+
* {@link FRAMEWORK_DEPS}, and the reason it is spelled out here.
|
|
160
|
+
*
|
|
161
|
+
* typescript-eslint reads TypeScript's compiler API directly rather than through a stable interface, so
|
|
162
|
+
* it accepts `typescript >=4.8.4 <6.1.0` and nothing above. `~6.0.3` is the newest that satisfies it:
|
|
163
|
+
* patch releases of 6.0, no minor. The framework itself stays on the TypeScript it is tested against —
|
|
164
|
+
* rshono's declarations compile the same under either, which is what makes this pin an app's business
|
|
165
|
+
* and not the framework's.
|
|
166
|
+
*
|
|
167
|
+
* When upstream widens the range, this constant and the ESLint feature's use of it are what to delete.
|
|
168
|
+
*/ const ESLINT_TYPESCRIPT = '~6.0.3';
|
|
150
169
|
|
|
151
170
|
;// CONCATENATED MODULE: ./src/features/deploy.ts
|
|
152
171
|
|
|
@@ -187,6 +206,11 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
187
206
|
devDependencies: {
|
|
188
207
|
wrangler: TOOL_VERSIONS.wrangler
|
|
189
208
|
},
|
|
209
|
+
// wrangler brings workerd, whose install script only picks the platform binary out of the optional
|
|
210
|
+
// dependency that already carries it — `workerd --version` answers without it having run.
|
|
211
|
+
allowBuilds: {
|
|
212
|
+
workerd: false
|
|
213
|
+
},
|
|
190
214
|
scripts: {
|
|
191
215
|
deploy: 'rshono build && wrangler deploy'
|
|
192
216
|
},
|
|
@@ -277,6 +301,30 @@ const OXLINT = {
|
|
|
277
301
|
'lint:fix': 'oxlint --fix'
|
|
278
302
|
}
|
|
279
303
|
};
|
|
304
|
+
/**
|
|
305
|
+
* The one feature that changes a dependency the framework otherwise decides: typescript-eslint cannot be
|
|
306
|
+
* installed alongside the TypeScript rshono is tested against, so an ESLint app pins the newest one its
|
|
307
|
+
* peer range accepts (see {@link ESLINT_TYPESCRIPT}). Every other preset leaves TypeScript alone.
|
|
308
|
+
*
|
|
309
|
+
* The rules are type-aware, which is the reason to reach for ESLint over a syntax-only linter at all —
|
|
310
|
+
* so the config it ships hands the whole program to the parser rather than linting file by file.
|
|
311
|
+
*/ const ESLINT = {
|
|
312
|
+
id: 'eslint',
|
|
313
|
+
overlays: [
|
|
314
|
+
'eslint'
|
|
315
|
+
],
|
|
316
|
+
devDependencies: {
|
|
317
|
+
eslint: TOOL_VERSIONS.eslint,
|
|
318
|
+
'@eslint/js': TOOL_VERSIONS["@eslint/js"],
|
|
319
|
+
'typescript-eslint': TOOL_VERSIONS["typescript-eslint"],
|
|
320
|
+
'eslint-plugin-react-hooks': TOOL_VERSIONS["eslint-plugin-react-hooks"],
|
|
321
|
+
typescript: (/* inlined export .ESLINT_TYPESCRIPT */"~6.0.3")
|
|
322
|
+
},
|
|
323
|
+
scripts: {
|
|
324
|
+
lint: 'eslint .',
|
|
325
|
+
'lint:fix': 'eslint . --fix'
|
|
326
|
+
}
|
|
327
|
+
};
|
|
280
328
|
const BIOME = {
|
|
281
329
|
id: 'biome',
|
|
282
330
|
overlays: [
|
|
@@ -301,6 +349,7 @@ const FORMATTERS = {
|
|
|
301
349
|
};
|
|
302
350
|
const LINTERS = {
|
|
303
351
|
oxlint: OXLINT,
|
|
352
|
+
eslint: ESLINT,
|
|
304
353
|
biome: BIOME,
|
|
305
354
|
none: null
|
|
306
355
|
};
|
|
@@ -388,6 +437,13 @@ const QUALITY_PRESETS = [
|
|
|
388
437
|
formatter: 'prettier',
|
|
389
438
|
linter: 'oxlint'
|
|
390
439
|
},
|
|
440
|
+
{
|
|
441
|
+
id: 'prettier-eslint',
|
|
442
|
+
label: 'Prettier + ESLint',
|
|
443
|
+
hint: 'type-aware rules — pins TypeScript 6, which is all typescript-eslint accepts',
|
|
444
|
+
formatter: 'prettier',
|
|
445
|
+
linter: 'eslint'
|
|
446
|
+
},
|
|
391
447
|
{
|
|
392
448
|
id: 'biome',
|
|
393
449
|
label: 'Biome',
|
|
@@ -454,6 +510,39 @@ const QUALITY_PRESETS = [
|
|
|
454
510
|
function sorted(record) {
|
|
455
511
|
return Object.fromEntries(Object.entries(record).sort(([a], [b])=>a < b ? -1 : 1));
|
|
456
512
|
}
|
|
513
|
+
/**
|
|
514
|
+
* The install script every app inherits, from the framework rather than from anything it chose. tsx
|
|
515
|
+
* reads `rshono.config.ts` through esbuild, and esbuild's script only picks the platform binary out of
|
|
516
|
+
* the optional dependency that already carries it — rshono's own repo denies it for the same reason.
|
|
517
|
+
*/ const BASE_ALLOW_BUILDS = {
|
|
518
|
+
esbuild: false
|
|
519
|
+
};
|
|
520
|
+
/**
|
|
521
|
+
* pnpm's settings for the new app — written for pnpm and for nobody else.
|
|
522
|
+
*
|
|
523
|
+
* It exists for one field. A dependency with an install script is a question pnpm will not answer on its
|
|
524
|
+
* own: it fails the install, and fails every `pnpm dev` after it, until the project has said whether the
|
|
525
|
+
* script should run. None of the ones an rshono app inherits need to (each is a native package whose
|
|
526
|
+
* binary arrives as an optional dependency), so a fresh app carries the answer rather than meeting
|
|
527
|
+
* `pnpm approve-builds` before it has rendered a page once.
|
|
528
|
+
*
|
|
529
|
+
* In this file rather than under a `pnpm` key in `package.json`, which pnpm 11 no longer reads, single-
|
|
530
|
+
* package projects included. What lands here is a decision about *this* app: a scaffolded file the app
|
|
531
|
+
* owns from then on, not something the framework reaches back into.
|
|
532
|
+
*/ function buildPnpmSettings(features) {
|
|
533
|
+
const allowBuilds = {
|
|
534
|
+
...BASE_ALLOW_BUILDS
|
|
535
|
+
};
|
|
536
|
+
for (const feature of features)Object.assign(allowBuilds, feature.allowBuilds);
|
|
537
|
+
return [
|
|
538
|
+
'# Which dependencies may run an install script. pnpm runs none it has not been told about, and',
|
|
539
|
+
'# fails the install rather than skip one quietly — so anything added later belongs here too.',
|
|
540
|
+
'# `false` means the script was looked at: these ship their real binary as an optional dependency.',
|
|
541
|
+
'allowBuilds:',
|
|
542
|
+
...Object.entries(sorted(allowBuilds)).map(([name, allowed])=>` ${name}: ${allowed}`),
|
|
543
|
+
''
|
|
544
|
+
].join('\n');
|
|
545
|
+
}
|
|
457
546
|
/**
|
|
458
547
|
* Assembles `package.json` from the answers and whatever the selected features contribute.
|
|
459
548
|
*
|
|
@@ -598,6 +687,7 @@ function readTemplateDir(dir) {
|
|
|
598
687
|
const gitignore = files.get('.gitignore');
|
|
599
688
|
if (gitignore) files.set('.gitignore', appendGitignore(gitignore, features));
|
|
600
689
|
files.set('package.json', buildPackageJson(answers, features, pm));
|
|
690
|
+
if (pm.name === 'pnpm') files.set('pnpm-workspace.yaml', buildPnpmSettings(features));
|
|
601
691
|
return {
|
|
602
692
|
// Sorted, so both the write order and a test's snapshot are stable.
|
|
603
693
|
files: new Map([
|
|
@@ -730,6 +820,7 @@ function inspectTarget(dir) {
|
|
|
730
820
|
|
|
731
821
|
|
|
732
822
|
var __webpack_exports__DEPLOY_TARGET_NAMES = __webpack_exports__.bp;
|
|
823
|
+
var __webpack_exports__ESLINT_TYPESCRIPT = __webpack_exports__.ml;
|
|
733
824
|
var __webpack_exports__FRAMEWORK_DEPS = __webpack_exports__.e7;
|
|
734
825
|
var __webpack_exports__PACKAGE_MANAGERS = __webpack_exports__.Ab;
|
|
735
826
|
var __webpack_exports__QUALITY_PRESETS = __webpack_exports__.pm;
|
|
@@ -741,9 +832,9 @@ var __webpack_exports__detectPackageManager = __webpack_exports__.Lv;
|
|
|
741
832
|
var __webpack_exports__inspectTarget = __webpack_exports__.WL;
|
|
742
833
|
var __webpack_exports__isDeployTarget = __webpack_exports__.p1;
|
|
743
834
|
var __webpack_exports__isValidPackageName = __webpack_exports__.eW;
|
|
744
|
-
var __webpack_exports__packageManager = __webpack_exports__.
|
|
835
|
+
var __webpack_exports__packageManager = __webpack_exports__.VZ;
|
|
745
836
|
var __webpack_exports__plan = __webpack_exports__.et;
|
|
746
837
|
var __webpack_exports__selectFeatures = __webpack_exports__.N7;
|
|
747
838
|
var __webpack_exports__toPackageName = __webpack_exports__.PQ;
|
|
748
839
|
var __webpack_exports__writePlan = __webpack_exports__.pD;
|
|
749
|
-
export { __webpack_exports__DEPLOY_TARGET_NAMES as DEPLOY_TARGET_NAMES, __webpack_exports__FRAMEWORK_DEPS as FRAMEWORK_DEPS, __webpack_exports__PACKAGE_MANAGERS as PACKAGE_MANAGERS, __webpack_exports__QUALITY_PRESETS as QUALITY_PRESETS, __webpack_exports__RSHONO_RANGE as RSHONO_RANGE, __webpack_exports__TOOL_VERSIONS as TOOL_VERSIONS, __webpack_exports__buildPackageJson as buildPackageJson, __webpack_exports__deployHint as deployHint, __webpack_exports__detectPackageManager as detectPackageManager, __webpack_exports__inspectTarget as inspectTarget, __webpack_exports__isDeployTarget as isDeployTarget, __webpack_exports__isValidPackageName as isValidPackageName, __webpack_exports__packageManager as packageManager, __webpack_exports__plan as plan, __webpack_exports__selectFeatures as selectFeatures, __webpack_exports__toPackageName as toPackageName, __webpack_exports__writePlan as writePlan };
|
|
840
|
+
export { __webpack_exports__DEPLOY_TARGET_NAMES as DEPLOY_TARGET_NAMES, __webpack_exports__ESLINT_TYPESCRIPT as ESLINT_TYPESCRIPT, __webpack_exports__FRAMEWORK_DEPS as FRAMEWORK_DEPS, __webpack_exports__PACKAGE_MANAGERS as PACKAGE_MANAGERS, __webpack_exports__QUALITY_PRESETS as QUALITY_PRESETS, __webpack_exports__RSHONO_RANGE as RSHONO_RANGE, __webpack_exports__TOOL_VERSIONS as TOOL_VERSIONS, __webpack_exports__buildPackageJson as buildPackageJson, __webpack_exports__deployHint as deployHint, __webpack_exports__detectPackageManager as detectPackageManager, __webpack_exports__inspectTarget as inspectTarget, __webpack_exports__isDeployTarget as isDeployTarget, __webpack_exports__isValidPackageName as isValidPackageName, __webpack_exports__packageManager as packageManager, __webpack_exports__plan as plan, __webpack_exports__selectFeatures as selectFeatures, __webpack_exports__toPackageName as toPackageName, __webpack_exports__writePlan as writePlan };
|
package/dist/cli.mjs
CHANGED
|
@@ -2943,7 +2943,7 @@ 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.1';
|
|
2947
2947
|
/**
|
|
2948
2948
|
* The dependency versions rshono is tested against, copied from its own manifest. Exact where it is
|
|
2949
2949
|
* exact — React's RSC internals are coupled across builds, and a generated app has no workspace
|
|
@@ -3010,6 +3010,13 @@ const QUALITY_PRESETS = [
|
|
|
3010
3010
|
formatter: 'prettier',
|
|
3011
3011
|
linter: 'oxlint'
|
|
3012
3012
|
},
|
|
3013
|
+
{
|
|
3014
|
+
id: 'prettier-eslint',
|
|
3015
|
+
label: 'Prettier + ESLint',
|
|
3016
|
+
hint: 'type-aware rules — pins TypeScript 6, which is all typescript-eslint accepts',
|
|
3017
|
+
formatter: 'prettier',
|
|
3018
|
+
linter: 'eslint'
|
|
3019
|
+
},
|
|
3013
3020
|
{
|
|
3014
3021
|
id: 'biome',
|
|
3015
3022
|
label: 'Biome',
|
|
@@ -3102,8 +3109,26 @@ const QUALITY_PRESETS = [
|
|
|
3102
3109
|
'@biomejs/biome': '^2.5.6',
|
|
3103
3110
|
oxlint: '^1.76.0',
|
|
3104
3111
|
oxfmt: '^0.61.0',
|
|
3112
|
+
eslint: '^10.8.0',
|
|
3113
|
+
// ESLint's own recommended JavaScript rules, which typescript-eslint layers on top of rather than
|
|
3114
|
+
// replaces, and the rules of hooks — the one class of React mistake no type checker sees.
|
|
3115
|
+
'@eslint/js': '^10.0.1',
|
|
3116
|
+
'typescript-eslint': '^8.65.0',
|
|
3117
|
+
'eslint-plugin-react-hooks': '^7.1.1',
|
|
3105
3118
|
wrangler: '^4.115.0'
|
|
3106
3119
|
};
|
|
3120
|
+
/**
|
|
3121
|
+
* The TypeScript an ESLint app pins, in place of the framework's own — the one deliberate exception to
|
|
3122
|
+
* {@link FRAMEWORK_DEPS}, and the reason it is spelled out here.
|
|
3123
|
+
*
|
|
3124
|
+
* typescript-eslint reads TypeScript's compiler API directly rather than through a stable interface, so
|
|
3125
|
+
* it accepts `typescript >=4.8.4 <6.1.0` and nothing above. `~6.0.3` is the newest that satisfies it:
|
|
3126
|
+
* patch releases of 6.0, no minor. The framework itself stays on the TypeScript it is tested against —
|
|
3127
|
+
* rshono's declarations compile the same under either, which is what makes this pin an app's business
|
|
3128
|
+
* and not the framework's.
|
|
3129
|
+
*
|
|
3130
|
+
* When upstream widens the range, this constant and the ESLint feature's use of it are what to delete.
|
|
3131
|
+
*/ const ESLINT_TYPESCRIPT = '~6.0.3';
|
|
3107
3132
|
|
|
3108
3133
|
;// CONCATENATED MODULE: ./src/features/deploy.ts
|
|
3109
3134
|
|
|
@@ -3144,6 +3169,11 @@ const QUALITY_PRESETS = [
|
|
|
3144
3169
|
devDependencies: {
|
|
3145
3170
|
wrangler: TOOL_VERSIONS.wrangler
|
|
3146
3171
|
},
|
|
3172
|
+
// wrangler brings workerd, whose install script only picks the platform binary out of the optional
|
|
3173
|
+
// dependency that already carries it — `workerd --version` answers without it having run.
|
|
3174
|
+
allowBuilds: {
|
|
3175
|
+
workerd: false
|
|
3176
|
+
},
|
|
3147
3177
|
scripts: {
|
|
3148
3178
|
deploy: 'rshono build && wrangler deploy'
|
|
3149
3179
|
},
|
|
@@ -3234,6 +3264,30 @@ const OXLINT = {
|
|
|
3234
3264
|
'lint:fix': 'oxlint --fix'
|
|
3235
3265
|
}
|
|
3236
3266
|
};
|
|
3267
|
+
/**
|
|
3268
|
+
* The one feature that changes a dependency the framework otherwise decides: typescript-eslint cannot be
|
|
3269
|
+
* installed alongside the TypeScript rshono is tested against, so an ESLint app pins the newest one its
|
|
3270
|
+
* peer range accepts (see {@link ESLINT_TYPESCRIPT}). Every other preset leaves TypeScript alone.
|
|
3271
|
+
*
|
|
3272
|
+
* The rules are type-aware, which is the reason to reach for ESLint over a syntax-only linter at all —
|
|
3273
|
+
* so the config it ships hands the whole program to the parser rather than linting file by file.
|
|
3274
|
+
*/ const ESLINT = {
|
|
3275
|
+
id: 'eslint',
|
|
3276
|
+
overlays: [
|
|
3277
|
+
'eslint'
|
|
3278
|
+
],
|
|
3279
|
+
devDependencies: {
|
|
3280
|
+
eslint: TOOL_VERSIONS.eslint,
|
|
3281
|
+
'@eslint/js': TOOL_VERSIONS["@eslint/js"],
|
|
3282
|
+
'typescript-eslint': TOOL_VERSIONS["typescript-eslint"],
|
|
3283
|
+
'eslint-plugin-react-hooks': TOOL_VERSIONS["eslint-plugin-react-hooks"],
|
|
3284
|
+
typescript: (/* inlined export .ESLINT_TYPESCRIPT */"~6.0.3")
|
|
3285
|
+
},
|
|
3286
|
+
scripts: {
|
|
3287
|
+
lint: 'eslint .',
|
|
3288
|
+
'lint:fix': 'eslint . --fix'
|
|
3289
|
+
}
|
|
3290
|
+
};
|
|
3237
3291
|
const BIOME = {
|
|
3238
3292
|
id: 'biome',
|
|
3239
3293
|
overlays: [
|
|
@@ -3258,6 +3312,7 @@ const FORMATTERS = {
|
|
|
3258
3312
|
};
|
|
3259
3313
|
const LINTERS = {
|
|
3260
3314
|
oxlint: OXLINT,
|
|
3315
|
+
eslint: ESLINT,
|
|
3261
3316
|
biome: BIOME,
|
|
3262
3317
|
none: null
|
|
3263
3318
|
};
|
|
@@ -3346,6 +3401,39 @@ function stylingFeature(styling) {
|
|
|
3346
3401
|
function sorted(record) {
|
|
3347
3402
|
return Object.fromEntries(Object.entries(record).sort(([a], [b])=>a < b ? -1 : 1));
|
|
3348
3403
|
}
|
|
3404
|
+
/**
|
|
3405
|
+
* The install script every app inherits, from the framework rather than from anything it chose. tsx
|
|
3406
|
+
* reads `rshono.config.ts` through esbuild, and esbuild's script only picks the platform binary out of
|
|
3407
|
+
* the optional dependency that already carries it — rshono's own repo denies it for the same reason.
|
|
3408
|
+
*/ const BASE_ALLOW_BUILDS = {
|
|
3409
|
+
esbuild: false
|
|
3410
|
+
};
|
|
3411
|
+
/**
|
|
3412
|
+
* pnpm's settings for the new app — written for pnpm and for nobody else.
|
|
3413
|
+
*
|
|
3414
|
+
* It exists for one field. A dependency with an install script is a question pnpm will not answer on its
|
|
3415
|
+
* own: it fails the install, and fails every `pnpm dev` after it, until the project has said whether the
|
|
3416
|
+
* script should run. None of the ones an rshono app inherits need to (each is a native package whose
|
|
3417
|
+
* binary arrives as an optional dependency), so a fresh app carries the answer rather than meeting
|
|
3418
|
+
* `pnpm approve-builds` before it has rendered a page once.
|
|
3419
|
+
*
|
|
3420
|
+
* In this file rather than under a `pnpm` key in `package.json`, which pnpm 11 no longer reads, single-
|
|
3421
|
+
* package projects included. What lands here is a decision about *this* app: a scaffolded file the app
|
|
3422
|
+
* owns from then on, not something the framework reaches back into.
|
|
3423
|
+
*/ function buildPnpmSettings(features) {
|
|
3424
|
+
const allowBuilds = {
|
|
3425
|
+
...BASE_ALLOW_BUILDS
|
|
3426
|
+
};
|
|
3427
|
+
for (const feature of features)Object.assign(allowBuilds, feature.allowBuilds);
|
|
3428
|
+
return [
|
|
3429
|
+
'# Which dependencies may run an install script. pnpm runs none it has not been told about, and',
|
|
3430
|
+
'# fails the install rather than skip one quietly — so anything added later belongs here too.',
|
|
3431
|
+
'# `false` means the script was looked at: these ship their real binary as an optional dependency.',
|
|
3432
|
+
'allowBuilds:',
|
|
3433
|
+
...Object.entries(sorted(allowBuilds)).map(([name, allowed])=>` ${name}: ${allowed}`),
|
|
3434
|
+
''
|
|
3435
|
+
].join('\n');
|
|
3436
|
+
}
|
|
3349
3437
|
/**
|
|
3350
3438
|
* Assembles `package.json` from the answers and whatever the selected features contribute.
|
|
3351
3439
|
*
|
|
@@ -3486,6 +3574,7 @@ function readTemplateDir(dir) {
|
|
|
3486
3574
|
const gitignore = files.get('.gitignore');
|
|
3487
3575
|
if (gitignore) files.set('.gitignore', appendGitignore(gitignore, features));
|
|
3488
3576
|
files.set('package.json', buildPackageJson(answers, features, pm));
|
|
3577
|
+
if (pm.name === 'pnpm') files.set('pnpm-workspace.yaml', buildPnpmSettings(features));
|
|
3489
3578
|
return {
|
|
3490
3579
|
// Sorted, so both the write order and a test's snapshot are stable.
|
|
3491
3580
|
files: new Map([
|
|
@@ -3658,9 +3747,9 @@ Options:
|
|
|
3658
3747
|
-d, --deploy <target> ${DEPLOY_TARGET_NAMES.join(' | ')}
|
|
3659
3748
|
--tailwind Tailwind CSS (--no-tailwind for plain CSS)
|
|
3660
3749
|
--quality <preset> ${QUALITY_PRESETS.map((preset)=>preset.id).join(' | ')}
|
|
3661
|
-
--formatter <name> prettier | biome | oxfmt | none
|
|
3662
|
-
--linter <name> oxlint | biome | none
|
|
3663
|
-
--pm <name> npm | pnpm | yarn | bun
|
|
3750
|
+
--formatter <name> prettier | biome | oxfmt | none (overrides --quality)
|
|
3751
|
+
--linter <name> oxlint | eslint | biome | none (overrides --quality; eslint pins TypeScript 6)
|
|
3752
|
+
--pm <name> npm | pnpm | yarn | bun (default: whatever ran this)
|
|
3664
3753
|
--no-install write the files and stop
|
|
3665
3754
|
--no-git do not initialize a repository
|
|
3666
3755
|
--force scaffold into a directory that is not empty
|
|
@@ -3747,7 +3836,7 @@ async function main() {
|
|
|
3747
3836
|
allowPositionals: true
|
|
3748
3837
|
});
|
|
3749
3838
|
if (values.help) return console.log(HELP);
|
|
3750
|
-
if (values.version) return console.log("1.0.0-rc.
|
|
3839
|
+
if (values.version) return console.log("1.0.0-rc.1");
|
|
3751
3840
|
// A pipe, a CI job or an agent gets the defaults rather than a prompt nothing can answer.
|
|
3752
3841
|
const interactive = Boolean(process.stdout.isTTY) && !values.yes;
|
|
3753
3842
|
const pmFlag = oneOf(values.pm, [
|
|
@@ -3766,6 +3855,7 @@ async function main() {
|
|
|
3766
3855
|
], 'formatter');
|
|
3767
3856
|
const linterFlag = oneOf(values.linter, [
|
|
3768
3857
|
'oxlint',
|
|
3858
|
+
'eslint',
|
|
3769
3859
|
'biome',
|
|
3770
3860
|
'none'
|
|
3771
3861
|
], 'linter');
|
|
@@ -3773,7 +3863,7 @@ async function main() {
|
|
|
3773
3863
|
const tailwindFlag = tristate(values.tailwind, values['no-tailwind'], 'tailwind');
|
|
3774
3864
|
const installFlag = tristate(values.install, values['no-install'], 'install');
|
|
3775
3865
|
const gitFlag = tristate(values.git, values['no-git'], 'git');
|
|
3776
|
-
intro(`create-rshono · rshono ${"1.0.0-rc.
|
|
3866
|
+
intro(`create-rshono · rshono ${"1.0.0-rc.1"}`);
|
|
3777
3867
|
// ── Where ───────────────────────────────────────────────────────────────────────────────────────
|
|
3778
3868
|
let directory = positionals[0];
|
|
3779
3869
|
if (!directory) {
|
package/package.json
CHANGED
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
* The signature is the one `useActionState` expects: previous state first, then the form data.
|
|
10
10
|
*/
|
|
11
11
|
export async function greet(_previous: string | null, formData: FormData): Promise<string> {
|
|
12
|
-
|
|
12
|
+
// A form field is a string or a File, never only a string — a file input posted under this name would
|
|
13
|
+
// stringify to "[object File]" rather than fail. Narrowing is the validation the endpoint owes itself.
|
|
14
|
+
const field = formData.get('name');
|
|
15
|
+
const name = typeof field === 'string' ? field.trim() : '';
|
|
13
16
|
if (!name) return 'Type a name first.';
|
|
14
17
|
|
|
15
18
|
// Where real work goes — a database write, an email, a queue push.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import js from '@eslint/js';
|
|
2
|
+
import reactHooks from 'eslint-plugin-react-hooks';
|
|
3
|
+
import tseslint from 'typescript-eslint';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Flat config, with type-aware rules — the reason to run ESLint over a syntax-only linter. `projectService`
|
|
7
|
+
* builds the same program `tsc` does from tsconfig.json, so a rule can ask what a value actually *is*:
|
|
8
|
+
* an unawaited promise, a `catch` that swallows an error, a `String()` around something that is not one.
|
|
9
|
+
*
|
|
10
|
+
* The cost is that ESLint needs TypeScript to answer, which is why this app pins TypeScript 6 —
|
|
11
|
+
* typescript-eslint reads the compiler API directly and accepts nothing newer. `npm run typecheck` is
|
|
12
|
+
* still the thing that decides whether the app compiles; these rules only see what it sees.
|
|
13
|
+
*/
|
|
14
|
+
export default tseslint.config(
|
|
15
|
+
// ESLint's own default ignores cover node_modules and nothing else, so the build output — and whatever
|
|
16
|
+
// the deploy target leaves beside it — would otherwise be linted as if you had written it.
|
|
17
|
+
{ ignores: ['dist/**', '.wrangler/**', '.vercel/**', '.netlify/**'] },
|
|
18
|
+
js.configs.recommended,
|
|
19
|
+
tseslint.configs.recommendedTypeChecked,
|
|
20
|
+
// The rules of hooks: the one class of React mistake no type checker catches, and the reason a React
|
|
21
|
+
// app wants a linter at all.
|
|
22
|
+
reactHooks.configs.flat['recommended-latest'],
|
|
23
|
+
{
|
|
24
|
+
languageOptions: {
|
|
25
|
+
parserOptions: { projectService: true, tsconfigRootDir: import.meta.dirname },
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
// This file and any other plain JavaScript sits outside the TypeScript program, so the type-aware rules
|
|
29
|
+
// have nothing to run against and would report every file as unconfigured.
|
|
30
|
+
{ files: ['**/*.{js,mjs,cjs}'], extends: [tseslint.configs.disableTypeChecked] },
|
|
31
|
+
);
|