@timber-js/app 0.2.0-alpha.209 → 0.2.0-alpha.210
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-BerlqoXA.js → actions-Rjk4htmA.js} +19 -25
- package/dist/_chunks/{actions-BerlqoXA.js.map → actions-Rjk4htmA.js.map} +1 -1
- package/dist/_chunks/als-registry-DaxkVjt5.js.map +1 -1
- package/dist/_chunks/{cache-api-CR23J_NC.js → cache-api-DGdYfNJn.js} +4 -4
- package/dist/_chunks/{cache-api-CR23J_NC.js.map → cache-api-DGdYfNJn.js.map} +1 -1
- package/dist/_chunks/{chains-CpFg56UB.js → chains-BoO51joc.js} +2 -2
- package/dist/_chunks/{chains-CpFg56UB.js.map → chains-BoO51joc.js.map} +1 -1
- package/dist/_chunks/{cli-check-C6Ev6wBO.js → cli-check-ajNY3B2e.js} +3 -3
- package/dist/_chunks/{cli-check-C6Ev6wBO.js.map → cli-check-ajNY3B2e.js.map} +1 -1
- package/dist/_chunks/{cli-schema-sync-CbT2AUUI.js → cli-schema-sync-D2eI8jEg.js} +2 -2
- package/dist/_chunks/{cli-schema-sync-CbT2AUUI.js.map → cli-schema-sync-D2eI8jEg.js.map} +1 -1
- package/dist/_chunks/{file-cache-Dw6BJPG7.js → codegen-Bps1sLKJ.js} +3 -29
- package/dist/_chunks/codegen-Bps1sLKJ.js.map +1 -0
- package/dist/_chunks/{convention-lint-jKTwKwPe.js → convention-lint-DLmhGsRS.js} +54 -316
- package/dist/_chunks/convention-lint-DLmhGsRS.js.map +1 -0
- package/dist/_chunks/{dev-server-C4WZdB7L.js → dev-server-v97rQH4b.js} +99 -9
- package/dist/_chunks/dev-server-v97rQH4b.js.map +1 -0
- package/dist/_chunks/{error-boundary-tA7kVfs4.js → error-boundary-DsNScGRM.js} +4 -4
- package/dist/_chunks/{error-boundary-tA7kVfs4.js.map → error-boundary-DsNScGRM.js.map} +1 -1
- package/dist/_chunks/{json-lossy-check-ip0Qi0MT.js → json-lossy-check-CVuRs2hG.js} +2 -2
- package/dist/_chunks/{json-lossy-check-ip0Qi0MT.js.map → json-lossy-check-CVuRs2hG.js.map} +1 -1
- package/dist/_chunks/{live-graph-Dv-JJCZw.js → live-graph-9cSnn_h9.js} +3 -3
- package/dist/_chunks/{live-graph-Dv-JJCZw.js.map → live-graph-9cSnn_h9.js.map} +1 -1
- package/dist/_chunks/{logger-DiDt5ppH.js → logger-BP0LN6vP.js} +17 -2
- package/dist/_chunks/{logger-DiDt5ppH.js.map → logger-BP0LN6vP.js.map} +1 -1
- package/dist/_chunks/metadata-routes-DSDjM_hJ.js.map +1 -1
- package/dist/_chunks/navigation-root-BQfo1-kG.js.map +1 -1
- package/dist/_chunks/{poison-scan-CpeT6_OJ.js → poison-scan-vGV7Re0B.js} +2 -2
- package/dist/_chunks/{poison-scan-CpeT6_OJ.js.map → poison-scan-vGV7Re0B.js.map} +1 -1
- package/dist/_chunks/{scanner-Bw0oq1HB.js → scanner-DmqdxzbW.js} +392 -7
- package/dist/_chunks/scanner-DmqdxzbW.js.map +1 -0
- package/dist/_chunks/segment-classify-C539Pa2O.js.map +1 -1
- package/dist/_chunks/{sizeof-UwzwB1uM.js → sizeof-BM1409x2.js} +2 -2
- package/dist/_chunks/{sizeof-UwzwB1uM.js.map → sizeof-BM1409x2.js.map} +1 -1
- package/dist/_chunks/{status-page-marker-gaihi0KZ.js → status-page-marker-BRX9Ib-d.js} +1 -45
- package/dist/_chunks/status-page-marker-BRX9Ib-d.js.map +1 -0
- package/dist/_chunks/{walkers-BXExhzzk.js → walkers-Czu2jXFq.js} +3 -3
- package/dist/_chunks/{walkers-BXExhzzk.js.map → walkers-Czu2jXFq.js.map} +1 -1
- package/dist/adapters/cloudflare-kv-cache.js +1 -1
- package/dist/analyze/crawl-entry.js +2 -2
- package/dist/analyze/graph-command.js +2 -2
- package/dist/cache/index.js +2 -2
- package/dist/cache/stores/memory.js +1 -1
- package/dist/cli.js +3 -3
- 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.js +1 -1
- package/dist/client/history.d.ts +0 -9
- package/dist/client/history.d.ts.map +1 -1
- package/dist/client/index.d.ts +2 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +9 -3
- package/dist/client/index.js.map +1 -1
- package/dist/client/internal.js +25 -38
- package/dist/client/internal.js.map +1 -1
- package/dist/client/link.d.ts +22 -0
- package/dist/client/link.d.ts.map +1 -1
- package/dist/client/navigation-api.d.ts +14 -2
- package/dist/client/navigation-api.d.ts.map +1 -1
- package/dist/client/navigation-root.d.ts +9 -1
- package/dist/client/navigation-root.d.ts.map +1 -1
- package/dist/client/navigation-transition.d.ts +6 -1
- package/dist/client/navigation-transition.d.ts.map +1 -1
- package/dist/client/react-root.d.ts.map +1 -1
- package/dist/client/router-effects.d.ts +10 -3
- package/dist/client/router-effects.d.ts.map +1 -1
- package/dist/client/router-pipeline.d.ts +3 -1
- package/dist/client/router-pipeline.d.ts.map +1 -1
- package/dist/client/router-types.d.ts +65 -9
- package/dist/client/router-types.d.ts.map +1 -1
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/segment-cache.d.ts +0 -15
- package/dist/client/segment-cache.d.ts.map +1 -1
- package/dist/client/use-router.d.ts +13 -6
- package/dist/client/use-router.d.ts.map +1 -1
- package/dist/config-validation.d.ts +19 -2
- package/dist/config-validation.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -7
- package/dist/index.js.map +1 -1
- package/dist/routing/convention-lint.d.ts.map +1 -1
- package/dist/routing/export-detect.d.ts +19 -0
- package/dist/routing/export-detect.d.ts.map +1 -1
- package/dist/routing/index.js +3 -3
- package/dist/routing/manifest-codegen.d.ts.map +1 -1
- package/dist/routing/scanner.d.ts.map +1 -1
- package/dist/routing/types.d.ts +7 -0
- package/dist/routing/types.d.ts.map +1 -1
- package/dist/server/action-handler.d.ts +1 -1
- package/dist/server/action-handler.d.ts.map +1 -1
- package/dist/server/actions.d.ts +9 -4
- package/dist/server/actions.d.ts.map +1 -1
- package/dist/server/csrf.d.ts +38 -19
- package/dist/server/csrf.d.ts.map +1 -1
- package/dist/server/error-boundary-wrapper.d.ts +8 -4
- package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
- package/dist/server/fallback-error.d.ts.map +1 -1
- package/dist/server/form-flash.d.ts +1 -1
- package/dist/server/index.js +2 -2
- package/dist/server/index.js.map +1 -1
- package/dist/server/internal.js +249 -16
- package/dist/server/internal.js.map +1 -1
- package/dist/server/logger.d.ts +16 -3
- package/dist/server/logger.d.ts.map +1 -1
- package/dist/server/metadata-collector.d.ts +2 -0
- package/dist/server/metadata-collector.d.ts.map +1 -1
- package/dist/server/metadata-routes.d.ts +12 -1
- package/dist/server/metadata-routes.d.ts.map +1 -1
- package/dist/server/pipeline-helpers.d.ts +29 -1
- package/dist/server/pipeline-helpers.d.ts.map +1 -1
- package/dist/server/pipeline.d.ts +8 -1
- package/dist/server/pipeline.d.ts.map +1 -1
- package/dist/server/route-element-builder.d.ts.map +1 -1
- package/dist/server/route-matcher.d.ts +8 -0
- package/dist/server/route-matcher.d.ts.map +1 -1
- package/dist/server/rsc-entry/{wrap-action-dispatch.d.ts → action-dispatcher.d.ts} +18 -40
- package/dist/server/rsc-entry/action-dispatcher.d.ts.map +1 -0
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/safe-load.d.ts +5 -12
- package/dist/server/safe-load.d.ts.map +1 -1
- package/docs/api/30-api-server.mdx +1 -1
- package/docs/api/31-api-client.mdx +23 -20
- package/docs/api/34-api-config.mdx +4 -2
- package/package.json +1 -1
- package/src/client/browser-entry/action-dispatch.ts +28 -28
- package/src/client/browser-entry/post-hydration.ts +11 -1
- package/src/client/browser-entry/router-init.ts +10 -4
- package/src/client/history.ts +0 -22
- package/src/client/index.ts +2 -1
- package/src/client/link.tsx +30 -1
- package/src/client/navigation-api.ts +36 -3
- package/src/client/navigation-root.tsx +13 -1
- package/src/client/navigation-transition.ts +17 -7
- package/src/client/react-root.ts +11 -2
- package/src/client/router-effects.ts +11 -4
- package/src/client/router-pipeline.ts +4 -0
- package/src/client/router-types.ts +80 -6
- package/src/client/router.ts +36 -24
- package/src/client/segment-cache.ts +0 -65
- package/src/client/use-router.ts +25 -6
- package/src/config-validation.ts +121 -5
- package/src/index.ts +5 -8
- package/src/routing/convention-lint.ts +75 -0
- package/src/routing/export-detect.ts +88 -0
- package/src/routing/manifest-codegen.ts +6 -0
- package/src/routing/scanner.ts +9 -0
- package/src/routing/types.ts +7 -0
- package/src/server/action-handler.ts +14 -11
- package/src/server/actions.ts +37 -42
- package/src/server/als-registry.ts +1 -1
- package/src/server/csrf.ts +100 -72
- package/src/server/error-boundary-wrapper.ts +11 -12
- package/src/server/fallback-error.ts +13 -21
- package/src/server/form-flash.ts +1 -1
- package/src/server/logger.ts +19 -3
- package/src/server/metadata-collector.ts +4 -1
- package/src/server/metadata-routes.ts +23 -8
- package/src/server/pipeline-helpers.ts +89 -8
- package/src/server/pipeline.ts +27 -2
- package/src/server/route-element-builder.ts +1 -0
- package/src/server/route-matcher.ts +11 -0
- package/src/server/rsc-entry/{wrap-action-dispatch.ts → action-dispatcher.ts} +20 -62
- package/src/server/rsc-entry/index.ts +14 -27
- package/src/server/safe-load.ts +5 -12
- package/dist/_chunks/convention-lint-jKTwKwPe.js.map +0 -1
- package/dist/_chunks/dev-server-C4WZdB7L.js.map +0 -1
- package/dist/_chunks/file-cache-Dw6BJPG7.js.map +0 -1
- package/dist/_chunks/scanner-Bw0oq1HB.js.map +0 -1
- package/dist/_chunks/status-page-marker-gaihi0KZ.js.map +0 -1
- package/dist/server/rsc-entry/wrap-action-dispatch.d.ts.map +0 -1
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { a as isDynamicMetadataExtension, n as classifyMetadataRoute } from "./metadata-routes-DSDjM_hJ.js";
|
|
2
|
-
import { n as NULL_BYTE_RE, t as ENCODED_SEPARATOR_RE } from "./canonicalize-CgHoscYO.js";
|
|
3
|
-
import { t as matchUrlParts } from "./tree-match-6yihorhk.js";
|
|
4
1
|
import { i as DEFAULT_PAGE_EXTENSIONS, n as classifyUrlSegment, r as effectiveUrlSegment, t as classifySegment } from "./segment-classify-C539Pa2O.js";
|
|
5
2
|
import { n as treePathDepth, t as computeSegmentTreePaths } from "./segment-keys-D5hu1hz4.js";
|
|
6
|
-
import {
|
|
3
|
+
import { t as matchUrlParts } from "./tree-match-6yihorhk.js";
|
|
4
|
+
import { a as isDynamicMetadataExtension, n as classifyMetadataRoute } from "./metadata-routes-DSDjM_hJ.js";
|
|
5
|
+
import { n as NULL_BYTE_RE, t as ENCODED_SEPARATOR_RE } from "./canonicalize-CgHoscYO.js";
|
|
6
|
+
import { v as swallow } from "./logger-BP0LN6vP.js";
|
|
7
|
+
import { parseAst } from "vite";
|
|
7
8
|
import { basename, extname, join } from "node:path";
|
|
8
|
-
import { readdirSync, statSync } from "node:fs";
|
|
9
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
9
10
|
//#region src/routing/collision-probe.ts
|
|
10
11
|
/**
|
|
11
12
|
* Find a URL in this collision space whose winner depends on `children` order,
|
|
@@ -1243,6 +1244,389 @@ function throwNestedSlot(slotPath, enclosingSlotPath) {
|
|
|
1243
1244
|
throw new Error(`Build error: parallel slot nested inside another parallel slot.\n Directory: app/${slotPath}\n Inside: app/${enclosingSlotPath}\nA parallel slot is resolved only for a segment on the matched route chain, and a slot's own subtree is never on that chain — the slot resolver walks the slot's matched chain to apply its layouts, error boundaries and access gates, and never looks at slots declared inside it. So app/${slotPath} and everything below it can never render, and no request-time signal can tell you: an unreachable slot produces exactly what a slot that simply did not match produces.\nDeclare the slot on a segment of the main route chain instead, or move its pages into app/${enclosingSlotPath}'s own subtree.\nSee design/07-routing.md §"Parallel Routes".`);
|
|
1244
1245
|
}
|
|
1245
1246
|
//#endregion
|
|
1247
|
+
//#region src/routing/file-cache.ts
|
|
1248
|
+
var cache = /* @__PURE__ */ new Map();
|
|
1249
|
+
/**
|
|
1250
|
+
* Read a file's text content, returning a cached copy when the file
|
|
1251
|
+
* hasn't been modified since the last read (checked via mtime + size).
|
|
1252
|
+
*
|
|
1253
|
+
* Falls back to a fresh read on any stat/read error.
|
|
1254
|
+
*/
|
|
1255
|
+
function readFileCached(filePath) {
|
|
1256
|
+
try {
|
|
1257
|
+
const stat = statSync(filePath);
|
|
1258
|
+
const entry = cache.get(filePath);
|
|
1259
|
+
if (entry && entry.mtimeMs === stat.mtimeMs && entry.size === stat.size) return entry.content;
|
|
1260
|
+
const content = readFileSync(filePath, "utf-8");
|
|
1261
|
+
cache.set(filePath, {
|
|
1262
|
+
mtimeMs: stat.mtimeMs,
|
|
1263
|
+
size: stat.size,
|
|
1264
|
+
content
|
|
1265
|
+
});
|
|
1266
|
+
return content;
|
|
1267
|
+
} catch {
|
|
1268
|
+
cache.delete(filePath);
|
|
1269
|
+
return readFileSync(filePath, "utf-8");
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
//#endregion
|
|
1273
|
+
//#region src/routing/export-detect.ts
|
|
1274
|
+
/**
|
|
1275
|
+
* AST-based export detection for route files.
|
|
1276
|
+
*
|
|
1277
|
+
* Uses Vite's `parseAst` (backed by oxc) to precisely detect named and
|
|
1278
|
+
* default exports, replacing the regex-based approach that was fragile
|
|
1279
|
+
* with TypeScript syntax (type exports, `as` aliases, comments, etc.).
|
|
1280
|
+
*/
|
|
1281
|
+
function tryParse(source) {
|
|
1282
|
+
try {
|
|
1283
|
+
return parseAst(source, { lang: "tsx" });
|
|
1284
|
+
} catch {
|
|
1285
|
+
return null;
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
/**
|
|
1289
|
+
* Collect all named export identifiers from a parsed program.
|
|
1290
|
+
* Handles: `export function X`, `export const X`, `export { X }`,
|
|
1291
|
+
* `export { X } from '...'`, `export { Y as X }`.
|
|
1292
|
+
*/
|
|
1293
|
+
function collectNamedExports(program) {
|
|
1294
|
+
const names = /* @__PURE__ */ new Set();
|
|
1295
|
+
for (const stmt of program.body) {
|
|
1296
|
+
if (stmt.type !== "ExportNamedDeclaration") continue;
|
|
1297
|
+
if (stmt.exportKind === "type") continue;
|
|
1298
|
+
if (stmt.declaration) {
|
|
1299
|
+
if (stmt.declaration.id?.name) names.add(stmt.declaration.id.name);
|
|
1300
|
+
if (stmt.declaration.declarations) {
|
|
1301
|
+
for (const decl of stmt.declaration.declarations) if (decl.id?.name) names.add(decl.id.name);
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
if (stmt.specifiers) for (const spec of stmt.specifiers) {
|
|
1305
|
+
if (spec.exportKind === "type") continue;
|
|
1306
|
+
if (spec.exported?.name && spec.exported.name !== "default") names.add(spec.exported.name);
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
return names;
|
|
1310
|
+
}
|
|
1311
|
+
/**
|
|
1312
|
+
* The specifier exporting a *runtime* default from this statement, if any.
|
|
1313
|
+
*
|
|
1314
|
+
* Type-only exports erase, so they are not default exports at runtime — and
|
|
1315
|
+
* TypeScript spells that two ways, on two different nodes: `export type { X as
|
|
1316
|
+
* default }` marks the **statement**, `export { type X as default }` marks the
|
|
1317
|
+
* **specifier**. Both have to be checked, or a type alias reads as a component:
|
|
1318
|
+
* a status file whose only default is a type has nothing for the error boundary
|
|
1319
|
+
* to receive (the renderer's `mod?.default` skips it), so treating it as one
|
|
1320
|
+
* would fail a build over a file that cannot break anything.
|
|
1321
|
+
*
|
|
1322
|
+
* One implementation because two callers must agree on what "has a default
|
|
1323
|
+
* export" means: `hasDefaultExport` decides whether to warn that a page has
|
|
1324
|
+
* none, and `fileDefaultExportIsLocalValue` decides whether to fail the build.
|
|
1325
|
+
*/
|
|
1326
|
+
function defaultSpecifier(stmt) {
|
|
1327
|
+
if (stmt.type !== "ExportNamedDeclaration" || !stmt.specifiers) return null;
|
|
1328
|
+
if (stmt.exportKind === "type") return null;
|
|
1329
|
+
for (const spec of stmt.specifiers) {
|
|
1330
|
+
if (spec.exportKind === "type") continue;
|
|
1331
|
+
if (spec.exported?.name === "default") return spec;
|
|
1332
|
+
}
|
|
1333
|
+
return null;
|
|
1334
|
+
}
|
|
1335
|
+
/**
|
|
1336
|
+
* Check whether a program has a default export.
|
|
1337
|
+
* Handles: `export default ...`, `export { X as default }`,
|
|
1338
|
+
* `export { default } from '...'`. Type-only defaults do not count —
|
|
1339
|
+
* see `defaultSpecifier`.
|
|
1340
|
+
*/
|
|
1341
|
+
function hasDefaultExport(program) {
|
|
1342
|
+
for (const stmt of program.body) {
|
|
1343
|
+
if (stmt.type === "ExportDefaultDeclaration") return true;
|
|
1344
|
+
if (defaultSpecifier(stmt)) return true;
|
|
1345
|
+
}
|
|
1346
|
+
return false;
|
|
1347
|
+
}
|
|
1348
|
+
/**
|
|
1349
|
+
* Check whether a program has any bare `export * from '...'` declarations.
|
|
1350
|
+
* Excludes `export * as X from '...'` (namespace re-exports) since those
|
|
1351
|
+
* create a namespace object, not individual top-level exports.
|
|
1352
|
+
*/
|
|
1353
|
+
function hasStarExport(program) {
|
|
1354
|
+
return program.body.some((stmt) => stmt.type === "ExportAllDeclaration" && !stmt.exported && stmt.exportKind !== "type");
|
|
1355
|
+
}
|
|
1356
|
+
/**
|
|
1357
|
+
* Check if a file has any of the given named exports.
|
|
1358
|
+
* Returns the set of matching export names.
|
|
1359
|
+
*/
|
|
1360
|
+
function fileHasAnyExport(filePath, exportNames) {
|
|
1361
|
+
const matches = /* @__PURE__ */ new Set();
|
|
1362
|
+
try {
|
|
1363
|
+
const program = tryParse(readFileCached(filePath));
|
|
1364
|
+
if (!program) return matches;
|
|
1365
|
+
const exports = collectNamedExports(program);
|
|
1366
|
+
for (const name of exportNames) if (exports.has(name)) matches.add(name);
|
|
1367
|
+
} catch {}
|
|
1368
|
+
return matches;
|
|
1369
|
+
}
|
|
1370
|
+
/**
|
|
1371
|
+
* Check if a file has a default export.
|
|
1372
|
+
* Returns false if the file doesn't exist or can't be parsed.
|
|
1373
|
+
*/
|
|
1374
|
+
function fileHasDefaultExport(filePath) {
|
|
1375
|
+
if (!existsSync(filePath)) return false;
|
|
1376
|
+
try {
|
|
1377
|
+
const program = tryParse(readFileCached(filePath));
|
|
1378
|
+
if (!program) return false;
|
|
1379
|
+
return hasDefaultExport(program);
|
|
1380
|
+
} catch {
|
|
1381
|
+
return false;
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
/** Node types whose value is unambiguously constructed by this module. */
|
|
1385
|
+
var FUNCTION_LIKE = /* @__PURE__ */ new Set([
|
|
1386
|
+
"FunctionDeclaration",
|
|
1387
|
+
"FunctionExpression",
|
|
1388
|
+
"ArrowFunctionExpression",
|
|
1389
|
+
"ClassDeclaration",
|
|
1390
|
+
"ClassExpression"
|
|
1391
|
+
]);
|
|
1392
|
+
/**
|
|
1393
|
+
* Expression wrappers that erase at runtime and so cannot change where a value
|
|
1394
|
+
* was constructed: `X as T`, `X satisfies T`, `<T>X`, `X!`, `X<T>`, `(X)`.
|
|
1395
|
+
*/
|
|
1396
|
+
var TRANSPARENT_WRAPPERS = /* @__PURE__ */ new Set([
|
|
1397
|
+
"TSAsExpression",
|
|
1398
|
+
"TSSatisfiesExpression",
|
|
1399
|
+
"TSTypeAssertion",
|
|
1400
|
+
"TSNonNullExpression",
|
|
1401
|
+
"TSInstantiationExpression",
|
|
1402
|
+
"ParenthesizedExpression"
|
|
1403
|
+
]);
|
|
1404
|
+
/**
|
|
1405
|
+
* Strip runtime-transparent wrappers so the node underneath can be classified.
|
|
1406
|
+
*
|
|
1407
|
+
* Without this, `(() => <h1 />) satisfies React.FC` reads as a
|
|
1408
|
+
* `TSSatisfiesExpression` rather than the arrow function it compiles to, and a
|
|
1409
|
+
* provably-local component is misread as one whose origin cannot be
|
|
1410
|
+
* determined — turning a build error into a warning.
|
|
1411
|
+
*/
|
|
1412
|
+
function unwrapTransparent(node) {
|
|
1413
|
+
let current = node;
|
|
1414
|
+
while (TRANSPARENT_WRAPPERS.has(current.type) && current.expression) current = current.expression;
|
|
1415
|
+
return current;
|
|
1416
|
+
}
|
|
1417
|
+
/**
|
|
1418
|
+
* Whether `name` is bound at module top level to a function or class written
|
|
1419
|
+
* in this file. An imported binding can never match: imports are
|
|
1420
|
+
* `ImportDeclaration` specifiers, not function/class/variable declarations,
|
|
1421
|
+
* and a module-level name cannot be both.
|
|
1422
|
+
*/
|
|
1423
|
+
function bindsLocalFunction(program, name) {
|
|
1424
|
+
for (const stmt of program.body) {
|
|
1425
|
+
const decl = stmt.type === "ExportNamedDeclaration" ? stmt.declaration : stmt;
|
|
1426
|
+
if (!decl) continue;
|
|
1427
|
+
if ((decl.type === "FunctionDeclaration" || decl.type === "ClassDeclaration") && decl.id?.name === name) return true;
|
|
1428
|
+
if (decl.type === "VariableDeclaration") {
|
|
1429
|
+
for (const d of decl.declarations ?? []) if (d.id?.name === name && d.init && FUNCTION_LIKE.has(unwrapTransparent(d.init).type)) return true;
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
return false;
|
|
1433
|
+
}
|
|
1434
|
+
/**
|
|
1435
|
+
* Whether the default export is *provably* a value constructed by this module:
|
|
1436
|
+
* `export default function/class/() => {}`, or a name bound to a function or
|
|
1437
|
+
* class declared here — whether it leaves via `export default Name` or
|
|
1438
|
+
* `export { Name as default }`. Both spellings are the same fact about where
|
|
1439
|
+
* the value was built, so both must answer the same way.
|
|
1440
|
+
*
|
|
1441
|
+
* This is the sound half of "is this a server component?". A file with no
|
|
1442
|
+
* `'use client'` directive whose default export is built here is a server
|
|
1443
|
+
* component, full stop. Everything else — `export { default } from
|
|
1444
|
+
* './client-thing'`, `export default Imported`, `export default memo(X)`,
|
|
1445
|
+
* an unparseable file — returns false, because the exported value may have
|
|
1446
|
+
* been minted in a module that *does* carry the directive. Callers must not
|
|
1447
|
+
* infer "server component" from a missing directive alone.
|
|
1448
|
+
*/
|
|
1449
|
+
function fileDefaultExportIsLocalValue(filePath) {
|
|
1450
|
+
if (!existsSync(filePath)) return false;
|
|
1451
|
+
try {
|
|
1452
|
+
const program = tryParse(readFileCached(filePath));
|
|
1453
|
+
if (!program) return false;
|
|
1454
|
+
for (const stmt of program.body) {
|
|
1455
|
+
if (stmt.type === "ExportDefaultDeclaration") {
|
|
1456
|
+
const decl = stmt.declaration && unwrapTransparent(stmt.declaration);
|
|
1457
|
+
if (!decl) return false;
|
|
1458
|
+
if (FUNCTION_LIKE.has(decl.type)) return true;
|
|
1459
|
+
if (decl.type === "Identifier" && decl.name) return bindsLocalFunction(program, decl.name);
|
|
1460
|
+
return false;
|
|
1461
|
+
}
|
|
1462
|
+
const spec = defaultSpecifier(stmt);
|
|
1463
|
+
if (spec) {
|
|
1464
|
+
if (stmt.source) return false;
|
|
1465
|
+
return spec.local?.name ? bindsLocalFunction(program, spec.local.name) : false;
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
return false;
|
|
1469
|
+
} catch {
|
|
1470
|
+
return false;
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
/**
|
|
1474
|
+
* Check if a file has any `export * from '...'` declarations.
|
|
1475
|
+
* Returns false if the file doesn't exist or can't be parsed.
|
|
1476
|
+
*/
|
|
1477
|
+
function fileHasStarExport(filePath) {
|
|
1478
|
+
if (!existsSync(filePath)) return false;
|
|
1479
|
+
try {
|
|
1480
|
+
const program = tryParse(readFileCached(filePath));
|
|
1481
|
+
if (!program) return false;
|
|
1482
|
+
return hasStarExport(program);
|
|
1483
|
+
} catch {
|
|
1484
|
+
return false;
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
function extractLiteralValue(node) {
|
|
1488
|
+
if (!node) return void 0;
|
|
1489
|
+
const n = node;
|
|
1490
|
+
if (n.type === "Literal") return n.value;
|
|
1491
|
+
if (n.type === "UnaryExpression" && n.operator === "-" && n.argument?.type === "Literal") return -n.argument.value;
|
|
1492
|
+
if (n.type === "ArrayExpression") {
|
|
1493
|
+
const elements = n.elements;
|
|
1494
|
+
if (!elements) return void 0;
|
|
1495
|
+
const result = [];
|
|
1496
|
+
for (const el of elements) {
|
|
1497
|
+
const v = extractLiteralValue(el);
|
|
1498
|
+
if (v === void 0) return void 0;
|
|
1499
|
+
result.push(v);
|
|
1500
|
+
}
|
|
1501
|
+
return result;
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
function extractObjectLiteral(node) {
|
|
1505
|
+
if (node.type !== "ObjectExpression") return void 0;
|
|
1506
|
+
const props = node.properties;
|
|
1507
|
+
if (!props) return void 0;
|
|
1508
|
+
const result = {};
|
|
1509
|
+
for (const prop of props) {
|
|
1510
|
+
const p = prop;
|
|
1511
|
+
if (p.type === "SpreadElement") return void 0;
|
|
1512
|
+
if (p.type !== "Property" || p.computed) return void 0;
|
|
1513
|
+
const key = p.key?.type === "Identifier" ? p.key.name : p.key?.type === "Literal" ? String(p.key.value) : void 0;
|
|
1514
|
+
if (!key) return void 0;
|
|
1515
|
+
const value = extractLiteralValue(p.value);
|
|
1516
|
+
if (value === void 0) return void 0;
|
|
1517
|
+
result[key] = value;
|
|
1518
|
+
}
|
|
1519
|
+
return result;
|
|
1520
|
+
}
|
|
1521
|
+
/**
|
|
1522
|
+
* Extract the value of `export const prerender` from a file.
|
|
1523
|
+
* Returns undefined if the file doesn't export `prerender` or can't be parsed.
|
|
1524
|
+
*
|
|
1525
|
+
* Supports:
|
|
1526
|
+
* export const prerender = true;
|
|
1527
|
+
* export const prerender = false;
|
|
1528
|
+
* export const prerender = { ttl: 3600, tags: ['docs'] };
|
|
1529
|
+
*/
|
|
1530
|
+
function getPrerenderExport(filePath) {
|
|
1531
|
+
if (!existsSync(filePath)) return void 0;
|
|
1532
|
+
try {
|
|
1533
|
+
const program = tryParse(readFileCached(filePath));
|
|
1534
|
+
if (!program) return void 0;
|
|
1535
|
+
for (const stmt of program.body) {
|
|
1536
|
+
if (stmt.type !== "ExportNamedDeclaration") continue;
|
|
1537
|
+
if (stmt.exportKind === "type") continue;
|
|
1538
|
+
if (!stmt.declaration) continue;
|
|
1539
|
+
const decl = stmt.declaration;
|
|
1540
|
+
if (decl.type !== "VariableDeclaration") continue;
|
|
1541
|
+
const declarations = decl.declarations;
|
|
1542
|
+
if (!declarations) continue;
|
|
1543
|
+
for (const d of declarations) {
|
|
1544
|
+
if (d.id?.name !== "prerender" || !d.init) continue;
|
|
1545
|
+
const init = d.init;
|
|
1546
|
+
if (init.type === "Literal" && typeof init.value === "boolean") return init.value;
|
|
1547
|
+
if (init.type === "ObjectExpression") {
|
|
1548
|
+
const obj = extractObjectLiteral(init);
|
|
1549
|
+
if (!obj) return void 0;
|
|
1550
|
+
const result = {};
|
|
1551
|
+
if ("ttl" in obj && typeof obj.ttl === "number") result.ttl = obj.ttl;
|
|
1552
|
+
if ("tags" in obj && Array.isArray(obj.tags) && obj.tags.every((t) => typeof t === "string")) result.tags = obj.tags;
|
|
1553
|
+
return result;
|
|
1554
|
+
}
|
|
1555
|
+
return;
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
return;
|
|
1559
|
+
} catch {
|
|
1560
|
+
return;
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
function getCsrfExport(filePath) {
|
|
1564
|
+
if (!existsSync(filePath)) return "absent";
|
|
1565
|
+
const program = tryParse(readFileCached(filePath));
|
|
1566
|
+
if (!program) return "absent";
|
|
1567
|
+
let found = "absent";
|
|
1568
|
+
for (const stmt of program.body) {
|
|
1569
|
+
if (stmt.type !== "ExportNamedDeclaration" || stmt.exportKind === "type") continue;
|
|
1570
|
+
for (const spec of stmt.specifiers ?? []) if (spec.exportKind !== "type" && specifierName(spec.exported) === "csrf") return "invalid";
|
|
1571
|
+
const decl = stmt.declaration;
|
|
1572
|
+
if (!decl) continue;
|
|
1573
|
+
if (decl.id?.name === "csrf") return "invalid";
|
|
1574
|
+
if (decl.type !== "VariableDeclaration") continue;
|
|
1575
|
+
for (const d of decl.declarations ?? []) {
|
|
1576
|
+
const id = d.id;
|
|
1577
|
+
if (id?.type === "Identifier") {
|
|
1578
|
+
if (id.name !== "csrf") continue;
|
|
1579
|
+
const init = d.init && unwrapTransparent(d.init);
|
|
1580
|
+
const literalFalse = init?.type === "Literal" && init.value === false;
|
|
1581
|
+
if (decl.kind !== "const" || !literalFalse) return "invalid";
|
|
1582
|
+
found = "exempt";
|
|
1583
|
+
} else if (id && patternBinds(id, "csrf")) return "invalid";
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
return found;
|
|
1587
|
+
}
|
|
1588
|
+
/** An export specifier's name: `csrf` or the string literal `"csrf"`. */
|
|
1589
|
+
function specifierName(node) {
|
|
1590
|
+
if (!node) return void 0;
|
|
1591
|
+
return typeof node.value === "string" ? node.value : node.name;
|
|
1592
|
+
}
|
|
1593
|
+
/** Whether a destructuring pattern binds `name` anywhere inside it. */
|
|
1594
|
+
function patternBinds(node, name) {
|
|
1595
|
+
if (!node || typeof node !== "object") return false;
|
|
1596
|
+
const n = node;
|
|
1597
|
+
switch (n.type) {
|
|
1598
|
+
case "Identifier": return n.name === name;
|
|
1599
|
+
case "ObjectPattern": return (n.properties ?? []).some((prop) => {
|
|
1600
|
+
const p = prop;
|
|
1601
|
+
return patternBinds(p.type === "RestElement" ? p.argument : p.value, name);
|
|
1602
|
+
});
|
|
1603
|
+
case "ArrayPattern": return (n.elements ?? []).some((el) => patternBinds(el, name));
|
|
1604
|
+
case "RestElement": return patternBinds(n.argument, name);
|
|
1605
|
+
case "AssignmentPattern": return patternBinds(n.left, name);
|
|
1606
|
+
default: return false;
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
/**
|
|
1610
|
+
* Check if a file starts with a specific directive (e.g. "use client").
|
|
1611
|
+
* Directives are string literal expression statements at the top of the file.
|
|
1612
|
+
*/
|
|
1613
|
+
function fileHasDirective(filePath, directive) {
|
|
1614
|
+
if (!existsSync(filePath)) return false;
|
|
1615
|
+
try {
|
|
1616
|
+
const program = tryParse(readFileCached(filePath));
|
|
1617
|
+
if (!program) return false;
|
|
1618
|
+
for (const stmt of program.body) {
|
|
1619
|
+
if (stmt.type !== "ExpressionStatement") break;
|
|
1620
|
+
const expr = stmt.expression;
|
|
1621
|
+
if (expr?.type !== "Literal" || typeof expr.value !== "string") break;
|
|
1622
|
+
if (expr.value === directive) return true;
|
|
1623
|
+
}
|
|
1624
|
+
return false;
|
|
1625
|
+
} catch {
|
|
1626
|
+
return false;
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
//#endregion
|
|
1246
1630
|
//#region src/routing/scanner.ts
|
|
1247
1631
|
/**
|
|
1248
1632
|
* Route discovery scanner.
|
|
@@ -1435,6 +1819,7 @@ function scanSegmentFiles(dirPath, node, extSet) {
|
|
|
1435
1819
|
}
|
|
1436
1820
|
}
|
|
1437
1821
|
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.`);
|
|
1822
|
+
if (node.route && getCsrfExport(node.route.filePath) === "exempt") node.csrfExempt = true;
|
|
1438
1823
|
}
|
|
1439
1824
|
/**
|
|
1440
1825
|
* Throw a build error for two files claiming the same convention slot in
|
|
@@ -1666,6 +2051,6 @@ function findScriptExtFile(dirPath, name) {
|
|
|
1666
2051
|
}
|
|
1667
2052
|
}
|
|
1668
2053
|
//#endregion
|
|
1669
|
-
export { scanRoutes as n,
|
|
2054
|
+
export { fileHasDefaultExport as a, getCsrfExport as c, collectInterceptionRewrites as d, fileHasAnyExport as i, getPrerenderExport as l, scanRoutes as n, fileHasDirective as o, fileDefaultExportIsLocalValue as r, fileHasStarExport as s, isRouteConventionFile as t, readFileCached as u };
|
|
1670
2055
|
|
|
1671
|
-
//# sourceMappingURL=scanner-
|
|
2056
|
+
//# sourceMappingURL=scanner-DmqdxzbW.js.map
|