@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
|
@@ -6,11 +6,13 @@ function getDefaultImportProtectionRules() {
|
|
|
6
6
|
return {
|
|
7
7
|
client: {
|
|
8
8
|
specifiers: clientSpecifiers,
|
|
9
|
-
files: ["**/*.server.*"]
|
|
9
|
+
files: ["**/*.server.*"],
|
|
10
|
+
excludeFiles: ["**/node_modules/**"]
|
|
10
11
|
},
|
|
11
12
|
server: {
|
|
12
13
|
specifiers: [],
|
|
13
|
-
files: ["**/*.client.*"]
|
|
14
|
+
files: ["**/*.client.*"],
|
|
15
|
+
excludeFiles: ["**/node_modules/**"]
|
|
14
16
|
}
|
|
15
17
|
};
|
|
16
18
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaults.js","sources":["../../../src/import-protection-plugin/defaults.ts"],"sourcesContent":["import type { ImportProtectionEnvRules } from '../schema'\nimport type { Pattern } from './utils'\n\nexport interface DefaultImportProtectionRules {\n client: Required<ImportProtectionEnvRules>\n server: Required<ImportProtectionEnvRules>\n}\n\nconst frameworks = ['react', 'solid', 'vue'] as const\n\n/**\n * Returns the default import protection rules.\n *\n * All three framework variants are always included so that, e.g., a React\n * project also denies `@tanstack/solid-start/server` imports.\n */\nexport function getDefaultImportProtectionRules(): DefaultImportProtectionRules {\n const clientSpecifiers: Array<Pattern> = frameworks.map(\n (fw) => `@tanstack/${fw}-start/server`,\n )\n\n return {\n client: {\n specifiers: clientSpecifiers,\n files: ['**/*.server.*'],\n },\n server: {\n specifiers: [],\n files: ['**/*.client.*'],\n },\n }\n}\n\n/**\n * Marker module specifiers that restrict a file to a specific environment.\n */\nexport function getMarkerSpecifiers(): {\n serverOnly: Array<string>\n clientOnly: Array<string>\n} {\n return {\n serverOnly: frameworks.map((fw) => `@tanstack/${fw}-start/server-only`),\n clientOnly: frameworks.map((fw) => `@tanstack/${fw}-start/client-only`),\n }\n}\n"],"names":[],"mappings":"AAQA,MAAM,aAAa,CAAC,SAAS,SAAS,KAAK;AAQpC,SAAS,kCAAgE;AAC9E,QAAM,mBAAmC,WAAW;AAAA,IAClD,CAAC,OAAO,aAAa,EAAE;AAAA,EAAA;AAGzB,SAAO;AAAA,IACL,QAAQ;AAAA,MACN,YAAY;AAAA,MACZ,OAAO,CAAC,eAAe;AAAA,IAAA;AAAA,
|
|
1
|
+
{"version":3,"file":"defaults.js","sources":["../../../src/import-protection-plugin/defaults.ts"],"sourcesContent":["import type { ImportProtectionEnvRules } from '../schema'\nimport type { Pattern } from './utils'\n\nexport interface DefaultImportProtectionRules {\n client: Required<ImportProtectionEnvRules>\n server: Required<ImportProtectionEnvRules>\n}\n\nconst frameworks = ['react', 'solid', 'vue'] as const\n\n/**\n * Returns the default import protection rules.\n *\n * All three framework variants are always included so that, e.g., a React\n * project also denies `@tanstack/solid-start/server` imports.\n */\nexport function getDefaultImportProtectionRules(): DefaultImportProtectionRules {\n const clientSpecifiers: Array<Pattern> = frameworks.map(\n (fw) => `@tanstack/${fw}-start/server`,\n )\n\n return {\n client: {\n specifiers: clientSpecifiers,\n files: ['**/*.server.*'],\n excludeFiles: ['**/node_modules/**'],\n },\n server: {\n specifiers: [],\n files: ['**/*.client.*'],\n excludeFiles: ['**/node_modules/**'],\n },\n }\n}\n\n/**\n * Marker module specifiers that restrict a file to a specific environment.\n */\nexport function getMarkerSpecifiers(): {\n serverOnly: Array<string>\n clientOnly: Array<string>\n} {\n return {\n serverOnly: frameworks.map((fw) => `@tanstack/${fw}-start/server-only`),\n clientOnly: frameworks.map((fw) => `@tanstack/${fw}-start/client-only`),\n }\n}\n"],"names":[],"mappings":"AAQA,MAAM,aAAa,CAAC,SAAS,SAAS,KAAK;AAQpC,SAAS,kCAAgE;AAC9E,QAAM,mBAAmC,WAAW;AAAA,IAClD,CAAC,OAAO,aAAa,EAAE;AAAA,EAAA;AAGzB,SAAO;AAAA,IACL,QAAQ;AAAA,MACN,YAAY;AAAA,MACZ,OAAO,CAAC,eAAe;AAAA,MACvB,cAAc,CAAC,oBAAoB;AAAA,IAAA;AAAA,IAErC,QAAQ;AAAA,MACN,YAAY,CAAA;AAAA,MACZ,OAAO,CAAC,eAAe;AAAA,MACvB,cAAc,CAAC,oBAAoB;AAAA,IAAA;AAAA,EACrC;AAEJ;AAKO,SAAS,sBAGd;AACA,SAAO;AAAA,IACL,YAAY,WAAW,IAAI,CAAC,OAAO,aAAa,EAAE,oBAAoB;AAAA,IACtE,YAAY,WAAW,IAAI,CAAC,OAAO,aAAa,EAAE,oBAAoB;AAAA,EAAA;AAE1E;"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { PluginOption } from 'vite';
|
|
2
2
|
import { CompileStartFrameworkOptions, GetConfigFn } from '../types.js';
|
|
3
|
-
export { RESOLVED_MOCK_MODULE_ID } from './virtualModules.js';
|
|
4
3
|
export { rewriteDeniedImports } from './rewriteDeniedImports.js';
|
|
5
4
|
export { dedupePatterns, extractImportSources } from './utils.js';
|
|
6
5
|
export type { Pattern } from './utils.js';
|
|
@@ -8,11 +8,9 @@ import { findPostCompileUsagePos } from "./postCompileUsage.js";
|
|
|
8
8
|
import { matchesAny, compileMatchers } from "./matchers.js";
|
|
9
9
|
import { escapeRegExp, normalizeFilePath, getOrCreate, clearNormalizeFilePathCache, dedupePatterns, extractImportSources, relativizePath } from "./utils.js";
|
|
10
10
|
import { collectMockExportNamesBySource } from "./rewriteDeniedImports.js";
|
|
11
|
-
import {
|
|
11
|
+
import { loadResolvedVirtualModule, getResolvedVirtualModuleMatchers, resolveInternalVirtualModuleId, resolvedMarkerVirtualModuleId, mockRuntimeModuleIdFromViolation, makeMockEdgeModuleId, MOCK_BUILD_PREFIX } from "./virtualModules.js";
|
|
12
12
|
import { clearImportPatternCache, pickOriginalCodeFromSourcesContent, buildLineIndex, ImportLocCache, findPostCompileUsageLocation, findImportStatementLocationFromTransformed, buildCodeSnippet, addTraceImportLocations } from "./sourceLocation.js";
|
|
13
13
|
const SERVER_FN_LOOKUP_QUERY = "?" + SERVER_FN_LOOKUP;
|
|
14
|
-
const RESOLVED_MARKER_SERVER_ONLY = resolveViteId(`${MARKER_PREFIX}server-only`);
|
|
15
|
-
const RESOLVED_MARKER_CLIENT_ONLY = resolveViteId(`${MARKER_PREFIX}client-only`);
|
|
16
14
|
const IMPORT_PROTECTION_DEBUG = process.env.TSR_IMPORT_PROTECTION_DEBUG === "1" || process.env.TSR_IMPORT_PROTECTION_DEBUG === "true";
|
|
17
15
|
const IMPORT_PROTECTION_DEBUG_FILTER = process.env.TSR_IMPORT_PROTECTION_DEBUG_FILTER;
|
|
18
16
|
function debugLog(...args) {
|
|
@@ -102,8 +100,8 @@ function importProtectionPlugin(opts) {
|
|
|
102
100
|
logMode: "once",
|
|
103
101
|
maxTraceDepth: 20,
|
|
104
102
|
compiledRules: {
|
|
105
|
-
client: { specifiers: [], files: [] },
|
|
106
|
-
server: { specifiers: [], files: [] }
|
|
103
|
+
client: { specifiers: [], files: [], excludeFiles: [] },
|
|
104
|
+
server: { specifiers: [], files: [], excludeFiles: [] }
|
|
107
105
|
},
|
|
108
106
|
includeMatchers: [],
|
|
109
107
|
excludeMatchers: [],
|
|
@@ -391,7 +389,7 @@ function importProtectionPlugin(opts) {
|
|
|
391
389
|
function deferViolation(env, importerFile, info, mockReturnValue) {
|
|
392
390
|
getOrCreate(env.pendingViolations, importerFile, () => []).push({
|
|
393
391
|
info,
|
|
394
|
-
mockReturnValue:
|
|
392
|
+
mockReturnValue: mockReturnValue ?? ""
|
|
395
393
|
});
|
|
396
394
|
}
|
|
397
395
|
let buildViolationCounter = 0;
|
|
@@ -431,21 +429,30 @@ function importProtectionPlugin(opts) {
|
|
|
431
429
|
config.mockAccess,
|
|
432
430
|
config.root
|
|
433
431
|
);
|
|
434
|
-
const
|
|
435
|
-
const
|
|
432
|
+
const importerFile2 = normalizeFilePath(info.importer);
|
|
433
|
+
const exports2 = env.mockExportsByImporter.get(importerFile2)?.get(info.specifier) ?? [];
|
|
436
434
|
return resolveViteId(
|
|
437
|
-
makeMockEdgeModuleId(
|
|
435
|
+
makeMockEdgeModuleId(exports2, info.specifier, runtimeId)
|
|
438
436
|
);
|
|
439
437
|
}
|
|
440
|
-
const
|
|
441
|
-
|
|
438
|
+
const baseMockId = `${MOCK_BUILD_PREFIX}${buildViolationCounter++}`;
|
|
439
|
+
const importerFile = normalizeFilePath(info.importer);
|
|
440
|
+
const exports = env.mockExportsByImporter.get(importerFile)?.get(info.specifier) ?? [];
|
|
441
|
+
return resolveViteId(
|
|
442
|
+
makeMockEdgeModuleId(exports, info.specifier, baseMockId)
|
|
443
|
+
);
|
|
442
444
|
}
|
|
443
445
|
async function reportOrDeferViolation(ctx, env, importerFile, info, shouldDefer, isPreTransformResolve) {
|
|
444
446
|
if (shouldDefer) {
|
|
445
447
|
const result = await handleViolation(ctx, env, info, { silent: true });
|
|
446
448
|
if (config.command === "build") {
|
|
447
|
-
const mockId =
|
|
448
|
-
env.deferredBuildViolations.push({
|
|
449
|
+
const mockId = result ?? "";
|
|
450
|
+
env.deferredBuildViolations.push({
|
|
451
|
+
info,
|
|
452
|
+
mockModuleId: mockId,
|
|
453
|
+
// For marker violations, check importer survival instead of mock.
|
|
454
|
+
checkModuleId: info.type === "marker" ? info.importer : void 0
|
|
455
|
+
});
|
|
449
456
|
} else {
|
|
450
457
|
deferViolation(env, importerFile, info, result);
|
|
451
458
|
await processPendingViolations(env, ctx.warn.bind(ctx));
|
|
@@ -501,15 +508,19 @@ function importProtectionPlugin(opts) {
|
|
|
501
508
|
...userOpts?.client?.specifiers ?? []
|
|
502
509
|
]);
|
|
503
510
|
const clientFiles = userOpts?.client?.files ? [...userOpts.client.files] : [...defaults.client.files];
|
|
511
|
+
const clientExcludeFiles = userOpts?.client?.excludeFiles ? [...userOpts.client.excludeFiles] : [...defaults.client.excludeFiles];
|
|
504
512
|
const serverSpecifiers = userOpts?.server?.specifiers ? dedupePatterns([...userOpts.server.specifiers]) : dedupePatterns([...defaults.server.specifiers]);
|
|
505
513
|
const serverFiles = userOpts?.server?.files ? [...userOpts.server.files] : [...defaults.server.files];
|
|
514
|
+
const serverExcludeFiles = userOpts?.server?.excludeFiles ? [...userOpts.server.excludeFiles] : [...defaults.server.excludeFiles];
|
|
506
515
|
config.compiledRules.client = {
|
|
507
516
|
specifiers: compileMatchers(clientSpecifiers),
|
|
508
|
-
files: compileMatchers(clientFiles)
|
|
517
|
+
files: compileMatchers(clientFiles),
|
|
518
|
+
excludeFiles: compileMatchers(clientExcludeFiles)
|
|
509
519
|
};
|
|
510
520
|
config.compiledRules.server = {
|
|
511
521
|
specifiers: compileMatchers(serverSpecifiers),
|
|
512
|
-
files: compileMatchers(serverFiles)
|
|
522
|
+
files: compileMatchers(serverFiles),
|
|
523
|
+
excludeFiles: compileMatchers(serverExcludeFiles)
|
|
513
524
|
};
|
|
514
525
|
if (userOpts?.include) {
|
|
515
526
|
config.includeMatchers = compileMatchers(userOpts.include);
|
|
@@ -613,18 +624,8 @@ function importProtectionPlugin(opts) {
|
|
|
613
624
|
});
|
|
614
625
|
}
|
|
615
626
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
}
|
|
619
|
-
if (source.startsWith(MOCK_EDGE_PREFIX)) {
|
|
620
|
-
return resolveViteId(source);
|
|
621
|
-
}
|
|
622
|
-
if (source.startsWith(MOCK_RUNTIME_PREFIX)) {
|
|
623
|
-
return resolveViteId(source);
|
|
624
|
-
}
|
|
625
|
-
if (source.startsWith(MARKER_PREFIX)) {
|
|
626
|
-
return resolveViteId(source);
|
|
627
|
-
}
|
|
627
|
+
const internalVirtualId = resolveInternalVirtualModuleId(source);
|
|
628
|
+
if (internalVirtualId) return internalVirtualId;
|
|
628
629
|
if (!importer) {
|
|
629
630
|
env.graph.addEntry(source);
|
|
630
631
|
await processPendingViolations(env, this.warn.bind(this));
|
|
@@ -678,7 +679,7 @@ function importProtectionPlugin(opts) {
|
|
|
678
679
|
return markerResult;
|
|
679
680
|
}
|
|
680
681
|
}
|
|
681
|
-
return markerKind === "server" ?
|
|
682
|
+
return markerKind === "server" ? resolvedMarkerVirtualModuleId("server") : resolvedMarkerVirtualModuleId("client");
|
|
682
683
|
}
|
|
683
684
|
if (!shouldCheckImporter(normalizedImporter)) {
|
|
684
685
|
return void 0;
|
|
@@ -732,51 +733,54 @@ function importProtectionPlugin(opts) {
|
|
|
732
733
|
env.serverFnLookupModules.add(resolved);
|
|
733
734
|
}
|
|
734
735
|
env.graph.addEdge(resolved, normalizedImporter, source);
|
|
735
|
-
const
|
|
736
|
-
if (
|
|
737
|
-
const
|
|
736
|
+
const isExcludedFile = matchers.excludeFiles.length > 0 && matchesAny(relativePath, matchers.excludeFiles);
|
|
737
|
+
if (!isExcludedFile) {
|
|
738
|
+
const fileMatch = matchers.files.length > 0 ? matchesAny(relativePath, matchers.files) : void 0;
|
|
739
|
+
if (fileMatch) {
|
|
740
|
+
const info = await buildViolationInfo(
|
|
741
|
+
provider,
|
|
742
|
+
env,
|
|
743
|
+
envName,
|
|
744
|
+
envType,
|
|
745
|
+
importer,
|
|
746
|
+
normalizedImporter,
|
|
747
|
+
source,
|
|
748
|
+
{
|
|
749
|
+
type: "file",
|
|
750
|
+
pattern: fileMatch.pattern,
|
|
751
|
+
resolved,
|
|
752
|
+
message: `Import "${source}" (resolved to "${relativePath}") is denied in the ${envType} environment`
|
|
753
|
+
}
|
|
754
|
+
);
|
|
755
|
+
return reportOrDeferViolation(
|
|
756
|
+
this,
|
|
757
|
+
env,
|
|
758
|
+
normalizedImporter,
|
|
759
|
+
info,
|
|
760
|
+
shouldDefer,
|
|
761
|
+
isPreTransformResolve
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
const markerInfo = await buildMarkerViolationFromResolvedImport(
|
|
738
765
|
provider,
|
|
739
766
|
env,
|
|
740
767
|
envName,
|
|
741
768
|
envType,
|
|
742
769
|
importer,
|
|
743
|
-
normalizedImporter,
|
|
744
770
|
source,
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
pattern: fileMatch.pattern,
|
|
748
|
-
resolved,
|
|
749
|
-
message: `Import "${source}" (resolved to "${relativePath}") is denied in the ${envType} environment`
|
|
750
|
-
}
|
|
751
|
-
);
|
|
752
|
-
return reportOrDeferViolation(
|
|
753
|
-
this,
|
|
754
|
-
env,
|
|
755
|
-
normalizedImporter,
|
|
756
|
-
info,
|
|
757
|
-
shouldDefer,
|
|
758
|
-
isPreTransformResolve
|
|
759
|
-
);
|
|
760
|
-
}
|
|
761
|
-
const markerInfo = await buildMarkerViolationFromResolvedImport(
|
|
762
|
-
provider,
|
|
763
|
-
env,
|
|
764
|
-
envName,
|
|
765
|
-
envType,
|
|
766
|
-
importer,
|
|
767
|
-
source,
|
|
768
|
-
resolved,
|
|
769
|
-
relativePath
|
|
770
|
-
);
|
|
771
|
-
if (markerInfo) {
|
|
772
|
-
return reportOrDeferViolation(
|
|
773
|
-
this,
|
|
774
|
-
env,
|
|
775
|
-
normalizedImporter,
|
|
776
|
-
markerInfo,
|
|
777
|
-
shouldDefer,
|
|
778
|
-
isPreTransformResolve
|
|
771
|
+
resolved,
|
|
772
|
+
relativePath
|
|
779
773
|
);
|
|
774
|
+
if (markerInfo) {
|
|
775
|
+
return reportOrDeferViolation(
|
|
776
|
+
this,
|
|
777
|
+
env,
|
|
778
|
+
normalizedImporter,
|
|
779
|
+
markerInfo,
|
|
780
|
+
shouldDefer,
|
|
781
|
+
isPreTransformResolve
|
|
782
|
+
);
|
|
783
|
+
}
|
|
780
784
|
}
|
|
781
785
|
}
|
|
782
786
|
return void 0;
|
|
@@ -784,13 +788,7 @@ function importProtectionPlugin(opts) {
|
|
|
784
788
|
load: {
|
|
785
789
|
filter: {
|
|
786
790
|
id: new RegExp(
|
|
787
|
-
|
|
788
|
-
RESOLVED_MOCK_MODULE_ID,
|
|
789
|
-
RESOLVED_MOCK_BUILD_PREFIX,
|
|
790
|
-
RESOLVED_MARKER_PREFIX,
|
|
791
|
-
RESOLVED_MOCK_EDGE_PREFIX,
|
|
792
|
-
RESOLVED_MOCK_RUNTIME_PREFIX
|
|
793
|
-
].map(escapeRegExp).join("|")
|
|
791
|
+
getResolvedVirtualModuleMatchers().map(escapeRegExp).join("|")
|
|
794
792
|
)
|
|
795
793
|
},
|
|
796
794
|
handler(id) {
|
|
@@ -802,26 +800,7 @@ function importProtectionPlugin(opts) {
|
|
|
802
800
|
});
|
|
803
801
|
}
|
|
804
802
|
}
|
|
805
|
-
|
|
806
|
-
return loadSilentMockModule();
|
|
807
|
-
}
|
|
808
|
-
if (id.startsWith(RESOLVED_MOCK_BUILD_PREFIX)) {
|
|
809
|
-
return loadSilentMockModule();
|
|
810
|
-
}
|
|
811
|
-
if (id.startsWith(RESOLVED_MOCK_EDGE_PREFIX)) {
|
|
812
|
-
return loadMockEdgeModule(
|
|
813
|
-
id.slice(RESOLVED_MOCK_EDGE_PREFIX.length)
|
|
814
|
-
);
|
|
815
|
-
}
|
|
816
|
-
if (id.startsWith(RESOLVED_MOCK_RUNTIME_PREFIX)) {
|
|
817
|
-
return loadMockRuntimeModule(
|
|
818
|
-
id.slice(RESOLVED_MOCK_RUNTIME_PREFIX.length)
|
|
819
|
-
);
|
|
820
|
-
}
|
|
821
|
-
if (id.startsWith(RESOLVED_MARKER_PREFIX)) {
|
|
822
|
-
return loadMarkerModule();
|
|
823
|
-
}
|
|
824
|
-
return void 0;
|
|
803
|
+
return loadResolvedVirtualModule(id);
|
|
825
804
|
}
|
|
826
805
|
},
|
|
827
806
|
async generateBundle(_options, bundle) {
|
|
@@ -837,8 +816,13 @@ function importProtectionPlugin(opts) {
|
|
|
837
816
|
}
|
|
838
817
|
}
|
|
839
818
|
const realViolations = [];
|
|
840
|
-
for (const {
|
|
841
|
-
|
|
819
|
+
for (const {
|
|
820
|
+
info,
|
|
821
|
+
mockModuleId,
|
|
822
|
+
checkModuleId
|
|
823
|
+
} of env.deferredBuildViolations) {
|
|
824
|
+
const checkId = checkModuleId ?? mockModuleId;
|
|
825
|
+
if (!survivingModules.has(checkId)) continue;
|
|
842
826
|
if (config.onViolation) {
|
|
843
827
|
const result = await config.onViolation(info);
|
|
844
828
|
if (result === false) continue;
|
|
@@ -962,11 +946,8 @@ function importProtectionPlugin(opts) {
|
|
|
962
946
|
// Separate plugin so the transform can be enabled/disabled per-environment.
|
|
963
947
|
name: "tanstack-start-core:import-protection-mock-rewrite",
|
|
964
948
|
enforce: "pre",
|
|
965
|
-
// Only needed during dev. In build, we rely on Rollup's syntheticNamedExports.
|
|
966
|
-
apply: "serve",
|
|
967
949
|
applyToEnvironment(env) {
|
|
968
950
|
if (!config.enabled) return false;
|
|
969
|
-
if (config.effectiveBehavior !== "mock") return false;
|
|
970
951
|
return environmentNames.has(env.name);
|
|
971
952
|
},
|
|
972
953
|
transform: {
|
|
@@ -994,7 +975,6 @@ function importProtectionPlugin(opts) {
|
|
|
994
975
|
];
|
|
995
976
|
}
|
|
996
977
|
export {
|
|
997
|
-
RESOLVED_MOCK_MODULE_ID,
|
|
998
978
|
dedupePatterns,
|
|
999
979
|
extractImportSources,
|
|
1000
980
|
importProtectionPlugin
|