@stratal/inertia 0.0.20 → 0.0.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/decorate-CzXVx7ZH.mjs +9 -0
- package/dist/generator/type-generator.worker.d.mts +1 -0
- package/dist/generator/type-generator.worker.mjs +25 -0
- package/dist/generator/type-generator.worker.mjs.map +1 -0
- package/dist/index.d.mts +68 -105
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +46 -383
- package/dist/index.mjs.map +1 -1
- package/dist/quarry.d.mts +44 -0
- package/dist/quarry.d.mts.map +1 -0
- package/dist/quarry.mjs +392 -0
- package/dist/quarry.mjs.map +1 -0
- package/dist/react.d.mts +1 -31
- package/dist/react.d.mts.map +1 -1
- package/dist/react.mjs +10 -42
- package/dist/react.mjs.map +1 -1
- package/dist/testing.mjs.map +1 -1
- package/dist/{type-generator-C5JljyzK.mjs → type-generator-bfo14BJI.mjs} +75 -14
- package/dist/type-generator-bfo14BJI.mjs.map +1 -0
- package/dist/vite.d.mts +19 -0
- package/dist/vite.d.mts.map +1 -1
- package/dist/vite.mjs +177 -14
- package/dist/vite.mjs.map +1 -1
- package/package.json +19 -21
- package/dist/type-generator-C5JljyzK.mjs.map +0 -1
package/dist/quarry.mjs
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
import { t as __decorate } from "./decorate-CzXVx7ZH.mjs";
|
|
2
|
+
import { n as runTypeGeneration, t as findPagesDir } from "./type-generator-bfo14BJI.mjs";
|
|
3
|
+
import { Module } from "stratal/module";
|
|
4
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { dirname, join, relative } from "node:path";
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
7
|
+
import { Command } from "stratal/quarry";
|
|
8
|
+
import { watch } from "node:fs/promises";
|
|
9
|
+
//#region src/vite/create-client-vite-config.ts
|
|
10
|
+
/**
|
|
11
|
+
* Emits a standalone Vite config for building the Inertia browser bundle.
|
|
12
|
+
*
|
|
13
|
+
* This runs as a separate `vite build` invocation BEFORE the worker build so
|
|
14
|
+
* the worker's `stratal:inertia-inject-manifest` plugin has a finished
|
|
15
|
+
* `<outDir>/.vite/manifest.json` to read. `@cloudflare/vite-plugin` builds its
|
|
16
|
+
* environments in parallel, which made a single-config build racy — splitting
|
|
17
|
+
* the two phases removes the race entirely and keeps each build minimal.
|
|
18
|
+
*/
|
|
19
|
+
function writeTempClientViteConfig(options) {
|
|
20
|
+
const configPath = join(join(options.cwd, "node_modules", ".stratal"), "vite.client.config.mjs");
|
|
21
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
22
|
+
const entry = (options.entry ?? "src/inertia/app.tsx").replace(/\\/g, "/");
|
|
23
|
+
const outDir = (options.outDir ?? "dist/client").replace(/\\/g, "/");
|
|
24
|
+
const hasUserConfig = existsSync(join(options.cwd, "vite.config.ts"));
|
|
25
|
+
writeFileSync(configPath, `
|
|
26
|
+
import { mergeConfig } from 'vite'
|
|
27
|
+
|
|
28
|
+
const baseConfig = {
|
|
29
|
+
publicDir: '${join(options.cwd, "src", "inertia", "public").replace(/\\/g, "/")}',
|
|
30
|
+
build: {
|
|
31
|
+
outDir: '${outDir}',
|
|
32
|
+
manifest: true,
|
|
33
|
+
emptyOutDir: true,
|
|
34
|
+
rollupOptions: {
|
|
35
|
+
input: { app: '${entry}' },
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
${hasUserConfig ? `const userModule = await import('${join(options.cwd, "vite.config.ts").replace(/\\/g, "/")}')
|
|
41
|
+
const userConfig = userModule.default ?? userModule
|
|
42
|
+
export default mergeConfig(userConfig, baseConfig)` : "export default baseConfig"}
|
|
43
|
+
`, "utf-8");
|
|
44
|
+
return configPath;
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/vite/create-vite-config.ts
|
|
48
|
+
function writeTempViteConfig(options) {
|
|
49
|
+
const configPath = join(join(options.cwd, "node_modules", ".stratal"), "vite.config.mjs");
|
|
50
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
51
|
+
const hasUserConfig = existsSync(join(options.cwd, "vite.config.ts"));
|
|
52
|
+
const serverConfig = options.server ? `server: { port: ${options.server.port}, host: ${options.server.host ? "true" : "undefined"} },` : "";
|
|
53
|
+
const outDirConfig = options.outDir ? `outDir: '${options.outDir}',` : "";
|
|
54
|
+
writeFileSync(configPath, `
|
|
55
|
+
import { mergeConfig } from 'vite'
|
|
56
|
+
import { cloudflare } from '@cloudflare/vite-plugin'
|
|
57
|
+
import { stratalInertia } from '@stratal/inertia/vite'
|
|
58
|
+
|
|
59
|
+
let inertiaPlugin = null
|
|
60
|
+
try {
|
|
61
|
+
const mod = await import('@inertiajs/vite')
|
|
62
|
+
const inertia = mod.default ?? mod
|
|
63
|
+
inertiaPlugin = inertia()
|
|
64
|
+
} catch {}
|
|
65
|
+
|
|
66
|
+
const baseConfig = {
|
|
67
|
+
plugins: [
|
|
68
|
+
cloudflare(${options.persistTo ? `{ persistState: { path: ${JSON.stringify(options.persistTo)} } }` : ""}),
|
|
69
|
+
...(inertiaPlugin ? [inertiaPlugin] : []),
|
|
70
|
+
...stratalInertia(${options.clientManifestPath ? `{ clientManifestPath: ${JSON.stringify(options.clientManifestPath)} }` : ""}),
|
|
71
|
+
],
|
|
72
|
+
publicDir: '${join(options.cwd, "src", "inertia", "public").replace(/\\/g, "/")}',
|
|
73
|
+
build: {
|
|
74
|
+
${outDirConfig}
|
|
75
|
+
},
|
|
76
|
+
${serverConfig}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
${hasUserConfig ? `const userModule = await import('${join(options.cwd, "vite.config.ts").replace(/\\/g, "/")}')
|
|
80
|
+
const userConfig = userModule.default ?? userModule
|
|
81
|
+
export default mergeConfig(baseConfig, userConfig)` : "export default baseConfig"}
|
|
82
|
+
`, "utf-8");
|
|
83
|
+
return configPath;
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
//#region src/commands/inertia-build.command.ts
|
|
87
|
+
var InertiaBuildCommand = class extends Command {
|
|
88
|
+
static command = "inertia:build {--outDir=dist : Output directory} {--ssr : Also build SSR bundle}";
|
|
89
|
+
static description = "Build Inertia.js frontend for production";
|
|
90
|
+
async handle() {
|
|
91
|
+
const outDir = this.string("outDir") || "dist";
|
|
92
|
+
const shouldBuildSsr = this.boolean("ssr");
|
|
93
|
+
const cwd = process.cwd();
|
|
94
|
+
const entryPath = "src/inertia/app.tsx";
|
|
95
|
+
if (!existsSync(join(cwd, entryPath))) {
|
|
96
|
+
this.fail("src/inertia/app.tsx not found. Run `quarry inertia:install` first.");
|
|
97
|
+
return 1;
|
|
98
|
+
}
|
|
99
|
+
const clientOutDir = join(outDir, "client").replace(/\\/g, "/");
|
|
100
|
+
const clientConfigPath = writeTempClientViteConfig({
|
|
101
|
+
cwd,
|
|
102
|
+
entry: entryPath,
|
|
103
|
+
outDir: clientOutDir
|
|
104
|
+
});
|
|
105
|
+
this.info("Building Inertia.js browser bundle...");
|
|
106
|
+
const browserCode = await this.spawnVite(cwd, clientConfigPath, ["build"]);
|
|
107
|
+
if (browserCode !== 0) {
|
|
108
|
+
this.fail("Browser bundle build failed.");
|
|
109
|
+
return browserCode;
|
|
110
|
+
}
|
|
111
|
+
this.success(`Browser bundle written to ${clientOutDir}/`);
|
|
112
|
+
const configPath = writeTempViteConfig({
|
|
113
|
+
cwd,
|
|
114
|
+
outDir,
|
|
115
|
+
clientManifestPath: join(clientOutDir, ".vite", "manifest.json").replace(/\\/g, "/")
|
|
116
|
+
});
|
|
117
|
+
this.info("Building Cloudflare worker bundle...");
|
|
118
|
+
const workerCode = await this.spawnVite(cwd, configPath, ["build"]);
|
|
119
|
+
if (workerCode !== 0) {
|
|
120
|
+
this.fail("Worker build failed.");
|
|
121
|
+
return workerCode;
|
|
122
|
+
}
|
|
123
|
+
this.success("Worker build complete!");
|
|
124
|
+
if (shouldBuildSsr) {
|
|
125
|
+
this.info("Building SSR bundle...");
|
|
126
|
+
const ssrCode = await this.spawnVite(cwd, configPath, ["build", "--ssr"]);
|
|
127
|
+
if (ssrCode !== 0) {
|
|
128
|
+
this.fail("SSR build failed.");
|
|
129
|
+
return ssrCode;
|
|
130
|
+
}
|
|
131
|
+
this.success("SSR build complete!");
|
|
132
|
+
}
|
|
133
|
+
this.success(`Output in ${outDir}/`);
|
|
134
|
+
this.info("Deploy with: npx wrangler deploy");
|
|
135
|
+
return 0;
|
|
136
|
+
}
|
|
137
|
+
spawnVite(cwd, configPath, args) {
|
|
138
|
+
return new Promise((resolve) => {
|
|
139
|
+
const child = spawn("npx", [
|
|
140
|
+
"vite",
|
|
141
|
+
"--config",
|
|
142
|
+
configPath,
|
|
143
|
+
...args
|
|
144
|
+
], {
|
|
145
|
+
cwd,
|
|
146
|
+
stdio: "inherit",
|
|
147
|
+
shell: true
|
|
148
|
+
});
|
|
149
|
+
child.on("error", (err) => {
|
|
150
|
+
this.fail(`Vite process error: ${err.message}`);
|
|
151
|
+
resolve(1);
|
|
152
|
+
});
|
|
153
|
+
child.on("close", (code) => {
|
|
154
|
+
resolve(code ?? 0);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
//#endregion
|
|
160
|
+
//#region src/commands/inertia-dev.command.ts
|
|
161
|
+
var InertiaDevCommand = class extends Command {
|
|
162
|
+
static command = "inertia:dev {--port= : Dev server port} {--host : Expose to network} {--persist-to= : Shared persist directory for @cloudflare/vite-plugin (relative to cwd; the plugin appends /v3). Use to share R2/KV/cache emulator state across multiple workers in dev.}";
|
|
163
|
+
static description = "Start Inertia.js Vite development server";
|
|
164
|
+
async handle() {
|
|
165
|
+
const port = this.number("port");
|
|
166
|
+
const host = this.boolean("host");
|
|
167
|
+
const persistTo = this.string("persist-to");
|
|
168
|
+
const cwd = process.cwd();
|
|
169
|
+
if (!existsSync(join(cwd, "src/inertia/app.tsx"))) {
|
|
170
|
+
this.fail("src/inertia/app.tsx not found. Run `quarry inertia:install` first.");
|
|
171
|
+
return 1;
|
|
172
|
+
}
|
|
173
|
+
const configPath = writeTempViteConfig({
|
|
174
|
+
cwd,
|
|
175
|
+
server: {
|
|
176
|
+
port,
|
|
177
|
+
host
|
|
178
|
+
},
|
|
179
|
+
persistTo
|
|
180
|
+
});
|
|
181
|
+
this.info("Starting Vite dev server...");
|
|
182
|
+
const args = [
|
|
183
|
+
"vite",
|
|
184
|
+
"dev",
|
|
185
|
+
"--config",
|
|
186
|
+
configPath
|
|
187
|
+
];
|
|
188
|
+
if (host) args.push("--host");
|
|
189
|
+
return new Promise((resolve) => {
|
|
190
|
+
const child = spawn("npx", args, {
|
|
191
|
+
cwd,
|
|
192
|
+
stdio: "inherit",
|
|
193
|
+
shell: true
|
|
194
|
+
});
|
|
195
|
+
child.on("error", (err) => {
|
|
196
|
+
this.fail(`Failed to start dev server: ${err.message}`);
|
|
197
|
+
resolve(1);
|
|
198
|
+
});
|
|
199
|
+
child.on("close", (code) => {
|
|
200
|
+
resolve(code ?? 0);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
//#endregion
|
|
206
|
+
//#region src/commands/inertia-install.command.ts
|
|
207
|
+
const ROOT_HTML = `<!DOCTYPE html>
|
|
208
|
+
<html lang="en">
|
|
209
|
+
<head>
|
|
210
|
+
<meta charset="utf-8" />
|
|
211
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
212
|
+
@viteHead
|
|
213
|
+
@inertiaHead
|
|
214
|
+
</head>
|
|
215
|
+
<body>
|
|
216
|
+
@inertia
|
|
217
|
+
@viteScripts
|
|
218
|
+
</body>
|
|
219
|
+
</html>`;
|
|
220
|
+
const APP_TSX = `import { createInertiaApp } from '@inertiajs/react'
|
|
221
|
+
|
|
222
|
+
createInertiaApp({
|
|
223
|
+
resolve: async (name) => {
|
|
224
|
+
const pages = import.meta.glob('./pages/**/*.tsx')
|
|
225
|
+
const page = await pages[\`./pages/\${name}.tsx\`]?.()
|
|
226
|
+
if (!page) throw new Error(\`Page not found: \${name}\`)
|
|
227
|
+
return page
|
|
228
|
+
},
|
|
229
|
+
})`;
|
|
230
|
+
const HOME_TSX = `export default function Home({ message }: { message: string }) {
|
|
231
|
+
return (
|
|
232
|
+
<div>
|
|
233
|
+
<h1>{message}</h1>
|
|
234
|
+
<p>This page is rendered with Inertia.js and Stratal.</p>
|
|
235
|
+
</div>
|
|
236
|
+
)
|
|
237
|
+
}`;
|
|
238
|
+
var InertiaInstallCommand = class extends Command {
|
|
239
|
+
static command = "inertia:install {--skip-deps : Skip installing npm dependencies}";
|
|
240
|
+
static description = "Scaffold Inertia.js files for a Stratal project";
|
|
241
|
+
async handle() {
|
|
242
|
+
const skipDeps = this.boolean("skip-deps");
|
|
243
|
+
const cwd = process.cwd();
|
|
244
|
+
const inertiaDir = join(cwd, "src", "inertia");
|
|
245
|
+
const pagesDir = join(inertiaDir, "pages");
|
|
246
|
+
this.info("Creating src/inertia/ directory...");
|
|
247
|
+
mkdirSync(pagesDir, { recursive: true });
|
|
248
|
+
const publicDir = join(inertiaDir, "public");
|
|
249
|
+
mkdirSync(publicDir, { recursive: true });
|
|
250
|
+
const gitkeepPath = join(publicDir, ".gitkeep");
|
|
251
|
+
if (!existsSync(gitkeepPath)) writeFileSync(gitkeepPath, "", "utf-8");
|
|
252
|
+
this.success("Created src/inertia/public/");
|
|
253
|
+
const files = [
|
|
254
|
+
{
|
|
255
|
+
path: join(inertiaDir, "root.html"),
|
|
256
|
+
content: ROOT_HTML,
|
|
257
|
+
name: "root.html"
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
path: join(inertiaDir, "app.tsx"),
|
|
261
|
+
content: APP_TSX,
|
|
262
|
+
name: "app.tsx"
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
path: join(pagesDir, "Home.tsx"),
|
|
266
|
+
content: HOME_TSX,
|
|
267
|
+
name: "pages/Home.tsx"
|
|
268
|
+
}
|
|
269
|
+
];
|
|
270
|
+
for (const file of files) if (existsSync(file.path)) this.warn(`Skipping ${file.name} (already exists)`);
|
|
271
|
+
else {
|
|
272
|
+
writeFileSync(file.path, file.content, "utf-8");
|
|
273
|
+
this.success(`Created src/inertia/${file.name}`);
|
|
274
|
+
}
|
|
275
|
+
const appModulePath = join(cwd, "src", "app.module.ts");
|
|
276
|
+
if (existsSync(appModulePath)) {
|
|
277
|
+
this.info("Updating src/app.module.ts...");
|
|
278
|
+
try {
|
|
279
|
+
if (await this.updateAppModule(appModulePath)) this.success("Updated src/app.module.ts with InertiaModule");
|
|
280
|
+
else this.info("InertiaModule already configured in app.module.ts");
|
|
281
|
+
} catch (err) {
|
|
282
|
+
this.warn(`Could not auto-update app.module.ts: ${err.message}`);
|
|
283
|
+
this.info("Please manually add InertiaModule.forRoot() to your module imports");
|
|
284
|
+
}
|
|
285
|
+
} else this.info("No src/app.module.ts found — please manually configure InertiaModule");
|
|
286
|
+
try {
|
|
287
|
+
const { outputPath, pageCount } = await runTypeGeneration(cwd);
|
|
288
|
+
const relPath = relative(cwd, outputPath);
|
|
289
|
+
this.success(`Generated ${relPath} (${pageCount} page${pageCount !== 1 ? "s" : ""})`);
|
|
290
|
+
} catch {
|
|
291
|
+
this.warn("Could not generate initial type definitions. Run `quarry inertia:types` manually.");
|
|
292
|
+
}
|
|
293
|
+
if (!skipDeps) {
|
|
294
|
+
this.newLine();
|
|
295
|
+
this.info("Install the following dependencies:");
|
|
296
|
+
this.line(" npm install @stratal/inertia @inertiajs/react @inertiajs/vite react react-dom");
|
|
297
|
+
this.line(" npm install -D @types/react @types/react-dom vite @cloudflare/vite-plugin");
|
|
298
|
+
}
|
|
299
|
+
this.newLine();
|
|
300
|
+
this.success("Inertia.js scaffolding complete!");
|
|
301
|
+
this.info("Run `quarry inertia:dev` to start the dev server");
|
|
302
|
+
return 0;
|
|
303
|
+
}
|
|
304
|
+
async updateAppModule(modulePath) {
|
|
305
|
+
const { Project, SyntaxKind } = await import("ts-morph");
|
|
306
|
+
const sourceFile = new Project({ useInMemoryFileSystem: false }).addSourceFileAtPath(modulePath);
|
|
307
|
+
if (sourceFile.getImportDeclaration((decl) => decl.getModuleSpecifierValue() === "@stratal/inertia")) return false;
|
|
308
|
+
sourceFile.addImportDeclaration({
|
|
309
|
+
defaultImport: "rootView",
|
|
310
|
+
moduleSpecifier: "./inertia/root.html?raw"
|
|
311
|
+
});
|
|
312
|
+
sourceFile.addImportDeclaration({
|
|
313
|
+
namedImports: ["InertiaModule"],
|
|
314
|
+
moduleSpecifier: "@stratal/inertia"
|
|
315
|
+
});
|
|
316
|
+
const classes = sourceFile.getClasses();
|
|
317
|
+
for (const cls of classes) {
|
|
318
|
+
const moduleDecorator = cls.getDecorator("Module");
|
|
319
|
+
if (!moduleDecorator) continue;
|
|
320
|
+
const args = moduleDecorator.getArguments();
|
|
321
|
+
if (args.length === 0) continue;
|
|
322
|
+
const objLiteral = args[0].asKind(SyntaxKind.ObjectLiteralExpression);
|
|
323
|
+
if (!objLiteral) continue;
|
|
324
|
+
const importsProp = objLiteral.getProperty("imports");
|
|
325
|
+
if (importsProp) {
|
|
326
|
+
const arrayLiteral = (importsProp.asKind(SyntaxKind.PropertyAssignment)?.getInitializer())?.asKind(SyntaxKind.ArrayLiteralExpression);
|
|
327
|
+
if (arrayLiteral) arrayLiteral.addElement(`InertiaModule.forRoot({\n rootView,\n })`);
|
|
328
|
+
} else objLiteral.addPropertyAssignment({
|
|
329
|
+
name: "imports",
|
|
330
|
+
initializer: `[\n InertiaModule.forRoot({\n rootView,\n }),\n ]`
|
|
331
|
+
});
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
await sourceFile.save();
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
//#endregion
|
|
339
|
+
//#region src/commands/inertia-types.command.ts
|
|
340
|
+
var InertiaTypesCommand = class extends Command {
|
|
341
|
+
static command = "inertia:types {--watch : Watch for changes and regenerate}";
|
|
342
|
+
static description = "Generate Inertia.js page type definitions";
|
|
343
|
+
async handle() {
|
|
344
|
+
const cwd = process.cwd();
|
|
345
|
+
if (!existsSync(findPagesDir(cwd))) {
|
|
346
|
+
this.fail("src/inertia/pages/ not found. Run `quarry inertia:install` first.");
|
|
347
|
+
return 1;
|
|
348
|
+
}
|
|
349
|
+
if (!await this.generate(cwd)) return 1;
|
|
350
|
+
if (this.boolean("watch")) {
|
|
351
|
+
this.info("Watching for changes...");
|
|
352
|
+
await this.watchForChanges(cwd);
|
|
353
|
+
}
|
|
354
|
+
return 0;
|
|
355
|
+
}
|
|
356
|
+
async generate(cwd) {
|
|
357
|
+
try {
|
|
358
|
+
const { outputPath, pageCount } = await runTypeGeneration(cwd);
|
|
359
|
+
const relPath = relative(cwd, outputPath);
|
|
360
|
+
this.success(`Generated ${relPath} (${pageCount} page${pageCount !== 1 ? "s" : ""})`);
|
|
361
|
+
return true;
|
|
362
|
+
} catch (err) {
|
|
363
|
+
this.fail(`Type generation failed: ${err.message}`);
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
async watchForChanges(cwd) {
|
|
368
|
+
const srcDir = join(cwd, "src");
|
|
369
|
+
try {
|
|
370
|
+
const watcher = watch(srcDir, { recursive: true });
|
|
371
|
+
for await (const event of watcher) if (event.filename && /\.(tsx|ts)$/.test(event.filename)) {
|
|
372
|
+
this.info(`Change detected: ${event.filename}`);
|
|
373
|
+
await this.generate(cwd);
|
|
374
|
+
}
|
|
375
|
+
} catch (err) {
|
|
376
|
+
this.fail(`Watch failed: ${err.message}`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
//#endregion
|
|
381
|
+
//#region src/quarry.ts
|
|
382
|
+
let InertiaQuarryModule = class InertiaQuarryModule {};
|
|
383
|
+
InertiaQuarryModule = __decorate([Module({ providers: [
|
|
384
|
+
InertiaInstallCommand,
|
|
385
|
+
InertiaTypesCommand,
|
|
386
|
+
InertiaDevCommand,
|
|
387
|
+
InertiaBuildCommand
|
|
388
|
+
] })], InertiaQuarryModule);
|
|
389
|
+
//#endregion
|
|
390
|
+
export { InertiaBuildCommand, InertiaDevCommand, InertiaInstallCommand, InertiaQuarryModule, InertiaTypesCommand, runTypeGeneration };
|
|
391
|
+
|
|
392
|
+
//# sourceMappingURL=quarry.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quarry.mjs","names":[],"sources":["../src/vite/create-client-vite-config.ts","../src/vite/create-vite-config.ts","../src/commands/inertia-build.command.ts","../src/commands/inertia-dev.command.ts","../src/commands/inertia-install.command.ts","../src/commands/inertia-types.command.ts","../src/quarry.ts"],"sourcesContent":["import { existsSync, mkdirSync, writeFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\n\nexport interface TempClientViteConfigOptions {\n cwd: string\n entry?: string\n outDir?: string\n}\n\n/**\n * Emits a standalone Vite config for building the Inertia browser bundle.\n *\n * This runs as a separate `vite build` invocation BEFORE the worker build so\n * the worker's `stratal:inertia-inject-manifest` plugin has a finished\n * `<outDir>/.vite/manifest.json` to read. `@cloudflare/vite-plugin` builds its\n * environments in parallel, which made a single-config build racy — splitting\n * the two phases removes the race entirely and keeps each build minimal.\n */\nexport function writeTempClientViteConfig(options: TempClientViteConfigOptions): string {\n const configDir = join(options.cwd, 'node_modules', '.stratal')\n const configPath = join(configDir, 'vite.client.config.mjs')\n mkdirSync(dirname(configPath), { recursive: true })\n\n const entry = (options.entry ?? 'src/inertia/app.tsx').replace(/\\\\/g, '/')\n const outDir = (options.outDir ?? 'dist/client').replace(/\\\\/g, '/')\n const hasUserConfig = existsSync(join(options.cwd, 'vite.config.ts'))\n const publicDir = join(options.cwd, 'src', 'inertia', 'public').replace(/\\\\/g, '/')\n\n const content = `\nimport { mergeConfig } from 'vite'\n\nconst baseConfig = {\n publicDir: '${publicDir}',\n build: {\n outDir: '${outDir}',\n manifest: true,\n emptyOutDir: true,\n rollupOptions: {\n input: { app: '${entry}' },\n },\n },\n}\n\n${hasUserConfig\n ? `const userModule = await import('${join(options.cwd, 'vite.config.ts').replace(/\\\\/g, '/')}')\nconst userConfig = userModule.default ?? userModule\nexport default mergeConfig(userConfig, baseConfig)`\n : 'export default baseConfig'\n }\n`\n\n writeFileSync(configPath, content, 'utf-8')\n return configPath\n}\n","import { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nexport interface TempViteConfigOptions {\n cwd: string\n server?: { port?: number; host?: boolean }\n outDir?: string\n persistTo?: string\n /**\n * Path (relative to `cwd`) to the Vite client manifest the worker bundle\n * should inline. Defaults to `dist/client/.vite/manifest.json`, matching\n * what `quarry inertia:build` emits in phase 1.\n */\n clientManifestPath?: string\n}\n\nexport function writeTempViteConfig(options: TempViteConfigOptions): string {\n const configDir = join(options.cwd, 'node_modules', '.stratal')\n const configPath = join(configDir, 'vite.config.mjs')\n mkdirSync(dirname(configPath), { recursive: true })\n\n const hasUserConfig = existsSync(join(options.cwd, 'vite.config.ts'))\n\n const serverConfig = options.server\n ? `server: { port: ${options.server.port}, host: ${options.server.host ? 'true' : 'undefined'} },`\n : ''\n\n const outDirConfig = options.outDir\n ? `outDir: '${options.outDir}',`\n : ''\n\n const cloudflareArgs = options.persistTo\n ? `{ persistState: { path: ${JSON.stringify(options.persistTo)} } }`\n : ''\n\n const stratalArgs = options.clientManifestPath\n ? `{ clientManifestPath: ${JSON.stringify(options.clientManifestPath)} }`\n : ''\n\n const content = `\nimport { mergeConfig } from 'vite'\nimport { cloudflare } from '@cloudflare/vite-plugin'\nimport { stratalInertia } from '@stratal/inertia/vite'\n\nlet inertiaPlugin = null\ntry {\n const mod = await import('@inertiajs/vite')\n const inertia = mod.default ?? mod\n inertiaPlugin = inertia()\n} catch {}\n\nconst baseConfig = {\n plugins: [\n cloudflare(${cloudflareArgs}),\n ...(inertiaPlugin ? [inertiaPlugin] : []),\n ...stratalInertia(${stratalArgs}),\n ],\n publicDir: '${join(options.cwd, 'src', 'inertia', 'public').replace(/\\\\/g, '/')}',\n build: {\n ${outDirConfig}\n },\n ${serverConfig}\n}\n\n${hasUserConfig\n ? `const userModule = await import('${join(options.cwd, 'vite.config.ts').replace(/\\\\/g, '/')}')\nconst userConfig = userModule.default ?? userModule\nexport default mergeConfig(baseConfig, userConfig)`\n : 'export default baseConfig'\n }\n`\n\n writeFileSync(configPath, content, 'utf-8')\n return configPath\n}\n","import { spawn } from 'node:child_process'\nimport { existsSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { Command } from 'stratal/quarry'\nimport { writeTempClientViteConfig } from '../vite/create-client-vite-config'\nimport { writeTempViteConfig } from '../vite/create-vite-config'\n\nexport class InertiaBuildCommand extends Command {\n static command = 'inertia:build {--outDir=dist : Output directory} {--ssr : Also build SSR bundle}'\n static description = 'Build Inertia.js frontend for production'\n\n async handle(): Promise<number | undefined> {\n const outDir = this.string('outDir') || 'dist'\n const shouldBuildSsr = this.boolean('ssr')\n const cwd = process.cwd()\n\n const entryPath = 'src/inertia/app.tsx'\n if (!existsSync(join(cwd, entryPath))) {\n this.fail('src/inertia/app.tsx not found. Run `quarry inertia:install` first.')\n return 1\n }\n\n // Phase 1: standalone browser-bundle build. Runs without the Cloudflare\n // vite-plugin so it isn't subject to its parallel env orchestration. The\n // resulting `<clientOutDir>/.vite/manifest.json` is what the worker build\n // (phase 2) inlines into the worker entry via `stratal:inertia-inject-manifest`.\n const clientOutDir = join(outDir, 'client').replace(/\\\\/g, '/')\n const clientConfigPath = writeTempClientViteConfig({\n cwd,\n entry: entryPath,\n outDir: clientOutDir,\n })\n\n this.info('Building Inertia.js browser bundle...')\n const browserCode = await this.spawnVite(cwd, clientConfigPath, ['build'])\n if (browserCode !== 0) {\n this.fail('Browser bundle build failed.')\n return browserCode\n }\n this.success(`Browser bundle written to ${clientOutDir}/`)\n\n // Phase 2: worker build (Cloudflare vite-plugin). The injector plugin\n // reads the manifest produced in phase 1 and inlines it onto the worker\n // entry chunk.\n const configPath = writeTempViteConfig({\n cwd,\n outDir,\n clientManifestPath: join(clientOutDir, '.vite', 'manifest.json').replace(/\\\\/g, '/'),\n })\n\n this.info('Building Cloudflare worker bundle...')\n const workerCode = await this.spawnVite(cwd, configPath, ['build'])\n if (workerCode !== 0) {\n this.fail('Worker build failed.')\n return workerCode\n }\n this.success('Worker build complete!')\n\n if (shouldBuildSsr) {\n this.info('Building SSR bundle...')\n const ssrCode = await this.spawnVite(cwd, configPath, ['build', '--ssr'])\n if (ssrCode !== 0) {\n this.fail('SSR build failed.')\n return ssrCode\n }\n this.success('SSR build complete!')\n }\n\n this.success(`Output in ${outDir}/`)\n this.info('Deploy with: npx wrangler deploy')\n return 0\n }\n\n private spawnVite(cwd: string, configPath: string, args: string[]): Promise<number> {\n return new Promise((resolve) => {\n const child = spawn('npx', ['vite', '--config', configPath, ...args], {\n cwd,\n stdio: 'inherit',\n shell: true,\n })\n\n child.on('error', (err) => {\n this.fail(`Vite process error: ${err.message}`)\n resolve(1)\n })\n\n child.on('close', (code) => {\n resolve(code ?? 0)\n })\n })\n }\n}\n","import { spawn } from 'node:child_process'\nimport { existsSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { Command } from 'stratal/quarry'\nimport { writeTempViteConfig } from '../vite/create-vite-config'\n\nexport class InertiaDevCommand extends Command {\n static command = 'inertia:dev {--port= : Dev server port} {--host : Expose to network} {--persist-to= : Shared persist directory for @cloudflare/vite-plugin (relative to cwd; the plugin appends /v3). Use to share R2/KV/cache emulator state across multiple workers in dev.}'\n static description = 'Start Inertia.js Vite development server'\n\n async handle(): Promise<number | undefined> {\n const port = this.number('port')\n const host = this.boolean('host')\n const persistTo = this.string('persist-to')\n const cwd = process.cwd()\n\n const entryPath = 'src/inertia/app.tsx'\n if (!existsSync(join(cwd, entryPath))) {\n this.fail('src/inertia/app.tsx not found. Run `quarry inertia:install` first.')\n return 1\n }\n\n const configPath = writeTempViteConfig({\n cwd,\n server: { port, host },\n persistTo,\n })\n\n this.info('Starting Vite dev server...')\n\n const args = ['vite', 'dev', '--config', configPath]\n if (host) args.push('--host')\n\n return new Promise<number>((resolve) => {\n const child = spawn('npx', args, {\n cwd,\n stdio: 'inherit',\n shell: true,\n })\n\n child.on('error', (err) => {\n this.fail(`Failed to start dev server: ${err.message}`)\n resolve(1)\n })\n\n child.on('close', (code) => {\n resolve(code ?? 0)\n })\n })\n }\n}\n","import { existsSync, mkdirSync, writeFileSync } from 'node:fs'\nimport { join, relative } from 'node:path'\nimport { Command } from 'stratal/quarry'\nimport { runTypeGeneration } from '../generator/type-generator'\n\nconst ROOT_HTML = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n @viteHead\n @inertiaHead\n</head>\n<body>\n @inertia\n @viteScripts\n</body>\n</html>`\n\nconst APP_TSX = `import { createInertiaApp } from '@inertiajs/react'\n\ncreateInertiaApp({\n resolve: async (name) => {\n const pages = import.meta.glob('./pages/**/*.tsx')\n const page = await pages[\\`./pages/\\${name}.tsx\\`]?.()\n if (!page) throw new Error(\\`Page not found: \\${name}\\`)\n return page\n },\n})`\n\nconst HOME_TSX = `export default function Home({ message }: { message: string }) {\n return (\n <div>\n <h1>{message}</h1>\n <p>This page is rendered with Inertia.js and Stratal.</p>\n </div>\n )\n}`\n\nexport class InertiaInstallCommand extends Command {\n static command = 'inertia:install {--skip-deps : Skip installing npm dependencies}'\n static description = 'Scaffold Inertia.js files for a Stratal project'\n\n async handle(): Promise<number | undefined> {\n const skipDeps = this.boolean('skip-deps')\n const cwd = process.cwd()\n const inertiaDir = join(cwd, 'src', 'inertia')\n const pagesDir = join(inertiaDir, 'pages')\n\n // Create directories\n this.info('Creating src/inertia/ directory...')\n mkdirSync(pagesDir, { recursive: true })\n\n const publicDir = join(inertiaDir, 'public')\n mkdirSync(publicDir, { recursive: true })\n const gitkeepPath = join(publicDir, '.gitkeep')\n if (!existsSync(gitkeepPath)) {\n writeFileSync(gitkeepPath, '', 'utf-8')\n }\n this.success('Created src/inertia/public/')\n\n // Write template files\n const files = [\n { path: join(inertiaDir, 'root.html'), content: ROOT_HTML, name: 'root.html' },\n { path: join(inertiaDir, 'app.tsx'), content: APP_TSX, name: 'app.tsx' },\n { path: join(pagesDir, 'Home.tsx'), content: HOME_TSX, name: 'pages/Home.tsx' },\n ]\n\n for (const file of files) {\n if (existsSync(file.path)) {\n this.warn(`Skipping ${file.name} (already exists)`)\n } else {\n writeFileSync(file.path, file.content, 'utf-8')\n this.success(`Created src/inertia/${file.name}`)\n }\n }\n\n // Modify app.module.ts\n const appModulePath = join(cwd, 'src', 'app.module.ts')\n if (existsSync(appModulePath)) {\n this.info('Updating src/app.module.ts...')\n try {\n const updated = await this.updateAppModule(appModulePath)\n if (updated) {\n this.success('Updated src/app.module.ts with InertiaModule')\n } else {\n this.info('InertiaModule already configured in app.module.ts')\n }\n } catch (err) {\n this.warn(`Could not auto-update app.module.ts: ${(err as Error).message}`)\n this.info('Please manually add InertiaModule.forRoot() to your module imports')\n }\n } else {\n this.info('No src/app.module.ts found — please manually configure InertiaModule')\n }\n\n // Generate initial type definitions\n try {\n const { outputPath, pageCount } = await runTypeGeneration(cwd)\n const relPath = relative(cwd, outputPath)\n this.success(`Generated ${relPath} (${pageCount} page${pageCount !== 1 ? 's' : ''})`)\n } catch {\n this.warn('Could not generate initial type definitions. Run `quarry inertia:types` manually.')\n }\n\n if (!skipDeps) {\n this.newLine()\n this.info('Install the following dependencies:')\n this.line(' npm install @stratal/inertia @inertiajs/react @inertiajs/vite react react-dom')\n this.line(' npm install -D @types/react @types/react-dom vite @cloudflare/vite-plugin')\n }\n\n this.newLine()\n this.success('Inertia.js scaffolding complete!')\n this.info('Run `quarry inertia:dev` to start the dev server')\n\n return 0\n }\n\n private async updateAppModule(modulePath: string): Promise<boolean> {\n const { Project, SyntaxKind } = await import('ts-morph')\n\n const project = new Project({ useInMemoryFileSystem: false })\n const sourceFile = project.addSourceFileAtPath(modulePath)\n\n // Check if InertiaModule is already imported\n const existingImport = sourceFile.getImportDeclaration((decl) =>\n decl.getModuleSpecifierValue() === '@stratal/inertia',\n )\n if (existingImport) {\n return false\n }\n\n // Add rootView import\n sourceFile.addImportDeclaration({\n defaultImport: 'rootView',\n moduleSpecifier: './inertia/root.html?raw',\n })\n\n // Add InertiaModule import\n sourceFile.addImportDeclaration({\n namedImports: ['InertiaModule'],\n moduleSpecifier: '@stratal/inertia',\n })\n\n // Find the @Module decorator and add InertiaModule to imports\n const classes = sourceFile.getClasses()\n for (const cls of classes) {\n const moduleDecorator = cls.getDecorator('Module')\n if (!moduleDecorator) continue\n\n const args = moduleDecorator.getArguments()\n if (args.length === 0) continue\n\n const objLiteral = args[0].asKind(SyntaxKind.ObjectLiteralExpression)\n if (!objLiteral) continue\n\n const importsProp = objLiteral.getProperty('imports')\n if (importsProp) {\n // Add to existing imports array\n const initializer = importsProp.asKind(SyntaxKind.PropertyAssignment)?.getInitializer()\n const arrayLiteral = initializer?.asKind(SyntaxKind.ArrayLiteralExpression)\n if (arrayLiteral) {\n arrayLiteral.addElement(`InertiaModule.forRoot({\\n rootView,\\n })`)\n }\n } else {\n // Add imports property\n objLiteral.addPropertyAssignment({\n name: 'imports',\n initializer: `[\\n InertiaModule.forRoot({\\n rootView,\\n }),\\n ]`,\n })\n }\n\n break\n }\n\n await sourceFile.save()\n return true\n }\n}\n","import { existsSync } from 'node:fs'\nimport { watch } from 'node:fs/promises'\nimport { join, relative } from 'node:path'\nimport { Command } from 'stratal/quarry'\nimport { findPagesDir, runTypeGeneration } from '../generator/type-generator'\n\nexport class InertiaTypesCommand extends Command {\n static command = 'inertia:types {--watch : Watch for changes and regenerate}'\n static description = 'Generate Inertia.js page type definitions'\n\n async handle(): Promise<number | undefined> {\n const cwd = process.cwd()\n const pagesDir = findPagesDir(cwd)\n\n if (!existsSync(pagesDir)) {\n this.fail('src/inertia/pages/ not found. Run `quarry inertia:install` first.')\n return 1\n }\n\n const result = await this.generate(cwd)\n if (!result) return 1\n\n if (this.boolean('watch')) {\n this.info('Watching for changes...')\n await this.watchForChanges(cwd)\n }\n\n return 0\n }\n\n private async generate(cwd: string): Promise<boolean> {\n try {\n const { outputPath, pageCount } = await runTypeGeneration(cwd)\n const relPath = relative(cwd, outputPath)\n this.success(`Generated ${relPath} (${pageCount} page${pageCount !== 1 ? 's' : ''})`)\n return true\n } catch (err) {\n this.fail(`Type generation failed: ${(err as Error).message}`)\n return false\n }\n }\n\n private async watchForChanges(cwd: string): Promise<void> {\n const srcDir = join(cwd, 'src')\n\n try {\n const watcher = watch(srcDir, { recursive: true })\n for await (const event of watcher) {\n if (event.filename && /\\.(tsx|ts)$/.test(event.filename)) {\n this.info(`Change detected: ${event.filename}`)\n await this.generate(cwd)\n }\n }\n } catch (err) {\n this.fail(`Watch failed: ${(err as Error).message}`)\n }\n }\n}\n","import { Module } from 'stratal/module'\nimport { InertiaBuildCommand } from './commands/inertia-build.command'\nimport { InertiaDevCommand } from './commands/inertia-dev.command'\nimport { InertiaInstallCommand } from './commands/inertia-install.command'\nimport { InertiaTypesCommand } from './commands/inertia-types.command'\n\n@Module({\n providers: [\n InertiaInstallCommand,\n InertiaTypesCommand,\n InertiaDevCommand,\n InertiaBuildCommand,\n ],\n})\nexport class InertiaQuarryModule {}\n\nexport { InertiaBuildCommand } from './commands/inertia-build.command'\nexport { InertiaDevCommand } from './commands/inertia-dev.command'\nexport { InertiaInstallCommand } from './commands/inertia-install.command'\nexport { InertiaTypesCommand } from './commands/inertia-types.command'\nexport { runTypeGeneration } from './generator/type-generator'\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkBA,SAAgB,0BAA0B,SAA8C;CAEtF,MAAM,aAAa,KADD,KAAK,QAAQ,KAAK,gBAAgB,WACnB,EAAE,yBAAyB;CAC5D,UAAU,QAAQ,WAAW,EAAE,EAAE,WAAW,MAAM,CAAC;CAEnD,MAAM,SAAS,QAAQ,SAAS,uBAAuB,QAAQ,OAAO,IAAI;CAC1E,MAAM,UAAU,QAAQ,UAAU,eAAe,QAAQ,OAAO,IAAI;CACpE,MAAM,gBAAgB,WAAW,KAAK,QAAQ,KAAK,iBAAiB,CAAC;CA0BrE,cAAc,YAAY;;;;gBAzBR,KAAK,QAAQ,KAAK,OAAO,WAAW,SAAS,CAAC,QAAQ,OAAO,IAMxD,CAAC;;eAEX,OAAO;;;;uBAIC,MAAM;;;;;EAK3B,gBACM,oCAAoC,KAAK,QAAQ,KAAK,iBAAiB,CAAC,QAAQ,OAAO,IAAI,CAAC;;sDAG5F,4BACH;GAGgC,QAAQ;CAC3C,OAAO;;;;ACrCT,SAAgB,oBAAoB,SAAwC;CAE1E,MAAM,aAAa,KADD,KAAK,QAAQ,KAAK,gBAAgB,WACnB,EAAE,kBAAkB;CACrD,UAAU,QAAQ,WAAW,EAAE,EAAE,WAAW,MAAM,CAAC;CAEnD,MAAM,gBAAgB,WAAW,KAAK,QAAQ,KAAK,iBAAiB,CAAC;CAErE,MAAM,eAAe,QAAQ,SACzB,mBAAmB,QAAQ,OAAO,KAAK,UAAU,QAAQ,OAAO,OAAO,SAAS,YAAY,OAC5F;CAEJ,MAAM,eAAe,QAAQ,SACzB,YAAY,QAAQ,OAAO,MAC3B;CA2CJ,cAAc,YAAY;;;;;;;;;;;;;;iBAzCH,QAAQ,YAC3B,2BAA2B,KAAK,UAAU,QAAQ,UAAU,CAAC,QAC7D,GAoB0B;;wBAlBV,QAAQ,qBACxB,yBAAyB,KAAK,UAAU,QAAQ,mBAAmB,CAAC,MACpE,GAkB8B;;gBAEpB,KAAK,QAAQ,KAAK,OAAO,WAAW,SAAS,CAAC,QAAQ,OAAO,IAAI,CAAC;;MAE5E,aAAa;;IAEf,aAAa;;;EAGf,gBACM,oCAAoC,KAAK,QAAQ,KAAK,iBAAiB,CAAC,QAAQ,OAAO,IAAI,CAAC;;sDAG5F,4BACH;GAGgC,QAAQ;CAC3C,OAAO;;;;ACjET,IAAa,sBAAb,cAAyC,QAAQ;CAC/C,OAAO,UAAU;CACjB,OAAO,cAAc;CAErB,MAAM,SAAsC;EAC1C,MAAM,SAAS,KAAK,OAAO,SAAS,IAAI;EACxC,MAAM,iBAAiB,KAAK,QAAQ,MAAM;EAC1C,MAAM,MAAM,QAAQ,KAAK;EAEzB,MAAM,YAAY;EAClB,IAAI,CAAC,WAAW,KAAK,KAAK,UAAU,CAAC,EAAE;GACrC,KAAK,KAAK,qEAAqE;GAC/E,OAAO;;EAOT,MAAM,eAAe,KAAK,QAAQ,SAAS,CAAC,QAAQ,OAAO,IAAI;EAC/D,MAAM,mBAAmB,0BAA0B;GACjD;GACA,OAAO;GACP,QAAQ;GACT,CAAC;EAEF,KAAK,KAAK,wCAAwC;EAClD,MAAM,cAAc,MAAM,KAAK,UAAU,KAAK,kBAAkB,CAAC,QAAQ,CAAC;EAC1E,IAAI,gBAAgB,GAAG;GACrB,KAAK,KAAK,+BAA+B;GACzC,OAAO;;EAET,KAAK,QAAQ,6BAA6B,aAAa,GAAG;EAK1D,MAAM,aAAa,oBAAoB;GACrC;GACA;GACA,oBAAoB,KAAK,cAAc,SAAS,gBAAgB,CAAC,QAAQ,OAAO,IAAI;GACrF,CAAC;EAEF,KAAK,KAAK,uCAAuC;EACjD,MAAM,aAAa,MAAM,KAAK,UAAU,KAAK,YAAY,CAAC,QAAQ,CAAC;EACnE,IAAI,eAAe,GAAG;GACpB,KAAK,KAAK,uBAAuB;GACjC,OAAO;;EAET,KAAK,QAAQ,yBAAyB;EAEtC,IAAI,gBAAgB;GAClB,KAAK,KAAK,yBAAyB;GACnC,MAAM,UAAU,MAAM,KAAK,UAAU,KAAK,YAAY,CAAC,SAAS,QAAQ,CAAC;GACzE,IAAI,YAAY,GAAG;IACjB,KAAK,KAAK,oBAAoB;IAC9B,OAAO;;GAET,KAAK,QAAQ,sBAAsB;;EAGrC,KAAK,QAAQ,aAAa,OAAO,GAAG;EACpC,KAAK,KAAK,mCAAmC;EAC7C,OAAO;;CAGT,UAAkB,KAAa,YAAoB,MAAiC;EAClF,OAAO,IAAI,SAAS,YAAY;GAC9B,MAAM,QAAQ,MAAM,OAAO;IAAC;IAAQ;IAAY;IAAY,GAAG;IAAK,EAAE;IACpE;IACA,OAAO;IACP,OAAO;IACR,CAAC;GAEF,MAAM,GAAG,UAAU,QAAQ;IACzB,KAAK,KAAK,uBAAuB,IAAI,UAAU;IAC/C,QAAQ,EAAE;KACV;GAEF,MAAM,GAAG,UAAU,SAAS;IAC1B,QAAQ,QAAQ,EAAE;KAClB;IACF;;;;;ACnFN,IAAa,oBAAb,cAAuC,QAAQ;CAC7C,OAAO,UAAU;CACjB,OAAO,cAAc;CAErB,MAAM,SAAsC;EAC1C,MAAM,OAAO,KAAK,OAAO,OAAO;EAChC,MAAM,OAAO,KAAK,QAAQ,OAAO;EACjC,MAAM,YAAY,KAAK,OAAO,aAAa;EAC3C,MAAM,MAAM,QAAQ,KAAK;EAGzB,IAAI,CAAC,WAAW,KAAK,KAAK,sBAAU,CAAC,EAAE;GACrC,KAAK,KAAK,qEAAqE;GAC/E,OAAO;;EAGT,MAAM,aAAa,oBAAoB;GACrC;GACA,QAAQ;IAAE;IAAM;IAAM;GACtB;GACD,CAAC;EAEF,KAAK,KAAK,8BAA8B;EAExC,MAAM,OAAO;GAAC;GAAQ;GAAO;GAAY;GAAW;EACpD,IAAI,MAAM,KAAK,KAAK,SAAS;EAE7B,OAAO,IAAI,SAAiB,YAAY;GACtC,MAAM,QAAQ,MAAM,OAAO,MAAM;IAC/B;IACA,OAAO;IACP,OAAO;IACR,CAAC;GAEF,MAAM,GAAG,UAAU,QAAQ;IACzB,KAAK,KAAK,+BAA+B,IAAI,UAAU;IACvD,QAAQ,EAAE;KACV;GAEF,MAAM,GAAG,UAAU,SAAS;IAC1B,QAAQ,QAAQ,EAAE;KAClB;IACF;;;;;AC3CN,MAAM,YAAY;;;;;;;;;;;;;AAclB,MAAM,UAAU;;;;;;;;;;AAWhB,MAAM,WAAW;;;;;;;;AASjB,IAAa,wBAAb,cAA2C,QAAQ;CACjD,OAAO,UAAU;CACjB,OAAO,cAAc;CAErB,MAAM,SAAsC;EAC1C,MAAM,WAAW,KAAK,QAAQ,YAAY;EAC1C,MAAM,MAAM,QAAQ,KAAK;EACzB,MAAM,aAAa,KAAK,KAAK,OAAO,UAAU;EAC9C,MAAM,WAAW,KAAK,YAAY,QAAQ;EAG1C,KAAK,KAAK,qCAAqC;EAC/C,UAAU,UAAU,EAAE,WAAW,MAAM,CAAC;EAExC,MAAM,YAAY,KAAK,YAAY,SAAS;EAC5C,UAAU,WAAW,EAAE,WAAW,MAAM,CAAC;EACzC,MAAM,cAAc,KAAK,WAAW,WAAW;EAC/C,IAAI,CAAC,WAAW,YAAY,EAC1B,cAAc,aAAa,IAAI,QAAQ;EAEzC,KAAK,QAAQ,8BAA8B;EAG3C,MAAM,QAAQ;GACZ;IAAE,MAAM,KAAK,YAAY,YAAY;IAAE,SAAS;IAAW,MAAM;IAAa;GAC9E;IAAE,MAAM,KAAK,YAAY,UAAU;IAAE,SAAS;IAAS,MAAM;IAAW;GACxE;IAAE,MAAM,KAAK,UAAU,WAAW;IAAE,SAAS;IAAU,MAAM;IAAkB;GAChF;EAED,KAAK,MAAM,QAAQ,OACjB,IAAI,WAAW,KAAK,KAAK,EACvB,KAAK,KAAK,YAAY,KAAK,KAAK,mBAAmB;OAC9C;GACL,cAAc,KAAK,MAAM,KAAK,SAAS,QAAQ;GAC/C,KAAK,QAAQ,uBAAuB,KAAK,OAAO;;EAKpD,MAAM,gBAAgB,KAAK,KAAK,OAAO,gBAAgB;EACvD,IAAI,WAAW,cAAc,EAAE;GAC7B,KAAK,KAAK,gCAAgC;GAC1C,IAAI;IAEF,IAAI,MADkB,KAAK,gBAAgB,cAAc,EAEvD,KAAK,QAAQ,+CAA+C;SAE5D,KAAK,KAAK,oDAAoD;YAEzD,KAAK;IACZ,KAAK,KAAK,wCAAyC,IAAc,UAAU;IAC3E,KAAK,KAAK,qEAAqE;;SAGjF,KAAK,KAAK,uEAAuE;EAInF,IAAI;GACF,MAAM,EAAE,YAAY,cAAc,MAAM,kBAAkB,IAAI;GAC9D,MAAM,UAAU,SAAS,KAAK,WAAW;GACzC,KAAK,QAAQ,aAAa,QAAQ,IAAI,UAAU,OAAO,cAAc,IAAI,MAAM,GAAG,GAAG;UAC/E;GACN,KAAK,KAAK,oFAAoF;;EAGhG,IAAI,CAAC,UAAU;GACb,KAAK,SAAS;GACd,KAAK,KAAK,sCAAsC;GAChD,KAAK,KAAK,kFAAkF;GAC5F,KAAK,KAAK,8EAA8E;;EAG1F,KAAK,SAAS;EACd,KAAK,QAAQ,mCAAmC;EAChD,KAAK,KAAK,mDAAmD;EAE7D,OAAO;;CAGT,MAAc,gBAAgB,YAAsC;EAClE,MAAM,EAAE,SAAS,eAAe,MAAM,OAAO;EAG7C,MAAM,aAAa,IADC,QAAQ,EAAE,uBAAuB,OAAO,CAClC,CAAC,oBAAoB,WAAW;EAM1D,IAHuB,WAAW,sBAAsB,SACtD,KAAK,yBAAyB,KAAK,mBAEnB,EAChB,OAAO;EAIT,WAAW,qBAAqB;GAC9B,eAAe;GACf,iBAAiB;GAClB,CAAC;EAGF,WAAW,qBAAqB;GAC9B,cAAc,CAAC,gBAAgB;GAC/B,iBAAiB;GAClB,CAAC;EAGF,MAAM,UAAU,WAAW,YAAY;EACvC,KAAK,MAAM,OAAO,SAAS;GACzB,MAAM,kBAAkB,IAAI,aAAa,SAAS;GAClD,IAAI,CAAC,iBAAiB;GAEtB,MAAM,OAAO,gBAAgB,cAAc;GAC3C,IAAI,KAAK,WAAW,GAAG;GAEvB,MAAM,aAAa,KAAK,GAAG,OAAO,WAAW,wBAAwB;GACrE,IAAI,CAAC,YAAY;GAEjB,MAAM,cAAc,WAAW,YAAY,UAAU;GACrD,IAAI,aAAa;IAGf,MAAM,gBADc,YAAY,OAAO,WAAW,mBAAmB,EAAE,gBAAgB,GACrD,OAAO,WAAW,uBAAuB;IAC3E,IAAI,cACF,aAAa,WAAW,+CAA+C;UAIzE,WAAW,sBAAsB;IAC/B,MAAM;IACN,aAAa;IACd,CAAC;GAGJ;;EAGF,MAAM,WAAW,MAAM;EACvB,OAAO;;;;;AC3KX,IAAa,sBAAb,cAAyC,QAAQ;CAC/C,OAAO,UAAU;CACjB,OAAO,cAAc;CAErB,MAAM,SAAsC;EAC1C,MAAM,MAAM,QAAQ,KAAK;EAGzB,IAAI,CAAC,WAFY,aAAa,IAEN,CAAC,EAAE;GACzB,KAAK,KAAK,oEAAoE;GAC9E,OAAO;;EAIT,IAAI,CAAC,MADgB,KAAK,SAAS,IAAI,EAC1B,OAAO;EAEpB,IAAI,KAAK,QAAQ,QAAQ,EAAE;GACzB,KAAK,KAAK,0BAA0B;GACpC,MAAM,KAAK,gBAAgB,IAAI;;EAGjC,OAAO;;CAGT,MAAc,SAAS,KAA+B;EACpD,IAAI;GACF,MAAM,EAAE,YAAY,cAAc,MAAM,kBAAkB,IAAI;GAC9D,MAAM,UAAU,SAAS,KAAK,WAAW;GACzC,KAAK,QAAQ,aAAa,QAAQ,IAAI,UAAU,OAAO,cAAc,IAAI,MAAM,GAAG,GAAG;GACrF,OAAO;WACA,KAAK;GACZ,KAAK,KAAK,2BAA4B,IAAc,UAAU;GAC9D,OAAO;;;CAIX,MAAc,gBAAgB,KAA4B;EACxD,MAAM,SAAS,KAAK,KAAK,MAAM;EAE/B,IAAI;GACF,MAAM,UAAU,MAAM,QAAQ,EAAE,WAAW,MAAM,CAAC;GAClD,WAAW,MAAM,SAAS,SACxB,IAAI,MAAM,YAAY,cAAc,KAAK,MAAM,SAAS,EAAE;IACxD,KAAK,KAAK,oBAAoB,MAAM,WAAW;IAC/C,MAAM,KAAK,SAAS,IAAI;;WAGrB,KAAK;GACZ,KAAK,KAAK,iBAAkB,IAAc,UAAU;;;;;;ACxCnD,IAAA,sBAAA,MAAM,oBAAoB;kCARhC,OAAO,EACN,WAAW;CACT;CACA;CACA;CACA;CACD,EACF,CAAC,CAAA,EAAA,oBAAA"}
|
package/dist/react.d.mts
CHANGED
|
@@ -1,38 +1,8 @@
|
|
|
1
1
|
/// <reference path="../global.d.ts" />
|
|
2
|
-
import { MessageKeys, MessageParams } from "stratal/i18n";
|
|
3
2
|
import { CurrentRoute, RouteMatcher, RouteName, RouteParams } from "stratal/router";
|
|
3
|
+
import { MessageKeys, MessageParams } from "stratal/i18n";
|
|
4
4
|
|
|
5
5
|
//#region src/react/use-i18n.d.ts
|
|
6
|
-
/**
|
|
7
|
-
* Hook that provides i18n translation capabilities in React components.
|
|
8
|
-
*
|
|
9
|
-
* Consumes `locale` and `translations` from Inertia shared props and returns
|
|
10
|
-
* a `t()` function that translates message keys with optional interpolation.
|
|
11
|
-
*
|
|
12
|
-
* Requires the `i18n` option to be set on `InertiaModule.forRoot()` to inject
|
|
13
|
-
* the shared props.
|
|
14
|
-
*
|
|
15
|
-
* @returns An object with:
|
|
16
|
-
* - `t` — Translation function accepting a message key and optional params
|
|
17
|
-
* - `locale` — The current locale string
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* ```tsx
|
|
21
|
-
* import { useI18n } from '@stratal/inertia/react'
|
|
22
|
-
*
|
|
23
|
-
* export default function Header() {
|
|
24
|
-
* const { t, locale } = useI18n()
|
|
25
|
-
*
|
|
26
|
-
* return (
|
|
27
|
-
* <header>
|
|
28
|
-
* <h1>{t('common.title')}</h1>
|
|
29
|
-
* <p>{t('common.greeting', { name: 'World' })}</p>
|
|
30
|
-
* <span>Locale: {locale}</span>
|
|
31
|
-
* </header>
|
|
32
|
-
* )
|
|
33
|
-
* }
|
|
34
|
-
* ```
|
|
35
|
-
*/
|
|
36
6
|
declare function useI18n(): {
|
|
37
7
|
t: (key: MessageKeys, params?: MessageParams) => string;
|
|
38
8
|
locale: string;
|
package/dist/react.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.d.mts","names":[],"sources":["../src/react/use-i18n.ts","../src/react/use-route.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"react.d.mts","names":[],"sources":["../src/react/use-i18n.ts","../src/react/use-route.ts"],"mappings":";;;;iBAWgB,OAAA,CAAA;WAUC,WAAA,EAAW,MAAA,GAAW,aAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBC6MvB,QAAA,CAAA;oBAKK,SAAA,EAAS,IAAA,EAAQ,CAAA,EAAC,MAAA,GAAW,WAAA,CAAY,CAAA;;QAOvC,SAAA;IAAA,OACG,YAAA;EAAA"}
|
package/dist/react.mjs
CHANGED
|
@@ -1,51 +1,19 @@
|
|
|
1
1
|
import { usePage } from "@inertiajs/react";
|
|
2
|
-
import
|
|
2
|
+
import IntlMessageFormat from "intl-messageformat";
|
|
3
3
|
import { useMemo } from "react";
|
|
4
4
|
//#region src/react/use-i18n.ts
|
|
5
|
-
registerMessageCompiler(compile);
|
|
6
|
-
/**
|
|
7
|
-
* Hook that provides i18n translation capabilities in React components.
|
|
8
|
-
*
|
|
9
|
-
* Consumes `locale` and `translations` from Inertia shared props and returns
|
|
10
|
-
* a `t()` function that translates message keys with optional interpolation.
|
|
11
|
-
*
|
|
12
|
-
* Requires the `i18n` option to be set on `InertiaModule.forRoot()` to inject
|
|
13
|
-
* the shared props.
|
|
14
|
-
*
|
|
15
|
-
* @returns An object with:
|
|
16
|
-
* - `t` — Translation function accepting a message key and optional params
|
|
17
|
-
* - `locale` — The current locale string
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* ```tsx
|
|
21
|
-
* import { useI18n } from '@stratal/inertia/react'
|
|
22
|
-
*
|
|
23
|
-
* export default function Header() {
|
|
24
|
-
* const { t, locale } = useI18n()
|
|
25
|
-
*
|
|
26
|
-
* return (
|
|
27
|
-
* <header>
|
|
28
|
-
* <h1>{t('common.title')}</h1>
|
|
29
|
-
* <p>{t('common.greeting', { name: 'World' })}</p>
|
|
30
|
-
* <span>Locale: {locale}</span>
|
|
31
|
-
* </header>
|
|
32
|
-
* )
|
|
33
|
-
* }
|
|
34
|
-
* ```
|
|
35
|
-
*/
|
|
36
5
|
function useI18n() {
|
|
37
6
|
const { locale, translations } = usePage().props;
|
|
38
|
-
const context = useMemo(() => createCoreContext({
|
|
39
|
-
locale,
|
|
40
|
-
messages: { [locale]: translations },
|
|
41
|
-
missingWarn: !import.meta.env.PROD,
|
|
42
|
-
fallbackWarn: !import.meta.env.PROD
|
|
43
|
-
}), [locale, translations]);
|
|
44
7
|
return {
|
|
45
|
-
t: useMemo(() =>
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
8
|
+
t: useMemo(() => {
|
|
9
|
+
const compiled = /* @__PURE__ */ new Map();
|
|
10
|
+
for (const [key, value] of Object.entries(translations)) compiled.set(key, new IntlMessageFormat(value, locale));
|
|
11
|
+
return (key, params) => {
|
|
12
|
+
const msg = compiled.get(key);
|
|
13
|
+
if (!msg) return key;
|
|
14
|
+
return String(msg.format(params));
|
|
15
|
+
};
|
|
16
|
+
}, [locale, translations]),
|
|
49
17
|
locale
|
|
50
18
|
};
|
|
51
19
|
}
|
package/dist/react.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.mjs","names":[],"sources":["../src/react/use-i18n.ts","../src/react/use-route.ts"],"sourcesContent":["/**\n * React hook for using Stratal's i18n translations on the frontend.\n *\n * Reads `locale` and `translations` from Inertia shared props (injected by\n * the `i18n` option on {@link InertiaModuleOptions}) and provides a type-safe\n * `t()` function powered by `@intlify/core-base`.\n *\n * @module\n */\n\nimport type { PageProps } from '@inertiajs/core'\nimport { usePage } from '@inertiajs/react'\nimport { compile, createCoreContext, registerMessageCompiler, translate } from '@intlify/core-base'\nimport { useMemo } from 'react'\nimport type { MessageKeys, MessageParams } from 'stratal/i18n'\n\n// Register JIT message compiler from the SAME @intlify/core-base instance that provides\n// createCoreContext/translate. Importing setupI18nCompiler from stratal/i18n/utils can\n// resolve a different @intlify/core-base copy (duplicate modules in node_modules),\n// causing the compiler registration to be invisible to this module's createCoreContext.\nregisterMessageCompiler(compile)\n\ninterface I18nPageProps extends PageProps {\n locale: string\n translations: Record<string, string>\n}\n\n/**\n * Hook that provides i18n translation capabilities in React components.\n *\n * Consumes `locale` and `translations` from Inertia shared props and returns\n * a `t()` function that translates message keys with optional interpolation.\n *\n * Requires the `i18n` option to be set on `InertiaModule.forRoot()` to inject\n * the shared props.\n *\n * @returns An object with:\n * - `t` — Translation function accepting a message key and optional params\n * - `locale` — The current locale string\n *\n * @example\n * ```tsx\n * import { useI18n } from '@stratal/inertia/react'\n *\n * export default function Header() {\n * const { t, locale } = useI18n()\n *\n * return (\n * <header>\n * <h1>{t('common.title')}</h1>\n * <p>{t('common.greeting', { name: 'World' })}</p>\n * <span>Locale: {locale}</span>\n * </header>\n * )\n * }\n * ```\n */\nexport function useI18n() {\n const { locale, translations } = usePage<I18nPageProps>().props\n\n const context = useMemo(\n () => createCoreContext({\n locale,\n messages: { [locale]: translations },\n missingWarn: !import.meta.env.PROD,\n fallbackWarn: !import.meta.env.PROD,\n }),\n [locale, translations],\n )\n\n const t = useMemo(\n () => (key: MessageKeys, params?: MessageParams): string => {\n const result = params !== undefined\n ? translate(context, key, params)\n : translate(context, key)\n return typeof result === 'string' ? result : key\n },\n [context],\n )\n\n return { t, locale }\n}\n","/**\n * React hook for Ziggy-like client-side URL generation.\n *\n * Reads serialized routes and the current request's matched-route snapshot\n * (injected by the `routes` option on {@link InertiaModuleOptions}) and\n * provides a type-safe `route()` function that mirrors the server-side\n * `buildRouteUrl()`, plus `current()` and `params` for current-route\n * introspection.\n *\n * @module\n */\n\nimport type { PageProps } from '@inertiajs/core'\nimport { usePage } from '@inertiajs/react'\nimport { useMemo } from 'react'\nimport type { CurrentRoute, RouteMatcher, RouteName, RouteParams, SerializedRoute, SerializedRoutes, TrailingSlashMode } from 'stratal/router'\n\ninterface RoutesPageProps extends PageProps {\n routes: SerializedRoutes\n trailingSlash?: TrailingSlashMode\n route: CurrentRoute\n}\n\n/**\n * Apply a trailing-slash mode to a URL or path.\n *\n * Pure reimplementation of `applyTrailingSlash()` from `stratal/router` —\n * mirrored here to keep the React bundle decoupled from server-only deps.\n *\n * - `'ignore'` — return as-is.\n * - `'always'` — append `/` unless path is root or last segment is file-like (`.json`, etc.).\n * - `'never'` — strip a single trailing `/` from the pathname (skip root).\n *\n * Preserves query string and hash. Handles relative paths and absolute URLs.\n */\nexport function applyTrailingSlash(url: string, mode: TrailingSlashMode): string {\n if (mode === 'ignore') return url\n\n const isAbsolute = /^https?:\\/\\//i.test(url)\n const parsed = isAbsolute ? new URL(url) : new URL(url, 'http://placeholder.local')\n const path = parsed.pathname\n if (path === '/') return url\n const hasTrailing = path.endsWith('/')\n\n if (mode === 'always' && !hasTrailing) {\n const lastSegment = path.slice(path.lastIndexOf('/') + 1)\n if (lastSegment.includes('.')) return url\n parsed.pathname = `${path}/`\n } else if (mode === 'never' && hasTrailing) {\n parsed.pathname = path.slice(0, -1)\n } else {\n return url\n }\n\n return isAbsolute\n ? parsed.toString()\n : `${parsed.pathname}${parsed.search}${parsed.hash}`\n}\n\n/**\n * Encode a path-param value while preserving forward slashes so catch-all\n * params (`:slug{.+}`) round-trip cleanly. Mirrors the server-side\n * `encodePathParam()` in `stratal/router`.\n */\nfunction encodePathParam(value: string): string {\n return value.split('/').map(encodeURIComponent).join('/')\n}\n\n/**\n * Build a URL from a serialized route definition.\n *\n * Mirrors `buildRouteUrl()` from `stratal/router` (pure reimplementation to\n * avoid pulling server-side dependencies into the browser bundle).\n */\nfunction buildUrl(route: SerializedRoute, name: string, params?: Record<string, string>): string {\n const allParams = { ...params }\n const consumedKeys = new Set<string>()\n let url = route.path\n\n if (allParams.locale && route.localePaths?.length) {\n url = `/${allParams.locale}${url === '/' ? '' : url}`\n consumedKeys.add('locale')\n }\n\n for (const paramName of route.paramNames) {\n const value = allParams[paramName]\n if (value === undefined) {\n throw new Error(`Missing required parameter \"${paramName}\" for route \"${name}\" (path: ${route.path})`)\n }\n url = url.replace(\n new RegExp(`:${paramName}(\\\\{[^}]*\\\\})?`),\n encodePathParam(value),\n )\n consumedKeys.add(paramName)\n }\n\n let domain: string | undefined\n if (route.domain) {\n domain = route.domain\n for (const domainParam of route.domainParamNames) {\n const value = allParams[domainParam]\n if (value === undefined) {\n throw new Error(`Missing required parameter \"${domainParam}\" for route \"${name}\" (domain: ${route.domain})`)\n }\n domain = domain.replace(`{${domainParam}}`, encodeURIComponent(value))\n consumedKeys.add(domainParam)\n }\n }\n\n const queryEntries = Object.entries(allParams).filter(([key]) => !consumedKeys.has(key))\n if (queryEntries.length > 0) {\n const queryString = queryEntries\n .filter(([, v]) => Boolean(v))\n .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)\n .join('&')\n url = `${url}${queryString.length ? `?${queryString}` : ''}`\n }\n\n if (domain) {\n url = `https://${domain}${url}`\n }\n\n return url\n}\n\n/**\n * Filter a param bag down to the keys the target route actually declares —\n * so a `companyId` carried over from the current URL never leaks into the\n * query string of an unrelated route.\n */\nfunction filterCarryover(carryover: Record<string, string>, route: SerializedRoute): Record<string, string> {\n const allowed = new Set<string>([...route.paramNames, ...route.domainParamNames])\n if (route.localePaths?.length) allowed.add('locale')\n if (allowed.size === 0) return {}\n\n const filtered: Record<string, string> = {}\n for (const [key, value] of Object.entries(carryover)) {\n if (allowed.has(key)) filtered[key] = value\n }\n return filtered\n}\n\n/**\n * Pure URL resolver. Mirrors what {@link useRoute}'s `route()` does, but\n * without React — exposed for testing and for non-hook callers.\n *\n * Merges params in order (last wins): sticky `defaults`, current-route\n * carryover (filtered to the target's declared params), explicit params.\n */\nexport function resolveUrl<N extends RouteName>(\n name: N,\n explicitParams: RouteParams<N> | undefined,\n routes: SerializedRoutes,\n currentRoute: CurrentRoute,\n trailingSlash: TrailingSlashMode = 'ignore',\n): string {\n const target = routes[name]\n if (!target) {\n throw new Error(`Route \"${name}\" not found.`)\n }\n\n const merged = {\n ...currentRoute.defaults,\n ...filterCarryover(currentRoute.params, target),\n ...explicitParams,\n } as Record<string, string>\n\n return applyTrailingSlash(buildUrl(target, name, merged), trailingSlash)\n}\n\n/**\n * Pure overload signatures for {@link matchCurrent} / `useRoute().current()`.\n *\n * - No arg → matched route name (or `null`).\n * - With a name → `true`/`false`. Strict-typed: only real route names and\n * dotted wildcard prefixes (`'users.*'`) are accepted.\n */\nexport function matchCurrent(currentRoute: CurrentRoute): RouteName | null\nexport function matchCurrent(currentRoute: CurrentRoute, name: RouteMatcher): boolean\nexport function matchCurrent(currentRoute: CurrentRoute, name?: RouteMatcher): RouteName | null | boolean {\n if (name === undefined) return currentRoute.name\n if (currentRoute.name === null) return false\n if (typeof name === 'string' && name.endsWith('.*')) {\n const prefix = name.slice(0, -1)\n return currentRoute.name.startsWith(prefix)\n }\n return currentRoute.name === name\n}\n\n/**\n * Hook that provides Ziggy-like route URL generation in React components.\n *\n * Consumes `routes` and the current-request snapshot (`route`) from Inertia\n * shared props. Route names and params are strictly typed from\n * `StratalRouteMap` (generated by `quarry route:types`).\n *\n * Requires the `routes` option to be set on `InertiaModule.forRoot()`.\n *\n * Sticky params — anything in `defaults` (set server-side via `Uri.defaults()`)\n * and anything in the current route's extracted `params` (filtered to the\n * target route's declared params) — are merged into every `route()` call.\n * Explicit params always win.\n *\n * @returns\n * - `route(name, params?)` — URL builder\n * - `current()` / `current(name)` — matched route name (or wildcard match)\n * - `params` — extracted params for the current request URL\n *\n * @example\n * ```tsx\n * import { useRoute } from '@stratal/inertia/react'\n *\n * export default function UserProfile({ user }) {\n * const { route, current, currentRoute } = useRoute()\n *\n * return (\n * <nav>\n * <a href={route('users.index')}>All Users</a>\n * <a href={route('users.show', { id: user.id })}>{user.name}</a>\n * {current('users.*') && <span>On a users page</span>}\n * {currentRoute.name === 'users.show' && <span>#{currentRoute.params.id}</span>}\n * </nav>\n * )\n * }\n * ```\n */\nexport function useRoute() {\n const page = usePage<RoutesPageProps>()\n const { routes, trailingSlash = 'ignore', route: currentRoute } = page.props\n\n const route = useMemo(\n () => <N extends RouteName>(name: N, params?: RouteParams<N>): string =>\n resolveUrl(name, params, routes, currentRoute, trailingSlash),\n [routes, trailingSlash, currentRoute],\n )\n\n const current = useMemo(\n () => {\n function impl(): RouteName | null\n function impl(name: RouteMatcher): boolean\n function impl(name?: RouteMatcher): RouteName | null | boolean {\n return name === undefined ? matchCurrent(currentRoute) : matchCurrent(currentRoute, name)\n }\n return impl\n },\n [currentRoute],\n )\n\n return { route, current, currentRoute, params: currentRoute.params }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"react.mjs","names":[],"sources":["../src/react/use-i18n.ts","../src/react/use-route.ts"],"sourcesContent":["import type { PageProps } from '@inertiajs/core'\nimport { usePage } from '@inertiajs/react'\nimport IntlMessageFormat from 'intl-messageformat'\nimport { useMemo } from 'react'\nimport type { MessageKeys, MessageParams } from 'stratal/i18n'\n\ninterface I18nPageProps extends PageProps {\n locale: string\n translations: Record<string, string>\n}\n\nexport function useI18n() {\n const { locale, translations } = usePage<I18nPageProps>().props\n\n const t = useMemo(() => {\n const compiled = new Map<string, IntlMessageFormat>()\n\n for (const [key, value] of Object.entries(translations)) {\n compiled.set(key, new IntlMessageFormat(value, locale))\n }\n\n return (key: MessageKeys, params?: MessageParams): string => {\n const msg = compiled.get(key)\n if (!msg) return key\n return String(msg.format(params as Record<string, string | number | boolean>))\n }\n }, [locale, translations])\n\n return { t, locale }\n}\n","/**\n * React hook for Ziggy-like client-side URL generation.\n *\n * Reads serialized routes and the current request's matched-route snapshot\n * (injected by the `routes` option on {@link InertiaModuleOptions}) and\n * provides a type-safe `route()` function that mirrors the server-side\n * `buildRouteUrl()`, plus `current()` and `params` for current-route\n * introspection.\n *\n * @module\n */\n\nimport type { PageProps } from '@inertiajs/core'\nimport { usePage } from '@inertiajs/react'\nimport { useMemo } from 'react'\nimport type { CurrentRoute, RouteMatcher, RouteName, RouteParams, SerializedRoute, SerializedRoutes, TrailingSlashMode } from 'stratal/router'\n\ninterface RoutesPageProps extends PageProps {\n routes: SerializedRoutes\n trailingSlash?: TrailingSlashMode\n route: CurrentRoute\n}\n\n/**\n * Apply a trailing-slash mode to a URL or path.\n *\n * Pure reimplementation of `applyTrailingSlash()` from `stratal/router` —\n * mirrored here to keep the React bundle decoupled from server-only deps.\n *\n * - `'ignore'` — return as-is.\n * - `'always'` — append `/` unless path is root or last segment is file-like (`.json`, etc.).\n * - `'never'` — strip a single trailing `/` from the pathname (skip root).\n *\n * Preserves query string and hash. Handles relative paths and absolute URLs.\n */\nexport function applyTrailingSlash(url: string, mode: TrailingSlashMode): string {\n if (mode === 'ignore') return url\n\n const isAbsolute = /^https?:\\/\\//i.test(url)\n const parsed = isAbsolute ? new URL(url) : new URL(url, 'http://placeholder.local')\n const path = parsed.pathname\n if (path === '/') return url\n const hasTrailing = path.endsWith('/')\n\n if (mode === 'always' && !hasTrailing) {\n const lastSegment = path.slice(path.lastIndexOf('/') + 1)\n if (lastSegment.includes('.')) return url\n parsed.pathname = `${path}/`\n } else if (mode === 'never' && hasTrailing) {\n parsed.pathname = path.slice(0, -1)\n } else {\n return url\n }\n\n return isAbsolute\n ? parsed.toString()\n : `${parsed.pathname}${parsed.search}${parsed.hash}`\n}\n\n/**\n * Encode a path-param value while preserving forward slashes so catch-all\n * params (`:slug{.+}`) round-trip cleanly. Mirrors the server-side\n * `encodePathParam()` in `stratal/router`.\n */\nfunction encodePathParam(value: string): string {\n return value.split('/').map(encodeURIComponent).join('/')\n}\n\n/**\n * Build a URL from a serialized route definition.\n *\n * Mirrors `buildRouteUrl()` from `stratal/router` (pure reimplementation to\n * avoid pulling server-side dependencies into the browser bundle).\n */\nfunction buildUrl(route: SerializedRoute, name: string, params?: Record<string, string>): string {\n const allParams = { ...params }\n const consumedKeys = new Set<string>()\n let url = route.path\n\n if (allParams.locale && route.localePaths?.length) {\n url = `/${allParams.locale}${url === '/' ? '' : url}`\n consumedKeys.add('locale')\n }\n\n for (const paramName of route.paramNames) {\n const value = allParams[paramName]\n if (value === undefined) {\n throw new Error(`Missing required parameter \"${paramName}\" for route \"${name}\" (path: ${route.path})`)\n }\n url = url.replace(\n new RegExp(`:${paramName}(\\\\{[^}]*\\\\})?`),\n encodePathParam(value),\n )\n consumedKeys.add(paramName)\n }\n\n let domain: string | undefined\n if (route.domain) {\n domain = route.domain\n for (const domainParam of route.domainParamNames) {\n const value = allParams[domainParam]\n if (value === undefined) {\n throw new Error(`Missing required parameter \"${domainParam}\" for route \"${name}\" (domain: ${route.domain})`)\n }\n domain = domain.replace(`{${domainParam}}`, encodeURIComponent(value))\n consumedKeys.add(domainParam)\n }\n }\n\n const queryEntries = Object.entries(allParams).filter(([key]) => !consumedKeys.has(key))\n if (queryEntries.length > 0) {\n const queryString = queryEntries\n .filter(([, v]) => Boolean(v))\n .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)\n .join('&')\n url = `${url}${queryString.length ? `?${queryString}` : ''}`\n }\n\n if (domain) {\n url = `https://${domain}${url}`\n }\n\n return url\n}\n\n/**\n * Filter a param bag down to the keys the target route actually declares —\n * so a `companyId` carried over from the current URL never leaks into the\n * query string of an unrelated route.\n */\nfunction filterCarryover(carryover: Record<string, string>, route: SerializedRoute): Record<string, string> {\n const allowed = new Set<string>([...route.paramNames, ...route.domainParamNames])\n if (route.localePaths?.length) allowed.add('locale')\n if (allowed.size === 0) return {}\n\n const filtered: Record<string, string> = {}\n for (const [key, value] of Object.entries(carryover)) {\n if (allowed.has(key)) filtered[key] = value\n }\n return filtered\n}\n\n/**\n * Pure URL resolver. Mirrors what {@link useRoute}'s `route()` does, but\n * without React — exposed for testing and for non-hook callers.\n *\n * Merges params in order (last wins): sticky `defaults`, current-route\n * carryover (filtered to the target's declared params), explicit params.\n */\nexport function resolveUrl<N extends RouteName>(\n name: N,\n explicitParams: RouteParams<N> | undefined,\n routes: SerializedRoutes,\n currentRoute: CurrentRoute,\n trailingSlash: TrailingSlashMode = 'ignore',\n): string {\n const target = routes[name]\n if (!target) {\n throw new Error(`Route \"${name}\" not found.`)\n }\n\n const merged = {\n ...currentRoute.defaults,\n ...filterCarryover(currentRoute.params, target),\n ...explicitParams,\n } as Record<string, string>\n\n return applyTrailingSlash(buildUrl(target, name, merged), trailingSlash)\n}\n\n/**\n * Pure overload signatures for {@link matchCurrent} / `useRoute().current()`.\n *\n * - No arg → matched route name (or `null`).\n * - With a name → `true`/`false`. Strict-typed: only real route names and\n * dotted wildcard prefixes (`'users.*'`) are accepted.\n */\nexport function matchCurrent(currentRoute: CurrentRoute): RouteName | null\nexport function matchCurrent(currentRoute: CurrentRoute, name: RouteMatcher): boolean\nexport function matchCurrent(currentRoute: CurrentRoute, name?: RouteMatcher): RouteName | null | boolean {\n if (name === undefined) return currentRoute.name\n if (currentRoute.name === null) return false\n if (typeof name === 'string' && name.endsWith('.*')) {\n const prefix = name.slice(0, -1)\n return currentRoute.name.startsWith(prefix)\n }\n return currentRoute.name === name\n}\n\n/**\n * Hook that provides Ziggy-like route URL generation in React components.\n *\n * Consumes `routes` and the current-request snapshot (`route`) from Inertia\n * shared props. Route names and params are strictly typed from\n * `StratalRouteMap` (generated by `quarry route:types`).\n *\n * Requires the `routes` option to be set on `InertiaModule.forRoot()`.\n *\n * Sticky params — anything in `defaults` (set server-side via `Uri.defaults()`)\n * and anything in the current route's extracted `params` (filtered to the\n * target route's declared params) — are merged into every `route()` call.\n * Explicit params always win.\n *\n * @returns\n * - `route(name, params?)` — URL builder\n * - `current()` / `current(name)` — matched route name (or wildcard match)\n * - `params` — extracted params for the current request URL\n *\n * @example\n * ```tsx\n * import { useRoute } from '@stratal/inertia/react'\n *\n * export default function UserProfile({ user }) {\n * const { route, current, currentRoute } = useRoute()\n *\n * return (\n * <nav>\n * <a href={route('users.index')}>All Users</a>\n * <a href={route('users.show', { id: user.id })}>{user.name}</a>\n * {current('users.*') && <span>On a users page</span>}\n * {currentRoute.name === 'users.show' && <span>#{currentRoute.params.id}</span>}\n * </nav>\n * )\n * }\n * ```\n */\nexport function useRoute() {\n const page = usePage<RoutesPageProps>()\n const { routes, trailingSlash = 'ignore', route: currentRoute } = page.props\n\n const route = useMemo(\n () => <N extends RouteName>(name: N, params?: RouteParams<N>): string =>\n resolveUrl(name, params, routes, currentRoute, trailingSlash),\n [routes, trailingSlash, currentRoute],\n )\n\n const current = useMemo(\n () => {\n function impl(): RouteName | null\n function impl(name: RouteMatcher): boolean\n function impl(name?: RouteMatcher): RouteName | null | boolean {\n return name === undefined ? matchCurrent(currentRoute) : matchCurrent(currentRoute, name)\n }\n return impl\n },\n [currentRoute],\n )\n\n return { route, current, currentRoute, params: currentRoute.params }\n}\n"],"mappings":"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"}
|