@rivetjs/nx 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +63 -2
- package/executors/build/executor.d.ts +12 -0
- package/executors/build/executor.js +50 -0
- package/executors/build/schema.d.ts +18 -0
- package/executors/build/schema.json +18 -0
- package/executors/check/executor.d.ts +11 -0
- package/executors/check/executor.js +31 -0
- package/executors/check/schema.d.ts +12 -0
- package/executors/check/schema.json +18 -0
- package/executors/dev/executor.d.ts +14 -0
- package/executors/dev/executor.js +32 -0
- package/executors/dev/schema.d.ts +16 -0
- package/executors/dev/schema.json +17 -0
- package/executors/package/executor.d.ts +20 -0
- package/executors/package/executor.js +65 -0
- package/executors/package/schema.d.ts +10 -0
- package/executors/package/schema.json +18 -0
- package/executors/prerender/executor.d.ts +8 -0
- package/executors/prerender/executor.js +13 -0
- package/executors/prerender/schema.d.ts +6 -0
- package/executors/prerender/schema.json +12 -0
- package/executors/preview/executor.d.ts +12 -0
- package/executors/preview/executor.js +52 -0
- package/executors/preview/schema.d.ts +10 -0
- package/executors/preview/schema.json +14 -0
- package/executors/run.d.ts +33 -0
- package/executors/run.js +88 -0
- package/executors/test/executor.d.ts +8 -0
- package/executors/test/executor.js +27 -0
- package/executors/test/schema.d.ts +24 -0
- package/executors/test/schema.json +21 -0
- package/executors/typecheck/executor.d.ts +9 -0
- package/executors/typecheck/executor.js +14 -0
- package/executors/typecheck/schema.d.ts +6 -0
- package/executors/typecheck/schema.json +12 -0
- package/executors.json +49 -0
- package/generators/api-route/files/__fileName__.ts.template +11 -0
- package/generators/api-route/generator.d.ts +5 -0
- package/generators/api-route/generator.js +30 -0
- package/generators/api-route/schema.d.ts +9 -0
- package/generators/api-route/schema.json +31 -0
- package/generators/application/files/tests/minimal/src/routes/index.test.tsx.template +9 -0
- package/generators/application/files/tests/server/src/routes/index.server.test.tsx.template +8 -0
- package/generators/application/files/tests/static/src/components/Counter.test.tsx.template +10 -0
- package/generators/application/files/tests/ui/src/components/LikeButton.server.test.tsx.template +7 -0
- package/generators/application/files/vitest/vitest.config.ts.template +16 -0
- package/generators/application/generator.d.ts +20 -0
- package/generators/application/generator.js +161 -0
- package/generators/application/schema.d.ts +24 -0
- package/generators/application/schema.json +57 -0
- package/generators/component/files/basic/__fileName__.tsx.template +24 -0
- package/generators/component/files/overlay/__fileName__.tsx.template +32 -0
- package/generators/component/files/story/__storyName__.tsx.template +24 -0
- package/generators/component/files/test-basic/__fileName__.test.tsx.template +10 -0
- package/generators/component/files/test-overlay/__fileName__.test.tsx.template +10 -0
- package/generators/component/generator.d.ts +5 -0
- package/generators/component/generator.js +45 -0
- package/generators/component/schema.d.ts +17 -0
- package/generators/component/schema.json +35 -0
- package/generators/i18n-setup/files/i18n.config.ts.template +10 -0
- package/generators/i18n-setup/generator.d.ts +7 -0
- package/generators/i18n-setup/generator.js +65 -0
- package/generators/i18n-setup/schema.d.ts +13 -0
- package/generators/i18n-setup/schema.json +24 -0
- package/generators/init/generator.d.ts +8 -0
- package/generators/init/generator.js +48 -0
- package/generators/init/schema.d.ts +13 -0
- package/generators/init/schema.json +19 -0
- package/generators/layout/files/_layout.tsx.template +12 -0
- package/generators/layout/generator.d.ts +5 -0
- package/generators/layout/generator.js +23 -0
- package/generators/layout/schema.d.ts +9 -0
- package/generators/layout/schema.json +26 -0
- package/generators/library/files/lib/README.md.template +18 -0
- package/generators/library/files/lib/package.json.template +12 -0
- package/generators/library/files/lib/src/__className__.tsx.template +16 -0
- package/generators/library/files/lib/src/index.ts.template +1 -0
- package/generators/library/files/lib/tsconfig.json.template +21 -0
- package/generators/library/files/publishable/tsconfig.lib.json.template +13 -0
- package/generators/library/files/subpath/src/__subpath__/__subpathClass__.tsx.template +10 -0
- package/generators/library/files/subpath/src/__subpath__/index.ts.template +1 -0
- package/generators/library/files/vitest/src/__className__.test.tsx.template +13 -0
- package/generators/library/generator.d.ts +13 -0
- package/generators/library/generator.js +116 -0
- package/generators/library/schema.d.ts +19 -0
- package/generators/library/schema.json +32 -0
- package/generators/preset/generator.d.ts +5 -0
- package/generators/preset/generator.js +29 -0
- package/generators/preset/schema.d.ts +16 -0
- package/generators/preset/schema.json +30 -0
- package/generators/route/files/__fileName__.tsx.template +43 -0
- package/generators/route/generator.d.ts +10 -0
- package/generators/route/generator.js +72 -0
- package/generators/route/schema.d.ts +15 -0
- package/generators/route/schema.json +29 -0
- package/generators/ui-setup/generator.d.ts +7 -0
- package/generators/ui-setup/generator.js +55 -0
- package/generators/ui-setup/schema.d.ts +7 -0
- package/generators/ui-setup/schema.json +21 -0
- package/generators.json +66 -0
- package/index.d.ts +22 -0
- package/index.js +24 -0
- package/migrations/0.0.1/example-noop.d.ts +2 -0
- package/migrations/0.0.1/example-noop.js +3 -0
- package/migrations.json +10 -0
- package/package.json +74 -4
- package/plugin.d.ts +12 -0
- package/plugin.js +44 -0
- package/utils/deploy.d.ts +14 -0
- package/utils/deploy.js +86 -0
- package/utils/detect.d.ts +42 -0
- package/utils/detect.js +60 -0
- package/utils/facts.d.ts +8 -0
- package/utils/facts.js +42 -0
- package/utils/project.d.ts +28 -0
- package/utils/project.js +67 -0
- package/utils/starter.d.ts +9 -0
- package/utils/starter.js +34 -0
- package/utils/targets.d.ts +22 -0
- package/utils/targets.js +117 -0
- package/utils/versions.d.ts +13 -0
- package/utils/versions.js +31 -0
- package/utils/vite-config.d.ts +8 -0
- package/utils/vite-config.js +104 -0
- package/utils/workspace.d.ts +13 -0
- package/utils/workspace.js +63 -0
package/utils/project.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// The Rivet project a generator targets (`--project`).
|
|
2
|
+
import { joinPathFragments, readProjectConfiguration } from "@nx/devkit";
|
|
3
|
+
import { treeFacts } from "./facts.js";
|
|
4
|
+
export function rivetProject(tree, name) {
|
|
5
|
+
const config = readProjectConfiguration(tree, name);
|
|
6
|
+
const root = config.root;
|
|
7
|
+
const src = config.sourceRoot ?? joinPathFragments(root, "src");
|
|
8
|
+
const facts = treeFacts(tree, root);
|
|
9
|
+
const library = config.projectType === "library" || (facts?.kind ?? "lib") === "lib";
|
|
10
|
+
return { name, root, src, routes: joinPathFragments(src, "routes"), facts, library };
|
|
11
|
+
}
|
|
12
|
+
/** Throws unless the project is a file-routed Rivet app (`rivetRouter()` in its Vite config). */
|
|
13
|
+
export function routedApp(tree, name, generator) {
|
|
14
|
+
const project = rivetProject(tree, name);
|
|
15
|
+
if (project.facts?.kind !== "app")
|
|
16
|
+
throw new Error(`${generator}: ${name} is not a Rivet app with file routes (no rivetRouter() in its vite.config)`);
|
|
17
|
+
return project;
|
|
18
|
+
}
|
|
19
|
+
/** Route path → segments: `blog/[slug]` → `["blog", "[slug]"]` (leading `/`, `src/routes/`, extension and `index` dropped). */
|
|
20
|
+
export function routeSegments(path) {
|
|
21
|
+
const clean = path
|
|
22
|
+
.replace(/\\/g, "/")
|
|
23
|
+
.replace(/^\/+|\/+$/g, "")
|
|
24
|
+
.replace(/^src\/routes\//, "")
|
|
25
|
+
.replace(/\.(tsx|ts|jsx|js)$/, "");
|
|
26
|
+
const segments = clean.split("/").filter(Boolean);
|
|
27
|
+
if (segments.at(-1) === "index")
|
|
28
|
+
segments.pop();
|
|
29
|
+
for (const segment of segments) {
|
|
30
|
+
if (!/^(\[\[[A-Za-z_$][\w$]*\]\]|\[(\.\.\.)?[A-Za-z_$][\w$]*\]|\([\w-]+\)|[\w.-]+)$/.test(segment))
|
|
31
|
+
throw new Error(`Invalid route segment "${segment}" (use name, [param], [[optional]], [...rest] or (group))`);
|
|
32
|
+
}
|
|
33
|
+
return segments;
|
|
34
|
+
}
|
|
35
|
+
export function routeParams(segments) {
|
|
36
|
+
return segments.flatMap((segment) => {
|
|
37
|
+
const optional = /^\[\[(.+)\]\]$/.exec(segment);
|
|
38
|
+
if (optional)
|
|
39
|
+
return [{ name: optional[1], kind: "optional" }];
|
|
40
|
+
const rest = /^\[\.\.\.(.+)\]$/.exec(segment);
|
|
41
|
+
if (rest)
|
|
42
|
+
return [{ name: rest[1], kind: "rest" }];
|
|
43
|
+
const one = /^\[(.+)\]$/.exec(segment);
|
|
44
|
+
return one ? [{ name: one[1], kind: "one" }] : [];
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/** `PascalCase` name of route segments (`blog/[slug]` → `BlogSlug`; root → `Home`). */
|
|
48
|
+
export function routeComponentName(segments) {
|
|
49
|
+
const words = segments
|
|
50
|
+
.map((segment) => segment.replace(/[[\]().]/g, " "))
|
|
51
|
+
.join(" ")
|
|
52
|
+
.split(/[^A-Za-z0-9]+/)
|
|
53
|
+
.filter(Boolean);
|
|
54
|
+
const name = words.map((word) => word[0].toUpperCase() + word.slice(1)).join("");
|
|
55
|
+
return name && /^[A-Za-z]/.test(name) ? name : "Home";
|
|
56
|
+
}
|
|
57
|
+
/** `../` from a file in `dir` (workspace-relative) back to `to`. */
|
|
58
|
+
export function relativeImport(fromDir, to) {
|
|
59
|
+
const from = fromDir.split("/").filter(Boolean);
|
|
60
|
+
const target = to.split("/").filter(Boolean);
|
|
61
|
+
let common = 0;
|
|
62
|
+
while (common < from.length && common < target.length - 1 && from[common] === target[common])
|
|
63
|
+
common++;
|
|
64
|
+
const up = from.length - common;
|
|
65
|
+
const rel = [...Array.from({ length: up }, () => ".."), ...target.slice(common)].join("/");
|
|
66
|
+
return rel.startsWith(".") ? rel : "./" + rel;
|
|
67
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Tree } from "@nx/devkit";
|
|
2
|
+
import { TEMPLATES, type PackageJson, type TemplateId } from "create-rivet";
|
|
3
|
+
export { TEMPLATES, type PackageJson, type TemplateId };
|
|
4
|
+
/** The folder (`starter-static`, …) of a template id. */
|
|
5
|
+
export declare function starterDir(template: TemplateId): string;
|
|
6
|
+
/** Writes a starter's files under `root` (package.json rewritten: `name`, `@rivetjs/*` → `range`). Returns the paths written. */
|
|
7
|
+
export declare function writeStarter(tree: Tree, template: TemplateId, root: string, name: string, range: string): string[];
|
|
8
|
+
/** One file of a starter (`serve.js` of `starter-server`, …). */
|
|
9
|
+
export declare function starterFile(template: TemplateId, path: string): Buffer;
|
package/utils/starter.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Starter templates in an Nx tree: the same files create-rivet copies (the repository's `templates/`, bundled into
|
|
2
|
+
// the create-rivet package), read through create-rivet's own helpers — no starter file is duplicated here.
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { findTemplate, rewriteManifest, templateFiles, templatesRoot, TEMPLATES } from "create-rivet";
|
|
6
|
+
export { TEMPLATES };
|
|
7
|
+
/** The folder (`starter-static`, …) of a template id. */
|
|
8
|
+
export function starterDir(template) {
|
|
9
|
+
const found = findTemplate(template);
|
|
10
|
+
if (!found)
|
|
11
|
+
throw new Error(`Unknown template "${template}" (${TEMPLATES.map((t) => t.id).join(", ")})`);
|
|
12
|
+
return found.dir;
|
|
13
|
+
}
|
|
14
|
+
/** Writes a starter's files under `root` (package.json rewritten: `name`, `@rivetjs/*` → `range`). Returns the paths written. */
|
|
15
|
+
export function writeStarter(tree, template, root, name, range) {
|
|
16
|
+
const source = join(templatesRoot(), starterDir(template));
|
|
17
|
+
const written = [];
|
|
18
|
+
for (const file of templateFiles(source)) {
|
|
19
|
+
const target = join(root, file.path);
|
|
20
|
+
if (file.path === "package.json") {
|
|
21
|
+
const pkg = rewriteManifest(JSON.parse(readFileSync(file.source, "utf8")), name, range);
|
|
22
|
+
tree.write(target, JSON.stringify(pkg, null, 2) + "\n");
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
tree.write(target, readFileSync(file.source));
|
|
26
|
+
}
|
|
27
|
+
written.push(file.path);
|
|
28
|
+
}
|
|
29
|
+
return written;
|
|
30
|
+
}
|
|
31
|
+
/** One file of a starter (`serve.js` of `starter-server`, …). */
|
|
32
|
+
export function starterFile(template, path) {
|
|
33
|
+
return readFileSync(join(templatesRoot(), starterDir(template), path));
|
|
34
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { TargetConfiguration } from "@nx/devkit";
|
|
2
|
+
import type { ProjectFacts } from "./detect.js";
|
|
3
|
+
/** Target names (plugin options in nx.json: `{ "plugin": "@rivetjs/nx/plugin", "options": { "buildTargetName": "bundle" } }`). */
|
|
4
|
+
export interface RivetPluginOptions {
|
|
5
|
+
buildTargetName?: string;
|
|
6
|
+
devTargetName?: string;
|
|
7
|
+
previewTargetName?: string;
|
|
8
|
+
testTargetName?: string;
|
|
9
|
+
checkTargetName?: string;
|
|
10
|
+
typecheckTargetName?: string;
|
|
11
|
+
/** Platform deploy (vercel / cloudflare adapters). */
|
|
12
|
+
deployTargetName?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare const DEFAULT_TARGET_NAMES: Required<RivetPluginOptions>;
|
|
15
|
+
export declare function targetNames(options: RivetPluginOptions | undefined): Required<RivetPluginOptions>;
|
|
16
|
+
/**
|
|
17
|
+
* The targets of a project. `production`: whether nx.json defines a `production` named input (then build inputs
|
|
18
|
+
* use it, as official plugins do); otherwise `default` minus test files.
|
|
19
|
+
*/
|
|
20
|
+
export declare function rivetTargets(facts: ProjectFacts, options?: RivetPluginOptions, { production }?: {
|
|
21
|
+
production?: boolean | undefined;
|
|
22
|
+
}): Record<string, TargetConfiguration>;
|
package/utils/targets.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { deployOutputs, deployScripts } from "./deploy.js";
|
|
2
|
+
export const DEFAULT_TARGET_NAMES = {
|
|
3
|
+
buildTargetName: "build",
|
|
4
|
+
devTargetName: "dev",
|
|
5
|
+
previewTargetName: "preview",
|
|
6
|
+
testTargetName: "test",
|
|
7
|
+
checkTargetName: "check",
|
|
8
|
+
typecheckTargetName: "typecheck",
|
|
9
|
+
deployTargetName: "deploy",
|
|
10
|
+
};
|
|
11
|
+
export function targetNames(options) {
|
|
12
|
+
const out = { ...DEFAULT_TARGET_NAMES };
|
|
13
|
+
for (const key of Object.keys(out)) {
|
|
14
|
+
const value = options?.[key];
|
|
15
|
+
if (typeof value === "string" && value)
|
|
16
|
+
out[key] = value;
|
|
17
|
+
}
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
/** Generated / output folders: never inputs (the build writes `.rivet/`, which would otherwise bust its own cache). */
|
|
21
|
+
const NOT_SOURCES = [
|
|
22
|
+
"!{projectRoot}/.rivet/**/*",
|
|
23
|
+
"!{projectRoot}/dist/**/*",
|
|
24
|
+
"!{projectRoot}/coverage/**/*",
|
|
25
|
+
"!{projectRoot}/src/rivet-i18n.d.ts",
|
|
26
|
+
// Platform packaging output / CLI state (`rivetRouter({ adapter })`).
|
|
27
|
+
"!{projectRoot}/.vercel/**/*",
|
|
28
|
+
"!{projectRoot}/.netlify/**/*",
|
|
29
|
+
"!{projectRoot}/.wrangler/**/*",
|
|
30
|
+
];
|
|
31
|
+
/** Test files are not inputs of build-like targets (Nx `production` convention). */
|
|
32
|
+
const NOT_TESTS = ["!{projectRoot}/**/*.test.{ts,tsx}", "!{projectRoot}/**/*.spec.{ts,tsx}", "!{projectRoot}/vitest.config.{ts,mts,js,mjs}"];
|
|
33
|
+
const tech = (description) => ({ technologies: ["rivet", "vite"], description });
|
|
34
|
+
/**
|
|
35
|
+
* The targets of a project. `production`: whether nx.json defines a `production` named input (then build inputs
|
|
36
|
+
* use it, as official plugins do); otherwise `default` minus test files.
|
|
37
|
+
*/
|
|
38
|
+
export function rivetTargets(facts, options, { production = false } = {}) {
|
|
39
|
+
const names = targetNames(options);
|
|
40
|
+
const source = production ? ["production", "^production"] : ["default", "^default", ...NOT_TESTS];
|
|
41
|
+
const targets = {};
|
|
42
|
+
if (facts.kind !== "lib") {
|
|
43
|
+
const buildOptions = {};
|
|
44
|
+
if (facts.locales)
|
|
45
|
+
buildOptions["locales"] = facts.locales;
|
|
46
|
+
targets[names.buildTargetName] = {
|
|
47
|
+
executor: "@rivetjs/nx:build",
|
|
48
|
+
options: buildOptions,
|
|
49
|
+
cache: true,
|
|
50
|
+
dependsOn: ["^build"],
|
|
51
|
+
inputs: [...source, ...NOT_SOURCES],
|
|
52
|
+
outputs: ["{projectRoot}/dist", ...deployOutputs(facts.adapter)],
|
|
53
|
+
metadata: tech(facts.static ? "Client + server build, then prerender every page (static mode)" : facts.csr ? "Browser build (csr mode)" : "Client + server build"),
|
|
54
|
+
};
|
|
55
|
+
targets[names.devTargetName] = {
|
|
56
|
+
executor: "@rivetjs/nx:dev",
|
|
57
|
+
continuous: true,
|
|
58
|
+
options: {},
|
|
59
|
+
metadata: tech("Vite dev server (pages resume like production; hot reload)"),
|
|
60
|
+
};
|
|
61
|
+
// Platform adapters: create-rivet's `--deploy` scripts (same commands), run from the project folder.
|
|
62
|
+
const scripts = deployScripts(facts.adapter);
|
|
63
|
+
const platformPreview = scripts["preview"] ?? scripts["start"];
|
|
64
|
+
targets[names.previewTargetName] = platformPreview
|
|
65
|
+
? {
|
|
66
|
+
executor: "nx:run-commands",
|
|
67
|
+
continuous: true,
|
|
68
|
+
dependsOn: [names.buildTargetName],
|
|
69
|
+
options: { command: platformPreview, cwd: "{projectRoot}" },
|
|
70
|
+
metadata: tech(`Serve the ${facts.adapter} build locally (${platformPreview})`),
|
|
71
|
+
}
|
|
72
|
+
: {
|
|
73
|
+
executor: "@rivetjs/nx:preview",
|
|
74
|
+
continuous: true,
|
|
75
|
+
dependsOn: [names.buildTargetName],
|
|
76
|
+
options: {},
|
|
77
|
+
metadata: tech(facts.serveScript ? "Serve the build with the project's serve.js" : "Serve the build"),
|
|
78
|
+
};
|
|
79
|
+
if (scripts["deploy"])
|
|
80
|
+
targets[names.deployTargetName] = {
|
|
81
|
+
executor: "nx:run-commands",
|
|
82
|
+
dependsOn: [names.buildTargetName],
|
|
83
|
+
options: { command: scripts["deploy"], cwd: "{projectRoot}" },
|
|
84
|
+
metadata: tech(`Deploy the build to ${facts.adapter} (${scripts["deploy"]})`),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (facts.test) {
|
|
88
|
+
targets[names.testTargetName] = {
|
|
89
|
+
executor: "@rivetjs/nx:test",
|
|
90
|
+
options: {},
|
|
91
|
+
cache: true,
|
|
92
|
+
inputs: ["default", production ? "^production" : "^default", ...NOT_SOURCES],
|
|
93
|
+
outputs: ["{projectRoot}/coverage"],
|
|
94
|
+
metadata: { technologies: ["rivet", "vitest"], description: "Vitest with rivetTest() (DOM + server targets)" },
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
if (facts.tsconfig) {
|
|
98
|
+
const typeInputs = ["default", production ? "^production" : "^default", ...NOT_SOURCES];
|
|
99
|
+
targets[names.checkTargetName] = {
|
|
100
|
+
executor: "@rivetjs/nx:check",
|
|
101
|
+
options: {},
|
|
102
|
+
cache: true,
|
|
103
|
+
inputs: typeInputs,
|
|
104
|
+
outputs: [],
|
|
105
|
+
metadata: tech("Rivet diagnostics (rivet-check: compiler + serializability + route links)"),
|
|
106
|
+
};
|
|
107
|
+
targets[names.typecheckTargetName] = {
|
|
108
|
+
executor: "@rivetjs/nx:typecheck",
|
|
109
|
+
options: {},
|
|
110
|
+
cache: true,
|
|
111
|
+
inputs: typeInputs,
|
|
112
|
+
outputs: [],
|
|
113
|
+
metadata: { technologies: ["rivet", "typescript"], description: "tsc --noEmit (after rivet sync for routed apps)" },
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return targets;
|
|
117
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** This package's version (= every `@rivetjs/*` package of the same release). */
|
|
2
|
+
export declare const RIVET_NX_VERSION: string;
|
|
3
|
+
/** The `@nx/devkit` version this plugin is built against. */
|
|
4
|
+
export declare const NX_VERSION: string;
|
|
5
|
+
/** The range written for `@rivetjs/*` dependencies (`--rivetVersion` overrides): exact for prereleases, `^x.y.z` otherwise. */
|
|
6
|
+
export declare function rivetRange(override?: string): string;
|
|
7
|
+
/** Test tooling ranges = `@rivetjs/testing` peer ranges (`tests/versions.test.ts` keeps them in sync). */
|
|
8
|
+
export declare const VITEST_VERSION = "^5.0.3";
|
|
9
|
+
export declare const HAPPY_DOM_VERSION = "^20.14.5";
|
|
10
|
+
/** Dependency ranges of a starter template's package.json (`starter-static`, …): the single source for tool versions. */
|
|
11
|
+
export declare function starterVersions(dir?: string): Record<string, string>;
|
|
12
|
+
/** A tool range from the starters (`vite`, `typescript`, `tailwindcss`, …). Throws when no starter names it. */
|
|
13
|
+
export declare function toolVersion(name: string): string;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Versions written into generated projects. `@rivetjs/*` follow this package's own version (every Rivet package is
|
|
2
|
+
// released with the same version, like create-rivet); tool ranges come from the starter templates where they exist.
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { templatesRoot, versionRange } from "create-rivet";
|
|
6
|
+
const own = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
7
|
+
/** This package's version (= every `@rivetjs/*` package of the same release). */
|
|
8
|
+
export const RIVET_NX_VERSION = own.version;
|
|
9
|
+
/** The `@nx/devkit` version this plugin is built against. */
|
|
10
|
+
export const NX_VERSION = own.dependencies?.["@nx/devkit"] ?? "23.2.1";
|
|
11
|
+
/** The range written for `@rivetjs/*` dependencies (`--rivetVersion` overrides): exact for prereleases, `^x.y.z` otherwise. */
|
|
12
|
+
export function rivetRange(override) {
|
|
13
|
+
return override || versionRange(RIVET_NX_VERSION);
|
|
14
|
+
}
|
|
15
|
+
/** Test tooling ranges = `@rivetjs/testing` peer ranges (`tests/versions.test.ts` keeps them in sync). */
|
|
16
|
+
export const VITEST_VERSION = "^5.0.3";
|
|
17
|
+
export const HAPPY_DOM_VERSION = "^20.14.5";
|
|
18
|
+
/** Dependency ranges of a starter template's package.json (`starter-static`, …): the single source for tool versions. */
|
|
19
|
+
export function starterVersions(dir = "starter-static") {
|
|
20
|
+
const pkg = JSON.parse(readFileSync(join(templatesRoot(), dir, "package.json"), "utf8"));
|
|
21
|
+
return { ...pkg.dependencies, ...pkg.devDependencies };
|
|
22
|
+
}
|
|
23
|
+
/** A tool range from the starters (`vite`, `typescript`, `tailwindcss`, …). Throws when no starter names it. */
|
|
24
|
+
export function toolVersion(name) {
|
|
25
|
+
for (const dir of ["starter-static", "starter-ui", "starter-server", "starter-minimal"]) {
|
|
26
|
+
const range = starterVersions(dir)[name];
|
|
27
|
+
if (range && !name.startsWith("@rivetjs/"))
|
|
28
|
+
return range;
|
|
29
|
+
}
|
|
30
|
+
throw new Error(`@rivetjs/nx: no starter template names a version of ${name}`);
|
|
31
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Adds `line` after the last top-level import (or at the top). No-op when already present. */
|
|
2
|
+
export declare function addImport(source: string, line: string): string;
|
|
3
|
+
/** Adds a plugin call (`i18n(config)`) first or last in `plugins`. No-op when the call name is already there. */
|
|
4
|
+
export declare function addPlugin(source: string, call: string, position?: "first" | "last"): string;
|
|
5
|
+
/** Removes a plugin call (`tailwindcss()`) from `plugins` and the import line matching `importPattern`. */
|
|
6
|
+
export declare function removePlugin(source: string, call: string, importPattern: RegExp): string;
|
|
7
|
+
/** Sets (`value` string) or removes (`undefined`) a top-level option of `rivetRouter({ … })` (`mode`, `styles`). */
|
|
8
|
+
export declare function setRouterOption(source: string, key: string, value: string | undefined): string;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// Text edits of a Vite config (`plugins: [ … ]`, `rivetRouter({ … })`). Configs written by the starters and the
|
|
2
|
+
// generators have one `defineConfig({ plugins: [...] })`; edits are idempotent and throw on shapes they cannot edit.
|
|
3
|
+
/** Adds `line` after the last top-level import (or at the top). No-op when already present. */
|
|
4
|
+
export function addImport(source, line) {
|
|
5
|
+
if (source.includes(line))
|
|
6
|
+
return source;
|
|
7
|
+
const imports = [...source.matchAll(/^import[^;]*;[^\n]*\n/gm)];
|
|
8
|
+
const last = imports.at(-1);
|
|
9
|
+
if (!last)
|
|
10
|
+
return line + "\n" + source;
|
|
11
|
+
const at = last.index + last[0].length;
|
|
12
|
+
return source.slice(0, at) + line + "\n" + source.slice(at);
|
|
13
|
+
}
|
|
14
|
+
/** Index just inside the `[` of `plugins: [` and of its matching `]`. */
|
|
15
|
+
function pluginsArray(source) {
|
|
16
|
+
const match = /\bplugins\s*:\s*\[/.exec(source);
|
|
17
|
+
if (!match)
|
|
18
|
+
throw new Error("vite config: no `plugins: [ … ]` array to edit");
|
|
19
|
+
const open = match.index + match[0].length;
|
|
20
|
+
let depth = 1;
|
|
21
|
+
for (let i = open; i < source.length; i++) {
|
|
22
|
+
const char = source[i];
|
|
23
|
+
if (char === "[" || char === "(" || char === "{")
|
|
24
|
+
depth++;
|
|
25
|
+
else if (char === "]" || char === ")" || char === "}") {
|
|
26
|
+
depth--;
|
|
27
|
+
if (depth === 0)
|
|
28
|
+
return { open, close: i };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
throw new Error("vite config: unbalanced `plugins` array");
|
|
32
|
+
}
|
|
33
|
+
/** Adds a plugin call (`i18n(config)`) first or last in `plugins`. No-op when the call name is already there. */
|
|
34
|
+
export function addPlugin(source, call, position = "last") {
|
|
35
|
+
const { open, close } = pluginsArray(source);
|
|
36
|
+
const name = call.slice(0, call.indexOf("("));
|
|
37
|
+
const list = source.slice(open, close);
|
|
38
|
+
if (new RegExp(`\\b${name}\\s*\\(`).test(list))
|
|
39
|
+
return source;
|
|
40
|
+
if (!list.trim())
|
|
41
|
+
return source.slice(0, open) + call + source.slice(close);
|
|
42
|
+
if (position === "first")
|
|
43
|
+
return source.slice(0, open) + call + ", " + source.slice(open);
|
|
44
|
+
const trimmed = list.replace(/[\s,]*$/, "");
|
|
45
|
+
return source.slice(0, open) + trimmed + ", " + call + source.slice(open + list.length);
|
|
46
|
+
}
|
|
47
|
+
/** Removes a plugin call (`tailwindcss()`) from `plugins` and the import line matching `importPattern`. */
|
|
48
|
+
export function removePlugin(source, call, importPattern) {
|
|
49
|
+
const { open, close } = pluginsArray(source);
|
|
50
|
+
const list = source.slice(open, close);
|
|
51
|
+
const escaped = call.replace(/[()]/g, "\\$&");
|
|
52
|
+
const next = list.replace(new RegExp(`\\s*,\\s*${escaped}|${escaped}\\s*,\\s*|${escaped}`), "");
|
|
53
|
+
return (source.slice(0, open) + next + source.slice(close)).replace(new RegExp(`^.*${importPattern.source}.*\\n`, "m"), "");
|
|
54
|
+
}
|
|
55
|
+
/** Start / end (exclusive, at `)`) of the first `rivetRouter(` call's argument text. */
|
|
56
|
+
function routerCall(source) {
|
|
57
|
+
const match = /\brivetRouter\s*\(/.exec(source);
|
|
58
|
+
if (!match)
|
|
59
|
+
throw new Error("vite config: no rivetRouter( … ) call to edit");
|
|
60
|
+
const start = match.index + match[0].length;
|
|
61
|
+
let depth = 1;
|
|
62
|
+
for (let i = start; i < source.length; i++) {
|
|
63
|
+
const char = source[i];
|
|
64
|
+
if (char === "(" || char === "[" || char === "{")
|
|
65
|
+
depth++;
|
|
66
|
+
else if (char === ")" || char === "]" || char === "}") {
|
|
67
|
+
depth--;
|
|
68
|
+
if (depth === 0)
|
|
69
|
+
return { start, end: i };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
throw new Error("vite config: unbalanced rivetRouter( … ) call");
|
|
73
|
+
}
|
|
74
|
+
/** Sets (`value` string) or removes (`undefined`) a top-level option of `rivetRouter({ … })` (`mode`, `styles`). */
|
|
75
|
+
export function setRouterOption(source, key, value) {
|
|
76
|
+
const { start, end } = routerCall(source);
|
|
77
|
+
let args = source.slice(start, end).trim();
|
|
78
|
+
if (!args)
|
|
79
|
+
args = "{}";
|
|
80
|
+
if (!args.startsWith("{"))
|
|
81
|
+
throw new Error("vite config: rivetRouter( … ) argument is not an object literal");
|
|
82
|
+
const inner = args.slice(1, -1);
|
|
83
|
+
// Top-level `key: value` inside the object (values here are literals / arrays: no nested commas outside brackets).
|
|
84
|
+
const entries = [];
|
|
85
|
+
let depth = 0;
|
|
86
|
+
let current = "";
|
|
87
|
+
for (const char of inner) {
|
|
88
|
+
if (char === "[" || char === "{" || char === "(")
|
|
89
|
+
depth++;
|
|
90
|
+
if (char === "]" || char === "}" || char === ")")
|
|
91
|
+
depth--;
|
|
92
|
+
if (char === "," && depth === 0) {
|
|
93
|
+
entries.push(current);
|
|
94
|
+
current = "";
|
|
95
|
+
}
|
|
96
|
+
else
|
|
97
|
+
current += char;
|
|
98
|
+
}
|
|
99
|
+
entries.push(current);
|
|
100
|
+
const kept = entries.map((entry) => entry.trim()).filter((entry) => entry && !new RegExp(`^${key}\\s*:`).test(entry));
|
|
101
|
+
const next = value === undefined ? kept : key === "mode" ? [`${key}: ${value}`, ...kept] : [...kept, `${key}: ${value}`];
|
|
102
|
+
const text = next.length ? `{ ${next.join(", ")} }` : "";
|
|
103
|
+
return source.slice(0, start) + text + source.slice(end);
|
|
104
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type Tree } from "@nx/devkit";
|
|
2
|
+
/** Whether new projects get inferred targets (official plugins' rule: `NX_ADD_PLUGINS` and nx.json `useInferencePlugins`). */
|
|
3
|
+
export declare function shouldAddPlugin(tree: Tree, addPlugin: boolean | undefined): boolean;
|
|
4
|
+
/** Appends missing lines to a `.gitignore`-style file. */
|
|
5
|
+
export declare function ensureLines(tree: Tree, path: string, lines: string[]): void;
|
|
6
|
+
/** The package manager workspaces globs (npm / yarn / bun `workspaces`, pnpm-workspace.yaml `packages`). */
|
|
7
|
+
export declare function workspaceGlobs(tree: Tree): string[] | undefined;
|
|
8
|
+
/** Whether `root` is a package manager workspace (its package.json dependencies get installed by the root install). */
|
|
9
|
+
export declare function isWorkspacePackage(tree: Tree, root: string): boolean;
|
|
10
|
+
/** Adds `root` to npm / yarn / bun `workspaces` when the root package.json has a workspaces list that misses it. */
|
|
11
|
+
export declare function addToWorkspaces(tree: Tree, root: string): void;
|
|
12
|
+
/** `../..` from a project root to the workspace root. */
|
|
13
|
+
export declare function offsetTo(root: string): string;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Workspace-level helpers shared by the generators.
|
|
2
|
+
import { readJson, readNxJson, updateJson } from "@nx/devkit";
|
|
3
|
+
/** Whether new projects get inferred targets (official plugins' rule: `NX_ADD_PLUGINS` and nx.json `useInferencePlugins`). */
|
|
4
|
+
export function shouldAddPlugin(tree, addPlugin) {
|
|
5
|
+
if (addPlugin !== undefined)
|
|
6
|
+
return addPlugin;
|
|
7
|
+
if (process.env["NX_ADD_PLUGINS"] === "false")
|
|
8
|
+
return false;
|
|
9
|
+
return readNxJson(tree)?.useInferencePlugins !== false;
|
|
10
|
+
}
|
|
11
|
+
/** Appends missing lines to a `.gitignore`-style file. */
|
|
12
|
+
export function ensureLines(tree, path, lines) {
|
|
13
|
+
const text = tree.exists(path) ? tree.read(path, "utf8") : "";
|
|
14
|
+
const have = new Set(text.split("\n").map((line) => line.trim()));
|
|
15
|
+
const missing = lines.filter((line) => !have.has(line));
|
|
16
|
+
if (!missing.length)
|
|
17
|
+
return;
|
|
18
|
+
tree.write(path, (text && !text.endsWith("\n") ? text + "\n" : text) + missing.join("\n") + "\n");
|
|
19
|
+
}
|
|
20
|
+
/** A workspaces glob (`apps/*`, `libs/**`) as a RegExp over project roots. */
|
|
21
|
+
function globRegExp(pattern) {
|
|
22
|
+
const body = pattern
|
|
23
|
+
.replace(/\/+$/, "")
|
|
24
|
+
.split("/")
|
|
25
|
+
.map((part) => (part === "**" ? ".*" : part.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]*")))
|
|
26
|
+
.join("/");
|
|
27
|
+
return new RegExp(`^${body}$`);
|
|
28
|
+
}
|
|
29
|
+
/** The package manager workspaces globs (npm / yarn / bun `workspaces`, pnpm-workspace.yaml `packages`). */
|
|
30
|
+
export function workspaceGlobs(tree) {
|
|
31
|
+
if (tree.exists("pnpm-workspace.yaml")) {
|
|
32
|
+
const text = tree.read("pnpm-workspace.yaml", "utf8");
|
|
33
|
+
return [...text.matchAll(/^\s*-\s*["']?([^"'\n]+)["']?\s*$/gm)].map((match) => match[1].trim());
|
|
34
|
+
}
|
|
35
|
+
if (!tree.exists("package.json"))
|
|
36
|
+
return undefined;
|
|
37
|
+
const workspaces = readJson(tree, "package.json").workspaces;
|
|
38
|
+
if (!workspaces)
|
|
39
|
+
return undefined;
|
|
40
|
+
return Array.isArray(workspaces) ? workspaces : (workspaces.packages ?? []);
|
|
41
|
+
}
|
|
42
|
+
/** Whether `root` is a package manager workspace (its package.json dependencies get installed by the root install). */
|
|
43
|
+
export function isWorkspacePackage(tree, root) {
|
|
44
|
+
const globs = workspaceGlobs(tree);
|
|
45
|
+
return Boolean(globs?.some((glob) => !glob.startsWith("!") && globRegExp(glob).test(root)));
|
|
46
|
+
}
|
|
47
|
+
/** Adds `root` to npm / yarn / bun `workspaces` when the root package.json has a workspaces list that misses it. */
|
|
48
|
+
export function addToWorkspaces(tree, root) {
|
|
49
|
+
if (tree.exists("pnpm-workspace.yaml") || !tree.exists("package.json") || isWorkspacePackage(tree, root))
|
|
50
|
+
return;
|
|
51
|
+
updateJson(tree, "package.json", (pkg) => {
|
|
52
|
+
if (Array.isArray(pkg.workspaces))
|
|
53
|
+
pkg.workspaces.push(root);
|
|
54
|
+
else if (pkg.workspaces)
|
|
55
|
+
(pkg.workspaces.packages ??= []).push(root);
|
|
56
|
+
return pkg;
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/** `../..` from a project root to the workspace root. */
|
|
60
|
+
export function offsetTo(root) {
|
|
61
|
+
const depth = root.split("/").filter((part) => part && part !== ".").length;
|
|
62
|
+
return depth ? Array.from({ length: depth }, () => "..").join("/") : ".";
|
|
63
|
+
}
|