@tanstack/start-plugin-core 1.161.4 → 1.162.1

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 (32) hide show
  1. package/dist/esm/import-protection-plugin/defaults.d.ts +6 -4
  2. package/dist/esm/import-protection-plugin/defaults.js +3 -12
  3. package/dist/esm/import-protection-plugin/defaults.js.map +1 -1
  4. package/dist/esm/import-protection-plugin/plugin.d.ts +1 -1
  5. package/dist/esm/import-protection-plugin/plugin.js +488 -257
  6. package/dist/esm/import-protection-plugin/plugin.js.map +1 -1
  7. package/dist/esm/import-protection-plugin/postCompileUsage.d.ts +4 -2
  8. package/dist/esm/import-protection-plugin/postCompileUsage.js +31 -150
  9. package/dist/esm/import-protection-plugin/postCompileUsage.js.map +1 -1
  10. package/dist/esm/import-protection-plugin/rewriteDeniedImports.js +13 -9
  11. package/dist/esm/import-protection-plugin/rewriteDeniedImports.js.map +1 -1
  12. package/dist/esm/import-protection-plugin/sourceLocation.d.ts +32 -66
  13. package/dist/esm/import-protection-plugin/sourceLocation.js +129 -56
  14. package/dist/esm/import-protection-plugin/sourceLocation.js.map +1 -1
  15. package/dist/esm/import-protection-plugin/trace.d.ts +10 -0
  16. package/dist/esm/import-protection-plugin/trace.js +30 -44
  17. package/dist/esm/import-protection-plugin/trace.js.map +1 -1
  18. package/dist/esm/import-protection-plugin/utils.d.ts +8 -4
  19. package/dist/esm/import-protection-plugin/utils.js +43 -1
  20. package/dist/esm/import-protection-plugin/utils.js.map +1 -1
  21. package/dist/esm/import-protection-plugin/virtualModules.d.ts +7 -1
  22. package/dist/esm/import-protection-plugin/virtualModules.js +104 -135
  23. package/dist/esm/import-protection-plugin/virtualModules.js.map +1 -1
  24. package/package.json +6 -6
  25. package/src/import-protection-plugin/defaults.ts +8 -19
  26. package/src/import-protection-plugin/plugin.ts +776 -433
  27. package/src/import-protection-plugin/postCompileUsage.ts +57 -229
  28. package/src/import-protection-plugin/rewriteDeniedImports.ts +34 -42
  29. package/src/import-protection-plugin/sourceLocation.ts +184 -185
  30. package/src/import-protection-plugin/trace.ts +38 -49
  31. package/src/import-protection-plugin/utils.ts +62 -1
  32. package/src/import-protection-plugin/virtualModules.ts +163 -177
@@ -1,4 +1,4 @@
1
- import * as path from 'pathe'
1
+ import { getOrCreate, relativizePath } from './utils'
2
2
 
