create-rsc-kit 0.13.1 → 0.15.0
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 +2 -0
- package/dist/init.js +3 -0
- package/dist/templates.d.ts +15 -0
- package/dist/templates.js +95 -10
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -140,9 +140,11 @@ function write(o) {
|
|
|
140
140
|
['.gitignore', t.gitignore],
|
|
141
141
|
['README.md', t.readme(o)],
|
|
142
142
|
['AGENTS.md', t.agents(o)],
|
|
143
|
+
['.mcp.json', t.mcp()],
|
|
143
144
|
['src/app/layout.tsx', t.layout(o)],
|
|
144
145
|
['src/app/page.tsx', t.page(o)],
|
|
145
146
|
['src/components/Counter.tsx', t.counter(o)],
|
|
147
|
+
['tests/app.test.ts', t.smokeTest(o)],
|
|
146
148
|
];
|
|
147
149
|
if (o.tailwind)
|
|
148
150
|
files.push(['src/app/styles.css', t.styles]);
|
package/dist/init.js
CHANGED
|
@@ -265,9 +265,12 @@ function routes(o, dir) {
|
|
|
265
265
|
// file of someone else's instructions is not one to append to blind, and
|
|
266
266
|
// the loop below skips it if it is already there.
|
|
267
267
|
['AGENTS.md', t.agents(o)],
|
|
268
|
+
['.mcp.json', t.mcp()],
|
|
268
269
|
];
|
|
269
270
|
if (o.tailwind)
|
|
270
271
|
files.push([join(o.sourceDir, 'app/styles.css'), t.styles]);
|
|
272
|
+
if (o.host !== 'laravel')
|
|
273
|
+
files.push(['tests/app.test.ts', t.smokeTest(o)]);
|
|
271
274
|
for (const [path, contents] of files) {
|
|
272
275
|
const full = join(dir, path);
|
|
273
276
|
if (existsSync(full)) {
|
package/dist/templates.d.ts
CHANGED
|
@@ -57,6 +57,14 @@ export declare function viteConfig(o: Options): string;
|
|
|
57
57
|
export declare const WRANGLER_FILE = "wrangler.toml";
|
|
58
58
|
export declare const tsconfig: (o: Options) => string;
|
|
59
59
|
export declare function layout(o: Options): string;
|
|
60
|
+
/**
|
|
61
|
+
* A test that goes through the real build, so there is one to extend.
|
|
62
|
+
*
|
|
63
|
+
* Without it, "add a test" starts with choosing a runner and a way to reach
|
|
64
|
+
* the app, and what gets chosen is a port, a spawned server, and a sleep. This
|
|
65
|
+
* is the shape instead: the deployed handler, a Request in, a Response out.
|
|
66
|
+
*/
|
|
67
|
+
export declare function smokeTest(o: Options): string;
|
|
60
68
|
export declare function page(o: Options): string;
|
|
61
69
|
export declare function counter(o: Options): string;
|
|
62
70
|
/**
|
|
@@ -96,4 +104,11 @@ export declare function oxlintConfig(o: Options): string;
|
|
|
96
104
|
* reads it too.
|
|
97
105
|
*/
|
|
98
106
|
export declare function agents(o: Options): string;
|
|
107
|
+
/**
|
|
108
|
+
* Project-scoped MCP config, which Claude Code reads from the project root
|
|
109
|
+
* and asks the user to approve on first use. Nothing is installed: npx fetches
|
|
110
|
+
* the server the first time an agent starts it. Other clients want the same
|
|
111
|
+
* four lines in their own file.
|
|
112
|
+
*/
|
|
113
|
+
export declare function mcp(): string;
|
|
99
114
|
export declare function readme(o: Options): string;
|
package/dist/templates.js
CHANGED
|
@@ -95,6 +95,18 @@ export function scripts(o) {
|
|
|
95
95
|
}),
|
|
96
96
|
typecheck: 'tsc --noEmit',
|
|
97
97
|
...(o.lint ? { lint: 'oxlint src --fix', 'lint:check': 'oxlint src --deny-warnings' } : {}),
|
|
98
|
+
// Laravel returned above: its pages call into PHP that createTestApp does
|
|
99
|
+
// not run, and its tests are Pest, on the other side.
|
|
100
|
+
...testScripts(o),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function testScripts(o) {
|
|
104
|
+
const test = o.host === 'node' ? 'node --test "tests/**/*.test.ts"' : 'bun test tests';
|
|
105
|
+
return {
|
|
106
|
+
test,
|
|
107
|
+
// The one command an agent runs before saying it is done. Each part exists
|
|
108
|
+
// on its own; this is so nothing has to remember the list.
|
|
109
|
+
check: ['tsc --noEmit', ...(o.lint ? ['oxlint src --deny-warnings'] : []), test].join(' && '),
|
|
98
110
|
};
|
|
99
111
|
}
|
|
100
112
|
export function packageJson(o) {
|
|
@@ -146,13 +158,7 @@ export function packageJson(o) {
|
|
|
146
158
|
name: o.name,
|
|
147
159
|
type: 'module',
|
|
148
160
|
private: true,
|
|
149
|
-
scripts:
|
|
150
|
-
...scripts(o),
|
|
151
|
-
typecheck: 'tsc --noEmit',
|
|
152
|
-
...(o.lint
|
|
153
|
-
? { lint: 'oxlint src --fix', 'lint:check': 'oxlint src --deny-warnings' }
|
|
154
|
-
: {}),
|
|
155
|
-
},
|
|
161
|
+
scripts: scripts(o),
|
|
156
162
|
dependencies: sorted(deps),
|
|
157
163
|
devDependencies: sorted(dev),
|
|
158
164
|
}, null, 2) + '\n');
|
|
@@ -269,7 +275,7 @@ export const tsconfig = (o) => JSON.stringify({
|
|
|
269
275
|
// .rsc-kit holds the generated ambient declarations. Ambient means
|
|
270
276
|
// inside the project, and `include` is what decides that — leave it out
|
|
271
277
|
// and typed routes silently fall back to string.
|
|
272
|
-
include: [`${o.sourceDir}/**/*`, '.rsc-kit/**/*'],
|
|
278
|
+
include: [`${o.sourceDir}/**/*`, 'tests/**/*', '.rsc-kit/**/*'],
|
|
273
279
|
}, null, 2) + '\n';
|
|
274
280
|
export function layout(o) {
|
|
275
281
|
return `${o.tailwind ? "import './styles.css'\n" : ''}import type { ReactNode } from 'react'
|
|
@@ -296,6 +302,54 @@ export default function RootLayout({ children }: { children: ReactNode }) {
|
|
|
296
302
|
}
|
|
297
303
|
`;
|
|
298
304
|
}
|
|
305
|
+
/**
|
|
306
|
+
* A test that goes through the real build, so there is one to extend.
|
|
307
|
+
*
|
|
308
|
+
* Without it, "add a test" starts with choosing a runner and a way to reach
|
|
309
|
+
* the app, and what gets chosen is a port, a spawned server, and a sleep. This
|
|
310
|
+
* is the shape instead: the deployed handler, a Request in, a Response out.
|
|
311
|
+
*/
|
|
312
|
+
export function smokeTest(o) {
|
|
313
|
+
const runner = o.host === 'node'
|
|
314
|
+
? `import { before, describe, test } from 'node:test'
|
|
315
|
+
import assert from 'node:assert/strict'`
|
|
316
|
+
: `import { beforeAll, describe, expect, test } from 'bun:test'`;
|
|
317
|
+
const setup = o.host === 'node' ? 'before' : 'beforeAll';
|
|
318
|
+
const ok = o.host === 'node'
|
|
319
|
+
? ` assert.equal(res.status, 200)
|
|
320
|
+
assert.match(await res.text(), /${o.name}/)`
|
|
321
|
+
: ` expect(res.status).toBe(200)
|
|
322
|
+
expect(await res.text()).toContain('${o.name}')`;
|
|
323
|
+
const missing = o.host === 'node'
|
|
324
|
+
? ` assert.equal((await app.fetch('/no-such-page')).status, 404)`
|
|
325
|
+
: ` expect((await app.fetch('/no-such-page')).status).toBe(404)`;
|
|
326
|
+
return `// The whole app as it is deployed, without a port or a browser.
|
|
327
|
+
//
|
|
328
|
+
// createTestApp builds when the source is newer than the last build and hands
|
|
329
|
+
// back the same Request → Response handler the server runs: the real router,
|
|
330
|
+
// the real middleware, the real api routes, the pages the build stored.
|
|
331
|
+
${runner}
|
|
332
|
+
import { createTestApp } from '@rsc-kit/core/testing'
|
|
333
|
+
|
|
334
|
+
let app: Awaited<ReturnType<typeof createTestApp>>
|
|
335
|
+
|
|
336
|
+
${setup}(async () => {
|
|
337
|
+
app = await createTestApp()
|
|
338
|
+
}${o.host === 'node' ? '' : ', 120_000'})
|
|
339
|
+
|
|
340
|
+
describe('the app', () => {
|
|
341
|
+
test('serves the home page', async () => {
|
|
342
|
+
const res = await app.fetch('/')
|
|
343
|
+
|
|
344
|
+
${ok}
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
test('answers a url that matches nothing with a 404', async () => {
|
|
348
|
+
${missing}
|
|
349
|
+
})
|
|
350
|
+
})
|
|
351
|
+
`;
|
|
352
|
+
}
|
|
299
353
|
export function page(o) {
|
|
300
354
|
const h1 = o.tailwind ? ' className="text-3xl font-bold"' : '';
|
|
301
355
|
const p = o.tailwind ? ' className="mt-4 text-slate-600"' : '';
|
|
@@ -456,14 +510,32 @@ generates it.
|
|
|
456
510
|
Read the guides at https://rsc-kit.dev before reaching for a pattern from
|
|
457
511
|
another framework. The notes below are only the things most often got wrong.
|
|
458
512
|
|
|
513
|
+
The \`rsc-kit\` MCP server in \`.mcp.json\` answers from this project's last
|
|
514
|
+
build — which routes froze and why, what is heaviest — and has the long-form
|
|
515
|
+
recipe for anything here (\`how_to\`). Ask it before guessing.
|
|
516
|
+
|
|
459
517
|
## Commands
|
|
460
518
|
|
|
461
519
|
\`\`\`sh
|
|
462
520
|
${pm} dev # vite, with the engine in it
|
|
463
521
|
${pm} build # builds and prerenders; prints what it froze
|
|
464
|
-
${pm} typecheck
|
|
522
|
+
${o.host === 'laravel' ? `${pm} typecheck` : `${pm} check # typecheck, lint and tests — run before saying it is done`}
|
|
465
523
|
\`\`\`
|
|
466
|
-
|
|
524
|
+
${o.host === 'laravel'
|
|
525
|
+
? ''
|
|
526
|
+
: `
|
|
527
|
+
Tests live in \`tests/\` and go through the real build: \`createTestApp()\` from
|
|
528
|
+
\`@rsc-kit/core/testing\` hands back the deployed Request → Response handler,
|
|
529
|
+
so a test fetches a url and reads the response. Extend \`tests/app.test.ts\`;
|
|
530
|
+
do not add a runner, a port or a spawned server. Actions, queries and api
|
|
531
|
+
routes are plain functions and can also be called directly.
|
|
532
|
+
|
|
533
|
+
**A change is not done without its test.** A guarded route gets a test that a
|
|
534
|
+
stranger is turned away; an action gets a test of its refusal, and one that
|
|
535
|
+
someone else's id is refused; an api route gets its 4xx. The exact shapes are
|
|
536
|
+
in \`how_to({ topic: 'testing' })\`. Then \`${pm} check\`. Nothing here needs
|
|
537
|
+
the app running - the build and the tests are the verification.
|
|
538
|
+
`}
|
|
467
539
|
**Read the build output.** It is not decoration — it says which routes were
|
|
468
540
|
stored, which render per request, and why:
|
|
469
541
|
|
|
@@ -597,6 +669,19 @@ from the request is answered from disk.
|
|
|
597
669
|
promises already cross that boundary.
|
|
598
670
|
`;
|
|
599
671
|
}
|
|
672
|
+
/**
|
|
673
|
+
* Project-scoped MCP config, which Claude Code reads from the project root
|
|
674
|
+
* and asks the user to approve on first use. Nothing is installed: npx fetches
|
|
675
|
+
* the server the first time an agent starts it. Other clients want the same
|
|
676
|
+
* four lines in their own file.
|
|
677
|
+
*/
|
|
678
|
+
export function mcp() {
|
|
679
|
+
return (JSON.stringify({
|
|
680
|
+
mcpServers: {
|
|
681
|
+
'rsc-kit': { command: 'npx', args: ['-y', '@rsc-kit/mcp'] },
|
|
682
|
+
},
|
|
683
|
+
}, null, 2) + '\n');
|
|
684
|
+
}
|
|
600
685
|
export function readme(o) {
|
|
601
686
|
const pm = o.host === 'node' ? 'npm run' : 'bun run';
|
|
602
687
|
const runtime = o.host === 'worker' ? 'Cloudflare Workers' : o.host === 'node' ? 'Node' : 'Bun';
|