@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.js";
|
|
2
2
|
class ImportGraph {
|
|
3
3
|
/**
|
|
4
4
|
* resolvedId -> Map<importer, specifier>
|
|
@@ -16,18 +16,11 @@ class ImportGraph {
|
|
|
16
16
|
forwardEdges = /* @__PURE__ */ new Map();
|
|
17
17
|
entries = /* @__PURE__ */ new Set();
|
|
18
18
|
addEdge(resolved, importer, specifier) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
importers.set(importer, specifier);
|
|
25
|
-
let targets = this.forwardEdges.get(importer);
|
|
26
|
-
if (!targets) {
|
|
27
|
-
targets = /* @__PURE__ */ new Set();
|
|
28
|
-
this.forwardEdges.set(importer, targets);
|
|
29
|
-
}
|
|
30
|
-
targets.add(resolved);
|
|
19
|
+
getOrCreate(this.reverseEdges, resolved, () => /* @__PURE__ */ new Map()).set(
|
|
20
|
+
importer,
|
|
21
|
+
specifier
|
|
22
|
+
);
|
|
23
|
+
getOrCreate(this.forwardEdges, importer, () => /* @__PURE__ */ new Set()).add(resolved);
|
|
31
24
|
}
|
|
32
25
|
/** Convenience for tests/debugging. */
|
|
33
26
|
getEdges(resolved) {
|
|
@@ -63,11 +56,11 @@ function buildTrace(graph, startNode, maxDepth = 20) {
|
|
|
63
56
|
const depthByNode = /* @__PURE__ */ new Map([[startNode, 0]]);
|
|
64
57
|
const down = /* @__PURE__ */ new Map();
|
|
65
58
|
const queue = [startNode];
|
|
66
|
-
let
|
|
59
|
+
let qi = 0;
|
|
67
60
|
let root = null;
|
|
68
|
-
while (
|
|
69
|
-
const node = queue[
|
|
70
|
-
const depth = depthByNode.get(node)
|
|
61
|
+
while (qi < queue.length) {
|
|
62
|
+
const node = queue[qi++];
|
|
63
|
+
const depth = depthByNode.get(node);
|
|
71
64
|
const importers = graph.reverseEdges.get(node);
|
|
72
65
|
if (node !== startNode) {
|
|
73
66
|
const isEntry = graph.entries.has(node) || !importers || importers.size === 0;
|
|
@@ -103,13 +96,19 @@ function buildTrace(graph, startNode, maxDepth = 20) {
|
|
|
103
96
|
}
|
|
104
97
|
return trace;
|
|
105
98
|
}
|
|
99
|
+
const CLIENT_ENV_SUGGESTIONS = [
|
|
100
|
+
"Use createServerFn().handler(() => ...) to keep the logic on the server and call it from the client via an RPC bridge",
|
|
101
|
+
"Use createServerOnlyFn(() => ...) to mark it as server-only (it will throw if accidentally called from the client)",
|
|
102
|
+
"Use createIsomorphicFn().client(() => ...).server(() => ...) to provide separate client and server implementations",
|
|
103
|
+
"Move the server-only import out of this file into a separate .server.ts module that is not imported by any client code"
|
|
104
|
+
];
|
|
105
|
+
const SERVER_ENV_SUGGESTIONS = [
|
|
106
|
+
"Use createClientOnlyFn(() => ...) to mark it as client-only (returns undefined on the server)",
|
|
107
|
+
"Use createIsomorphicFn().client(() => ...).server(() => ...) to provide separate client and server implementations",
|
|
108
|
+
"Move the client-only import out of this file into a separate .client.ts module that is not imported by any server code"
|
|
109
|
+
];
|
|
106
110
|
function formatViolation(info, root) {
|
|
107
|
-
const rel = (p) =>
|
|
108
|
-
if (p.startsWith(root)) {
|
|
109
|
-
return path.relative(root, p);
|
|
110
|
-
}
|
|
111
|
-
return p;
|
|
112
|
-
};
|
|
111
|
+
const rel = (p) => relativizePath(p, root);
|
|
113
112
|
const relLoc = (p, loc) => {
|
|
114
113
|
const r = rel(p);
|
|
115
114
|
const file = loc?.file ? rel(loc.file) : r;
|
|
@@ -162,18 +161,9 @@ function formatViolation(info, root) {
|
|
|
162
161
|
lines.push(``);
|
|
163
162
|
if (info.envType === "client") {
|
|
164
163
|
lines.push(` Suggestions:`);
|
|
165
|
-
|
|
166
|
-
` -
|
|
167
|
-
|
|
168
|
-
lines.push(
|
|
169
|
-
` - Use createServerOnlyFn(() => ...) to mark it as server-only (it will throw if accidentally called from the client)`
|
|
170
|
-
);
|
|
171
|
-
lines.push(
|
|
172
|
-
` - Use createIsomorphicFn().client(() => ...).server(() => ...) to provide separate client and server implementations`
|
|
173
|
-
);
|
|
174
|
-
lines.push(
|
|
175
|
-
` - Move the server-only import out of this file into a separate .server.ts module that is not imported by any client code`
|
|
176
|
-
);
|
|
164
|
+
for (const s of CLIENT_ENV_SUGGESTIONS) {
|
|
165
|
+
lines.push(` - ${s}`);
|
|
166
|
+
}
|
|
177
167
|
} else {
|
|
178
168
|
const snippetText = info.snippet?.lines.join("\n") ?? "";
|
|
179
169
|
const looksLikeJsx = /<[A-Z]/.test(snippetText) || /\{.*\(.*\).*\}/.test(snippetText) && /</.test(snippetText);
|
|
@@ -183,21 +173,17 @@ function formatViolation(info, root) {
|
|
|
183
173
|
` - Wrap the JSX in <ClientOnly fallback={<Loading />}>...</ClientOnly> so it only renders in the browser after hydration`
|
|
184
174
|
);
|
|
185
175
|
}
|
|
186
|
-
|
|
187
|
-
` -
|
|
188
|
-
|
|
189
|
-
lines.push(
|
|
190
|
-
` - Use createIsomorphicFn().client(() => ...).server(() => ...) to provide separate client and server implementations`
|
|
191
|
-
);
|
|
192
|
-
lines.push(
|
|
193
|
-
` - Move the client-only import out of this file into a separate .client.ts module that is not imported by any server code`
|
|
194
|
-
);
|
|
176
|
+
for (const s of SERVER_ENV_SUGGESTIONS) {
|
|
177
|
+
lines.push(` - ${s}`);
|
|
178
|
+
}
|
|
195
179
|
}
|
|
196
180
|
lines.push(``);
|
|
197
181
|
return lines.join("\n");
|
|
198
182
|
}
|
|
199
183
|
export {
|
|
184
|
+
CLIENT_ENV_SUGGESTIONS,
|
|
200
185
|
ImportGraph,
|
|
186
|
+
SERVER_ENV_SUGGESTIONS,
|
|
201
187
|
buildTrace,
|
|
202
188
|
formatViolation
|
|
203
189
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trace.js","sources":["../../../src/import-protection-plugin/trace.ts"],"sourcesContent":["import * as path from 'pathe'\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 let importers = this.reverseEdges.get(resolved)\n if (!importers) {\n importers = new Map()\n this.reverseEdges.set(resolved, importers)\n }\n // Last writer wins; good enough for trace display.\n importers.set(importer, specifier)\n\n // Maintain forward index\n let targets = this.forwardEdges.get(importer)\n if (!targets) {\n targets = new Set()\n this.forwardEdges.set(importer, targets)\n }\n targets.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 queueIndex = 0\n\n let root: string | null = null\n\n while (queueIndex < queue.length) {\n const node = queue[queueIndex++]!\n const depth = depthByNode.get(node) ?? 0\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 message: string\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\nexport function formatViolation(info: ViolationInfo, root: string): string {\n const rel = (p: string) => {\n if (p.startsWith(root)) {\n return path.relative(root, p)\n }\n return p\n }\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 // Server-only code leaking into the client environment\n lines.push(` Suggestions:`)\n lines.push(\n ` - Use createServerFn().handler(() => ...) to keep the logic on the server and call it from the client via an RPC bridge`,\n )\n lines.push(\n ` - Use createServerOnlyFn(() => ...) to mark it as server-only (it will throw if accidentally called from the client)`,\n )\n lines.push(\n ` - Use createIsomorphicFn().client(() => ...).server(() => ...) to provide separate client and server implementations`,\n )\n lines.push(\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 )\n } else {\n // Client-only code leaking into the server environment\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 lines.push(\n ` - Use createClientOnlyFn(() => ...) to mark it as client-only (returns undefined on the server)`,\n )\n lines.push(\n ` - Use createIsomorphicFn().client(() => ...).server(() => ...) to provide separate client and server implementations`,\n )\n lines.push(\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 )\n }\n\n lines.push(``)\n return lines.join('\\n')\n}\n"],"names":[],"mappings":";AAWO,MAAM,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMd,mCACH,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASW,mCAA6C,IAAA;AAAA,EAErD,8BAA2B,IAAA;AAAA,EAEpC,QAAQ,UAAkB,UAAkB,WAA0B;AACpE,QAAI,YAAY,KAAK,aAAa,IAAI,QAAQ;AAC9C,QAAI,CAAC,WAAW;AACd,sCAAgB,IAAA;AAChB,WAAK,aAAa,IAAI,UAAU,SAAS;AAAA,IAC3C;AAEA,cAAU,IAAI,UAAU,SAAS;AAGjC,QAAI,UAAU,KAAK,aAAa,IAAI,QAAQ;AAC5C,QAAI,CAAC,SAAS;AACZ,oCAAc,IAAA;AACd,WAAK,aAAa,IAAI,UAAU,OAAO;AAAA,IACzC;AACA,YAAQ,IAAI,QAAQ;AAAA,EACtB;AAAA;AAAA,EAGA,SAAS,UAA8C;AACrD,UAAM,YAAY,KAAK,aAAa,IAAI,QAAQ;AAChD,QAAI,CAAC,UAAW,QAAO;AACvB,UAAM,0BAAU,IAAA;AAChB,eAAW,CAAC,UAAU,SAAS,KAAK,WAAW;AAC7C,UAAI,IAAI,EAAE,UAAU,UAAA,CAAW;AAAA,IACjC;AACA,WAAO;AAAA,EACT;AAAA,EAEA,SAAS,IAAkB;AACzB,SAAK,QAAQ,IAAI,EAAE;AAAA,EACrB;AAAA,EAEA,QAAc;AACZ,SAAK,aAAa,MAAA;AAClB,SAAK,aAAa,MAAA;AAClB,SAAK,QAAQ,MAAA;AAAA,EACf;AAAA,EAEA,WAAW,IAAkB;AAE3B,UAAM,UAAU,KAAK,aAAa,IAAI,EAAE;AACxC,QAAI,SAAS;AACX,iBAAW,YAAY,SAAS;AAC9B,aAAK,aAAa,IAAI,QAAQ,GAAG,OAAO,EAAE;AAAA,MAC5C;AACA,WAAK,aAAa,OAAO,EAAE;AAAA,IAC7B;AAEA,SAAK,aAAa,OAAO,EAAE;AAAA,EAC7B;AACF;AAmBO,SAAS,WACd,OACA,WACA,WAAmB,IACD;AAElB,QAAM,UAAU,oBAAI,IAAY,CAAC,SAAS,CAAC;AAC3C,QAAM,kCAAkB,IAAoB,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC;AAI5D,QAAM,2BAAW,IAAA;AAEjB,QAAM,QAAuB,CAAC,SAAS;AACvC,MAAI,aAAa;AAEjB,MAAI,OAAsB;AAE1B,SAAO,aAAa,MAAM,QAAQ;AAChC,UAAM,OAAO,MAAM,YAAY;AAC/B,UAAM,QAAQ,YAAY,IAAI,IAAI,KAAK;AACvC,UAAM,YAAY,MAAM,aAAa,IAAI,IAAI;AAE7C,QAAI,SAAS,WAAW;AACtB,YAAM,UACJ,MAAM,QAAQ,IAAI,IAAI,KAAK,CAAC,aAAa,UAAU,SAAS;AAC9D,UAAI,SAAS;AACX,eAAO;AACP;AAAA,MACF;AAAA,IACF;AAEA,QAAI,SAAS,UAAU;AACrB;AAAA,IACF;AAEA,QAAI,CAAC,aAAa,UAAU,SAAS,GAAG;AACtC;AAAA,IACF;AAEA,eAAW,CAAC,UAAU,SAAS,KAAK,WAAW;AAC7C,UAAI,QAAQ,IAAI,QAAQ,EAAG;AAC3B,cAAQ,IAAI,QAAQ;AACpB,kBAAY,IAAI,UAAU,QAAQ,CAAC;AACnC,WAAK,IAAI,UAAU,EAAE,MAAM,MAAM,WAAW;AAC5C,YAAM,KAAK,QAAQ;AAAA,IACrB;AAAA,EACF;AAGA,MAAI,CAAC,MAAM;AACT,WAAO;AAAA,EACT;AAEA,QAAM,QAA0B,CAAA;AAChC,MAAI,UAAU;AACd,WAAS,IAAI,GAAG,KAAK,WAAW,GAAG,KAAK;AACtC,UAAM,OAAO,KAAK,IAAI,OAAO;AAC7B,UAAM,KAAK,EAAE,MAAM,SAAS,WAAW,MAAM,WAAW;AACxD,QAAI,CAAC,KAAM;AACX,cAAU,KAAK;AAAA,EACjB;AAEA,SAAO;AACT;AAsBO,SAAS,gBAAgB,MAAqB,MAAsB;AACzE,QAAM,MAAM,CAAC,MAAc;AACzB,QAAI,EAAE,WAAW,IAAI,GAAG;AACtB,aAAO,KAAK,SAAS,MAAM,CAAC;AAAA,IAC9B;AACA,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,CAAC,GAAW,QAAc;AACvC,UAAM,IAAI,IAAI,CAAC;AACf,UAAM,OAAO,KAAK,OAAO,IAAI,IAAI,IAAI,IAAI;AACzC,WAAO,MAAM,GAAG,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,MAAM,KAAK;AAAA,EACrD;AAEA,QAAM,eAAe,CAAC,SAA4B;AAChD,UAAM,OAAO,IAAI,KAAK,IAAI;AAC1B,QAAI,KAAK,QAAQ,KAAM,QAAO;AAC9B,UAAM,MAAM,KAAK,UAAU;AAC3B,WAAO,GAAG,IAAI,IAAI,KAAK,IAAI,IAAI,GAAG;AAAA,EACpC;AAEA,QAAM,QAAuB,CAAA;AAC7B,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,wCAAwC,KAAK,OAAO,cAAc;AAC7E,QAAM,KAAK,EAAE;AAEb,MAAI,KAAK,SAAS,aAAa;AAC7B,UAAM,KAAK,kCAAkC,OAAO,KAAK,OAAO,CAAC,EAAE;AAAA,EACrE,WAAW,KAAK,SAAS,QAAQ;AAC/B,UAAM,KAAK,6BAA6B,OAAO,KAAK,OAAO,CAAC,EAAE;AAAA,EAChE,OAAO;AACL,UAAM;AAAA,MACJ;AAAA,IAAA;AAAA,EAEJ;AAEA,QAAM,KAAK,eAAe,OAAO,KAAK,UAAU,KAAK,WAAW,CAAC,EAAE;AACnE,QAAM,KAAK,cAAc,IAAI,KAAK,SAAS,CAAC,GAAG;AAC/C,MAAI,KAAK,UAAU;AACjB,UAAM,KAAK,eAAe,IAAI,KAAK,QAAQ,CAAC,EAAE;AAAA,EAChD;AAEA,MAAI,KAAK,MAAM,SAAS,GAAG;AACzB,UAAM,KAAK,EAAE;AACb,UAAM,KAAK,UAAU;AACrB,aAAS,IAAI,GAAG,IAAI,KAAK,MAAM,QAAQ,KAAK;AAC1C,YAAM,OAAO,KAAK,MAAM,CAAC;AACzB,YAAM,UAAU,MAAM;AACtB,YAAM,MAAM,UAAU,aAAa;AACnC,YAAM,OAAO,KAAK,YAAY,aAAa,IAAI,KAAK,SAAS,CAAC,OAAO;AACrE,YAAM,KAAK,OAAO,IAAI,CAAC,KAAK,aAAa,IAAI,CAAC,GAAG,GAAG,GAAG,IAAI,EAAE;AAAA,IAC/D;AAAA,EACF;AAEA,MAAI,KAAK,SAAS;AAChB,UAAM,KAAK,EAAE;AACb,UAAM,KAAK,SAAS;AACpB,eAAW,eAAe,KAAK,QAAQ,OAAO;AAC5C,YAAM,KAAK,WAAW;AAAA,IACxB;AACA,UAAM,KAAK,EAAE;AACb,UAAM,KAAK,KAAK,IAAI,KAAK,QAAQ,QAAQ,CAAC,EAAE;AAAA,EAC9C;AAEA,QAAM,KAAK,EAAE;AAGb,MAAI,KAAK,YAAY,UAAU;AAE7B,UAAM,KAAK,gBAAgB;AAC3B,UAAM;AAAA,MACJ;AAAA,IAAA;AAEF,UAAM;AAAA,MACJ;AAAA,IAAA;AAEF,UAAM;AAAA,MACJ;AAAA,IAAA;AAEF,UAAM;AAAA,MACJ;AAAA,IAAA;AAAA,EAEJ,OAAO;AAEL,UAAM,cAAc,KAAK,SAAS,MAAM,KAAK,IAAI,KAAK;AACtD,UAAM,eACJ,SAAS,KAAK,WAAW,KACxB,iBAAiB,KAAK,WAAW,KAAK,IAAI,KAAK,WAAW;AAE7D,UAAM,KAAK,gBAAgB;AAC3B,QAAI,cAAc;AAChB,YAAM;AAAA,QACJ;AAAA,MAAA;AAAA,IAEJ;AACA,UAAM;AAAA,MACJ;AAAA,IAAA;AAEF,UAAM;AAAA,MACJ;AAAA,IAAA;AAEF,UAAM;AAAA,MACJ;AAAA,IAAA;AAAA,EAEJ;AAEA,QAAM,KAAK,EAAE;AACb,SAAO,MAAM,KAAK,IAAI;AACxB;"}
|
|
1
|
+
{"version":3,"file":"trace.js","sources":["../../../src/import-protection-plugin/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 message: string\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"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
export type Pattern = string | RegExp;
|
|
2
2
|
export declare function dedupePatterns(patterns: Array<Pattern>): Array<Pattern>;
|
|
3
3
|
export declare function stripViteQuery(id: string): string;
|
|
4
|
-
/**
|
|
5
|
-
* Strip Vite query parameters and normalize the path in one step.
|
|
6
|
-
* Replaces the repeated `normalizePath(stripViteQuery(id))` pattern.
|
|
7
|
-
*/
|
|
8
4
|
export declare function normalizeFilePath(id: string): string;
|
|
5
|
+
/** Clear the memoization cache (call from buildStart to bound growth). */
|
|
6
|
+
export declare function clearNormalizeFilePathCache(): void;
|
|
7
|
+
export declare function escapeRegExp(s: string): string;
|
|
8
|
+
/** Get a value from a Map, creating it with `factory` if absent. */
|
|
9
|
+
export declare function getOrCreate<TKey, TValue>(map: Map<TKey, TValue>, key: TKey, factory: () => TValue): TValue;
|
|
10
|
+
/** Make a path relative to `root`, keeping non-rooted paths as-is. */
|
|
11
|
+
export declare function relativizePath(p: string, root: string): string;
|
|
12
|
+
export declare function extractImportSources(code: string): Array<string>;
|
|
@@ -18,12 +18,54 @@ function stripViteQuery(id) {
|
|
|
18
18
|
if (h === -1) return id.slice(0, q);
|
|
19
19
|
return id.slice(0, Math.min(q, h));
|
|
20
20
|
}
|
|
21
|
+
const normalizeFilePathCache = /* @__PURE__ */ new Map();
|
|
21
22
|
function normalizeFilePath(id) {
|
|
22
|
-
|
|
23
|
+
let result = normalizeFilePathCache.get(id);
|
|
24
|
+
if (result === void 0) {
|
|
25
|
+
result = normalizePath(stripViteQuery(id));
|
|
26
|
+
normalizeFilePathCache.set(id, result);
|
|
27
|
+
}
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
function clearNormalizeFilePathCache() {
|
|
31
|
+
normalizeFilePathCache.clear();
|
|
32
|
+
}
|
|
33
|
+
const importSourceRe = /\bfrom\s+(?:"([^"]+)"|'([^']+)')|import\s*\(\s*(?:"([^"]+)"|'([^']+)')\s*\)/g;
|
|
34
|
+
function escapeRegExp(s) {
|
|
35
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
36
|
+
}
|
|
37
|
+
function getOrCreate(map, key, factory) {
|
|
38
|
+
let value = map.get(key);
|
|
39
|
+
if (value === void 0) {
|
|
40
|
+
value = factory();
|
|
41
|
+
map.set(key, value);
|
|
42
|
+
}
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
function relativizePath(p, root) {
|
|
46
|
+
if (!p.startsWith(root)) return p;
|
|
47
|
+
const ch = p.charCodeAt(root.length);
|
|
48
|
+
if (ch !== 47 && !Number.isNaN(ch)) return p;
|
|
49
|
+
return ch === 47 ? p.slice(root.length + 1) : p.slice(root.length);
|
|
50
|
+
}
|
|
51
|
+
function extractImportSources(code) {
|
|
52
|
+
const sources = [];
|
|
53
|
+
let m;
|
|
54
|
+
importSourceRe.lastIndex = 0;
|
|
55
|
+
while ((m = importSourceRe.exec(code)) !== null) {
|
|
56
|
+
const src = m[1] ?? m[2] ?? m[3] ?? m[4];
|
|
57
|
+
if (src) sources.push(src);
|
|
58
|
+
}
|
|
59
|
+
return sources;
|
|
23
60
|
}
|
|
24
61
|
export {
|
|
62
|
+
clearNormalizeFilePathCache,
|
|
25
63
|
dedupePatterns,
|
|
64
|
+
escapeRegExp,
|
|
65
|
+
extractImportSources,
|
|
66
|
+
getOrCreate,
|
|
26
67
|
normalizeFilePath,
|
|
68
|
+
relativizePath,
|
|
27
69
|
stripViteQuery
|
|
28
70
|
};
|
|
29
71
|
//# sourceMappingURL=utils.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sources":["../../../src/import-protection-plugin/utils.ts"],"sourcesContent":["import { normalizePath } from 'vite'\n\nexport type Pattern = string | RegExp\n\nexport function dedupePatterns(patterns: Array<Pattern>): Array<Pattern> {\n const out: Array<Pattern> = []\n const seen = new Set<string>()\n for (const p of patterns) {\n const key = typeof p === 'string' ? `s:${p}` : `r:${p.toString()}`\n if (seen.has(key)) continue\n seen.add(key)\n out.push(p)\n }\n return out\n}\n\nexport function stripViteQuery(id: string): string {\n const q = id.indexOf('?')\n const h = id.indexOf('#')\n if (q === -1 && h === -1) return id\n if (q === -1) return id.slice(0, h)\n if (h === -1) return id.slice(0, q)\n return id.slice(0, Math.min(q, h))\n}\n\n/**\n * Strip Vite query parameters and normalize the path in one step.\n * Replaces the repeated `normalizePath(stripViteQuery(id))` pattern.\n */\nexport function normalizeFilePath(id: string): string {\n
|
|
1
|
+
{"version":3,"file":"utils.js","sources":["../../../src/import-protection-plugin/utils.ts"],"sourcesContent":["import { normalizePath } from 'vite'\n\nexport type Pattern = string | RegExp\n\nexport function dedupePatterns(patterns: Array<Pattern>): Array<Pattern> {\n const out: Array<Pattern> = []\n const seen = new Set<string>()\n for (const p of patterns) {\n const key = typeof p === 'string' ? `s:${p}` : `r:${p.toString()}`\n if (seen.has(key)) continue\n seen.add(key)\n out.push(p)\n }\n return out\n}\n\nexport function stripViteQuery(id: string): string {\n const q = id.indexOf('?')\n const h = id.indexOf('#')\n if (q === -1 && h === -1) return id\n if (q === -1) return id.slice(0, h)\n if (h === -1) return id.slice(0, q)\n return id.slice(0, Math.min(q, h))\n}\n\n/**\n * Strip Vite query parameters and normalize the path in one step.\n * Replaces the repeated `normalizePath(stripViteQuery(id))` pattern.\n *\n * Results are memoized because the same module IDs are processed many\n * times across resolveId, transform, and trace-building hooks.\n */\nconst normalizeFilePathCache = new Map<string, string>()\nexport function normalizeFilePath(id: string): string {\n let result = normalizeFilePathCache.get(id)\n if (result === undefined) {\n result = normalizePath(stripViteQuery(id))\n normalizeFilePathCache.set(id, result)\n }\n return result\n}\n\n/** Clear the memoization cache (call from buildStart to bound growth). */\nexport function clearNormalizeFilePathCache(): void {\n normalizeFilePathCache.clear()\n}\n\n/**\n * Lightweight regex to extract all import/re-export source strings from\n * post-transform code. Matches:\n * - `from \"...\"` / `from '...'` (static import/export)\n * - `import(\"...\")` / `import('...')` (dynamic import)\n */\nconst importSourceRe =\n /\\bfrom\\s+(?:\"([^\"]+)\"|'([^']+)')|import\\s*\\(\\s*(?:\"([^\"]+)\"|'([^']+)')\\s*\\)/g\n\nexport function escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\n/** Get a value from a Map, creating it with `factory` if absent. */\nexport function getOrCreate<TKey, TValue>(\n map: Map<TKey, TValue>,\n key: TKey,\n factory: () => TValue,\n): TValue {\n let value = map.get(key)\n if (value === undefined) {\n value = factory()\n map.set(key, value)\n }\n return value\n}\n\n/** Make a path relative to `root`, keeping non-rooted paths as-is. */\nexport function relativizePath(p: string, root: string): string {\n if (!p.startsWith(root)) return p\n const ch = p.charCodeAt(root.length)\n // Must be followed by a separator or end-of-string to be a true child\n if (ch !== 47 && !Number.isNaN(ch)) return p\n return ch === 47 ? p.slice(root.length + 1) : p.slice(root.length)\n}\n\nexport function extractImportSources(code: string): Array<string> {\n const sources: Array<string> = []\n let m: RegExpExecArray | null\n importSourceRe.lastIndex = 0\n while ((m = importSourceRe.exec(code)) !== null) {\n const src = m[1] ?? m[2] ?? m[3] ?? m[4]\n if (src) sources.push(src)\n }\n return sources\n}\n"],"names":[],"mappings":";AAIO,SAAS,eAAe,UAA0C;AACvE,QAAM,MAAsB,CAAA;AAC5B,QAAM,2BAAW,IAAA;AACjB,aAAW,KAAK,UAAU;AACxB,UAAM,MAAM,OAAO,MAAM,WAAW,KAAK,CAAC,KAAK,KAAK,EAAE,SAAA,CAAU;AAChE,QAAI,KAAK,IAAI,GAAG,EAAG;AACnB,SAAK,IAAI,GAAG;AACZ,QAAI,KAAK,CAAC;AAAA,EACZ;AACA,SAAO;AACT;AAEO,SAAS,eAAe,IAAoB;AACjD,QAAM,IAAI,GAAG,QAAQ,GAAG;AACxB,QAAM,IAAI,GAAG,QAAQ,GAAG;AACxB,MAAI,MAAM,MAAM,MAAM,GAAI,QAAO;AACjC,MAAI,MAAM,GAAI,QAAO,GAAG,MAAM,GAAG,CAAC;AAClC,MAAI,MAAM,GAAI,QAAO,GAAG,MAAM,GAAG,CAAC;AAClC,SAAO,GAAG,MAAM,GAAG,KAAK,IAAI,GAAG,CAAC,CAAC;AACnC;AASA,MAAM,6CAA6B,IAAA;AAC5B,SAAS,kBAAkB,IAAoB;AACpD,MAAI,SAAS,uBAAuB,IAAI,EAAE;AAC1C,MAAI,WAAW,QAAW;AACxB,aAAS,cAAc,eAAe,EAAE,CAAC;AACzC,2BAAuB,IAAI,IAAI,MAAM;AAAA,EACvC;AACA,SAAO;AACT;AAGO,SAAS,8BAAoC;AAClD,yBAAuB,MAAA;AACzB;AAQA,MAAM,iBACJ;AAEK,SAAS,aAAa,GAAmB;AAC9C,SAAO,EAAE,QAAQ,uBAAuB,MAAM;AAChD;AAGO,SAAS,YACd,KACA,KACA,SACQ;AACR,MAAI,QAAQ,IAAI,IAAI,GAAG;AACvB,MAAI,UAAU,QAAW;AACvB,YAAQ,QAAA;AACR,QAAI,IAAI,KAAK,KAAK;AAAA,EACpB;AACA,SAAO;AACT;AAGO,SAAS,eAAe,GAAW,MAAsB;AAC9D,MAAI,CAAC,EAAE,WAAW,IAAI,EAAG,QAAO;AAChC,QAAM,KAAK,EAAE,WAAW,KAAK,MAAM;AAEnC,MAAI,OAAO,MAAM,CAAC,OAAO,MAAM,EAAE,EAAG,QAAO;AAC3C,SAAO,OAAO,KAAK,EAAE,MAAM,KAAK,SAAS,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM;AACnE;AAEO,SAAS,qBAAqB,MAA6B;AAChE,QAAM,UAAyB,CAAA;AAC/B,MAAI;AACJ,iBAAe,YAAY;AAC3B,UAAQ,IAAI,eAAe,KAAK,IAAI,OAAO,MAAM;AAC/C,UAAM,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,CAAC;AACvC,QAAI,IAAK,SAAQ,KAAK,GAAG;AAAA,EAC3B;AACA,SAAO;AACT;"}
|
|
@@ -7,7 +7,12 @@ export declare const MOCK_RUNTIME_PREFIX = "tanstack-start-import-protection:moc
|
|
|
7
7
|
export declare const RESOLVED_MOCK_RUNTIME_PREFIX: string;
|
|
8
8
|
export declare const MARKER_PREFIX = "tanstack-start-import-protection:marker:";
|
|
9
9
|
export declare const RESOLVED_MARKER_PREFIX: string;
|
|
10
|
-
|
|
10
|
+
type MockAccessMode = 'error' | 'warn' | 'off';
|
|
11
|
+
/**
|
|
12
|
+
* Compact runtime suggestion text for browser console, derived from
|
|
13
|
+
* {@link CLIENT_ENV_SUGGESTIONS} so there's a single source of truth.
|
|
14
|
+
*/
|
|
15
|
+
export declare const RUNTIME_SUGGESTION_TEXT: string;
|
|
11
16
|
export declare function mockRuntimeModuleIdFromViolation(info: ViolationInfo, mode: MockAccessMode, root: string): string;
|
|
12
17
|
export declare function makeMockEdgeModuleId(exports: Array<string>, source: string, runtimeId: string): string;
|
|
13
18
|
export declare function loadSilentMockModule(): {
|
|
@@ -23,3 +28,4 @@ export declare function loadMockRuntimeModule(encodedPayload: string): {
|
|
|
23
28
|
export declare function loadMarkerModule(): {
|
|
24
29
|
code: string;
|
|
25
30
|
};
|
|
31
|
+
export {};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { normalizePath } from "vite";
|
|
2
|
-
import * as path from "pathe";
|
|
3
1
|
import { resolveViteId } from "../utils.js";
|
|
4
2
|
import { VITE_ENVIRONMENT_NAMES } from "../constants.js";
|
|
5
3
|
import { isValidExportName } from "./rewriteDeniedImports.js";
|
|
4
|
+
import { CLIENT_ENV_SUGGESTIONS } from "./trace.js";
|
|
5
|
+
import { relativizePath } from "./utils.js";
|
|
6
6
|
const MOCK_MODULE_ID = "tanstack-start-import-protection:mock";
|
|
7
7
|
const RESOLVED_MOCK_MODULE_ID = resolveViteId(MOCK_MODULE_ID);
|
|
8
8
|
const MOCK_EDGE_PREFIX = "tanstack-start-import-protection:mock-edge:";
|
|
@@ -17,118 +17,34 @@ function toBase64Url(input) {
|
|
|
17
17
|
function fromBase64Url(input) {
|
|
18
18
|
return Buffer.from(input, "base64url").toString("utf8");
|
|
19
19
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const rel = (p) =>
|
|
25
|
-
|
|
26
|
-
return p;
|
|
27
|
-
};
|
|
28
|
-
return trace.map((s) => {
|
|
20
|
+
const RUNTIME_SUGGESTION_TEXT = "Fix: " + CLIENT_ENV_SUGGESTIONS.join(". ") + '. To disable these runtime diagnostics, set importProtection.mockAccess: "off".';
|
|
21
|
+
function mockRuntimeModuleIdFromViolation(info, mode, root) {
|
|
22
|
+
if (mode === "off") return MOCK_MODULE_ID;
|
|
23
|
+
if (info.env !== VITE_ENVIRONMENT_NAMES.client) return MOCK_MODULE_ID;
|
|
24
|
+
const rel = (p) => relativizePath(p, root);
|
|
25
|
+
const trace = info.trace.map((s) => {
|
|
29
26
|
const file = rel(s.file);
|
|
30
27
|
if (s.line == null) return file;
|
|
31
28
|
return `${file}:${s.line}:${s.column ?? 1}`;
|
|
32
29
|
});
|
|
33
|
-
|
|
34
|
-
function mockRuntimeModuleIdFromViolation(info, mode, root) {
|
|
35
|
-
if (mode === "off") return MOCK_MODULE_ID;
|
|
36
|
-
if (info.env !== VITE_ENVIRONMENT_NAMES.client) return MOCK_MODULE_ID;
|
|
37
|
-
return makeMockRuntimeModuleId({
|
|
30
|
+
const payload = {
|
|
38
31
|
env: info.env,
|
|
39
32
|
importer: info.importer,
|
|
40
33
|
specifier: info.specifier,
|
|
41
|
-
trace
|
|
34
|
+
trace,
|
|
42
35
|
mode
|
|
43
|
-
}
|
|
36
|
+
};
|
|
37
|
+
return `${MOCK_RUNTIME_PREFIX}${toBase64Url(JSON.stringify(payload))}`;
|
|
44
38
|
}
|
|
45
39
|
function makeMockEdgeModuleId(exports, source, runtimeId) {
|
|
46
|
-
const payload = {
|
|
47
|
-
source,
|
|
48
|
-
exports,
|
|
49
|
-
runtimeId
|
|
50
|
-
};
|
|
40
|
+
const payload = { source, exports, runtimeId };
|
|
51
41
|
return `${MOCK_EDGE_PREFIX}${toBase64Url(JSON.stringify(payload))}`;
|
|
52
42
|
}
|
|
53
|
-
function
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
syntheticNamedExports: true,
|
|
59
|
-
code: `
|
|
60
|
-
function createMock(name) {
|
|
61
|
-
const fn = function () {};
|
|
62
|
-
fn.prototype.name = name;
|
|
63
|
-
const children = Object.create(null);
|
|
64
|
-
const proxy = new Proxy(fn, {
|
|
65
|
-
get(target, prop) {
|
|
66
|
-
if (prop === '__esModule') return true;
|
|
67
|
-
if (prop === 'default') return proxy;
|
|
68
|
-
if (prop === 'caller') return null;
|
|
69
|
-
if (typeof prop === 'symbol') return undefined;
|
|
70
|
-
// Thenable support: prevent await from hanging
|
|
71
|
-
if (prop === 'then') return (fn) => Promise.resolve(fn(proxy));
|
|
72
|
-
if (prop === 'catch') return () => Promise.resolve(proxy);
|
|
73
|
-
if (prop === 'finally') return (fn) => { fn(); return Promise.resolve(proxy); };
|
|
74
|
-
// Memoize child proxies so mock.foo === mock.foo
|
|
75
|
-
if (!(prop in children)) {
|
|
76
|
-
children[prop] = createMock(name + '.' + prop);
|
|
77
|
-
}
|
|
78
|
-
return children[prop];
|
|
79
|
-
},
|
|
80
|
-
apply() {
|
|
81
|
-
return createMock(name + '()');
|
|
82
|
-
},
|
|
83
|
-
construct() {
|
|
84
|
-
return createMock('new ' + name);
|
|
85
|
-
},
|
|
86
|
-
});
|
|
87
|
-
return proxy;
|
|
88
|
-
}
|
|
89
|
-
const mock = createMock('mock');
|
|
90
|
-
export default mock;
|
|
91
|
-
`
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
function loadMockEdgeModule(encodedPayload) {
|
|
95
|
-
let payload;
|
|
96
|
-
try {
|
|
97
|
-
payload = JSON.parse(fromBase64Url(encodedPayload));
|
|
98
|
-
} catch {
|
|
99
|
-
payload = { exports: [] };
|
|
100
|
-
}
|
|
101
|
-
const names = Array.isArray(payload.exports) ? payload.exports.filter(
|
|
102
|
-
(n) => typeof n === "string" && isValidExportName(n)
|
|
103
|
-
) : [];
|
|
104
|
-
const runtimeId = typeof payload.runtimeId === "string" && payload.runtimeId.length > 0 ? payload.runtimeId : MOCK_MODULE_ID;
|
|
105
|
-
const exportLines = names.map((n) => `export const ${n} = mock.${n};`);
|
|
106
|
-
return {
|
|
107
|
-
code: `
|
|
108
|
-
import mock from ${JSON.stringify(runtimeId)};
|
|
109
|
-
${exportLines.join("\n")}
|
|
110
|
-
export default mock;
|
|
111
|
-
`
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
function loadMockRuntimeModule(encodedPayload) {
|
|
115
|
-
let payload;
|
|
116
|
-
try {
|
|
117
|
-
payload = JSON.parse(fromBase64Url(encodedPayload));
|
|
118
|
-
} catch {
|
|
119
|
-
payload = {};
|
|
120
|
-
}
|
|
121
|
-
const mode = payload.mode === "warn" || payload.mode === "off" ? payload.mode : "error";
|
|
122
|
-
const meta = {
|
|
123
|
-
env: String(payload.env ?? ""),
|
|
124
|
-
importer: String(payload.importer ?? ""),
|
|
125
|
-
specifier: String(payload.specifier ?? ""),
|
|
126
|
-
trace: Array.isArray(payload.trace) ? payload.trace : []
|
|
127
|
-
};
|
|
128
|
-
return {
|
|
129
|
-
code: `
|
|
130
|
-
const __meta = ${JSON.stringify(meta)};
|
|
131
|
-
const __mode = ${JSON.stringify(mode)};
|
|
43
|
+
function generateMockCode(diagnostics) {
|
|
44
|
+
const fnName = diagnostics ? "__createMock" : "createMock";
|
|
45
|
+
const hasDiag = !!diagnostics;
|
|
46
|
+
const preamble = hasDiag ? `const __meta = ${JSON.stringify(diagnostics.meta)};
|
|
47
|
+
const __mode = ${JSON.stringify(diagnostics.mode)};
|
|
132
48
|
|
|
133
49
|
const __seen = new Set();
|
|
134
50
|
function __report(action, accessPath) {
|
|
@@ -147,7 +63,7 @@ function __report(action, accessPath) {
|
|
|
147
63
|
'Importer: ' + __meta.importer + '\\n' +
|
|
148
64
|
'Access: ' + accessPath + ' (' + action + ')' +
|
|
149
65
|
traceLines +
|
|
150
|
-
'\\n\\
|
|
66
|
+
'\\n\\n' + ${JSON.stringify(RUNTIME_SUGGESTION_TEXT)};
|
|
151
67
|
|
|
152
68
|
const err = new Error(msg);
|
|
153
69
|
if (__mode === 'warn') {
|
|
@@ -156,22 +72,8 @@ function __report(action, accessPath) {
|
|
|
156
72
|
console.error(err);
|
|
157
73
|
}
|
|
158
74
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const fn = function () {};
|
|
162
|
-
fn.prototype.name = name;
|
|
163
|
-
const children = Object.create(null);
|
|
164
|
-
|
|
165
|
-
const proxy = new Proxy(fn, {
|
|
166
|
-
get(_target, prop) {
|
|
167
|
-
if (prop === '__esModule') return true;
|
|
168
|
-
if (prop === 'default') return proxy;
|
|
169
|
-
if (prop === 'caller') return null;
|
|
170
|
-
if (prop === 'then') return (f) => Promise.resolve(f(proxy));
|
|
171
|
-
if (prop === 'catch') return () => Promise.resolve(proxy);
|
|
172
|
-
if (prop === 'finally') return (f) => { f(); return Promise.resolve(proxy); };
|
|
173
|
-
|
|
174
|
-
// Trigger a runtime diagnostic for primitive conversions.
|
|
75
|
+
` : "";
|
|
76
|
+
const diagGetTraps = hasDiag ? `
|
|
175
77
|
if (prop === Symbol.toPrimitive) {
|
|
176
78
|
return () => {
|
|
177
79
|
__report('toPrimitive', name);
|
|
@@ -183,38 +85,104 @@ function __createMock(name) {
|
|
|
183
85
|
__report(String(prop), name);
|
|
184
86
|
return '[import-protection mock]';
|
|
185
87
|
};
|
|
186
|
-
}
|
|
187
|
-
|
|
88
|
+
}` : "";
|
|
89
|
+
const applyBody = hasDiag ? `__report('call', name + '()');
|
|
90
|
+
return ${fnName}(name + '()');` : `return ${fnName}(name + '()');`;
|
|
91
|
+
const constructBody = hasDiag ? `__report('construct', 'new ' + name);
|
|
92
|
+
return ${fnName}('new ' + name);` : `return ${fnName}('new ' + name);`;
|
|
93
|
+
const setTrap = hasDiag ? `
|
|
94
|
+
set(_target, prop) {
|
|
95
|
+
__report('set', name + '.' + String(prop));
|
|
96
|
+
return true;
|
|
97
|
+
},` : "";
|
|
98
|
+
return `
|
|
99
|
+
${preamble}function ${fnName}(name) {
|
|
100
|
+
const fn = function () {};
|
|
101
|
+
fn.prototype.name = name;
|
|
102
|
+
const children = Object.create(null);
|
|
103
|
+
const proxy = new Proxy(fn, {
|
|
104
|
+
get(_target, prop) {
|
|
105
|
+
if (prop === '__esModule') return true;
|
|
106
|
+
if (prop === 'default') return proxy;
|
|
107
|
+
if (prop === 'caller') return null;
|
|
108
|
+
if (prop === 'then') return (f) => Promise.resolve(f(proxy));
|
|
109
|
+
if (prop === 'catch') return () => Promise.resolve(proxy);
|
|
110
|
+
if (prop === 'finally') return (f) => { f(); return Promise.resolve(proxy); };${diagGetTraps}
|
|
188
111
|
if (typeof prop === 'symbol') return undefined;
|
|
189
112
|
if (!(prop in children)) {
|
|
190
|
-
children[prop] =
|
|
113
|
+
children[prop] = ${fnName}(name + '.' + prop);
|
|
191
114
|
}
|
|
192
115
|
return children[prop];
|
|
193
116
|
},
|
|
194
117
|
apply() {
|
|
195
|
-
|
|
196
|
-
return __createMock(name + '()');
|
|
118
|
+
${applyBody}
|
|
197
119
|
},
|
|
198
120
|
construct() {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
},
|
|
202
|
-
set(_target, prop) {
|
|
203
|
-
__report('set', name + '.' + String(prop));
|
|
204
|
-
return true;
|
|
205
|
-
},
|
|
121
|
+
${constructBody}
|
|
122
|
+
},${setTrap}
|
|
206
123
|
});
|
|
207
|
-
|
|
208
124
|
return proxy;
|
|
209
125
|
}
|
|
210
|
-
|
|
211
|
-
|
|
126
|
+
const mock = ${fnName}('mock');
|
|
127
|
+
export default mock;
|
|
128
|
+
`;
|
|
129
|
+
}
|
|
130
|
+
function loadSilentMockModule() {
|
|
131
|
+
return { syntheticNamedExports: true, code: generateMockCode() };
|
|
132
|
+
}
|
|
133
|
+
function loadMockEdgeModule(encodedPayload) {
|
|
134
|
+
let payload;
|
|
135
|
+
try {
|
|
136
|
+
payload = JSON.parse(fromBase64Url(encodedPayload));
|
|
137
|
+
} catch {
|
|
138
|
+
payload = { exports: [] };
|
|
139
|
+
}
|
|
140
|
+
const names = Array.isArray(payload.exports) ? payload.exports.filter(
|
|
141
|
+
(n) => typeof n === "string" && n.length > 0 && n !== "default"
|
|
142
|
+
) : [];
|
|
143
|
+
const runtimeId = typeof payload.runtimeId === "string" && payload.runtimeId.length > 0 ? payload.runtimeId : MOCK_MODULE_ID;
|
|
144
|
+
const exportLines = [];
|
|
145
|
+
const stringExports = [];
|
|
146
|
+
for (let i = 0; i < names.length; i++) {
|
|
147
|
+
const n = names[i];
|
|
148
|
+
if (isValidExportName(n)) {
|
|
149
|
+
exportLines.push(`export const ${n} = mock.${n};`);
|
|
150
|
+
} else {
|
|
151
|
+
const alias = `__tss_str_${i}`;
|
|
152
|
+
exportLines.push(`const ${alias} = mock[${JSON.stringify(n)}];`);
|
|
153
|
+
stringExports.push({ alias, name: n });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (stringExports.length > 0) {
|
|
157
|
+
const reexports = stringExports.map((s) => `${s.alias} as ${JSON.stringify(s.name)}`).join(", ");
|
|
158
|
+
exportLines.push(`export { ${reexports} };`);
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
code: `import mock from ${JSON.stringify(runtimeId)};
|
|
162
|
+
${exportLines.join("\n")}
|
|
212
163
|
export default mock;
|
|
213
164
|
`
|
|
214
165
|
};
|
|
215
166
|
}
|
|
167
|
+
function loadMockRuntimeModule(encodedPayload) {
|
|
168
|
+
let payload;
|
|
169
|
+
try {
|
|
170
|
+
payload = JSON.parse(fromBase64Url(encodedPayload));
|
|
171
|
+
} catch {
|
|
172
|
+
payload = {};
|
|
173
|
+
}
|
|
174
|
+
const mode = payload.mode === "warn" || payload.mode === "off" ? payload.mode : "error";
|
|
175
|
+
const meta = {
|
|
176
|
+
env: String(payload.env ?? ""),
|
|
177
|
+
importer: String(payload.importer ?? ""),
|
|
178
|
+
specifier: String(payload.specifier ?? ""),
|
|
179
|
+
trace: Array.isArray(payload.trace) ? payload.trace : []
|
|
180
|
+
};
|
|
181
|
+
return { code: generateMockCode({ meta, mode }) };
|
|
182
|
+
}
|
|
183
|
+
const MARKER_MODULE_RESULT = { code: "export {}" };
|
|
216
184
|
function loadMarkerModule() {
|
|
217
|
-
return
|
|
185
|
+
return MARKER_MODULE_RESULT;
|
|
218
186
|
}
|
|
219
187
|
export {
|
|
220
188
|
MARKER_PREFIX,
|
|
@@ -225,6 +193,7 @@ export {
|
|
|
225
193
|
RESOLVED_MOCK_EDGE_PREFIX,
|
|
226
194
|
RESOLVED_MOCK_MODULE_ID,
|
|
227
195
|
RESOLVED_MOCK_RUNTIME_PREFIX,
|
|
196
|
+
RUNTIME_SUGGESTION_TEXT,
|
|
228
197
|
loadMarkerModule,
|
|
229
198
|
loadMockEdgeModule,
|
|
230
199
|
loadMockRuntimeModule,
|