@zerotal/inertia 1.14.3 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,27 @@ follows the Zerotal monorepo's unified versioning.
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [1.15.0] — 2026-09-04
12
+
13
+ ### Added
14
+
15
+ - **`bun zt route:types` now regenerates the page registry too.** Adding a page and rendering
16
+ it failed typecheck with `TS2345: Argument of type '"ops/orders"' is not assignable to
17
+ parameter of type 'PageName'` — accurate and unhelpful, because it reads as a mistyped page
18
+ name when the actual state is a registry that has not been rebuilt. `route:types` is the
19
+ command whose name says it fixes that and used to regenerate only the routes half.
20
+
21
+ `InertiaProvider` registers `generatePageRegistry` through core's `registerTypeGenerator`,
22
+ so one command refreshes both, and `route:types --check` gates on both in CI. `PageName`
23
+ also carries a doc comment saying what to run, so the editor hover answers the question the
24
+ compiler error raises.
25
+
26
+ ### Fixed
27
+
28
+ - **`generatePageRegistry` rewrote `pages.generated.ts` even when nothing had changed**,
29
+ churning its mtime on every dev rebuild and at every production boot — which is what a file
30
+ watcher keys on. It now compares before writing.
31
+
11
32
  ## [1.14.0] — 2026-09-01
12
33
 
13
34
  ### Changed — **BREAKING**
package/api-surface.md CHANGED
@@ -3,6 +3,9 @@
3
3
  <!-- AUTO-GENERATED by scripts/api-surface.ts. Do not edit by hand.
4
4
  Run `bun run api:surface` to regenerate after an intentional API change. -->
5
5
 
6
+ > Not a guide — this is a flat, alphabetical snapshot used to detect unintended
7
+ > API changes. For how to use these, read the [documentation](https://zerotal.dev/docs).
8
+
6
9
  ## . `(./src/index.ts)`
7
10
 
8
11
  class AlwaysProp = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zerotal/inertia",
3
- "version": "1.14.3",
3
+ "version": "1.15.0",
4
4
  "license": "MIT",
5
5
  "maturity": "stable",
6
6
  "private": false,
@@ -34,7 +34,7 @@
34
34
  "typecheck": "tsc --noEmit"
35
35
  },
