@tanstack/start-plugin-core 1.162.9 → 1.163.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/import-protection-plugin/defaults.js +4 -2
- package/dist/esm/import-protection-plugin/defaults.js.map +1 -1
- package/dist/esm/import-protection-plugin/plugin.d.ts +0 -1
- package/dist/esm/import-protection-plugin/plugin.js +79 -99
- package/dist/esm/import-protection-plugin/plugin.js.map +1 -1
- package/dist/esm/import-protection-plugin/virtualModules.d.ts +18 -6
- package/dist/esm/import-protection-plugin/virtualModules.js +50 -9
- package/dist/esm/import-protection-plugin/virtualModules.js.map +1 -1
- package/dist/esm/schema.d.ts +29 -0
- package/dist/esm/schema.js +2 -1
- package/dist/esm/schema.js.map +1 -1
- package/package.json +2 -2
- package/src/import-protection-plugin/INTERNALS.md +25 -17
- package/src/import-protection-plugin/defaults.ts +2 -0
- package/src/import-protection-plugin/plugin.ts +133 -140
- package/src/import-protection-plugin/virtualModules.ts +85 -13
- package/src/schema.ts +1 -0
|
@@ -21,21 +21,13 @@ import {
|
|
|
21
21
|
} from './utils'
|
|
22
22
|
import { collectMockExportNamesBySource } from './rewriteDeniedImports'
|
|
23
23
|
import {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
MOCK_RUNTIME_PREFIX,
|
|
28
|
-
RESOLVED_MARKER_PREFIX,
|
|
29
|
-
RESOLVED_MOCK_BUILD_PREFIX,
|
|
30
|
-
RESOLVED_MOCK_EDGE_PREFIX,
|
|
31
|
-
RESOLVED_MOCK_MODULE_ID,
|
|
32
|
-
RESOLVED_MOCK_RUNTIME_PREFIX,
|
|
33
|
-
loadMarkerModule,
|
|
34
|
-
loadMockEdgeModule,
|
|
35
|
-
loadMockRuntimeModule,
|
|
36
|
-
loadSilentMockModule,
|
|
24
|
+
MOCK_BUILD_PREFIX,
|
|
25
|
+
getResolvedVirtualModuleMatchers,
|
|
26
|
+
loadResolvedVirtualModule,
|
|
37
27
|
makeMockEdgeModuleId,
|
|
38
28
|
mockRuntimeModuleIdFromViolation,
|
|
29
|
+
resolveInternalVirtualModuleId,
|
|
30
|
+
resolvedMarkerVirtualModuleId,
|
|
39
31
|
} from './virtualModules'
|
|
40
32
|
import {
|
|
41
33
|
ImportLocCache,
|
|
@@ -62,9 +54,6 @@ import type {
|
|
|
62
54
|
import type { CompileStartFrameworkOptions, GetConfigFn } from '../types'
|
|
63
55
|
|
|
64
56
|
const SERVER_FN_LOOKUP_QUERY = '?' + SERVER_FN_LOOKUP
|
|
65
|
-
const RESOLVED_MARKER_SERVER_ONLY = resolveViteId(`${MARKER_PREFIX}server-only`)
|
|
66
|
-
const RESOLVED_MARKER_CLIENT_ONLY = resolveViteId(`${MARKER_PREFIX}client-only`)
|
|
67
|
-
|
|
68
57
|
const IMPORT_PROTECTION_DEBUG =
|
|
69
58
|
process.env.TSR_IMPORT_PROTECTION_DEBUG === '1' ||
|
|
70
59
|
process.env.TSR_IMPORT_PROTECTION_DEBUG === 'true'
|
|
@@ -82,7 +71,6 @@ function matchesDebugFilter(...values: Array<string>): boolean {
|
|
|
82
71
|
return values.some((v) => v.includes(IMPORT_PROTECTION_DEBUG_FILTER))
|
|
83
72
|
}
|
|
84
73
|
|
|
85
|
-
export { RESOLVED_MOCK_MODULE_ID } from './virtualModules'
|
|
86
74
|
export { rewriteDeniedImports } from './rewriteDeniedImports'
|
|
87
75
|
export { dedupePatterns, extractImportSources } from './utils'
|
|
88
76
|
export type { Pattern } from './utils'
|
|
@@ -110,10 +98,12 @@ interface PluginConfig {
|
|
|
110
98
|
client: {
|
|
111
99
|
specifiers: Array<CompiledMatcher>
|
|
112
100
|
files: Array<CompiledMatcher>
|
|
101
|
+
excludeFiles: Array<CompiledMatcher>
|
|
113
102
|
}
|
|
114
103
|
server: {
|
|
115
104
|
specifiers: Array<CompiledMatcher>
|
|
116
105
|
files: Array<CompiledMatcher>
|
|
106
|
+
excludeFiles: Array<CompiledMatcher>
|
|
117
107
|
}
|
|
118
108
|
}
|
|
119
109
|
includeMatchers: Array<CompiledMatcher>
|
|
@@ -214,6 +204,16 @@ interface DeferredBuildViolation {
|
|
|
214
204
|
info: ViolationInfo
|
|
215
205
|
/** Unique mock module ID assigned to this violation. */
|
|
216
206
|
mockModuleId: string
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Module ID to check for tree-shaking survival in `generateBundle`.
|
|
210
|
+
*
|
|
211
|
+
* For most violations we check the unique mock module ID.
|
|
212
|
+
* For `marker` violations the import is a bare side-effect import that often
|
|
213
|
+
* gets optimized away regardless of whether the importer survives, so we
|
|
214
|
+
* instead check whether the importer module itself survived.
|
|
215
|
+
*/
|
|
216
|
+
checkModuleId?: string
|
|
217
217
|
}
|
|
218
218
|
|
|
219
219
|
/**
|
|
@@ -350,8 +350,8 @@ export function importProtectionPlugin(
|
|
|
350
350
|
logMode: 'once',
|
|
351
351
|
maxTraceDepth: 20,
|
|
352
352
|
compiledRules: {
|
|
353
|
-
client: { specifiers: [], files: [] },
|
|
354
|
-
server: { specifiers: [], files: [] },
|
|
353
|
+
client: { specifiers: [], files: [], excludeFiles: [] },
|
|
354
|
+
server: { specifiers: [], files: [], excludeFiles: [] },
|
|
355
355
|
},
|
|
356
356
|
includeMatchers: [],
|
|
357
357
|
excludeMatchers: [],
|
|
@@ -540,6 +540,7 @@ export function importProtectionPlugin(
|
|
|
540
540
|
function getRulesForEnvironment(envName: string): {
|
|
541
541
|
specifiers: Array<CompiledMatcher>
|
|
542
542
|
files: Array<CompiledMatcher>
|
|
543
|
+
excludeFiles: Array<CompiledMatcher>
|
|
543
544
|
} {
|
|
544
545
|
const type = getEnvType(envName)
|
|
545
546
|
return type === 'client'
|
|
@@ -815,27 +816,18 @@ export function importProtectionPlugin(
|
|
|
815
816
|
env: EnvState,
|
|
816
817
|
importerFile: string,
|
|
817
818
|
info: ViolationInfo,
|
|
818
|
-
mockReturnValue:
|
|
819
|
-
| { id: string; syntheticNamedExports: boolean }
|
|
820
|
-
| string
|
|
821
|
-
| undefined,
|
|
819
|
+
mockReturnValue: string | undefined,
|
|
822
820
|
): void {
|
|
823
821
|
getOrCreate(env.pendingViolations, importerFile, () => []).push({
|
|
824
822
|
info,
|
|
825
|
-
mockReturnValue:
|
|
826
|
-
typeof mockReturnValue === 'string'
|
|
827
|
-
? mockReturnValue
|
|
828
|
-
: (mockReturnValue?.id ?? ''),
|
|
823
|
+
mockReturnValue: mockReturnValue ?? '',
|
|
829
824
|
})
|
|
830
825
|
}
|
|
831
826
|
|
|
832
827
|
/** Counter for generating unique per-violation mock module IDs in build mode. */
|
|
833
828
|
let buildViolationCounter = 0
|
|
834
829
|
|
|
835
|
-
type HandleViolationResult =
|
|
836
|
-
| { id: string; syntheticNamedExports: boolean }
|
|
837
|
-
| string
|
|
838
|
-
| undefined
|
|
830
|
+
type HandleViolationResult = string | undefined
|
|
839
831
|
|
|
840
832
|
async function handleViolation(
|
|
841
833
|
ctx: ViolationReporter,
|
|
@@ -906,8 +898,22 @@ export function importProtectionPlugin(
|
|
|
906
898
|
|
|
907
899
|
// Build: unique per-violation mock IDs so generateBundle can check
|
|
908
900
|
// which violations survived tree-shaking (both mock and error mode).
|
|
909
|
-
|
|
910
|
-
|
|
901
|
+
// We wrap the base mock in a mock-edge module that provides explicit
|
|
902
|
+
// named exports — Rolldown doesn't support Rollup's
|
|
903
|
+
// syntheticNamedExports, so without this named imports like
|
|
904
|
+
// `import { Foo } from "denied-pkg"` would fail with MISSING_EXPORT.
|
|
905
|
+
//
|
|
906
|
+
// Use the unresolved MOCK_BUILD_PREFIX (without \0) as the runtimeId
|
|
907
|
+
// so the mock-edge module's `import mock from "..."` goes through
|
|
908
|
+
// resolveId, which adds the \0 prefix. Using the resolved ID directly
|
|
909
|
+
// would cause Rollup/Vite to skip resolveId and fail to find the module.
|
|
910
|
+
const baseMockId = `${MOCK_BUILD_PREFIX}${buildViolationCounter++}`
|
|
911
|
+
const importerFile = normalizeFilePath(info.importer)
|
|
912
|
+
const exports =
|
|
913
|
+
env.mockExportsByImporter.get(importerFile)?.get(info.specifier) ?? []
|
|
914
|
+
return resolveViteId(
|
|
915
|
+
makeMockEdgeModuleId(exports, info.specifier, baseMockId),
|
|
916
|
+
)
|
|
911
917
|
}
|
|
912
918
|
|
|
913
919
|
/**
|
|
@@ -937,9 +943,14 @@ export function importProtectionPlugin(
|
|
|
937
943
|
|
|
938
944
|
if (config.command === 'build') {
|
|
939
945
|
// Build mode: store for generateBundle tree-shaking check.
|
|
940
|
-
// The
|
|
941
|
-
const mockId =
|
|
942
|
-
env.deferredBuildViolations.push({
|
|
946
|
+
// The mock-edge module ID is returned as a plain string.
|
|
947
|
+
const mockId = result ?? ''
|
|
948
|
+
env.deferredBuildViolations.push({
|
|
949
|
+
info,
|
|
950
|
+
mockModuleId: mockId,
|
|
951
|
+
// For marker violations, check importer survival instead of mock.
|
|
952
|
+
checkModuleId: info.type === 'marker' ? info.importer : undefined,
|
|
953
|
+
})
|
|
943
954
|
} else {
|
|
944
955
|
// Dev mock: store for graph-reachability check.
|
|
945
956
|
deferViolation(env, importerFile, info, result)
|
|
@@ -1021,20 +1032,28 @@ export function importProtectionPlugin(
|
|
|
1021
1032
|
const clientFiles = userOpts?.client?.files
|
|
1022
1033
|
? [...userOpts.client.files]
|
|
1023
1034
|
: [...defaults.client.files]
|
|
1035
|
+
const clientExcludeFiles = userOpts?.client?.excludeFiles
|
|
1036
|
+
? [...userOpts.client.excludeFiles]
|
|
1037
|
+
: [...defaults.client.excludeFiles]
|
|
1024
1038
|
const serverSpecifiers = userOpts?.server?.specifiers
|
|
1025
1039
|
? dedupePatterns([...userOpts.server.specifiers])
|
|
1026
1040
|
: dedupePatterns([...defaults.server.specifiers])
|
|
1027
1041
|
const serverFiles = userOpts?.server?.files
|
|
1028
1042
|
? [...userOpts.server.files]
|
|
1029
1043
|
: [...defaults.server.files]
|
|
1044
|
+
const serverExcludeFiles = userOpts?.server?.excludeFiles
|
|
1045
|
+
? [...userOpts.server.excludeFiles]
|
|
1046
|
+
: [...defaults.server.excludeFiles]
|
|
1030
1047
|
|
|
1031
1048
|
config.compiledRules.client = {
|
|
1032
1049
|
specifiers: compileMatchers(clientSpecifiers),
|
|
1033
1050
|
files: compileMatchers(clientFiles),
|
|
1051
|
+
excludeFiles: compileMatchers(clientExcludeFiles),
|
|
1034
1052
|
}
|
|
1035
1053
|
config.compiledRules.server = {
|
|
1036
1054
|
specifiers: compileMatchers(serverSpecifiers),
|
|
1037
1055
|
files: compileMatchers(serverFiles),
|
|
1056
|
+
excludeFiles: compileMatchers(serverExcludeFiles),
|
|
1038
1057
|
}
|
|
1039
1058
|
|
|
1040
1059
|
// Include/exclude
|
|
@@ -1171,18 +1190,8 @@ export function importProtectionPlugin(
|
|
|
1171
1190
|
}
|
|
1172
1191
|
|
|
1173
1192
|
// Internal virtual modules
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
}
|
|
1177
|
-
if (source.startsWith(MOCK_EDGE_PREFIX)) {
|
|
1178
|
-
return resolveViteId(source)
|
|
1179
|
-
}
|
|
1180
|
-
if (source.startsWith(MOCK_RUNTIME_PREFIX)) {
|
|
1181
|
-
return resolveViteId(source)
|
|
1182
|
-
}
|
|
1183
|
-
if (source.startsWith(MARKER_PREFIX)) {
|
|
1184
|
-
return resolveViteId(source)
|
|
1185
|
-
}
|
|
1193
|
+
const internalVirtualId = resolveInternalVirtualModuleId(source)
|
|
1194
|
+
if (internalVirtualId) return internalVirtualId
|
|
1186
1195
|
|
|
1187
1196
|
if (!importer) {
|
|
1188
1197
|
env.graph.addEntry(source)
|
|
@@ -1276,8 +1285,8 @@ export function importProtectionPlugin(
|
|
|
1276
1285
|
}
|
|
1277
1286
|
|
|
1278
1287
|
return markerKind === 'server'
|
|
1279
|
-
?
|
|
1280
|
-
:
|
|
1288
|
+
? resolvedMarkerVirtualModuleId('server')
|
|
1289
|
+
: resolvedMarkerVirtualModuleId('client')
|
|
1281
1290
|
}
|
|
1282
1291
|
|
|
1283
1292
|
// Check if the importer is within our scope
|
|
@@ -1344,56 +1353,69 @@ export function importProtectionPlugin(
|
|
|
1344
1353
|
|
|
1345
1354
|
env.graph.addEdge(resolved, normalizedImporter, source)
|
|
1346
1355
|
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1356
|
+
// Skip file-based and marker-based denial for resolved paths that
|
|
1357
|
+
// match the per-environment `excludeFiles` patterns. By default
|
|
1358
|
+
// this includes `**/node_modules/**` so that third-party packages
|
|
1359
|
+
// using `.client.` / `.server.` in their filenames (e.g. react-tweet
|
|
1360
|
+
// exports `index.client.js`) are not treated as user-authored
|
|
1361
|
+
// environment boundaries. Users can override `excludeFiles` per
|
|
1362
|
+
// environment to narrow or widen this exclusion.
|
|
1363
|
+
const isExcludedFile =
|
|
1364
|
+
matchers.excludeFiles.length > 0 &&
|
|
1365
|
+
matchesAny(relativePath, matchers.excludeFiles)
|
|
1366
|
+
|
|
1367
|
+
if (!isExcludedFile) {
|
|
1368
|
+
const fileMatch =
|
|
1369
|
+
matchers.files.length > 0
|
|
1370
|
+
? matchesAny(relativePath, matchers.files)
|
|
1371
|
+
: undefined
|
|
1372
|
+
|
|
1373
|
+
if (fileMatch) {
|
|
1374
|
+
const info = await buildViolationInfo(
|
|
1375
|
+
provider,
|
|
1376
|
+
env,
|
|
1377
|
+
envName,
|
|
1378
|
+
envType,
|
|
1379
|
+
importer,
|
|
1380
|
+
normalizedImporter,
|
|
1381
|
+
source,
|
|
1382
|
+
{
|
|
1383
|
+
type: 'file',
|
|
1384
|
+
pattern: fileMatch.pattern,
|
|
1385
|
+
resolved,
|
|
1386
|
+
message: `Import "${source}" (resolved to "${relativePath}") is denied in the ${envType} environment`,
|
|
1387
|
+
},
|
|
1388
|
+
)
|
|
1389
|
+
return reportOrDeferViolation(
|
|
1390
|
+
this,
|
|
1391
|
+
env,
|
|
1392
|
+
normalizedImporter,
|
|
1393
|
+
info,
|
|
1394
|
+
shouldDefer,
|
|
1395
|
+
isPreTransformResolve,
|
|
1396
|
+
)
|
|
1397
|
+
}
|
|
1351
1398
|
|
|
1352
|
-
|
|
1353
|
-
const info = await buildViolationInfo(
|
|
1399
|
+
const markerInfo = await buildMarkerViolationFromResolvedImport(
|
|
1354
1400
|
provider,
|
|
1355
1401
|
env,
|
|
1356
1402
|
envName,
|
|
1357
1403
|
envType,
|
|
1358
1404
|
importer,
|
|
1359
|
-
normalizedImporter,
|
|
1360
1405
|
source,
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
pattern: fileMatch.pattern,
|
|
1364
|
-
resolved,
|
|
1365
|
-
message: `Import "${source}" (resolved to "${relativePath}") is denied in the ${envType} environment`,
|
|
1366
|
-
},
|
|
1367
|
-
)
|
|
1368
|
-
return reportOrDeferViolation(
|
|
1369
|
-
this,
|
|
1370
|
-
env,
|
|
1371
|
-
normalizedImporter,
|
|
1372
|
-
info,
|
|
1373
|
-
shouldDefer,
|
|
1374
|
-
isPreTransformResolve,
|
|
1375
|
-
)
|
|
1376
|
-
}
|
|
1377
|
-
|
|
1378
|
-
const markerInfo = await buildMarkerViolationFromResolvedImport(
|
|
1379
|
-
provider,
|
|
1380
|
-
env,
|
|
1381
|
-
envName,
|
|
1382
|
-
envType,
|
|
1383
|
-
importer,
|
|
1384
|
-
source,
|
|
1385
|
-
resolved,
|
|
1386
|
-
relativePath,
|
|
1387
|
-
)
|
|
1388
|
-
if (markerInfo) {
|
|
1389
|
-
return reportOrDeferViolation(
|
|
1390
|
-
this,
|
|
1391
|
-
env,
|
|
1392
|
-
normalizedImporter,
|
|
1393
|
-
markerInfo,
|
|
1394
|
-
shouldDefer,
|
|
1395
|
-
isPreTransformResolve,
|
|
1406
|
+
resolved,
|
|
1407
|
+
relativePath,
|
|
1396
1408
|
)
|
|
1409
|
+
if (markerInfo) {
|
|
1410
|
+
return reportOrDeferViolation(
|
|
1411
|
+
this,
|
|
1412
|
+
env,
|
|
1413
|
+
normalizedImporter,
|
|
1414
|
+
markerInfo,
|
|
1415
|
+
shouldDefer,
|
|
1416
|
+
isPreTransformResolve,
|
|
1417
|
+
)
|
|
1418
|
+
}
|
|
1397
1419
|
}
|
|
1398
1420
|
}
|
|
1399
1421
|
|
|
@@ -1403,15 +1425,7 @@ export function importProtectionPlugin(
|
|
|
1403
1425
|
load: {
|
|
1404
1426
|
filter: {
|
|
1405
1427
|
id: new RegExp(
|
|
1406
|
-
|
|
1407
|
-
RESOLVED_MOCK_MODULE_ID,
|
|
1408
|
-
RESOLVED_MOCK_BUILD_PREFIX,
|
|
1409
|
-
RESOLVED_MARKER_PREFIX,
|
|
1410
|
-
RESOLVED_MOCK_EDGE_PREFIX,
|
|
1411
|
-
RESOLVED_MOCK_RUNTIME_PREFIX,
|
|
1412
|
-
]
|
|
1413
|
-
.map(escapeRegExp)
|
|
1414
|
-
.join('|'),
|
|
1428
|
+
getResolvedVirtualModuleMatchers().map(escapeRegExp).join('|'),
|
|
1415
1429
|
),
|
|
1416
1430
|
},
|
|
1417
1431
|
handler(id) {
|
|
@@ -1424,32 +1438,7 @@ export function importProtectionPlugin(
|
|
|
1424
1438
|
}
|
|
1425
1439
|
}
|
|
1426
1440
|
|
|
1427
|
-
|
|
1428
|
-
return loadSilentMockModule()
|
|
1429
|
-
}
|
|
1430
|
-
|
|
1431
|
-
// Per-violation build mock modules — same silent mock code
|
|
1432
|
-
if (id.startsWith(RESOLVED_MOCK_BUILD_PREFIX)) {
|
|
1433
|
-
return loadSilentMockModule()
|
|
1434
|
-
}
|
|
1435
|
-
|
|
1436
|
-
if (id.startsWith(RESOLVED_MOCK_EDGE_PREFIX)) {
|
|
1437
|
-
return loadMockEdgeModule(
|
|
1438
|
-
id.slice(RESOLVED_MOCK_EDGE_PREFIX.length),
|
|
1439
|
-
)
|
|
1440
|
-
}
|
|
1441
|
-
|
|
1442
|
-
if (id.startsWith(RESOLVED_MOCK_RUNTIME_PREFIX)) {
|
|
1443
|
-
return loadMockRuntimeModule(
|
|
1444
|
-
id.slice(RESOLVED_MOCK_RUNTIME_PREFIX.length),
|
|
1445
|
-
)
|
|
1446
|
-
}
|
|
1447
|
-
|
|
1448
|
-
if (id.startsWith(RESOLVED_MARKER_PREFIX)) {
|
|
1449
|
-
return loadMarkerModule()
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
|
-
return undefined
|
|
1441
|
+
return loadResolvedVirtualModule(id)
|
|
1453
1442
|
},
|
|
1454
1443
|
},
|
|
1455
1444
|
|
|
@@ -1468,11 +1457,16 @@ export function importProtectionPlugin(
|
|
|
1468
1457
|
}
|
|
1469
1458
|
}
|
|
1470
1459
|
|
|
1471
|
-
// Check each deferred violation: if its
|
|
1460
|
+
// Check each deferred violation: if its check module survived
|
|
1472
1461
|
// in the bundle, the import was NOT tree-shaken — real leak.
|
|
1473
1462
|
const realViolations: Array<ViolationInfo> = []
|
|
1474
|
-
for (const {
|
|
1475
|
-
|
|
1463
|
+
for (const {
|
|
1464
|
+
info,
|
|
1465
|
+
mockModuleId,
|
|
1466
|
+
checkModuleId,
|
|
1467
|
+
} of env.deferredBuildViolations) {
|
|
1468
|
+
const checkId = checkModuleId ?? mockModuleId
|
|
1469
|
+
if (!survivingModules.has(checkId)) continue
|
|
1476
1470
|
|
|
1477
1471
|
if (config.onViolation) {
|
|
1478
1472
|
const result = await config.onViolation(info)
|
|
@@ -1624,18 +1618,17 @@ export function importProtectionPlugin(
|
|
|
1624
1618
|
name: 'tanstack-start-core:import-protection-mock-rewrite',
|
|
1625
1619
|
enforce: 'pre',
|
|
1626
1620
|
|
|
1627
|
-
// Only needed during dev. In build, we rely on Rollup's syntheticNamedExports.
|
|
1628
|
-
apply: 'serve',
|
|
1629
|
-
|
|
1630
1621
|
applyToEnvironment(env) {
|
|
1631
1622
|
if (!config.enabled) return false
|
|
1632
|
-
//
|
|
1633
|
-
//
|
|
1634
|
-
//
|
|
1635
|
-
|
|
1636
|
-
//
|
|
1637
|
-
//
|
|
1638
|
-
//
|
|
1623
|
+
// We record expected named exports per importer so mock-edge modules
|
|
1624
|
+
// can provide explicit ESM named exports. This is needed in both dev
|
|
1625
|
+
// and build: native ESM (dev) requires real named exports, and
|
|
1626
|
+
// Rolldown (used in Vite 6+) doesn't support Rollup's
|
|
1627
|
+
// syntheticNamedExports flag which was previously relied upon in build.
|
|
1628
|
+
//
|
|
1629
|
+
// In build+error mode we still emit mock modules for deferred
|
|
1630
|
+
// violations (checked at generateBundle time), so we always need the
|
|
1631
|
+
// export name data when import protection is active.
|
|
1639
1632
|
return environmentNames.has(env.name)
|
|
1640
1633
|
},
|
|
1641
1634
|
|
|
@@ -6,7 +6,7 @@ import { relativizePath } from './utils'
|
|
|
6
6
|
import type { ViolationInfo } from './trace'
|
|
7
7
|
|
|
8
8
|
export const MOCK_MODULE_ID = 'tanstack-start-import-protection:mock'
|
|
9
|
-
|
|
9
|
+
const RESOLVED_MOCK_MODULE_ID = resolveViteId(MOCK_MODULE_ID)
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Per-violation mock prefix used in build+error mode.
|
|
@@ -14,17 +14,63 @@ export const RESOLVED_MOCK_MODULE_ID = resolveViteId(MOCK_MODULE_ID)
|
|
|
14
14
|
* survived tree-shaking in `generateBundle`.
|
|
15
15
|
*/
|
|
16
16
|
export const MOCK_BUILD_PREFIX = 'tanstack-start-import-protection:mock:build:'
|
|
17
|
-
|
|
17
|
+
const RESOLVED_MOCK_BUILD_PREFIX = resolveViteId(MOCK_BUILD_PREFIX)
|
|
18
18
|
|
|
19
19
|
export const MOCK_EDGE_PREFIX = 'tanstack-start-import-protection:mock-edge:'
|
|
20
|
-
|
|
20
|
+
const RESOLVED_MOCK_EDGE_PREFIX = resolveViteId(MOCK_EDGE_PREFIX)
|
|
21
21
|
|
|
22
22
|
export const MOCK_RUNTIME_PREFIX =
|
|
23
23
|
'tanstack-start-import-protection:mock-runtime:'
|
|
24
|
-
|
|
24
|
+
const RESOLVED_MOCK_RUNTIME_PREFIX = resolveViteId(MOCK_RUNTIME_PREFIX)
|
|
25
25
|
|
|
26
26
|
export const MARKER_PREFIX = 'tanstack-start-import-protection:marker:'
|
|
27
|
-
|
|
27
|
+
const RESOLVED_MARKER_PREFIX = resolveViteId(MARKER_PREFIX)
|
|
28
|
+
|
|
29
|
+
const RESOLVED_MARKER_SERVER_ONLY = resolveViteId(`${MARKER_PREFIX}server-only`)
|
|
30
|
+
const RESOLVED_MARKER_CLIENT_ONLY = resolveViteId(`${MARKER_PREFIX}client-only`)
|
|
31
|
+
|
|
32
|
+
export function resolvedMarkerVirtualModuleId(
|
|
33
|
+
kind: 'server' | 'client',
|
|
34
|
+
): string {
|
|
35
|
+
return kind === 'server'
|
|
36
|
+
? RESOLVED_MARKER_SERVER_ONLY
|
|
37
|
+
: RESOLVED_MARKER_CLIENT_ONLY
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Convenience list for plugin `load` filters/handlers.
|
|
42
|
+
*
|
|
43
|
+
* Vite/Rollup call `load(id)` with the *resolved* virtual id (prefixed by `\0`).
|
|
44
|
+
* `resolveId(source)` sees the *unresolved* id/prefix (without `\0`).
|
|
45
|
+
*/
|
|
46
|
+
export function getResolvedVirtualModuleMatchers(): ReadonlyArray<string> {
|
|
47
|
+
return RESOLVED_VIRTUAL_MODULE_MATCHERS
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const RESOLVED_VIRTUAL_MODULE_MATCHERS = [
|
|
51
|
+
RESOLVED_MOCK_MODULE_ID,
|
|
52
|
+
RESOLVED_MOCK_BUILD_PREFIX,
|
|
53
|
+
RESOLVED_MOCK_EDGE_PREFIX,
|
|
54
|
+
RESOLVED_MOCK_RUNTIME_PREFIX,
|
|
55
|
+
RESOLVED_MARKER_PREFIX,
|
|
56
|
+
] as const
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Resolve import-protection's internal virtual module IDs.
|
|
60
|
+
*
|
|
61
|
+
* `resolveId(source)` sees *unresolved* ids/prefixes (no `\0`).
|
|
62
|
+
* Returning a resolved id (with `\0`) ensures Vite/Rollup route it to `load`.
|
|
63
|
+
*/
|
|
64
|
+
export function resolveInternalVirtualModuleId(
|
|
65
|
+
source: string,
|
|
66
|
+
): string | undefined {
|
|
67
|
+
if (source === MOCK_MODULE_ID) return RESOLVED_MOCK_MODULE_ID
|
|
68
|
+
if (source.startsWith(MOCK_EDGE_PREFIX)) return resolveViteId(source)
|
|
69
|
+
if (source.startsWith(MOCK_RUNTIME_PREFIX)) return resolveViteId(source)
|
|
70
|
+
if (source.startsWith(MOCK_BUILD_PREFIX)) return resolveViteId(source)
|
|
71
|
+
if (source.startsWith(MARKER_PREFIX)) return resolveViteId(source)
|
|
72
|
+
return undefined
|
|
73
|
+
}
|
|
28
74
|
|
|
29
75
|
function toBase64Url(input: string): string {
|
|
30
76
|
return Buffer.from(input, 'utf8').toString('base64url')
|
|
@@ -87,7 +133,8 @@ export function makeMockEdgeModuleId(
|
|
|
87
133
|
* (property access for primitive coercion, calls, construction, sets).
|
|
88
134
|
*
|
|
89
135
|
* When `diagnostics` is omitted, the mock is completely silent — suitable
|
|
90
|
-
* for
|
|
136
|
+
* for base mock modules (e.g. `MOCK_MODULE_ID` or per-violation build mocks)
|
|
137
|
+
* that are consumed by mock-edge modules providing explicit named exports.
|
|
91
138
|
*/
|
|
92
139
|
function generateMockCode(diagnostics?: {
|
|
93
140
|
meta: {
|
|
@@ -170,7 +217,8 @@ function __report(action, accessPath) {
|
|
|
170
217
|
: ''
|
|
171
218
|
|
|
172
219
|
return `
|
|
173
|
-
${preamble}
|
|
220
|
+
${preamble}/* @__NO_SIDE_EFFECTS__ */
|
|
221
|
+
function ${fnName}(name) {
|
|
174
222
|
const fn = function () {};
|
|
175
223
|
fn.prototype.name = name;
|
|
176
224
|
const children = Object.create(null);
|
|
@@ -197,16 +245,13 @@ ${preamble}function ${fnName}(name) {
|
|
|
197
245
|
});
|
|
198
246
|
return proxy;
|
|
199
247
|
}
|
|
200
|
-
const mock = ${fnName}('mock');
|
|
248
|
+
const mock = /* @__PURE__ */ ${fnName}('mock');
|
|
201
249
|
export default mock;
|
|
202
250
|
`
|
|
203
251
|
}
|
|
204
252
|
|
|
205
|
-
export function loadSilentMockModule(): {
|
|
206
|
-
|
|
207
|
-
code: string
|
|
208
|
-
} {
|
|
209
|
-
return { syntheticNamedExports: true, code: generateMockCode() }
|
|
253
|
+
export function loadSilentMockModule(): { code: string } {
|
|
254
|
+
return { code: generateMockCode() }
|
|
210
255
|
}
|
|
211
256
|
|
|
212
257
|
export function loadMockEdgeModule(encodedPayload: string): { code: string } {
|
|
@@ -292,3 +337,30 @@ const MARKER_MODULE_RESULT = { code: 'export {}' } as const
|
|
|
292
337
|
export function loadMarkerModule(): { code: string } {
|
|
293
338
|
return MARKER_MODULE_RESULT
|
|
294
339
|
}
|
|
340
|
+
|
|
341
|
+
export function loadResolvedVirtualModule(
|
|
342
|
+
id: string,
|
|
343
|
+
): { code: string } | undefined {
|
|
344
|
+
if (id === RESOLVED_MOCK_MODULE_ID) {
|
|
345
|
+
return loadSilentMockModule()
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Per-violation build mock modules — same silent mock code
|
|
349
|
+
if (id.startsWith(RESOLVED_MOCK_BUILD_PREFIX)) {
|
|
350
|
+
return loadSilentMockModule()
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (id.startsWith(RESOLVED_MOCK_EDGE_PREFIX)) {
|
|
354
|
+
return loadMockEdgeModule(id.slice(RESOLVED_MOCK_EDGE_PREFIX.length))
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (id.startsWith(RESOLVED_MOCK_RUNTIME_PREFIX)) {
|
|
358
|
+
return loadMockRuntimeModule(id.slice(RESOLVED_MOCK_RUNTIME_PREFIX.length))
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (id.startsWith(RESOLVED_MARKER_PREFIX)) {
|
|
362
|
+
return loadMarkerModule()
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return undefined
|
|
366
|
+
}
|
package/src/schema.ts
CHANGED
|
@@ -16,6 +16,7 @@ const importProtectionBehaviorSchema = z.enum(['error', 'mock'])
|
|
|
16
16
|
const importProtectionEnvRulesSchema = z.object({
|
|
17
17
|
specifiers: z.array(patternSchema).optional(),
|
|
18
18
|
files: z.array(patternSchema).optional(),
|
|
19
|
+
excludeFiles: z.array(patternSchema).optional(),
|
|
19
20
|
})
|
|
20
21
|
|
|
21
22
|
const importProtectionOptionsSchema = z
|