create-rsc-kit 0.18.0 → 0.19.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 +8 -1
- package/dist/init.js +98 -17
- package/dist/templates.d.ts +13 -1
- package/dist/templates.js +62 -8
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -152,12 +152,19 @@ function write(o) {
|
|
|
152
152
|
['.gitignore', t.gitignore],
|
|
153
153
|
['README.md', t.readme(o)],
|
|
154
154
|
['AGENTS.md', t.agents(o)],
|
|
155
|
-
['.mcp.json', t.mcp()],
|
|
155
|
+
['.mcp.json', t.mcp(o)],
|
|
156
156
|
['src/app/layout.tsx', t.layout(o)],
|
|
157
157
|
['src/app/page.tsx', t.page(o)],
|
|
158
158
|
['src/components/Counter.tsx', t.counter(o)],
|
|
159
159
|
['tests/app.test.ts', t.smokeTest(o)],
|
|
160
160
|
];
|
|
161
|
+
// Bun's test runner reads the real server-only package, which throws on
|
|
162
|
+
// import; a preload stubs it so an action file that carries the line is
|
|
163
|
+
// still a function a test can call.
|
|
164
|
+
if (o.host !== 'node') {
|
|
165
|
+
files.push(['bunfig.toml', t.bunfig]);
|
|
166
|
+
files.push(['tests/preload.ts', t.testPreload]);
|
|
167
|
+
}
|
|
161
168
|
if (o.tailwind)
|
|
162
169
|
files.push(['src/app/styles.css', t.styles]);
|
|
163
170
|
if (o.env) {
|
package/dist/init.js
CHANGED
|
@@ -182,6 +182,59 @@ function combine(name, theirs, ours) {
|
|
|
182
182
|
* that quietly replaces a working build script loses someone's trust
|
|
183
183
|
* permanently, and it only has to be wrong once.
|
|
184
184
|
*/
|
|
185
|
+
/** Where this tool's section of an AGENTS.md begins and ends, so a second run finds it. */
|
|
186
|
+
const AGENTS_START = '<!-- rsc-kit:start -->';
|
|
187
|
+
const AGENTS_END = '<!-- rsc-kit:end -->';
|
|
188
|
+
/**
|
|
189
|
+
* This tool's instructions, added to an existing AGENTS.md or written as one.
|
|
190
|
+
*
|
|
191
|
+
* Someone else's instructions are not a file to rewrite, but a delimited
|
|
192
|
+
* section at the end is not a rewrite: theirs stay exactly as written above
|
|
193
|
+
* it, the markers say what is ours, and a second run finds the markers and
|
|
194
|
+
* leaves it be. A project that already has an AGENTS.md is the project whose
|
|
195
|
+
* agents most need to hear how this works.
|
|
196
|
+
*/
|
|
197
|
+
function mergeAgents(dir, o) {
|
|
198
|
+
const path = join(dir, 'AGENTS.md');
|
|
199
|
+
if (!existsSync(path)) {
|
|
200
|
+
writeFileSync(path, t.agents(o));
|
|
201
|
+
return { kind: 'wrote', what: 'AGENTS.md' };
|
|
202
|
+
}
|
|
203
|
+
const existing = readFileSync(path, 'utf8');
|
|
204
|
+
if (existing.includes(AGENTS_START) || /rsc-kit/i.test(existing)) {
|
|
205
|
+
return { kind: 'skipped', what: 'AGENTS.md', detail: 'already covers rsc-kit' };
|
|
206
|
+
}
|
|
207
|
+
writeFileSync(path, existing.replace(/\s*$/, '\n\n') + `${AGENTS_START}\n${t.agents(o).trim()}\n${AGENTS_END}\n`);
|
|
208
|
+
return { kind: 'merged', what: 'AGENTS.md', detail: 'added a section at the end; yours is untouched above it' };
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* The rsc-kit server, added to an existing .mcp.json or written as a new one.
|
|
212
|
+
*
|
|
213
|
+
* The file is a map of servers under `mcpServers`; a project with other
|
|
214
|
+
* servers keeps them, and one that already lists rsc-kit is left alone. A
|
|
215
|
+
* file that does not parse is not one to rewrite.
|
|
216
|
+
*/
|
|
217
|
+
function mergeMcp(dir, o) {
|
|
218
|
+
const path = join(dir, '.mcp.json');
|
|
219
|
+
if (!existsSync(path)) {
|
|
220
|
+
writeFileSync(path, t.mcp(o));
|
|
221
|
+
return { kind: 'wrote', what: '.mcp.json' };
|
|
222
|
+
}
|
|
223
|
+
let existing;
|
|
224
|
+
try {
|
|
225
|
+
existing = JSON.parse(readFileSync(path, 'utf8'));
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
return { kind: 'skipped', what: '.mcp.json', detail: 'already exists and is not JSON; add the rsc-kit server by hand' };
|
|
229
|
+
}
|
|
230
|
+
if (existing.mcpServers?.['rsc-kit']) {
|
|
231
|
+
return { kind: 'skipped', what: '.mcp.json', detail: 'already lists rsc-kit' };
|
|
232
|
+
}
|
|
233
|
+
const ours = JSON.parse(t.mcp(o)).mcpServers['rsc-kit'];
|
|
234
|
+
existing.mcpServers = { ...(existing.mcpServers ?? {}), 'rsc-kit': ours };
|
|
235
|
+
writeFileSync(path, JSON.stringify(existing, null, 2) + '\n');
|
|
236
|
+
return { kind: 'merged', what: '.mcp.json', detail: 'added the rsc-kit server beside the others' };
|
|
237
|
+
}
|
|
185
238
|
function mergeScripts(o, found) {
|
|
186
239
|
const pkg = found.packageJson;
|
|
187
240
|
const scripts = pkg.scripts ?? {};
|
|
@@ -315,18 +368,17 @@ function manualPluginStep(o, existing) {
|
|
|
315
368
|
function routes(o, dir) {
|
|
316
369
|
const appDir = join(dir, o.sourceDir, 'app');
|
|
317
370
|
const steps = [];
|
|
371
|
+
// The two files beside the tree are looked at whether or not the tree is
|
|
372
|
+
// here: a project that already has pages is the one whose agents and
|
|
373
|
+
// editor most need to know about this.
|
|
374
|
+
steps.push(mergeAgents(dir, o), mergeMcp(dir, o));
|
|
318
375
|
if (existsSync(join(appDir, 'layout.tsx')) || existsSync(join(appDir, 'page.tsx'))) {
|
|
319
|
-
return [{ kind: 'skipped', what: `${o.sourceDir}/app`, detail: 'a route tree is already here' }];
|
|
376
|
+
return [...steps, { kind: 'skipped', what: `${o.sourceDir}/app`, detail: 'a route tree is already here' }];
|
|
320
377
|
}
|
|
321
378
|
const files = [
|
|
322
379
|
[join(o.sourceDir, 'app/layout.tsx'), t.layout(o)],
|
|
323
380
|
[join(o.sourceDir, 'app/page.tsx'), t.page(o)],
|
|
324
381
|
[join(o.sourceDir, 'components/Counter.tsx'), t.counter(o)],
|
|
325
|
-
// Beside the route tree rather than merged into an existing AGENTS.md: a
|
|
326
|
-
// file of someone else's instructions is not one to append to blind, and
|
|
327
|
-
// the loop below skips it if it is already there.
|
|
328
|
-
['AGENTS.md', t.agents(o)],
|
|
329
|
-
['.mcp.json', t.mcp()],
|
|
330
382
|
];
|
|
331
383
|
if (o.tailwind)
|
|
332
384
|
files.push([join(o.sourceDir, 'app/styles.css'), t.styles]);
|
|
@@ -401,10 +453,10 @@ function tsconfig(o, found, dir) {
|
|
|
401
453
|
writeFileSync(path, t.tsconfig(o));
|
|
402
454
|
return [{ kind: 'wrote', what: 'tsconfig.json' }];
|
|
403
455
|
}
|
|
404
|
-
//
|
|
405
|
-
//
|
|
406
|
-
//
|
|
407
|
-
//
|
|
456
|
+
// Edited in place, never reprinted: parsing and printing the file would
|
|
457
|
+
// lose the comments a tsconfig is allowed to have and the formatting
|
|
458
|
+
// someone chose. The entry goes in as text, at the front of the include
|
|
459
|
+
// list that is there, or as a list of its own after the opening brace.
|
|
408
460
|
const current = readFileSync(path, 'utf-8');
|
|
409
461
|
// Comments are legal here and JSON.parse does not take them. Stripped
|
|
410
462
|
// outside strings only: a glob like ".rsc-kit/**/*" holds "/*", and a
|
|
@@ -428,13 +480,25 @@ function tsconfig(o, found, dir) {
|
|
|
428
480
|
// with a dot is outside it. So both cases need the entry, and a project with
|
|
429
481
|
// no `include` needs `**\/*` written alongside it or it loses everything
|
|
430
482
|
// else. Measured both ways.
|
|
483
|
+
const opened = /"include"\s*:\s*\[/.exec(current);
|
|
484
|
+
if (include && opened) {
|
|
485
|
+
const at = opened.index + opened[0].length;
|
|
486
|
+
const rest = current.slice(at);
|
|
487
|
+
// The list's own style: one entry per line, or all on one.
|
|
488
|
+
const separator = /^\s*\n/.test(rest) ? rest.match(/^\s*\n(\s*)/)[0] : ' ';
|
|
489
|
+
writeFileSync(path, current.slice(0, at) + `${separator}"${TYPES_GLOB}",` + (separator === ' ' ? ' ' : '') + rest.replace(/^\s*\n/, ''));
|
|
490
|
+
return [{ kind: 'merged', what: 'tsconfig.json', detail: `added "${TYPES_GLOB}" to "include"` }];
|
|
491
|
+
}
|
|
492
|
+
const brace = current.indexOf('{');
|
|
493
|
+
if (!include && brace !== -1) {
|
|
494
|
+
writeFileSync(path, current.slice(0, brace + 1) + `\n "include": ["**/*", "${TYPES_GLOB}"],` + current.slice(brace + 1));
|
|
495
|
+
return [{ kind: 'merged', what: 'tsconfig.json', detail: `added "include": ["**/*", "${TYPES_GLOB}"]` }];
|
|
496
|
+
}
|
|
431
497
|
return [
|
|
432
498
|
{
|
|
433
499
|
kind: 'manual',
|
|
434
500
|
what: 'tsconfig.json',
|
|
435
|
-
detail: include
|
|
436
|
-
? `add "${TYPES_GLOB}" to "include", or typed routes fall back to string`
|
|
437
|
-
: `add "include": ["**/*", "${TYPES_GLOB}"], or typed routes fall back to string`,
|
|
501
|
+
detail: `add "${TYPES_GLOB}" to "include", or typed routes fall back to string`,
|
|
438
502
|
},
|
|
439
503
|
];
|
|
440
504
|
}
|
|
@@ -483,7 +547,23 @@ function backend(o, found, dir) {
|
|
|
483
547
|
const steps = [];
|
|
484
548
|
const env = join(dir, '.env');
|
|
485
549
|
if (existsSync(env)) {
|
|
486
|
-
|
|
550
|
+
// The two lines, added to what is there. A secret already present is
|
|
551
|
+
// the one the backend was given and is never regenerated; a backend
|
|
552
|
+
// already named is left as named.
|
|
553
|
+
const current = readFileSync(env, 'utf8');
|
|
554
|
+
const missing = [];
|
|
555
|
+
if (!/^\s*RSC_BACKEND=/m.test(current))
|
|
556
|
+
missing.push(`RSC_BACKEND=${o.backend}`);
|
|
557
|
+
if (!/^\s*RSC_HOST_CALL_SECRET=/m.test(current)) {
|
|
558
|
+
missing.push(`RSC_HOST_CALL_SECRET=${randomBytes(32).toString('base64url')}`);
|
|
559
|
+
}
|
|
560
|
+
if (missing.length === 0) {
|
|
561
|
+
steps.push({ kind: 'skipped', what: '.env', detail: 'already has RSC_BACKEND and RSC_HOST_CALL_SECRET' });
|
|
562
|
+
}
|
|
563
|
+
else {
|
|
564
|
+
writeFileSync(env, current.replace(/\s*$/, '\n\n') + missing.join('\n') + '\n');
|
|
565
|
+
steps.push({ kind: 'merged', what: '.env', detail: `added ${missing.map((line) => line.split('=')[0]).join(' and ')}` });
|
|
566
|
+
}
|
|
487
567
|
}
|
|
488
568
|
else {
|
|
489
569
|
writeFileSync(env, t.backendEnv(o.backend, randomBytes(32).toString('base64url')));
|
|
@@ -515,9 +595,10 @@ const DEFAULT_BACKEND = 'http://127.0.0.1:8080';
|
|
|
515
595
|
const INIT_HELP = `
|
|
516
596
|
rsc-kit init — add RSC to the project in this directory
|
|
517
597
|
|
|
518
|
-
Nothing existing is
|
|
519
|
-
|
|
520
|
-
|
|
598
|
+
Nothing existing is rewritten. New files are written; a file that is a list
|
|
599
|
+
gets our entry added to it - .mcp.json, AGENTS.md, tsconfig.json's include,
|
|
600
|
+
.env - with yours left as written; and for anything else already there the
|
|
601
|
+
exact edit is printed for you to make.
|
|
521
602
|
|
|
522
603
|
Options
|
|
523
604
|
--source-dir <dir> where app/ should live (detected, usually src)
|
package/dist/templates.d.ts
CHANGED
|
@@ -66,6 +66,18 @@ export declare function layout(o: Options): string;
|
|
|
66
66
|
* the app, and what gets chosen is a port, a spawned server, and a sleep. This
|
|
67
67
|
* is the shape instead: the deployed handler, a Request in, a Response out.
|
|
68
68
|
*/
|
|
69
|
+
/** Bun's test settings: the preload below, before every test file. */
|
|
70
|
+
export declare const bunfig = "[test]\npreload = [\"./tests/preload.ts\"]\n";
|
|
71
|
+
/**
|
|
72
|
+
* What every test file sees first.
|
|
73
|
+
*
|
|
74
|
+
* `import 'server-only'` is honoured by the build - a client file importing
|
|
75
|
+
* the module fails to build rather than shipping a secret - and resolves to
|
|
76
|
+
* nothing on the server. Under bun test it is the real package, which throws
|
|
77
|
+
* on import, so an action or a route that carries the line would not be a
|
|
78
|
+
* function a test can call. Stubbed here, once.
|
|
79
|
+
*/
|
|
80
|
+
export declare const testPreload = "import { mock } from 'bun:test'\n\n// The build honours this import and resolves it to nothing on the server;\n// the real package throws when imported, which is what a test would hit.\nmock.module('server-only', () => ({}))\nmock.module('client-only', () => ({}))\n";
|
|
69
81
|
export declare function smokeTest(o: Options): string;
|
|
70
82
|
export declare function page(o: Options): string;
|
|
71
83
|
export declare function counter(o: Options): string;
|
|
@@ -112,7 +124,7 @@ export declare function agents(o: Options): string;
|
|
|
112
124
|
* the server the first time an agent starts it. Other clients want the same
|
|
113
125
|
* four lines in their own file.
|
|
114
126
|
*/
|
|
115
|
-
export declare function mcp(): string;
|
|
127
|
+
export declare function mcp(o?: Options): string;
|
|
116
128
|
export declare function readme(o: Options): string;
|
|
117
129
|
/**
|
|
118
130
|
* Typed environment variables, read once at startup.
|
package/dist/templates.js
CHANGED
|
@@ -61,9 +61,16 @@ export function scripts(o) {
|
|
|
61
61
|
start: 'bun .output/server/index.mjs',
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
|
+
// On Bun, Vite itself runs on Bun. `vite` is a bin with a node shebang, so
|
|
65
|
+
// `bun run dev` alone still started it under Node - and a project that
|
|
66
|
+
// imports `bun` or `bun:sqlite` failed at the first render with "Cannot
|
|
67
|
+
// find package 'bun'", in a scaffold that had just said it was a Bun app.
|
|
68
|
+
// `bun --bun` runs the bin on Bun's runtime; the dev server, the build and
|
|
69
|
+
// the prerender then see the same runtime the server will.
|
|
70
|
+
const vite = o.host === 'bun' ? 'bun --bun vite' : 'vite';
|
|
64
71
|
return {
|
|
65
|
-
dev:
|
|
66
|
-
build:
|
|
72
|
+
dev: vite,
|
|
73
|
+
build: `${vite} build`,
|
|
67
74
|
// The two that produce something you ship build first, rather than reading
|
|
68
75
|
// whatever .output happens to hold. Run on a project that has never been
|
|
69
76
|
// built, they failed with `ENOENT opening root directory ".output/server"`
|
|
@@ -85,12 +92,17 @@ export function scripts(o) {
|
|
|
85
92
|
// every asset — the static path resolves into Bun's virtual
|
|
86
93
|
// filesystem, where the files on disk are not.
|
|
87
94
|
//
|
|
95
|
+
// compile.mjs rather than index.mjs: the build writes it beside the
|
|
96
|
+
// server, and it is what puts the frozen pages inside the binary -
|
|
97
|
+
// imported by name, which a compile embeds, where the server's own
|
|
98
|
+
// computed import is invisible to it.
|
|
99
|
+
//
|
|
88
100
|
// Into dist/, which is already ignored. Named after the project it
|
|
89
101
|
// landed a 63MB binary in the root of, next to the source, with
|
|
90
102
|
// nothing in .gitignore covering it.
|
|
91
103
|
...(o.host === 'bun'
|
|
92
104
|
? {
|
|
93
|
-
compile:
|
|
105
|
+
compile: `${vite} build && bun build --compile .output/server/compile.mjs --outfile dist/app`,
|
|
94
106
|
}
|
|
95
107
|
: {}),
|
|
96
108
|
}),
|
|
@@ -319,6 +331,26 @@ export default function RootLayout({ children }: { children: ReactNode }) {
|
|
|
319
331
|
* the app, and what gets chosen is a port, a spawned server, and a sleep. This
|
|
320
332
|
* is the shape instead: the deployed handler, a Request in, a Response out.
|
|
321
333
|
*/
|
|
334
|
+
/** Bun's test settings: the preload below, before every test file. */
|
|
335
|
+
export const bunfig = `[test]
|
|
336
|
+
preload = ["./tests/preload.ts"]
|
|
337
|
+
`;
|
|
338
|
+
/**
|
|
339
|
+
* What every test file sees first.
|
|
340
|
+
*
|
|
341
|
+
* `import 'server-only'` is honoured by the build - a client file importing
|
|
342
|
+
* the module fails to build rather than shipping a secret - and resolves to
|
|
343
|
+
* nothing on the server. Under bun test it is the real package, which throws
|
|
344
|
+
* on import, so an action or a route that carries the line would not be a
|
|
345
|
+
* function a test can call. Stubbed here, once.
|
|
346
|
+
*/
|
|
347
|
+
export const testPreload = `import { mock } from 'bun:test'
|
|
348
|
+
|
|
349
|
+
// The build honours this import and resolves it to nothing on the server;
|
|
350
|
+
// the real package throws when imported, which is what a test would hit.
|
|
351
|
+
mock.module('server-only', () => ({}))
|
|
352
|
+
mock.module('client-only', () => ({}))
|
|
353
|
+
`;
|
|
322
354
|
export function smokeTest(o) {
|
|
323
355
|
const runner = o.host === 'node'
|
|
324
356
|
? `import { before, describe, test } from 'node:test'
|
|
@@ -584,6 +616,11 @@ export async function createPost(input) { … } // callable from a client comp
|
|
|
584
616
|
Do not put \`"use server"\` at the top of a page to make it a server component.
|
|
585
617
|
It already is one.
|
|
586
618
|
|
|
619
|
+
A callback that a timer, a subscription or a listener calls and that must see
|
|
620
|
+
the latest props is \`useEffectEvent\` from React, not a ref you assign every
|
|
621
|
+
render. An Effect Event is never a dependency: leave it out of the array. The
|
|
622
|
+
engine's own hooks are written this way.
|
|
623
|
+
|
|
587
624
|
## Reading the request
|
|
588
625
|
|
|
589
626
|
\`cookies()\`, \`headers()\` and \`searchParams()\` come from
|
|
@@ -606,6 +643,13 @@ it before any page. ${o.env
|
|
|
606
643
|
render waits for it). Do not import a bootstrap module from pages to get the
|
|
607
644
|
same effect; it depends on nobody forgetting.
|
|
608
645
|
|
|
646
|
+
## Forms
|
|
647
|
+
|
|
648
|
+
Uncontrolled. Inputs keep their value in the DOM, an initial value is
|
|
649
|
+
\`defaultValue\`, and the action reads \`FormData\`. Do not write \`useState\` +
|
|
650
|
+
\`value\`/\`onChange\` per input. Control one field only when the UI must react
|
|
651
|
+
as the user types, and bind that one with \`useField\`.
|
|
652
|
+
|
|
609
653
|
## Data
|
|
610
654
|
|
|
611
655
|
Fetch in a server component and await it. There is no loader and no
|
|
@@ -679,7 +723,8 @@ not hand-parse \`Number(searchParams.get('page'))\`.
|
|
|
679
723
|
|
|
680
724
|
\`${o.sourceDir}/app/**/route.ts\`, exporting \`GET\`, \`POST\` and so on.
|
|
681
725
|
A real \`Request\` in, a real \`Response\` out. Await \`params\`,
|
|
682
|
-
\`searchParams\` and \`body\` from the second argument
|
|
726
|
+
\`searchParams\` and \`body\` from the second argument - never
|
|
727
|
+
\`new URL(request.url).searchParams\`, which the build cannot see.
|
|
683
728
|
|
|
684
729
|
They run their directory's \`middleware.ts\`, and a \`GET\` that reads nothing
|
|
685
730
|
from the request is answered from disk.
|
|
@@ -700,10 +745,15 @@ from the request is answered from disk.
|
|
|
700
745
|
* the server the first time an agent starts it. Other clients want the same
|
|
701
746
|
* four lines in their own file.
|
|
702
747
|
*/
|
|
703
|
-
export function mcp() {
|
|
748
|
+
export function mcp(o) {
|
|
749
|
+
// bunx on a Bun project, npx elsewhere: the same server either way, but an
|
|
750
|
+
// editor launching it should not need npm on a machine that has Bun.
|
|
751
|
+
const launcher = o && o.host !== 'node'
|
|
752
|
+
? { command: 'bunx', args: ['@rsc-kit/mcp'] }
|
|
753
|
+
: { command: 'npx', args: ['-y', '@rsc-kit/mcp'] };
|
|
704
754
|
return (JSON.stringify({
|
|
705
755
|
mcpServers: {
|
|
706
|
-
'rsc-kit':
|
|
756
|
+
'rsc-kit': launcher,
|
|
707
757
|
},
|
|
708
758
|
}, null, 2) + '\n');
|
|
709
759
|
}
|
|
@@ -803,6 +853,8 @@ import { createEnv } from '@t3-oss/env-core'
|
|
|
803
853
|
// to start with PUBLIC_, and is read from import.meta.env, which is what Vite
|
|
804
854
|
// exposes there. Add a variable: one line in the schema, and every reader is
|
|
805
855
|
// typed.
|
|
856
|
+
const processEnv: Record<string, string | undefined> = typeof process === 'undefined' ? {} : process.env
|
|
857
|
+
|
|
806
858
|
export const env = createEnv({
|
|
807
859
|
server: {
|
|
808
860
|
NODE_ENV: ${lib.opt},
|
|
@@ -813,11 +865,13 @@ export const env = createEnv({
|
|
|
813
865
|
client: {
|
|
814
866
|
// PUBLIC_SITE_URL: ${lib.url},
|
|
815
867
|
},
|
|
816
|
-
|
|
868
|
+
// process is the server's; a "use client" file importing this for a
|
|
869
|
+
// PUBLIC_ value has only import.meta.env, and Vite fills the PUBLIC_ ones.
|
|
870
|
+
runtimeEnv: { ...processEnv, ...import.meta.env },
|
|
817
871
|
emptyStringAsUndefined: true,
|
|
818
872
|
// A build machine without the production variables: SKIP_ENV_VALIDATION=1
|
|
819
873
|
// builds anyway, and the server that runs the build validates at startup.
|
|
820
|
-
skipValidation: !!
|
|
874
|
+
skipValidation: !!processEnv.SKIP_ENV_VALIDATION,
|
|
821
875
|
})
|
|
822
876
|
`;
|
|
823
877
|
}
|