@rivetjs/nx 0.1.0-alpha.0 → 0.1.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/generators/api-route/files/__fileName__.ts.template +3 -3
- package/generators/api-route/schema.json +1 -1
- package/generators/layout/files/_layout.tsx.template +3 -3
- package/generators/layout/schema.d.ts +1 -1
- package/generators/layout/schema.json +2 -2
- package/generators/route/files/__fileName__.tsx.template +7 -7
- package/generators/route/generator.d.ts +1 -1
- package/generators/route/generator.js +9 -4
- package/generators/route/schema.d.ts +3 -3
- package/generators/route/schema.json +4 -4
- package/generators.json +3 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @rivetjs/nx
|
|
2
2
|
|
|
3
|
-
[Nx](https://nx.dev) plugin for [Rivet](https://github.com/rivetjs
|
|
3
|
+
[Nx](https://nx.dev) plugin for [Rivet](https://github.com/N1XUS/rivetjs): a workspace preset, generators for apps,
|
|
4
4
|
libraries, routes and components, executors, and inferred `build` / `dev` / `preview` / `test` / `check` /
|
|
5
5
|
`typecheck` targets with Nx caching.
|
|
6
6
|
|
|
@@ -26,7 +26,7 @@ Node.js 24 or newer, Nx 23.
|
|
|
26
26
|
| `component` | `c` | A resumable component + test: `--kind=basic\|overlay` (render-function children), `--story` (a `/stories/<name>` page), `--directory`, `--export`. |
|
|
27
27
|
| `route` | `r`, `page` | A file route: `blog/[slug]`, `docs/[[lang]]/[...path]`, `(group)/page`; `--loader`, `--action`, `--guard`, `--layout`. |
|
|
28
28
|
| `layout` | | `src/routes/<dir>/_layout.tsx`, `--guard`. |
|
|
29
|
-
| `api-route` | `endpoint` | An endpoint route with `onGet` / `onPost` / … (`--methods=get,post`). |
|
|
29
|
+
| `api-route` | `endpoint` | An endpoint route with `onGet(…)` / `onPost(…)` / … exports (`--methods=get,post`). |
|
|
30
30
|
| `ui-setup` | | `@rivetjs/ui` + Tailwind v4 + its styles (Rivet theme; other looks = `@rivetjs/ui/theme` presets). |
|
|
31
31
|
| `i18n-setup` | | `@rivetjs/i18n`: `i18n.config.ts`, catalogs, the Vite plugin (`--locales=en,de`, `--prefix`). |
|
|
32
32
|
| `init` | | Plugin in `nx.json`, shared tooling dependencies, `.rivet` in `.gitignore`. |
|
|
@@ -61,4 +61,4 @@ Every folder with a `package.json` or `project.json` and a Rivet Vite config (`r
|
|
|
61
61
|
make `preview` run `wrangler dev` / Bun / Deno. `build`, `test`, `check`, `typecheck` are cached; `.rivet/`,
|
|
62
62
|
`dist/` and `coverage/` are never inputs; `build` outputs `{projectRoot}/dist`.
|
|
63
63
|
|
|
64
|
-
Docs: [Nx guide](https://github.com/rivetjs/
|
|
64
|
+
Docs: [Nx guide](https://github.com/N1XUS/rivetjs/blob/main/apps/docs/content/guides/tooling/nx.md).
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { <%- methods.map((method) => "on" + method[0].toUpperCase() + method.slice(1)).join(", ") %>, type RequestEvent } from "@rivetjs/router";
|
|
2
2
|
<% for (const method of methods) { %>
|
|
3
3
|
/** `<%= method.toUpperCase() %> <%= url %>`. Runs only on the server; must return a `Response`. */
|
|
4
|
-
export const on<%= method[0].toUpperCase() + method.slice(1) %>
|
|
4
|
+
export const <%= method.toUpperCase() %> = on<%= method[0].toUpperCase() + method.slice(1) %>(async (<% if (method === "get" || method === "delete") { %>{ params }<% } else { %>{ request, params }<% } %>: RequestEvent) => {<% if (method === "get") { %>
|
|
5
5
|
return Response.json({ ok: true, params }, { headers: { "cache-control": "no-store" } });<% } else if (method === "delete") { %>
|
|
6
6
|
console.log("delete", params);
|
|
7
7
|
return new Response(null, { status: 204 });<% } else { %>
|
|
8
8
|
const body: unknown = await request.json().catch(() => null);
|
|
9
9
|
return Response.json({ ok: true, params, body }<% if (method === "post") { %>, { status: 201 }<% } %>);<% } %>
|
|
10
|
-
};
|
|
10
|
+
});
|
|
11
11
|
<% } %>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/schema",
|
|
3
3
|
"$id": "RivetApiRoute",
|
|
4
4
|
"title": "Rivet API route",
|
|
5
|
-
"description": "Adds an endpoint route (a plain .ts file in src/routes with onGet / onPost / … request
|
|
5
|
+
"description": "Adds an endpoint route (a plain .ts file in src/routes with onGet(…) / onPost(…) / … request handler exports) to a server-rendered Rivet app.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"properties": {
|
|
8
8
|
"path": {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
<% if (guard) { %>import { redirect, type LayoutProps, type RequestEvent } from "@rivetjs/router";
|
|
1
|
+
<% if (guard) { %>import { guard, redirect, type LayoutProps, type RequestEvent } from "@rivetjs/router";
|
|
2
2
|
|
|
3
3
|
/** Runs for every page and endpoint in this folder, before their loaders. */
|
|
4
|
-
export const
|
|
4
|
+
export const access = guard(({ cookies, url }: RequestEvent) => {
|
|
5
5
|
if (!cookies.get("session")) return redirect("/login?next=" + encodeURIComponent(url.pathname));
|
|
6
|
-
};
|
|
6
|
+
});
|
|
7
7
|
<% } else { %>import type { LayoutProps } from "@rivetjs/router";
|
|
8
8
|
<% } %>
|
|
9
9
|
/** Wraps every page in <%= where %>. Stays mounted across client navigation between them. */
|
|
@@ -3,7 +3,7 @@ export interface LayoutGeneratorSchema {
|
|
|
3
3
|
directory?: string;
|
|
4
4
|
/** The Rivet app. */
|
|
5
5
|
project: string;
|
|
6
|
-
/** `
|
|
6
|
+
/** A `guard(…)` export for the whole folder (redirects to /login without a `session` cookie). */
|
|
7
7
|
guard?: boolean;
|
|
8
8
|
skipFormat?: boolean;
|
|
9
9
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/schema",
|
|
3
3
|
"$id": "RivetLayout",
|
|
4
4
|
"title": "Rivet layout",
|
|
5
|
-
"description": "Adds a _layout.tsx to a folder of a Rivet app's routes (the root layout without a directory), optionally with a
|
|
5
|
+
"description": "Adds a _layout.tsx to a folder of a Rivet app's routes (the root layout without a directory), optionally with a guard(…) export for everything below it.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"properties": {
|
|
8
8
|
"directory": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"x-dropdown": "projects",
|
|
19
19
|
"x-prompt": "Which project?"
|
|
20
20
|
},
|
|
21
|
-
"guard": { "type": "boolean", "default": false, "description": "Add a
|
|
21
|
+
"guard": { "type": "boolean", "default": false, "description": "Add a guard(…) export for the whole folder." },
|
|
22
22
|
"skipFormat": { "type": "boolean", "default": false, "x-priority": "internal", "description": "Skip formatting files." }
|
|
23
23
|
},
|
|
24
24
|
"required": ["project"],
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
<% if (routerImports.length) { %>import { <%- routerImports.join(", ") %> } from "@rivetjs/router";
|
|
2
2
|
<% } %><% if (guard) { %>
|
|
3
3
|
/** Runs on the server before the loader. Return `redirect()`, `deny()` or `notFound()` to stop the request. */
|
|
4
|
-
export const
|
|
4
|
+
export const access = guard(({ url }: RequestEvent) => {
|
|
5
5
|
if (url.searchParams.has("deny")) return deny();
|
|
6
|
-
};
|
|
6
|
+
});
|
|
7
7
|
<% } %><% if (entries) { %>
|
|
8
8
|
/** Parameter values to prerender (static mode): one page per entry. */
|
|
9
|
-
export const
|
|
9
|
+
export const paths = entries(() => [<%- entries %>]);
|
|
10
10
|
<% } %><% if (loader) { %>
|
|
11
11
|
/** Data for the page, loaded on the server; removed from the browser build with everything only it uses. */
|
|
12
|
-
export const
|
|
12
|
+
export const loader = load((<% if (params.length) { %>{ params }<% } else { %>_event<% } %>: RequestEvent) => ({
|
|
13
13
|
title: <%- loaderTitle %>,
|
|
14
|
-
});
|
|
14
|
+
}));
|
|
15
15
|
<% } %><% if (action) { %>
|
|
16
|
-
export const
|
|
16
|
+
export const forms = actions({
|
|
17
17
|
/** Handles `<form method="post" action="?/save">`. Works without JavaScript. */
|
|
18
18
|
save: async ({ request, url }: RequestEvent) => {
|
|
19
19
|
const text = String((await request.formData()).get("text") ?? "").trim();
|
|
@@ -21,7 +21,7 @@ export const serverActions = {
|
|
|
21
21
|
// Post/redirect/get: reloading the page does not submit the form again.
|
|
22
22
|
return redirect(url.pathname, 303);
|
|
23
23
|
},
|
|
24
|
-
};
|
|
24
|
+
});
|
|
25
25
|
<% } %>
|
|
26
26
|
export const head = { title: "<%= title %>" };
|
|
27
27
|
|
|
@@ -2,7 +2,7 @@ import { type Tree } from "@nx/devkit";
|
|
|
2
2
|
import { routeParams } from "../../utils/project.js";
|
|
3
3
|
import type { RouteGeneratorSchema } from "./schema.js";
|
|
4
4
|
export type { RouteGeneratorSchema } from "./schema.js";
|
|
5
|
-
/** Sample `
|
|
5
|
+
/** Sample `entries(…)` value of a route's params (`{ slug: "example" }`), or undefined without required params. */
|
|
6
6
|
export declare function sampleEntry(params: ReturnType<typeof routeParams>): string | undefined;
|
|
7
7
|
/** The URL pattern of route segments (`/blog/:slug`). */
|
|
8
8
|
export declare function routeUrl(segments: string[]): string;
|
|
@@ -4,7 +4,7 @@ import { formatFiles, generateFiles, joinPathFragments } from "@nx/devkit";
|
|
|
4
4
|
import { routeComponentName, routedApp, routeParams, routeSegments } from "../../utils/project.js";
|
|
5
5
|
import { layoutGenerator } from "../layout/generator.js";
|
|
6
6
|
const FILES = fileURLToPath(new URL("./files", import.meta.url));
|
|
7
|
-
/** Sample `
|
|
7
|
+
/** Sample `entries(…)` value of a route's params (`{ slug: "example" }`), or undefined without required params. */
|
|
8
8
|
export function sampleEntry(params) {
|
|
9
9
|
const required = params.filter((param) => param.kind !== "optional");
|
|
10
10
|
if (!required.length)
|
|
@@ -26,7 +26,7 @@ export async function routeGenerator(tree, options) {
|
|
|
26
26
|
const segments = routeSegments(options.path);
|
|
27
27
|
const params = routeParams(segments);
|
|
28
28
|
if (options.action && project.facts?.static)
|
|
29
|
-
throw new Error('route:
|
|
29
|
+
throw new Error('route: actions(…) need a server; this app is prerendered (rivetRouter({ mode: "static" }))');
|
|
30
30
|
const index = !segments.length || /^\(.*\)$/.test(segments.at(-1)) || /(^|\/)index(\.\w+)?\/?$/.test(options.path) || options.path.endsWith("/");
|
|
31
31
|
const folder = joinPathFragments(project.routes, ...(index ? segments : segments.slice(0, -1)));
|
|
32
32
|
const fileName = index ? "index" : segments.at(-1);
|
|
@@ -37,7 +37,12 @@ export async function routeGenerator(tree, options) {
|
|
|
37
37
|
const name = routeComponentName(segments.filter((segment) => !/^\(.*\)$/.test(segment)));
|
|
38
38
|
const title = name.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
|
|
39
39
|
const first = params[0];
|
|
40
|
+
const entries = project.facts?.static ? (sampleEntry(params) ?? "") : "";
|
|
40
41
|
const routerImports = [
|
|
42
|
+
...(options.guard ? ["guard"] : []),
|
|
43
|
+
...(entries ? ["entries"] : []),
|
|
44
|
+
...(options.loader ? ["load"] : []),
|
|
45
|
+
...(options.action ? ["actions"] : []),
|
|
41
46
|
...(options.guard ? ["deny"] : []),
|
|
42
47
|
...(options.action ? ["redirect"] : []),
|
|
43
48
|
...(options.loader || params.length ? ["type PageProps"] : []),
|
|
@@ -54,9 +59,9 @@ export async function routeGenerator(tree, options) {
|
|
|
54
59
|
loader: Boolean(options.loader),
|
|
55
60
|
action: Boolean(options.action),
|
|
56
61
|
params,
|
|
57
|
-
entries
|
|
62
|
+
entries,
|
|
58
63
|
loaderTitle: first ? `\`${title}: \${params.${first.name} ?? ""}\`` : JSON.stringify(title),
|
|
59
|
-
props: props.length ? `{ ${props.join(", ")} }: PageProps${options.loader ? "<typeof
|
|
64
|
+
props: props.length ? `{ ${props.join(", ")} }: PageProps${options.loader ? "<typeof loader>" : ""}` : "",
|
|
60
65
|
});
|
|
61
66
|
if (options.layout && !tree.exists(joinPathFragments(folder, "_layout.tsx"))) {
|
|
62
67
|
await layoutGenerator(tree, { project: options.project, directory: folder.slice(project.routes.length + 1), skipFormat: true });
|
|
@@ -3,11 +3,11 @@ export interface RouteGeneratorSchema {
|
|
|
3
3
|
path: string;
|
|
4
4
|
/** The Rivet app. */
|
|
5
5
|
project: string;
|
|
6
|
-
/** `
|
|
6
|
+
/** A `load(…)` export (`loader`) + typed `data` prop. */
|
|
7
7
|
loader?: boolean;
|
|
8
|
-
/** `
|
|
8
|
+
/** An `actions(…)` export (`forms`) + a progressive-enhancement form (node adapter only). */
|
|
9
9
|
action?: boolean;
|
|
10
|
-
/** `
|
|
10
|
+
/** A `guard(…)` export (`access`). */
|
|
11
11
|
guard?: boolean;
|
|
12
12
|
/** Also create a `_layout.tsx` in the route's folder (when it has none). */
|
|
13
13
|
layout?: boolean;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/schema",
|
|
3
3
|
"$id": "RivetRoute",
|
|
4
4
|
"title": "Rivet route",
|
|
5
|
-
"description": "Adds a file route (page) to a Rivet app: dynamic segments ([param], [[optional]], [...rest]), groups ((name)), optional
|
|
5
|
+
"description": "Adds a file route (page) to a Rivet app: dynamic segments ([param], [[optional]], [...rest]), groups ((name)), optional load(…) / actions(…) / guard(…) exports and a folder layout.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"properties": {
|
|
8
8
|
"path": {
|
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
"x-dropdown": "projects",
|
|
19
19
|
"x-prompt": "Which project?"
|
|
20
20
|
},
|
|
21
|
-
"loader": { "type": "boolean", "default": false, "description": "Add
|
|
22
|
-
"action": { "type": "boolean", "default": false, "description": "Add
|
|
23
|
-
"guard": { "type": "boolean", "default": false, "description": "Add
|
|
21
|
+
"loader": { "type": "boolean", "default": false, "description": "Add a load(…) export and a typed data prop." },
|
|
22
|
+
"action": { "type": "boolean", "default": false, "description": "Add an actions(…) export and a form that posts to it (node adapter only)." },
|
|
23
|
+
"guard": { "type": "boolean", "default": false, "description": "Add a guard(…) export." },
|
|
24
24
|
"layout": { "type": "boolean", "default": false, "description": "Also create _layout.tsx in the route's folder (when it has none)." },
|
|
25
25
|
"skipFormat": { "type": "boolean", "default": false, "x-priority": "internal", "description": "Skip formatting files." }
|
|
26
26
|
},
|
package/generators.json
CHANGED
|
@@ -39,18 +39,18 @@
|
|
|
39
39
|
"factory": "./generators/route/generator",
|
|
40
40
|
"schema": "./generators/route/schema.json",
|
|
41
41
|
"aliases": ["r", "page"],
|
|
42
|
-
"description": "Add a file route: dynamic segments,
|
|
42
|
+
"description": "Add a file route: dynamic segments, load(…) / actions(…) / guard(…) exports, folder layout."
|
|
43
43
|
},
|
|
44
44
|
"layout": {
|
|
45
45
|
"factory": "./generators/layout/generator",
|
|
46
46
|
"schema": "./generators/layout/schema.json",
|
|
47
|
-
"description": "Add a _layout.tsx to a routes folder (optional
|
|
47
|
+
"description": "Add a _layout.tsx to a routes folder (optional guard(…) export)."
|
|
48
48
|
},
|
|
49
49
|
"api-route": {
|
|
50
50
|
"factory": "./generators/api-route/generator",
|
|
51
51
|
"schema": "./generators/api-route/schema.json",
|
|
52
52
|
"aliases": ["endpoint"],
|
|
53
|
-
"description": "Add an endpoint route (onGet / onPost / … request
|
|
53
|
+
"description": "Add an endpoint route (onGet(…) / onPost(…) / … request handler exports)."
|
|
54
54
|
},
|
|
55
55
|
"ui-setup": {
|
|
56
56
|
"factory": "./generators/ui-setup/generator",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rivetjs/nx",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Nx plugin for Rivet: generators, executors and inferred tasks for Rivet apps and libraries.",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@nx/devkit": "23.2.1",
|
|
41
|
-
"create-rivet": "0.1.0-alpha.
|
|
41
|
+
"create-rivet": "0.1.0-alpha.1"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"nx": ">=23 <24"
|