3
3
  export interface TraceEdge {
4
4
  importer: string
@@ -30,21 +30,11 @@ export class ImportGraph {
30
30
  readonly entries: Set<string> = new Set()
31
31
 
32
32
  addEdge(resolved: string, importer: string, specifier?: string): void {
33
- let importers = this.reverseEdges.get(resolved)
34
- if (!importers) {
35
- importers = new Map()
36
- this.reverseEdges.set(resolved, importers)
37
- }
38
- // Last writer wins; good enough for trace display.
39
- importers.set(importer, specifier)
40
-
41
- // Maintain forward index
42
- let targets = this.forwardEdges.get(importer)
43
- if (!targets) {
44
- targets = new Set()
45
- this.forwardEdges.set(importer, targets)
46
- }
47
- targets.add(resolved)
33
+ getOrCreate(this.reverseEdges, resolved, () => new Map()).set(
34
+ importer,
35
+ specifier,
36
+ )
37
+ getOrCreate(this.forwardEdges, importer, () => new Set()).add(resolved)
48
38
  }
49
39
 
50
40
  /** Convenience for tests/debugging. */
@@ -113,13 +103,13 @@ export function buildTrace(
113
103
  const down = new Map<string, { next: string; specifier?: string }>()
114
104
 
115
105
  const queue: Array<string> = [startNode]
116
- let queueIndex = 0
106
+ let qi = 0
117
107
 
118
108
  let root: string | null = null
119
109
 
120
- while (queueIndex < queue.length) {
121
- const node = queue[queueIndex++]!
122
- const depth = depthByNode.get(node) ?? 0
110
+ while (qi < queue.length) {
111
+ const node = queue[qi++]!
112
+ const depth = depthByNode.get(node)!
123
113
  const importers = graph.reverseEdges.get(node)
124
114
 
125
115
  if (node !== startNode) {
@@ -185,13 +175,29 @@ export interface ViolationInfo {
185
175
  }
186
176
  }
187
177
 
178
+ /**
179
+ * Suggestion strings for server-only code leaking into client environments.
180
+ * Used by both `formatViolation` (terminal) and runtime mock modules (browser).
181
+ */
182
+ export const CLIENT_ENV_SUGGESTIONS = [
183
+ 'Use createServerFn().handler(() => ...) to keep the logic on the server and call it from the client via an RPC bridge',
184
+ 'Use createServerOnlyFn(() => ...) to mark it as server-only (it will throw if accidentally called from the client)',
185
+ 'Use createIsomorphicFn().client(() => ...).server(() => ...) to provide separate client and server implementations',
186
+ 'Move the server-only import out of this file into a separate .server.ts module that is not imported by any client code',
187
+ ] as const
188
+
189
+ /**
190
+ * Suggestion strings for client-only code leaking into server environments.
191
+ * The JSX-specific suggestion is conditionally prepended by `formatViolation`.
192
+ */
193
+ export const SERVER_ENV_SUGGESTIONS = [
194
+ 'Use createClientOnlyFn(() => ...) to mark it as client-only (returns undefined on the server)',
195
+ 'Use createIsomorphicFn().client(() => ...).server(() => ...) to provide separate client and server implementations',
196
+ 'Move the client-only import out of this file into a separate .client.ts module that is not imported by any server code',
197
+ ] as const
198
+
188
199
  export function formatViolation(info: ViolationInfo, root: string): string {
189
- const rel = (p: string) => {
190
- if (p.startsWith(root)) {
191
- return path.relative(root, p)
192
- }
193
- return p
194
- }
200
+ const rel = (p: string) => relativizePath(p, root)
195
201
 
196
202
  const relLoc = (p: string, loc?: Loc) => {
197
203
  const r = rel(p)
@@ -253,22 +259,11 @@ export function formatViolation(info: ViolationInfo, root: string): string {
253
259
 
254
260
  // Add suggestions
255
261
  if (info.envType === 'client') {
256
- // Server-only code leaking into the client environment
257
262
  lines.push(` Suggestions:`)
258
- lines.push(
259
- ` - Use createServerFn().handler(() => ...) to keep the logic on the server and call it from the client via an RPC bridge`,
260
- )
261
- lines.push(
262
- ` - Use createServerOnlyFn(() => ...) to mark it as server-only (it will throw if accidentally called from the client)`,
263
- )
264
- lines.push(
265
- ` - Use createIsomorphicFn().client(() => ...).server(() => ...) to provide separate client and server implementations`,
266
- )
267
- lines.push(
268
- ` - Move the server-only import out of this file into a separate .server.ts module that is not imported by any client code`,
269
- )
263
+ for (const s of CLIENT_ENV_SUGGESTIONS) {
264
+ lines.push(` - ${s}`)
265
+ }
270
266
  } else {
271
- // Client-only code leaking into the server environment
272
267
  const snippetText = info.snippet?.lines.join('\n') ?? ''
273
268
  const looksLikeJsx =
274
269
  /<[A-Z]/.test(snippetText) ||
@@ -280,15 +275,9 @@ export function formatViolation(info: ViolationInfo, root: string): string {
280
275
  ` - Wrap the JSX in <ClientOnly fallback={<Loading />}>...</ClientOnly> so it only renders in the browser after hydration`,
281
276
  )
282
277
  }
283
- lines.push(
284
- ` - Use createClientOnlyFn(() => ...) to mark it as client-only (returns undefined on the server)`,
285
- )
286
- lines.push(
287
- ` - Use createIsomorphicFn().client(() => ...).server(() => ...) to provide separate client and server implementations`,
288
- )
289
- lines.push(
290
- ` - Move the client-only import out of this file into a separate .client.ts module that is not imported by any server code`,
291
- )
278
+ for (const s of SERVER_ENV_SUGGESTIONS) {
279
+ lines.push(` - ${s}`)
280
+ }
292
281
  }
293
282
 
294
283
  lines.push(``)
@@ -26,7 +26,68 @@ export function stripViteQuery(id: string): string {
26
26
  /**
27
27
  * Strip Vite query parameters and normalize the path in one step.
28
28
  * Replaces the repeated `normalizePath(stripViteQuery(id))` pattern.
29
+ *
30
+ * Results are memoized because the same module IDs are processed many
31
+ * times across resolveId, transform, and trace-building hooks.
29
32
  */
33
+ const normalizeFilePathCache = new Map<string, string>()
30
34
  export function normalizeFilePath(id: string): string {
31
- return normalizePath(stripViteQuery(id))
35
+ let result = normalizeFilePathCache.get(id)
36
+ if (result === undefined) {
37
+ result = normalizePath(stripViteQuery(id))
38
+ normalizeFilePathCache.set(id, result)
39
+ }
40
+ return result
41
+ }
42
+
43
+ /** Clear the memoization cache (call from buildStart to bound growth). */
44
+ export function clearNormalizeFilePathCache(): void {
45
+ normalizeFilePathCache.clear()
46
+ }
47
+
48
+ /**
49
+ * Lightweight regex to extract all import/re-export source strings from
50
+ * post-transform code. Matches:
51
+ * - `from "..."` / `from '...'` (static import/export)
52
+ * - `import("...")` / `import('...')` (dynamic import)
53
+ */
54
+ const importSourceRe =
55
+ /\bfrom\s+(?:"([^"]+)"|'([^']+)')|import\s*\(\s*(?:"([^"]+)"|'([^']+)')\s*\)/g
56
+
57
+ export function escapeRegExp(s: string): string {
58
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
59
+ }
60
+
61
+ /** Get a value from a Map, creating it with `factory` if absent. */
62
+ export function getOrCreate<TKey, TValue>(
63
+ map: Map<TKey, TValue>,
64
+ key: TKey,
65
+ factory: () => TValue,
66
+ ): TValue {
67
+ let value = map.get(key)
68
+ if (value === undefined) {
69
+ value = factory()
70
+ map.set(key, value)
71
+ }
72
+ return value
73
+ }
74
+
75
+ /** Make a path relative to `root`, keeping non-rooted paths as-is. */
76
+ export function relativizePath(p: string, root: string): string {
77
+ if (!p.startsWith(root)) return p
78
+ const ch = p.charCodeAt(root.length)
79
+ // Must be followed by a separator or end-of-string to be a true child
80
+ if (ch !== 47 && !Number.isNaN(ch)) return p
81
+ return ch === 47 ? p.slice(root.length + 1) : p.slice(root.length)
82
+ }
83
+
84
+ export function extractImportSources(code: string): Array<string> {
85
+ const sources: Array<string> = []
86
+ let m: RegExpExecArray | null
87
+ importSourceRe.lastIndex = 0
88
+ while ((m = importSourceRe.exec(code)) !== null) {
89
+ const src = m[1] ?? m[2] ?? m[3] ?? m[4]
90
+ if (src) sources.push(src)
91
+ }
92
+ return sources
32
93
  }
@@ -1,22 +1,16 @@
1
- import { normalizePath } from 'vite'
2
- import * as path from 'pathe'
3
-
4
1
  import { resolveViteId } from '../utils'
5
2
  import { VITE_ENVIRONMENT_NAMES } from '../constants'
6
3
  import { isValidExportName } from './rewriteDeniedImports'
4
+ import { CLIENT_ENV_SUGGESTIONS } from './trace'
5
+ import { relativizePath } from './utils'
7
6
  import type { ViolationInfo } from './trace'
8
7
 
9
- // ---------------------------------------------------------------------------
10
- // Virtual module ID constants
11
- // ---------------------------------------------------------------------------
12
-
13
8
  export const MOCK_MODULE_ID = 'tanstack-start-import-protection:mock'
14
9
  export const RESOLVED_MOCK_MODULE_ID = resolveViteId(MOCK_MODULE_ID)
15
10
 
16
11
  export const MOCK_EDGE_PREFIX = 'tanstack-start-import-protection:mock-edge:'
17
12
  export const RESOLVED_MOCK_EDGE_PREFIX = resolveViteId(MOCK_EDGE_PREFIX)
18
13
 
19
- // Dev-only runtime-diagnostic mock modules (used only by the client rewrite pass)
20
14
  export const MOCK_RUNTIME_PREFIX =
21
15
  'tanstack-start-import-protection:mock-runtime:'
22
16
  export const RESOLVED_MOCK_RUNTIME_PREFIX = resolveViteId(MOCK_RUNTIME_PREFIX)
@@ -24,10 +18,6 @@ export const RESOLVED_MOCK_RUNTIME_PREFIX = resolveViteId(MOCK_RUNTIME_PREFIX)
24
18
  export const MARKER_PREFIX = 'tanstack-start-import-protection:marker:'
25
19
  export const RESOLVED_MARKER_PREFIX = resolveViteId(MARKER_PREFIX)
26
20
 
27
- // ---------------------------------------------------------------------------
28
- // Base64url helpers
29
- // ---------------------------------------------------------------------------
30
-
31
21
  function toBase64Url(input: string): string {
32
22
  return Buffer.from(input, 'utf8').toString('base64url')
33
23
  }
@@ -36,38 +26,16 @@ function fromBase64Url(input: string): string {
36
26
  return Buffer.from(input, 'base64url').toString('utf8')
37
27
  }
38
28
 
39
- // ---------------------------------------------------------------------------
40
- // Mock-runtime module helpers
41
- // ---------------------------------------------------------------------------
29
+ type MockAccessMode = 'error' | 'warn' | 'off'
42
30
 
43
- export type MockAccessMode = 'error' | 'warn' | 'off'
44
-
45
- function makeMockRuntimeModuleId(payload: {
46
- env: string
47
- importer: string
48
- specifier: string
49
- trace: Array<string>
50
- mode: MockAccessMode
51
- }): string {
52
- return `${MOCK_RUNTIME_PREFIX}${toBase64Url(JSON.stringify(payload))}`
53
- }
54
-
55
- function stripTraceFormatting(
56
- trace: Array<{ file: string; line?: number; column?: number }>,
57
- root: string,
58
- ): Array<string> {
59
- // Keep this very small: runtime warning should show an actionable chain.
60
- // Format: relativePath[:line:col]
61
- const rel = (p: string) => {
62
- if (p.startsWith(root)) return normalizePath(path.relative(root, p))
63
- return p
64
- }
65
- return trace.map((s) => {
66
- const file = rel(s.file)
67
- if (s.line == null) return file
68
- return `${file}:${s.line}:${s.column ?? 1}`
69
- })
70
- }
31
+ /**
32
+ * Compact runtime suggestion text for browser console, derived from
33
+ * {@link CLIENT_ENV_SUGGESTIONS} so there's a single source of truth.
34
+ */
35
+ export const RUNTIME_SUGGESTION_TEXT =
36
+ 'Fix: ' +
37
+ CLIENT_ENV_SUGGESTIONS.join('. ') +
38
+ '. To disable these runtime diagnostics, set importProtection.mockAccess: "off".'
71
39
 
72
40
  export function mockRuntimeModuleIdFromViolation(
73
41
  info: ViolationInfo,
@@ -75,81 +43,162 @@ export function mockRuntimeModuleIdFromViolation(
75
43
  root: string,
76
44
  ): string {
77
45
  if (mode === 'off') return MOCK_MODULE_ID
78
- // Only emit runtime diagnostics in dev and only on the client environment.
79
46
  if (info.env !== VITE_ENVIRONMENT_NAMES.client) return MOCK_MODULE_ID
80
- return makeMockRuntimeModuleId({
47
+
48
+ const rel = (p: string) => relativizePath(p, root)
49
+ const trace = info.trace.map((s) => {
50
+ const file = rel(s.file)
51
+ if (s.line == null) return file
52
+ return `${file}:${s.line}:${s.column ?? 1}`
53
+ })
54
+
55
+ const payload = {
81
56
  env: info.env,
82
57
  importer: info.importer,
83
58
  specifier: info.specifier,
84
- trace: stripTraceFormatting(info.trace, root),
59
+ trace,
85
60
  mode,
86
- })
61
+ }
62
+ return `${MOCK_RUNTIME_PREFIX}${toBase64Url(JSON.stringify(payload))}`
87
63
  }
88
64
 
89
- // ---------------------------------------------------------------------------
90
- // Mock-edge module ID builder
91
- // ---------------------------------------------------------------------------
92
-
93
65
  export function makeMockEdgeModuleId(
94
66
  exports: Array<string>,
95
67
  source: string,
96
68
  runtimeId: string,
97
69
  ): string {
98
- const payload = {
99
- source,
100
- exports,
101
- runtimeId,
102
- }
70
+ const payload = { source, exports, runtimeId }
103
71
  return `${MOCK_EDGE_PREFIX}${toBase64Url(JSON.stringify(payload))}`
104
72
  }
105
73
 
106
- // ---------------------------------------------------------------------------
107
- // Load handler helpers virtual module source code generators
108
- // ---------------------------------------------------------------------------
74
+ /**
75
+ * Generate a recursive Proxy-based mock module.
76
+ *
77
+ * When `diagnostics` is provided, the generated code includes a `__report`
78
+ * function that logs runtime warnings/errors when the mock is actually used
79
+ * (property access for primitive coercion, calls, construction, sets).
80
+ *
81
+ * When `diagnostics` is omitted, the mock is completely silent — suitable
82
+ * for the shared `MOCK_MODULE_ID` that uses `syntheticNamedExports`.
83
+ */
84
+ function generateMockCode(diagnostics?: {
85
+ meta: {
86
+ env: string
87
+ importer: string
88
+ specifier: string
89
+ trace: Array<unknown>
90
+ }
91
+ mode: 'error' | 'warn' | 'off'
92
+ }): string {
93
+ const fnName = diagnostics ? '__createMock' : 'createMock'
94
+ const hasDiag = !!diagnostics
109
95
 
110
- export function loadSilentMockModule(): {
111
- syntheticNamedExports: boolean
112
- code: string
113
- } {
114
- return {
115
- // syntheticNamedExports tells Rollup to derive named exports
116
- // from the default export. Combined with the Proxy-based mock,
117
- // this allows `import { anything } from 'mock'` to work.
118
- syntheticNamedExports: true,
119
- code: `
120
- function createMock(name) {
96
+ const preamble = hasDiag
97
+ ? `const __meta = ${JSON.stringify(diagnostics.meta)};
98
+ const __mode = ${JSON.stringify(diagnostics.mode)};
99
+
100
+ const __seen = new Set();
101
+ function __report(action, accessPath) {
102
+ if (__mode === 'off') return;
103
+ const key = action + ':' + accessPath;
104
+ if (__seen.has(key)) return;
105
+ __seen.add(key);
106
+
107
+ const traceLines = Array.isArray(__meta.trace) && __meta.trace.length
108
+ ? "\\n\\nTrace:\\n" + __meta.trace.map((t, i) => ' ' + (i + 1) + '. ' + String(t)).join('\\n')
109
+ : '';
110
+
111
+ const msg =
112
+ '[import-protection] Mocked import used in dev client\\n\\n' +
113
+ 'Denied import: "' + __meta.specifier + '"\\n' +
114
+ 'Importer: ' + __meta.importer + '\\n' +
115
+ 'Access: ' + accessPath + ' (' + action + ')' +
116
+ traceLines +
117
+ '\\n\\n' + ${JSON.stringify(RUNTIME_SUGGESTION_TEXT)};
118
+
119
+ const err = new Error(msg);
120
+ if (__mode === 'warn') {
121
+ console.warn(err);
122
+ } else {
123
+ console.error(err);
124
+ }
125
+ }
126
+ `
127
+ : ''
128
+
129
+ // Diagnostic-only traps for primitive coercion, set
130
+ const diagGetTraps = hasDiag
131
+ ? `
132
+ if (prop === Symbol.toPrimitive) {
133
+ return () => {
134
+ __report('toPrimitive', name);
135
+ return '[import-protection mock]';
136
+ };
137
+ }
138
+ if (prop === 'toString' || prop === 'valueOf' || prop === 'toJSON') {
139
+ return () => {
140
+ __report(String(prop), name);
141
+ return '[import-protection mock]';
142
+ };
143
+ }`
144
+ : ''
145
+
146
+ const applyBody = hasDiag
147
+ ? `__report('call', name + '()');
148
+ return ${fnName}(name + '()');`
149
+ : `return ${fnName}(name + '()');`
150
+
151
+ const constructBody = hasDiag
152
+ ? `__report('construct', 'new ' + name);
153
+ return ${fnName}('new ' + name);`
154
+ : `return ${fnName}('new ' + name);`
155
+
156
+ const setTrap = hasDiag
157
+ ? `
158
+ set(_target, prop) {
159
+ __report('set', name + '.' + String(prop));
160
+ return true;
161
+ },`
162
+ : ''
163
+
164
+ return `
165
+ ${preamble}function ${fnName}(name) {
121
166
  const fn = function () {};
122
167
  fn.prototype.name = name;
123
168
  const children = Object.create(null);
124
169
  const proxy = new Proxy(fn, {
125
- get(target, prop) {
170
+ get(_target, prop) {
126
171
  if (prop === '__esModule') return true;
127
172
  if (prop === 'default') return proxy;
128
173
  if (prop === 'caller') return null;
129
- if (typeof prop === 'symbol') return undefined;
130
- // Thenable support: prevent await from hanging
131
- if (prop === 'then') return (fn) => Promise.resolve(fn(proxy));
174
+ if (prop === 'then') return (f) => Promise.resolve(f(proxy));
132
175
  if (prop === 'catch') return () => Promise.resolve(proxy);
133
- if (prop === 'finally') return (fn) => { fn(); return Promise.resolve(proxy); };
134
- // Memoize child proxies so mock.foo === mock.foo
176
+ if (prop === 'finally') return (f) => { f(); return Promise.resolve(proxy); };${diagGetTraps}
177
+ if (typeof prop === 'symbol') return undefined;
135
178
  if (!(prop in children)) {
136
- children[prop] = createMock(name + '.' + prop);
179
+ children[prop] = ${fnName}(name + '.' + prop);
137
180
  }
138
181
  return children[prop];
139
182
  },
140
183
  apply() {
141
- return createMock(name + '()');
184
+ ${applyBody}
142
185
  },
143
186
  construct() {
144
- return createMock('new ' + name);
145
- },
187
+ ${constructBody}
188
+ },${setTrap}
146
189
  });
147
190
  return proxy;
148
191
  }
149
- const mock = createMock('mock');
192
+ const mock = ${fnName}('mock');
150
193
  export default mock;
151
- `,
152
- }
194
+ `
195
+ }
196
+
197
+ export function loadSilentMockModule(): {
198
+ syntheticNamedExports: boolean
199
+ code: string
200
+ } {
201
+ return { syntheticNamedExports: true, code: generateMockCode() }
153
202
  }
154
203
 
155
204
  export function loadMockEdgeModule(encodedPayload: string): { code: string } {
@@ -161,7 +210,8 @@ export function loadMockEdgeModule(encodedPayload: string): { code: string } {
161
210
  }
162
211
  const names: Array<string> = Array.isArray(payload.exports)
163
212
  ? payload.exports.filter(
164
- (n): n is string => typeof n === 'string' && isValidExportName(n),
213
+ (n): n is string =>
214
+ typeof n === 'string' && n.length > 0 && n !== 'default',
165
215
  )
166
216
  : []
167
217
 
@@ -170,13 +220,33 @@ export function loadMockEdgeModule(encodedPayload: string): { code: string } {
170
220
  ? payload.runtimeId
171
221
  : MOCK_MODULE_ID
172
222
 
173
- const exportLines = names.map((n) => `export const ${n} = mock.${n};`)
223
+ const exportLines: Array<string> = []
224
+ const stringExports: Array<{ alias: string; name: string }> = []
225
+
226
+ for (let i = 0; i < names.length; i++) {
227
+ const n = names[i]!
228
+ if (isValidExportName(n)) {
229
+ exportLines.push(`export const ${n} = mock.${n};`)
230
+ } else {
231
+ // ES2022 string-keyed export: use a temp var + re-export with string literal
232
+ const alias = `__tss_str_${i}`
233
+ exportLines.push(`const ${alias} = mock[${JSON.stringify(n)}];`)
234
+ stringExports.push({ alias, name: n })
235
+ }
236
+ }
237
+
238
+ if (stringExports.length > 0) {
239
+ const reexports = stringExports
240
+ .map((s) => `${s.alias} as ${JSON.stringify(s.name)}`)
241
+ .join(', ')
242
+ exportLines.push(`export { ${reexports} };`)
243
+ }
244
+
174
245
  return {
175
- code: `
176
- import mock from ${JSON.stringify(runtimeId)};
177
- ${exportLines.join('\n')}
178
- export default mock;
179
- `,
246
+ code: `import mock from ${JSON.stringify(runtimeId)};
247
+ ${exportLines.join('\n')}
248
+ export default mock;
249
+ `,
180
250
  }
181
251
  }
182
252
 
@@ -206,95 +276,11 @@ export function loadMockRuntimeModule(encodedPayload: string): {
206
276
  trace: Array.isArray(payload.trace) ? payload.trace : [],
207
277
  }
208
278
 
209
- return {
210
- code: `
211
- const __meta = ${JSON.stringify(meta)};
212
- const __mode = ${JSON.stringify(mode)};
213
-
214
- const __seen = new Set();
215
- function __report(action, accessPath) {
216
- if (__mode === 'off') return;
217
- const key = action + ':' + accessPath;
218
- if (__seen.has(key)) return;
219
- __seen.add(key);
220
-
221
- const traceLines = Array.isArray(__meta.trace) && __meta.trace.length
222
- ? "\\n\\nTrace:\\n" + __meta.trace.map((t, i) => ' ' + (i + 1) + '. ' + String(t)).join('\\n')
223
- : '';
224
-
225
- const msg =
226
- '[import-protection] Mocked import used in dev client\\n\\n' +
227
- 'Denied import: "' + __meta.specifier + '"\\n' +
228
- 'Importer: ' + __meta.importer + '\\n' +
229
- 'Access: ' + accessPath + ' (' + action + ')' +
230
- traceLines +
231
- '\\n\\nFix: Remove server-only imports from client code. Use createServerFn().handler(() => ...) to call server logic from the client via RPC, or move the import into a .server.ts file. To disable these runtime diagnostics, set importProtection.mockAccess: "off".';
232
-
233
- const err = new Error(msg);
234
- if (__mode === 'warn') {
235
- console.warn(err);
236
- } else {
237
- console.error(err);
238
- }
239
- }
240
-
241
- function __createMock(name) {
242
- const fn = function () {};
243
- fn.prototype.name = name;
244
- const children = Object.create(null);
245
-
246
- const proxy = new Proxy(fn, {
247
- get(_target, prop) {
248
- if (prop === '__esModule') return true;
249
- if (prop === 'default') return proxy;
250
- if (prop === 'caller') return null;
251
- if (prop === 'then') return (f) => Promise.resolve(f(proxy));
252
- if (prop === 'catch') return () => Promise.resolve(proxy);
253
- if (prop === 'finally') return (f) => { f(); return Promise.resolve(proxy); };
254
-
255
- // Trigger a runtime diagnostic for primitive conversions.
256
- if (prop === Symbol.toPrimitive) {
257
- return () => {
258
- __report('toPrimitive', name);
259
- return '[import-protection mock]';
260
- };
261
- }
262
- if (prop === 'toString' || prop === 'valueOf' || prop === 'toJSON') {
263
- return () => {
264
- __report(String(prop), name);
265
- return '[import-protection mock]';
266
- };
267
- }
268
-
269
- if (typeof prop === 'symbol') return undefined;
270
- if (!(prop in children)) {
271
- children[prop] = __createMock(name + '.' + prop);
272
- }
273
- return children[prop];
274
- },
275
- apply() {
276
- __report('call', name + '()');
277
- return __createMock(name + '()');
278
- },
279
- construct() {
280
- __report('construct', 'new ' + name);
281
- return __createMock('new ' + name);
282
- },
283
- set(_target, prop) {
284
- __report('set', name + '.' + String(prop));
285
- return true;
286
- },
287
- });
288
-
289
- return proxy;
279
+ return { code: generateMockCode({ meta, mode }) }
290
280
  }
291
281
 
292
- const mock = __createMock('mock');
293
- export default mock;
294
- `,
295
- }
296
- }
282
+ const MARKER_MODULE_RESULT = { code: 'export {}' } as const
297
283
 
298
284
  export function loadMarkerModule(): { code: string } {
299
- return { code: 'export {}' }
285
+ return MARKER_MODULE_RESULT
300
286
  }