create-rsc-kit 0.6.0 → 0.7.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/dist/index.js +97 -5
- package/dist/options.d.ts +1 -1
- package/dist/options.js +16 -4
- package/dist/templates.js +3 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// several things in it that are invisible when wrong: NODE_ENV on the build,
|
|
6
6
|
// @source for Tailwind, the ambient declaration for the generated engine
|
|
7
7
|
// bundle. Each of those fails by producing a page that looks nearly right.
|
|
8
|
-
import { existsSync, mkdirSync, readdirSync, writeFileSync } from 'node:fs';
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
9
9
|
import { dirname, join, resolve } from 'node:path';
|
|
10
10
|
import { fileURLToPath } from 'node:url';
|
|
11
11
|
import { spawnSync } from 'node:child_process';
|
|
@@ -13,7 +13,39 @@ import { argv, exit, stdout } from 'node:process';
|
|
|
13
13
|
import { DEFAULT_COMPILER, HELP, HOSTS, publishedCore, assertInsideCwd, assertUsableName, defaultCore, parseArgs, } from './options.js';
|
|
14
14
|
import { Prompter, bold, cyan, dim } from './prompt.js';
|
|
15
15
|
import * as t from './templates.js';
|
|
16
|
-
|
|
16
|
+
// Same treatment as a refusal from write(): a bad flag is a decision this tool
|
|
17
|
+
// made, and it happens before the try/catch below because the flags are what
|
|
18
|
+
// everything else is built from.
|
|
19
|
+
/**
|
|
20
|
+
* This package's own version, printed so a stale one is visible.
|
|
21
|
+
*
|
|
22
|
+
* `bun create rsc-kit` is `bunx create-rsc-kit`, and bunx caches its
|
|
23
|
+
* resolution of `latest` under that key — so a machine that ran it once keeps
|
|
24
|
+
* scaffolding from whatever it downloaded then, however many releases ago.
|
|
25
|
+
* There is nothing this package can do about that, but there is something it
|
|
26
|
+
* can do about the symptom, which was a prompt offering hosts that had been
|
|
27
|
+
* removed two releases earlier and no way to tell why.
|
|
28
|
+
*
|
|
29
|
+
* Read rather than baked in: the version is only written at publish time.
|
|
30
|
+
*/
|
|
31
|
+
function selfVersion() {
|
|
32
|
+
try {
|
|
33
|
+
const path = join(dirname(fileURLToPath(import.meta.url)), '../package.json');
|
|
34
|
+
return `v${JSON.parse(readFileSync(path, 'utf-8')).version}`;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const flags = (() => {
|
|
41
|
+
try {
|
|
42
|
+
return parseArgs(argv.slice(2));
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
stdout.write(`\n${bold('Cannot scaffold here.')}\n ${error.message}\n\n`);
|
|
46
|
+
exit(1);
|
|
47
|
+
}
|
|
48
|
+
})();
|
|
17
49
|
if (flags.help) {
|
|
18
50
|
stdout.write(HELP);
|
|
19
51
|
exit(0);
|
|
@@ -21,6 +53,8 @@ if (flags.help) {
|
|
|
21
53
|
const unattended = flags.yes === true || flags.host !== undefined;
|
|
22
54
|
// Resolved once: it walks up from this file looking for the sibling package.
|
|
23
55
|
const core = flags.core ?? defaultCore(dirname(fileURLToPath(import.meta.url)));
|
|
56
|
+
// Started before the first question and never awaited here — see notifyIfStale.
|
|
57
|
+
const staleCheck = checkForNewer();
|
|
24
58
|
const options = await collect();
|
|
25
59
|
try {
|
|
26
60
|
write(options);
|
|
@@ -47,6 +81,7 @@ if (options.install) {
|
|
|
47
81
|
}
|
|
48
82
|
}
|
|
49
83
|
report(options);
|
|
84
|
+
await notifyIfStale();
|
|
50
85
|
// ── ─────────────────────────────────────────────────────────────────────────
|
|
51
86
|
/** Declared, not assigned: collect() runs at module top level, above this. */
|
|
52
87
|
function basename(path) {
|
|
@@ -77,11 +112,11 @@ async function collect() {
|
|
|
77
112
|
HELP);
|
|
78
113
|
exit(1);
|
|
79
114
|
}
|
|
80
|
-
stdout.write(`\n${bold('Create an RSC app')}\n\n`);
|
|
115
|
+
stdout.write(`\n${bold('Create an RSC app')} ${dim(selfVersion())}\n\n`);
|
|
81
116
|
const p = new Prompter();
|
|
82
117
|
try {
|
|
83
118
|
const dir = flags.dir ?? (await p.text('Directory', 'my-app'));
|
|
84
|
-
const host = await p.select('
|
|
119
|
+
const host = await p.select('Where will it run', HOSTS);
|
|
85
120
|
// Yes/no, not which: see DEFAULT_COMPILER.
|
|
86
121
|
const compiler = flags.compiler ?? ((await p.confirm('React Compiler', true)) ? DEFAULT_COMPILER : 'none');
|
|
87
122
|
const tailwind = flags.tailwind ?? (await p.confirm('Tailwind CSS', true));
|
|
@@ -165,6 +200,8 @@ function report(o) {
|
|
|
165
200
|
const steps = [
|
|
166
201
|
`cd ${relativeish(o.dir)}`,
|
|
167
202
|
...(o.install ? [] : [o.host === 'node' ? 'npm install' : 'bun install']),
|
|
203
|
+
// dev first: it is what someone wants next, and it needs no build.
|
|
204
|
+
`${pm} dev`,
|
|
168
205
|
`${pm} build`,
|
|
169
206
|
// A Worker has no start: wrangler runs it on workerd, or deploys it.
|
|
170
207
|
...(o.host === 'worker' ? [`${pm} preview`, `${pm} deploy`] : [`${pm} start`]),
|
|
@@ -172,7 +209,62 @@ function report(o) {
|
|
|
172
209
|
stdout.write(`\n${bold('Done.')} ${dim(o.dir)}\n\n`);
|
|
173
210
|
for (const step of steps)
|
|
174
211
|
stdout.write(` ${cyan(step)}\n`);
|
|
175
|
-
stdout.write(`\n${dim('
|
|
212
|
+
stdout.write(`\n${dim('Pages live in src/app. Where it deploys is the Nitro preset in vite.config.ts.')}\n\n`);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Ask the registry what `latest` is, so a stale copy can say so.
|
|
216
|
+
*
|
|
217
|
+
* Printing the version alone does not help: nobody knows 0.5.0 is two behind
|
|
218
|
+
* unless something tells them.
|
|
219
|
+
*
|
|
220
|
+
* `bun create rsc-kit` is `bunx create-rsc-kit`, and bunx reuses what it
|
|
221
|
+
* downloaded the first time — a machine that ran this once keeps scaffolding
|
|
222
|
+
* from that copy however many releases later. Writing the tag out is what
|
|
223
|
+
* makes it revalidate. Measured against a 0.6.0 planted in the cache:
|
|
224
|
+
*
|
|
225
|
+
* bun create rsc-kit stale
|
|
226
|
+
* bunx create-rsc-kit stale
|
|
227
|
+
* bun create rsc-kit@latest fresh
|
|
228
|
+
* bunx create-rsc-kit@latest fresh
|
|
229
|
+
* npx create-rsc-kit@latest fresh
|
|
230
|
+
*
|
|
231
|
+
* So the escape is one word, and this is what tells someone to use it — the
|
|
232
|
+
* form without it is the one every set of install instructions writes.
|
|
233
|
+
*
|
|
234
|
+
* create-next-app does this with the `update-check` package. Not here: this
|
|
235
|
+
* package is installed by bunx before it can ask anything, so a dependency is
|
|
236
|
+
* latency every user waits through — the same reason the prompts are written
|
|
237
|
+
* against readline. It is one request.
|
|
238
|
+
*
|
|
239
|
+
* Started before the first prompt and read after the last, so it costs nothing
|
|
240
|
+
* — a person answering questions is slower than a registry. Failure is
|
|
241
|
+
* silence: offline, behind a proxy, or a registry that is down are all just no
|
|
242
|
+
* notice rather than a scaffolder that will not run.
|
|
243
|
+
*/
|
|
244
|
+
function checkForNewer() {
|
|
245
|
+
// Unknown is not stale. selfVersion() answers '' when it cannot read the
|
|
246
|
+
// manifest, and comparing against that would report every run as behind.
|
|
247
|
+
const mine = selfVersion().slice(1);
|
|
248
|
+
if (!mine)
|
|
249
|
+
return Promise.resolve(null);
|
|
250
|
+
return (async () => {
|
|
251
|
+
const response = await fetch('https://registry.npmjs.org/create-rsc-kit/latest', {
|
|
252
|
+
signal: AbortSignal.timeout(3000),
|
|
253
|
+
headers: { accept: 'application/json' },
|
|
254
|
+
});
|
|
255
|
+
if (!response.ok)
|
|
256
|
+
return null;
|
|
257
|
+
const latest = (await response.json()).version;
|
|
258
|
+
return latest && latest !== mine ? latest : null;
|
|
259
|
+
})().catch(() => null);
|
|
260
|
+
}
|
|
261
|
+
async function notifyIfStale() {
|
|
262
|
+
const latest = await staleCheck;
|
|
263
|
+
if (!latest)
|
|
264
|
+
return;
|
|
265
|
+
stdout.write(`${dim('A newer create-rsc-kit is out:')} ${bold(`v${latest}`)}${dim(`, and this is ${selfVersion()}.`)}\n` +
|
|
266
|
+
`${dim('bunx reuses the copy it downloaded first. Name the tag and it fetches:')}\n\n` +
|
|
267
|
+
` ${cyan('bun create rsc-kit@latest')}\n\n`);
|
|
176
268
|
}
|
|
177
269
|
/** A path the user can paste, when it is under where they are. */
|
|
178
270
|
function relativeish(dir) {
|
package/dist/options.d.ts
CHANGED
|
@@ -93,4 +93,4 @@ export declare function assertUsableName(name: string): void;
|
|
|
93
93
|
* generator nobody can run without reading it first.
|
|
94
94
|
*/
|
|
95
95
|
export declare function assertInsideCwd(dir: string, cwd: string): void;
|
|
96
|
-
export declare const HELP = "\n create-rsc-kit \u2014 scaffold an RSC app\n\n Usage\n create-rsc-kit <dir> [options]\n bun create rsc-kit <dir> [options]
|
|
96
|
+
export declare const HELP = "\n create-rsc-kit \u2014 scaffold an RSC app\n\n Usage\n create-rsc-kit <dir> [options]\n bun create rsc-kit@latest <dir> [options]\n\n Options\n --host=bun|node|worker where it runs \u2014 picks the Nitro preset\n --compiler=none|oxc|babel React Compiler (prompt offers oxc; babel by flag)\n --tailwind / --no-tailwind include Tailwind\n --lint / --no-lint include oxlint\n --source-dir <dir> where app/ lives (default: src)\n --init add to the project here, rather than scaffold\n --core=<spec> engine dependency, e.g. file:../rsc-kit/packages/core\n --no-install skip installing dependencies\n --no-git skip git init\n -y, --yes accept every default, ask nothing\n -h, --help this\n";
|
package/dist/options.js
CHANGED
|
@@ -12,8 +12,20 @@ import { fileURLToPath } from 'node:url';
|
|
|
12
12
|
export const HOSTS = [
|
|
13
13
|
{ value: 'bun', label: 'Bun', hint: 'and compiles to a single binary' },
|
|
14
14
|
{ value: 'node', label: 'Node', hint: 'the default everywhere else' },
|
|
15
|
-
{ value: 'worker', label: 'Cloudflare Workers', hint: '
|
|
15
|
+
{ value: 'worker', label: 'Cloudflare Workers', hint: 'deployed with wrangler, not started' },
|
|
16
16
|
];
|
|
17
|
+
/**
|
|
18
|
+
* `--host` reaches `preset()`, which has a fallback branch — so an unknown
|
|
19
|
+
* value would not fail, it would quietly scaffold a Bun app. The names that
|
|
20
|
+
* used to be valid here (`hono`, `elysia`) are exactly the ones someone still
|
|
21
|
+
* has in a script, so being told is the point.
|
|
22
|
+
*/
|
|
23
|
+
function assertHost(value) {
|
|
24
|
+
if (value === 'laravel' || HOSTS.some((host) => host.value === value))
|
|
25
|
+
return value;
|
|
26
|
+
throw new Error(`Unknown --host=${value}. Pick one of: ${HOSTS.map((host) => host.value).join(', ')}. ` +
|
|
27
|
+
'Anywhere else is a Nitro preset in vite.config.ts, not a flag here.');
|
|
28
|
+
}
|
|
17
29
|
/**
|
|
18
30
|
* Not asked as a three-way.
|
|
19
31
|
*
|
|
@@ -117,7 +129,7 @@ export function parseArgs(argv) {
|
|
|
117
129
|
else if (arg === '--no-tailwind')
|
|
118
130
|
out.tailwind = false;
|
|
119
131
|
else if (arg.startsWith('--host='))
|
|
120
|
-
out.host = arg.slice(7);
|
|
132
|
+
out.host = assertHost(arg.slice(7));
|
|
121
133
|
else if (arg.startsWith('--compiler='))
|
|
122
134
|
out.compiler = arg.slice(11);
|
|
123
135
|
else if (arg.startsWith('--core='))
|
|
@@ -164,10 +176,10 @@ export const HELP = `
|
|
|
164
176
|
|
|
165
177
|
Usage
|
|
166
178
|
create-rsc-kit <dir> [options]
|
|
167
|
-
bun create rsc-kit <dir> [options]
|
|
179
|
+
bun create rsc-kit@latest <dir> [options]
|
|
168
180
|
|
|
169
181
|
Options
|
|
170
|
-
--host=bun|
|
|
182
|
+
--host=bun|node|worker where it runs — picks the Nitro preset
|
|
171
183
|
--compiler=none|oxc|babel React Compiler (prompt offers oxc; babel by flag)
|
|
172
184
|
--tailwind / --no-tailwind include Tailwind
|
|
173
185
|
--lint / --no-lint include oxlint
|
package/dist/templates.js
CHANGED
|
@@ -175,9 +175,11 @@ export function viteConfig(o) {
|
|
|
175
175
|
// and rscKit tells plugin-rsc not to install a handler competing for that
|
|
176
176
|
// role. serveStatic: 'inline' embeds the built assets, without which a
|
|
177
177
|
// compiled binary serves pages and 404s every asset.
|
|
178
|
+
//
|
|
179
|
+
// Not optional, and not a flag on rscKit() either — the plugin builds for
|
|
180
|
+
// Nitro and nothing else, so a config without this line has no server.
|
|
178
181
|
plugins.push(`nitro({ preset: ${JSON.stringify(preset(o.host))}, serveStatic: 'inline' })`);
|
|
179
182
|
plugins.push(`rscKit({
|
|
180
|
-
nitro: true,
|
|
181
183
|
${options.join(',\n ')},
|
|
182
184
|
})`);
|
|
183
185
|
plugins.push(o.compiler === 'oxc' ? 'react({ compiler: true })' : 'react()');
|