@timber-js/app 0.2.0-alpha.146 → 0.2.0-alpha.148
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/_chunks/{actions-OIZ8siQu.js → actions-a0RczDnB.js} +8 -34
- package/dist/_chunks/{actions-OIZ8siQu.js.map → actions-a0RczDnB.js.map} +1 -1
- package/dist/_chunks/cache-api-Co-yI-gP.js +424 -0
- package/dist/_chunks/cache-api-Co-yI-gP.js.map +1 -0
- package/dist/_chunks/{metadata-routes-BjepjefT.js → canonicalize-P41GR6tY.js} +65 -2
- package/dist/_chunks/canonicalize-P41GR6tY.js.map +1 -0
- package/dist/_chunks/{cli-schema-sync-D2IpXicg.js → cli-schema-sync-DJDdyEae.js} +157 -313
- package/dist/_chunks/cli-schema-sync-DJDdyEae.js.map +1 -0
- package/dist/_chunks/cli-schema-sync-D_IhHEUW.js +2 -0
- package/dist/_chunks/{define-DMVhsipD.js → define-Bssfp6ot.js} +3 -33
- package/dist/_chunks/define-Bssfp6ot.js.map +1 -0
- package/dist/_chunks/{define-C3RvHQPK.js → define-CFmvb4Bt.js} +15 -7
- package/dist/_chunks/define-CFmvb4Bt.js.map +1 -0
- package/dist/_chunks/{href-validation-DyZIzfMJ.js → href-validation-FiA_rToW.js} +2 -1
- package/dist/_chunks/{href-validation-DyZIzfMJ.js.map → href-validation-FiA_rToW.js.map} +1 -1
- package/dist/_chunks/{tracing-M_zKubhk.js → logger-CM0clugc.js} +394 -107
- package/dist/_chunks/logger-CM0clugc.js.map +1 -0
- package/dist/_chunks/navigation-root-bUDZLw74.js.map +1 -1
- package/dist/_chunks/plugin-context-DurqFBuY.js +2 -0
- package/dist/_chunks/plugin-context-GfDWsdd2.js +268 -0
- package/dist/_chunks/plugin-context-GfDWsdd2.js.map +1 -0
- package/dist/_chunks/{resolve-schema-Dj8tQloi.js → resolve-schema-3iUvBV5T.js} +3 -30
- package/dist/_chunks/resolve-schema-3iUvBV5T.js.map +1 -0
- package/dist/_chunks/{schema-bridge-DK_89h8O.js → schema-bridge-BY3QLBL7.js} +35 -2
- package/dist/_chunks/schema-bridge-BY3QLBL7.js.map +1 -0
- package/dist/_chunks/tree-match-D2l830j2.js +102 -0
- package/dist/_chunks/tree-match-D2l830j2.js.map +1 -0
- package/dist/_chunks/use-segment-params-D1CzlgBo.js.map +1 -1
- package/dist/_chunks/{walkers-7FBPM-hX.js → walkers-DvWDj453.js} +154 -88
- package/dist/_chunks/walkers-DvWDj453.js.map +1 -0
- package/dist/adapters/cloudflare-kv-cache.d.ts.map +1 -1
- package/dist/adapters/cloudflare-kv-cache.js +36 -14
- package/dist/adapters/cloudflare-kv-cache.js.map +1 -1
- package/dist/adapters/compress-module.d.ts +5 -4
- package/dist/adapters/compress-module.d.ts.map +1 -1
- package/dist/adapters/nitro.d.ts +1 -3
- package/dist/adapters/nitro.d.ts.map +1 -1
- package/dist/adapters/nitro.js +14 -134
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/cache/cache-api.d.ts.map +1 -1
- package/dist/cache/index.d.ts +3 -1
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +20 -235
- package/dist/cache/index.js.map +1 -1
- package/dist/cache/invalidation-epoch.d.ts +43 -0
- package/dist/cache/invalidation-epoch.d.ts.map +1 -0
- package/dist/cache/redis-handler.d.ts +13 -0
- package/dist/cache/redis-handler.d.ts.map +1 -1
- package/dist/cache/timber-cache.d.ts +17 -9
- package/dist/cache/timber-cache.d.ts.map +1 -1
- package/dist/cli-schema-sync.d.ts +21 -1
- package/dist/cli-schema-sync.d.ts.map +1 -1
- package/dist/cli.d.ts +12 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +26 -31
- package/dist/cli.js.map +1 -1
- package/dist/client/browser-entry/hmr.d.ts.map +1 -1
- package/dist/client/browser-entry/hydrate.d.ts.map +1 -1
- package/dist/client/browser-entry/post-hydration.d.ts.map +1 -1
- package/dist/client/browser-entry/router-init.d.ts.map +1 -1
- package/dist/client/error-boundary.d.ts.map +1 -1
- package/dist/client/error-boundary.js +13 -4
- package/dist/client/error-boundary.js.map +1 -1
- package/dist/client/error-reconstituter.d.ts +8 -1
- package/dist/client/error-reconstituter.d.ts.map +1 -1
- package/dist/client/history.d.ts +10 -17
- package/dist/client/history.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/internal.js +30 -33
- package/dist/client/internal.js.map +1 -1
- package/dist/client/navigation-api.d.ts +9 -2
- package/dist/client/navigation-api.d.ts.map +1 -1
- package/dist/client/navigation-context.d.ts +0 -11
- package/dist/client/navigation-context.d.ts.map +1 -1
- package/dist/client/navigation-root.d.ts +0 -5
- package/dist/client/navigation-root.d.ts.map +1 -1
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/client/use-search-params.d.ts +10 -5
- package/dist/client/use-search-params.d.ts.map +1 -1
- package/dist/codec.js +1 -1
- package/dist/config-validation.d.ts.map +1 -1
- package/dist/cookies/define-cookie.d.ts.map +1 -1
- package/dist/cookies/index.js +2 -4
- package/dist/cookies/index.js.map +1 -1
- package/dist/dev-tools/dev-page-shell.d.ts +1 -0
- package/dist/dev-tools/dev-page-shell.d.ts.map +1 -1
- package/dist/dev-tools/hydration-overlay.d.ts +11 -0
- package/dist/dev-tools/hydration-overlay.d.ts.map +1 -0
- package/dist/dev-tools/index.d.ts +1 -2
- package/dist/dev-tools/index.d.ts.map +1 -1
- package/dist/fonts/ast-walk.d.ts +68 -0
- package/dist/fonts/ast-walk.d.ts.map +1 -0
- package/dist/fonts/ast.d.ts +23 -11
- package/dist/fonts/ast.d.ts.map +1 -1
- package/dist/fonts/bundle.d.ts +3 -1
- package/dist/fonts/bundle.d.ts.map +1 -1
- package/dist/fonts/dev-middleware.d.ts +1 -1
- package/dist/fonts/dev-middleware.d.ts.map +1 -1
- package/dist/fonts/fallbacks.d.ts +0 -7
- package/dist/fonts/fallbacks.d.ts.map +1 -1
- package/dist/fonts/google.d.ts +4 -2
- package/dist/fonts/google.d.ts.map +1 -1
- package/dist/fonts/local.d.ts +3 -3
- package/dist/fonts/local.d.ts.map +1 -1
- package/dist/fonts/paths.d.ts +43 -0
- package/dist/fonts/paths.d.ts.map +1 -0
- package/dist/fonts/pipeline.d.ts +7 -21
- package/dist/fonts/pipeline.d.ts.map +1 -1
- package/dist/fonts/transform.d.ts.map +1 -1
- package/dist/fonts/virtual-modules.d.ts +1 -18
- package/dist/fonts/virtual-modules.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +609 -406
- package/dist/index.js.map +1 -1
- package/dist/params/index.js +1 -1
- package/dist/params/resolve-schema.d.ts.map +1 -1
- package/dist/plugin-context.d.ts +28 -5
- package/dist/plugin-context.d.ts.map +1 -1
- package/dist/plugins/cache.d.ts +13 -5
- package/dist/plugins/cache.d.ts.map +1 -1
- package/dist/plugins/dev-server.d.ts.map +1 -1
- package/dist/plugins/fonts.d.ts +1 -2
- package/dist/plugins/fonts.d.ts.map +1 -1
- package/dist/plugins/routing.d.ts.map +1 -1
- package/dist/routing/codegen-types.d.ts +4 -0
- package/dist/routing/codegen-types.d.ts.map +1 -1
- package/dist/routing/codegen.d.ts.map +1 -1
- package/dist/routing/convention-lint.d.ts.map +1 -1
- package/dist/routing/file-cache.d.ts +8 -0
- package/dist/routing/file-cache.d.ts.map +1 -0
- package/dist/routing/index.js +2 -2
- package/dist/routing/interception.d.ts +0 -5
- package/dist/routing/interception.d.ts.map +1 -1
- package/dist/routing/manifest-codegen.d.ts +32 -0
- package/dist/routing/manifest-codegen.d.ts.map +1 -0
- package/dist/routing/scanner.d.ts.map +1 -1
- package/dist/schema-bridge.d.ts +15 -0
- package/dist/schema-bridge.d.ts.map +1 -1
- package/dist/search-params/define.d.ts.map +1 -1
- package/dist/search-params/index.js +7 -3
- package/dist/search-params/index.js.map +1 -1
- package/dist/search-params/wrappers.d.ts.map +1 -1
- package/dist/segment-params/define.d.ts.map +1 -1
- package/dist/segment-params/index.js +1 -1
- package/dist/server/access-gate.d.ts.map +1 -1
- package/dist/server/actions.d.ts +0 -14
- package/dist/server/actions.d.ts.map +1 -1
- package/dist/server/build-manifest.d.ts +0 -29
- package/dist/server/build-manifest.d.ts.map +1 -1
- package/dist/server/canonicalize.d.ts +10 -0
- package/dist/server/canonicalize.d.ts.map +1 -1
- package/dist/server/deny-boundary.d.ts +27 -2
- package/dist/server/deny-boundary.d.ts.map +1 -1
- package/dist/server/deny-renderer.d.ts +1 -1
- package/dist/server/deny-renderer.d.ts.map +1 -1
- package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
- package/dist/server/html-injector-core.d.ts +8 -0
- package/dist/server/html-injector-core.d.ts.map +1 -1
- package/dist/server/html-injectors.d.ts.map +1 -1
- package/dist/server/index.js +2 -2
- package/dist/server/internal.d.ts +1 -5
- package/dist/server/internal.d.ts.map +1 -1
- package/dist/server/internal.js +238 -349
- package/dist/server/internal.js.map +1 -1
- package/dist/server/node-stream-transforms.d.ts +0 -11
- package/dist/server/node-stream-transforms.d.ts.map +1 -1
- package/dist/server/render-utils.d.ts +25 -0
- package/dist/server/render-utils.d.ts.map +1 -0
- package/dist/server/route-element-builder.d.ts +11 -25
- package/dist/server/route-element-builder.d.ts.map +1 -1
- package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/helpers.d.ts +2 -18
- package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
- package/dist/server/ssr-render.d.ts.map +1 -1
- package/dist/server/status-code-resolver.d.ts +6 -0
- package/dist/server/status-code-resolver.d.ts.map +1 -1
- package/dist/server/utils/mdx-file.d.ts +15 -0
- package/dist/server/utils/mdx-file.d.ts.map +1 -0
- package/dist/shared/href-validation.d.ts.map +1 -1
- package/dist/shims/image.d.ts +15 -15
- package/package.json +3 -3
- package/src/adapters/cloudflare-kv-cache.ts +56 -19
- package/src/adapters/compress-module.ts +27 -118
- package/src/adapters/nitro.ts +3 -41
- package/src/adapters/raw-imports.d.ts +5 -0
- package/src/cache/cache-api.ts +9 -1
- package/src/cache/index.ts +5 -2
- package/src/cache/invalidation-epoch.ts +93 -0
- package/src/cache/redis-handler.ts +33 -2
- package/src/cache/timber-cache.ts +109 -53
- package/src/cli-schema-sync.ts +123 -30
- package/src/cli.ts +33 -37
- package/src/client/browser-entry/hmr.ts +0 -11
- package/src/client/browser-entry/hydrate.ts +39 -5
- package/src/client/browser-entry/post-hydration.ts +6 -15
- package/src/client/browser-entry/router-init.ts +98 -3
- package/src/client/error-boundary.tsx +14 -0
- package/src/client/error-reconstituter.tsx +15 -1
- package/src/client/history.ts +10 -24
- package/src/client/navigation-api.ts +14 -9
- package/src/client/navigation-context.ts +1 -21
- package/src/client/navigation-root.tsx +0 -8
- package/src/client/router.ts +18 -8
- package/src/client/rsc-fetch.ts +7 -2
- package/src/client/use-search-params.ts +10 -5
- package/src/config-validation.ts +33 -25
- package/src/cookies/define-cookie.ts +2 -18
- package/src/dev-tools/dev-page-shell.ts +6 -2
- package/src/dev-tools/hydration-overlay.ts +158 -0
- package/src/dev-tools/index.ts +3 -16
- package/src/fonts/ast-walk.ts +235 -0
- package/src/fonts/ast.ts +91 -249
- package/src/fonts/bundle.ts +16 -9
- package/src/fonts/dev-middleware.ts +16 -5
- package/src/fonts/fallbacks.ts +0 -23
- package/src/fonts/google.ts +7 -3
- package/src/fonts/local.ts +7 -6
- package/src/fonts/paths.ts +58 -0
- package/src/fonts/pipeline.ts +27 -48
- package/src/fonts/transform.ts +27 -0
- package/src/fonts/virtual-modules.ts +1 -57
- package/src/index.ts +5 -11
- package/src/params/resolve-schema.ts +2 -34
- package/src/plugin-context.ts +95 -17
- package/src/plugins/cache.ts +64 -17
- package/src/plugins/dev-server.ts +61 -9
- package/src/plugins/fonts.ts +21 -6
- package/src/plugins/routing.ts +5 -348
- package/src/routing/codegen-types.ts +4 -0
- package/src/routing/codegen.ts +66 -8
- package/src/routing/convention-lint.ts +4 -3
- package/src/routing/file-cache.ts +23 -0
- package/src/routing/interception.ts +2 -21
- package/src/routing/manifest-codegen.ts +290 -0
- package/src/routing/scanner.ts +85 -80
- package/src/routing/schema-validation.ts +2 -2
- package/src/schema-bridge.ts +63 -2
- package/src/search-params/define.ts +34 -2
- package/src/search-params/wrappers.ts +21 -3
- package/src/segment-params/define.ts +2 -62
- package/src/server/access-gate.tsx +8 -0
- package/src/server/actions.ts +8 -31
- package/src/server/build-manifest.ts +0 -56
- package/src/server/canonicalize.ts +6 -3
- package/src/server/deny-boundary.ts +106 -13
- package/src/server/deny-renderer.ts +46 -10
- package/src/server/error-boundary-wrapper.ts +1 -14
- package/src/server/html-injector-core.ts +19 -0
- package/src/server/html-injectors.ts +14 -4
- package/src/server/internal.ts +1 -18
- package/src/server/node-stream-transforms.ts +5 -72
- package/src/server/render-utils.ts +31 -0
- package/src/server/route-element-builder.ts +77 -47
- package/src/server/route-matcher.ts +22 -6
- package/src/server/rsc-entry/error-renderer.ts +2 -12
- package/src/server/rsc-entry/helpers.ts +6 -34
- package/src/server/rsc-entry/index.ts +11 -0
- package/src/server/rsc-entry/render-route.ts +7 -9
- package/src/server/rsc-entry/rsc-payload.ts +2 -1
- package/src/server/rsc-entry/ssr-renderer.ts +2 -2
- package/src/server/ssr-render.ts +1 -11
- package/src/server/status-code-resolver.ts +3 -2
- package/src/server/tree-match.ts +15 -5
- package/src/server/utils/mdx-file.ts +22 -0
- package/src/shared/href-validation.ts +8 -0
- package/dist/_chunks/cli-schema-sync-D2IpXicg.js.map +0 -1
- package/dist/_chunks/cli-schema-sync-DH-M8Poy.js +0 -2
- package/dist/_chunks/define-C3RvHQPK.js.map +0 -1
- package/dist/_chunks/define-DMVhsipD.js.map +0 -1
- package/dist/_chunks/handler-store-BVDgyTh7.js +0 -78
- package/dist/_chunks/handler-store-BVDgyTh7.js.map +0 -1
- package/dist/_chunks/logger-Dt-jQaBO.js +0 -291
- package/dist/_chunks/logger-Dt-jQaBO.js.map +0 -1
- package/dist/_chunks/metadata-routes-BjepjefT.js.map +0 -1
- package/dist/_chunks/resolve-schema-Dj8tQloi.js.map +0 -1
- package/dist/_chunks/schema-bridge-DK_89h8O.js.map +0 -1
- package/dist/_chunks/tracing-M_zKubhk.js.map +0 -1
- package/dist/_chunks/walkers-7FBPM-hX.js.map +0 -1
- package/dist/_chunks/warnings-CNPXaypM.js +0 -125
- package/dist/_chunks/warnings-CNPXaypM.js.map +0 -1
- package/dist/client/link-pending-store.d.ts +0 -65
- package/dist/client/link-pending-store.d.ts.map +0 -1
- package/dist/client/nav-link-store.d.ts +0 -27
- package/dist/client/nav-link-store.d.ts.map +0 -1
- package/dist/dev-tools/warnings.d.ts +0 -109
- package/dist/dev-tools/warnings.d.ts.map +0 -1
- package/dist/server/flush.d.ts +0 -74
- package/dist/server/flush.d.ts.map +0 -1
- package/src/client/link-pending-store.ts +0 -116
- package/src/client/nav-link-store.ts +0 -37
- package/src/dev-tools/warnings.ts +0 -238
- package/src/server/flush.ts +0 -183
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { a as isDynamicMetadataExtension, n as
|
|
1
|
+
import { a as classifyMetadataRoute, c as isDynamicMetadataExtension, n as NULL_BYTE_RE, t as ENCODED_SEPARATOR_RE } from "./canonicalize-P41GR6tY.js";
|
|
2
2
|
import { r as DEFAULT_PAGE_EXTENSIONS, t as classifySegment } from "./segment-classify-CDDRVKs7.js";
|
|
3
|
-
import { h as swallow } from "./logger-
|
|
3
|
+
import { h as swallow } from "./logger-CM0clugc.js";
|
|
4
|
+
import { o as resolveAppDir } from "./plugin-context-GfDWsdd2.js";
|
|
4
5
|
import { basename, extname, join } from "node:path";
|
|
5
|
-
import { createRequire } from "node:module";
|
|
6
6
|
import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
7
|
-
import { performance } from "node:perf_hooks";
|
|
8
7
|
//#region src/routing/scanner.ts
|
|
9
8
|
/**
|
|
10
9
|
* Route discovery scanner.
|
|
@@ -16,17 +15,6 @@ import { performance } from "node:perf_hooks";
|
|
|
16
15
|
* is discovery only.
|
|
17
16
|
*/
|
|
18
17
|
/**
|
|
19
|
-
* Pattern matching encoded path delimiters that must be rejected during route discovery.
|
|
20
|
-
* %2F / %2f (forward slash) and %5C / %5c (backslash) can cause route collisions
|
|
21
|
-
* when decoded. See design/13-security.md §"Encoded separators rejected".
|
|
22
|
-
*/
|
|
23
|
-
var ENCODED_SEPARATOR_PATTERN = /%(?:2[fF]|5[cC])/;
|
|
24
|
-
/**
|
|
25
|
-
* Pattern matching encoded null bytes (%00) that must be rejected.
|
|
26
|
-
* See design/13-security.md §"Null bytes rejected".
|
|
27
|
-
*/
|
|
28
|
-
var ENCODED_NULL_PATTERN = /%00/;
|
|
29
|
-
/**
|
|
30
18
|
* File convention names that use pageExtensions (can be .tsx, .ts, .jsx, .js, .mdx, etc.)
|
|
31
19
|
*/
|
|
32
20
|
var PAGE_EXT_CONVENTIONS = new Set([
|
|
@@ -127,48 +115,23 @@ function scanSegmentFiles(dirPath, node, extSet) {
|
|
|
127
115
|
const ext = extname(entry).slice(1);
|
|
128
116
|
const name = basename(entry, `.${ext}`);
|
|
129
117
|
if (PAGE_EXT_CONVENTIONS.has(name) && extSet.has(ext)) {
|
|
130
|
-
const
|
|
118
|
+
const key = name;
|
|
119
|
+
const existing = node[key];
|
|
120
|
+
if (existing) throwDuplicateConvention(name, existing.filePath, fullPath);
|
|
121
|
+
node[key] = {
|
|
131
122
|
filePath: fullPath,
|
|
132
123
|
extension: ext
|
|
133
124
|
};
|
|
134
|
-
switch (name) {
|
|
135
|
-
case "page":
|
|
136
|
-
node.page = file;
|
|
137
|
-
break;
|
|
138
|
-
case "layout":
|
|
139
|
-
node.layout = file;
|
|
140
|
-
break;
|
|
141
|
-
case "error":
|
|
142
|
-
node.error = file;
|
|
143
|
-
break;
|
|
144
|
-
case "default":
|
|
145
|
-
node.default = file;
|
|
146
|
-
break;
|
|
147
|
-
case "denied":
|
|
148
|
-
node.denied = file;
|
|
149
|
-
break;
|
|
150
|
-
}
|
|
151
125
|
continue;
|
|
152
126
|
}
|
|
153
|
-
if (FIXED_CONVENTIONS.has(name) &&
|
|
154
|
-
const
|
|
127
|
+
if (FIXED_CONVENTIONS.has(name) && /^[jt]sx?$/.test(ext)) {
|
|
128
|
+
const key = name;
|
|
129
|
+
const existing = node[key];
|
|
130
|
+
if (existing) throwDuplicateConvention(name, existing.filePath, fullPath);
|
|
131
|
+
node[key] = {
|
|
155
132
|
filePath: fullPath,
|
|
156
133
|
extension: ext
|
|
157
134
|
};
|
|
158
|
-
switch (name) {
|
|
159
|
-
case "middleware":
|
|
160
|
-
node.middleware = file;
|
|
161
|
-
break;
|
|
162
|
-
case "access":
|
|
163
|
-
node.access = file;
|
|
164
|
-
break;
|
|
165
|
-
case "route":
|
|
166
|
-
node.route = file;
|
|
167
|
-
break;
|
|
168
|
-
case "params":
|
|
169
|
-
node.params = file;
|
|
170
|
-
break;
|
|
171
|
-
}
|
|
172
135
|
continue;
|
|
173
136
|
}
|
|
174
137
|
if (STATUS_CODE_PATTERN.test(name) && ext === "json") {
|
|
@@ -181,6 +144,8 @@ function scanSegmentFiles(dirPath, node, extSet) {
|
|
|
181
144
|
}
|
|
182
145
|
if (STATUS_CODE_PATTERN.test(name) && extSet.has(ext)) {
|
|
183
146
|
if (!node.statusFiles) node.statusFiles = {};
|
|
147
|
+
const existing = node.statusFiles[name];
|
|
148
|
+
if (existing) throwDuplicateConvention(name, existing.filePath, fullPath);
|
|
184
149
|
node.statusFiles[name] = {
|
|
185
150
|
filePath: fullPath,
|
|
186
151
|
extension: ext
|
|
@@ -189,6 +154,8 @@ function scanSegmentFiles(dirPath, node, extSet) {
|
|
|
189
154
|
}
|
|
190
155
|
if (name in LEGACY_STATUS_FILES && extSet.has(ext)) {
|
|
191
156
|
if (!node.legacyStatusFiles) node.legacyStatusFiles = {};
|
|
157
|
+
const existing = node.legacyStatusFiles[name];
|
|
158
|
+
if (existing) throwDuplicateConvention(name, existing.filePath, fullPath);
|
|
192
159
|
node.legacyStatusFiles[name] = {
|
|
193
160
|
filePath: fullPath,
|
|
194
161
|
extension: ext
|
|
@@ -200,7 +167,9 @@ function scanSegmentFiles(dirPath, node, extSet) {
|
|
|
200
167
|
const existing = node.metadataRoutes[name];
|
|
201
168
|
if (existing) {
|
|
202
169
|
const existingIsDynamic = isDynamicMetadataExtension(name, existing.extension);
|
|
203
|
-
|
|
170
|
+
const newIsDynamic = isDynamicMetadataExtension(name, ext);
|
|
171
|
+
if (existingIsDynamic === newIsDynamic) throwDuplicateConvention(name, existing.filePath, fullPath);
|
|
172
|
+
if (newIsDynamic) node.metadataRoutes[name] = {
|
|
204
173
|
filePath: fullPath,
|
|
205
174
|
extension: ext
|
|
206
175
|
};
|
|
@@ -213,6 +182,16 @@ function scanSegmentFiles(dirPath, node, extSet) {
|
|
|
213
182
|
if (node.route && node.page) throw new Error(`Build error: route.ts and page.* cannot coexist in the same segment.\n route.ts: ${node.route.filePath}\n page: ${node.page.filePath}\nA URL is either an API endpoint or a rendered page, not both.`);
|
|
214
183
|
}
|
|
215
184
|
/**
|
|
185
|
+
* Throw a build error for two files claiming the same convention slot in
|
|
186
|
+
* one segment (e.g. page.tsx + page.jsx). Which file readdir returns first
|
|
187
|
+
* is platform-dependent (APFS sorts entries, ext4 does not) — silently
|
|
188
|
+
* picking one would let macOS dev and Linux CI serve different
|
|
189
|
+
* implementations of the same route.
|
|
190
|
+
*/
|
|
191
|
+
function throwDuplicateConvention(convention, firstPath, secondPath) {
|
|
192
|
+
throw new Error(`Build error: duplicate '${convention}' files in the same route segment.\n File 1: ${firstPath}\n File 2: ${secondPath}\nEach segment may define '${convention}' at most once. Remove one of the files.`);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
216
195
|
* Recursively scan child directories and build the segment tree.
|
|
217
196
|
*/
|
|
218
197
|
function scanChildren(dirPath, parentNode, extSet) {
|
|
@@ -230,8 +209,8 @@ function scanChildren(dirPath, parentNode, extSet) {
|
|
|
230
209
|
} catch {
|
|
231
210
|
continue;
|
|
232
211
|
}
|
|
233
|
-
if (
|
|
234
|
-
if (
|
|
212
|
+
if (ENCODED_SEPARATOR_RE.test(entry)) throw new Error(`Build error: directory name contains an encoded path delimiter (%2F or %5C).\n Directory: ${fullPath}\nEncoded separators in directory names cause route collisions when decoded. Rename the directory to remove the encoded delimiter.`);
|
|
213
|
+
if (NULL_BYTE_RE.test(entry)) throw new Error(`Build error: directory name contains an encoded null byte (%00).\n Directory: ${fullPath}\nEncoded null bytes in directory names are not allowed. Rename the directory to remove the null byte encoding.`);
|
|
235
214
|
const { type, paramName, interceptionMarker, interceptedSegmentName } = classifySegment(entry);
|
|
236
215
|
if (type === "private") continue;
|
|
237
216
|
const childNode = createSegmentNode(entry, type, computeUrlPath(parentNode.urlPath, entry, type), paramName, interceptionMarker, interceptedSegmentName);
|
|
@@ -249,32 +228,34 @@ function scanChildren(dirPath, parentNode, extSet) {
|
|
|
249
228
|
* Two route groups like (auth)/login/page.tsx and (marketing)/login/page.tsx both claim
|
|
250
229
|
* /login — the scanner must detect and reject this at build time.
|
|
251
230
|
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
231
|
+
* Each parallel slot is validated as its OWN collision space: a slot's pages
|
|
232
|
+
* intentionally share URL paths with the parent tree (that's the point of
|
|
233
|
+
* parallel routes), but two route groups INSIDE one slot claiming the same
|
|
234
|
+
* URL — e.g. @side/(a)/x/page.tsx + @side/(b)/x/page.tsx — still collide.
|
|
254
235
|
*/
|
|
255
236
|
function validateRouteGroupCollisions(root) {
|
|
256
|
-
collectRoutableLeaves(root, /* @__PURE__ */ new Map(), ""
|
|
237
|
+
collectRoutableLeaves(root, /* @__PURE__ */ new Map(), "");
|
|
257
238
|
}
|
|
258
239
|
/**
|
|
259
240
|
* Walk the segment tree and collect all routable leaves (page or route files),
|
|
260
|
-
* throwing on collision.
|
|
261
|
-
*
|
|
241
|
+
* throwing on collision. Each slot recurses with a FRESH collision map —
|
|
242
|
+
* slots are parallel routes that intentionally share URL paths with their
|
|
243
|
+
* parent tree, but collisions within a single slot are real errors.
|
|
262
244
|
*/
|
|
263
|
-
function collectRoutableLeaves(node, seen, segmentPath
|
|
245
|
+
function collectRoutableLeaves(node, seen, segmentPath) {
|
|
246
|
+
if (node.segmentType === "intercepting") return;
|
|
264
247
|
const currentPath = segmentPath ? `${segmentPath}/${node.segmentName}` : node.segmentName || "(root)";
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
});
|
|
274
|
-
}
|
|
248
|
+
const routableFile = node.page ?? node.route;
|
|
249
|
+
if (routableFile) {
|
|
250
|
+
const existing = seen.get(node.urlPath);
|
|
251
|
+
if (existing) throw new Error(`Build error: route collision — multiple route groups produce a page/route at the same URL path.\n URL path: ${node.urlPath}\n File 1: ${existing.filePath} (via ${existing.segmentPath})\n File 2: ${routableFile.filePath} (via ${currentPath})\nEach URL path must map to exactly one page or route handler. Rename or move one of the conflicting files.`);
|
|
252
|
+
seen.set(node.urlPath, {
|
|
253
|
+
filePath: routableFile.filePath,
|
|
254
|
+
segmentPath: currentPath
|
|
255
|
+
});
|
|
275
256
|
}
|
|
276
|
-
for (const child of node.children) collectRoutableLeaves(child, seen, currentPath
|
|
277
|
-
for (const slotNode of Object.values(node.slots)) collectRoutableLeaves(slotNode,
|
|
257
|
+
for (const child of node.children) collectRoutableLeaves(child, seen, currentPath);
|
|
258
|
+
for (const slotNode of Object.values(node.slots)) collectRoutableLeaves(slotNode, /* @__PURE__ */ new Map(), currentPath);
|
|
278
259
|
}
|
|
279
260
|
/**
|
|
280
261
|
* Validate that no route chain contains duplicate dynamic param names.
|
|
@@ -369,225 +350,6 @@ function findPageExtFile(dirPath, name, extSet) {
|
|
|
369
350
|
}
|
|
370
351
|
}
|
|
371
352
|
//#endregion
|
|
372
|
-
//#region src/utils/startup-timer.ts
|
|
373
|
-
/**
|
|
374
|
-
* Startup timer — records named phases with their durations.
|
|
375
|
-
*
|
|
376
|
-
* Used by the plugin system to instrument cold start and report a
|
|
377
|
-
* timing breakdown in dev mode. Zero overhead in production (disabled).
|
|
378
|
-
*
|
|
379
|
-
* See design/18-build-system.md, TIM-155.
|
|
380
|
-
*/
|
|
381
|
-
/**
|
|
382
|
-
* Create a startup timer that records phase durations.
|
|
383
|
-
*/
|
|
384
|
-
function createStartupTimer() {
|
|
385
|
-
const pending = /* @__PURE__ */ new Map();
|
|
386
|
-
const phases = [];
|
|
387
|
-
return {
|
|
388
|
-
start(phase) {
|
|
389
|
-
pending.set(phase, performance.now());
|
|
390
|
-
},
|
|
391
|
-
end(phase) {
|
|
392
|
-
const startMs = pending.get(phase);
|
|
393
|
-
if (startMs === void 0) return 0;
|
|
394
|
-
pending.delete(phase);
|
|
395
|
-
const durationMs = performance.now() - startMs;
|
|
396
|
-
phases.push({
|
|
397
|
-
name: phase,
|
|
398
|
-
startMs,
|
|
399
|
-
durationMs
|
|
400
|
-
});
|
|
401
|
-
return durationMs;
|
|
402
|
-
},
|
|
403
|
-
getPhases() {
|
|
404
|
-
return [...phases].sort((a, b) => a.startMs - b.startMs);
|
|
405
|
-
},
|
|
406
|
-
totalMs() {
|
|
407
|
-
if (phases.length === 0) return 0;
|
|
408
|
-
const sorted = this.getPhases();
|
|
409
|
-
const first = sorted[0];
|
|
410
|
-
const last = sorted[sorted.length - 1];
|
|
411
|
-
return last.startMs + last.durationMs - first.startMs;
|
|
412
|
-
},
|
|
413
|
-
formatSummary() {
|
|
414
|
-
const sorted = this.getPhases();
|
|
415
|
-
if (sorted.length === 0) return "No phases recorded.";
|
|
416
|
-
const lines = sorted.map((p) => {
|
|
417
|
-
const ms = p.durationMs.toFixed(1);
|
|
418
|
-
return ` ${p.name.padEnd(30)} ${ms.padStart(8)}ms`;
|
|
419
|
-
});
|
|
420
|
-
const total = this.totalMs().toFixed(1);
|
|
421
|
-
lines.push(` ${"total".padEnd(30)} ${total.padStart(8)}ms`);
|
|
422
|
-
return ["[timber] startup timing:", ...lines].join("\n");
|
|
423
|
-
}
|
|
424
|
-
};
|
|
425
|
-
}
|
|
426
|
-
/**
|
|
427
|
-
* No-op timer for production builds — all methods are empty.
|
|
428
|
-
*/
|
|
429
|
-
function createNoopTimer() {
|
|
430
|
-
return {
|
|
431
|
-
start() {},
|
|
432
|
-
end() {
|
|
433
|
-
return 0;
|
|
434
|
-
},
|
|
435
|
-
getPhases() {
|
|
436
|
-
return [];
|
|
437
|
-
},
|
|
438
|
-
totalMs() {
|
|
439
|
-
return 0;
|
|
440
|
-
},
|
|
441
|
-
formatSummary() {
|
|
442
|
-
return "";
|
|
443
|
-
}
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
//#endregion
|
|
447
|
-
//#region src/plugin-context.ts
|
|
448
|
-
/**
|
|
449
|
-
* Plugin context — internal types and helpers for timber sub-plugins.
|
|
450
|
-
*
|
|
451
|
-
* These are NOT part of the user-facing API. They are consumed by
|
|
452
|
-
* sub-plugins (routing, entries, shims, etc.) via relative imports.
|
|
453
|
-
* The root entry point (index.ts) does not re-export them.
|
|
454
|
-
*
|
|
455
|
-
* Design doc: 18-build-system.md §"Shared Plugin Context"
|
|
456
|
-
*/
|
|
457
|
-
/**
|
|
458
|
-
* Resolve `clientJavascript` into a fully resolved config.
|
|
459
|
-
*/
|
|
460
|
-
function resolveClientJavascript(config) {
|
|
461
|
-
if (config.clientJavascript !== void 0) {
|
|
462
|
-
if (typeof config.clientJavascript === "boolean") return {
|
|
463
|
-
disabled: !config.clientJavascript,
|
|
464
|
-
enableHMRInDev: !config.clientJavascript
|
|
465
|
-
};
|
|
466
|
-
return {
|
|
467
|
-
disabled: config.clientJavascript.disabled,
|
|
468
|
-
enableHMRInDev: config.clientJavascript.enableHMRInDev ?? config.clientJavascript.disabled
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
return {
|
|
472
|
-
disabled: false,
|
|
473
|
-
enableHMRInDev: false
|
|
474
|
-
};
|
|
475
|
-
}
|
|
476
|
-
/**
|
|
477
|
-
* Resolve the app directory. Checks (in order):
|
|
478
|
-
* 1. Explicit `configAppDir` from timber.config.ts
|
|
479
|
-
* 2. `<root>/app`
|
|
480
|
-
* 3. `<root>/src/app`
|
|
481
|
-
*
|
|
482
|
-
* Throws if none exist.
|
|
483
|
-
*/
|
|
484
|
-
function resolveAppDir(root, configAppDir) {
|
|
485
|
-
if (configAppDir) {
|
|
486
|
-
const explicit = join(root, configAppDir);
|
|
487
|
-
if (!existsSync(explicit)) throw new Error(`[timber] Configured appDir "${configAppDir}" does not exist at ${explicit}`);
|
|
488
|
-
return explicit;
|
|
489
|
-
}
|
|
490
|
-
const rootApp = join(root, "app");
|
|
491
|
-
if (existsSync(rootApp)) return rootApp;
|
|
492
|
-
const srcApp = join(root, "src", "app");
|
|
493
|
-
if (existsSync(srcApp)) return srcApp;
|
|
494
|
-
throw new Error(`[timber] Could not find app directory. Expected "app/" or "src/app/" in ${root}. You can set appDir in timber.config.ts to specify a custom location.`);
|
|
495
|
-
}
|
|
496
|
-
function createPluginContext(config, root) {
|
|
497
|
-
const projectRoot = root ?? process.cwd();
|
|
498
|
-
const resolvedConfig = { ...config };
|
|
499
|
-
return {
|
|
500
|
-
config: resolvedConfig,
|
|
501
|
-
clientJavascript: resolveClientJavascript(resolvedConfig),
|
|
502
|
-
routeTree: null,
|
|
503
|
-
appDir: join(projectRoot, "app"),
|
|
504
|
-
root: projectRoot,
|
|
505
|
-
dev: false,
|
|
506
|
-
buildManifest: null,
|
|
507
|
-
deploymentId: null,
|
|
508
|
-
timer: createStartupTimer(),
|
|
509
|
-
holdingServer: null,
|
|
510
|
-
buildDir: resolveBuildDir(projectRoot, resolvedConfig.buildDir)
|
|
511
|
-
};
|
|
512
|
-
}
|
|
513
|
-
/** Default build output directory (relative to root). */
|
|
514
|
-
var DEFAULT_BUILD_DIR = join(".timber", "dist");
|
|
515
|
-
/**
|
|
516
|
-
* Resolve the build output directory.
|
|
517
|
-
*
|
|
518
|
-
* Priority:
|
|
519
|
-
* 1. Explicit `timberBuildDir` from timber.config.ts
|
|
520
|
-
* 2. Explicit `viteBuildOutDir` from Vite's build.outDir (if not the default 'dist')
|
|
521
|
-
* 3. Default: `.timber/dist`
|
|
522
|
-
*
|
|
523
|
-
* Returns an absolute path.
|
|
524
|
-
*/
|
|
525
|
-
function resolveBuildDir(root, timberBuildDir, viteBuildOutDir) {
|
|
526
|
-
if (timberBuildDir) return join(root, timberBuildDir);
|
|
527
|
-
if (viteBuildOutDir && viteBuildOutDir !== "dist") return join(root, viteBuildOutDir);
|
|
528
|
-
return join(root, DEFAULT_BUILD_DIR);
|
|
529
|
-
}
|
|
530
|
-
/**
|
|
531
|
-
* Load timber.config.ts (or .js, .mjs) from the project root.
|
|
532
|
-
* Returns the config object or null if no config file is found.
|
|
533
|
-
*
|
|
534
|
-
* Uses require() which works for ESM modules on Node 22.12+.
|
|
535
|
-
* This keeps timber() synchronous — no async config loading needed.
|
|
536
|
-
*/
|
|
537
|
-
function loadTimberConfigFile(root) {
|
|
538
|
-
const configNames = [
|
|
539
|
-
"timber.config.ts",
|
|
540
|
-
"timber.config.js",
|
|
541
|
-
"timber.config.mjs"
|
|
542
|
-
];
|
|
543
|
-
const req = createRequire(join(root, "package.json"));
|
|
544
|
-
for (const name of configNames) {
|
|
545
|
-
const configPath = join(root, name);
|
|
546
|
-
if (existsSync(configPath)) {
|
|
547
|
-
const mod = req(configPath);
|
|
548
|
-
return mod.default ?? mod;
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
return null;
|
|
552
|
-
}
|
|
553
|
-
/**
|
|
554
|
-
* Detect config keys set in both inline (vite.config.ts) and file (timber.config.ts)
|
|
555
|
-
* and warn the user. The `output` key is excluded because it defaults to 'server'
|
|
556
|
-
* in createPluginContext and would always appear as an inline key.
|
|
557
|
-
*
|
|
558
|
-
* Returns the list of conflicting key names (for testing).
|
|
559
|
-
*/
|
|
560
|
-
function warnConfigConflicts(inline, fileConfig) {
|
|
561
|
-
const conflicts = [];
|
|
562
|
-
for (const key of Object.keys(fileConfig)) if (key in inline && inline[key] !== void 0) conflicts.push(key);
|
|
563
|
-
if (conflicts.length > 0) console.warn(`[timber] Config conflict: ${conflicts.map((k) => `"${k}"`).join(", ")} set in both vite.config.ts (inline) and timber.config.ts. Move all config to timber.config.ts to avoid confusion. The inline value from vite.config.ts will be used.`);
|
|
564
|
-
return conflicts;
|
|
565
|
-
}
|
|
566
|
-
/**
|
|
567
|
-
* Merge file-based config into ctx.config. Inline config (already in ctx.config)
|
|
568
|
-
* takes precedence — file config only fills in missing fields.
|
|
569
|
-
*/
|
|
570
|
-
function mergeFileConfig(ctx, fileConfig) {
|
|
571
|
-
const inline = ctx.config;
|
|
572
|
-
warnConfigConflicts(inline, fileConfig);
|
|
573
|
-
ctx.config = {
|
|
574
|
-
...fileConfig,
|
|
575
|
-
...inline,
|
|
576
|
-
...fileConfig.limits && inline.limits ? { limits: {
|
|
577
|
-
...fileConfig.limits,
|
|
578
|
-
...inline.limits
|
|
579
|
-
} } : {},
|
|
580
|
-
...fileConfig.dev && inline.dev ? { dev: {
|
|
581
|
-
...fileConfig.dev,
|
|
582
|
-
...inline.dev
|
|
583
|
-
} } : {},
|
|
584
|
-
...fileConfig.mdx && inline.mdx ? { mdx: {
|
|
585
|
-
...fileConfig.mdx,
|
|
586
|
-
...inline.mdx
|
|
587
|
-
} } : {}
|
|
588
|
-
};
|
|
589
|
-
}
|
|
590
|
-
//#endregion
|
|
591
353
|
//#region src/cli-schema-sync.ts
|
|
592
354
|
/**
|
|
593
355
|
* `timber schema sync` — scan filesystem for dynamic segments, append
|
|
@@ -612,14 +374,98 @@ function walkSegments(node, segments) {
|
|
|
612
374
|
for (const slot of Object.values(node.slots)) walkSegments(slot, segments);
|
|
613
375
|
}
|
|
614
376
|
/**
|
|
377
|
+
* Replace comment bodies (and optionally string contents) with spaces,
|
|
378
|
+
* preserving length and newlines so every index maps 1:1 to the original.
|
|
379
|
+
* Lets the structural scanning below ignore `segmentParams` mentions and
|
|
380
|
+
* braces inside comments/strings without a TS parser (fine pre-alpha,
|
|
381
|
+
* see TIM-1066).
|
|
382
|
+
*
|
|
383
|
+
* With `maskStrings`, string contents are blanked too — except literal
|
|
384
|
+
* 'segmentParams' strings, kept so a quoted property key stays findable.
|
|
385
|
+
*/
|
|
386
|
+
function maskSource(content, maskStrings) {
|
|
387
|
+
const out = content.split("");
|
|
388
|
+
const n = content.length;
|
|
389
|
+
let i = 0;
|
|
390
|
+
const blank = (from, to) => {
|
|
391
|
+
for (let j = from; j < to; j++) if (content[j] !== "\n") out[j] = " ";
|
|
392
|
+
};
|
|
393
|
+
while (i < n) {
|
|
394
|
+
const ch = content[i];
|
|
395
|
+
const next = content[i + 1];
|
|
396
|
+
if (ch === "/" && next === "/") {
|
|
397
|
+
const start = i;
|
|
398
|
+
while (i < n && content[i] !== "\n") i++;
|
|
399
|
+
blank(start, i);
|
|
400
|
+
} else if (ch === "/" && next === "*") {
|
|
401
|
+
const start = i;
|
|
402
|
+
i += 2;
|
|
403
|
+
while (i < n && !(content[i] === "*" && content[i + 1] === "/")) i++;
|
|
404
|
+
i = Math.min(i + 2, n);
|
|
405
|
+
blank(start, i);
|
|
406
|
+
} else if (ch === "\"" || ch === "'" || ch === "`") {
|
|
407
|
+
const start = i;
|
|
408
|
+
i++;
|
|
409
|
+
while (i < n && content[i] !== ch) {
|
|
410
|
+
if (content[i] === "\\") i++;
|
|
411
|
+
i++;
|
|
412
|
+
}
|
|
413
|
+
const end = i;
|
|
414
|
+
if (maskStrings && content.slice(start + 1, end) !== "segmentParams") blank(start + 1, end);
|
|
415
|
+
i++;
|
|
416
|
+
} else i++;
|
|
417
|
+
}
|
|
418
|
+
return out.join("");
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Locate the `segmentParams: { ... }` block in a schema file.
|
|
422
|
+
* Returns the indices of the opening and matching closing brace, or null
|
|
423
|
+
* if no segmentParams property is found. Comments and strings are masked
|
|
424
|
+
* first, so a JSDoc example mentioning `segmentParams: { ... }` above the
|
|
425
|
+
* real defineSchema call is not mistaken for the block, and braces inside
|
|
426
|
+
* comments/strings don't skew the depth count. The search is anchored to
|
|
427
|
+
* the `defineSchema(` call when present, so helper objects with their own
|
|
428
|
+
* segmentParams property earlier in the file are skipped.
|
|
429
|
+
*/
|
|
430
|
+
function findSegmentParamsBlock(content) {
|
|
431
|
+
const masked = maskSource(content, true);
|
|
432
|
+
const defineCall = /\bdefineSchema\s*\(/.exec(masked);
|
|
433
|
+
const keyRe = /(['"]?)segmentParams\1\s*:\s*\{/g;
|
|
434
|
+
keyRe.lastIndex = defineCall ? defineCall.index + defineCall[0].length : 0;
|
|
435
|
+
const keyMatch = keyRe.exec(masked);
|
|
436
|
+
if (!keyMatch) return null;
|
|
437
|
+
const openBrace = keyMatch.index + keyMatch[0].length - 1;
|
|
438
|
+
let depth = 1;
|
|
439
|
+
let i = openBrace + 1;
|
|
440
|
+
while (i < masked.length && depth > 0) {
|
|
441
|
+
if (masked[i] === "{") depth++;
|
|
442
|
+
else if (masked[i] === "}") depth--;
|
|
443
|
+
i++;
|
|
444
|
+
}
|
|
445
|
+
if (depth > 0) return null;
|
|
446
|
+
return {
|
|
447
|
+
openBrace,
|
|
448
|
+
closeBrace: i - 1
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
615
452
|
* Parse existing bracket keys from an app/schema.ts file.
|
|
616
|
-
*
|
|
453
|
+
* Scoped to the segmentParams: { ... } block — bracket keys elsewhere in
|
|
454
|
+
* the file (unrelated objects, comments above defineSchema) are ignored.
|
|
455
|
+
* Comments inside the block are masked, so a commented-out entry does not
|
|
456
|
+
* count as registered.
|
|
457
|
+
*
|
|
458
|
+
* The key charset matches the route classifier (segment-classify.ts):
|
|
459
|
+
* any character except `]`, so hyphenated params like '[my-param]' parse.
|
|
617
460
|
*/
|
|
618
461
|
function parseExistingSchemaKeys(content) {
|
|
462
|
+
const block = findSegmentParamsBlock(content);
|
|
463
|
+
if (!block) return [];
|
|
464
|
+
const blockContent = maskSource(content, false).slice(block.openBrace, block.closeBrace);
|
|
619
465
|
const keys = [];
|
|
620
|
-
const re = /['"](\[{1,2}(?:\.{3})
|
|
466
|
+
const re = /['"](\[{1,2}(?:\.{3})?[^\]]+\]{1,2})['"]\s*:/g;
|
|
621
467
|
let m;
|
|
622
|
-
while ((m = re.exec(
|
|
468
|
+
while ((m = re.exec(blockContent)) !== null) keys.push(m[1]);
|
|
623
469
|
return keys;
|
|
624
470
|
}
|
|
625
471
|
/** Schema file extensions to check. */
|
|
@@ -678,18 +524,9 @@ function ensureCodecImport(content) {
|
|
|
678
524
|
}
|
|
679
525
|
function appendToSchema(content, newSegments) {
|
|
680
526
|
content = ensureCodecImport(content);
|
|
681
|
-
const
|
|
682
|
-
if (
|
|
683
|
-
const openBrace
|
|
684
|
-
if (openBrace === -1) throw new Error("[timber] Could not find opening brace for segmentParams.");
|
|
685
|
-
let depth = 1;
|
|
686
|
-
let i = openBrace + 1;
|
|
687
|
-
while (i < content.length && depth > 0) {
|
|
688
|
-
if (content[i] === "{") depth++;
|
|
689
|
-
else if (content[i] === "}") depth--;
|
|
690
|
-
i++;
|
|
691
|
-
}
|
|
692
|
-
const closeBrace = i - 1;
|
|
527
|
+
const block = findSegmentParamsBlock(content);
|
|
528
|
+
if (!block) throw new Error("[timber] Could not find segmentParams in schema file. Expected a defineSchema({ segmentParams: { ... } }) call.");
|
|
529
|
+
const { openBrace, closeBrace } = block;
|
|
693
530
|
const existingLine = content.slice(openBrace, closeBrace).match(/^(\s+)['"]\[/m);
|
|
694
531
|
const indent = existingLine ? existingLine[1] : " ";
|
|
695
532
|
const insertion = newSegments.map((seg) => `${indent}'${seg}': ${defaultCodecForSegment(seg)},`).join("\n");
|
|
@@ -699,20 +536,26 @@ function appendToSchema(content, newSegments) {
|
|
|
699
536
|
}
|
|
700
537
|
/**
|
|
701
538
|
* Remove entries from the segmentParams block whose keys match `staleSegments`.
|
|
702
|
-
* Removes the entire
|
|
539
|
+
* Removes the entire entry including trailing comma/whitespace. Scoped to
|
|
540
|
+
* the segmentParams block — identical keys in unrelated objects elsewhere
|
|
541
|
+
* in the file are left untouched.
|
|
703
542
|
*/
|
|
704
543
|
function removeFromSchema(content, staleSegments) {
|
|
544
|
+
const block = findSegmentParamsBlock(content);
|
|
545
|
+
if (!block) return content;
|
|
546
|
+
let blockContent = content.slice(block.openBrace, block.closeBrace);
|
|
547
|
+
let blockMasked = maskSource(blockContent, false);
|
|
705
548
|
for (const seg of staleSegments) {
|
|
706
549
|
const escaped = seg.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
707
550
|
const keyRe = new RegExp(`^[ \t]*['"]${escaped}['"][ \t]*:`, "gm");
|
|
708
551
|
let m;
|
|
709
|
-
while ((m = keyRe.exec(
|
|
552
|
+
while ((m = keyRe.exec(blockMasked)) !== null) {
|
|
710
553
|
const start = m.index;
|
|
711
554
|
let i = m.index + m[0].length;
|
|
712
555
|
let depth = 0;
|
|
713
556
|
let inString = null;
|
|
714
|
-
while (i <
|
|
715
|
-
const ch =
|
|
557
|
+
while (i < blockMasked.length) {
|
|
558
|
+
const ch = blockMasked[i];
|
|
716
559
|
if (inString) {
|
|
717
560
|
if (ch === "\\") i++;
|
|
718
561
|
else if (ch === inString) inString = null;
|
|
@@ -725,12 +568,13 @@ function removeFromSchema(content, staleSegments) {
|
|
|
725
568
|
} else if (depth === 0 && (ch === "\n" || ch === "}")) break;
|
|
726
569
|
i++;
|
|
727
570
|
}
|
|
728
|
-
if (i <
|
|
729
|
-
|
|
571
|
+
if (i < blockMasked.length && blockMasked[i] === "\n") i++;
|
|
572
|
+
blockContent = blockContent.slice(0, start) + blockContent.slice(i);
|
|
573
|
+
blockMasked = blockMasked.slice(0, start) + blockMasked.slice(i);
|
|
730
574
|
keyRe.lastIndex = start;
|
|
731
575
|
}
|
|
732
576
|
}
|
|
733
|
-
return content;
|
|
577
|
+
return content.slice(0, block.openBrace) + blockContent + content.slice(block.closeBrace);
|
|
734
578
|
}
|
|
735
579
|
/**
|
|
736
580
|
* Run the schema sync: scan filesystem, diff against schema, append missing.
|
|
@@ -780,6 +624,6 @@ function runSchemaSync(projectRoot) {
|
|
|
780
624
|
};
|
|
781
625
|
}
|
|
782
626
|
//#endregion
|
|
783
|
-
export {
|
|
627
|
+
export { parseExistingSchemaKeys as a, findSegmentParamsBlock as i, defaultCodecForSegment as n, runSchemaSync as o, findSchemaFile as r, scanRoutes as s, collectDynamicSegments as t };
|
|
784
628
|
|
|
785
|
-
//# sourceMappingURL=cli-schema-sync-
|
|
629
|
+
//# sourceMappingURL=cli-schema-sync-DJDdyEae.js.map
|