@timber-js/app 0.2.0-alpha.138 → 0.2.0-alpha.139

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.
Files changed (108) hide show
  1. package/dist/_chunks/{actions-CHFblsdT.js → actions-DzFMnUAR.js} +7 -7
  2. package/dist/_chunks/{actions-CHFblsdT.js.map → actions-DzFMnUAR.js.map} +1 -1
  3. package/dist/_chunks/{build-output-helper-CfBfOU9m.js → build-output-helper-B20ZuTSA.js} +1 -1
  4. package/dist/_chunks/{build-output-helper-CfBfOU9m.js.map → build-output-helper-B20ZuTSA.js.map} +1 -1
  5. package/dist/_chunks/{cli-schema-sync-q-Ce6-iG.js → cli-schema-sync-D2IpXicg.js} +5 -5
  6. package/dist/_chunks/cli-schema-sync-D2IpXicg.js.map +1 -0
  7. package/dist/_chunks/{cli-schema-sync-CObqjGWF.js → cli-schema-sync-DH-M8Poy.js} +1 -1
  8. package/dist/_chunks/{codec-registry-G9hD5LOq.js → codec-registry-oOUxugz3.js} +1 -1
  9. package/dist/_chunks/{codec-registry-G9hD5LOq.js.map → codec-registry-oOUxugz3.js.map} +1 -1
  10. package/dist/_chunks/{define-DXWUJb-K.js → define-C3RvHQPK.js} +3 -3
  11. package/dist/_chunks/{define-DXWUJb-K.js.map → define-C3RvHQPK.js.map} +1 -1
  12. package/dist/_chunks/{define-Dp-qu3ql.js → define-DMVhsipD.js} +3 -3
  13. package/dist/_chunks/{define-Dp-qu3ql.js.map → define-DMVhsipD.js.map} +1 -1
  14. package/dist/_chunks/{fast-hash-DXZ7S2D5.js → fast-hash-D6hIVt1Y.js} +1 -1
  15. package/dist/_chunks/{fast-hash-DXZ7S2D5.js.map → fast-hash-D6hIVt1Y.js.map} +1 -1
  16. package/dist/_chunks/{format-CR6v1pWE.js → format-CfwjgPz9.js} +1 -1
  17. package/dist/_chunks/{format-CR6v1pWE.js.map → format-CfwjgPz9.js.map} +1 -1
  18. package/dist/_chunks/{handler-store-aQMpqPe7.js → handler-store-DjISKYEB.js} +1 -1
  19. package/dist/_chunks/{handler-store-aQMpqPe7.js.map → handler-store-DjISKYEB.js.map} +1 -1
  20. package/dist/_chunks/{href-validation-2G-HZaQ8.js → href-validation-EZktELp8.js} +1 -1
  21. package/dist/_chunks/{href-validation-2G-HZaQ8.js.map → href-validation-EZktELp8.js.map} +1 -1
  22. package/dist/_chunks/{logger-CMd9P1xQ.js → logger-Dt-jQaBO.js} +2 -2
  23. package/dist/_chunks/{logger-CMd9P1xQ.js.map → logger-Dt-jQaBO.js.map} +1 -1
  24. package/dist/_chunks/{metadata-routes-DESG8uk5.js → metadata-routes-BjepjefT.js} +1 -1
  25. package/dist/_chunks/{metadata-routes-DESG8uk5.js.map → metadata-routes-BjepjefT.js.map} +1 -1
  26. package/dist/_chunks/{navigation-root-BQqFQI-n.js → navigation-root-DLc4s4ZC.js} +3 -3
  27. package/dist/_chunks/{navigation-root-BQqFQI-n.js.map → navigation-root-DLc4s4ZC.js.map} +1 -1
  28. package/dist/_chunks/{registry-Dp_hJL4s.js → registry-DbJPKoBp.js} +1 -1
  29. package/dist/_chunks/{registry-Dp_hJL4s.js.map → registry-DbJPKoBp.js.map} +1 -1
  30. package/dist/_chunks/{resolve-schema-D3CbcdCW.js → resolve-schema-Dj8tQloi.js} +2 -2
  31. package/dist/_chunks/{resolve-schema-D3CbcdCW.js.map → resolve-schema-Dj8tQloi.js.map} +1 -1
  32. package/dist/_chunks/{router-ref-B50j42zB.js → router-ref-CtmF-aPv.js} +2 -2
  33. package/dist/_chunks/{router-ref-B50j42zB.js.map → router-ref-CtmF-aPv.js.map} +1 -1
  34. package/dist/_chunks/{schema-bridge-Cuy8BaYu.js → schema-bridge-DK_89h8O.js} +1 -1
  35. package/dist/_chunks/{schema-bridge-Cuy8BaYu.js.map → schema-bridge-DK_89h8O.js.map} +1 -1
  36. package/dist/_chunks/{segment-classify-BjfuctV2.js → segment-classify-CDDRVKs7.js} +1 -1
  37. package/dist/_chunks/{segment-classify-BjfuctV2.js.map → segment-classify-CDDRVKs7.js.map} +1 -1
  38. package/dist/_chunks/{segment-context-BnxoPSFQ.js → segment-context-ZDnXDkbz.js} +1 -1
  39. package/dist/_chunks/{segment-context-BnxoPSFQ.js.map → segment-context-ZDnXDkbz.js.map} +1 -1
  40. package/dist/_chunks/{ssr-data-DxXyoRrz.js → ssr-data-BOWsq18U.js} +1 -1
  41. package/dist/_chunks/{ssr-data-DxXyoRrz.js.map → ssr-data-BOWsq18U.js.map} +1 -1
  42. package/dist/_chunks/{tracing-DP5djRq5.js → tracing-M_zKubhk.js} +1 -1
  43. package/dist/_chunks/{tracing-DP5djRq5.js.map → tracing-M_zKubhk.js.map} +1 -1
  44. package/dist/_chunks/{use-query-states-D9SBlAfp.js → use-query-states-CbeQmext.js} +2 -2
  45. package/dist/_chunks/{use-query-states-D9SBlAfp.js.map → use-query-states-CbeQmext.js.map} +1 -1
  46. package/dist/_chunks/{use-segment-params-x2poKmk8.js → use-segment-params-D1CzlgBo.js} +2 -2
  47. package/dist/_chunks/{use-segment-params-x2poKmk8.js.map → use-segment-params-D1CzlgBo.js.map} +1 -1
  48. package/dist/_chunks/{validation-BoWM_qI1.js → validation-taUkvA4Q.js} +1 -1
  49. package/dist/_chunks/{validation-BoWM_qI1.js.map → validation-taUkvA4Q.js.map} +1 -1
  50. package/dist/_chunks/{walkers-BbEu2_Pp.js → walkers-7FBPM-hX.js} +3 -3
  51. package/dist/_chunks/{walkers-BbEu2_Pp.js.map → walkers-7FBPM-hX.js.map} +1 -1
  52. package/dist/_chunks/{warnings-CAzunaGo.js → warnings-CNPXaypM.js} +2 -2
  53. package/dist/_chunks/{warnings-CAzunaGo.js.map → warnings-CNPXaypM.js.map} +1 -1
  54. package/dist/adapters/cloudflare-dev.js.map +1 -1
  55. package/dist/adapters/cloudflare-kv-cache.js.map +1 -1
  56. package/dist/adapters/cloudflare.js +1 -1
  57. package/dist/adapters/cloudflare.js.map +1 -1
  58. package/dist/adapters/nitro.js +1 -1
  59. package/dist/adapters/nitro.js.map +1 -1
  60. package/dist/cache/index.js +4 -4
  61. package/dist/cache/index.js.map +1 -1
  62. package/dist/cli.js +1 -1
  63. package/dist/cli.js.map +1 -1
  64. package/dist/client/error-boundary.js +2 -2
  65. package/dist/client/error-boundary.js.map +1 -1
  66. package/dist/client/index.js +9 -9
  67. package/dist/client/index.js.map +1 -1
  68. package/dist/client/internal.js +88 -8
  69. package/dist/client/internal.js.map +1 -1
  70. package/dist/codec.js +1 -1
  71. package/dist/codec.js.map +1 -1
  72. package/dist/cookies/index.js +3 -3
  73. package/dist/cookies/index.js.map +1 -1
  74. package/dist/index.js +110 -13
  75. package/dist/index.js.map +1 -1
  76. package/dist/params/index.js +2 -2
  77. package/dist/params/index.js.map +1 -1
  78. package/dist/plugin-context.d.ts +7 -0
  79. package/dist/plugin-context.d.ts.map +1 -1
  80. package/dist/plugins/adapter-build.d.ts +19 -0
  81. package/dist/plugins/adapter-build.d.ts.map +1 -1
  82. package/dist/plugins/build-manifest.d.ts +5 -1
  83. package/dist/plugins/build-manifest.d.ts.map +1 -1
  84. package/dist/routing/index.js +3 -3
  85. package/dist/search-params/index.js +3 -3
  86. package/dist/search-params/index.js.map +1 -1
  87. package/dist/segment-params/index.js +1 -1
  88. package/dist/server/early-hints.d.ts +0 -6
  89. package/dist/server/early-hints.d.ts.map +1 -1
  90. package/dist/server/index.js +3 -3
  91. package/dist/server/index.js.map +1 -1
  92. package/dist/server/internal.js +8 -19
  93. package/dist/server/internal.js.map +1 -1
  94. package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
  95. package/dist/shims/font-google.js.map +1 -1
  96. package/dist/shims/font-local.js.map +1 -1
  97. package/dist/shims/image.d.ts +1 -1
  98. package/package.json +9 -8
  99. package/src/cli.ts +0 -0
  100. package/src/plugin-context.ts +7 -0
  101. package/src/plugins/adapter-build.ts +86 -0
  102. package/src/plugins/build-manifest.ts +99 -17
  103. package/src/server/early-hints.ts +0 -13
  104. package/src/server/rsc-entry/render-route.ts +12 -12
  105. package/LICENSE +0 -8
  106. package/dist/_chunks/cli-schema-sync-q-Ce6-iG.js.map +0 -1
  107. package/dist/_chunks/stale-reload-DIIIVGh_.js +0 -84
  108. package/dist/_chunks/stale-reload-DIIIVGh_.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"render-route.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/render-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAU1D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAkB/D;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,eAAe,EAAE,qBAAqB,CAAC;IACvC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,WAAW,EAAE,mBAAmB,CAAC;IACjC,aAAa,EAAE,aAAa,CAAC;IAC7B,WAAW,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CAClE;AAED;;;;;;;;;GASG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,UAAU,EACjB,eAAe,EAAE,OAAO,EACxB,IAAI,EAAE,eAAe,EACrB,YAAY,CAAC,EAAE,mBAAmB,GACjC,OAAO,CAAC,QAAQ,CAAC,CAwOnB"}
