@tanstack/start-plugin-core 1.163.3 → 1.163.4
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/constants.d.ts +1 -0
- package/dist/esm/constants.js +2 -0
- package/dist/esm/constants.js.map +1 -1
- package/dist/esm/import-protection-plugin/ast.d.ts +3 -0
- package/dist/esm/import-protection-plugin/ast.js +8 -0
- package/dist/esm/import-protection-plugin/ast.js.map +1 -0
- package/dist/esm/import-protection-plugin/constants.d.ts +6 -0
- package/dist/esm/import-protection-plugin/constants.js +24 -0
- package/dist/esm/import-protection-plugin/constants.js.map +1 -0
- package/dist/esm/import-protection-plugin/extensionlessAbsoluteIdResolver.d.ts +22 -0
- package/dist/esm/import-protection-plugin/extensionlessAbsoluteIdResolver.js +95 -0
- package/dist/esm/import-protection-plugin/extensionlessAbsoluteIdResolver.js.map +1 -0
- package/dist/esm/import-protection-plugin/plugin.d.ts +2 -13
- package/dist/esm/import-protection-plugin/plugin.js +684 -299
- package/dist/esm/import-protection-plugin/plugin.js.map +1 -1
- package/dist/esm/import-protection-plugin/postCompileUsage.js +4 -2
- package/dist/esm/import-protection-plugin/postCompileUsage.js.map +1 -1
- package/dist/esm/import-protection-plugin/rewriteDeniedImports.d.ts +4 -5
- package/dist/esm/import-protection-plugin/rewriteDeniedImports.js +225 -3
- package/dist/esm/import-protection-plugin/rewriteDeniedImports.js.map +1 -1
- package/dist/esm/import-protection-plugin/sourceLocation.d.ts +4 -7
- package/dist/esm/import-protection-plugin/sourceLocation.js +18 -73
- package/dist/esm/import-protection-plugin/sourceLocation.js.map +1 -1
- package/dist/esm/import-protection-plugin/types.d.ts +94 -0
- package/dist/esm/import-protection-plugin/utils.d.ts +33 -1
- package/dist/esm/import-protection-plugin/utils.js +69 -3
- package/dist/esm/import-protection-plugin/utils.js.map +1 -1
- package/dist/esm/import-protection-plugin/virtualModules.d.ts +30 -2
- package/dist/esm/import-protection-plugin/virtualModules.js +66 -23
- package/dist/esm/import-protection-plugin/virtualModules.js.map +1 -1
- package/dist/esm/start-compiler-plugin/plugin.d.ts +2 -1
- package/dist/esm/start-compiler-plugin/plugin.js +1 -2
- package/dist/esm/start-compiler-plugin/plugin.js.map +1 -1
- package/package.json +4 -4
- package/src/constants.ts +2 -0
- package/src/import-protection-plugin/INTERNALS.md +462 -60
- package/src/import-protection-plugin/ast.ts +7 -0
- package/src/import-protection-plugin/constants.ts +25 -0
- package/src/import-protection-plugin/extensionlessAbsoluteIdResolver.ts +121 -0
- package/src/import-protection-plugin/plugin.ts +1080 -597
- package/src/import-protection-plugin/postCompileUsage.ts +8 -2
- package/src/import-protection-plugin/rewriteDeniedImports.ts +141 -9
- package/src/import-protection-plugin/sourceLocation.ts +19 -89
- package/src/import-protection-plugin/types.ts +103 -0
- package/src/import-protection-plugin/utils.ts +123 -4
- package/src/import-protection-plugin/virtualModules.ts +117 -31
- package/src/start-compiler-plugin/plugin.ts +7 -2
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { CompileStartFrameworkOptions, GetConfigFn } from '../types.js';
|
|
2
|
+
import { ImportProtectionBehavior } from '../schema.js';
|
|
3
|
+
import { CompiledMatcher } from './matchers.js';
|
|
4
|
+
import { ImportGraph, ViolationInfo } from './trace.js';
|
|
5
|
+
import { ImportLocCache, TransformResult, TransformResultProvider } from './sourceLocation.js';
|
|
6
|
+
/** Compiled deny/exclude patterns for one environment (client or server). */
|
|
7
|
+
export interface EnvRules {
|
|
8
|
+
specifiers: Array<CompiledMatcher>;
|
|
9
|
+
files: Array<CompiledMatcher>;
|
|
10
|
+
excludeFiles: Array<CompiledMatcher>;
|
|
11
|
+
}
|
|
12
|
+
export interface PluginConfig {
|
|
13
|
+
enabled: boolean;
|
|
14
|
+
root: string;
|
|
15
|
+
command: 'build' | 'serve';
|
|
16
|
+
srcDirectory: string;
|
|
17
|
+
framework: CompileStartFrameworkOptions;
|
|
18
|
+
effectiveBehavior: ImportProtectionBehavior;
|
|
19
|
+
mockAccess: 'error' | 'warn' | 'off';
|
|
20
|
+
logMode: 'once' | 'always';
|
|
21
|
+
maxTraceDepth: number;
|
|
22
|
+
compiledRules: {
|
|
23
|
+
client: EnvRules;
|
|
24
|
+
server: EnvRules;
|
|
25
|
+
};
|
|
26
|
+
includeMatchers: Array<CompiledMatcher>;
|
|
27
|
+
excludeMatchers: Array<CompiledMatcher>;
|
|
28
|
+
ignoreImporterMatchers: Array<CompiledMatcher>;
|
|
29
|
+
markerSpecifiers: {
|
|
30
|
+
serverOnly: Set<string>;
|
|
31
|
+
clientOnly: Set<string>;
|
|
32
|
+
};
|
|
33
|
+
envTypeMap: Map<string, 'client' | 'server'>;
|
|
34
|
+
onViolation?: (info: ViolationInfo) => boolean | void | Promise<boolean | void>;
|
|
35
|
+
}
|
|
36
|
+
export interface EnvState {
|
|
37
|
+
graph: ImportGraph;
|
|
38
|
+
mockExportsByImporter: Map<string, Map<string, Array<string>>>;
|
|
39
|
+
resolveCache: Map<string, string | null>;
|
|
40
|
+
resolveCacheByFile: Map<string, Set<string>>;
|
|
41
|
+
importLocCache: ImportLocCache;
|
|
42
|
+
seenViolations: Set<string>;
|
|
43
|
+
serverFnLookupModules: Set<string>;
|
|
44
|
+
transformResultCache: Map<string, TransformResult>;
|
|
45
|
+
transformResultKeysByFile: Map<string, Set<string>>;
|
|
46
|
+
transformResultProvider: TransformResultProvider;
|
|
47
|
+
postTransformImports: Map<string, Set<string>>;
|
|
48
|
+
pendingViolations: Map<string, Array<PendingViolation>>;
|
|
49
|
+
deferredBuildViolations: Array<DeferredBuildViolation>;
|
|
50
|
+
}
|
|
51
|
+
export interface PendingViolation {
|
|
52
|
+
info: ViolationInfo;
|
|
53
|
+
/** True when the violation originates from a pre-transform resolveId call
|
|
54
|
+
* (e.g. server-fn lookup). These need edge-survival verification because
|
|
55
|
+
* the Start compiler may strip the import later. */
|
|
56
|
+
fromPreTransformResolve?: boolean;
|
|
57
|
+
}
|
|
58
|
+
export interface DeferredBuildViolation {
|
|
59
|
+
info: ViolationInfo;
|
|
60
|
+
mockModuleId: string;
|
|
61
|
+
checkModuleId?: string;
|
|
62
|
+
}
|
|
63
|
+
export interface SharedState {
|
|
64
|
+
fileMarkerKind: Map<string, 'server' | 'client'>;
|
|
65
|
+
}
|
|
66
|
+
export interface ImportProtectionPluginOptions {
|
|
67
|
+
getConfig: GetConfigFn;
|
|
68
|
+
framework: CompileStartFrameworkOptions;
|
|
69
|
+
environments: Array<{
|
|
70
|
+
name: string;
|
|
71
|
+
type: 'client' | 'server';
|
|
72
|
+
}>;
|
|
73
|
+
providerEnvName: string;
|
|
74
|
+
}
|
|
75
|
+
export type ModuleGraphNode = {
|
|
76
|
+
id?: string | null;
|
|
77
|
+
url?: string;
|
|
78
|
+
importers: Set<ModuleGraphNode>;
|
|
79
|
+
};
|
|
80
|
+
export type ViolationReporter = {
|
|
81
|
+
warn: (msg: string) => void;
|
|
82
|
+
error: (msg: string) => never;
|
|
83
|
+
resolve?: (source: string, importer?: string, options?: {
|
|
84
|
+
skipSelf?: boolean;
|
|
85
|
+
custom?: Record<string, unknown>;
|
|
86
|
+
}) => Promise<{
|
|
87
|
+
id: string;
|
|
88
|
+
external?: boolean | 'absolute';
|
|
89
|
+
} | null>;
|
|
90
|
+
getModuleInfo?: (id: string) => {
|
|
91
|
+
code?: string | null;
|
|
92
|
+
} | null;
|
|
93
|
+
};
|
|
94
|
+
export type HandleViolationResult = string | undefined;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export type Pattern = string | RegExp;
|
|
2
2
|
export declare function dedupePatterns(patterns: Array<Pattern>): Array<Pattern>;
|
|
3
|
-
|
|
3
|
+
/** Strip both `?query` and `#hash` from a module ID. */
|
|
4
|
+
export declare function stripQueryAndHash(id: string): string;
|
|
4
5
|
export declare function normalizeFilePath(id: string): string;
|
|
5
6
|
/** Clear the memoization cache (call from buildStart to bound growth). */
|
|
6
7
|
export declare function clearNormalizeFilePathCache(): void;
|
|
@@ -10,3 +11,34 @@ export declare function getOrCreate<TKey, TValue>(map: Map<TKey, TValue>, key: T
|
|
|
10
11
|
/** Make a path relative to `root`, keeping non-rooted paths as-is. */
|
|
11
12
|
export declare function relativizePath(p: string, root: string): string;
|
|
12
13
|
export declare function extractImportSources(code: string): Array<string>;
|
|
14
|
+
/** Log import-protection debug output when debug mode is enabled. */
|
|
15
|
+
export declare function debugLog(...args: Array<unknown>): void;
|
|
16
|
+
/** Check if any value matches the configured debug filter (if present). */
|
|
17
|
+
export declare function matchesDebugFilter(...values: Array<string>): boolean;
|
|
18
|
+
/** Strip `?query` (but not `#hash`) from a module ID. */
|
|
19
|
+
export declare function stripQuery(id: string): string;
|
|
20
|
+
export declare function withoutKnownExtension(id: string): string;
|
|
21
|
+
/**
|
|
22
|
+
* Check whether `filePath` is contained inside `directory` using a
|
|
23
|
+
* boundary-safe comparison. A naïve `filePath.startsWith(directory)`
|
|
24
|
+
* would incorrectly treat `/app/src2/foo.ts` as inside `/app/src`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function isInsideDirectory(filePath: string, directory: string): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Decide whether a violation should be deferred for later verification
|
|
29
|
+
* rather than reported immediately.
|
|
30
|
+
*
|
|
31
|
+
* Build mode: always defer — generateBundle checks tree-shaking.
|
|
32
|
+
* Dev mock mode: always defer — edge-survival verifies whether the Start
|
|
33
|
+
* compiler strips the import (factory-safe pattern). All violation
|
|
34
|
+
* types and specifier formats are handled uniformly by the
|
|
35
|
+
* edge-survival mechanism in processPendingViolations.
|
|
36
|
+
* Dev error mode: never defer — throw immediately (no mock fallback).
|
|
37
|
+
*/
|
|
38
|
+
export declare function shouldDeferViolation(opts: {
|
|
39
|
+
isBuild: boolean;
|
|
40
|
+
isDevMock: boolean;
|
|
41
|
+
}): boolean;
|
|
42
|
+
export declare function buildSourceCandidates(source: string, resolved: string | undefined, root: string): Set<string>;
|
|
43
|
+
export declare function buildResolutionCandidates(id: string): Array<string>;
|
|
44
|
+
export declare function canonicalizeResolvedId(id: string, root: string, resolveExtensionlessAbsoluteId: (value: string) => string): string;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { isAbsolute, resolve, relative, extname } from "node:path";
|
|
1
2
|
import { normalizePath } from "vite";
|
|
3
|
+
import { IMPORT_PROTECTION_DEBUG_FILTER, IMPORT_PROTECTION_DEBUG, KNOWN_SOURCE_EXTENSIONS } from "./constants.js";
|
|
2
4
|
function dedupePatterns(patterns) {
|
|
3
5
|
const out = [];
|
|
4
6
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -10,7 +12,7 @@ function dedupePatterns(patterns) {
|
|
|
10
12
|
}
|
|
11
13
|
return out;
|
|
12
14
|
}
|
|
13
|
-
function
|
|
15
|
+
function stripQueryAndHash(id) {
|
|
14
16
|
const q = id.indexOf("?");
|
|
15
17
|
const h = id.indexOf("#");
|
|
16
18
|
if (q === -1 && h === -1) return id;
|
|
@@ -22,7 +24,7 @@ const normalizeFilePathCache = /* @__PURE__ */ new Map();
|
|
|
22
24
|
function normalizeFilePath(id) {
|
|
23
25
|
let result = normalizeFilePathCache.get(id);
|
|
24
26
|
if (result === void 0) {
|
|
25
|
-
result = normalizePath(
|
|
27
|
+
result = normalizePath(stripQueryAndHash(id));
|
|
26
28
|
normalizeFilePathCache.set(id, result);
|
|
27
29
|
}
|
|
28
30
|
return result;
|
|
@@ -58,14 +60,78 @@ function extractImportSources(code) {
|
|
|
58
60
|
}
|
|
59
61
|
return sources;
|
|
60
62
|
}
|
|
63
|
+
function debugLog(...args) {
|
|
64
|
+
if (!IMPORT_PROTECTION_DEBUG) return;
|
|
65
|
+
console.warn("[import-protection:debug]", ...args);
|
|
66
|
+
}
|
|
67
|
+
function matchesDebugFilter(...values) {
|
|
68
|
+
const debugFilter = IMPORT_PROTECTION_DEBUG_FILTER;
|
|
69
|
+
if (!debugFilter) return true;
|
|
70
|
+
return values.some((v) => v.includes(debugFilter));
|
|
71
|
+
}
|
|
72
|
+
function stripQuery(id) {
|
|
73
|
+
const queryIndex = id.indexOf("?");
|
|
74
|
+
return queryIndex === -1 ? id : id.slice(0, queryIndex);
|
|
75
|
+
}
|
|
76
|
+
function withoutKnownExtension(id) {
|
|
77
|
+
const ext = extname(id);
|
|
78
|
+
return KNOWN_SOURCE_EXTENSIONS.has(ext) ? id.slice(0, -ext.length) : id;
|
|
79
|
+
}
|
|
80
|
+
function isInsideDirectory(filePath, directory) {
|
|
81
|
+
const rel = relative(resolve(directory), resolve(filePath));
|
|
82
|
+
return rel.length > 0 && !rel.startsWith("..") && !isAbsolute(rel);
|
|
83
|
+
}
|
|
84
|
+
function shouldDeferViolation(opts) {
|
|
85
|
+
return opts.isBuild || opts.isDevMock;
|
|
86
|
+
}
|
|
87
|
+
function buildSourceCandidates(source, resolved, root) {
|
|
88
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
89
|
+
const push = (value) => {
|
|
90
|
+
if (!value) return;
|
|
91
|
+
candidates.add(value);
|
|
92
|
+
candidates.add(stripQuery(value));
|
|
93
|
+
candidates.add(withoutKnownExtension(stripQuery(value)));
|
|
94
|
+
};
|
|
95
|
+
push(source);
|
|
96
|
+
if (resolved) {
|
|
97
|
+
push(resolved);
|
|
98
|
+
const relativeResolved = relativizePath(resolved, root);
|
|
99
|
+
push(relativeResolved);
|
|
100
|
+
push(`./${relativeResolved}`);
|
|
101
|
+
push(`/${relativeResolved}`);
|
|
102
|
+
}
|
|
103
|
+
return candidates;
|
|
104
|
+
}
|
|
105
|
+
function buildResolutionCandidates(id) {
|
|
106
|
+
const normalized = normalizeFilePath(id);
|
|
107
|
+
const stripped = stripQuery(normalized);
|
|
108
|
+
return [.../* @__PURE__ */ new Set([id, normalized, stripped])];
|
|
109
|
+
}
|
|
110
|
+
function canonicalizeResolvedId(id, root, resolveExtensionlessAbsoluteId) {
|
|
111
|
+
const stripped = stripQuery(id);
|
|
112
|
+
let normalized = normalizeFilePath(stripped);
|
|
113
|
+
if (!isAbsolute(normalized) && !normalized.startsWith(".") && !normalized.startsWith("\0") && !/^[a-zA-Z]+:/.test(normalized)) {
|
|
114
|
+
normalized = normalizeFilePath(resolve(root, normalized));
|
|
115
|
+
}
|
|
116
|
+
return resolveExtensionlessAbsoluteId(normalized);
|
|
117
|
+
}
|
|
61
118
|
export {
|
|
119
|
+
buildResolutionCandidates,
|
|
120
|
+
buildSourceCandidates,
|
|
121
|
+
canonicalizeResolvedId,
|
|
62
122
|
clearNormalizeFilePathCache,
|
|
123
|
+
debugLog,
|
|
63
124
|
dedupePatterns,
|
|
64
125
|
escapeRegExp,
|
|
65
126
|
extractImportSources,
|
|
66
127
|
getOrCreate,
|
|
128
|
+
isInsideDirectory,
|
|
129
|
+
matchesDebugFilter,
|
|
67
130
|
normalizeFilePath,
|
|
68
131
|
relativizePath,
|
|
69
|
-
|
|
132
|
+
shouldDeferViolation,
|
|
133
|
+
stripQuery,
|
|
134
|
+
stripQueryAndHash,
|
|
135
|
+
withoutKnownExtension
|
|
70
136
|
};
|
|
71
137
|
//# 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
|
|
1
|
+
{"version":3,"file":"utils.js","sources":["../../../src/import-protection-plugin/utils.ts"],"sourcesContent":["import {\n extname,\n isAbsolute,\n relative,\n resolve as resolvePath,\n} from 'node:path'\nimport { normalizePath } from 'vite'\n\nimport {\n IMPORT_PROTECTION_DEBUG,\n IMPORT_PROTECTION_DEBUG_FILTER,\n KNOWN_SOURCE_EXTENSIONS,\n} from './constants'\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\n/** Strip both `?query` and `#hash` from a module ID. */\nexport function stripQueryAndHash(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/hash parameters and normalize the path in one step.\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(stripQueryAndHash(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\n/** Log import-protection debug output when debug mode is enabled. */\nexport function debugLog(...args: Array<unknown>): void {\n if (!IMPORT_PROTECTION_DEBUG) return\n console.warn('[import-protection:debug]', ...args)\n}\n\n/** Check if any value matches the configured debug filter (if present). */\nexport function matchesDebugFilter(...values: Array<string>): boolean {\n const debugFilter = IMPORT_PROTECTION_DEBUG_FILTER\n if (!debugFilter) return true\n return values.some((v) => v.includes(debugFilter))\n}\n\n/** Strip `?query` (but not `#hash`) from a module ID. */\nexport function stripQuery(id: string): string {\n const queryIndex = id.indexOf('?')\n return queryIndex === -1 ? id : id.slice(0, queryIndex)\n}\n\nexport function withoutKnownExtension(id: string): string {\n const ext = extname(id)\n return KNOWN_SOURCE_EXTENSIONS.has(ext) ? id.slice(0, -ext.length) : id\n}\n\n/**\n * Check whether `filePath` is contained inside `directory` using a\n * boundary-safe comparison. A naïve `filePath.startsWith(directory)`\n * would incorrectly treat `/app/src2/foo.ts` as inside `/app/src`.\n */\nexport function isInsideDirectory(\n filePath: string,\n directory: string,\n): boolean {\n const rel = relative(resolvePath(directory), resolvePath(filePath))\n return rel.length > 0 && !rel.startsWith('..') && !isAbsolute(rel)\n}\n\n/**\n * Decide whether a violation should be deferred for later verification\n * rather than reported immediately.\n *\n * Build mode: always defer — generateBundle checks tree-shaking.\n * Dev mock mode: always defer — edge-survival verifies whether the Start\n * compiler strips the import (factory-safe pattern). All violation\n * types and specifier formats are handled uniformly by the\n * edge-survival mechanism in processPendingViolations.\n * Dev error mode: never defer — throw immediately (no mock fallback).\n */\nexport function shouldDeferViolation(opts: {\n isBuild: boolean\n isDevMock: boolean\n}): boolean {\n return opts.isBuild || opts.isDevMock\n}\n\nexport function buildSourceCandidates(\n source: string,\n resolved: string | undefined,\n root: string,\n): Set<string> {\n const candidates = new Set<string>()\n const push = (value: string | undefined) => {\n if (!value) return\n candidates.add(value)\n candidates.add(stripQuery(value))\n candidates.add(withoutKnownExtension(stripQuery(value)))\n }\n\n push(source)\n if (resolved) {\n push(resolved)\n const relativeResolved = relativizePath(resolved, root)\n push(relativeResolved)\n push(`./${relativeResolved}`)\n push(`/${relativeResolved}`)\n }\n\n return candidates\n}\n\nexport function buildResolutionCandidates(id: string): Array<string> {\n const normalized = normalizeFilePath(id)\n const stripped = stripQuery(normalized)\n\n return [...new Set([id, normalized, stripped])]\n}\n\nexport function canonicalizeResolvedId(\n id: string,\n root: string,\n resolveExtensionlessAbsoluteId: (value: string) => string,\n): string {\n const stripped = stripQuery(id)\n let normalized = normalizeFilePath(stripped)\n\n if (\n !isAbsolute(normalized) &&\n !normalized.startsWith('.') &&\n !normalized.startsWith('\\0') &&\n !/^[a-zA-Z]+:/.test(normalized)\n ) {\n normalized = normalizeFilePath(resolvePath(root, normalized))\n }\n\n return resolveExtensionlessAbsoluteId(normalized)\n}\n"],"names":["resolvePath"],"mappings":"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"}
|
|
@@ -8,7 +8,6 @@ export declare const MOCK_MODULE_ID = "tanstack-start-import-protection:mock";
|
|
|
8
8
|
export declare const MOCK_BUILD_PREFIX = "tanstack-start-import-protection:mock:build:";
|
|
9
9
|
export declare const MOCK_EDGE_PREFIX = "tanstack-start-import-protection:mock-edge:";
|
|
10
10
|
export declare const MOCK_RUNTIME_PREFIX = "tanstack-start-import-protection:mock-runtime:";
|
|
11
|
-
export declare const MARKER_PREFIX = "tanstack-start-import-protection:marker:";
|
|
12
11
|
export declare function resolvedMarkerVirtualModuleId(kind: 'server' | 'client'): string;
|
|
13
12
|
/**
|
|
14
13
|
* Convenience list for plugin `load` filters/handlers.
|
|
@@ -31,10 +30,39 @@ type MockAccessMode = 'error' | 'warn' | 'off';
|
|
|
31
30
|
*/
|
|
32
31
|
export declare const RUNTIME_SUGGESTION_TEXT: string;
|
|
33
32
|
export declare function mockRuntimeModuleIdFromViolation(info: ViolationInfo, mode: MockAccessMode, root: string): string;
|
|
34
|
-
export declare function makeMockEdgeModuleId(exports: Array<string>,
|
|
33
|
+
export declare function makeMockEdgeModuleId(exports: Array<string>, runtimeId: string): string;
|
|
35
34
|
export declare function loadSilentMockModule(): {
|
|
36
35
|
code: string;
|
|
37
36
|
};
|
|
37
|
+
/**
|
|
38
|
+
* Generate a self-contained mock module with explicit named exports.
|
|
39
|
+
*
|
|
40
|
+
* Used by the transform hook's "self-denial" check: when a denied file
|
|
41
|
+
* (e.g. `.server.ts` in the client environment) is transformed, its entire
|
|
42
|
+
* content is replaced with this mock module. This avoids returning virtual
|
|
43
|
+
* module IDs from `resolveId`, which prevents cross-environment cache
|
|
44
|
+
* contamination from third-party resolver plugins.
|
|
45
|
+
*
|
|
46
|
+
* The generated code is side-effect-free and tree-shakeable.
|
|
47
|
+
*/
|
|
48
|
+
export declare function generateSelfContainedMockModule(exportNames: Array<string>): {
|
|
49
|
+
code: string;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Generate a dev-mode mock module for self-denial transforms.
|
|
53
|
+
*
|
|
54
|
+
* Similar to `loadMockEdgeModule` but takes export names and a runtime ID
|
|
55
|
+
* directly (instead of parsing them from a base64url-encoded payload).
|
|
56
|
+
* Used by the transform hook when a denied file (e.g. `.server.ts` in
|
|
57
|
+
* the client environment) is replaced in dev mode.
|
|
58
|
+
*
|
|
59
|
+
* The generated module imports mock-runtime for runtime diagnostics
|
|
60
|
+
* (error/warn on property access) and re-exports explicit named exports
|
|
61
|
+
* so that `import { foo } from './denied.server'` works.
|
|
62
|
+
*/
|
|
63
|
+
export declare function generateDevSelfDenialModule(exportNames: Array<string>, runtimeId: string): {
|
|
64
|
+
code: string;
|
|
65
|
+
};
|
|
38
66
|
export declare function loadMockEdgeModule(encodedPayload: string): {
|
|
39
67
|
code: string;
|
|
40
68
|
};
|
|
@@ -2,6 +2,7 @@ import { resolveViteId } from "../utils.js";
|
|
|
2
2
|
import { VITE_ENVIRONMENT_NAMES } from "../constants.js";
|
|
3
3
|
import { isValidExportName } from "./rewriteDeniedImports.js";
|
|
4
4
|
import { CLIENT_ENV_SUGGESTIONS } from "./trace.js";
|
|
5
|
+
import { VITE_BROWSER_VIRTUAL_PREFIX } from "./constants.js";
|
|
5
6
|
import { relativizePath } from "./utils.js";
|
|
6
7
|
const MOCK_MODULE_ID = "tanstack-start-import-protection:mock";
|
|
7
8
|
const RESOLVED_MOCK_MODULE_ID = resolveViteId(MOCK_MODULE_ID);
|
|
@@ -28,12 +29,29 @@ const RESOLVED_VIRTUAL_MODULE_MATCHERS = [
|
|
|
28
29
|
RESOLVED_MOCK_RUNTIME_PREFIX,
|
|
29
30
|
RESOLVED_MARKER_PREFIX
|
|
30
31
|
];
|
|
32
|
+
const RESOLVE_PREFIX_PAIRS = [
|
|
33
|
+
[MOCK_EDGE_PREFIX, RESOLVED_MOCK_EDGE_PREFIX],
|
|
34
|
+
[MOCK_RUNTIME_PREFIX, RESOLVED_MOCK_RUNTIME_PREFIX],
|
|
35
|
+
[MOCK_BUILD_PREFIX, RESOLVED_MOCK_BUILD_PREFIX],
|
|
36
|
+
[MARKER_PREFIX, RESOLVED_MARKER_PREFIX]
|
|
37
|
+
];
|
|
31
38
|
function resolveInternalVirtualModuleId(source) {
|
|
32
|
-
if (source
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
if (source.startsWith(VITE_BROWSER_VIRTUAL_PREFIX)) {
|
|
40
|
+
return resolveInternalVirtualModuleId(
|
|
41
|
+
`\0${source.slice(VITE_BROWSER_VIRTUAL_PREFIX.length)}`
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
if (source === MOCK_MODULE_ID || source === RESOLVED_MOCK_MODULE_ID) {
|
|
45
|
+
return RESOLVED_MOCK_MODULE_ID;
|
|
46
|
+
}
|
|
47
|
+
for (const [unresolvedPrefix, resolvedPrefix] of RESOLVE_PREFIX_PAIRS) {
|
|
48
|
+
if (source.startsWith(unresolvedPrefix)) {
|
|
49
|
+
return resolveViteId(source);
|
|
50
|
+
}
|
|
51
|
+
if (source.startsWith(resolvedPrefix)) {
|
|
52
|
+
return source;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
37
55
|
return void 0;
|
|
38
56
|
}
|
|
39
57
|
function toBase64Url(input) {
|
|
@@ -61,8 +79,8 @@ function mockRuntimeModuleIdFromViolation(info, mode, root) {
|
|
|
61
79
|
};
|
|
62
80
|
return `${MOCK_RUNTIME_PREFIX}${toBase64Url(JSON.stringify(payload))}`;
|
|
63
81
|
}
|
|
64
|
-
function makeMockEdgeModuleId(exports,
|
|
65
|
-
const payload = {
|
|
82
|
+
function makeMockEdgeModuleId(exports, runtimeId) {
|
|
83
|
+
const payload = { exports, runtimeId };
|
|
66
84
|
return `${MOCK_EDGE_PREFIX}${toBase64Url(JSON.stringify(payload))}`;
|
|
67
85
|
}
|
|
68
86
|
function generateMockCode(diagnostics) {
|
|
@@ -156,33 +174,57 @@ export default mock;
|
|
|
156
174
|
function loadSilentMockModule() {
|
|
157
175
|
return { code: generateMockCode() };
|
|
158
176
|
}
|
|
159
|
-
function
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
payload = { exports: [] };
|
|
165
|
-
}
|
|
166
|
-
const names = Array.isArray(payload.exports) ? payload.exports.filter(
|
|
167
|
-
(n) => typeof n === "string" && n.length > 0 && n !== "default"
|
|
168
|
-
) : [];
|
|
169
|
-
const runtimeId = typeof payload.runtimeId === "string" && payload.runtimeId.length > 0 ? payload.runtimeId : MOCK_MODULE_ID;
|
|
170
|
-
const exportLines = [];
|
|
177
|
+
function filterExportNames(exports) {
|
|
178
|
+
return exports.filter((n) => n.length > 0 && n !== "default");
|
|
179
|
+
}
|
|
180
|
+
function generateExportLines(names) {
|
|
181
|
+
const lines = [];
|
|
171
182
|
const stringExports = [];
|
|
172
183
|
for (let i = 0; i < names.length; i++) {
|
|
173
184
|
const n = names[i];
|
|
174
185
|
if (isValidExportName(n)) {
|
|
175
|
-
|
|
186
|
+
lines.push(`export const ${n} = mock.${n};`);
|
|
176
187
|
} else {
|
|
177
188
|
const alias = `__tss_str_${i}`;
|
|
178
|
-
|
|
189
|
+
lines.push(`const ${alias} = mock[${JSON.stringify(n)}];`);
|
|
179
190
|
stringExports.push({ alias, name: n });
|
|
180
191
|
}
|
|
181
192
|
}
|
|
182
193
|
if (stringExports.length > 0) {
|
|
183
194
|
const reexports = stringExports.map((s) => `${s.alias} as ${JSON.stringify(s.name)}`).join(", ");
|
|
184
|
-
|
|
195
|
+
lines.push(`export { ${reexports} };`);
|
|
196
|
+
}
|
|
197
|
+
return lines;
|
|
198
|
+
}
|
|
199
|
+
function generateSelfContainedMockModule(exportNames) {
|
|
200
|
+
const mockCode = generateMockCode();
|
|
201
|
+
const exportLines = generateExportLines(filterExportNames(exportNames));
|
|
202
|
+
return {
|
|
203
|
+
code: `${mockCode}
|
|
204
|
+
${exportLines.join("\n")}
|
|
205
|
+
`
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function generateDevSelfDenialModule(exportNames, runtimeId) {
|
|
209
|
+
const names = filterExportNames(exportNames);
|
|
210
|
+
const exportLines = generateExportLines(names);
|
|
211
|
+
return {
|
|
212
|
+
code: `import mock from ${JSON.stringify(runtimeId)};
|
|
213
|
+
${exportLines.join("\n")}
|
|
214
|
+
export default mock;
|
|
215
|
+
`
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function loadMockEdgeModule(encodedPayload) {
|
|
219
|
+
let payload;
|
|
220
|
+
try {
|
|
221
|
+
payload = JSON.parse(fromBase64Url(encodedPayload));
|
|
222
|
+
} catch {
|
|
223
|
+
payload = { exports: [] };
|
|
185
224
|
}
|
|
225
|
+
const names = filterExportNames(payload.exports ?? []);
|
|
226
|
+
const runtimeId = typeof payload.runtimeId === "string" && payload.runtimeId.length > 0 ? payload.runtimeId : MOCK_MODULE_ID;
|
|
227
|
+
const exportLines = generateExportLines(names);
|
|
186
228
|
return {
|
|
187
229
|
code: `import mock from ${JSON.stringify(runtimeId)};
|
|
188
230
|
${exportLines.join("\n")}
|
|
@@ -229,12 +271,13 @@ function loadResolvedVirtualModule(id) {
|
|
|
229
271
|
return void 0;
|
|
230
272
|
}
|
|
231
273
|
export {
|
|
232
|
-
MARKER_PREFIX,
|
|
233
274
|
MOCK_BUILD_PREFIX,
|
|
234
275
|
MOCK_EDGE_PREFIX,
|
|
235
276
|
MOCK_MODULE_ID,
|
|
236
277
|
MOCK_RUNTIME_PREFIX,
|
|
237
278
|
RUNTIME_SUGGESTION_TEXT,
|
|
279
|
+
generateDevSelfDenialModule,
|
|
280
|
+
generateSelfContainedMockModule,
|
|
238
281
|
getResolvedVirtualModuleMatchers,
|
|
239
282
|
loadMarkerModule,
|
|
240
283
|
loadMockEdgeModule,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"virtualModules.js","sources":["../../../src/import-protection-plugin/virtualModules.ts"],"sourcesContent":["import { resolveViteId } from '../utils'\nimport { VITE_ENVIRONMENT_NAMES } from '../constants'\nimport { isValidExportName } from './rewriteDeniedImports'\nimport { CLIENT_ENV_SUGGESTIONS } from './trace'\nimport { relativizePath } from './utils'\nimport type { ViolationInfo } from './trace'\n\nexport const MOCK_MODULE_ID = 'tanstack-start-import-protection:mock'\nconst RESOLVED_MOCK_MODULE_ID = resolveViteId(MOCK_MODULE_ID)\n\n/**\n * Per-violation mock prefix used in build+error mode.\n * Each deferred violation gets a unique ID so we can check which ones\n * survived tree-shaking in `generateBundle`.\n */\nexport const MOCK_BUILD_PREFIX = 'tanstack-start-import-protection:mock:build:'\nconst RESOLVED_MOCK_BUILD_PREFIX = resolveViteId(MOCK_BUILD_PREFIX)\n\nexport const MOCK_EDGE_PREFIX = 'tanstack-start-import-protection:mock-edge:'\nconst RESOLVED_MOCK_EDGE_PREFIX = resolveViteId(MOCK_EDGE_PREFIX)\n\nexport const MOCK_RUNTIME_PREFIX =\n 'tanstack-start-import-protection:mock-runtime:'\nconst RESOLVED_MOCK_RUNTIME_PREFIX = resolveViteId(MOCK_RUNTIME_PREFIX)\n\nexport const MARKER_PREFIX = 'tanstack-start-import-protection:marker:'\nconst RESOLVED_MARKER_PREFIX = resolveViteId(MARKER_PREFIX)\n\nconst RESOLVED_MARKER_SERVER_ONLY = resolveViteId(`${MARKER_PREFIX}server-only`)\nconst RESOLVED_MARKER_CLIENT_ONLY = resolveViteId(`${MARKER_PREFIX}client-only`)\n\nexport function resolvedMarkerVirtualModuleId(\n kind: 'server' | 'client',\n): string {\n return kind === 'server'\n ? RESOLVED_MARKER_SERVER_ONLY\n : RESOLVED_MARKER_CLIENT_ONLY\n}\n\n/**\n * Convenience list for plugin `load` filters/handlers.\n *\n * Vite/Rollup call `load(id)` with the *resolved* virtual id (prefixed by `\\0`).\n * `resolveId(source)` sees the *unresolved* id/prefix (without `\\0`).\n */\nexport function getResolvedVirtualModuleMatchers(): ReadonlyArray<string> {\n return RESOLVED_VIRTUAL_MODULE_MATCHERS\n}\n\nconst RESOLVED_VIRTUAL_MODULE_MATCHERS = [\n RESOLVED_MOCK_MODULE_ID,\n RESOLVED_MOCK_BUILD_PREFIX,\n RESOLVED_MOCK_EDGE_PREFIX,\n RESOLVED_MOCK_RUNTIME_PREFIX,\n RESOLVED_MARKER_PREFIX,\n] as const\n\n/**\n * Resolve import-protection's internal virtual module IDs.\n *\n * `resolveId(source)` sees *unresolved* ids/prefixes (no `\\0`).\n * Returning a resolved id (with `\\0`) ensures Vite/Rollup route it to `load`.\n */\nexport function resolveInternalVirtualModuleId(\n source: string,\n): string | undefined {\n if (source === MOCK_MODULE_ID) return RESOLVED_MOCK_MODULE_ID\n if (source.startsWith(MOCK_EDGE_PREFIX)) return resolveViteId(source)\n if (source.startsWith(MOCK_RUNTIME_PREFIX)) return resolveViteId(source)\n if (source.startsWith(MOCK_BUILD_PREFIX)) return resolveViteId(source)\n if (source.startsWith(MARKER_PREFIX)) return resolveViteId(source)\n return undefined\n}\n\nfunction toBase64Url(input: string): string {\n return Buffer.from(input, 'utf8').toString('base64url')\n}\n\nfunction fromBase64Url(input: string): string {\n return Buffer.from(input, 'base64url').toString('utf8')\n}\n\ntype MockAccessMode = 'error' | 'warn' | 'off'\n\n/**\n * Compact runtime suggestion text for browser console, derived from\n * {@link CLIENT_ENV_SUGGESTIONS} so there's a single source of truth.\n */\nexport const RUNTIME_SUGGESTION_TEXT =\n 'Fix: ' +\n CLIENT_ENV_SUGGESTIONS.join('. ') +\n '. To disable these runtime diagnostics, set importProtection.mockAccess: \"off\".'\n\nexport function mockRuntimeModuleIdFromViolation(\n info: ViolationInfo,\n mode: MockAccessMode,\n root: string,\n): string {\n if (mode === 'off') return MOCK_MODULE_ID\n if (info.env !== VITE_ENVIRONMENT_NAMES.client) return MOCK_MODULE_ID\n\n const rel = (p: string) => relativizePath(p, root)\n const trace = info.trace.map((s) => {\n const file = rel(s.file)\n if (s.line == null) return file\n return `${file}:${s.line}:${s.column ?? 1}`\n })\n\n const payload = {\n env: info.env,\n importer: info.importer,\n specifier: info.specifier,\n trace,\n mode,\n }\n return `${MOCK_RUNTIME_PREFIX}${toBase64Url(JSON.stringify(payload))}`\n}\n\nexport function makeMockEdgeModuleId(\n exports: Array<string>,\n source: string,\n runtimeId: string,\n): string {\n const payload = { source, exports, runtimeId }\n return `${MOCK_EDGE_PREFIX}${toBase64Url(JSON.stringify(payload))}`\n}\n\n/**\n * Generate a recursive Proxy-based mock module.\n *\n * When `diagnostics` is provided, the generated code includes a `__report`\n * function that logs runtime warnings/errors when the mock is actually used\n * (property access for primitive coercion, calls, construction, sets).\n *\n * When `diagnostics` is omitted, the mock is completely silent — suitable\n * for base mock modules (e.g. `MOCK_MODULE_ID` or per-violation build mocks)\n * that are consumed by mock-edge modules providing explicit named exports.\n */\nfunction generateMockCode(diagnostics?: {\n meta: {\n env: string\n importer: string\n specifier: string\n trace: Array<unknown>\n }\n mode: 'error' | 'warn' | 'off'\n}): string {\n const fnName = diagnostics ? '__createMock' : 'createMock'\n const hasDiag = !!diagnostics\n\n const preamble = hasDiag\n ? `const __meta = ${JSON.stringify(diagnostics.meta)};\nconst __mode = ${JSON.stringify(diagnostics.mode)};\n\nconst __seen = new Set();\nfunction __report(action, accessPath) {\n if (__mode === 'off') return;\n const key = action + ':' + accessPath;\n if (__seen.has(key)) return;\n __seen.add(key);\n\n const traceLines = Array.isArray(__meta.trace) && __meta.trace.length\n ? \"\\\\n\\\\nTrace:\\\\n\" + __meta.trace.map((t, i) => ' ' + (i + 1) + '. ' + String(t)).join('\\\\n')\n : '';\n\n const msg =\n '[import-protection] Mocked import used in dev client\\\\n\\\\n' +\n 'Denied import: \"' + __meta.specifier + '\"\\\\n' +\n 'Importer: ' + __meta.importer + '\\\\n' +\n 'Access: ' + accessPath + ' (' + action + ')' +\n traceLines +\n '\\\\n\\\\n' + ${JSON.stringify(RUNTIME_SUGGESTION_TEXT)};\n\n const err = new Error(msg);\n if (__mode === 'warn') {\n console.warn(err);\n } else {\n console.error(err);\n }\n}\n`\n : ''\n\n // Diagnostic-only traps for primitive coercion, set\n const diagGetTraps = hasDiag\n ? `\n if (prop === Symbol.toPrimitive) {\n return () => {\n __report('toPrimitive', name);\n return '[import-protection mock]';\n };\n }\n if (prop === 'toString' || prop === 'valueOf' || prop === 'toJSON') {\n return () => {\n __report(String(prop), name);\n return '[import-protection mock]';\n };\n }`\n : ''\n\n const applyBody = hasDiag\n ? `__report('call', name + '()');\n return ${fnName}(name + '()');`\n : `return ${fnName}(name + '()');`\n\n const constructBody = hasDiag\n ? `__report('construct', 'new ' + name);\n return ${fnName}('new ' + name);`\n : `return ${fnName}('new ' + name);`\n\n const setTrap = hasDiag\n ? `\n set(_target, prop) {\n __report('set', name + '.' + String(prop));\n return true;\n },`\n : ''\n\n return `\n${preamble}/* @__NO_SIDE_EFFECTS__ */\nfunction ${fnName}(name) {\n const fn = function () {};\n fn.prototype.name = name;\n const children = Object.create(null);\n const proxy = new Proxy(fn, {\n get(_target, prop) {\n if (prop === '__esModule') return true;\n if (prop === 'default') return proxy;\n if (prop === 'caller') return null;\n if (prop === 'then') return (f) => Promise.resolve(f(proxy));\n if (prop === 'catch') return () => Promise.resolve(proxy);\n if (prop === 'finally') return (f) => { f(); return Promise.resolve(proxy); };${diagGetTraps}\n if (typeof prop === 'symbol') return undefined;\n if (!(prop in children)) {\n children[prop] = ${fnName}(name + '.' + prop);\n }\n return children[prop];\n },\n apply() {\n ${applyBody}\n },\n construct() {\n ${constructBody}\n },${setTrap}\n });\n return proxy;\n}\nconst mock = /* @__PURE__ */ ${fnName}('mock');\nexport default mock;\n`\n}\n\nexport function loadSilentMockModule(): { code: string } {\n return { code: generateMockCode() }\n}\n\nexport function loadMockEdgeModule(encodedPayload: string): { code: string } {\n let payload: { exports?: Array<string>; runtimeId?: string }\n try {\n payload = JSON.parse(fromBase64Url(encodedPayload)) as typeof payload\n } catch {\n payload = { exports: [] }\n }\n const names: Array<string> = Array.isArray(payload.exports)\n ? payload.exports.filter(\n (n): n is string =>\n typeof n === 'string' && n.length > 0 && n !== 'default',\n )\n : []\n\n const runtimeId: string =\n typeof payload.runtimeId === 'string' && payload.runtimeId.length > 0\n ? payload.runtimeId\n : MOCK_MODULE_ID\n\n const exportLines: Array<string> = []\n const stringExports: Array<{ alias: string; name: string }> = []\n\n for (let i = 0; i < names.length; i++) {\n const n = names[i]!\n if (isValidExportName(n)) {\n exportLines.push(`export const ${n} = mock.${n};`)\n } else {\n // ES2022 string-keyed export: use a temp var + re-export with string literal\n const alias = `__tss_str_${i}`\n exportLines.push(`const ${alias} = mock[${JSON.stringify(n)}];`)\n stringExports.push({ alias, name: n })\n }\n }\n\n if (stringExports.length > 0) {\n const reexports = stringExports\n .map((s) => `${s.alias} as ${JSON.stringify(s.name)}`)\n .join(', ')\n exportLines.push(`export { ${reexports} };`)\n }\n\n return {\n code: `import mock from ${JSON.stringify(runtimeId)};\n${exportLines.join('\\n')}\nexport default mock;\n`,\n }\n}\n\nexport function loadMockRuntimeModule(encodedPayload: string): {\n code: string\n} {\n let payload: {\n mode?: string\n env?: string\n importer?: string\n specifier?: string\n trace?: Array<unknown>\n }\n try {\n payload = JSON.parse(fromBase64Url(encodedPayload)) as typeof payload\n } catch {\n payload = {}\n }\n\n const mode: 'error' | 'warn' | 'off' =\n payload.mode === 'warn' || payload.mode === 'off' ? payload.mode : 'error'\n\n const meta = {\n env: String(payload.env ?? ''),\n importer: String(payload.importer ?? ''),\n specifier: String(payload.specifier ?? ''),\n trace: Array.isArray(payload.trace) ? payload.trace : [],\n }\n\n return { code: generateMockCode({ meta, mode }) }\n}\n\nconst MARKER_MODULE_RESULT = { code: 'export {}' } as const\n\nexport function loadMarkerModule(): { code: string } {\n return MARKER_MODULE_RESULT\n}\n\nexport function loadResolvedVirtualModule(\n id: string,\n): { code: string } | undefined {\n if (id === RESOLVED_MOCK_MODULE_ID) {\n return loadSilentMockModule()\n }\n\n // Per-violation build mock modules — same silent mock code\n if (id.startsWith(RESOLVED_MOCK_BUILD_PREFIX)) {\n return loadSilentMockModule()\n }\n\n if (id.startsWith(RESOLVED_MOCK_EDGE_PREFIX)) {\n return loadMockEdgeModule(id.slice(RESOLVED_MOCK_EDGE_PREFIX.length))\n }\n\n if (id.startsWith(RESOLVED_MOCK_RUNTIME_PREFIX)) {\n return loadMockRuntimeModule(id.slice(RESOLVED_MOCK_RUNTIME_PREFIX.length))\n }\n\n if (id.startsWith(RESOLVED_MARKER_PREFIX)) {\n return loadMarkerModule()\n }\n\n return undefined\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"virtualModules.js","sources":["../../../src/import-protection-plugin/virtualModules.ts"],"sourcesContent":["import { resolveViteId } from '../utils'\nimport { VITE_ENVIRONMENT_NAMES } from '../constants'\nimport { isValidExportName } from './rewriteDeniedImports'\nimport { CLIENT_ENV_SUGGESTIONS } from './trace'\nimport { VITE_BROWSER_VIRTUAL_PREFIX } from './constants'\nimport { relativizePath } from './utils'\nimport type { ViolationInfo } from './trace'\n\nexport const MOCK_MODULE_ID = 'tanstack-start-import-protection:mock'\nconst RESOLVED_MOCK_MODULE_ID = resolveViteId(MOCK_MODULE_ID)\n\n/**\n * Per-violation mock prefix used in build+error mode.\n * Each deferred violation gets a unique ID so we can check which ones\n * survived tree-shaking in `generateBundle`.\n */\nexport const MOCK_BUILD_PREFIX = 'tanstack-start-import-protection:mock:build:'\nconst RESOLVED_MOCK_BUILD_PREFIX = resolveViteId(MOCK_BUILD_PREFIX)\n\nexport const MOCK_EDGE_PREFIX = 'tanstack-start-import-protection:mock-edge:'\nconst RESOLVED_MOCK_EDGE_PREFIX = resolveViteId(MOCK_EDGE_PREFIX)\n\nexport const MOCK_RUNTIME_PREFIX =\n 'tanstack-start-import-protection:mock-runtime:'\nconst RESOLVED_MOCK_RUNTIME_PREFIX = resolveViteId(MOCK_RUNTIME_PREFIX)\n\nconst MARKER_PREFIX = 'tanstack-start-import-protection:marker:'\nconst RESOLVED_MARKER_PREFIX = resolveViteId(MARKER_PREFIX)\n\nconst RESOLVED_MARKER_SERVER_ONLY = resolveViteId(`${MARKER_PREFIX}server-only`)\nconst RESOLVED_MARKER_CLIENT_ONLY = resolveViteId(`${MARKER_PREFIX}client-only`)\n\nexport function resolvedMarkerVirtualModuleId(\n kind: 'server' | 'client',\n): string {\n return kind === 'server'\n ? RESOLVED_MARKER_SERVER_ONLY\n : RESOLVED_MARKER_CLIENT_ONLY\n}\n\n/**\n * Convenience list for plugin `load` filters/handlers.\n *\n * Vite/Rollup call `load(id)` with the *resolved* virtual id (prefixed by `\\0`).\n * `resolveId(source)` sees the *unresolved* id/prefix (without `\\0`).\n */\nexport function getResolvedVirtualModuleMatchers(): ReadonlyArray<string> {\n return RESOLVED_VIRTUAL_MODULE_MATCHERS\n}\n\nconst RESOLVED_VIRTUAL_MODULE_MATCHERS = [\n RESOLVED_MOCK_MODULE_ID,\n RESOLVED_MOCK_BUILD_PREFIX,\n RESOLVED_MOCK_EDGE_PREFIX,\n RESOLVED_MOCK_RUNTIME_PREFIX,\n RESOLVED_MARKER_PREFIX,\n] as const\n\nconst RESOLVE_PREFIX_PAIRS = [\n [MOCK_EDGE_PREFIX, RESOLVED_MOCK_EDGE_PREFIX],\n [MOCK_RUNTIME_PREFIX, RESOLVED_MOCK_RUNTIME_PREFIX],\n [MOCK_BUILD_PREFIX, RESOLVED_MOCK_BUILD_PREFIX],\n [MARKER_PREFIX, RESOLVED_MARKER_PREFIX],\n] as const\n\n/**\n * Resolve import-protection's internal virtual module IDs.\n *\n * `resolveId(source)` sees *unresolved* ids/prefixes (no `\\0`).\n * Returning a resolved id (with `\\0`) ensures Vite/Rollup route it to `load`.\n */\nexport function resolveInternalVirtualModuleId(\n source: string,\n): string | undefined {\n if (source.startsWith(VITE_BROWSER_VIRTUAL_PREFIX)) {\n return resolveInternalVirtualModuleId(\n `\\0${source.slice(VITE_BROWSER_VIRTUAL_PREFIX.length)}`,\n )\n }\n\n if (source === MOCK_MODULE_ID || source === RESOLVED_MOCK_MODULE_ID) {\n return RESOLVED_MOCK_MODULE_ID\n }\n\n for (const [unresolvedPrefix, resolvedPrefix] of RESOLVE_PREFIX_PAIRS) {\n if (source.startsWith(unresolvedPrefix)) {\n return resolveViteId(source)\n }\n\n if (source.startsWith(resolvedPrefix)) {\n return source\n }\n }\n\n return undefined\n}\n\nfunction toBase64Url(input: string): string {\n return Buffer.from(input, 'utf8').toString('base64url')\n}\n\nfunction fromBase64Url(input: string): string {\n return Buffer.from(input, 'base64url').toString('utf8')\n}\n\ntype MockAccessMode = 'error' | 'warn' | 'off'\n\n/**\n * Compact runtime suggestion text for browser console, derived from\n * {@link CLIENT_ENV_SUGGESTIONS} so there's a single source of truth.\n */\nexport const RUNTIME_SUGGESTION_TEXT =\n 'Fix: ' +\n CLIENT_ENV_SUGGESTIONS.join('. ') +\n '. To disable these runtime diagnostics, set importProtection.mockAccess: \"off\".'\n\nexport function mockRuntimeModuleIdFromViolation(\n info: ViolationInfo,\n mode: MockAccessMode,\n root: string,\n): string {\n if (mode === 'off') return MOCK_MODULE_ID\n if (info.env !== VITE_ENVIRONMENT_NAMES.client) return MOCK_MODULE_ID\n\n const rel = (p: string) => relativizePath(p, root)\n const trace = info.trace.map((s) => {\n const file = rel(s.file)\n if (s.line == null) return file\n return `${file}:${s.line}:${s.column ?? 1}`\n })\n\n const payload = {\n env: info.env,\n importer: info.importer,\n specifier: info.specifier,\n trace,\n mode,\n }\n return `${MOCK_RUNTIME_PREFIX}${toBase64Url(JSON.stringify(payload))}`\n}\n\nexport function makeMockEdgeModuleId(\n exports: Array<string>,\n runtimeId: string,\n): string {\n const payload = { exports, runtimeId }\n return `${MOCK_EDGE_PREFIX}${toBase64Url(JSON.stringify(payload))}`\n}\n\n/**\n * Generate a recursive Proxy-based mock module.\n *\n * When `diagnostics` is provided, the generated code includes a `__report`\n * function that logs runtime warnings/errors when the mock is actually used\n * (property access for primitive coercion, calls, construction, sets).\n *\n * When `diagnostics` is omitted, the mock is completely silent — suitable\n * for base mock modules (e.g. `MOCK_MODULE_ID` or per-violation build mocks)\n * that are consumed by mock-edge modules providing explicit named exports.\n */\nfunction generateMockCode(diagnostics?: {\n meta: {\n env: string\n importer: string\n specifier: string\n trace: Array<unknown>\n }\n mode: 'error' | 'warn' | 'off'\n}): string {\n const fnName = diagnostics ? '__createMock' : 'createMock'\n const hasDiag = !!diagnostics\n\n const preamble = hasDiag\n ? `const __meta = ${JSON.stringify(diagnostics.meta)};\nconst __mode = ${JSON.stringify(diagnostics.mode)};\n\nconst __seen = new Set();\nfunction __report(action, accessPath) {\n if (__mode === 'off') return;\n const key = action + ':' + accessPath;\n if (__seen.has(key)) return;\n __seen.add(key);\n\n const traceLines = Array.isArray(__meta.trace) && __meta.trace.length\n ? \"\\\\n\\\\nTrace:\\\\n\" + __meta.trace.map((t, i) => ' ' + (i + 1) + '. ' + String(t)).join('\\\\n')\n : '';\n\n const msg =\n '[import-protection] Mocked import used in dev client\\\\n\\\\n' +\n 'Denied import: \"' + __meta.specifier + '\"\\\\n' +\n 'Importer: ' + __meta.importer + '\\\\n' +\n 'Access: ' + accessPath + ' (' + action + ')' +\n traceLines +\n '\\\\n\\\\n' + ${JSON.stringify(RUNTIME_SUGGESTION_TEXT)};\n\n const err = new Error(msg);\n if (__mode === 'warn') {\n console.warn(err);\n } else {\n console.error(err);\n }\n}\n`\n : ''\n\n // Diagnostic-only traps for primitive coercion, set\n const diagGetTraps = hasDiag\n ? `\n if (prop === Symbol.toPrimitive) {\n return () => {\n __report('toPrimitive', name);\n return '[import-protection mock]';\n };\n }\n if (prop === 'toString' || prop === 'valueOf' || prop === 'toJSON') {\n return () => {\n __report(String(prop), name);\n return '[import-protection mock]';\n };\n }`\n : ''\n\n const applyBody = hasDiag\n ? `__report('call', name + '()');\n return ${fnName}(name + '()');`\n : `return ${fnName}(name + '()');`\n\n const constructBody = hasDiag\n ? `__report('construct', 'new ' + name);\n return ${fnName}('new ' + name);`\n : `return ${fnName}('new ' + name);`\n\n const setTrap = hasDiag\n ? `\n set(_target, prop) {\n __report('set', name + '.' + String(prop));\n return true;\n },`\n : ''\n\n return `\n${preamble}/* @__NO_SIDE_EFFECTS__ */\nfunction ${fnName}(name) {\n const fn = function () {};\n fn.prototype.name = name;\n const children = Object.create(null);\n const proxy = new Proxy(fn, {\n get(_target, prop) {\n if (prop === '__esModule') return true;\n if (prop === 'default') return proxy;\n if (prop === 'caller') return null;\n if (prop === 'then') return (f) => Promise.resolve(f(proxy));\n if (prop === 'catch') return () => Promise.resolve(proxy);\n if (prop === 'finally') return (f) => { f(); return Promise.resolve(proxy); };${diagGetTraps}\n if (typeof prop === 'symbol') return undefined;\n if (!(prop in children)) {\n children[prop] = ${fnName}(name + '.' + prop);\n }\n return children[prop];\n },\n apply() {\n ${applyBody}\n },\n construct() {\n ${constructBody}\n },${setTrap}\n });\n return proxy;\n}\nconst mock = /* @__PURE__ */ ${fnName}('mock');\nexport default mock;\n`\n}\n\nexport function loadSilentMockModule(): { code: string } {\n return { code: generateMockCode() }\n}\n\n/**\n * Filter export names to valid, non-default names.\n */\nfunction filterExportNames(exports: ReadonlyArray<string>): Array<string> {\n return exports.filter((n) => n.length > 0 && n !== 'default')\n}\n\n/**\n * Generate ESM export lines that re-export named properties from `mock`.\n *\n * Produces `export const foo = mock.foo;` for valid identifiers and\n * string-keyed re-exports for non-identifier names.\n */\nfunction generateExportLines(names: ReadonlyArray<string>): Array<string> {\n const lines: Array<string> = []\n const stringExports: Array<{ alias: string; name: string }> = []\n\n for (let i = 0; i < names.length; i++) {\n const n = names[i]!\n if (isValidExportName(n)) {\n lines.push(`export const ${n} = mock.${n};`)\n } else {\n const alias = `__tss_str_${i}`\n lines.push(`const ${alias} = mock[${JSON.stringify(n)}];`)\n stringExports.push({ alias, name: n })\n }\n }\n\n if (stringExports.length > 0) {\n const reexports = stringExports\n .map((s) => `${s.alias} as ${JSON.stringify(s.name)}`)\n .join(', ')\n lines.push(`export { ${reexports} };`)\n }\n\n return lines\n}\n\n/**\n * Generate a self-contained mock module with explicit named exports.\n *\n * Used by the transform hook's \"self-denial\" check: when a denied file\n * (e.g. `.server.ts` in the client environment) is transformed, its entire\n * content is replaced with this mock module. This avoids returning virtual\n * module IDs from `resolveId`, which prevents cross-environment cache\n * contamination from third-party resolver plugins.\n *\n * The generated code is side-effect-free and tree-shakeable.\n */\nexport function generateSelfContainedMockModule(exportNames: Array<string>): {\n code: string\n} {\n const mockCode = generateMockCode()\n const exportLines = generateExportLines(filterExportNames(exportNames))\n\n return {\n code: `${mockCode}\n${exportLines.join('\\n')}\n`,\n }\n}\n\n/**\n * Generate a dev-mode mock module for self-denial transforms.\n *\n * Similar to `loadMockEdgeModule` but takes export names and a runtime ID\n * directly (instead of parsing them from a base64url-encoded payload).\n * Used by the transform hook when a denied file (e.g. `.server.ts` in\n * the client environment) is replaced in dev mode.\n *\n * The generated module imports mock-runtime for runtime diagnostics\n * (error/warn on property access) and re-exports explicit named exports\n * so that `import { foo } from './denied.server'` works.\n */\nexport function generateDevSelfDenialModule(\n exportNames: Array<string>,\n runtimeId: string,\n): { code: string } {\n const names = filterExportNames(exportNames)\n const exportLines = generateExportLines(names)\n\n return {\n code: `import mock from ${JSON.stringify(runtimeId)};\n${exportLines.join('\\n')}\nexport default mock;\n`,\n }\n}\n\nexport function loadMockEdgeModule(encodedPayload: string): { code: string } {\n let payload: { exports?: Array<string>; runtimeId?: string }\n try {\n payload = JSON.parse(fromBase64Url(encodedPayload)) as typeof payload\n } catch {\n payload = { exports: [] }\n }\n const names = filterExportNames(payload.exports ?? [])\n\n const runtimeId: string =\n typeof payload.runtimeId === 'string' && payload.runtimeId.length > 0\n ? payload.runtimeId\n : MOCK_MODULE_ID\n\n const exportLines = generateExportLines(names)\n\n return {\n code: `import mock from ${JSON.stringify(runtimeId)};\n${exportLines.join('\\n')}\nexport default mock;\n`,\n }\n}\n\nexport function loadMockRuntimeModule(encodedPayload: string): {\n code: string\n} {\n let payload: {\n mode?: string\n env?: string\n importer?: string\n specifier?: string\n trace?: Array<unknown>\n }\n try {\n payload = JSON.parse(fromBase64Url(encodedPayload)) as typeof payload\n } catch {\n payload = {}\n }\n\n const mode: 'error' | 'warn' | 'off' =\n payload.mode === 'warn' || payload.mode === 'off' ? payload.mode : 'error'\n\n const meta = {\n env: String(payload.env ?? ''),\n importer: String(payload.importer ?? ''),\n specifier: String(payload.specifier ?? ''),\n trace: Array.isArray(payload.trace) ? payload.trace : [],\n }\n\n return { code: generateMockCode({ meta, mode }) }\n}\n\nconst MARKER_MODULE_RESULT = { code: 'export {}' } as const\n\nexport function loadMarkerModule(): { code: string } {\n return MARKER_MODULE_RESULT\n}\n\nexport function loadResolvedVirtualModule(\n id: string,\n): { code: string } | undefined {\n if (id === RESOLVED_MOCK_MODULE_ID) {\n return loadSilentMockModule()\n }\n\n // Per-violation build mock modules — same silent mock code\n if (id.startsWith(RESOLVED_MOCK_BUILD_PREFIX)) {\n return loadSilentMockModule()\n }\n\n if (id.startsWith(RESOLVED_MOCK_EDGE_PREFIX)) {\n return loadMockEdgeModule(id.slice(RESOLVED_MOCK_EDGE_PREFIX.length))\n }\n\n if (id.startsWith(RESOLVED_MOCK_RUNTIME_PREFIX)) {\n return loadMockRuntimeModule(id.slice(RESOLVED_MOCK_RUNTIME_PREFIX.length))\n }\n\n if (id.startsWith(RESOLVED_MARKER_PREFIX)) {\n return loadMarkerModule()\n }\n\n return undefined\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { SERVER_FN_LOOKUP } from '../constants.js';
|
|
1
2
|
import { CompileStartFrameworkOptions } from '../types.js';
|
|
2
3
|
import { GenerateFunctionIdFnOptional } from './types.js';
|
|
3
4
|
import { PluginOption } from 'vite';
|
|
4
|
-
export
|
|
5
|
+
export { SERVER_FN_LOOKUP };
|
|
5
6
|
export interface StartCompilerPluginOptions {
|
|
6
7
|
framework: CompileStartFrameworkOptions;
|
|
7
8
|
environments: Array<{
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import assert from "node:assert";
|
|
2
2
|
import { VIRTUAL_MODULES } from "@tanstack/start-server-core";
|
|
3
3
|
import { resolve } from "pathe";
|
|
4
|
-
import { VITE_ENVIRONMENT_NAMES, TRANSFORM_ID_REGEX } from "../constants.js";
|
|
4
|
+
import { VITE_ENVIRONMENT_NAMES, SERVER_FN_LOOKUP, TRANSFORM_ID_REGEX } from "../constants.js";
|
|
5
5
|
import { StartCompiler, LookupKindsPerEnv, detectKindsInCode, KindDetectionPatterns } from "./compiler.js";
|
|
6
6
|
import { cleanId } from "./utils.js";
|
|
7
7
|
function getTransformCodeFilterForEnv(env) {
|
|
@@ -62,7 +62,6 @@ const getLookupConfigurationsForEnv = (env, framework) => {
|
|
|
62
62
|
];
|
|
63
63
|
}
|
|
64
64
|
};
|
|
65
|
-
const SERVER_FN_LOOKUP = "server-fn-module-lookup";
|
|
66
65
|
function resolveViteId(id) {
|
|
67
66
|
return `\0${id}`;
|
|
68
67
|
}
|