36
36
  "dependencies": {
37
- "@zerotal/core": "1.14.3"
37
+ "@zerotal/core": "1.15.0"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "react": "^18 || ^19",
@@ -11,7 +11,7 @@
11
11
  * Import paths in the generated file are computed relative to the generated
12
12
  * file's own location (`resources/js/`), so any pages directory works.
13
13
  *
14
- * Run via: bun zt inertia:build
14
+ * Run via: bun zt route:types (or bun zt inertia:build, which also bundles).
15
15
  * Or automatically during InertiaProvider.onBooted() in production.
16
16
  */
17
17
  import { config } from "@zerotal/core";
@@ -24,21 +24,39 @@ import { DEFAULT_PAGES_DIR } from "./config.ts";
24
24
  const GENERATED_DIR = "resources/js";
25
25
  const GENERATED_FILE = `${GENERATED_DIR}/pages.generated.ts`;
26
26
 
27
+ /**
28
+ * What a registry generation did, for `route:types` to report and `--check` to
29
+ * gate on.
30
+ *
31
+ * @internal Returned by the `@internal` {@link generatePageRegistry}; the shape
32
+ * core's type-generator hook expects, not app-facing API.
33
+ */
34
+ export interface PageRegistryResult {
35
+ /** The generated file, relative to the project root. */
36
+ file: string;
37
+ /** Page count, phrased for a summary line. */
38
+ summary: string;
39
+ /** Whether the file on disk differed from what the scan produced. */
40
+ changed: boolean;
41
+ }
42
+
27
43
  /**
28
44
  * Scan the pages directory and (re)write `resources/js/pages.generated.ts`, a map of
29
45
  * page name → dynamic `import()` thunk that `Bun.build({ splitting: true })` turns into
30
- * one lazy-loaded chunk per page. Called by the `inertia:build` / `make:page` commands
31
- * and by `InertiaProvider` at boot in production.
46
+ * one lazy-loaded chunk per page. Called by the `route:types` / `inertia:build` /
47
+ * `make:page` commands and by `InertiaProvider` at boot in production.
32
48
  *
33
49
  * @param cwd - Project root to scan and write relative to. Defaults to `process.cwd()`.
34
50
  * @param pagesDir - Pages directory relative to `cwd`. Defaults to the `inertia.pagesDir` config (`resources/js/pages`).
35
- * @returns Resolves once the registry file has been written.
51
+ * @param options - `check: true` compares against disk and reports without writing.
52
+ * @returns What was generated, and whether it differed from the file on disk.
36
53
  * @internal Framework scaffolding; not called from application code.
37
54
  */
38
55
  export async function generatePageRegistry(
39
56
  cwd = process.cwd(),
40
57
  pagesDir: string = config.safe("inertia.pagesDir", DEFAULT_PAGES_DIR),
41
- ): Promise<void> {
58
+ options: { check?: boolean } = {},
59
+ ): Promise<PageRegistryResult> {
42
60
  // Normalise to a cwd-relative POSIX path without a trailing slash.
43
61
  const dir = pagesDir.replace(/\\/g, "/").replace(/\/+$/, "");
44
62
 
@@ -79,7 +97,7 @@ export async function generatePageRegistry(
79
97
 
80
98
  const content = [
81
99
  "// Auto-generated by @zerotal/inertia — do not edit manually.",
82
- "// Regenerate with: bun zt inertia:build",
100
+ "// Regenerate with: bun zt route:types (or bun zt inertia:build, which also bundles)",
83
101
  "//",
84
102
  "// IMPORTANT: These are dynamic import THUNKS, not static imports.",
85
103
  "// Bun.build with splitting:true creates one .js chunk per page.",
@@ -107,9 +125,29 @@ export async function generatePageRegistry(
107
125
  "",
108
126
  ].join("\n");
109
127
 
110
- await Bun.write(`${cwd}/${GENERATED_FILE}`, content);
128
+ // Compare before writing. The file is regenerated on every dev rebuild and at
129
+ // boot in production, and rewriting identical bytes churns the mtime — which
130
+ // is what a file watcher keys on, so the write would retrigger the rebuild
131
+ // that produced it. It also gives `route:types --check` something to gate on.
132
+ const target = `${cwd}/${GENERATED_FILE}`;
133
+ const existing = await Bun.file(target)
134
+ .text()
135
+ .catch(() => null);
136
+ const changed = existing !== content;
137
+
138
+ if (changed && options.check !== true) {
139
+ await Bun.write(target, content);
140
+ }
141
+
142
+ if (changed) {
143
+ frameworkLog("inertia").info(`Generated page registry: ${thunks.length} pages`, {
144
+ pages: thunks.length,
145
+ });
146
+ }
111
147
 
112
- frameworkLog("inertia").info(`Generated page registry: ${thunks.length} pages`, {
113
- pages: thunks.length,
114
- });
148
+ return {
149
+ file: GENERATED_FILE,
150
+ summary: `${thunks.length} page${thunks.length === 1 ? "" : "s"}`,
151
+ changed,
152
+ };
115
153
  }
package/src/index.ts CHANGED
@@ -64,6 +64,7 @@ export { PrecognitionMiddleware } from "./middleware/PrecognitionMiddleware.ts";
64
64
  export { sharedProps } from "./SharedProps.ts";
65
65
  export { assetVersion, setAssetVersion } from "./version.ts";
66
66
  export { generatePageRegistry } from "./PageRegistry.ts";
67
+ export type { PageRegistryResult } from "./PageRegistry.ts";
67
68
  // The typed page registry: `InertiaPageRegistry` is what `pages.generated.ts`
68
69
  // augments, `SharedProps` is what the app declares for `Inertia.share()`.
69
70
  export type {
package/src/pages.ts CHANGED
@@ -88,12 +88,31 @@ type AllSharedProps = SharedProps & BuiltInSharedProps;
88
88
  /** The generated `pages` map, or an empty map before the registry exists. */
89
89
  type PageModules = InertiaPageRegistry extends { pages: infer Map } ? Map : Record<never, never>;
90
90
 
91
- /** Every page name in the generated registry. `never` until it is generated. */
91
+ /**
92
+ * Every page name in the generated registry. `never` until it is generated.
93
+ *
94
+ * **If a name you can see on disk is rejected here, the registry is stale, not
95
+ * the name** — `resources/js/pages.generated.ts` is written by a scan of the
96
+ * pages directory and does not know about a file added since. Regenerate it:
97
+ *
98
+ * ```sh
99
+ * bun zt route:types
100
+ * ```
101
+ *
102
+ * TypeScript reports the mismatch as TS2345 ("not assignable to parameter of
103
+ * type 'PageName'"), which reads like a typo, so this note is here to be the
104
+ * first thing the hover shows.
105
+ */
92
106
  export type PageName = Extract<keyof PageModules, string>;
93
107
 
94
108
  /**
95
109
  * What the Inertia helpers accept as a component name: the generated page names
96
110
  * once the registry exists, any string before that.
111
+ *
112
+ * The `never` fallback is a bootstrap — a new app type-checks before its first
113
+ * build — but it does mean page names are unchecked until the registry exists
114
+ * once, so the first error of this kind arrives after a build that succeeded.
115
+ * See {@link PageName} if a name you can see on disk is being rejected.
97
116
  */
98
117
  export type PageTarget = [PageName] extends [never] ? string : PageName;
99
118
 
@@ -1,4 +1,5 @@
1
1
  import { ServiceProvider, Router, ConfigError, ThrottleMiddleware } from "@zerotal/core";
2
+ import { registerTypeGenerator } from "@zerotal/core/commands";
2
3
  import {
3
4
  registerDevBuildHook,
4
5
  pruneBuildOutput,
@@ -132,6 +133,14 @@ export class InertiaProvider extends ServiceProvider {
132
133
  // trigger a full pages-manifest sync + asset rebuild without @zerotal/core
133
134
  // importing @zerotal/inertia (which would create a circular dependency).
134
135
  const cwd = process.cwd();
136
+
137
+ // Same inversion, for `bun zt route:types`. The page registry and the route
138
+ // map are both "types generated from the file tree" and go stale on the same
139
+ // edits, but only the routes half was wired to the command named for it —
140
+ // so adding a page, running `route:types`, and getting the same `PageName`
141
+ // error back sent people looking at their page name instead of the registry.
142
+ registerTypeGenerator("inertia", (options) => generatePageRegistry(cwd, undefined, options));
143
+
135
144
  registerDevBuildHook("inertia", async () => {
136
145
  await generatePageRegistry(cwd);
137
146
  const plugins = [...(await detectCssPlugins(cwd)), ...(await detectVuePlugin(cwd))];