1
+ {"version":3,"file":"render-route.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/render-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAY1D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAkB/D;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,eAAe,EAAE,qBAAqB,CAAC;IACvC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,WAAW,EAAE,mBAAmB,CAAC;IACjC,aAAa,EAAE,aAAa,CAAC;IAC7B,WAAW,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CAClE;AAED;;;;;;;;;GASG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,UAAU,EACjB,eAAe,EAAE,OAAO,EACxB,IAAI,EAAE,eAAe,EACrB,YAAY,CAAC,EAAE,mBAAmB,GACjC,OAAO,CAAC,QAAQ,CAAC,CAsOnB"}
@@ -1 +1 @@
1
- {"version":3,"file":"font-google.js","names":[],"sources":["../../src/shims/font-google.ts"],"sourcesContent":["/**\n * Fallback shim for `next/font/google` → `@timber/fonts/google`.\n *\n * At build time, the timber-fonts plugin's transform hook replaces font\n * function calls with static FontResult objects containing real class names\n * and font stacks. This file serves two purposes:\n *\n * 1. **TypeScript resolution** — provides types for IDEs and `tsc` outside\n * of Vite's module graph (where the virtual module handles it).\n * 2. **Runtime fallback** — returns empty className/fontFamily values when\n * the plugin hasn't processed a call (e.g. in tests or non-Vite environments).\n *\n * The shim resolution path (`next/font/google` → `\\0@timber/fonts/google`)\n * is unchanged — this file is NOT on that resolution path. It exists as a\n * physical module for direct imports and type re-exports.\n *\n * Design doc: 24-fonts.md, \"Next.js Font Compatibility\"\n */\n\nimport type { GoogleFontConfig, FontResult } from '../fonts/types.js';\n\nexport type { GoogleFontConfig, FontResult };\n\n/**\n * Create a stub FontResult with empty values.\n *\n * The timber-fonts plugin replaces these calls at build time with real\n * class names and font stacks. This stub ensures code that imports\n * font functions outside of the build pipeline (tests, type-checking)\n * gets a valid FontResult shape without runtime errors.\n */\nfunction createStubFontResult(config?: GoogleFontConfig): FontResult {\n return {\n className: '',\n style: { fontFamily: '' },\n variable: config?.variable,\n };\n}\n\n/**\n * Generic font loader — accepts any font name, returns a stub FontResult.\n *\n * Named exports like `Inter`, `Roboto`, etc. are generated dynamically\n * by the virtual module at build time. This function provides a catch-all\n * for non-Vite contexts.\n */\nexport function createFont(_family: string, config?: GoogleFontConfig): FontResult {\n return createStubFontResult(config);\n}\n\n// Common Google Font exports for TypeScript autocomplete.\n// These are stubs — the virtual module and transform hook provide real values.\nexport const Inter = (config?: GoogleFontConfig): FontResult => createStubFontResult(config);\nexport const Roboto = (config?: GoogleFontConfig): FontResult => createStubFontResult(config);\nexport const Open_Sans = (config?: GoogleFontConfig): FontResult => createStubFontResult(config);\nexport const Lato = (config?: GoogleFontConfig): FontResult => createStubFontResult(config);\nexport const Montserrat = (config?: GoogleFontConfig): FontResult => createStubFontResult(config);\nexport const Poppins = (config?: GoogleFontConfig): FontResult => createStubFontResult(config);\nexport const Geist = (config?: GoogleFontConfig): FontResult => createStubFontResult(config);\nexport const Geist_Mono = (config?: GoogleFontConfig): FontResult => createStubFontResult(config);\nexport const JetBrains_Mono = (config?: GoogleFontConfig): FontResult =>\n createStubFontResult(config);\nexport const Source_Code_Pro = (config?: GoogleFontConfig): FontResult =>\n createStubFontResult(config);\nexport const Playfair_Display = (config?: GoogleFontConfig): FontResult =>\n createStubFontResult(config);\nexport const Merriweather = (config?: GoogleFontConfig): FontResult => createStubFontResult(config);\n"],"mappings":";;;;;;;;;AA+BA,SAAS,qBAAqB,QAAuC;CACnE,OAAO;EACL,WAAW;EACX,OAAO,EAAE,YAAY,IAAI;EACzB,UAAU,QAAQ;EACnB;;;;;;;;;AAUH,SAAgB,WAAW,SAAiB,QAAuC;CACjF,OAAO,qBAAqB,OAAO;;AAKrC,IAAa,SAAS,WAA0C,qBAAqB,OAAO;AAC5F,IAAa,UAAU,WAA0C,qBAAqB,OAAO;AAC7F,IAAa,aAAa,WAA0C,qBAAqB,OAAO;AAChG,IAAa,QAAQ,WAA0C,qBAAqB,OAAO;AAC3F,IAAa,cAAc,WAA0C,qBAAqB,OAAO;AACjG,IAAa,WAAW,WAA0C,qBAAqB,OAAO;AAC9F,IAAa,SAAS,WAA0C,qBAAqB,OAAO;AAC5F,IAAa,cAAc,WAA0C,qBAAqB,OAAO;AACjG,IAAa,kBAAkB,WAC7B,qBAAqB,OAAO;AAC9B,IAAa,mBAAmB,WAC9B,qBAAqB,OAAO;AAC9B,IAAa,oBAAoB,WAC/B,qBAAqB,OAAO;AAC9B,IAAa,gBAAgB,WAA0C,qBAAqB,OAAO"}
1
+ {"version":3,"file":"font-google.js","names":[],"sources":["../../src/shims/font-google.ts"],"sourcesContent":["/**\n * Fallback shim for `next/font/google` → `@timber/fonts/google`.\n *\n * At build time, the timber-fonts plugin's transform hook replaces font\n * function calls with static FontResult objects containing real class names\n * and font stacks. This file serves two purposes:\n *\n * 1. **TypeScript resolution** — provides types for IDEs and `tsc` outside\n * of Vite's module graph (where the virtual module handles it).\n * 2. **Runtime fallback** — returns empty className/fontFamily values when\n * the plugin hasn't processed a call (e.g. in tests or non-Vite environments).\n *\n * The shim resolution path (`next/font/google` → `\\0@timber/fonts/google`)\n * is unchanged — this file is NOT on that resolution path. It exists as a\n * physical module for direct imports and type re-exports.\n *\n * Design doc: 24-fonts.md, \"Next.js Font Compatibility\"\n */\n\nimport type { GoogleFontConfig, FontResult } from '../fonts/types.js';\n\nexport type { GoogleFontConfig, FontResult };\n\n/**\n * Create a stub FontResult with empty values.\n *\n * The timber-fonts plugin replaces these calls at build time with real\n * class names and font stacks. This stub ensures code that imports\n * font functions outside of the build pipeline (tests, type-checking)\n * gets a valid FontResult shape without runtime errors.\n */\nfunction createStubFontResult(config?: GoogleFontConfig): FontResult {\n return {\n className: '',\n style: { fontFamily: '' },\n variable: config?.variable,\n };\n}\n\n/**\n * Generic font loader — accepts any font name, returns a stub FontResult.\n *\n * Named exports like `Inter`, `Roboto`, etc. are generated dynamically\n * by the virtual module at build time. This function provides a catch-all\n * for non-Vite contexts.\n */\nexport function createFont(_family: string, config?: GoogleFontConfig): FontResult {\n return createStubFontResult(config);\n}\n\n// Common Google Font exports for TypeScript autocomplete.\n// These are stubs — the virtual module and transform hook provide real values.\nexport const Inter = (config?: GoogleFontConfig): FontResult => createStubFontResult(config);\nexport const Roboto = (config?: GoogleFontConfig): FontResult => createStubFontResult(config);\nexport const Open_Sans = (config?: GoogleFontConfig): FontResult => createStubFontResult(config);\nexport const Lato = (config?: GoogleFontConfig): FontResult => createStubFontResult(config);\nexport const Montserrat = (config?: GoogleFontConfig): FontResult => createStubFontResult(config);\nexport const Poppins = (config?: GoogleFontConfig): FontResult => createStubFontResult(config);\nexport const Geist = (config?: GoogleFontConfig): FontResult => createStubFontResult(config);\nexport const Geist_Mono = (config?: GoogleFontConfig): FontResult => createStubFontResult(config);\nexport const JetBrains_Mono = (config?: GoogleFontConfig): FontResult =>\n createStubFontResult(config);\nexport const Source_Code_Pro = (config?: GoogleFontConfig): FontResult =>\n createStubFontResult(config);\nexport const Playfair_Display = (config?: GoogleFontConfig): FontResult =>\n createStubFontResult(config);\nexport const Merriweather = (config?: GoogleFontConfig): FontResult => createStubFontResult(config);\n"],"mappings":";;;;;;;;;AA+BA,SAAS,qBAAqB,QAAuC;CACnE,OAAO;EACL,WAAW;EACX,OAAO,EAAE,YAAY,GAAG;EACxB,UAAU,QAAQ;CACpB;AACF;;;;;;;;AASA,SAAgB,WAAW,SAAiB,QAAuC;CACjF,OAAO,qBAAqB,MAAM;AACpC;AAIA,IAAa,SAAS,WAA0C,qBAAqB,MAAM;AAC3F,IAAa,UAAU,WAA0C,qBAAqB,MAAM;AAC5F,IAAa,aAAa,WAA0C,qBAAqB,MAAM;AAC/F,IAAa,QAAQ,WAA0C,qBAAqB,MAAM;AAC1F,IAAa,cAAc,WAA0C,qBAAqB,MAAM;AAChG,IAAa,WAAW,WAA0C,qBAAqB,MAAM;AAC7F,IAAa,SAAS,WAA0C,qBAAqB,MAAM;AAC3F,IAAa,cAAc,WAA0C,qBAAqB,MAAM;AAChG,IAAa,kBAAkB,WAC7B,qBAAqB,MAAM;AAC7B,IAAa,mBAAmB,WAC9B,qBAAqB,MAAM;AAC7B,IAAa,oBAAoB,WAC/B,qBAAqB,MAAM;AAC7B,IAAa,gBAAgB,WAA0C,qBAAqB,MAAM"}
@@ -1 +1 @@
1
- {"version":3,"file":"font-local.js","names":[],"sources":["../../src/shims/font-local.ts"],"sourcesContent":["/**\n * Fallback shim for `next/font/local` → `@timber/fonts/local`.\n *\n * At build time, the timber-fonts plugin's transform hook replaces\n * `localFont()` calls with static FontResult objects containing real\n * class names and font stacks. This file serves two purposes:\n *\n * 1. **TypeScript resolution** — provides types for IDEs and `tsc` outside\n * of Vite's module graph (where the virtual module handles it).\n * 2. **Runtime fallback** — returns empty className/fontFamily values when\n * the plugin hasn't processed a call (e.g. in tests or non-Vite environments).\n *\n * Design doc: 24-fonts.md, \"Next.js Font Compatibility\"\n */\n\nimport type { LocalFontConfig, FontResult } from '../fonts/types.js';\n\nexport type { LocalFontConfig, FontResult };\n\n/**\n * Create a local font.\n *\n * The timber-fonts plugin replaces these calls at build time with real\n * class names and font stacks. This stub ensures code that imports\n * `localFont` outside of the build pipeline (tests, type-checking)\n * gets a valid FontResult shape without runtime errors.\n */\nexport default function localFont(config?: LocalFontConfig): FontResult {\n return {\n className: '',\n style: { fontFamily: '' },\n variable: config?.variable,\n };\n}\n"],"mappings":";;;;;;;;;AA2BA,SAAwB,UAAU,QAAsC;CACtE,OAAO;EACL,WAAW;EACX,OAAO,EAAE,YAAY,IAAI;EACzB,UAAU,QAAQ;EACnB"}
1
+ {"version":3,"file":"font-local.js","names":[],"sources":["../../src/shims/font-local.ts"],"sourcesContent":["/**\n * Fallback shim for `next/font/local` → `@timber/fonts/local`.\n *\n * At build time, the timber-fonts plugin's transform hook replaces\n * `localFont()` calls with static FontResult objects containing real\n * class names and font stacks. This file serves two purposes:\n *\n * 1. **TypeScript resolution** — provides types for IDEs and `tsc` outside\n * of Vite's module graph (where the virtual module handles it).\n * 2. **Runtime fallback** — returns empty className/fontFamily values when\n * the plugin hasn't processed a call (e.g. in tests or non-Vite environments).\n *\n * Design doc: 24-fonts.md, \"Next.js Font Compatibility\"\n */\n\nimport type { LocalFontConfig, FontResult } from '../fonts/types.js';\n\nexport type { LocalFontConfig, FontResult };\n\n/**\n * Create a local font.\n *\n * The timber-fonts plugin replaces these calls at build time with real\n * class names and font stacks. This stub ensures code that imports\n * `localFont` outside of the build pipeline (tests, type-checking)\n * gets a valid FontResult shape without runtime errors.\n */\nexport default function localFont(config?: LocalFontConfig): FontResult {\n return {\n className: '',\n style: { fontFamily: '' },\n variable: config?.variable,\n };\n}\n"],"mappings":";;;;;;;;;AA2BA,SAAwB,UAAU,QAAsC;CACtE,OAAO;EACL,WAAW;EACX,OAAO,EAAE,YAAY,GAAG;EACxB,UAAU,QAAQ;CACpB;AACF"}
@@ -34,7 +34,7 @@ export declare function Image({ priority: _priority, quality: _quality, fill: _f
34
34
  alt?: string | undefined;
35
35
  crossOrigin?: "" | "anonymous" | "use-credentials" | undefined;
36
36
  decoding?: "async" | "auto" | "sync" | undefined;
37
- fetchPriority?: "high" | "low" | "auto";
37
+ fetchPriority?: "high" | "low" | "auto" | undefined;
38
38
  height?: number | string | undefined;
39
39
  loading?: "eager" | "lazy" | undefined;
40
40
  referrerPolicy?: import("react").HTMLAttributeReferrerPolicy | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timber-js/app",
3
- "version": "0.2.0-alpha.138",
3
+ "version": "0.2.0-alpha.139",
4
4
  "description": "Vite-native React framework built for Servers and Serverless Platforms — correct HTTP semantics, real status codes, pages that work without JavaScript",
5
5
  "keywords": [
6
6
  "cloudflare-workers",
@@ -114,12 +114,17 @@
114
114
  "publishConfig": {
115
115
  "access": "public"
116
116
  },
117
+ "scripts": {
118
+ "build": "vite build --config vite.lib.config.ts && tsc --emitDeclarationOnly --project tsconfig.json --outDir dist",
119
+ "typecheck": "tsgo --noEmit",
120
+ "prepublishOnly": "pnpm run build"
121
+ },
117
122
  "dependencies": {
118
123
  "@opentelemetry/api": "^1.9.1",
119
124
  "@opentelemetry/context-async-hooks": "^2.7.1",
120
125
  "@opentelemetry/sdk-trace-base": "^2.7.1",
121
126
  "magic-string": "^0.30.21",
122
- "nitro": "3.0.260429-beta"
127
+ "nitro": "3.0.260522-beta"
123
128
  },
124
129
  "peerDependencies": {
125
130
  "@content-collections/core": "^0.14.0",
@@ -131,7 +136,7 @@
131
136
  "nuqs": "^2.0.0",
132
137
  "react": "19.3.0-canary-d5736f09-20260507",
133
138
  "react-dom": "19.3.0-canary-d5736f09-20260507",
134
- "vite": "^8.0.8",
139
+ "vite": "^8.0.13",
135
140
  "wrangler": "^4.0.0",
136
141
  "zod": "^3.22.0 || ^4.0.0"
137
142
  },
@@ -157,9 +162,5 @@
157
162
  },
158
163
  "engines": {
159
164
  "node": ">=22.12.0"
160
- },
161
- "scripts": {
162
- "build": "vite build --config vite.lib.config.ts && tsc --emitDeclarationOnly --project tsconfig.json --outDir dist",
163
- "typecheck": "tsgo --noEmit"
164
165
  }
165
- }
166
+ }
package/src/cli.ts CHANGED
File without changes
@@ -87,6 +87,13 @@ export interface PluginContext {
87
87
  * Used by adapter-build and cli preview.
88
88
  */
89
89
  buildDir: string;
90
+ /**
91
+ * Per-route client reference keys captured during the RSC build.
92
+ * Maps absolute route file paths to the set of client reference
93
+ * hash keys they transitively import. Used by adapter-build to
94
+ * populate per-route modulepreload entries from clientReferenceDeps.
95
+ */
96
+ routeClientRefKeys?: Map<string, Set<string>>;
90
97
  }
