@tanstack/start-plugin-core 1.161.4 → 1.162.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.
- package/dist/esm/import-protection-plugin/defaults.d.ts +6 -4
- package/dist/esm/import-protection-plugin/defaults.js +3 -12
- package/dist/esm/import-protection-plugin/defaults.js.map +1 -1
- package/dist/esm/import-protection-plugin/plugin.d.ts +1 -1
- package/dist/esm/import-protection-plugin/plugin.js +488 -257
- package/dist/esm/import-protection-plugin/plugin.js.map +1 -1
- package/dist/esm/import-protection-plugin/postCompileUsage.d.ts +4 -2
- package/dist/esm/import-protection-plugin/postCompileUsage.js +31 -150
- package/dist/esm/import-protection-plugin/postCompileUsage.js.map +1 -1
- package/dist/esm/import-protection-plugin/rewriteDeniedImports.js +13 -9
- package/dist/esm/import-protection-plugin/rewriteDeniedImports.js.map +1 -1
- package/dist/esm/import-protection-plugin/sourceLocation.d.ts +32 -66
- package/dist/esm/import-protection-plugin/sourceLocation.js +129 -56
- package/dist/esm/import-protection-plugin/sourceLocation.js.map +1 -1
- package/dist/esm/import-protection-plugin/trace.d.ts +10 -0
- package/dist/esm/import-protection-plugin/trace.js +30 -44
- package/dist/esm/import-protection-plugin/trace.js.map +1 -1
- package/dist/esm/import-protection-plugin/utils.d.ts +8 -4
- package/dist/esm/import-protection-plugin/utils.js +43 -1
- package/dist/esm/import-protection-plugin/utils.js.map +1 -1
- package/dist/esm/import-protection-plugin/virtualModules.d.ts +7 -1
- package/dist/esm/import-protection-plugin/virtualModules.js +104 -135
- package/dist/esm/import-protection-plugin/virtualModules.js.map +1 -1
- package/package.json +2 -2
- package/src/import-protection-plugin/defaults.ts +8 -19
- package/src/import-protection-plugin/plugin.ts +776 -433
- package/src/import-protection-plugin/postCompileUsage.ts +57 -229
- package/src/import-protection-plugin/rewriteDeniedImports.ts +34 -42
- package/src/import-protection-plugin/sourceLocation.ts +184 -185
- package/src/import-protection-plugin/trace.ts +38 -49
- package/src/import-protection-plugin/utils.ts +62 -1
- package/src/import-protection-plugin/virtualModules.ts +163 -177
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
106
|
+
let qi = 0
|
|
117
107
|
|
|
118
108
|
let root: string | null = null
|
|
119
109
|
|
|
120
|
-
while (
|
|
121
|
-
const node = queue[
|
|
122
|
-
const depth = depthByNode.get(node)
|
|
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
|
-
|
|
259
|
-
` -
|
|
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
|
-
|
|
284
|
-
` -
|
|
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
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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(
|
|
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 (
|
|
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 (
|
|
134
|
-
|
|
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] =
|
|
179
|
+
children[prop] = ${fnName}(name + '.' + prop);
|
|
137
180
|
}
|
|
138
181
|
return children[prop];
|
|
139
182
|
},
|
|
140
183
|
apply() {
|
|
141
|
-
|
|
184
|
+
${applyBody}
|
|
142
185
|
},
|
|
143
186
|
construct() {
|
|
144
|
-
|
|
145
|
-
}
|
|
187
|
+
${constructBody}
|
|
188
|
+
},${setTrap}
|
|
146
189
|
});
|
|
147
190
|
return proxy;
|
|
148
191
|
}
|
|
149
|
-
const 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 =>
|
|
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
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
|
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
|
|
285
|
+
return MARKER_MODULE_RESULT
|
|
300
286
|
}
|