@tanstack/start-plugin-core 1.167.35 → 1.169.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/dist/esm/import-protection/adapterUtils.d.ts +27 -0
  2. package/dist/esm/import-protection/adapterUtils.js +31 -0
  3. package/dist/esm/import-protection/adapterUtils.js.map +1 -0
  4. package/dist/esm/import-protection/analysis.d.ts +36 -0
  5. package/dist/esm/import-protection/analysis.js +407 -0
  6. package/dist/esm/import-protection/analysis.js.map +1 -0
  7. package/dist/esm/{import-protection-plugin → import-protection}/ast.js +1 -1
  8. package/dist/esm/import-protection/ast.js.map +1 -0
  9. package/dist/esm/import-protection/constants.d.ts +11 -0
  10. package/dist/esm/{import-protection-plugin → import-protection}/constants.js +7 -2
  11. package/dist/esm/import-protection/constants.js.map +1 -0
  12. package/dist/esm/{import-protection-plugin → import-protection}/defaults.js +1 -1
  13. package/dist/esm/import-protection/defaults.js.map +1 -0
  14. package/dist/esm/{import-protection-plugin → import-protection}/extensionlessAbsoluteIdResolver.js +2 -2
  15. package/dist/esm/import-protection/extensionlessAbsoluteIdResolver.js.map +1 -0
  16. package/dist/esm/{import-protection-plugin → import-protection}/matchers.js +1 -1
  17. package/dist/esm/import-protection/matchers.js.map +1 -0
  18. package/dist/esm/{import-protection-plugin/rewriteDeniedImports.d.ts → import-protection/rewrite.d.ts} +0 -4
  19. package/dist/esm/import-protection/rewrite.js +121 -0
  20. package/dist/esm/import-protection/rewrite.js.map +1 -0
  21. package/dist/esm/{import-protection-plugin → import-protection}/sourceLocation.d.ts +32 -3
  22. package/dist/esm/{import-protection-plugin → import-protection}/sourceLocation.js +65 -10
  23. package/dist/esm/import-protection/sourceLocation.js.map +1 -0
  24. package/dist/esm/{import-protection-plugin → import-protection}/trace.d.ts +0 -1
  25. package/dist/esm/{import-protection-plugin → import-protection}/trace.js +1 -1
  26. package/dist/esm/import-protection/trace.js.map +1 -0
  27. package/dist/esm/{import-protection-plugin → import-protection}/utils.d.ts +18 -1
  28. package/dist/esm/{import-protection-plugin → import-protection}/utils.js +13 -20
  29. package/dist/esm/import-protection/utils.js.map +1 -0
  30. package/dist/esm/import-protection/virtualModules.d.ts +25 -0
  31. package/dist/esm/{import-protection-plugin → import-protection}/virtualModules.js +5 -117
  32. package/dist/esm/import-protection/virtualModules.js.map +1 -0
  33. package/dist/esm/index.d.ts +1 -5
  34. package/dist/esm/index.js +2 -4
  35. package/dist/esm/post-build.d.ts +9 -0
  36. package/dist/esm/post-build.js +37 -0
  37. package/dist/esm/post-build.js.map +1 -0
  38. package/dist/esm/prerender.d.ts +11 -0
  39. package/dist/esm/prerender.js +159 -0
  40. package/dist/esm/prerender.js.map +1 -0
  41. package/dist/esm/rsbuild/dev-server.d.ts +21 -0
  42. package/dist/esm/rsbuild/dev-server.js +76 -0
  43. package/dist/esm/rsbuild/dev-server.js.map +1 -0
  44. package/dist/esm/rsbuild/import-protection.d.ts +10 -0
  45. package/dist/esm/rsbuild/import-protection.js +775 -0
  46. package/dist/esm/rsbuild/import-protection.js.map +1 -0
  47. package/dist/esm/rsbuild/index.d.ts +4 -0
  48. package/dist/esm/rsbuild/index.js +3 -0
  49. package/dist/esm/rsbuild/normalized-client-build.d.ts +18 -0
  50. package/dist/esm/rsbuild/normalized-client-build.js +207 -0
  51. package/dist/esm/rsbuild/normalized-client-build.js.map +1 -0
  52. package/dist/esm/rsbuild/planning.d.ts +52 -0
  53. package/dist/esm/rsbuild/planning.js +108 -0
  54. package/dist/esm/rsbuild/planning.js.map +1 -0
  55. package/dist/esm/rsbuild/plugin.d.ts +4 -0
  56. package/dist/esm/rsbuild/plugin.js +344 -0
  57. package/dist/esm/rsbuild/plugin.js.map +1 -0
  58. package/dist/esm/rsbuild/post-build.d.ts +6 -0
  59. package/dist/esm/rsbuild/post-build.js +57 -0
  60. package/dist/esm/rsbuild/post-build.js.map +1 -0
  61. package/dist/esm/rsbuild/schema.d.ts +3372 -0
  62. package/dist/esm/rsbuild/schema.js +12 -0
  63. package/dist/esm/rsbuild/schema.js.map +1 -0
  64. package/dist/esm/rsbuild/start-compiler-host.d.ts +20 -0
  65. package/dist/esm/rsbuild/start-compiler-host.js +150 -0
  66. package/dist/esm/rsbuild/start-compiler-host.js.map +1 -0
  67. package/dist/esm/rsbuild/start-router-plugin.d.ts +18 -0
  68. package/dist/esm/rsbuild/start-router-plugin.js +63 -0
  69. package/dist/esm/rsbuild/start-router-plugin.js.map +1 -0
  70. package/dist/esm/rsbuild/swc-rsc.d.ts +14 -0
  71. package/dist/esm/rsbuild/swc-rsc.js +93 -0
  72. package/dist/esm/rsbuild/swc-rsc.js.map +1 -0
  73. package/dist/esm/rsbuild/types.d.ts +17 -0
  74. package/dist/esm/rsbuild/types.js +0 -0
  75. package/dist/esm/rsbuild/virtual-modules.d.ts +53 -0
  76. package/dist/esm/rsbuild/virtual-modules.js +287 -0
  77. package/dist/esm/rsbuild/virtual-modules.js.map +1 -0
  78. package/dist/esm/schema.d.ts +43 -43
  79. package/dist/esm/start-compiler/compiler.d.ts +1 -1
  80. package/dist/esm/start-compiler/compiler.js +80 -9
  81. package/dist/esm/start-compiler/compiler.js.map +1 -1
  82. package/dist/esm/start-compiler/handleCreateServerFn.js +9 -0
  83. package/dist/esm/start-compiler/handleCreateServerFn.js.map +1 -1
  84. package/dist/esm/start-compiler/host.js +5 -1
  85. package/dist/esm/start-compiler/host.js.map +1 -1
  86. package/dist/esm/start-compiler/types.d.ts +1 -0
  87. package/dist/esm/utils.d.ts +1 -0
  88. package/dist/esm/utils.js +10 -1
  89. package/dist/esm/utils.js.map +1 -1
  90. package/dist/esm/{import-protection-plugin → vite/import-protection-plugin}/plugin.js +41 -92
  91. package/dist/esm/vite/import-protection-plugin/plugin.js.map +1 -0
  92. package/dist/esm/{import-protection-plugin → vite/import-protection-plugin}/types.d.ts +5 -5
  93. package/dist/esm/vite/import-protection-plugin/virtualModules.d.ts +8 -0
  94. package/dist/esm/vite/import-protection-plugin/virtualModules.js +49 -0
  95. package/dist/esm/vite/import-protection-plugin/virtualModules.js.map +1 -0
  96. package/dist/esm/vite/index.d.ts +5 -0
  97. package/dist/esm/vite/index.js +4 -0
  98. package/dist/esm/vite/plugin.js +1 -1
  99. package/dist/esm/vite/plugin.js.map +1 -1
  100. package/dist/esm/vite/post-server-build.js +14 -32
  101. package/dist/esm/vite/post-server-build.js.map +1 -1
  102. package/dist/esm/vite/prerender.d.ts +2 -2
  103. package/dist/esm/vite/prerender.js +17 -147
  104. package/dist/esm/vite/prerender.js.map +1 -1
  105. package/dist/esm/vite/schema.d.ts +23 -23
  106. package/dist/esm/vite/start-compiler-plugin/hot-update.d.ts +2 -0
  107. package/dist/esm/vite/start-compiler-plugin/hot-update.js +16 -0
  108. package/dist/esm/vite/start-compiler-plugin/hot-update.js.map +1 -0
  109. package/dist/esm/vite/start-compiler-plugin/module-specifier.js +9 -4
  110. package/dist/esm/vite/start-compiler-plugin/module-specifier.js.map +1 -1
  111. package/dist/esm/vite/start-compiler-plugin/plugin.js +86 -13
  112. package/dist/esm/vite/start-compiler-plugin/plugin.js.map +1 -1
  113. package/package.json +32 -4
  114. package/src/import-protection/INTERNALS.md +266 -0
  115. package/src/import-protection/adapterUtils.ts +94 -0
  116. package/src/import-protection/analysis.ts +853 -0
  117. package/src/{import-protection-plugin → import-protection}/constants.ts +7 -0
  118. package/src/import-protection/rewrite.ts +229 -0
  119. package/src/{import-protection-plugin → import-protection}/sourceLocation.ts +125 -9
  120. package/src/{import-protection-plugin → import-protection}/trace.ts +0 -1
  121. package/src/{import-protection-plugin → import-protection}/utils.ts +36 -21
  122. package/src/{import-protection-plugin → import-protection}/virtualModules.ts +30 -177
  123. package/src/index.ts +1 -8
  124. package/src/post-build.ts +64 -0
  125. package/src/prerender.ts +292 -0
  126. package/src/rsbuild/INTERNALS-import-protection.md +169 -0
  127. package/src/rsbuild/dev-server.ts +129 -0
  128. package/src/rsbuild/import-protection.ts +1599 -0
  129. package/src/rsbuild/index.ts +4 -0
  130. package/src/rsbuild/normalized-client-build.ts +346 -0
  131. package/src/rsbuild/planning.ts +234 -0
  132. package/src/rsbuild/plugin.ts +754 -0
  133. package/src/rsbuild/post-build.ts +96 -0
  134. package/src/rsbuild/schema.ts +31 -0
  135. package/src/rsbuild/start-compiler-host.ts +250 -0
  136. package/src/rsbuild/start-router-plugin.ts +86 -0
  137. package/src/rsbuild/swc-rsc.ts +166 -0
  138. package/src/rsbuild/types.ts +20 -0
  139. package/src/rsbuild/virtual-modules.ts +565 -0
  140. package/src/start-compiler/compiler.ts +153 -19
  141. package/src/start-compiler/handleCreateServerFn.ts +18 -0
  142. package/src/start-compiler/types.ts +1 -0
  143. package/src/utils.ts +14 -0
  144. package/src/vite/import-protection-plugin/INTERNALS.md +187 -0
  145. package/src/{import-protection-plugin → vite/import-protection-plugin}/plugin.ts +73 -158
  146. package/src/{import-protection-plugin → vite/import-protection-plugin}/types.ts +5 -5
  147. package/src/vite/import-protection-plugin/virtualModules.ts +122 -0
  148. package/src/vite/index.ts +8 -0
  149. package/src/vite/plugin.ts +1 -1
  150. package/src/vite/post-server-build.ts +14 -57
  151. package/src/vite/prerender.ts +19 -260
  152. package/src/vite/start-compiler-plugin/hot-update.ts +24 -0
  153. package/src/vite/start-compiler-plugin/module-specifier.ts +15 -5
  154. package/src/vite/start-compiler-plugin/plugin.ts +193 -18
  155. package/dist/esm/import-protection-plugin/ast.js.map +0 -1
  156. package/dist/esm/import-protection-plugin/constants.d.ts +0 -6
  157. package/dist/esm/import-protection-plugin/constants.js.map +0 -1
  158. package/dist/esm/import-protection-plugin/defaults.js.map +0 -1
  159. package/dist/esm/import-protection-plugin/extensionlessAbsoluteIdResolver.js.map +0 -1
  160. package/dist/esm/import-protection-plugin/matchers.js.map +0 -1
  161. package/dist/esm/import-protection-plugin/plugin.js.map +0 -1
  162. package/dist/esm/import-protection-plugin/postCompileUsage.d.ts +0 -13
  163. package/dist/esm/import-protection-plugin/postCompileUsage.js +0 -63
  164. package/dist/esm/import-protection-plugin/postCompileUsage.js.map +0 -1
  165. package/dist/esm/import-protection-plugin/rewriteDeniedImports.js +0 -205
  166. package/dist/esm/import-protection-plugin/rewriteDeniedImports.js.map +0 -1
  167. package/dist/esm/import-protection-plugin/sourceLocation.js.map +0 -1
  168. package/dist/esm/import-protection-plugin/trace.js.map +0 -1
  169. package/dist/esm/import-protection-plugin/utils.js.map +0 -1
  170. package/dist/esm/import-protection-plugin/virtualModules.d.ts +0 -78
  171. package/dist/esm/import-protection-plugin/virtualModules.js.map +0 -1
  172. package/dist/esm/start-compiler/load-module.d.ts +0 -14
  173. package/dist/esm/start-compiler/load-module.js +0 -18
  174. package/dist/esm/start-compiler/load-module.js.map +0 -1
  175. package/src/import-protection-plugin/INTERNALS.md +0 -700
  176. package/src/import-protection-plugin/postCompileUsage.ts +0 -100
  177. package/src/import-protection-plugin/rewriteDeniedImports.ts +0 -379
  178. package/src/start-compiler/load-module.ts +0 -31
  179. /package/dist/esm/{import-protection-plugin → import-protection}/ast.d.ts +0 -0
  180. /package/dist/esm/{import-protection-plugin → import-protection}/defaults.d.ts +0 -0
  181. /package/dist/esm/{import-protection-plugin → import-protection}/extensionlessAbsoluteIdResolver.d.ts +0 -0
  182. /package/dist/esm/{import-protection-plugin → import-protection}/matchers.d.ts +0 -0
  183. /package/dist/esm/{import-protection-plugin → vite/import-protection-plugin}/plugin.d.ts +0 -0
  184. /package/src/{import-protection-plugin → import-protection}/ast.ts +0 -0
  185. /package/src/{import-protection-plugin → import-protection}/defaults.ts +0 -0
  186. /package/src/{import-protection-plugin → import-protection}/extensionlessAbsoluteIdResolver.ts +0 -0
  187. /package/src/{import-protection-plugin → import-protection}/matchers.ts +0 -0