91
98
 
92
99
  // ── App directory resolution ──────────────────────────────────────────────
@@ -18,6 +18,7 @@ import { join } from 'node:path';
18
18
  import { readFile, writeFile } from 'node:fs/promises';
19
19
  import type { PluginContext } from '../plugin-context.js';
20
20
  import type { TimberPlatformAdapter, TimberConfig } from '../adapters/types.js';
21
+ import type { BuildManifest } from '../server/build-manifest.js';
21
22
  import { swallow } from '../server/logger.js';
22
23
 
23
24
  export function timberAdapterBuild(ctx: PluginContext): Plugin {
@@ -38,6 +39,19 @@ export function timberAdapterBuild(ctx: PluginContext): Plugin {
38
39
 
39
40
  const buildDir = ctx.buildDir;
40
41
 
42
+ // Merge per-route CSS and client reference JS from the RSC plugin's
43
+ // assets manifest into the build manifest. This data enables
44
+ // <link rel="stylesheet"> and <link rel="modulepreload"> tags in
45
+ // <head> for server component routes (TIM-1014).
46
+ if (ctx.buildManifest) {
47
+ await mergeRscAssetsManifest(
48
+ buildDir,
49
+ ctx.root,
50
+ ctx.buildManifest,
51
+ ctx.routeClientRefKeys
52
+ );
53
+ }
54
+
41
55
  // Serialize the build manifest as a JS module that sets the global.
42
56
  // The adapter writes this as _timber-manifest-init.mjs and imports it
43
57
  // before the RSC handler, so globalThis.__TIMBER_BUILD_MANIFEST__ is
@@ -88,6 +102,78 @@ export function timberAdapterBuild(ctx: PluginContext): Plugin {
88
102
  * intercept them. This rewrites the files on disk after all builds
89
103
  * complete but before the adapter copies them to the output directory.
90
104
  */
105
+ /**
106
+ * Read the RSC plugin's assets manifest and merge per-route CSS and
107
+ * per-route JS modulepreloads into the build manifest.
108
+ *
109
+ * CSS: `serverResources` maps server component paths to CSS URLs.
110
+ * JS: `clientReferenceDeps` maps reference hashes to JS dep URLs.
111
+ * Combined with `routeClientRefKeys` (from the RSC generateBundle
112
+ * module graph walk) to produce per-route modulepreload entries.
113
+ */
114
+ export async function mergeRscAssetsManifest(
115
+ buildDir: string,
116
+ root: string,
117
+ buildManifest: BuildManifest,
118
+ routeClientRefKeys?: Map<string, Set<string>>
119
+ ): Promise<void> {
120
+ const manifestPath = join(buildDir, 'rsc', '__vite_rsc_assets_manifest.js');
121
+ let content: string;
122
+ try {
123
+ content = await readFile(manifestPath, 'utf-8');
124
+ } catch {
125
+ return;
126
+ }
127
+
128
+ const jsonStr = content.replace(/^export default\s*/, '').replace(/;?\s*$/, '');
129
+ let manifest: Record<string, unknown>;
130
+ try {
131
+ manifest = JSON.parse(jsonStr);
132
+ } catch {
133
+ return;
134
+ }
135
+
136
+ // Merge serverResources CSS into buildManifest.css.
137
+ // Keys in serverResources are root-relative (e.g., "app/layout.tsx" or
138
+ // "src/app/layout.tsx"). Convert to absolute paths to match the runtime
139
+ // route manifest's filePath values.
140
+ const serverResources = manifest.serverResources as
141
+ | Record<string, { js: string[]; css: string[] }>
142
+ | undefined;
143
+ if (serverResources) {
144
+ for (const [relativePath, deps] of Object.entries(serverResources)) {
145
+ if (deps.css && deps.css.length > 0) {
146
+ const absolutePath = join(root, relativePath);
147
+ buildManifest.css[absolutePath] = deps.css;
148
+ }
149
+ }
150
+ }
151
+
152
+ // Build per-route modulepreload entries from routeClientRefKeys +
153
+ // clientReferenceDeps. Each route file's transitive client reference
154
+ // keys (captured during RSC generateBundle) are mapped to their JS
155
+ // chunk URLs via clientReferenceDeps.
156
+ if (routeClientRefKeys && routeClientRefKeys.size > 0) {
157
+ const clientReferenceDeps = manifest.clientReferenceDeps as
158
+ | Record<string, { js: string[]; css: string[] }>
159
+ | undefined;
160
+ if (clientReferenceDeps) {
161
+ for (const [filePath, refKeys] of routeClientRefKeys) {
162
+ const jsUrls = new Set<string>();
163
+ for (const key of refKeys) {
164
+ const deps = clientReferenceDeps[key];
165
+ if (deps?.js) {
166
+ for (const url of deps.js) jsUrls.add(url);
167
+ }
168
+ }
169
+ if (jsUrls.size > 0) {
170
+ buildManifest.modulepreload[filePath] = Array.from(jsUrls);
171
+ }
172
+ }
173
+ }
174
+ }
175
+ }
176
+
91
177
  async function stripJsFromRscAssetsManifests(buildDir: string): Promise<void> {
92
178
  const manifestName = '__vite_rsc_assets_manifest.js';
93
179
  const paths = [join(buildDir, 'rsc', manifestName), join(buildDir, 'ssr', manifestName)];
@@ -34,6 +34,7 @@ interface OutputChunk {
34
34
  name: string;
35
35
  code: string;
36
36
  viteMetadata?: { importedCss?: Set<string> };
37
+ modules?: Record<string, { code: string | null; renderedLength: number }>;
37
38
  }
38
39
 
39
40
  interface OutputAsset {
@@ -184,22 +185,6 @@ export function buildManifestFromBundle(
184
185
  }
185
186
  }
186
187
 
187
- // Collect ALL CSS assets from the bundle under the `_global` key.
188
- // Route files (app/layout.tsx, app/page.tsx) are server components —
189
- // they don't appear in the client bundle, so per-route CSS keying
190
- // via facadeModuleId doesn't work. The RSC plugin handles per-route
191
- // CSS injection via data-rsc-css-href. For Link headers (103 Early
192
- // Hints), we emit all CSS files — they're just prefetch hints.
193
- const allCss: string[] = [];
194
- for (const item of Object.values(bundle) as (OutputChunk | OutputAsset)[]) {
195
- if (item.type === 'asset' && item.fileName.endsWith('.css')) {
196
- allCss.push(base + item.fileName);
197
- }
198
- }
199
- if (allCss.length > 0) {
200
- css['_global'] = allCss;
201
- }
202
-
203
188
  return { css, js, modulepreload, fonts: {}, metadataRouteHashes: {} };
204
189
  }
205
190
 
@@ -275,10 +260,79 @@ function computeMetadataRouteHashes(node: SegmentNode<RouteFile>): Record<string
275
260
  return hashes;
276
261
  }
277
262
 
263
+ // Regex to extract client reference hash keys from registerClientReference calls.
264
+ // Matches: registerClientReference(..., "abc123def456", "ExportName")
265
+ const CLIENT_REF_KEY_RE = /registerClientReference\([^,]+,\s*"([0-9a-f]{12})"/g;
266
+
267
+ /**
268
+ * Collect all route file paths (layout, page) from the route tree.
269
+ */
270
+ function collectRouteFilePaths(node: SegmentNode<RouteFile>): string[] {
271
+ const paths: string[] = [];
272
+ if (node.layout) paths.push(node.layout.filePath);
273
+ if (node.page) paths.push(node.page.filePath);
274
+ for (const child of node.children) paths.push(...collectRouteFilePaths(child));
275
+ for (const slot of Object.values(node.slots)) paths.push(...collectRouteFilePaths(slot));
276
+ return paths;
277
+ }
278
+
279
+ /**
280
+ * Scan RSC bundle chunks to find which modules contain registerClientReference
281
+ * calls, and extract the reference hash keys from each.
282
+ */
283
+ function buildClientRefIndex(bundle: OutputBundle): Map<string, string[]> {
284
+ const index = new Map<string, string[]>();
285
+ for (const item of Object.values(bundle)) {
286
+ if (item.type !== 'chunk' || !item.modules) continue;
287
+ for (const [moduleId, mod] of Object.entries(item.modules)) {
288
+ if (!mod.code) continue;
289
+ const keys: string[] = [];
290
+ for (const match of mod.code.matchAll(CLIENT_REF_KEY_RE)) {
291
+ keys.push(match[1]);
292
+ }
293
+ if (keys.length > 0) {
294
+ index.set(moduleId, keys);
295
+ }
296
+ }
297
+ }
298
+ return index;
299
+ }
300
+
301
+ /** Rollup getModuleInfo shape (subset). */
302
+ interface ModuleInfo {
303
+ importedIds: string[];
304
+ }
305
+
306
+ /**
307
+ * Walk a module's transitive imports to collect all client reference
308
+ * keys reachable from it.
309
+ */
310
+ function walkModuleImports(
311
+ moduleId: string,
312
+ clientRefIndex: Map<string, string[]>,
313
+ visited: Set<string>,
314
+ result: Set<string>,
315
+ getModuleInfo: (id: string) => ModuleInfo | null
316
+ ): void {
317
+ if (visited.has(moduleId)) return;
318
+ visited.add(moduleId);
319
+
320
+ const refKeys = clientRefIndex.get(moduleId);
321
+ if (refKeys) {
322
+ for (const key of refKeys) result.add(key);
323
+ }
324
+
325
+ const info = getModuleInfo(moduleId);
326
+ if (!info) return;
327
+ for (const importedId of info.importedIds) {
328
+ walkModuleImports(importedId, clientRefIndex, visited, result, getModuleInfo);
329
+ }
330
+ }
331
+
278
332
  /**
279
333
  * Create the timber-build-manifest Vite plugin.
280
334
  *
281
- * Hooks: configResolved, resolveId, load, generateBundle (client env only)
335
+ * Hooks: configResolved, resolveId, load, generateBundle (client and RSC envs)
282
336
  */
283
337
  export function timberBuildManifest(ctx: PluginContext): Plugin {
284
338
  let resolvedBase = '/';
@@ -353,6 +407,34 @@ export function timberBuildManifest(ctx: PluginContext): Plugin {
353
407
 
354
408
  const envName = (this as { environment?: { name: string } }).environment?.name;
355
409
 
410
+ // RSC environment: walk the module graph to find which client
411
+ // references each route file transitively imports. Store the
412
+ // mapping in ctx so the adapter-build post hook can cross-reference
413
+ // with clientReferenceDeps to populate per-route modulepreloads.
414
+ if (envName === 'rsc' && ctx.routeTree) {
415
+ const clientRefIndex = buildClientRefIndex(bundle);
416
+ if (clientRefIndex.size > 0) {
417
+ const routeFiles = collectRouteFilePaths(ctx.routeTree.root);
418
+ const routeClientRefKeys = new Map<string, Set<string>>();
419
+
420
+ const getInfo = (id: string) =>
421
+ (this as { getModuleInfo?: (id: string) => ModuleInfo | null }).getModuleInfo?.(id) ??
422
+ null;
423
+
424
+ for (const filePath of routeFiles) {
425
+ const refKeys = new Set<string>();
426
+ walkModuleImports(filePath, clientRefIndex, new Set(), refKeys, getInfo);
427
+ if (refKeys.size > 0) {
428
+ routeClientRefKeys.set(filePath, refKeys);
429
+ }
430
+ }
431
+
432
+ if (routeClientRefKeys.size > 0) {
433
+ ctx.routeClientRefKeys = routeClientRefKeys;
434
+ }
435
+ }
436
+ }
437
+
356
438
  if (envName === 'client') {
357
439
  ctx.buildManifest = buildManifestFromBundle(bundle, resolvedBase, ctx.root);
358
440
 
@@ -104,12 +104,6 @@ export interface EarlyHintOptions {
104
104
  * - Font assets (as=font; rel=preload; crossorigin)
105
105
  * - JS modulepreload hints (rel=modulepreload) — unless skipJs is set
106
106
  *
107
- * Also emits global CSS from the `_global` manifest key. Route files
108
- * are server components that don't appear in the client bundle, so
109
- * per-route CSS keying doesn't work with the RSC plugin. The `_global`
110
- * key contains all CSS assets from the client build — fine for early
111
- * hints since they're just prefetch signals.
112
- *
113
107
  * Returns formatted Link header strings, deduplicated by URL, root → leaf order.
114
108
  * Returns an empty array in dev mode (manifest is empty).
115
109
  */
@@ -137,13 +131,6 @@ export function collectEarlyHintHeaders(
137
131
  add(url, formatLinkHeader({ href: url, rel: 'preload', as: 'style' }));
138
132
  }
139
133
 
140
- // Global CSS — all CSS assets from the client bundle.
141
- // Covers CSS that the RSC plugin injects via data-rsc-css-href,
142
- // which isn't keyed to route segments in our manifest.
143
- for (const url of manifest.css['_global'] ?? []) {
144
- add(url, formatLinkHeader({ href: url, rel: 'preload', as: 'style' }));
145
- }
146
-
147
134
  // Fonts — as=font; rel=preload; crossorigin (crossorigin required per spec)
148
135
  for (const font of collectRouteFonts(segments, manifest)) {
149
136
  add(
@@ -20,8 +20,10 @@
20
20
 
21
21
  import type { BuildManifest } from '../build-manifest.js';
22
22
  import {
23
+ buildCssLinkTags,
23
24
  buildFontPreloadTags,
24
25
  buildModulepreloadTags,
26
+ collectRouteCss,
25
27
  collectRouteFonts,
26
28
  collectRouteModulepreloads,
27
29
  } from '../build-manifest.js';
@@ -152,18 +154,6 @@ export async function renderRoute(
152
154
  // — these ensure resources load even on platforms without Early Hints support.
153
155
  let headHtml = '';
154
156
 
155
- // CSS is handled by the RSC plugin via ReactDOM.preinit() with
156
- // data-precedence attributes. This injects <link rel="stylesheet">
157
- // tags during the RSC render phase — before our headHtml injection.
158
- // We do NOT emit additional <link rel="stylesheet"> tags here because:
159
- // 1. React's Float system deduplicates them, making ours redundant
160
- // 2. The duplicate reference confuses React's client-side preload
161
- // deduplication, causing "preload ignored" browser warnings
162
- //
163
- // CSS URLs are still collected for Early Hints (Link headers) in
164
- // buildEarlyHintsHeaders() — those are HTTP headers, not DOM elements,
165
- // so they don't conflict with Float.
166
-
167
157
  // Font CSS is NOT inlined here anymore (TIM-828). The transform hook
168
158
  // injects a side-effect `import 'virtual:timber-font-css/<hash>.css'`
169
159
  // into each file that calls a font function. @vitejs/plugin-rsc's
@@ -181,6 +171,16 @@ export async function renderRoute(
181
171
  headHtml += buildFontPreloadTags(fontEntries);
182
172
  }
183
173
 
174
+ // Emit <link rel="stylesheet"> tags for per-route CSS (TIM-1014).
175
+ // React Float also emits stylesheet links via preinit() during Fizz
176
+ // rendering, but those arrive too late — after the RSC stream starts.
177
+ // These <head> tags ensure CSS is discovered during HTML parsing.
178
+ // Float deduplicates, so no double-fetch.
179
+ const cssUrls = collectRouteCss(segments, buildManifest);
180
+ if (cssUrls.length > 0) {
181
+ headHtml += buildCssLinkTags(cssUrls);
182
+ }
183
+
184
184
  // Skip modulepreload tags when client JavaScript is disabled — no JS to preload.
185
185
  if (!clientJsDisabled) {
186
186
  const preloadUrls = collectRouteModulepreloads(segments, buildManifest);
package/LICENSE DELETED
@@ -1,8 +0,0 @@
1
- DONTFUCKINGUSE LICENSE
2
-
3
- Copyright (c) 2025 Daniel Saewitz
4
-
5
- This software may not be used, copied, modified, merged, published,
6
- distributed, sublicensed, or sold by anyone other than the copyright holder.
7
-
8
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
@@ -1 +0,0 @@
1
- {"version":3,"file":"cli-schema-sync-q-Ce6-iG.js","names":[],"sources":["../../src/routing/scanner.ts","../../src/utils/startup-timer.ts","../../src/plugin-context.ts","../../src/cli-schema-sync.ts"],"sourcesContent":["/**\n * Route discovery scanner.\n *\n * Pure function: (appDir, config) → RouteTree\n *\n * Scans the app/ directory and builds a segment tree recognizing all\n * timber.js file conventions. Does NOT handle request matching — this\n * is discovery only.\n */\n\nimport { readdirSync, statSync } from 'node:fs';\nimport { join, extname, basename } from 'node:path';\nimport type {\n RouteTree,\n SegmentNode,\n SegmentType,\n RouteFile,\n ScannerConfig,\n InterceptionMarker,\n} from './types.js';\nimport { classifySegment } from './segment-classify.js';\nimport { DEFAULT_PAGE_EXTENSIONS } from './types.js';\nimport { classifyMetadataRoute, isDynamicMetadataExtension } from '../server/metadata-routes.js';\nimport { swallow } from '../server/logger.js';\n\n/**\n * Pattern matching encoded path delimiters that must be rejected during route discovery.\n * %2F / %2f (forward slash) and %5C / %5c (backslash) can cause route collisions\n * when decoded. See design/13-security.md §\"Encoded separators rejected\".\n */\nconst ENCODED_SEPARATOR_PATTERN = /%(?:2[fF]|5[cC])/;\n\n/**\n * Pattern matching encoded null bytes (%00) that must be rejected.\n * See design/13-security.md §\"Null bytes rejected\".\n */\nconst ENCODED_NULL_PATTERN = /%00/;\n\n/**\n * File convention names that use pageExtensions (can be .tsx, .ts, .jsx, .js, .mdx, etc.)\n */\nconst PAGE_EXT_CONVENTIONS = new Set(['page', 'layout', 'error', 'default', 'denied']);\n\n/**\n * Legacy compat status-code files.\n * Maps legacy file name → HTTP status code for the fallback chain.\n * See design/10-error-handling.md §\"Fallback Chain\".\n */\nconst LEGACY_STATUS_FILES: Record<string, number> = {\n 'not-found': 404,\n 'forbidden': 403,\n 'unauthorized': 401,\n};\n\n/**\n * File convention names that are always .ts/.tsx (never .mdx etc.)\n */\nconst FIXED_CONVENTIONS = new Set(['middleware', 'access', 'route', 'params']);\n\n/**\n * Status-code file patterns:\n * - Exact 3-digit codes: 401.tsx, 429.tsx, 503.tsx\n * - Category catch-alls: 4xx.tsx, 5xx.tsx\n */\nconst STATUS_CODE_PATTERN = /^(\\d{3}|[45]xx)$/;\n\n/**\n * Scan the app/ directory and build the route tree.\n *\n * @param appDir - Absolute path to the app/ directory\n * @param config - Scanner configuration\n * @returns The complete route tree\n */\nexport function scanRoutes(appDir: string, config: ScannerConfig = {}): RouteTree {\n const pageExtensions = config.pageExtensions ?? DEFAULT_PAGE_EXTENSIONS;\n const extSet = new Set(pageExtensions);\n\n const tree: RouteTree = {\n root: createSegmentNode('', 'static', '/'),\n };\n\n // Check for proxy.ts at app root\n const proxyFile = findFixedFile(appDir, 'proxy');\n if (proxyFile) {\n tree.proxy = proxyFile;\n }\n\n // Check for global-error.{tsx,ts,jsx,js} at app root.\n // Tier 2 error page — renders standalone (no layouts) when no segment-level\n // error file is found. See design/10-error-handling.md §\"Tier 2\".\n const globalErrorFile = findPageExtFile(appDir, 'global-error', extSet);\n if (globalErrorFile) {\n tree.globalError = globalErrorFile;\n }\n\n // Scan the root directory's files\n scanSegmentFiles(appDir, tree.root, extSet);\n\n // Scan children recursively\n scanChildren(appDir, tree.root, extSet);\n\n // Validate: detect route group collisions (different groups producing pages at the same URL)\n validateRouteGroupCollisions(tree.root);\n\n // Validate: detect duplicate param names in nested dynamic segments\n // e.g., /[id]/items/[id] — same param name in ancestor and descendant\n validateDuplicateParamNames(tree.root);\n\n // Validate: detect same param name used with different bracket forms\n // e.g., [id] in one route and [...id] in another\n validateUniqueBracketForms(tree.root);\n\n return tree;\n}\n\n/**\n * Create an empty segment node.\n */\nfunction createSegmentNode(\n segmentName: string,\n segmentType: SegmentType,\n urlPath: string,\n paramName?: string,\n interceptionMarker?: InterceptionMarker,\n interceptedSegmentName?: string\n): SegmentNode {\n return {\n segmentName,\n segmentType,\n urlPath,\n paramName,\n interceptionMarker,\n interceptedSegmentName,\n children: [],\n slots: {},\n };\n}\n\n/**\n * Compute the URL path for a child segment given its parent's URL path.\n * Route groups, slots, and intercepting routes do NOT add URL depth.\n */\nfunction computeUrlPath(parentUrlPath: string, dirName: string, segmentType: SegmentType): string {\n // Groups, slots, and intercepting routes don't add to URL path\n if (segmentType === 'group' || segmentType === 'slot' || segmentType === 'intercepting') {\n return parentUrlPath;\n }\n\n const parentPath = parentUrlPath === '/' ? '' : parentUrlPath;\n return `${parentPath}/${dirName}`;\n}\n\n/**\n * Scan a directory for file conventions and populate the segment node.\n */\nfunction scanSegmentFiles(dirPath: string, node: SegmentNode, extSet: Set<string>): void {\n let entries: string[];\n try {\n entries = readdirSync(dirPath);\n } catch (err) {\n swallow(err, `scanSegmentFiles: unreadable directory ${dirPath}`, { level: 'warn' });\n return;\n }\n\n for (const entry of entries) {\n const fullPath = join(dirPath, entry);\n\n // Skip directories — handled by scanChildren\n try {\n if (statSync(fullPath).isDirectory()) continue;\n } catch {\n continue;\n }\n\n const ext = extname(entry).slice(1); // remove leading dot\n const name = basename(entry, `.${ext}`);\n\n // Page-extension conventions (page, layout, error, default, denied)\n if (PAGE_EXT_CONVENTIONS.has(name) && extSet.has(ext)) {\n const file: RouteFile = { filePath: fullPath, extension: ext };\n switch (name) {\n case 'page':\n node.page = file;\n break;\n case 'layout':\n node.layout = file;\n break;\n case 'error':\n node.error = file;\n break;\n case 'default':\n node.default = file;\n break;\n case 'denied':\n node.denied = file;\n break;\n }\n continue;\n }\n\n // Fixed conventions (middleware, access, route) — always .ts or .tsx\n if (FIXED_CONVENTIONS.has(name) && /\\.?[jt]sx?$/.test(ext)) {\n const file: RouteFile = { filePath: fullPath, extension: ext };\n switch (name) {\n case 'middleware':\n node.middleware = file;\n break;\n case 'access':\n node.access = file;\n break;\n case 'route':\n node.route = file;\n break;\n case 'params':\n node.params = file;\n break;\n }\n continue;\n }\n\n // JSON status-code files (401.json, 4xx.json, 503.json, 5xx.json)\n // Recognized regardless of pageExtensions — .json is a data format, not a page extension.\n if (STATUS_CODE_PATTERN.test(name) && ext === 'json') {\n if (!node.jsonStatusFiles) {\n node.jsonStatusFiles = {};\n }\n node.jsonStatusFiles[name] = { filePath: fullPath, extension: ext };\n continue;\n }\n\n // Status-code files (401.tsx, 4xx.tsx, 503.tsx, 5xx.tsx)\n if (STATUS_CODE_PATTERN.test(name) && extSet.has(ext)) {\n if (!node.statusFiles) {\n node.statusFiles = {};\n }\n node.statusFiles[name] = { filePath: fullPath, extension: ext };\n continue;\n }\n\n // Legacy compat files (not-found.tsx, forbidden.tsx, unauthorized.tsx)\n if (name in LEGACY_STATUS_FILES && extSet.has(ext)) {\n if (!node.legacyStatusFiles) {\n node.legacyStatusFiles = {};\n }\n node.legacyStatusFiles[name] = { filePath: fullPath, extension: ext };\n continue;\n }\n\n // Metadata route files (sitemap.ts, robots.ts, icon.tsx, opengraph-image.tsx, etc.)\n // Both static (.xml, .txt, .png, .ico, etc.) and dynamic (.ts, .tsx) files are recognized.\n // When both exist for the same base name, dynamic takes precedence.\n // See design/16-metadata.md §\"Metadata Routes\"\n const metaInfo = classifyMetadataRoute(entry);\n if (metaInfo) {\n if (!node.metadataRoutes) {\n node.metadataRoutes = {};\n }\n const existing = node.metadataRoutes[name];\n if (existing) {\n // Dynamic > static precedence: only overwrite if the new file is dynamic\n // or the existing file is static (dynamic always wins).\n const existingIsDynamic = isDynamicMetadataExtension(name, existing.extension);\n const newIsDynamic = isDynamicMetadataExtension(name, ext);\n if (newIsDynamic || !existingIsDynamic) {\n node.metadataRoutes[name] = { filePath: fullPath, extension: ext };\n }\n } else {\n node.metadataRoutes[name] = { filePath: fullPath, extension: ext };\n }\n }\n }\n\n // Validate: route.ts + page.* is a hard build error\n if (node.route && node.page) {\n throw new Error(\n `Build error: route.ts and page.* cannot coexist in the same segment.\\n` +\n ` route.ts: ${node.route.filePath}\\n` +\n ` page: ${node.page.filePath}\\n` +\n `A URL is either an API endpoint or a rendered page, not both.`\n );\n }\n}\n\n/**\n * Recursively scan child directories and build the segment tree.\n */\nfunction scanChildren(dirPath: string, parentNode: SegmentNode, extSet: Set<string>): void {\n let entries: string[];\n try {\n entries = readdirSync(dirPath);\n } catch (err) {\n swallow(err, `scanChildren: unreadable directory ${dirPath}`, { level: 'warn' });\n return;\n }\n\n for (const entry of entries) {\n const fullPath = join(dirPath, entry);\n\n try {\n if (!statSync(fullPath).isDirectory()) continue;\n } catch {\n continue;\n }\n\n // Reject directories with encoded path delimiters or null bytes.\n // These can cause route collisions when decoded at the URL boundary.\n // See design/13-security.md §\"Encoded separators rejected\" and §\"Null bytes rejected\".\n if (ENCODED_SEPARATOR_PATTERN.test(entry)) {\n throw new Error(\n `Build error: directory name contains an encoded path delimiter (%%2F or %%5C).\\n` +\n ` Directory: ${fullPath}\\n` +\n `Encoded separators in directory names cause route collisions when decoded. ` +\n `Rename the directory to remove the encoded delimiter.`\n );\n }\n if (ENCODED_NULL_PATTERN.test(entry)) {\n throw new Error(\n `Build error: directory name contains an encoded null byte (%%00).\\n` +\n ` Directory: ${fullPath}\\n` +\n `Encoded null bytes in directory names are not allowed. ` +\n `Rename the directory to remove the null byte encoding.`\n );\n }\n\n const { type, paramName, interceptionMarker, interceptedSegmentName } = classifySegment(entry);\n\n // Skip private folders — underscore-prefixed dirs are excluded from routing\n if (type === 'private') continue;\n\n const urlPath = computeUrlPath(parentNode.urlPath, entry, type);\n const childNode = createSegmentNode(\n entry,\n type,\n urlPath,\n paramName,\n interceptionMarker,\n interceptedSegmentName\n );\n\n // Scan this segment's files\n scanSegmentFiles(fullPath, childNode, extSet);\n\n // Recurse into subdirectories\n scanChildren(fullPath, childNode, extSet);\n\n // Attach to parent: slots go into slots record, everything else is a child\n if (type === 'slot') {\n const slotName = entry.slice(1); // remove @\n parentNode.slots[slotName] = childNode;\n } else {\n parentNode.children.push(childNode);\n }\n }\n}\n\n/**\n * Validate that route groups don't produce conflicting pages/routes at the same URL path.\n *\n * Two route groups like (auth)/login/page.tsx and (marketing)/login/page.tsx both claim\n * /login — the scanner must detect and reject this at build time.\n *\n * Parallel slots are excluded from collision detection — they intentionally coexist at\n * the same URL path as their parent (that's the whole point of parallel routes).\n */\nfunction validateRouteGroupCollisions(root: SegmentNode): void {\n // Map from urlPath → { filePath, source } for the first page/route seen at that path\n const seen = new Map<string, { filePath: string; segmentPath: string }>();\n collectRoutableLeaves(root, seen, '', false);\n}\n\n/**\n * Walk the segment tree and collect all routable leaves (page or route files),\n * throwing on collision. Slots are tracked in their own collision space since\n * they are parallel routes that intentionally share URL paths with their parent.\n */\nfunction collectRoutableLeaves(\n node: SegmentNode,\n seen: Map<string, { filePath: string; segmentPath: string }>,\n segmentPath: string,\n insideSlot: boolean\n): void {\n const currentPath = segmentPath\n ? `${segmentPath}/${node.segmentName}`\n : node.segmentName || '(root)';\n\n // Only check collisions for non-slot pages — slots intentionally share URL paths\n if (!insideSlot) {\n const routableFile = node.page ?? node.route;\n if (routableFile) {\n const existing = seen.get(node.urlPath);\n if (existing) {\n throw new Error(\n `Build error: route collision — multiple route groups produce a page/route at the same URL path.\\n` +\n ` URL path: ${node.urlPath}\\n` +\n ` File 1: ${existing.filePath} (via ${existing.segmentPath})\\n` +\n ` File 2: ${routableFile.filePath} (via ${currentPath})\\n` +\n `Each URL path must map to exactly one page or route handler. ` +\n `Rename or move one of the conflicting files.`\n );\n }\n seen.set(node.urlPath, { filePath: routableFile.filePath, segmentPath: currentPath });\n }\n }\n\n // Recurse into children\n for (const child of node.children) {\n collectRoutableLeaves(child, seen, currentPath, insideSlot);\n }\n\n // Recurse into slots — each slot is its own parallel route space\n for (const slotNode of Object.values(node.slots)) {\n collectRoutableLeaves(slotNode, seen, currentPath, true);\n }\n}\n\n/**\n * Validate that no route chain contains duplicate dynamic param names.\n *\n * Example violation:\n * app/[id]/items/[id]/page.tsx — 'id' appears twice in the ancestor chain.\n *\n * Route groups are transparent — params accumulate through them.\n * Slots are independent — duplicate detection does NOT cross slot boundaries.\n *\n * See design/07-routing.md §\"Duplicate Param Name Detection\"\n */\nfunction validateDuplicateParamNames(root: SegmentNode): void {\n walkForDuplicateParams(root, new Map());\n}\n\n/**\n * Recursively walk the segment tree, tracking seen param names → segment paths.\n * Throws on the first duplicate found.\n */\nfunction walkForDuplicateParams(node: SegmentNode, seen: Map<string, string>): void {\n // If this node introduces a param name, check for duplicates\n if (node.paramName) {\n const existing = seen.get(node.paramName);\n if (existing) {\n throw new Error(\n `[timber] Duplicate param name '${node.paramName}' in route chain.\\n` +\n ` First defined at: ${existing}\\n` +\n ` Duplicate at: ${node.urlPath || '/'}\\n` +\n ` Rename one of the segments to avoid ambiguity.`\n );\n }\n // Add to seen for descendants (use a new Map to avoid polluting siblings)\n seen = new Map(seen);\n seen.set(node.paramName, node.urlPath || '/');\n }\n\n // Recurse into children (they inherit the accumulated params)\n for (const child of node.children) {\n walkForDuplicateParams(child, seen);\n }\n\n // Slots are independent parallel routes — start fresh param tracking\n // (a slot's params don't conflict with the main route's params)\n for (const slotNode of Object.values(node.slots)) {\n walkForDuplicateParams(slotNode, new Map(seen));\n }\n}\n\n/**\n * Validate that each param name uses a single bracket form across the entire route tree.\n *\n * Different bracket forms produce different types:\n * [id] → string\n * [...id] → string[]\n * [[...id]] → string[] | undefined\n *\n * If the same param name appears with multiple forms, the no-argument\n * getSegmentParams() / useSegmentParams() return type becomes ambiguous.\n *\n * Same name + same form across different routes is fine and expected.\n */\nfunction validateUniqueBracketForms(root: SegmentNode): void {\n const forms = new Map<string, { segmentType: SegmentType; urlPath: string }>();\n collectBracketForms(root, forms);\n}\n\nfunction collectBracketForms(\n node: SegmentNode,\n forms: Map<string, { segmentType: SegmentType; urlPath: string }>\n): void {\n if (node.paramName && node.segmentType !== 'static' && node.segmentType !== 'group') {\n const existing = forms.get(node.paramName);\n if (existing && existing.segmentType !== node.segmentType) {\n const bracketExamples: Record<string, string> = {\n 'dynamic': `[${node.paramName}]`,\n 'catch-all': `[...${node.paramName}]`,\n 'optional-catch-all': `[[...${node.paramName}]]`,\n };\n throw new Error(\n `[timber] Param name '${node.paramName}' uses multiple bracket forms.\\n` +\n ` ${bracketExamples[existing.segmentType] ?? existing.segmentType} at: ${existing.urlPath}\\n` +\n ` ${bracketExamples[node.segmentType] ?? node.segmentType} at: ${node.urlPath || '/'}\\n` +\n ` Each param name must use a single bracket form across all routes.\\n` +\n ` Rename one of the segments to fix this (e.g., [${node.paramName}] → [${node.paramName}Id]).`\n );\n }\n if (!existing) {\n forms.set(node.paramName, {\n segmentType: node.segmentType,\n urlPath: node.urlPath || '/',\n });\n }\n }\n\n for (const child of node.children) {\n collectBracketForms(child, forms);\n }\n for (const slotNode of Object.values(node.slots)) {\n collectBracketForms(slotNode, forms);\n }\n}\n\n/**\n * Find a fixed-extension file (proxy.ts) in a directory.\n */\nfunction findFixedFile(dirPath: string, name: string): RouteFile | undefined {\n for (const ext of ['ts', 'tsx']) {\n const fullPath = join(dirPath, `${name}.${ext}`);\n try {\n if (statSync(fullPath).isFile()) {\n return { filePath: fullPath, extension: ext };\n }\n } catch {\n // File doesn't exist\n }\n }\n return undefined;\n}\n\n/**\n * Find a file using the configured page extensions (tsx, ts, jsx, js, mdx, etc.).\n * Used for app-root conventions like global-error that aren't per-segment.\n */\nfunction findPageExtFile(\n dirPath: string,\n name: string,\n extSet: Set<string>\n): RouteFile | undefined {\n for (const ext of extSet) {\n const fullPath = join(dirPath, `${name}.${ext}`);\n try {\n if (statSync(fullPath).isFile()) {\n return { filePath: fullPath, extension: ext };\n }\n } catch {\n // File doesn't exist\n }\n }\n return undefined;\n}\n","/**\n * Startup timer — records named phases with their durations.\n *\n * Used by the plugin system to instrument cold start and report a\n * timing breakdown in dev mode. Zero overhead in production (disabled).\n *\n * See design/18-build-system.md, TIM-155.\n */\n\nimport { performance } from 'node:perf_hooks';\n\nexport interface PhaseRecord {\n name: string;\n startMs: number;\n durationMs: number;\n}\n\nexport interface StartupTimer {\n /** Mark the beginning of a named phase. */\n start(phase: string): void;\n /** Mark the end of a named phase. Returns duration in ms. */\n end(phase: string): number;\n /** Get all completed phase records, ordered by start time. */\n getPhases(): PhaseRecord[];\n /** Total elapsed time from first start() to last end(). */\n totalMs(): number;\n /** Format a human-readable summary string. */\n formatSummary(): string;\n}\n\n/**\n * Create a startup timer that records phase durations.\n */\nexport function createStartupTimer(): StartupTimer {\n const pending = new Map<string, number>();\n const phases: PhaseRecord[] = [];\n\n return {\n start(phase: string): void {\n pending.set(phase, performance.now());\n },\n\n end(phase: string): number {\n const startMs = pending.get(phase);\n if (startMs === undefined) {\n return 0;\n }\n pending.delete(phase);\n const durationMs = performance.now() - startMs;\n phases.push({ name: phase, startMs, durationMs });\n return durationMs;\n },\n\n getPhases(): PhaseRecord[] {\n return [...phases].sort((a, b) => a.startMs - b.startMs);\n },\n\n totalMs(): number {\n if (phases.length === 0) return 0;\n const sorted = this.getPhases();\n const first = sorted[0];\n const last = sorted[sorted.length - 1];\n return last.startMs + last.durationMs - first.startMs;\n },\n\n formatSummary(): string {\n const sorted = this.getPhases();\n if (sorted.length === 0) return 'No phases recorded.';\n\n const lines = sorted.map((p) => {\n const ms = p.durationMs.toFixed(1);\n return ` ${p.name.padEnd(30)} ${ms.padStart(8)}ms`;\n });\n\n const total = this.totalMs().toFixed(1);\n lines.push(` ${'total'.padEnd(30)} ${total.padStart(8)}ms`);\n\n return ['[timber] startup timing:', ...lines].join('\\n');\n },\n };\n}\n\n/**\n * No-op timer for production builds — all methods are empty.\n */\nexport function createNoopTimer(): StartupTimer {\n return {\n start() {},\n end() {\n return 0;\n },\n getPhases() {\n return [];\n },\n totalMs() {\n return 0;\n },\n formatSummary() {\n return '';\n },\n };\n}\n","/**\n * Plugin context — internal types and helpers for timber sub-plugins.\n *\n * These are NOT part of the user-facing API. They are consumed by\n * sub-plugins (routing, entries, shims, etc.) via relative imports.\n * The root entry point (index.ts) does not re-export them.\n *\n * Design doc: 18-build-system.md §\"Shared Plugin Context\"\n */\n\nimport { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { createRequire } from 'node:module';\nimport type { RouteTree } from './routing/types';\nimport type { BuildManifest } from './server/build-manifest';\nimport type { StartupTimer } from './utils/startup-timer';\nimport { createStartupTimer } from './utils/startup-timer';\nimport type { TimberUserConfig, ClientJavascriptConfig } from './config-types.js';\nimport type { HoldingServer } from './dev-tools/holding-server.js';\n\n// Re-export for sub-plugin convenience — they import from plugin-context.ts\nexport type { TimberUserConfig, ClientJavascriptConfig } from './config-types.js';\n\n// ── Client JavaScript config ─────────────────────────────────────────────\n\n/** Fully resolved client JavaScript configuration (no optionals). */\nexport interface ResolvedClientJavascript {\n disabled: boolean;\n enableHMRInDev: boolean;\n}\n\n/**\n * Resolve `clientJavascript` into a fully resolved config.\n */\nexport function resolveClientJavascript(config: TimberUserConfig): ResolvedClientJavascript {\n if (config.clientJavascript !== undefined) {\n if (typeof config.clientJavascript === 'boolean') {\n return {\n disabled: !config.clientJavascript,\n enableHMRInDev: !config.clientJavascript,\n };\n }\n return {\n disabled: config.clientJavascript.disabled,\n enableHMRInDev: config.clientJavascript.enableHMRInDev ?? config.clientJavascript.disabled,\n };\n }\n return { disabled: false, enableHMRInDev: false };\n}\n\n// ── Plugin Context ────────────────────────────────────────────────────────\n\n/**\n * Shared context object passed to all sub-plugins via closure.\n *\n * Sub-plugins communicate through this context — not through Vite's\n * plugin API or global state.\n * See design/18-build-system.md §\"Shared Plugin Context\".\n */\nexport interface PluginContext {\n config: TimberUserConfig;\n /** Resolved client JavaScript configuration */\n clientJavascript: ResolvedClientJavascript;\n /** The scanned route tree (populated by timber-routing, consumed by timber-entries) */\n routeTree: RouteTree | null;\n /** Absolute path to the app/ directory */\n appDir: string;\n /** Absolute path to the project root */\n root: string;\n /** Whether the dev server is running (set by timber-root-sync in configResolved) */\n dev: boolean;\n /** CSS build manifest (populated by adapter after client build, null in dev) */\n buildManifest: BuildManifest | null;\n /** Per-build deployment ID for version skew detection (null in dev) */\n deploymentId: string | null;\n /** Startup timer for profiling cold start phases (active in dev, no-op in prod) */\n timer: StartupTimer;\n /** Holding server that binds the port during dev startup (closed in timber-dev-server) */\n holdingServer?: HoldingServer | null;\n /**\n * Resolved absolute path to the build output directory.\n *\n * Defaults to `<root>/.timber/dist`. Can be overridden via\n * `timber.config.ts` `buildDir` or Vite's `build.outDir`.\n * Timber config takes precedence when both are set.\n *\n * Used by adapter-build and cli preview.\n */\n buildDir: string;\n}\n\n// ── App directory resolution ──────────────────────────────────────────────\n\n/**\n * Resolve the app directory. Checks (in order):\n * 1. Explicit `configAppDir` from timber.config.ts\n * 2. `<root>/app`\n * 3. `<root>/src/app`\n *\n * Throws if none exist.\n */\nexport function resolveAppDir(root: string, configAppDir?: string): string {\n if (configAppDir) {\n const explicit = join(root, configAppDir);\n if (!existsSync(explicit)) {\n throw new Error(`[timber] Configured appDir \"${configAppDir}\" does not exist at ${explicit}`);\n }\n return explicit;\n }\n\n const rootApp = join(root, 'app');\n if (existsSync(rootApp)) return rootApp;\n\n const srcApp = join(root, 'src', 'app');\n if (existsSync(srcApp)) return srcApp;\n\n throw new Error(\n `[timber] Could not find app directory. Expected \"app/\" or \"src/app/\" in ${root}. ` +\n `You can set appDir in timber.config.ts to specify a custom location.`\n );\n}\n\n// ── Plugin context factory ────────────────────────────────────────────────\n\nexport function createPluginContext(config?: TimberUserConfig, root?: string): PluginContext {\n const projectRoot = root ?? process.cwd();\n const resolvedConfig: TimberUserConfig = { ...config };\n return {\n config: resolvedConfig,\n clientJavascript: resolveClientJavascript(resolvedConfig),\n routeTree: null,\n appDir: join(projectRoot, 'app'),\n root: projectRoot,\n dev: false,\n buildManifest: null,\n deploymentId: null,\n timer: createStartupTimer(),\n holdingServer: null,\n buildDir: resolveBuildDir(projectRoot, resolvedConfig.buildDir),\n };\n}\n\n// ── Build directory resolution ────────────────────────────────────────────\n\n/** Default build output directory (relative to root). */\nexport const DEFAULT_BUILD_DIR = join('.timber', 'dist');\n\n/**\n * Resolve the build output directory.\n *\n * Priority:\n * 1. Explicit `timberBuildDir` from timber.config.ts\n * 2. Explicit `viteBuildOutDir` from Vite's build.outDir (if not the default 'dist')\n * 3. Default: `.timber/dist`\n *\n * Returns an absolute path.\n */\nexport function resolveBuildDir(\n root: string,\n timberBuildDir?: string,\n viteBuildOutDir?: string\n): string {\n if (timberBuildDir) {\n return join(root, timberBuildDir);\n }\n if (viteBuildOutDir && viteBuildOutDir !== 'dist') {\n return join(root, viteBuildOutDir);\n }\n return join(root, DEFAULT_BUILD_DIR);\n}\n\n// ── Config file loading ───────────────────────────────────────────────────\n\n/**\n * Load timber.config.ts (or .js, .mjs) from the project root.\n * Returns the config object or null if no config file is found.\n *\n * Uses require() which works for ESM modules on Node 22.12+.\n * This keeps timber() synchronous — no async config loading needed.\n */\nexport function loadTimberConfigFile(root: string): TimberUserConfig | null {\n const configNames = ['timber.config.ts', 'timber.config.js', 'timber.config.mjs'];\n const req = createRequire(join(root, 'package.json'));\n\n for (const name of configNames) {\n const configPath = join(root, name);\n if (existsSync(configPath)) {\n const mod = req(configPath);\n return (mod.default ?? mod) as TimberUserConfig;\n }\n }\n return null;\n}\n\n/**\n * Detect config keys set in both inline (vite.config.ts) and file (timber.config.ts)\n * and warn the user. The `output` key is excluded because it defaults to 'server'\n * in createPluginContext and would always appear as an inline key.\n *\n * Returns the list of conflicting key names (for testing).\n */\nexport function warnConfigConflicts(\n inline: TimberUserConfig,\n fileConfig: TimberUserConfig\n): string[] {\n const conflicts: string[] = [];\n for (const key of Object.keys(fileConfig) as (keyof TimberUserConfig)[]) {\n if (key in inline && inline[key] !== undefined) {\n conflicts.push(key);\n }\n }\n if (conflicts.length > 0) {\n console.warn(\n `[timber] Config conflict: ${conflicts.map((k) => `\"${k}\"`).join(', ')} set in both ` +\n `vite.config.ts (inline) and timber.config.ts. ` +\n `Move all config to timber.config.ts to avoid confusion. ` +\n `The inline value from vite.config.ts will be used.`\n );\n }\n return conflicts;\n}\n\n/**\n * Merge file-based config into ctx.config. Inline config (already in ctx.config)\n * takes precedence — file config only fills in missing fields.\n */\nexport function mergeFileConfig(ctx: PluginContext, fileConfig: TimberUserConfig): void {\n const inline = ctx.config;\n warnConfigConflicts(inline, fileConfig);\n\n ctx.config = {\n ...fileConfig,\n ...inline,\n ...(fileConfig.limits && inline.limits\n ? { limits: { ...fileConfig.limits, ...inline.limits } }\n : {}),\n ...(fileConfig.dev && inline.dev ? { dev: { ...fileConfig.dev, ...inline.dev } } : {}),\n ...(fileConfig.mdx && inline.mdx ? { mdx: { ...fileConfig.mdx, ...inline.mdx } } : {}),\n };\n}\n","/**\n * `timber schema sync` — scan filesystem for dynamic segments, append\n * missing entries with `codec.string` defaults, and remove stale entries\n * whose segments no longer exist in the filesystem.\n *\n * Design doc: design/41-global-params.md §Auto-Scaffolding\n */\n\nimport { readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { scanRoutes } from './routing/scanner.js';\nimport type { SegmentNode } from './routing/types.js';\nimport { resolveAppDir } from './plugin-context.js';\n\n/**\n * Collect all unique bracket-keyed dynamic segment names from the route tree.\n * Returns entries like '[artistSlug]', '[...slug]', '[[...topic]]'.\n */\nexport function collectDynamicSegments(appDir: string): string[] {\n const tree = scanRoutes(appDir);\n const segments = new Set<string>();\n walkSegments(tree.root, segments);\n return [...segments].sort();\n}\n\nfunction walkSegments(node: SegmentNode, segments: Set<string>): void {\n if (node.paramName) {\n // Reconstruct bracket key from segmentName (already has brackets)\n segments.add(node.segmentName);\n }\n for (const child of node.children) {\n walkSegments(child, segments);\n }\n for (const slot of Object.values(node.slots)) {\n walkSegments(slot, segments);\n }\n}\n\n/**\n * Parse existing bracket keys from an app/schema.ts file.\n * Uses regex to find keys in segmentParams: { '[key]': ... } blocks.\n */\nexport function parseExistingSchemaKeys(content: string): string[] {\n const keys: string[] = [];\n // Match quoted bracket keys like '[artistSlug]', '[...slug]', '[[...topic]]'\n const re = /['\"](\\[{1,2}(?:\\.{3})?\\w+\\]{1,2})['\"]\\s*:/g;\n let m: RegExpExecArray | null;\n while ((m = re.exec(content)) !== null) {\n keys.push(m[1]);\n }\n return keys;\n}\n\n/** Schema file extensions to check. */\nconst SCHEMA_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx'];\n\n/**\n * Find the schema file path if it exists.\n */\nexport function findSchemaFile(appDir: string): string | undefined {\n for (const ext of SCHEMA_EXTENSIONS) {\n const p = join(appDir, `schema${ext}`);\n if (existsSync(p)) return p;\n }\n return undefined;\n}\n\nexport function defaultCodecForSegment(seg: string): string {\n if (seg.startsWith('[[...')) return 'codec.optionalCatchAll(codec.string)';\n if (seg.startsWith('[...')) return 'codec.stringArray';\n return 'codec.string';\n}\n\n/**\n * Generate the content for a new app/schema.ts with the given segment keys.\n */\nfunction generateNewSchema(segments: string[]): string {\n const lines: string[] = [];\n lines.push(\"import { defineSchema } from '@timber-js/app/params';\");\n lines.push(\"import { codec } from '@timber-js/app/codec';\");\n lines.push('');\n lines.push('export default defineSchema({');\n lines.push(' segmentParams: {');\n for (const seg of segments) {\n lines.push(` '${seg}': ${defaultCodecForSegment(seg)},`);\n }\n lines.push(' },');\n lines.push('});');\n lines.push('');\n return lines.join('\\n');\n}\n\n/**\n * Append missing segment entries to an existing schema file.\n * Inserts new entries before the closing `}` of segmentParams.\n */\nconst CODEC_IMPORT = \"import { codec } from '@timber-js/app/codec';\";\n\nfunction hasCodecImport(content: string): boolean {\n return /import\\s+\\{[^}]*\\bcodec\\b[^}]*\\}\\s+from\\s+['\"]@timber-js\\/app\\/codec['\"]/.test(content);\n}\n\nfunction ensureCodecImport(content: string): string {\n if (hasCodecImport(content)) return content;\n\n // Insert after the last existing import statement\n const lines = content.split('\\n');\n let lastImportIndex = -1;\n for (let i = 0; i < lines.length; i++) {\n if (/^\\s*import\\s/.test(lines[i])) lastImportIndex = i;\n }\n\n if (lastImportIndex === -1) {\n return CODEC_IMPORT + '\\n' + content;\n }\n\n lines.splice(lastImportIndex + 1, 0, CODEC_IMPORT);\n return lines.join('\\n');\n}\n\nfunction appendToSchema(content: string, newSegments: string[]): string {\n content = ensureCodecImport(content);\n\n // Find \"segmentParams\" key position\n const spIndex = content.indexOf('segmentParams');\n if (spIndex === -1) {\n throw new Error(\n '[timber] Could not find segmentParams in schema file. ' +\n 'Expected a defineSchema({ segmentParams: { ... } }) call.'\n );\n }\n\n // Find the opening brace after segmentParams\n const openBrace = content.indexOf('{', spIndex + 'segmentParams'.length);\n if (openBrace === -1) {\n throw new Error('[timber] Could not find opening brace for segmentParams.');\n }\n\n // Count braces to find the matching close\n let depth = 1;\n let i = openBrace + 1;\n while (i < content.length && depth > 0) {\n if (content[i] === '{') depth++;\n else if (content[i] === '}') depth--;\n i++;\n }\n // i now points to just after the closing brace\n const closeBrace = i - 1;\n\n // Detect indentation from existing entries\n const existingLine = content.slice(openBrace, closeBrace).match(/^(\\s+)['\"]\\[/m);\n const indent = existingLine ? existingLine[1] : ' ';\n\n // Build insertion text\n const insertion = newSegments\n .map((seg) => `${indent}'${seg}': ${defaultCodecForSegment(seg)},`)\n .join('\\n');\n\n // Insert before the closing brace\n const before = content.slice(0, closeBrace);\n const after = content.slice(closeBrace);\n\n // Add newline if the content before doesn't end with one\n const separator = before.trimEnd().endsWith(',') ? '\\n' : '\\n';\n\n return before + separator + insertion + '\\n' + after;\n}\n\n/**\n * Remove entries from the segmentParams block whose keys match `staleSegments`.\n * Removes the entire line including trailing comma/whitespace.\n */\nfunction removeFromSchema(content: string, staleSegments: string[]): string {\n for (const seg of staleSegments) {\n const escaped = seg.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const keyRe = new RegExp(`^[ \\t]*['\"]${escaped}['\"][ \\t]*:`, 'gm');\n let m: RegExpExecArray | null;\n while ((m = keyRe.exec(content)) !== null) {\n const start = m.index;\n let i = m.index + m[0].length;\n let depth = 0;\n let inString: string | null = null;\n\n while (i < content.length) {\n const ch = content[i];\n if (inString) {\n if (ch === '\\\\') {\n i++;\n } else if (ch === inString) {\n inString = null;\n }\n } else if (ch === '\"' || ch === \"'\" || ch === '`') {\n inString = ch;\n } else if (ch === '(' || ch === '{' || ch === '[') {\n depth++;\n } else if (ch === ')' || ch === '}' || ch === ']') {\n depth--;\n } else if (depth === 0 && ch === ',') {\n i++;\n break;\n } else if (depth === 0 && (ch === '\\n' || ch === '}')) {\n break;\n }\n i++;\n }\n\n // Consume trailing newline\n if (i < content.length && content[i] === '\\n') i++;\n\n content = content.slice(0, start) + content.slice(i);\n keyRe.lastIndex = start;\n }\n }\n return content;\n}\n\nexport interface SchemaSyncResult {\n /** Segments found in the filesystem. */\n filesystemSegments: string[];\n /** Segments already defined in app/schema.ts. */\n existingSegments: string[];\n /** Segments that were added. */\n addedSegments: string[];\n /** Segments that were removed (no longer in filesystem). */\n removedSegments: string[];\n /** Whether a new schema file was created. */\n created: boolean;\n}\n\n/**\n * Run the schema sync: scan filesystem, diff against schema, append missing.\n */\nexport function runSchemaSync(projectRoot: string): SchemaSyncResult {\n const appDir = resolveAppDir(projectRoot);\n\n const filesystemSegments = collectDynamicSegments(appDir);\n\n const schemaPath = findSchemaFile(appDir);\n\n if (filesystemSegments.length === 0 && !schemaPath) {\n return {\n filesystemSegments: [],\n existingSegments: [],\n addedSegments: [],\n removedSegments: [],\n created: false,\n };\n }\n\n if (!schemaPath) {\n // Create new schema file\n const newPath = join(appDir, 'schema.ts');\n writeFileSync(newPath, generateNewSchema(filesystemSegments), 'utf-8');\n return {\n filesystemSegments,\n existingSegments: [],\n addedSegments: filesystemSegments,\n removedSegments: [],\n created: true,\n };\n }\n\n // Read existing schema and diff\n const content = readFileSync(schemaPath, 'utf-8');\n const existingSegments = parseExistingSchemaKeys(content);\n const missing = filesystemSegments.filter((seg) => !existingSegments.includes(seg));\n const stale = existingSegments.filter((seg) => !filesystemSegments.includes(seg));\n\n if (missing.length === 0 && stale.length === 0) {\n return {\n filesystemSegments,\n existingSegments,\n addedSegments: [],\n removedSegments: [],\n created: false,\n };\n }\n\n let updated = content;\n if (stale.length > 0) {\n updated = removeFromSchema(updated, stale);\n }\n if (missing.length > 0) {\n updated = appendToSchema(updated, missing);\n }\n writeFileSync(schemaPath, updated, 'utf-8');\n\n return {\n filesystemSegments,\n existingSegments,\n addedSegments: missing,\n removedSegments: stale,\n created: false,\n };\n}\n"],"mappings":"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"}