create-rivet 0.0.0-stage → 0.1.0-alpha.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/LICENSE +21 -0
- package/README.md +45 -2
- package/cli.d.ts +2 -0
- package/cli.js +10 -0
- package/deploy.d.ts +32 -0
- package/deploy.js +99 -0
- package/index.d.ts +118 -0
- package/index.js +184 -0
- package/main.d.ts +9 -0
- package/main.js +169 -0
- package/package.json +57 -4
- package/prompt.d.ts +24 -0
- package/prompt.js +127 -0
- package/templates/starter-minimal/.agents/skills/rivet-add-route/LICENSE.txt +21 -0
- package/templates/starter-minimal/.agents/skills/rivet-add-route/SKILL.md +36 -0
- package/templates/starter-minimal/.agents/skills/rivet-add-ui-page/LICENSE.txt +21 -0
- package/templates/starter-minimal/.agents/skills/rivet-add-ui-page/SKILL.md +42 -0
- package/templates/starter-minimal/.agents/skills/rivet-fix-resumability-error/LICENSE.txt +21 -0
- package/templates/starter-minimal/.agents/skills/rivet-fix-resumability-error/SKILL.md +26 -0
- package/templates/starter-minimal/.agents/skills/rivet-form-in-drawer/LICENSE.txt +21 -0
- package/templates/starter-minimal/.agents/skills/rivet-form-in-drawer/SKILL.md +62 -0
- package/templates/starter-minimal/.agents/skills/rivet-port-react-component/LICENSE.txt +21 -0
- package/templates/starter-minimal/.agents/skills/rivet-port-react-component/SKILL.md +36 -0
- package/templates/starter-minimal/.agents/skills/rivet-ui-design/LICENSE.txt +21 -0
- package/templates/starter-minimal/.agents/skills/rivet-ui-design/SKILL.md +41 -0
- package/templates/starter-minimal/.agents/skills/rivet-ui-design/references/component-composition.md +55 -0
- package/templates/starter-minimal/.agents/skills/rivet-ui-design/references/interactions.md +49 -0
- package/templates/starter-minimal/.agents/skills/rivet-ui-design/references/rivet-implementation.md +66 -0
- package/templates/starter-minimal/.agents/skills/rivet-ui-design/references/structure-and-layout.md +51 -0
- package/templates/starter-minimal/.agents/skills/rivet-ui-design/references/visual-foundations.md +64 -0
- package/templates/starter-minimal/.claude/skills/rivet-add-route/LICENSE.txt +21 -0
- package/templates/starter-minimal/.claude/skills/rivet-add-route/SKILL.md +36 -0
- package/templates/starter-minimal/.claude/skills/rivet-add-ui-page/LICENSE.txt +21 -0
- package/templates/starter-minimal/.claude/skills/rivet-add-ui-page/SKILL.md +42 -0
- package/templates/starter-minimal/.claude/skills/rivet-fix-resumability-error/LICENSE.txt +21 -0
- package/templates/starter-minimal/.claude/skills/rivet-fix-resumability-error/SKILL.md +26 -0
- package/templates/starter-minimal/.claude/skills/rivet-form-in-drawer/LICENSE.txt +21 -0
- package/templates/starter-minimal/.claude/skills/rivet-form-in-drawer/SKILL.md +62 -0
- package/templates/starter-minimal/.claude/skills/rivet-port-react-component/LICENSE.txt +21 -0
- package/templates/starter-minimal/.claude/skills/rivet-port-react-component/SKILL.md +36 -0
- package/templates/starter-minimal/.claude/skills/rivet-ui-design/LICENSE.txt +21 -0
- package/templates/starter-minimal/.claude/skills/rivet-ui-design/SKILL.md +41 -0
- package/templates/starter-minimal/.claude/skills/rivet-ui-design/references/component-composition.md +55 -0
- package/templates/starter-minimal/.claude/skills/rivet-ui-design/references/interactions.md +49 -0
- package/templates/starter-minimal/.claude/skills/rivet-ui-design/references/rivet-implementation.md +66 -0
- package/templates/starter-minimal/.claude/skills/rivet-ui-design/references/structure-and-layout.md +51 -0
- package/templates/starter-minimal/.claude/skills/rivet-ui-design/references/visual-foundations.md +64 -0
- package/templates/starter-minimal/.cursor/rules/rivet-add-route.mdc +37 -0
- package/templates/starter-minimal/.cursor/rules/rivet-add-ui-page.mdc +43 -0
- package/templates/starter-minimal/.cursor/rules/rivet-fix-resumability-error.mdc +27 -0
- package/templates/starter-minimal/.cursor/rules/rivet-form-in-drawer.mdc +63 -0
- package/templates/starter-minimal/.cursor/rules/rivet-port-react-component.mdc +37 -0
- package/templates/starter-minimal/.cursor/rules/rivet-ui-design.mdc +42 -0
- package/templates/starter-minimal/.cursor/rules/rivet.mdc +27 -0
- package/templates/starter-minimal/.github/copilot-instructions.md +21 -0
- package/templates/starter-minimal/AGENTS.md +457 -0
- package/templates/starter-minimal/CLAUDE.md +457 -0
- package/templates/starter-minimal/README.md +26 -0
- package/templates/starter-minimal/_gitignore +3 -0
- package/templates/starter-minimal/package.json +28 -0
- package/templates/starter-minimal/prerender.js +5 -0
- package/templates/starter-minimal/src/document.ts +14 -0
- package/templates/starter-minimal/src/routes/index.tsx +16 -0
- package/templates/starter-minimal/tsconfig.json +21 -0
- package/templates/starter-minimal/vite.config.ts +4 -0
- package/templates/starter-server/.agents/skills/rivet-add-route/LICENSE.txt +21 -0
- package/templates/starter-server/.agents/skills/rivet-add-route/SKILL.md +36 -0
- package/templates/starter-server/.agents/skills/rivet-add-ui-page/LICENSE.txt +21 -0
- package/templates/starter-server/.agents/skills/rivet-add-ui-page/SKILL.md +42 -0
- package/templates/starter-server/.agents/skills/rivet-fix-resumability-error/LICENSE.txt +21 -0
- package/templates/starter-server/.agents/skills/rivet-fix-resumability-error/SKILL.md +26 -0
- package/templates/starter-server/.agents/skills/rivet-form-in-drawer/LICENSE.txt +21 -0
- package/templates/starter-server/.agents/skills/rivet-form-in-drawer/SKILL.md +62 -0
- package/templates/starter-server/.agents/skills/rivet-port-react-component/LICENSE.txt +21 -0
- package/templates/starter-server/.agents/skills/rivet-port-react-component/SKILL.md +36 -0
- package/templates/starter-server/.agents/skills/rivet-ui-design/LICENSE.txt +21 -0
- package/templates/starter-server/.agents/skills/rivet-ui-design/SKILL.md +41 -0
- package/templates/starter-server/.agents/skills/rivet-ui-design/references/component-composition.md +55 -0
- package/templates/starter-server/.agents/skills/rivet-ui-design/references/interactions.md +49 -0
- package/templates/starter-server/.agents/skills/rivet-ui-design/references/rivet-implementation.md +66 -0
- package/templates/starter-server/.agents/skills/rivet-ui-design/references/structure-and-layout.md +51 -0
- package/templates/starter-server/.agents/skills/rivet-ui-design/references/visual-foundations.md +64 -0
- package/templates/starter-server/.claude/skills/rivet-add-route/LICENSE.txt +21 -0
- package/templates/starter-server/.claude/skills/rivet-add-route/SKILL.md +36 -0
- package/templates/starter-server/.claude/skills/rivet-add-ui-page/LICENSE.txt +21 -0
- package/templates/starter-server/.claude/skills/rivet-add-ui-page/SKILL.md +42 -0
- package/templates/starter-server/.claude/skills/rivet-fix-resumability-error/LICENSE.txt +21 -0
- package/templates/starter-server/.claude/skills/rivet-fix-resumability-error/SKILL.md +26 -0
- package/templates/starter-server/.claude/skills/rivet-form-in-drawer/LICENSE.txt +21 -0
- package/templates/starter-server/.claude/skills/rivet-form-in-drawer/SKILL.md +62 -0
- package/templates/starter-server/.claude/skills/rivet-port-react-component/LICENSE.txt +21 -0
- package/templates/starter-server/.claude/skills/rivet-port-react-component/SKILL.md +36 -0
- package/templates/starter-server/.claude/skills/rivet-ui-design/LICENSE.txt +21 -0
- package/templates/starter-server/.claude/skills/rivet-ui-design/SKILL.md +41 -0
- package/templates/starter-server/.claude/skills/rivet-ui-design/references/component-composition.md +55 -0
- package/templates/starter-server/.claude/skills/rivet-ui-design/references/interactions.md +49 -0
- package/templates/starter-server/.claude/skills/rivet-ui-design/references/rivet-implementation.md +66 -0
- package/templates/starter-server/.claude/skills/rivet-ui-design/references/structure-and-layout.md +51 -0
- package/templates/starter-server/.claude/skills/rivet-ui-design/references/visual-foundations.md +64 -0
- package/templates/starter-server/.cursor/rules/rivet-add-route.mdc +37 -0
- package/templates/starter-server/.cursor/rules/rivet-add-ui-page.mdc +43 -0
- package/templates/starter-server/.cursor/rules/rivet-fix-resumability-error.mdc +27 -0
- package/templates/starter-server/.cursor/rules/rivet-form-in-drawer.mdc +63 -0
- package/templates/starter-server/.cursor/rules/rivet-port-react-component.mdc +37 -0
- package/templates/starter-server/.cursor/rules/rivet-ui-design.mdc +42 -0
- package/templates/starter-server/.cursor/rules/rivet.mdc +27 -0
- package/templates/starter-server/.dockerignore +7 -0
- package/templates/starter-server/.github/copilot-instructions.md +21 -0
- package/templates/starter-server/AGENTS.md +457 -0
- package/templates/starter-server/CLAUDE.md +457 -0
- package/templates/starter-server/Dockerfile +18 -0
- package/templates/starter-server/README.md +30 -0
- package/templates/starter-server/_gitignore +3 -0
- package/templates/starter-server/package.json +31 -0
- package/templates/starter-server/serve.js +6 -0
- package/templates/starter-server/src/document.ts +14 -0
- package/templates/starter-server/src/routes/index.tsx +41 -0
- package/templates/starter-server/src/styles.css +9 -0
- package/templates/starter-server/tsconfig.json +21 -0
- package/templates/starter-server/vite.config.ts +8 -0
- package/templates/starter-static/.agents/skills/rivet-add-route/LICENSE.txt +21 -0
- package/templates/starter-static/.agents/skills/rivet-add-route/SKILL.md +36 -0
- package/templates/starter-static/.agents/skills/rivet-add-ui-page/LICENSE.txt +21 -0
- package/templates/starter-static/.agents/skills/rivet-add-ui-page/SKILL.md +42 -0
- package/templates/starter-static/.agents/skills/rivet-fix-resumability-error/LICENSE.txt +21 -0
- package/templates/starter-static/.agents/skills/rivet-fix-resumability-error/SKILL.md +26 -0
- package/templates/starter-static/.agents/skills/rivet-form-in-drawer/LICENSE.txt +21 -0
- package/templates/starter-static/.agents/skills/rivet-form-in-drawer/SKILL.md +62 -0
- package/templates/starter-static/.agents/skills/rivet-port-react-component/LICENSE.txt +21 -0
- package/templates/starter-static/.agents/skills/rivet-port-react-component/SKILL.md +36 -0
- package/templates/starter-static/.agents/skills/rivet-ui-design/LICENSE.txt +21 -0
- package/templates/starter-static/.agents/skills/rivet-ui-design/SKILL.md +41 -0
- package/templates/starter-static/.agents/skills/rivet-ui-design/references/component-composition.md +55 -0
- package/templates/starter-static/.agents/skills/rivet-ui-design/references/interactions.md +49 -0
- package/templates/starter-static/.agents/skills/rivet-ui-design/references/rivet-implementation.md +66 -0
- package/templates/starter-static/.agents/skills/rivet-ui-design/references/structure-and-layout.md +51 -0
- package/templates/starter-static/.agents/skills/rivet-ui-design/references/visual-foundations.md +64 -0
- package/templates/starter-static/.claude/skills/rivet-add-route/LICENSE.txt +21 -0
- package/templates/starter-static/.claude/skills/rivet-add-route/SKILL.md +36 -0
- package/templates/starter-static/.claude/skills/rivet-add-ui-page/LICENSE.txt +21 -0
- package/templates/starter-static/.claude/skills/rivet-add-ui-page/SKILL.md +42 -0
- package/templates/starter-static/.claude/skills/rivet-fix-resumability-error/LICENSE.txt +21 -0
- package/templates/starter-static/.claude/skills/rivet-fix-resumability-error/SKILL.md +26 -0
- package/templates/starter-static/.claude/skills/rivet-form-in-drawer/LICENSE.txt +21 -0
- package/templates/starter-static/.claude/skills/rivet-form-in-drawer/SKILL.md +62 -0
- package/templates/starter-static/.claude/skills/rivet-port-react-component/LICENSE.txt +21 -0
- package/templates/starter-static/.claude/skills/rivet-port-react-component/SKILL.md +36 -0
- package/templates/starter-static/.claude/skills/rivet-ui-design/LICENSE.txt +21 -0
- package/templates/starter-static/.claude/skills/rivet-ui-design/SKILL.md +41 -0
- package/templates/starter-static/.claude/skills/rivet-ui-design/references/component-composition.md +55 -0
- package/templates/starter-static/.claude/skills/rivet-ui-design/references/interactions.md +49 -0
- package/templates/starter-static/.claude/skills/rivet-ui-design/references/rivet-implementation.md +66 -0
- package/templates/starter-static/.claude/skills/rivet-ui-design/references/structure-and-layout.md +51 -0
- package/templates/starter-static/.claude/skills/rivet-ui-design/references/visual-foundations.md +64 -0
- package/templates/starter-static/.cursor/rules/rivet-add-route.mdc +37 -0
- package/templates/starter-static/.cursor/rules/rivet-add-ui-page.mdc +43 -0
- package/templates/starter-static/.cursor/rules/rivet-fix-resumability-error.mdc +27 -0
- package/templates/starter-static/.cursor/rules/rivet-form-in-drawer.mdc +63 -0
- package/templates/starter-static/.cursor/rules/rivet-port-react-component.mdc +37 -0
- package/templates/starter-static/.cursor/rules/rivet-ui-design.mdc +42 -0
- package/templates/starter-static/.cursor/rules/rivet.mdc +27 -0
- package/templates/starter-static/.github/copilot-instructions.md +21 -0
- package/templates/starter-static/AGENTS.md +457 -0
- package/templates/starter-static/CLAUDE.md +457 -0
- package/templates/starter-static/README.md +30 -0
- package/templates/starter-static/_gitignore +3 -0
- package/templates/starter-static/package.json +31 -0
- package/templates/starter-static/prerender.js +5 -0
- package/templates/starter-static/serve.js +21 -0
- package/templates/starter-static/src/components/Counter.tsx +15 -0
- package/templates/starter-static/src/document.ts +14 -0
- package/templates/starter-static/src/routes/_layout.tsx +18 -0
- package/templates/starter-static/src/routes/about.tsx +11 -0
- package/templates/starter-static/src/routes/index.tsx +14 -0
- package/templates/starter-static/src/styles.css +9 -0
- package/templates/starter-static/tsconfig.json +21 -0
- package/templates/starter-static/vite.config.ts +9 -0
- package/templates/starter-ui/.agents/skills/rivet-add-route/LICENSE.txt +21 -0
- package/templates/starter-ui/.agents/skills/rivet-add-route/SKILL.md +36 -0
- package/templates/starter-ui/.agents/skills/rivet-add-ui-page/LICENSE.txt +21 -0
- package/templates/starter-ui/.agents/skills/rivet-add-ui-page/SKILL.md +42 -0
- package/templates/starter-ui/.agents/skills/rivet-fix-resumability-error/LICENSE.txt +21 -0
- package/templates/starter-ui/.agents/skills/rivet-fix-resumability-error/SKILL.md +26 -0
- package/templates/starter-ui/.agents/skills/rivet-form-in-drawer/LICENSE.txt +21 -0
- package/templates/starter-ui/.agents/skills/rivet-form-in-drawer/SKILL.md +62 -0
- package/templates/starter-ui/.agents/skills/rivet-port-react-component/LICENSE.txt +21 -0
- package/templates/starter-ui/.agents/skills/rivet-port-react-component/SKILL.md +36 -0
- package/templates/starter-ui/.agents/skills/rivet-ui-design/LICENSE.txt +21 -0
- package/templates/starter-ui/.agents/skills/rivet-ui-design/SKILL.md +41 -0
- package/templates/starter-ui/.agents/skills/rivet-ui-design/references/component-composition.md +55 -0
- package/templates/starter-ui/.agents/skills/rivet-ui-design/references/interactions.md +49 -0
- package/templates/starter-ui/.agents/skills/rivet-ui-design/references/rivet-implementation.md +66 -0
- package/templates/starter-ui/.agents/skills/rivet-ui-design/references/structure-and-layout.md +51 -0
- package/templates/starter-ui/.agents/skills/rivet-ui-design/references/visual-foundations.md +64 -0
- package/templates/starter-ui/.claude/skills/rivet-add-route/LICENSE.txt +21 -0
- package/templates/starter-ui/.claude/skills/rivet-add-route/SKILL.md +36 -0
- package/templates/starter-ui/.claude/skills/rivet-add-ui-page/LICENSE.txt +21 -0
- package/templates/starter-ui/.claude/skills/rivet-add-ui-page/SKILL.md +42 -0
- package/templates/starter-ui/.claude/skills/rivet-fix-resumability-error/LICENSE.txt +21 -0
- package/templates/starter-ui/.claude/skills/rivet-fix-resumability-error/SKILL.md +26 -0
- package/templates/starter-ui/.claude/skills/rivet-form-in-drawer/LICENSE.txt +21 -0
- package/templates/starter-ui/.claude/skills/rivet-form-in-drawer/SKILL.md +62 -0
- package/templates/starter-ui/.claude/skills/rivet-port-react-component/LICENSE.txt +21 -0
- package/templates/starter-ui/.claude/skills/rivet-port-react-component/SKILL.md +36 -0
- package/templates/starter-ui/.claude/skills/rivet-ui-design/LICENSE.txt +21 -0
- package/templates/starter-ui/.claude/skills/rivet-ui-design/SKILL.md +41 -0
- package/templates/starter-ui/.claude/skills/rivet-ui-design/references/component-composition.md +55 -0
- package/templates/starter-ui/.claude/skills/rivet-ui-design/references/interactions.md +49 -0
- package/templates/starter-ui/.claude/skills/rivet-ui-design/references/rivet-implementation.md +66 -0
- package/templates/starter-ui/.claude/skills/rivet-ui-design/references/structure-and-layout.md +51 -0
- package/templates/starter-ui/.claude/skills/rivet-ui-design/references/visual-foundations.md +64 -0
- package/templates/starter-ui/.cursor/rules/rivet-add-route.mdc +37 -0
- package/templates/starter-ui/.cursor/rules/rivet-add-ui-page.mdc +43 -0
- package/templates/starter-ui/.cursor/rules/rivet-fix-resumability-error.mdc +27 -0
- package/templates/starter-ui/.cursor/rules/rivet-form-in-drawer.mdc +63 -0
- package/templates/starter-ui/.cursor/rules/rivet-port-react-component.mdc +37 -0
- package/templates/starter-ui/.cursor/rules/rivet-ui-design.mdc +42 -0
- package/templates/starter-ui/.cursor/rules/rivet.mdc +27 -0
- package/templates/starter-ui/.github/copilot-instructions.md +21 -0
- package/templates/starter-ui/AGENTS.md +457 -0
- package/templates/starter-ui/CLAUDE.md +457 -0
- package/templates/starter-ui/README.md +32 -0
- package/templates/starter-ui/_gitignore +3 -0
- package/templates/starter-ui/package.json +34 -0
- package/templates/starter-ui/prerender.js +5 -0
- package/templates/starter-ui/serve.js +21 -0
- package/templates/starter-ui/src/components/LikeButton.tsx +12 -0
- package/templates/starter-ui/src/document.ts +14 -0
- package/templates/starter-ui/src/routes/index.tsx +39 -0
- package/templates/starter-ui/src/styles.css +11 -0
- package/templates/starter-ui/tsconfig.json +21 -0
- package/templates/starter-ui/vite.config.ts +9 -0
package/main.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// The create-rivet command: arguments (node:util parseArgs), questions (./prompt.ts), then ./index.ts does the work.
|
|
2
|
+
// npm create rivet@latest [dir] [-- --template <id>] [--yes] [--no-install] [--no-git] …
|
|
3
|
+
import { basename, relative, resolve } from "node:path";
|
|
4
|
+
import { parseArgs, styleText } from "node:util";
|
|
5
|
+
import { commands, createProject, detectPackageManager, findTemplate, initGit, installDependencies, isEmptyDir, isFile, ownVersion, PACKAGE_MANAGERS, TEMPLATES, toPackageName, validatePackageName, } from "./index.js";
|
|
6
|
+
import { DEPLOY_TARGETS, deployOverlay, deployProblem, findDeployTarget } from "./deploy.js";
|
|
7
|
+
import { closePrompts, confirm, PromptCancelled, select, text } from "./prompt.js";
|
|
8
|
+
export const HELP = `Create a Rivet app.
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
npm create rivet@latest [dir] [-- options]
|
|
12
|
+
pnpm create rivet [dir] [options] yarn create rivet [dir] [options] bun create rivet [dir] [options]
|
|
13
|
+
|
|
14
|
+
Options:
|
|
15
|
+
-t, --template <id> ${TEMPLATES.map((t) => t.id).join(" | ")} (asked when omitted; default static)
|
|
16
|
+
-d, --deploy <target> ${DEPLOY_TARGETS.map((t) => t.id).join(" | ")} (default: none = Node server / static files)
|
|
17
|
+
-n, --name <name> package.json name (default: the folder name)
|
|
18
|
+
-y, --yes accept the defaults, ask nothing
|
|
19
|
+
--install / --no-install install dependencies (default: yes)
|
|
20
|
+
--git / --no-git git init + first commit (default: yes)
|
|
21
|
+
--pm <name> ${PACKAGE_MANAGERS.join(" | ")} (default: the one running create, else npm)
|
|
22
|
+
--rivet-version <spec> @rivetjs/* dependency spec (default: this release)
|
|
23
|
+
-f, --force write into a folder that is not empty
|
|
24
|
+
-h, --help this text
|
|
25
|
+
-v, --version create-rivet version
|
|
26
|
+
|
|
27
|
+
Templates:
|
|
28
|
+
${TEMPLATES.map((t) => ` ${t.id.padEnd(9)} ${t.label}: ${t.hint}`).join("\n")}
|
|
29
|
+
`;
|
|
30
|
+
/** Runs the command; resolves with the exit code (0 ok, 1 error, 130 cancelled). */
|
|
31
|
+
export async function main(argv, io) {
|
|
32
|
+
let args;
|
|
33
|
+
try {
|
|
34
|
+
args = parseArgs({
|
|
35
|
+
args: argv,
|
|
36
|
+
allowPositionals: true,
|
|
37
|
+
allowNegative: true,
|
|
38
|
+
options: {
|
|
39
|
+
template: { type: "string", short: "t" },
|
|
40
|
+
deploy: { type: "string", short: "d" },
|
|
41
|
+
name: { type: "string", short: "n" },
|
|
42
|
+
yes: { type: "boolean", short: "y" },
|
|
43
|
+
install: { type: "boolean" },
|
|
44
|
+
git: { type: "boolean" },
|
|
45
|
+
pm: { type: "string" },
|
|
46
|
+
"rivet-version": { type: "string" },
|
|
47
|
+
force: { type: "boolean", short: "f" },
|
|
48
|
+
help: { type: "boolean", short: "h" },
|
|
49
|
+
version: { type: "boolean", short: "v" },
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
io.error.write(`${error.message}\n\n${HELP}`);
|
|
55
|
+
return 1;
|
|
56
|
+
}
|
|
57
|
+
const { values, positionals } = args;
|
|
58
|
+
const paint = (format, value) => styleText(format, value, { stream: io.output });
|
|
59
|
+
if (values.help)
|
|
60
|
+
return (io.output.write(HELP), 0);
|
|
61
|
+
if (values.version)
|
|
62
|
+
return (io.output.write(ownVersion() + "\n"), 0);
|
|
63
|
+
if (positionals.length > 1)
|
|
64
|
+
return fail(io, `Expected one folder, got ${positionals.join(" ")}`);
|
|
65
|
+
if (values.template && !findTemplate(values.template))
|
|
66
|
+
return fail(io, `Unknown template "${values.template}" (${TEMPLATES.map((t) => t.id).join(", ")})`);
|
|
67
|
+
if (values.deploy && !findDeployTarget(values.deploy))
|
|
68
|
+
return fail(io, `Unknown deploy target "${values.deploy}" (${DEPLOY_TARGETS.map((t) => t.id).join(", ")})`);
|
|
69
|
+
if (values.pm && !PACKAGE_MANAGERS.includes(values.pm))
|
|
70
|
+
return fail(io, `Unknown package manager "${values.pm}" (${PACKAGE_MANAGERS.join(", ")})`);
|
|
71
|
+
if (values.name && validatePackageName(values.name))
|
|
72
|
+
return fail(io, `--name: ${validatePackageName(values.name)}`);
|
|
73
|
+
// Questions only on a terminal and without --yes; otherwise every missing answer takes its default.
|
|
74
|
+
const ask = !values.yes && Boolean(io.input.isTTY);
|
|
75
|
+
try {
|
|
76
|
+
io.output.write(`\n${paint(["bold", "blue"], "create-rivet")} ${paint("dim", "v" + ownVersion())}\n\n`);
|
|
77
|
+
let name = values.name;
|
|
78
|
+
let folder = positionals[0];
|
|
79
|
+
if (!folder) {
|
|
80
|
+
const answer = ask
|
|
81
|
+
? await text(io, "Project name:", "rivet-app", (value) => validatePackageName(toPackageName(value)) && "Not a usable name")
|
|
82
|
+
: "rivet-app";
|
|
83
|
+
folder = answer;
|
|
84
|
+
name ??= toPackageName(basename(answer));
|
|
85
|
+
}
|
|
86
|
+
const dir = resolve(io.cwd, folder);
|
|
87
|
+
name ??= toPackageName(basename(dir));
|
|
88
|
+
if (isFile(dir))
|
|
89
|
+
return fail(io, `${folder} is a file`);
|
|
90
|
+
if (!isEmptyDir(dir) && !values.force) {
|
|
91
|
+
if (!ask)
|
|
92
|
+
return fail(io, `${folder} is not empty (use --force to write into it)`);
|
|
93
|
+
if (!(await confirm(io, `${folder} is not empty. Write into it anyway (files with the same names are replaced)?`, false)))
|
|
94
|
+
return cancelled(io);
|
|
95
|
+
}
|
|
96
|
+
const template = (values.template
|
|
97
|
+
? findTemplate(values.template).id
|
|
98
|
+
: ask
|
|
99
|
+
? await select(io, "Template:", TEMPLATES.map((t) => ({ value: t.id, label: t.label, hint: t.hint })))
|
|
100
|
+
: "static");
|
|
101
|
+
let deploy = values.deploy;
|
|
102
|
+
if (deploy && deployProblem(deploy, template))
|
|
103
|
+
return fail(io, deployProblem(deploy, template));
|
|
104
|
+
if (!deploy && ask) {
|
|
105
|
+
const choices = [
|
|
106
|
+
{ value: "none", label: "None", hint: template === "server" ? "Node server (serve.js, Dockerfile)" : "static files (dist/client)" },
|
|
107
|
+
...DEPLOY_TARGETS.filter((t) => !deployProblem(t.id, template)).map((t) => ({ value: t.id, label: t.label, hint: t.hint })),
|
|
108
|
+
];
|
|
109
|
+
const answer = await select(io, "Deploy target:", choices);
|
|
110
|
+
if (answer !== "none")
|
|
111
|
+
deploy = answer;
|
|
112
|
+
}
|
|
113
|
+
const pm = (values.pm ?? detectPackageManager(io.env["npm_config_user_agent"]));
|
|
114
|
+
const install = values.install ?? (ask ? await confirm(io, `Install dependencies with ${pm}?`, true) : true);
|
|
115
|
+
const git = values.git ?? (ask ? await confirm(io, "Initialise a git repository?", true) : true);
|
|
116
|
+
closePrompts(io);
|
|
117
|
+
const created = createProject({
|
|
118
|
+
dir,
|
|
119
|
+
template,
|
|
120
|
+
name,
|
|
121
|
+
...(values["rivet-version"] ? { rivetVersion: values["rivet-version"] } : {}),
|
|
122
|
+
...(deploy ? { overlays: [deployOverlay(deploy, pm)] } : {}),
|
|
123
|
+
});
|
|
124
|
+
io.output.write(`${paint("green", "Created")} ${created.name} (${findTemplate(template).label}${deploy ? ", " + findDeployTarget(deploy).label : ""}) in ${dir} — ${created.files.length} files\n`);
|
|
125
|
+
const run = commands(pm);
|
|
126
|
+
let installed = false;
|
|
127
|
+
if (install) {
|
|
128
|
+
io.output.write(`\n${paint("dim", "$ " + run.install)}\n`);
|
|
129
|
+
installed = installDependencies(dir, pm);
|
|
130
|
+
if (!installed)
|
|
131
|
+
io.error.write(paint("yellow", `\n${run.install} failed; run it yourself in ${folder}.\n`));
|
|
132
|
+
}
|
|
133
|
+
if (git) {
|
|
134
|
+
const result = initGit(dir);
|
|
135
|
+
if (result === "committed")
|
|
136
|
+
io.output.write(`${paint("green", "Git")} repository created with a first commit\n`);
|
|
137
|
+
else if (result === "created")
|
|
138
|
+
io.output.write(`${paint("green", "Git")} repository created (files staged; no commit: set git user.name / user.email)\n`);
|
|
139
|
+
else if (result === "inside-repo")
|
|
140
|
+
io.output.write(`${paint("dim", "Git: already inside a repository, skipped")}\n`);
|
|
141
|
+
else
|
|
142
|
+
io.output.write(`${paint("dim", "Git: not available, skipped")}\n`);
|
|
143
|
+
}
|
|
144
|
+
const cd = relative(io.cwd, dir) || ".";
|
|
145
|
+
io.output.write(`\nNext:\n`);
|
|
146
|
+
if (cd !== ".")
|
|
147
|
+
io.output.write(` cd ${cd.includes(" ") ? JSON.stringify(cd) : cd}\n`);
|
|
148
|
+
if (!installed)
|
|
149
|
+
io.output.write(` ${run.install}\n`);
|
|
150
|
+
io.output.write(` ${run.run("dev")}\n\n`);
|
|
151
|
+
return 0;
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
if (error instanceof PromptCancelled)
|
|
155
|
+
return cancelled(io);
|
|
156
|
+
return fail(io, error.message);
|
|
157
|
+
}
|
|
158
|
+
finally {
|
|
159
|
+
closePrompts(io);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function fail(io, message) {
|
|
163
|
+
io.error.write(styleText("red", "Error: ", { stream: io.error }) + message + "\n");
|
|
164
|
+
return 1;
|
|
165
|
+
}
|
|
166
|
+
function cancelled(io) {
|
|
167
|
+
io.error.write("Cancelled.\n");
|
|
168
|
+
return 130;
|
|
169
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,59 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-rivet",
|
|
3
|
-
"version": "0.0.0
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0-alpha.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "Create a Rivet app: npm create rivet@latest",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"rivet",
|
|
9
|
+
"create",
|
|
10
|
+
"starter",
|
|
11
|
+
"template",
|
|
12
|
+
"jsx",
|
|
13
|
+
"ssr",
|
|
14
|
+
"resumable"
|
|
15
|
+
],
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./index.d.ts",
|
|
19
|
+
"default": "./index.js"
|
|
20
|
+
},
|
|
21
|
+
"./deploy": {
|
|
22
|
+
"types": "./deploy.d.ts",
|
|
23
|
+
"default": "./deploy.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"bin": {
|
|
27
|
+
"create-rivet": "./cli.js"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=24"
|
|
31
|
+
},
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/N1XUS/rivetjs.git",
|
|
35
|
+
"directory": "libs/create-rivet"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/N1XUS/rivetjs/tree/main/libs/create-rivet#readme",
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/N1XUS/rivetjs/issues"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"LICENSE",
|
|
46
|
+
"README.md",
|
|
47
|
+
"cli.d.ts",
|
|
48
|
+
"cli.js",
|
|
49
|
+
"deploy.d.ts",
|
|
50
|
+
"deploy.js",
|
|
51
|
+
"index.d.ts",
|
|
52
|
+
"index.js",
|
|
53
|
+
"main.d.ts",
|
|
54
|
+
"main.js",
|
|
55
|
+
"prompt.d.ts",
|
|
56
|
+
"prompt.js",
|
|
57
|
+
"templates"
|
|
58
|
+
]
|
|
59
|
+
}
|
package/prompt.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface PromptIO {
|
|
2
|
+
input: NodeJS.ReadableStream & {
|
|
3
|
+
isTTY?: boolean;
|
|
4
|
+
setRawMode?: (mode: boolean) => unknown;
|
|
5
|
+
};
|
|
6
|
+
output: NodeJS.WritableStream;
|
|
7
|
+
}
|
|
8
|
+
/** Thrown when the user cancels (Ctrl+C, Ctrl+D / end of input). */
|
|
9
|
+
export declare class PromptCancelled extends Error {
|
|
10
|
+
constructor();
|
|
11
|
+
}
|
|
12
|
+
/** Releases the input stream (end of the prompts: lets the process exit). */
|
|
13
|
+
export declare function closePrompts(io: PromptIO): void;
|
|
14
|
+
/** Free text; `validate` returns an error message to ask again. */
|
|
15
|
+
export declare function text(io: PromptIO, question: string, fallback: string, validate?: (value: string) => string | undefined): Promise<string>;
|
|
16
|
+
/** Yes / no. */
|
|
17
|
+
export declare function confirm(io: PromptIO, question: string, fallback: boolean): Promise<boolean>;
|
|
18
|
+
export interface Choice<T extends string> {
|
|
19
|
+
value: T;
|
|
20
|
+
label: string;
|
|
21
|
+
hint?: string;
|
|
22
|
+
}
|
|
23
|
+
/** One of `choices`: arrow keys + Enter on a TTY, else a number (or a value) typed on a line. */
|
|
24
|
+
export declare function select<T extends string>(io: PromptIO, question: string, choices: readonly Choice<T>[], initial?: number): Promise<T>;
|
package/prompt.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// Tiny terminal prompts on node:readline (no dependencies): text, yes/no, and a choice list (arrow keys on a
|
|
2
|
+
// TTY, a numbered answer otherwise). Every prompt takes its streams, so tests drive them with PassThrough.
|
|
3
|
+
import { emitKeypressEvents } from "node:readline";
|
|
4
|
+
import { createInterface } from "node:readline/promises";
|
|
5
|
+
import { styleText } from "node:util";
|
|
6
|
+
/** Thrown when the user cancels (Ctrl+C, Ctrl+D / end of input). */
|
|
7
|
+
export class PromptCancelled extends Error {
|
|
8
|
+
constructor() {
|
|
9
|
+
super("cancelled");
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
const color = (format, text, io) => styleText(format, text, { stream: io.output });
|
|
13
|
+
const readers = new WeakMap();
|
|
14
|
+
/** One line reader per input stream (a reader per question would drop lines buffered after the first). */
|
|
15
|
+
function reader(io) {
|
|
16
|
+
let current = readers.get(io.input);
|
|
17
|
+
if (current)
|
|
18
|
+
return current;
|
|
19
|
+
const rl = createInterface({ input: io.input, terminal: false });
|
|
20
|
+
const queue = [];
|
|
21
|
+
const waiters = [];
|
|
22
|
+
const push = (value) => (waiters.length ? waiters.shift()(value) : queue.push(value));
|
|
23
|
+
rl.on("line", push);
|
|
24
|
+
rl.on("close", () => push(null));
|
|
25
|
+
current = {
|
|
26
|
+
close: () => (readers.delete(io.input), rl.close()),
|
|
27
|
+
next: () => (queue.length ? Promise.resolve(queue.shift()) : new Promise((resolve) => waiters.push(resolve))),
|
|
28
|
+
};
|
|
29
|
+
readers.set(io.input, current);
|
|
30
|
+
return current;
|
|
31
|
+
}
|
|
32
|
+
/** Releases the input stream (end of the prompts: lets the process exit). */
|
|
33
|
+
export function closePrompts(io) {
|
|
34
|
+
readers.get(io.input)?.close();
|
|
35
|
+
}
|
|
36
|
+
/** Reads one line (trimmed). */
|
|
37
|
+
async function line(question, io) {
|
|
38
|
+
io.output.write(question);
|
|
39
|
+
const answer = await reader(io).next();
|
|
40
|
+
if (answer === null)
|
|
41
|
+
throw new PromptCancelled();
|
|
42
|
+
return answer.trim();
|
|
43
|
+
}
|
|
44
|
+
/** Free text; `validate` returns an error message to ask again. */
|
|
45
|
+
export async function text(io, question, fallback, validate) {
|
|
46
|
+
for (;;) {
|
|
47
|
+
const value = (await line(`${color("cyan", "?", io)} ${question} ${color("dim", `(${fallback})`, io)} `, io)) || fallback;
|
|
48
|
+
const error = validate?.(value);
|
|
49
|
+
if (!error)
|
|
50
|
+
return value;
|
|
51
|
+
io.output.write(` ${color("red", error, io)}\n`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Yes / no. */
|
|
55
|
+
export async function confirm(io, question, fallback) {
|
|
56
|
+
for (;;) {
|
|
57
|
+
const value = (await line(`${color("cyan", "?", io)} ${question} ${color("dim", fallback ? "(Y/n)" : "(y/N)", io)} `, io)).toLowerCase();
|
|
58
|
+
if (!value)
|
|
59
|
+
return fallback;
|
|
60
|
+
if (value === "y" || value === "yes")
|
|
61
|
+
return true;
|
|
62
|
+
if (value === "n" || value === "no")
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** One of `choices`: arrow keys + Enter on a TTY, else a number (or a value) typed on a line. */
|
|
67
|
+
export async function select(io, question, choices, initial = 0) {
|
|
68
|
+
const { input, output } = io;
|
|
69
|
+
if (!input.isTTY || !input.setRawMode) {
|
|
70
|
+
output.write(`${color("cyan", "?", io)} ${question}\n`);
|
|
71
|
+
choices.forEach((choice, i) => output.write(` ${i + 1}. ${choice.label}${choice.hint ? color("dim", ` - ${choice.hint}`, io) : ""}\n`));
|
|
72
|
+
for (;;) {
|
|
73
|
+
const value = await line(` ${color("dim", `number (${initial + 1})`, io)} `, io);
|
|
74
|
+
if (!value)
|
|
75
|
+
return choices[initial].value;
|
|
76
|
+
const byValue = choices.find((choice) => choice.value === value);
|
|
77
|
+
if (byValue)
|
|
78
|
+
return byValue.value;
|
|
79
|
+
const index = Number(value) - 1;
|
|
80
|
+
if (Number.isInteger(index) && choices[index])
|
|
81
|
+
return choices[index].value;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
let index = initial;
|
|
85
|
+
const render = (first) => {
|
|
86
|
+
if (!first)
|
|
87
|
+
output.write(`\x1b[${choices.length}A`);
|
|
88
|
+
choices.forEach((choice, i) => {
|
|
89
|
+
const active = i === index;
|
|
90
|
+
const label = active ? color(["cyan", "bold"], `> ${choice.label}`, io) : ` ${choice.label}`;
|
|
91
|
+
output.write(`\x1b[2K${label}${choice.hint ? color("dim", ` - ${choice.hint}`, io) : ""}\n`);
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
// Raw keypresses: the line reader must not consume them.
|
|
95
|
+
closePrompts(io);
|
|
96
|
+
output.write(`${color("cyan", "?", io)} ${question} ${color("dim", "(arrow keys, Enter)", io)}\n`);
|
|
97
|
+
render(true);
|
|
98
|
+
emitKeypressEvents(input);
|
|
99
|
+
input.setRawMode(true);
|
|
100
|
+
input.resume();
|
|
101
|
+
try {
|
|
102
|
+
return await new Promise((resolve, reject) => {
|
|
103
|
+
const onKey = (_, key = {}) => {
|
|
104
|
+
if (key.ctrl && (key.name === "c" || key.name === "d"))
|
|
105
|
+
return done(() => reject(new PromptCancelled()));
|
|
106
|
+
if (key.name === "up" || key.name === "k")
|
|
107
|
+
index = (index - 1 + choices.length) % choices.length;
|
|
108
|
+
else if (key.name === "down" || key.name === "j")
|
|
109
|
+
index = (index + 1) % choices.length;
|
|
110
|
+
else if (key.name === "return" || key.name === "enter")
|
|
111
|
+
return done(() => resolve(choices[index].value));
|
|
112
|
+
else
|
|
113
|
+
return;
|
|
114
|
+
render(false);
|
|
115
|
+
};
|
|
116
|
+
const done = (settle) => {
|
|
117
|
+
input.off("keypress", onKey);
|
|
118
|
+
settle();
|
|
119
|
+
};
|
|
120
|
+
input.on("keypress", onKey);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
input.setRawMode(false);
|
|
125
|
+
input.pause();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Denys Severyn
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rivet-add-route
|
|
3
|
+
description: "Add a page or endpoint to a Rivet app with file routes, server data (serverLoad), actions, head and layouts. Use when the user asks for a new page, URL, route, endpoint or loader in a Rivet project."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- Generated by libs/mcp/ai/sync.ts from libs/mcp/ai/skills/add-route.md in the Rivet repository: edit the source there and run the script. In an app created from a starter this file is yours to edit. -->
|
|
7
|
+
|
|
8
|
+
# Add a route to a Rivet app
|
|
9
|
+
|
|
10
|
+
1. Pick the file under `src/routes/` (URL = path): `about.tsx` → `/about`, `blog/[slug].tsx` → `/blog/:slug`, `docs/[[lang]]/x.tsx` (optional param), `files/[...path].tsx` (rest), `(group)/p.tsx` → `/p` (shares the group's `_layout.tsx`), `api/time.ts` → endpoint (`export const onGet = () => Response.json(…)`). `_layout.tsx` wraps its folder; `_404.tsx` / `_error.tsx` handle misses / errors. Look up details with `get_doc("docs:/guides/routing/file-routes/")`.
|
|
11
|
+
2. Write the page. Server code lives in `server…` exports (stripped from the browser build):
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { notFound, type PageProps, type RequestEvent } from "@rivetjs/router";
|
|
15
|
+
|
|
16
|
+
export const serverLoad = async ({ params }: RequestEvent<"blog/[slug]">) => {
|
|
17
|
+
const post = await findPost(params.slug); // server-only: database, files, secrets
|
|
18
|
+
if (!post) throw notFound();
|
|
19
|
+
return { post };
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const head = { title: "Post" };
|
|
23
|
+
|
|
24
|
+
export default function Post({ data }: PageProps<"blog/[slug]">) {
|
|
25
|
+
return <h1 class="text-2xl">{data.post.title}</h1>;
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- Page props `data`, `params`, `url` are plain values; in `_layout.tsx` (`LayoutProps`) they are accessors: `data()`, `params()`, `url()`, plus `children`.
|
|
30
|
+
- Mutations: `export const serverActions = { name: async ({ request }) => … }` + `<form method="post" action="?/name">`; with validation use `@rivetjs/forms` (`action(def, fn)` from `@rivetjs/forms/server`).
|
|
31
|
+
- Access checks: `serverGuard` (throw / return `redirect()`, `deny()`, `notFound()`).
|
|
32
|
+
- Static mode (`rivetRouter({ mode: "static" })`): `serverLoad` runs at build time; dynamic `[param]` pages list their params in `serverEntries`.
|
|
33
|
+
- Server-only helpers go in `*.server.ts` files (browser code cannot import them: RV3008).
|
|
34
|
+
|
|
35
|
+
3. Link to it: plain `<a href="/blog/hello">` (client navigation), or typed `<Link to="/blog/hello">` from `@rivetjs/router/link`; programmatic `navigate()` from `@rivetjs/router/client`.
|
|
36
|
+
4. Run `npm run check` (`rivet sync` regenerates the typed routes in `.rivet/`, `rivet-check` reports `RV` codes; RV3101 = a link matches no route). Fix every diagnostic (`explain_diagnostic(code)`), then `npm run build`.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Denys Severyn
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rivet-add-ui-page
|
|
3
|
+
description: "Build a page or component from @rivetjs/ui components - correct subpath imports, compound parts, two-way signal props, styles and lucide icons. Use when the user asks for UI built with Rivet UI components (buttons, selects, modals, cards, forms) in a Rivet app."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- Generated by libs/mcp/ai/sync.ts from libs/mcp/ai/skills/add-ui-page.md in the Rivet repository: edit the source there and run the script. In an app created from a starter this file is yours to edit. -->
|
|
7
|
+
|
|
8
|
+
# Page with @rivetjs/ui components
|
|
9
|
+
|
|
10
|
+
1. Setup (the `ui` starter has it): dependencies `@rivetjs/ui`, `@rivetjs/kit`, `lucide`; `src/styles.css` = `@import "tailwindcss";` then `@import "@rivetjs/ui/styles";` (other looks: `@rivetjs/ui/theme` presets, variables pasted after it).
|
|
11
|
+
2. For every component: `get_component("Select")` (import path, parts, props, example source) or `get_doc("ui:/components/select/")`. Copy the structure of the docs example, not React component-library code.
|
|
12
|
+
3. Rules that differ from React component libraries:
|
|
13
|
+
- Subpath imports: `import { Select } from "@rivetjs/ui/select"`, `import { ListBox } from "@rivetjs/ui/list-box"`, `import { Label } from "@rivetjs/ui/label"`.
|
|
14
|
+
- `class`, not `className`. Events follow React Aria: `onPress`, `onSelectionChange`, `onOpenChange`.
|
|
15
|
+
- Two-way props take a signal: `isOpen={open}`, `value={selected}`, `selectedKeys={keys}`; a plain value is one-way.
|
|
16
|
+
- Render props are accessors: `({ isPlaceholder }) => (isPlaceholder() ? … : …)`.
|
|
17
|
+
- Icons: `<Icon icon={ChevronDown} />` (`@rivetjs/kit/icon` + lucide); never inline SVG paths or Unicode glyphs.
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { signal } from "@rivetjs/reactivity";
|
|
21
|
+
import { Button } from "@rivetjs/ui/button";
|
|
22
|
+
import { Modal } from "@rivetjs/ui/modal";
|
|
23
|
+
|
|
24
|
+
export default function Page() {
|
|
25
|
+
const open = signal(false);
|
|
26
|
+
return (
|
|
27
|
+
<>
|
|
28
|
+
<Button onPress={() => open.set(true)}>Open</Button>
|
|
29
|
+
<Modal.Content isOpen={open}>
|
|
30
|
+
<Modal.Header>Title</Modal.Header>
|
|
31
|
+
<Modal.Body>…</Modal.Body>
|
|
32
|
+
<Modal.Footer>
|
|
33
|
+
<Button slot="close">Close</Button>
|
|
34
|
+
</Modal.Footer>
|
|
35
|
+
</Modal.Content>
|
|
36
|
+
</>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
4. Custom-styled widget instead: the headless `@rivetjs/cdk/<module>` (same parts, `rv-*` classes + `data-*` state, no styles).
|
|
42
|
+
5. `check_code`, `npm run check`, then look at it in `npm run dev` (overlays render in a portal on the div overlay layer, never native `<dialog>` / `popover`).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Denys Severyn
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rivet-fix-resumability-error
|
|
3
|
+
description: 'Diagnose and fix Rivet resumability errors - RV2xxx compiler diagnostics, RV1016 handler errors, "is not serializable" / "Cannot resume" build or prerender errors, browser globals during server rendering, state that resets after the first click. Use when a Rivet build, check or page fails with such an error.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- Generated by libs/mcp/ai/sync.ts from libs/mcp/ai/skills/fix-resumability-error.md in the Rivet repository: edit the source there and run the script. In an app created from a starter this file is yours to edit. -->
|
|
7
|
+
|
|
8
|
+
# Fix a resumability error
|
|
9
|
+
|
|
10
|
+
A Rivet page is rendered on the server and resumed in the browser: handlers, bindings and client effects run there with the values they capture, which the server serialized into the HTML. Errors come from code that cannot cross that line.
|
|
11
|
+
|
|
12
|
+
1. Get the exact diagnostic: `npm run check` (`file:line:col - error RVnnnn`), the Vite overlay (`[RVnnnn] file:line:col`), or `check_code(source, filename)`. For the fix: `explain_diagnostic("RVnnnn")`; `check_code` also returns the quick-fixed source when one applies.
|
|
13
|
+
2. Common codes:
|
|
14
|
+
- RV2001 browser global (`window`, `document`, `localStorage`) read during server rendering → move it into a handler, `clientEffect(() => …)`, `clientEvent(window, "resize", …)` or `clientOnly(() => import("…"))`.
|
|
15
|
+
- RV2003 captured class instance (`new Intl.NumberFormat`, an SDK client) → capture plain data; create the object inside the handler or `clientEffect`, or build it with `reconstruct(() => new X(…))` from `@rivetjs/runtime/dom` (the browser runs the factory on first read). A data class declared in the component module (plain fields) resumes as is.
|
|
16
|
+
- RV2102 captured instance of a module class that cannot resume as data (`#private` field, a decorator, a field arrow using `super`, a base that is not a data class of the module, `window` / `document` / `WebSocket` read while constructing) → create it inside the handler, build it with `reconstruct(() => new X(…))`, keep only plain fields, or add `[serialize]()` + `static [revive](data)` (from `@rivetjs/runtime/resume-format`; package classes: `registerClass(Cls, { id, serialize, revive })` in a module imported by the server code and the resume entry). At render, `RV2102: X has [serialize] but no static [revive]` → add the `static [revive]`.
|
|
17
|
+
- RV1016 handler expression reads state when it binds (`onClick={a() ? f : g}`) → wrap: `onClick={(e) => (a() ? f(e) : g(e))}`.
|
|
18
|
+
- RV2002 ref callback must assign a local → `ref={(el) => (local = el)}` or `const el = ref()` + `ref={el}`.
|
|
19
|
+
- RV2006 JSX inside a handler / effect / computed → render JSX in the component, switch it with a signal.
|
|
20
|
+
- RV2007 `this` in resumable code → `event.currentTarget`.
|
|
21
|
+
- RV2009 two module references share a browser name (two imports of different modules under one name) → rename one import (`import { fmt as fmtDate } from …`).
|
|
22
|
+
- RV2101 (warning, type-aware) a captured value's type does not serialize → as RV2003.
|
|
23
|
+
3. Build / prerender error `Cannot resume <file>#<Component>$<hash>: function is not serializable` (dev server: ``capture `<name>` function is not serializable``; dev build: `capture #<i> …`): a captured function Rivet did not compile (created at runtime by a library such as `memoize(fn)`, a `.bind(…)` written inside an object literal (keep `const inc = add.bind(null, 2)` in a local constant and store `inc`; `.bind(ctx)` of a function using `this` stays RV2007), a method, or held by an object built outside a component: at module level such as `const table = { format: formatEuro }`, or by your own hook / helper such as `function useFormat() { return { format: formatEuro }; }`). Wrap it in an inline arrow, or build the object inside the component (module-level and imported functions used by name there resume by reference). A library object holding functions (a chart, editor, socket client from a package): build it with `reconstruct(() => createChart(…))` from `@rivetjs/runtime/dom` — no value in the page, the browser runs the factory on first read (module level: one server instance per process, never read request state in it). A class instance at render (`X instance is not serializable`): your class → `reconstruct(() => new X(…))` or `[serialize]()` + `static [revive](data)`; a package class → `registerClass(X, { id, serialize, revive })` from `@rivetjs/runtime/resume-format`. `NODE_ENV=development vite build` names the value path. Rules: `get_doc("docs:/best-practices/serializable-captures/")`.
|
|
24
|
+
4. State resets on the first change after resume: a child destructures its props (`function C({ value })`) and is re-created → read `props.value`. Copies of `props.children` in a local are not tracked → render `{props.children}` directly.
|
|
25
|
+
5. `.ts` module importing `@rivetjs/runtime/dom` (bundles the DOM runtime into SSR) → move that code into a `.tsx` file.
|
|
26
|
+
6. Re-run `npm run check` and `npm run build` (prerender exercises serialization); both must be clean.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Denys Severyn
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rivet-form-in-drawer
|
|
3
|
+
description: 'Build a validated form inside a drawer or dialog on a Rivet live page, so a submit updates the list behind it in place, keeps the overlay open on errors and works without JavaScript. Use for "add/edit X in a drawer/modal/dialog" tasks in a Rivet project.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- Generated by libs/mcp/ai/sync.ts from libs/mcp/ai/skills/form-in-drawer.md in the Rivet repository: edit the source there and run the script. In an app created from a starter this file is yours to edit. -->
|
|
7
|
+
|
|
8
|
+
# Form in a drawer with a live page
|
|
9
|
+
|
|
10
|
+
Reference implementation: `get_doc("docs:/guides/forms/forms-in-overlays/")` (example `docs:/examples/drawer-form/`). Read it first; the steps below are its shape.
|
|
11
|
+
|
|
12
|
+
1. Define the form once, shared by page and action (any Standard Schema library):
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
// src/forms/customer.ts
|
|
16
|
+
import { defineForm } from "@rivetjs/forms";
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
export const customerForm = defineForm({ name: "customer", schema: z.object({ name: z.string().min(1), email: z.string().email() }) });
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
2. Make the page live and add the action:
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import type { LivePageProps } from "@rivetjs/router";
|
|
25
|
+
import { actionResult } from "@rivetjs/router/client";
|
|
26
|
+
import { action, invalid } from "@rivetjs/forms/server";
|
|
27
|
+
|
|
28
|
+
export const live = true; // data is an accessor, refreshed in place after a submit: signals (open drawer, input, focus) survive
|
|
29
|
+
export const serverLoad = ({ url }: { url: URL }) => ({ customers: [...customers], creating: url.searchParams.has("new") });
|
|
30
|
+
export const serverActions = {
|
|
31
|
+
customer: action(customerForm, (value) => {
|
|
32
|
+
if (customers.some((c) => c.email === value.email)) return invalid({ email: "Already a customer" });
|
|
33
|
+
customers.push({ id: customers.length + 1, ...value });
|
|
34
|
+
}),
|
|
35
|
+
};
|
|
36
|
+
export default function Customers({ data }: LivePageProps<typeof serverLoad>) {
|
|
37
|
+
/* … */
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
3. In the component: `const result = actionResult("customer")`, `const open = signal(data().creating || (result?.status ?? 0) >= 400)` (no-JS entry: `<a href="?new" onClick={(e) => { e.preventDefault(); open.set(true); }}>`), `const form = createForm(customerForm, { result })`. Render the list from `data()` with `<For each={data().customers} by={(c) => c.id}>`; never copy `data()` into a signal.
|
|
42
|
+
4. The overlay: `@rivetjs/ui` (`Drawer` / `Modal`, `isOpen={open}`) or cdk `ModalOverlay isDismissable isOpen={open}` + `Modal` + `Dialog` (div overlay layer, two-way `isOpen`). The form inside:
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
<form id={formId(form)} method="post" action="?/customer" onSubmit={async (event) => (await onSubmit(customerForm, form, event)) && open.set(false)}>
|
|
46
|
+
<label for={fieldId(form, "email")}>Email</label>
|
|
47
|
+
<input
|
|
48
|
+
id={fieldId(form, "email")}
|
|
49
|
+
name="email"
|
|
50
|
+
value={defaultValue(form, "email")}
|
|
51
|
+
aria-invalid={isInvalid(form, "email")}
|
|
52
|
+
aria-describedby={describedBy(form, "email")}
|
|
53
|
+
/>
|
|
54
|
+
<FieldError of={form} name="email" />
|
|
55
|
+
<button aria-disabled={submitting(form) ? "true" : undefined}>Save</button>
|
|
56
|
+
</form>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`onSubmit` resolves true once the server accepted the form and the page data is fresh: close the drawer then. Errors stay in the open drawer. Imports: `createForm, defaultValue, describedBy, fieldId, formId, isInvalid, onSubmit, submitting` from `@rivetjs/forms`, `FieldError` from `@rivetjs/forms/components`.
|
|
60
|
+
|
|
61
|
+
5. A layout `serverLoad` re-runs too (counts in headers update).
|
|
62
|
+
6. Check: `check_code` on the page, `npm run check`, then try it with JavaScript disabled (plain POST, 400 re-render with the drawer open).
|