@@ -1,4 +1,4 @@
1
- //#region src/import-protection-plugin/defaults.ts
1
+ //#region src/import-protection/defaults.ts
2
2
  var frameworks = [
3
3
  "react",
4
4
  "solid",
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaults.js","names":[],"sources":["../../../src/import-protection/defaults.ts"],"sourcesContent":["import type { ImportProtectionEnvRules } from '../schema'\nimport type { Pattern } from './utils'\n\nexport interface DefaultImportProtectionRules {\n client: Required<ImportProtectionEnvRules>\n server: Required<ImportProtectionEnvRules>\n}\n\nconst frameworks = ['react', 'solid', 'vue'] as const\n\n/**\n * Returns the default import protection rules.\n *\n * All three framework variants are always included so that, e.g., a React\n * project also denies `@tanstack/solid-start/server` imports.\n */\nexport function getDefaultImportProtectionRules(): DefaultImportProtectionRules {\n const clientSpecifiers: Array<Pattern> = frameworks.map(\n (fw) => `@tanstack/${fw}-start/server`,\n )\n\n return {\n client: {\n specifiers: clientSpecifiers,\n files: ['**/*.server.*'],\n excludeFiles: ['**/node_modules/**'],\n },\n server: {\n specifiers: [],\n files: ['**/*.client.*'],\n excludeFiles: ['**/node_modules/**'],\n },\n }\n}\n\n/**\n * Marker module specifiers that restrict a file to a specific environment.\n */\nexport function getMarkerSpecifiers(): {\n serverOnly: Array<string>\n clientOnly: Array<string>\n} {\n return {\n serverOnly: frameworks.map((fw) => `@tanstack/${fw}-start/server-only`),\n clientOnly: frameworks.map((fw) => `@tanstack/${fw}-start/client-only`),\n }\n}\n"],"mappings":";AAQA,IAAM,aAAa;CAAC;CAAS;CAAS;CAAM;;;;;;;AAQ5C,SAAgB,kCAAgE;AAK9E,QAAO;EACL,QAAQ;GACN,YANqC,WAAW,KACjD,OAAO,aAAa,GAAG,eACzB;GAKG,OAAO,CAAC,gBAAgB;GACxB,cAAc,CAAC,qBAAqB;GACrC;EACD,QAAQ;GACN,YAAY,EAAE;GACd,OAAO,CAAC,gBAAgB;GACxB,cAAc,CAAC,qBAAqB;GACrC;EACF;;;;;AAMH,SAAgB,sBAGd;AACA,QAAO;EACL,YAAY,WAAW,KAAK,OAAO,aAAa,GAAG,oBAAoB;EACvE,YAAY,WAAW,KAAK,OAAO,aAAa,GAAG,oBAAoB;EACxE"}
@@ -1,8 +1,8 @@
1
1
  import { KNOWN_SOURCE_EXTENSIONS } from "./constants.js";
2
2
  import { normalizeFilePath } from "./utils.js";
3
- import { resolveModulePath } from "exsolve";
4
3
  import { basename, dirname, extname, isAbsolute } from "node:path";
5
- //#region src/import-protection-plugin/extensionlessAbsoluteIdResolver.ts
4
+ import { resolveModulePath } from "exsolve";
5
+ //#region src/import-protection/extensionlessAbsoluteIdResolver.ts
6
6
  var FILE_RESOLUTION_EXTENSIONS = [...KNOWN_SOURCE_EXTENSIONS];
7
7
  /**
8
8
  * Canonicalize extensionless absolute IDs like `/src/foo.server` to the
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extensionlessAbsoluteIdResolver.js","names":[],"sources":["../../../src/import-protection/extensionlessAbsoluteIdResolver.ts"],"sourcesContent":["import { basename, dirname, extname, isAbsolute } from 'node:path'\nimport { resolveModulePath } from 'exsolve'\n\nimport { KNOWN_SOURCE_EXTENSIONS } from './constants'\nimport { normalizeFilePath } from './utils'\n\nconst FILE_RESOLUTION_EXTENSIONS = [...KNOWN_SOURCE_EXTENSIONS]\n\ntype DepKey = `file:${string}` | `dir:${string}`\n\n/**\n * Canonicalize extensionless absolute IDs like `/src/foo.server` to the\n * physical file when possible.\n *\n * Keeps a small cache plus a reverse index so we can invalidate on HMR\n * updates without clearing the whole map.\n */\nexport class ExtensionlessAbsoluteIdResolver {\n private entries = new Map<string, { value: string; deps: Set<DepKey> }>()\n private keysByDep = new Map<DepKey, Set<string>>()\n\n clear(): void {\n this.entries.clear()\n this.keysByDep.clear()\n }\n\n /**\n * Invalidate any cached entries that might be affected by changes to `id`.\n * We invalidate both the file and its containing directory.\n */\n invalidateByFile(id: string): void {\n const file = normalizeFilePath(id)\n this.invalidateDep(`file:${file}`)\n if (isAbsolute(file)) {\n this.invalidateDep(`dir:${dirname(file)}`)\n }\n }\n\n resolve(id: string): string {\n const key = normalizeFilePath(id)\n const cached = this.entries.get(key)\n if (cached) return cached.value\n\n let result = key\n let resolvedPhysical: string | undefined\n\n if (isAbsolute(key)) {\n const ext = extname(key)\n if (!FILE_RESOLUTION_EXTENSIONS.includes(ext)) {\n const resolved = resolveModulePath(`./${basename(key)}`, {\n from: dirname(key),\n extensions: FILE_RESOLUTION_EXTENSIONS,\n try: true,\n })\n if (resolved) {\n resolvedPhysical = resolved\n result = normalizeFilePath(resolved)\n }\n }\n }\n\n const resolvedFile = resolvedPhysical\n ? normalizeFilePath(resolvedPhysical)\n : undefined\n\n const deps = this.buildDepsForKey(key, resolvedFile)\n this.entries.set(key, { value: result, deps })\n this.indexDeps(key, deps)\n return result\n }\n\n private invalidateDep(dep: DepKey): void {\n const keys = this.keysByDep.get(dep)\n if (!keys) return\n\n // Copy because deleting keys mutates indexes.\n for (const key of Array.from(keys)) {\n this.deleteKey(key)\n }\n }\n\n private buildDepsForKey(key: string, resolvedFile: string | undefined) {\n const deps = new Set<DepKey>()\n deps.add(`file:${key}`)\n\n if (isAbsolute(key)) {\n deps.add(`dir:${dirname(key)}`)\n }\n if (resolvedFile) {\n deps.add(`file:${resolvedFile}`)\n }\n\n return deps\n }\n\n private indexDeps(key: string, deps: Set<DepKey>): void {\n for (const dep of deps) {\n let keys = this.keysByDep.get(dep)\n if (!keys) {\n keys = new Set<string>()\n this.keysByDep.set(dep, keys)\n }\n keys.add(key)\n }\n }\n\n private deleteKey(key: string): void {\n const entry = this.entries.get(key)\n this.entries.delete(key)\n if (!entry) return\n\n for (const dep of entry.deps) {\n const keys = this.keysByDep.get(dep)\n if (!keys) continue\n keys.delete(key)\n if (keys.size === 0) {\n this.keysByDep.delete(dep)\n }\n }\n }\n}\n"],"mappings":";;;;;AAMA,IAAM,6BAA6B,CAAC,GAAG,wBAAwB;;;;;;;;AAW/D,IAAa,kCAAb,MAA6C;CAC3C,0BAAkB,IAAI,KAAmD;CACzE,4BAAoB,IAAI,KAA0B;CAElD,QAAc;AACZ,OAAK,QAAQ,OAAO;AACpB,OAAK,UAAU,OAAO;;;;;;CAOxB,iBAAiB,IAAkB;EACjC,MAAM,OAAO,kBAAkB,GAAG;AAClC,OAAK,cAAc,QAAQ,OAAO;AAClC,MAAI,WAAW,KAAK,CAClB,MAAK,cAAc,OAAO,QAAQ,KAAK,GAAG;;CAI9C,QAAQ,IAAoB;EAC1B,MAAM,MAAM,kBAAkB,GAAG;EACjC,MAAM,SAAS,KAAK,QAAQ,IAAI,IAAI;AACpC,MAAI,OAAQ,QAAO,OAAO;EAE1B,IAAI,SAAS;EACb,IAAI;AAEJ,MAAI,WAAW,IAAI,EAAE;GACnB,MAAM,MAAM,QAAQ,IAAI;AACxB,OAAI,CAAC,2BAA2B,SAAS,IAAI,EAAE;IAC7C,MAAM,WAAW,kBAAkB,KAAK,SAAS,IAAI,IAAI;KACvD,MAAM,QAAQ,IAAI;KAClB,YAAY;KACZ,KAAK;KACN,CAAC;AACF,QAAI,UAAU;AACZ,wBAAmB;AACnB,cAAS,kBAAkB,SAAS;;;;EAK1C,MAAM,eAAe,mBACjB,kBAAkB,iBAAiB,GACnC,KAAA;EAEJ,MAAM,OAAO,KAAK,gBAAgB,KAAK,aAAa;AACpD,OAAK,QAAQ,IAAI,KAAK;GAAE,OAAO;GAAQ;GAAM,CAAC;AAC9C,OAAK,UAAU,KAAK,KAAK;AACzB,SAAO;;CAGT,cAAsB,KAAmB;EACvC,MAAM,OAAO,KAAK,UAAU,IAAI,IAAI;AACpC,MAAI,CAAC,KAAM;AAGX,OAAK,MAAM,OAAO,MAAM,KAAK,KAAK,CAChC,MAAK,UAAU,IAAI;;CAIvB,gBAAwB,KAAa,cAAkC;EACrE,MAAM,uBAAO,IAAI,KAAa;AAC9B,OAAK,IAAI,QAAQ,MAAM;AAEvB,MAAI,WAAW,IAAI,CACjB,MAAK,IAAI,OAAO,QAAQ,IAAI,GAAG;AAEjC,MAAI,aACF,MAAK,IAAI,QAAQ,eAAe;AAGlC,SAAO;;CAGT,UAAkB,KAAa,MAAyB;AACtD,OAAK,MAAM,OAAO,MAAM;GACtB,IAAI,OAAO,KAAK,UAAU,IAAI,IAAI;AAClC,OAAI,CAAC,MAAM;AACT,2BAAO,IAAI,KAAa;AACxB,SAAK,UAAU,IAAI,KAAK,KAAK;;AAE/B,QAAK,IAAI,IAAI;;;CAIjB,UAAkB,KAAmB;EACnC,MAAM,QAAQ,KAAK,QAAQ,IAAI,IAAI;AACnC,OAAK,QAAQ,OAAO,IAAI;AACxB,MAAI,CAAC,MAAO;AAEZ,OAAK,MAAM,OAAO,MAAM,MAAM;GAC5B,MAAM,OAAO,KAAK,UAAU,IAAI,IAAI;AACpC,OAAI,CAAC,KAAM;AACX,QAAK,OAAO,IAAI;AAChB,OAAI,KAAK,SAAS,EAChB,MAAK,UAAU,OAAO,IAAI"}
@@ -1,5 +1,5 @@
1
1
  import picomatch from "picomatch";
2
- //#region src/import-protection-plugin/matchers.ts
2
+ //#region src/import-protection/matchers.ts
3
3
  /**
4
4
  * Compile a Pattern (string glob or RegExp) into a fast test function.
5
5
  * String patterns use picomatch for full glob support (**, *, ?, braces, etc.).
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matchers.js","names":[],"sources":["../../../src/import-protection/matchers.ts"],"sourcesContent":["import picomatch from 'picomatch'\n\nimport type { Pattern } from './utils'\n\nexport interface CompiledMatcher {\n pattern: Pattern\n test: (value: string) => boolean\n}\n\n/**\n * Compile a Pattern (string glob or RegExp) into a fast test function.\n * String patterns use picomatch for full glob support (**, *, ?, braces, etc.).\n * RegExp patterns are used as-is.\n */\nexport function compileMatcher(pattern: Pattern): CompiledMatcher {\n if (pattern instanceof RegExp) {\n // RegExp with `g` or `y` flags are stateful because `.test()` mutates\n // `lastIndex`. Reset it to keep matcher evaluation deterministic.\n return {\n pattern,\n test: (value: string) => {\n pattern.lastIndex = 0\n return pattern.test(value)\n },\n }\n }\n\n const isMatch = picomatch(pattern, { dot: true })\n return { pattern, test: isMatch }\n}\n\nexport function compileMatchers(\n patterns: Array<Pattern>,\n): Array<CompiledMatcher> {\n return patterns.map(compileMatcher)\n}\n\nexport function matchesAny(\n value: string,\n matchers: Array<CompiledMatcher>,\n): CompiledMatcher | undefined {\n for (const matcher of matchers) {\n if (matcher.test(value)) {\n return matcher\n }\n }\n return undefined\n}\n"],"mappings":";;;;;;;AAcA,SAAgB,eAAe,SAAmC;AAChE,KAAI,mBAAmB,OAGrB,QAAO;EACL;EACA,OAAO,UAAkB;AACvB,WAAQ,YAAY;AACpB,UAAO,QAAQ,KAAK,MAAM;;EAE7B;AAIH,QAAO;EAAE;EAAS,MADF,UAAU,SAAS,EAAE,KAAK,MAAM,CAAC;EAChB;;AAGnC,SAAgB,gBACd,UACwB;AACxB,QAAO,SAAS,IAAI,eAAe;;AAGrC,SAAgB,WACd,OACA,UAC6B;AAC7B,MAAK,MAAM,WAAW,SACpB,KAAI,QAAQ,KAAK,MAAM,CACrB,QAAO"}
@@ -1,8 +1,4 @@
1
1
  import { SourceMapLike } from './sourceLocation.js';
2
- export declare function isValidExportName(name: string): boolean;
3
- export declare function collectMockExportNamesBySource(code: string): Map<string, Array<string>>;
4
- /** Collect all valid named export identifiers from the given code. */
5
- export declare function collectNamedExports(code: string): Array<string>;
6
2
  /**
7
3
  * Rewrite static imports/re-exports from denied sources using Babel AST transforms.
8
4
  *
@@ -0,0 +1,121 @@
1
+ import { MOCK_MODULE_ID } from "./constants.js";
2
+ import { parseImportProtectionAst } from "./ast.js";
3
+ import * as t from "@babel/types";
4
+ import { generateFromAst } from "@tanstack/router-utils";
5
+ //#region src/import-protection/rewrite.ts
6
+ function getModuleExportName(node) {
7
+ return t.isIdentifier(node) ? node.name : node.value;
8
+ }
9
+ function toMemberExpressionProperty(name) {
10
+ return t.isValidIdentifier(name) ? {
11
+ property: t.identifier(name),
12
+ computed: false
13
+ } : {
14
+ property: t.stringLiteral(name),
15
+ computed: true
16
+ };
17
+ }
18
+ function toModuleExportNameNode(name) {
19
+ return t.isValidIdentifier(name) ? t.identifier(name) : t.stringLiteral(name);
20
+ }
21
+ function createInternalReexportVarName(localName, mockIndex, usedNames) {
22
+ const baseName = t.isValidIdentifier(localName) ? `__tss_reexport_${localName}` : `__tss_reexport_${mockIndex}`;
23
+ let candidate = baseName;
24
+ let suffix = 0;
25
+ while (usedNames.has(candidate)) {
26
+ suffix++;
27
+ candidate = `${baseName}_${suffix}`;
28
+ }
29
+ usedNames.add(candidate);
30
+ return candidate;
31
+ }
32
+ /**
33
+ * Rewrite static imports/re-exports from denied sources using Babel AST transforms.
34
+ *
35
+ * Transforms:
36
+ * import { a as b, c } from 'denied'
37
+ * Into:
38
+ * import __tss_deny_0 from 'tanstack-start-import-protection:mock'
39
+ * const b = __tss_deny_0.a
40
+ * const c = __tss_deny_0.c
41
+ *
42
+ * Also handles:
43
+ * import def from 'denied' -> import def from mock
44
+ * import * as ns from 'denied' -> import ns from mock
45
+ * export { x } from 'denied' -> export const x = mock.x
46
+ * export * from 'denied' -> removed
47
+ * export { x as y } from 'denied' -> export const y = mock.x
48
+ */
49
+ function rewriteDeniedImports(code, id, deniedSources, getMockModuleId = () => MOCK_MODULE_ID) {
50
+ return rewriteDeniedImportsFromAst(parseImportProtectionAst(code), id, deniedSources, getMockModuleId);
51
+ }
52
+ function rewriteDeniedImportsFromAst(ast, id, deniedSources, getMockModuleId = () => MOCK_MODULE_ID) {
53
+ let modified = false;
54
+ let mockCounter = 0;
55
+ for (let i = ast.program.body.length - 1; i >= 0; i--) {
56
+ const node = ast.program.body[i];
57
+ if (t.isImportDeclaration(node)) {
58
+ if (node.importKind === "type") continue;
59
+ if (!deniedSources.has(node.source.value)) continue;
60
+ const mockVar = `__tss_deny_${mockCounter++}`;
61
+ const replacements = [];
62
+ replacements.push(t.importDeclaration([t.importDefaultSpecifier(t.identifier(mockVar))], t.stringLiteral(getMockModuleId(node.source.value))));
63
+ for (const specifier of node.specifiers) if (t.isImportDefaultSpecifier(specifier) || t.isImportNamespaceSpecifier(specifier)) replacements.push(t.variableDeclaration("const", [t.variableDeclarator(t.identifier(specifier.local.name), t.identifier(mockVar))]));
64
+ else if (t.isImportSpecifier(specifier)) {
65
+ if (specifier.importKind === "type") continue;
66
+ const memberProperty = toMemberExpressionProperty(getModuleExportName(specifier.imported));
67
+ replacements.push(t.variableDeclaration("const", [t.variableDeclarator(t.identifier(specifier.local.name), t.memberExpression(t.identifier(mockVar), memberProperty.property, memberProperty.computed))]));
68
+ }
69
+ ast.program.body.splice(i, 1, ...replacements);
70
+ modified = true;
71
+ continue;
72
+ }
73
+ if (t.isExportNamedDeclaration(node) && node.source) {
74
+ if (node.exportKind === "type") continue;
75
+ if (!deniedSources.has(node.source.value)) continue;
76
+ const mockIndex = mockCounter++;
77
+ const mockVar = `__tss_deny_${mockIndex}`;
78
+ const replacements = [];
79
+ replacements.push(t.importDeclaration([t.importDefaultSpecifier(t.identifier(mockVar))], t.stringLiteral(getMockModuleId(node.source.value))));
80
+ const usedInternalVars = /* @__PURE__ */ new Set();
81
+ const exportSpecifiers = [];
82
+ for (const specifier of node.specifiers) if (t.isExportSpecifier(specifier)) {
83
+ if (specifier.exportKind === "type") continue;
84
+ const localName = getModuleExportName(specifier.local);
85
+ const exportedName = getModuleExportName(specifier.exported);
86
+ const memberProperty = toMemberExpressionProperty(localName);
87
+ const internalVar = createInternalReexportVarName(localName, mockIndex, usedInternalVars);
88
+ replacements.push(t.variableDeclaration("const", [t.variableDeclarator(t.identifier(internalVar), t.memberExpression(t.identifier(mockVar), memberProperty.property, memberProperty.computed))]));
89
+ exportSpecifiers.push({
90
+ localName: internalVar,
91
+ exportedName
92
+ });
93
+ }
94
+ if (exportSpecifiers.length > 0) replacements.push(t.exportNamedDeclaration(null, exportSpecifiers.map((s) => t.exportSpecifier(t.identifier(s.localName), toModuleExportNameNode(s.exportedName)))));
95
+ ast.program.body.splice(i, 1, ...replacements);
96
+ modified = true;
97
+ continue;
98
+ }
99
+ if (t.isExportAllDeclaration(node)) {
100
+ if (node.exportKind === "type") continue;
101
+ if (!deniedSources.has(node.source.value)) continue;
102
+ ast.program.body.splice(i, 1);
103
+ modified = true;
104
+ continue;
105
+ }
106
+ }
107
+ if (!modified) return void 0;
108
+ const result = generateFromAst(ast, {
109
+ sourceMaps: true,
110
+ sourceFileName: id,
111
+ filename: id
112
+ });
113
+ return {
114
+ code: result.code,
115
+ ...result.map ? { map: result.map } : {}
116
+ };
117
+ }
118
+ //#endregion
119
+ export { rewriteDeniedImports };
120
+
121
+ //# sourceMappingURL=rewrite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rewrite.js","names":[],"sources":["../../../src/import-protection/rewrite.ts"],"sourcesContent":["import * as t from '@babel/types'\nimport { generateFromAst } from '@tanstack/router-utils'\n\nimport { parseImportProtectionAst } from './ast'\nimport { MOCK_MODULE_ID } from './constants'\nimport type { ParsedAst } from './ast'\nimport type { SourceMapLike } from './sourceLocation'\n\nfunction getModuleExportName(node: t.Identifier | t.StringLiteral): string {\n return t.isIdentifier(node) ? node.name : node.value\n}\n\nfunction toMemberExpressionProperty(name: string): {\n property: t.Identifier | t.StringLiteral\n computed: boolean\n} {\n return t.isValidIdentifier(name)\n ? { property: t.identifier(name), computed: false }\n : { property: t.stringLiteral(name), computed: true }\n}\n\nfunction toModuleExportNameNode(name: string): t.Identifier | t.StringLiteral {\n return t.isValidIdentifier(name) ? t.identifier(name) : t.stringLiteral(name)\n}\n\nfunction createInternalReexportVarName(\n localName: string,\n mockIndex: number,\n usedNames: Set<string>,\n): string {\n const baseName = t.isValidIdentifier(localName)\n ? `__tss_reexport_${localName}`\n : `__tss_reexport_${mockIndex}`\n\n let candidate = baseName\n let suffix = 0\n while (usedNames.has(candidate)) {\n suffix++\n candidate = `${baseName}_${suffix}`\n }\n\n usedNames.add(candidate)\n return candidate\n}\n\n/**\n * Rewrite static imports/re-exports from denied sources using Babel AST transforms.\n *\n * Transforms:\n * import { a as b, c } from 'denied'\n * Into:\n * import __tss_deny_0 from 'tanstack-start-import-protection:mock'\n * const b = __tss_deny_0.a\n * const c = __tss_deny_0.c\n *\n * Also handles:\n * import def from 'denied' -> import def from mock\n * import * as ns from 'denied' -> import ns from mock\n * export { x } from 'denied' -> export const x = mock.x\n * export * from 'denied' -> removed\n * export { x as y } from 'denied' -> export const y = mock.x\n */\nexport function rewriteDeniedImports(\n code: string,\n id: string,\n deniedSources: Set<string>,\n getMockModuleId: (source: string) => string = () => MOCK_MODULE_ID,\n): { code: string; map?: SourceMapLike } | undefined {\n return rewriteDeniedImportsFromAst(\n parseImportProtectionAst(code),\n id,\n deniedSources,\n getMockModuleId,\n )\n}\n\nfunction rewriteDeniedImportsFromAst(\n ast: ParsedAst,\n id: string,\n deniedSources: Set<string>,\n getMockModuleId: (source: string) => string = () => MOCK_MODULE_ID,\n): { code: string; map?: SourceMapLike } | undefined {\n let modified = false\n let mockCounter = 0\n\n // Walk program body in reverse so splice indices stay valid\n for (let i = ast.program.body.length - 1; i >= 0; i--) {\n const node = ast.program.body[i]!\n\n if (t.isImportDeclaration(node)) {\n if (node.importKind === 'type') continue\n if (!deniedSources.has(node.source.value)) continue\n\n const mockVar = `__tss_deny_${mockCounter++}`\n const replacements: Array<t.Statement> = []\n\n replacements.push(\n t.importDeclaration(\n [t.importDefaultSpecifier(t.identifier(mockVar))],\n t.stringLiteral(getMockModuleId(node.source.value)),\n ),\n )\n\n for (const specifier of node.specifiers) {\n if (\n t.isImportDefaultSpecifier(specifier) ||\n t.isImportNamespaceSpecifier(specifier)\n ) {\n replacements.push(\n t.variableDeclaration('const', [\n t.variableDeclarator(\n t.identifier(specifier.local.name),\n t.identifier(mockVar),\n ),\n ]),\n )\n } else if (t.isImportSpecifier(specifier)) {\n if (specifier.importKind === 'type') continue\n const importedName = getModuleExportName(specifier.imported)\n const memberProperty = toMemberExpressionProperty(importedName)\n replacements.push(\n t.variableDeclaration('const', [\n t.variableDeclarator(\n t.identifier(specifier.local.name),\n t.memberExpression(\n t.identifier(mockVar),\n memberProperty.property,\n memberProperty.computed,\n ),\n ),\n ]),\n )\n }\n }\n\n ast.program.body.splice(i, 1, ...replacements)\n modified = true\n continue\n }\n\n if (t.isExportNamedDeclaration(node) && node.source) {\n if (node.exportKind === 'type') continue\n if (!deniedSources.has(node.source.value)) continue\n\n const mockIndex = mockCounter++\n const mockVar = `__tss_deny_${mockIndex}`\n const replacements: Array<t.Statement> = []\n\n replacements.push(\n t.importDeclaration(\n [t.importDefaultSpecifier(t.identifier(mockVar))],\n t.stringLiteral(getMockModuleId(node.source.value)),\n ),\n )\n const usedInternalVars = new Set<string>()\n const exportSpecifiers: Array<{\n localName: string\n exportedName: string\n }> = []\n for (const specifier of node.specifiers) {\n if (t.isExportSpecifier(specifier)) {\n if (specifier.exportKind === 'type') continue\n const localName = getModuleExportName(specifier.local)\n const exportedName = getModuleExportName(specifier.exported)\n const memberProperty = toMemberExpressionProperty(localName)\n\n const internalVar = createInternalReexportVarName(\n localName,\n mockIndex,\n usedInternalVars,\n )\n replacements.push(\n t.variableDeclaration('const', [\n t.variableDeclarator(\n t.identifier(internalVar),\n t.memberExpression(\n t.identifier(mockVar),\n memberProperty.property,\n memberProperty.computed,\n ),\n ),\n ]),\n )\n exportSpecifiers.push({ localName: internalVar, exportedName })\n }\n }\n\n if (exportSpecifiers.length > 0) {\n replacements.push(\n t.exportNamedDeclaration(\n null,\n exportSpecifiers.map((s) =>\n t.exportSpecifier(\n t.identifier(s.localName),\n toModuleExportNameNode(s.exportedName),\n ),\n ),\n ),\n )\n }\n\n ast.program.body.splice(i, 1, ...replacements)\n modified = true\n continue\n }\n\n if (t.isExportAllDeclaration(node)) {\n if (node.exportKind === 'type') continue\n if (!deniedSources.has(node.source.value)) continue\n\n ast.program.body.splice(i, 1)\n modified = true\n continue\n }\n }\n\n if (!modified) return undefined\n\n const result = generateFromAst(ast, {\n sourceMaps: true,\n sourceFileName: id,\n filename: id,\n })\n\n return {\n code: result.code,\n ...(result.map ? { map: result.map as SourceMapLike } : {}),\n }\n}\n"],"mappings":"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"}
@@ -1,3 +1,5 @@
1
+ import { ImportAnalysis } from './analysis.js';
2
+ import { ParsedAst } from './ast.js';
1
3
  import { Loc } from './trace.js';
2
4
  /**
3
5
  * Minimal source-map shape used throughout the import-protection plugin.
@@ -15,8 +17,11 @@ export interface TransformResult {
15
17
  code: string;
16
18
  map: SourceMapLike | undefined;
17
19
  originalCode: string | undefined;
20
+ originalResult?: TransformResult;
18
21
  /** Precomputed line index for `code` (index → line/col). */
19
22
  lineIndex?: LineIndex;
23
+ parsedAst?: ParsedAst;
24
+ analysis?: ImportAnalysis;
20
25
  }
21
26
  /**
22
27
  * Provides the transformed code and composed sourcemap for a module.
@@ -27,10 +32,26 @@ export interface TransformResult {
27
32
  export interface TransformResultProvider {
28
33
  getTransformResult: (id: string) => TransformResult | undefined;
29
34
  }
35
+ export interface ImportSpecifierLocationIndex {
36
+ find: FindImportSpecifierLocationIndex;
37
+ }
30
38
  export type LineIndex = {
31
39
  offsets: Array<number>;
32
40
  };
33
41
  export declare function buildLineIndex(code: string): LineIndex;
42
+ export declare function indexToLineColumn(lineIndex: LineIndex, idx: number): {
43
+ line: number;
44
+ column: number;
45
+ };
46
+ export declare function normalizeSourceMap(map: SourceMapLike | null | undefined): {
47
+ version: number;
48
+ file: string;
49
+ sourceRoot?: string;
50
+ sources: Array<string>;
51
+ names: Array<string>;
52
+ sourcesContent?: Array<string>;
53
+ mappings: string;
54
+ } | undefined;
34
55
  /**
35
56
  * Pick the most-likely original source text for `importerFile` from
36
57
  * a sourcemap that may contain multiple sources.
@@ -55,19 +76,27 @@ export declare class ImportLocCache {
55
76
  /** Remove all cache entries where the importer matches `file`. */
56
77
  deleteByFile(file: string): void;
57
78
  }
58
- export type FindImportSpecifierIndex = (code: string, source: string) => number;
79
+ export type FindImportSpecifierLocationIndex = (result: TransformResult, source: string) => number;
80
+ export declare function getOrCreateOriginalTransformResult(result: TransformResult): TransformResult | undefined;
81
+ export declare function createImportSpecifierLocationIndex(): ImportSpecifierLocationIndex;
59
82
  /**
60
83
  * Find the location of an import statement in a transformed module
61
84
  * by searching the post-transform code and mapping back via sourcemap.
62
85
  * Results are cached in `importLocCache`.
63
86
  */
64
- export declare function findImportStatementLocationFromTransformed(provider: TransformResultProvider, importerId: string, source: string, importLocCache: ImportLocCache, findImportSpecifierIndex: FindImportSpecifierIndex): Promise<Loc | undefined>;
87
+ export declare function findImportStatementLocationFromTransformed(provider: TransformResultProvider, importerId: string, source: string, importLocCache: ImportLocCache, findImportSpecifierLocationIndex: FindImportSpecifierLocationIndex): Promise<Loc | undefined>;
65
88
  /**
66
89
  * Find the first post-compile usage location for a denied import specifier.
67
90
  * Best-effort: searches transformed code for non-import uses of imported
68
91
  * bindings and maps back to original source via sourcemap.
69
92
  */
70
93
  export declare function findPostCompileUsageLocation(provider: TransformResultProvider, importerId: string, source: string): Promise<Loc | undefined>;
94
+ /**
95
+ * Best-effort original-source usage lookup for cases where a later transform
96
+ * removes or rewrites the import from emitted code but preserves the original
97
+ * source in `sourcesContent`.
98
+ */
99
+ export declare function findOriginalUsageLocation(provider: TransformResultProvider, importerId: string, source: string, envType?: 'client' | 'server'): Loc | undefined;
71
100
  /**
72
101
  * Annotate each trace hop with the location of the import that created the
73
102
  * edge (file:line:col). Skips steps that already have a location.
@@ -77,7 +106,7 @@ export declare function addTraceImportLocations(provider: TransformResultProvide
77
106
  specifier?: string;
78
107
  line?: number;
79
108
  column?: number;
80
- }>, importLocCache: ImportLocCache, findImportSpecifierIndex: FindImportSpecifierIndex): Promise<void>;
109
+ }>, importLocCache: ImportLocCache, findImportSpecifierLocationIndex: FindImportSpecifierLocationIndex): Promise<void>;
81
110
  export interface CodeSnippet {
82
111
  /** Source lines with line numbers, e.g. `[" 6 | import { getSecret } from './secret.server'", ...]` */
83
112
  lines: Array<string>;
@@ -1,8 +1,8 @@
1
1
  import { getOrCreate, normalizeFilePath } from "./utils.js";
2
- import { findPostCompileUsagePos } from "./postCompileUsage.js";
2
+ import { findOriginalUnsafeUsagePosFromResult, findPostCompileUsagePosFromResult, getImportSpecifierLocationFromResult } from "./analysis.js";
3
3
  import * as path$1 from "pathe";
4
4
  import { SourceMapConsumer } from "source-map";
5
- //#region src/import-protection-plugin/sourceLocation.ts
5
+ //#region src/import-protection/sourceLocation.ts
6
6
  function buildLineIndex(code) {
7
7
  const offsets = [0];
8
8
  for (let i = 0; i < code.length; i++) if (code.charCodeAt(i) === 10) offsets.push(i + 1);
@@ -29,6 +29,23 @@ function indexToLineColWithIndex(lineIndex, idx) {
29
29
  column0: Math.max(0, idx - lineStart)
30
30
  };
31
31
  }
32
+ function indexToLineColumn(lineIndex, idx) {
33
+ const { line, column0 } = indexToLineColWithIndex(lineIndex, idx);
34
+ return {
35
+ line,
36
+ column: column0 + 1
37
+ };
38
+ }
39
+ function normalizeSourceMap(map) {
40
+ if (!map) return;
41
+ return {
42
+ ...map,
43
+ version: Number(map.version),
44
+ file: map.file ?? "",
45
+ names: Array.isArray(map.names) ? map.names : [],
46
+ sourcesContent: map.sourcesContent?.map((value) => value ?? "") ?? void 0
47
+ };
48
+ }
32
49
  /**
33
50
  * Pick the most-likely original source text for `importerFile` from
34
51
  * a sourcemap that may contain multiple sources.
@@ -147,12 +164,27 @@ var ImportLocCache = class {
147
164
  }
148
165
  }
149
166
  };
167
+ function getOrCreateOriginalTransformResult(result) {
168
+ if (!result.originalCode) return;
169
+ if (!result.originalResult) result.originalResult = {
170
+ code: result.originalCode,
171
+ map: void 0,
172
+ originalCode: result.originalCode
173
+ };
174
+ return result.originalResult;
175
+ }
176
+ function createImportSpecifierLocationIndex() {
177
+ return { find(result, source) {
178
+ if (!result.code.includes(source)) return -1;
179
+ return getImportSpecifierLocationFromResult(result, source);
180
+ } };
181
+ }
150
182
  /**
151
183
  * Find the location of an import statement in a transformed module
152
184
  * by searching the post-transform code and mapping back via sourcemap.
153
185
  * Results are cached in `importLocCache`.
154
186
  */
155
- async function findImportStatementLocationFromTransformed(provider, importerId, source, importLocCache, findImportSpecifierIndex) {
187
+ async function findImportStatementLocationFromTransformed(provider, importerId, source, importLocCache, findImportSpecifierLocationIndex) {
156
188
  const importerFile = normalizeFilePath(importerId);
157
189
  const cacheKey = `${importerFile}::${source}`;
158
190
  if (importLocCache.has(cacheKey)) return importLocCache.get(cacheKey) ?? void 0;
@@ -162,9 +194,9 @@ async function findImportStatementLocationFromTransformed(provider, importerId,
162
194
  importLocCache.set(cacheKey, null);
163
195
  return;
164
196
  }
165
- const { code, map } = res;
166
- const lineIndex = res.lineIndex ?? buildLineIndex(code);
167
- const idx = findImportSpecifierIndex(code, source);
197
+ const { map } = res;
198
+ const lineIndex = res.lineIndex ?? (res.lineIndex = buildLineIndex(res.code));
199
+ const idx = findImportSpecifierLocationIndex(res, source);
168
200
  if (idx === -1) {
169
201
  importLocCache.set(cacheKey, null);
170
202
  return;
@@ -189,7 +221,7 @@ async function findPostCompileUsageLocation(provider, importerId, source) {
189
221
  if (!res) return void 0;
190
222
  const { code, map } = res;
191
223
  if (!res.lineIndex) res.lineIndex = buildLineIndex(code);
192
- const pos = findPostCompileUsagePos(code, source);
224
+ const pos = findPostCompileUsagePosFromResult(res, source);
193
225
  if (!pos) return void 0;
194
226
  return await mapGeneratedToOriginal(map, pos, importerFile);
195
227
  } catch {
@@ -197,14 +229,37 @@ async function findPostCompileUsageLocation(provider, importerId, source) {
197
229
  }
198
230
  }
199
231
  /**
232
+ * Best-effort original-source usage lookup for cases where a later transform
233
+ * removes or rewrites the import from emitted code but preserves the original
234
+ * source in `sourcesContent`.
235
+ */
236
+ function findOriginalUsageLocation(provider, importerId, source, envType) {
237
+ try {
238
+ const importerFile = normalizeFilePath(importerId);
239
+ const res = provider.getTransformResult(importerId);
240
+ if (!res) return void 0;
241
+ const originalResult = getOrCreateOriginalTransformResult(res);
242
+ if (!originalResult) return void 0;
243
+ const pos = envType ? findOriginalUnsafeUsagePosFromResult(originalResult, source, envType) : findPostCompileUsagePosFromResult(originalResult, source);
244
+ if (!pos) return void 0;
245
+ return {
246
+ file: importerFile,
247
+ line: pos.line,
248
+ column: pos.column0 + 1
249
+ };
250
+ } catch {
251
+ return;
252
+ }
253
+ }
254
+ /**
200
255
  * Annotate each trace hop with the location of the import that created the
201
256
  * edge (file:line:col). Skips steps that already have a location.
202
257
  */
203
- async function addTraceImportLocations(provider, trace, importLocCache, findImportSpecifierIndex) {
258
+ async function addTraceImportLocations(provider, trace, importLocCache, findImportSpecifierLocationIndex) {
204
259
  for (const step of trace) {
205
260
  if (!step.specifier) continue;
206
261
  if (step.line != null && step.column != null) continue;
207
- const loc = await findImportStatementLocationFromTransformed(provider, step.file, step.specifier, importLocCache, findImportSpecifierIndex);
262
+ const loc = await findImportStatementLocationFromTransformed(provider, step.file, step.specifier, importLocCache, findImportSpecifierLocationIndex);
208
263
  if (!loc) continue;
209
264
  step.line = loc.line;
210
265
  step.column = loc.column;
@@ -258,6 +313,6 @@ function buildCodeSnippet(provider, moduleId, loc, contextLines = 2) {
258
313
  }
259
314
  }
260
315
  //#endregion
261
- export { ImportLocCache, addTraceImportLocations, buildCodeSnippet, buildLineIndex, findImportStatementLocationFromTransformed, findPostCompileUsageLocation, pickOriginalCodeFromSourcesContent };
316
+ export { ImportLocCache, addTraceImportLocations, buildCodeSnippet, buildLineIndex, createImportSpecifierLocationIndex, findImportStatementLocationFromTransformed, findOriginalUsageLocation, findPostCompileUsageLocation, getOrCreateOriginalTransformResult, indexToLineColumn, normalizeSourceMap, pickOriginalCodeFromSourcesContent };
262
317
 
263
318
  //# sourceMappingURL=sourceLocation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sourceLocation.js","names":[],"sources":["../../../src/import-protection/sourceLocation.ts"],"sourcesContent":["import { SourceMapConsumer } from 'source-map'\nimport * as path from 'pathe'\n\nimport {\n findOriginalUnsafeUsagePosFromResult,\n findPostCompileUsagePosFromResult,\n getImportSpecifierLocationFromResult,\n} from './analysis'\nimport { getOrCreate, normalizeFilePath } from './utils'\nimport type { ImportAnalysis } from './analysis'\nimport type { ParsedAst } from './ast'\nimport type { Loc } from './trace'\nimport type { RawSourceMap } from 'source-map'\n\n// Source-map type compatible with both Rollup's SourceMap and source-map's\n// RawSourceMap. Structural type avoids version: number vs string mismatch.\n\n/**\n * Minimal source-map shape used throughout the import-protection plugin.\n */\nexport interface SourceMapLike {\n file?: string\n sourceRoot?: string\n version: number | string\n sources: Array<string>\n names: Array<string>\n sourcesContent?: Array<string | null>\n mappings: string\n}\n\n// Transform result provider (replaces ctx.load() which doesn't work in dev)\nexport interface TransformResult {\n code: string\n map: SourceMapLike | undefined\n originalCode: string | undefined\n originalResult?: TransformResult\n /** Precomputed line index for `code` (index → line/col). */\n lineIndex?: LineIndex\n parsedAst?: ParsedAst\n analysis?: ImportAnalysis\n}\n\n/**\n * Provides the transformed code and composed sourcemap for a module.\n *\n * Populated from a late-running transform hook. By the time `resolveId`\n * fires for an import, the importer has already been fully transformed.\n */\nexport interface TransformResultProvider {\n getTransformResult: (id: string) => TransformResult | undefined\n}\n\nexport interface ImportSpecifierLocationIndex {\n find: FindImportSpecifierLocationIndex\n}\n\n// Index → line/column conversion\n\nexport type LineIndex = {\n offsets: Array<number>\n}\n\nexport function buildLineIndex(code: string): LineIndex {\n const offsets: Array<number> = [0]\n for (let i = 0; i < code.length; i++) {\n if (code.charCodeAt(i) === 10) {\n offsets.push(i + 1)\n }\n }\n return { offsets }\n}\n\nfunction upperBound(values: Array<number>, x: number): number {\n let lo = 0\n let hi = values.length\n while (lo < hi) {\n const mid = (lo + hi) >> 1\n if (values[mid]! <= x) lo = mid + 1\n else hi = mid\n }\n return lo\n}\n\nfunction indexToLineColWithIndex(\n lineIndex: LineIndex,\n idx: number,\n): { line: number; column0: number } {\n const offsets = lineIndex.offsets\n const ub = upperBound(offsets, idx)\n const lineIdx = Math.max(0, ub - 1)\n const line = lineIdx + 1\n\n const lineStart = offsets[lineIdx] ?? 0\n return { line, column0: Math.max(0, idx - lineStart) }\n}\n\nexport function indexToLineColumn(\n lineIndex: LineIndex,\n idx: number,\n): { line: number; column: number } {\n const { line, column0 } = indexToLineColWithIndex(lineIndex, idx)\n return {\n line,\n column: column0 + 1,\n }\n}\n\nexport function normalizeSourceMap(map: SourceMapLike | null | undefined):\n | {\n version: number\n file: string\n sourceRoot?: string\n sources: Array<string>\n names: Array<string>\n sourcesContent?: Array<string>\n mappings: string\n }\n | undefined {\n if (!map) {\n return undefined\n }\n\n return {\n ...map,\n version: Number(map.version),\n file: map.file ?? '',\n names: Array.isArray(map.names) ? map.names : [],\n sourcesContent:\n map.sourcesContent?.map((value) => value ?? '') ?? undefined,\n }\n}\n\n/**\n * Pick the most-likely original source text for `importerFile` from\n * a sourcemap that may contain multiple sources.\n */\nexport function pickOriginalCodeFromSourcesContent(\n map: SourceMapLike | undefined,\n importerFile: string,\n root: string,\n): string | undefined {\n if (!map?.sourcesContent || map.sources.length === 0) {\n return undefined\n }\n\n const file = normalizeFilePath(importerFile)\n const sourceRoot = map.sourceRoot\n const fileSeg = file.split('/').filter(Boolean)\n\n const resolveBase = sourceRoot ? path.resolve(root, sourceRoot) : root\n\n let bestIdx = -1\n let bestScore = -1\n\n for (let i = 0; i < map.sources.length; i++) {\n const content = map.sourcesContent[i]\n if (typeof content !== 'string') continue\n\n const src = map.sources[i] ?? ''\n\n const normalizedSrc = normalizeFilePath(src)\n if (normalizedSrc === file) {\n return content\n }\n\n let resolved: string\n if (!src) {\n resolved = ''\n } else if (path.isAbsolute(src)) {\n resolved = normalizeFilePath(src)\n } else {\n resolved = normalizeFilePath(path.resolve(resolveBase, src))\n }\n if (resolved === file) {\n return content\n }\n\n // Count matching path segments from the end.\n const normalizedSrcSeg = normalizedSrc.split('/').filter(Boolean)\n const resolvedSeg =\n resolved !== normalizedSrc\n ? resolved.split('/').filter(Boolean)\n : normalizedSrcSeg\n const score = Math.max(\n segmentSuffixScore(normalizedSrcSeg, fileSeg),\n segmentSuffixScore(resolvedSeg, fileSeg),\n )\n\n if (score > bestScore) {\n bestScore = score\n bestIdx = i\n }\n }\n\n if (bestIdx !== -1 && bestScore >= 1) {\n return map.sourcesContent[bestIdx] ?? undefined\n }\n\n return map.sourcesContent[0] ?? undefined\n}\n\n/** Count matching path segments from the end of `aSeg` against `bSeg`. */\nfunction segmentSuffixScore(aSeg: Array<string>, bSeg: Array<string>): number {\n let score = 0\n for (\n let i = aSeg.length - 1, j = bSeg.length - 1;\n i >= 0 && j >= 0;\n i--, j--\n ) {\n if (aSeg[i] !== bSeg[j]) break\n score++\n }\n return score\n}\n\nasync function mapGeneratedToOriginal(\n map: SourceMapLike | undefined,\n generated: { line: number; column0: number },\n fallbackFile: string,\n): Promise<Loc> {\n const fallback: Loc = {\n file: fallbackFile,\n line: generated.line,\n column: generated.column0 + 1,\n }\n\n if (!map) {\n return fallback\n }\n\n const consumer = await getSourceMapConsumer(map)\n if (!consumer) return fallback\n\n try {\n const orig = consumer.originalPositionFor({\n line: generated.line,\n column: generated.column0,\n })\n if (orig.line != null && orig.column != null) {\n return {\n file: orig.source ? normalizeFilePath(orig.source) : fallbackFile,\n line: orig.line,\n column: orig.column + 1,\n }\n }\n } catch {\n // Malformed sourcemap\n }\n\n return fallback\n}\n\nconst consumerCache = new WeakMap<object, Promise<SourceMapConsumer | null>>()\n\nfunction toRawSourceMap(map: SourceMapLike): RawSourceMap {\n return {\n ...map,\n file: map.file ?? '',\n version: Number(map.version),\n sourcesContent: map.sourcesContent?.map((s) => s ?? '') ?? [],\n }\n}\n\nasync function getSourceMapConsumer(\n map: SourceMapLike,\n): Promise<SourceMapConsumer | null> {\n const cached = consumerCache.get(map)\n if (cached) return cached\n\n const promise = (async () => {\n try {\n return await new SourceMapConsumer(toRawSourceMap(map))\n } catch {\n return null\n }\n })()\n\n consumerCache.set(map, promise)\n return promise\n}\n\nexport type ImportLocEntry = { file?: string; line: number; column: number }\n\n/**\n * Cache for import statement locations with reverse index for O(1)\n * invalidation by file. Keys: `${importerFile}::${source}`.\n */\nexport class ImportLocCache {\n private cache = new Map<string, ImportLocEntry | null>()\n private reverseIndex = new Map<string, Set<string>>()\n\n has(key: string): boolean {\n return this.cache.has(key)\n }\n\n get(key: string): ImportLocEntry | null | undefined {\n return this.cache.get(key)\n }\n\n set(key: string, value: ImportLocEntry | null): void {\n this.cache.set(key, value)\n const file = key.slice(0, key.indexOf('::'))\n getOrCreate(this.reverseIndex, file, () => new Set()).add(key)\n }\n\n clear(): void {\n this.cache.clear()\n this.reverseIndex.clear()\n }\n\n /** Remove all cache entries where the importer matches `file`. */\n deleteByFile(file: string): void {\n const keys = this.reverseIndex.get(file)\n if (keys) {\n for (const key of keys) {\n this.cache.delete(key)\n }\n this.reverseIndex.delete(file)\n }\n }\n}\n\nexport type FindImportSpecifierLocationIndex = (\n result: TransformResult,\n source: string,\n) => number\n\nexport function getOrCreateOriginalTransformResult(\n result: TransformResult,\n): TransformResult | undefined {\n if (!result.originalCode) {\n return undefined\n }\n\n if (!result.originalResult) {\n result.originalResult = {\n code: result.originalCode,\n map: undefined,\n originalCode: result.originalCode,\n }\n }\n\n return result.originalResult\n}\n\nexport function createImportSpecifierLocationIndex(): ImportSpecifierLocationIndex {\n return {\n find(result: TransformResult, source: string): number {\n if (!result.code.includes(source)) {\n return -1\n }\n\n return getImportSpecifierLocationFromResult(result, source)\n },\n }\n}\n\n/**\n * Find the location of an import statement in a transformed module\n * by searching the post-transform code and mapping back via sourcemap.\n * Results are cached in `importLocCache`.\n */\nexport async function findImportStatementLocationFromTransformed(\n provider: TransformResultProvider,\n importerId: string,\n source: string,\n importLocCache: ImportLocCache,\n findImportSpecifierLocationIndex: FindImportSpecifierLocationIndex,\n): Promise<Loc | undefined> {\n const importerFile = normalizeFilePath(importerId)\n const cacheKey = `${importerFile}::${source}`\n if (importLocCache.has(cacheKey)) {\n return importLocCache.get(cacheKey) ?? undefined\n }\n\n try {\n const res = provider.getTransformResult(importerId)\n if (!res) {\n importLocCache.set(cacheKey, null)\n return undefined\n }\n\n const { map } = res\n\n const lineIndex =\n res.lineIndex ?? (res.lineIndex = buildLineIndex(res.code))\n\n const idx = findImportSpecifierLocationIndex(res, source)\n if (idx === -1) {\n importLocCache.set(cacheKey, null)\n return undefined\n }\n\n const generated = indexToLineColWithIndex(lineIndex, idx)\n const loc = await mapGeneratedToOriginal(map, generated, importerFile)\n importLocCache.set(cacheKey, loc)\n return loc\n } catch {\n importLocCache.set(cacheKey, null)\n return undefined\n }\n}\n\n/**\n * Find the first post-compile usage location for a denied import specifier.\n * Best-effort: searches transformed code for non-import uses of imported\n * bindings and maps back to original source via sourcemap.\n */\nexport async function findPostCompileUsageLocation(\n provider: TransformResultProvider,\n importerId: string,\n source: string,\n): Promise<Loc | undefined> {\n try {\n const importerFile = normalizeFilePath(importerId)\n const res = provider.getTransformResult(importerId)\n if (!res) return undefined\n const { code, map } = res\n\n if (!res.lineIndex) {\n res.lineIndex = buildLineIndex(code)\n }\n\n const pos = findPostCompileUsagePosFromResult(res, source)\n if (!pos) return undefined\n\n return await mapGeneratedToOriginal(map, pos, importerFile)\n } catch {\n return undefined\n }\n}\n\n/**\n * Best-effort original-source usage lookup for cases where a later transform\n * removes or rewrites the import from emitted code but preserves the original\n * source in `sourcesContent`.\n */\nexport function findOriginalUsageLocation(\n provider: TransformResultProvider,\n importerId: string,\n source: string,\n envType?: 'client' | 'server',\n): Loc | undefined {\n try {\n const importerFile = normalizeFilePath(importerId)\n const res = provider.getTransformResult(importerId)\n if (!res) return undefined\n const originalResult = getOrCreateOriginalTransformResult(res)\n if (!originalResult) return undefined\n\n const pos = envType\n ? findOriginalUnsafeUsagePosFromResult(originalResult, source, envType)\n : findPostCompileUsagePosFromResult(originalResult, source)\n if (!pos) return undefined\n\n return {\n file: importerFile,\n line: pos.line,\n column: pos.column0 + 1,\n }\n } catch {\n return undefined\n }\n}\n\n/**\n * Annotate each trace hop with the location of the import that created the\n * edge (file:line:col). Skips steps that already have a location.\n */\nexport async function addTraceImportLocations(\n provider: TransformResultProvider,\n trace: Array<{\n file: string\n specifier?: string\n line?: number\n column?: number\n }>,\n importLocCache: ImportLocCache,\n findImportSpecifierLocationIndex: FindImportSpecifierLocationIndex,\n): Promise<void> {\n for (const step of trace) {\n if (!step.specifier) continue\n if (step.line != null && step.column != null) continue\n const loc = await findImportStatementLocationFromTransformed(\n provider,\n step.file,\n step.specifier,\n importLocCache,\n findImportSpecifierLocationIndex,\n )\n if (!loc) continue\n step.line = loc.line\n step.column = loc.column\n }\n}\n\n// Code snippet extraction (vitest-style context around a location)\n\nexport interface CodeSnippet {\n /** Source lines with line numbers, e.g. `[\" 6 | import { getSecret } from './secret.server'\", ...]` */\n lines: Array<string>\n /** The highlighted line (1-indexed original line number) */\n highlightLine: number\n /** Clickable file:line reference */\n location: string\n}\n\n/**\n * Build a vitest-style code snippet showing lines surrounding a location.\n *\n * Prefers `originalCode` from the sourcemap's sourcesContent; falls back\n * to transformed code when unavailable.\n */\nexport function buildCodeSnippet(\n provider: TransformResultProvider,\n moduleId: string,\n loc: Loc,\n contextLines: number = 2,\n): CodeSnippet | undefined {\n try {\n const importerFile = normalizeFilePath(moduleId)\n const res = provider.getTransformResult(moduleId)\n if (!res) return undefined\n\n const sourceCode = res.originalCode ?? res.code\n const targetLine = loc.line // 1-indexed\n const targetCol = loc.column // 1-indexed\n\n if (targetLine < 1) return undefined\n\n const allLines = sourceCode.split('\\n')\n // Strip trailing \\r from \\r\\n line endings\n for (let i = 0; i < allLines.length; i++) {\n const line = allLines[i]!\n if (line.endsWith('\\r')) allLines[i] = line.slice(0, -1)\n }\n\n const wantStart = Math.max(1, targetLine - contextLines)\n const wantEnd = Math.min(allLines.length, targetLine + contextLines)\n\n if (targetLine > allLines.length) return undefined\n\n const lines = allLines.slice(wantStart - 1, wantEnd)\n const gutterWidth = String(wantEnd).length\n\n const sourceFile = loc.file ?? importerFile\n const snippetLines: Array<string> = []\n for (let i = 0; i < lines.length; i++) {\n const ln = wantStart + i\n const lineContent = lines[i]!\n const lineNumStr = String(ln).padStart(gutterWidth, ' ')\n const marker = ln === targetLine ? '>' : ' '\n snippetLines.push(` ${marker} ${lineNumStr} | ${lineContent}`)\n\n if (ln === targetLine && targetCol > 0) {\n const padding = ' '.repeat(targetCol - 1)\n snippetLines.push(` ${' '.repeat(gutterWidth)} | ${padding}^`)\n }\n }\n\n return {\n lines: snippetLines,\n highlightLine: targetLine,\n location: `${sourceFile}:${targetLine}:${targetCol}`,\n }\n } catch {\n return undefined\n }\n}\n"],"mappings":"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"}
@@ -56,7 +56,6 @@ export interface ViolationInfo {
56
56
  importerLoc?: Loc;
57
57
  resolved?: string;
58
58
  trace: Array<TraceStep>;
59
- message: string;
60
59
  /** Vitest-style code snippet showing the offending usage in the leaf module. */
61
60
  snippet?: {
62
61
  lines: Array<string>;
@@ -1,5 +1,5 @@
1
1
  import { getOrCreate, relativizePath } from "./utils.js";
2
- //#region src/import-protection-plugin/trace.ts
2
+ //#region src/import-protection/trace.ts
3
3
  /**
4
4
  * Per-environment reverse import graph.
5
5
  * Maps a resolved module id to the set of modules that import it.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace.js","names":[],"sources":["../../../src/import-protection/trace.ts"],"sourcesContent":["import { getOrCreate, relativizePath } from './utils'\n\nexport interface TraceEdge {\n importer: string\n specifier?: string\n}\n\n/**\n * Per-environment reverse import graph.\n * Maps a resolved module id to the set of modules that import it.\n */\nexport class ImportGraph {\n /**\n * resolvedId -> Map<importer, specifier>\n *\n * We use a Map instead of a Set of objects so edges dedupe correctly.\n */\n readonly reverseEdges: Map<string, Map<string, string | undefined>> =\n new Map()\n\n /**\n * Forward-edge index: importer -> Set<resolvedId>.\n *\n * Maintained alongside reverseEdges so that {@link invalidate} can remove\n * all outgoing edges for a file in O(outgoing-edges) instead of scanning\n * every reverse-edge map in the graph.\n */\n private readonly forwardEdges: Map<string, Set<string>> = new Map()\n\n readonly entries: Set<string> = new Set()\n\n addEdge(resolved: string, importer: string, specifier?: string): void {\n getOrCreate(this.reverseEdges, resolved, () => new Map()).set(\n importer,\n specifier,\n )\n getOrCreate(this.forwardEdges, importer, () => new Set()).add(resolved)\n }\n\n /** Convenience for tests/debugging. */\n getEdges(resolved: string): Set<TraceEdge> | undefined {\n const importers = this.reverseEdges.get(resolved)\n if (!importers) return undefined\n const out = new Set<TraceEdge>()\n for (const [importer, specifier] of importers) {\n out.add({ importer, specifier })\n }\n return out\n }\n\n addEntry(id: string): void {\n this.entries.add(id)\n }\n\n clear(): void {\n this.reverseEdges.clear()\n this.forwardEdges.clear()\n this.entries.clear()\n }\n\n invalidate(id: string): void {\n // Remove all outgoing edges (id as importer) using the forward index.\n const targets = this.forwardEdges.get(id)\n if (targets) {\n for (const resolved of targets) {\n this.reverseEdges.get(resolved)?.delete(id)\n }\n this.forwardEdges.delete(id)\n }\n // Remove as a target (id as resolved module)\n this.reverseEdges.delete(id)\n }\n}\n\nexport interface TraceStep {\n file: string\n specifier?: string\n line?: number\n column?: number\n}\n\nexport interface Loc {\n file?: string\n line: number\n column: number\n}\n\n/**\n * BFS from a node upward through reverse edges to find the shortest\n * path to an entry module.\n */\nexport function buildTrace(\n graph: ImportGraph,\n startNode: string,\n maxDepth: number = 20,\n): Array<TraceStep> {\n // BFS upward (startNode -> importers -> ...)\n const visited = new Set<string>([startNode])\n const depthByNode = new Map<string, number>([[startNode, 0]])\n\n // For any importer we visit, store the \"down\" link back toward startNode.\n // importer --(specifier)--> next\n const down = new Map<string, { next: string; specifier?: string }>()\n\n const queue: Array<string> = [startNode]\n let qi = 0\n\n let root: string | null = null\n\n while (qi < queue.length) {\n const node = queue[qi++]!\n const depth = depthByNode.get(node)!\n const importers = graph.reverseEdges.get(node)\n\n if (node !== startNode) {\n const isEntry =\n graph.entries.has(node) || !importers || importers.size === 0\n if (isEntry) {\n root = node\n break\n }\n }\n\n if (depth >= maxDepth) {\n continue\n }\n\n if (!importers || importers.size === 0) {\n continue\n }\n\n for (const [importer, specifier] of importers) {\n if (visited.has(importer)) continue\n visited.add(importer)\n depthByNode.set(importer, depth + 1)\n down.set(importer, { next: node, specifier })\n queue.push(importer)\n }\n }\n\n // Best-effort: if we never found a root, just start from the original node.\n if (!root) {\n root = startNode\n }\n\n const trace: Array<TraceStep> = []\n let current = root\n for (let i = 0; i <= maxDepth + 1; i++) {\n const link = down.get(current)\n trace.push({ file: current, specifier: link?.specifier })\n if (!link) break\n current = link.next\n }\n\n return trace\n}\n\nexport interface ViolationInfo {\n env: string\n envType: 'client' | 'server'\n type: 'specifier' | 'file' | 'marker'\n behavior: 'error' | 'mock'\n pattern?: string | RegExp\n specifier: string\n importer: string\n importerLoc?: Loc\n resolved?: string\n trace: Array<TraceStep>\n /** Vitest-style code snippet showing the offending usage in the leaf module. */\n snippet?: {\n lines: Array<string>\n highlightLine: number\n location: string\n }\n}\n\n/**\n * Suggestion strings for server-only code leaking into client environments.\n * Used by both `formatViolation` (terminal) and runtime mock modules (browser).\n */\nexport const CLIENT_ENV_SUGGESTIONS = [\n 'Use createServerFn().handler(() => ...) to keep the logic on the server and call it from the client via an RPC bridge',\n 'Use createServerOnlyFn(() => ...) to mark it as server-only (it will throw if accidentally called from the client)',\n 'Use createIsomorphicFn().client(() => ...).server(() => ...) to provide separate client and server implementations',\n 'Move the server-only import out of this file into a separate .server.ts module that is not imported by any client code',\n] as const\n\n/**\n * Suggestion strings for client-only code leaking into server environments.\n * The JSX-specific suggestion is conditionally prepended by `formatViolation`.\n */\nexport const SERVER_ENV_SUGGESTIONS = [\n 'Use createClientOnlyFn(() => ...) to mark it as client-only (returns undefined on the server)',\n 'Use createIsomorphicFn().client(() => ...).server(() => ...) to provide separate client and server implementations',\n 'Move the client-only import out of this file into a separate .client.ts module that is not imported by any server code',\n] as const\n\nexport function formatViolation(info: ViolationInfo, root: string): string {\n const rel = (p: string) => relativizePath(p, root)\n\n const relLoc = (p: string, loc?: Loc) => {\n const r = rel(p)\n const file = loc?.file ? rel(loc.file) : r\n return loc ? `${file}:${loc.line}:${loc.column}` : r\n }\n\n const relTraceStep = (step: TraceStep): string => {\n const file = rel(step.file)\n if (step.line == null) return file\n const col = step.column ?? 1\n return `${file}:${step.line}:${col}`\n }\n\n const lines: Array<string> = []\n lines.push(``)\n lines.push(`[import-protection] Import denied in ${info.envType} environment`)\n lines.push(``)\n\n if (info.type === 'specifier') {\n lines.push(` Denied by specifier pattern: ${String(info.pattern)}`)\n } else if (info.type === 'file') {\n lines.push(` Denied by file pattern: ${String(info.pattern)}`)\n } else {\n lines.push(\n ` Denied by marker: module is restricted to the opposite environment`,\n )\n }\n\n lines.push(` Importer: ${relLoc(info.importer, info.importerLoc)}`)\n lines.push(` Import: \"${rel(info.specifier)}\"`)\n if (info.resolved) {\n lines.push(` Resolved: ${rel(info.resolved)}`)\n }\n\n if (info.trace.length > 0) {\n lines.push(``)\n lines.push(` Trace:`)\n for (let i = 0; i < info.trace.length; i++) {\n const step = info.trace[i]!\n const isEntry = i === 0\n const tag = isEntry ? ' (entry)' : ''\n const spec = step.specifier ? ` (import \"${rel(step.specifier)}\")` : ''\n lines.push(` ${i + 1}. ${relTraceStep(step)}${tag}${spec}`)\n }\n }\n\n if (info.snippet) {\n lines.push(``)\n lines.push(` Code:`)\n for (const snippetLine of info.snippet.lines) {\n lines.push(snippetLine)\n }\n lines.push(``)\n lines.push(` ${rel(info.snippet.location)}`)\n }\n\n lines.push(``)\n\n // Add suggestions\n if (info.envType === 'client') {\n lines.push(` Suggestions:`)\n for (const s of CLIENT_ENV_SUGGESTIONS) {\n lines.push(` - ${s}`)\n }\n } else {\n const snippetText = info.snippet?.lines.join('\\n') ?? ''\n const looksLikeJsx =\n /<[A-Z]/.test(snippetText) ||\n (/\\{.*\\(.*\\).*\\}/.test(snippetText) && /</.test(snippetText))\n\n lines.push(` Suggestions:`)\n if (looksLikeJsx) {\n lines.push(\n ` - Wrap the JSX in <ClientOnly fallback={<Loading />}>...</ClientOnly> so it only renders in the browser after hydration`,\n )\n }\n for (const s of SERVER_ENV_SUGGESTIONS) {\n lines.push(` - ${s}`)\n }\n }\n\n lines.push(``)\n return lines.join('\\n')\n}\n"],"mappings":"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"}
@@ -1,5 +1,23 @@
1
1
  export type Pattern = string | RegExp;
2
2
  export declare function dedupePatterns(patterns: Array<Pattern>): Array<Pattern>;
3
+ export interface FileMatchers {
4
+ files: Array<{
5
+ pattern: Pattern;
6
+ test: (value: string) => boolean;
7
+ }>;
8
+ excludeFiles: Array<{
9
+ pattern: Pattern;
10
+ test: (value: string) => boolean;
11
+ }>;
12
+ }
13
+ export declare function isFileExcluded(relativePath: string, matchers: Pick<FileMatchers, 'excludeFiles'>): boolean;
14
+ export declare function checkFileDenial(relativePath: string, matchers: FileMatchers): FileMatchers['files'][number] | undefined;
15
+ export declare function dedupeViolationKey(info: {
16
+ type: string;
17
+ importer: string;
18
+ specifier: string;
19
+ resolved?: string;
20
+ }): string;
3
21
  /** Strip both `?query` and `#hash` from a module ID. */
4
22
  export declare function stripQueryAndHash(id: string): string;
5
23
  export declare function normalizeFilePath(id: string): string;
@@ -10,7 +28,6 @@ export declare function escapeRegExp(s: string): string;
10
28
  export declare function getOrCreate<TKey, TValue>(map: Map<TKey, TValue>, key: TKey, factory: () => TValue): TValue;
11
29
  /** Make a path relative to `root`, keeping non-rooted paths as-is. */
12
30
  export declare function relativizePath(p: string, root: string): string;
13
- export declare function extractImportSources(code: string): Array<string>;
14
31
  /** Log import-protection debug output when debug mode is enabled. */
15
32
  export declare function debugLog(...args: Array<unknown>): void;
16
33
  /** Check if any value matches the configured debug filter (if present). */
@@ -1,7 +1,7 @@
1
+ import { normalizePath } from "../utils.js";
1
2
  import { IMPORT_PROTECTION_DEBUG, IMPORT_PROTECTION_DEBUG_FILTER, KNOWN_SOURCE_EXTENSIONS } from "./constants.js";
2
- import { normalizePath } from "vite";
3
3
  import { extname, isAbsolute, relative, resolve } from "node:path";
4
- //#region src/import-protection-plugin/utils.ts
4
+ //#region src/import-protection/utils.ts
5
5
  function dedupePatterns(patterns) {
6
6
  const out = [];
7
7
  const seen = /* @__PURE__ */ new Set();
@@ -13,6 +13,16 @@ function dedupePatterns(patterns) {
13
13
  }
14
14
  return out;
15
15
  }
16
+ function isFileExcluded(relativePath, matchers) {
17
+ return matchers.excludeFiles.length > 0 && matchers.excludeFiles.some((matcher) => matcher.test(relativePath));
18
+ }
19
+ function checkFileDenial(relativePath, matchers) {
20
+ if (isFileExcluded(relativePath, matchers)) return;
21
+ return matchers.files.find((matcher) => matcher.test(relativePath));
22
+ }
23
+ function dedupeViolationKey(info) {
24
+ return `${info.type}:${info.importer}:${info.specifier}:${info.resolved ?? ""}`;
25
+ }
16
26
  /** Strip both `?query` and `#hash` from a module ID. */
17
27
  function stripQueryAndHash(id) {
18
28
  const q = id.indexOf("?");
@@ -41,13 +51,6 @@ function normalizeFilePath(id) {
41
51
  function clearNormalizeFilePathCache() {
42
52
  normalizeFilePathCache.clear();
43
53
  }
44
- /**
45
- * Lightweight regex to extract all import/re-export source strings from
46
- * post-transform code. Matches:
47
- * - `from "..."` / `from '...'` (static import/export)
48
- * - `import("...")` / `import('...')` (dynamic import)
49
- */
50
- var importSourceRe = /\bfrom\s+(?:"([^"]+)"|'([^']+)')|import\s*\(\s*(?:"([^"]+)"|'([^']+)')\s*\)/g;
51
54
  function escapeRegExp(s) {
52
55
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
53
56
  }
@@ -67,16 +70,6 @@ function relativizePath(p, root) {
67
70
  if (ch !== 47 && !Number.isNaN(ch)) return p;
68
71
  return ch === 47 ? p.slice(root.length + 1) : p.slice(root.length);
69
72
  }
70
- function extractImportSources(code) {
71
- const sources = [];
72
- let m;
73
- importSourceRe.lastIndex = 0;
74
- while ((m = importSourceRe.exec(code)) !== null) {
75
- const src = m[1] ?? m[2] ?? m[3] ?? m[4];
76
- if (src) sources.push(src);
77
- }
78
- return sources;
79
- }
80
73
  /** Log import-protection debug output when debug mode is enabled. */
81
74
  function debugLog(...args) {
82
75
  if (!IMPORT_PROTECTION_DEBUG) return;
@@ -153,6 +146,6 @@ function canonicalizeResolvedId(id, root, resolveExtensionlessAbsoluteId) {
153
146
  return resolveExtensionlessAbsoluteId(normalized);
154
147
  }
155
148
  //#endregion
156
- export { buildResolutionCandidates, buildSourceCandidates, canonicalizeResolvedId, clearNormalizeFilePathCache, debugLog, dedupePatterns, escapeRegExp, extractImportSources, getOrCreate, isInsideDirectory, matchesDebugFilter, normalizeFilePath, relativizePath, shouldDeferViolation };
149
+ export { buildResolutionCandidates, buildSourceCandidates, canonicalizeResolvedId, checkFileDenial, clearNormalizeFilePathCache, debugLog, dedupePatterns, dedupeViolationKey, escapeRegExp, getOrCreate, isFileExcluded, isInsideDirectory, matchesDebugFilter, normalizeFilePath, relativizePath, shouldDeferViolation };
157
150
 
158
151
  //# sourceMappingURL=utils.js.map