create-payload-app 3.67.0-internal.87c53da → 3.68.0-canary.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/ast-integration.spec.js +131 -0
  2. package/dist/ast-integration.spec.js.map +1 -0
  3. package/dist/lib/ast/adapter-config.d.ts +40 -0
  4. package/dist/lib/ast/adapter-config.d.ts.map +1 -0
  5. package/dist/lib/ast/adapter-config.js +145 -0
  6. package/dist/lib/ast/adapter-config.js.map +1 -0
  7. package/dist/lib/ast/package-json.d.ts +13 -0
  8. package/dist/lib/ast/package-json.d.ts.map +1 -0
  9. package/dist/lib/ast/package-json.js +103 -0
  10. package/dist/lib/ast/package-json.js.map +1 -0
  11. package/dist/lib/ast/package-json.spec.js +66 -0
  12. package/dist/lib/ast/package-json.spec.js.map +1 -0
  13. package/dist/lib/ast/payload-config.d.ts +23 -0
  14. package/dist/lib/ast/payload-config.d.ts.map +1 -0
  15. package/dist/lib/ast/payload-config.js +655 -0
  16. package/dist/lib/ast/payload-config.js.map +1 -0
  17. package/dist/lib/ast/payload-config.spec.js +364 -0
  18. package/dist/lib/ast/payload-config.spec.js.map +1 -0
  19. package/dist/lib/ast/types.d.ts +126 -0
  20. package/dist/lib/ast/types.d.ts.map +1 -0
  21. package/dist/lib/ast/types.js +18 -0
  22. package/dist/lib/ast/types.js.map +1 -0
  23. package/dist/lib/ast/utils.d.ts +48 -0
  24. package/dist/lib/ast/utils.d.ts.map +1 -0
  25. package/dist/lib/ast/utils.js +189 -0
  26. package/dist/lib/ast/utils.js.map +1 -0
  27. package/dist/lib/ast/utils.spec.js +225 -0
  28. package/dist/lib/ast/utils.spec.js.map +1 -0
  29. package/dist/lib/configure-payload-config.d.ts.map +1 -1
  30. package/dist/lib/configure-payload-config.js +30 -86
  31. package/dist/lib/configure-payload-config.js.map +1 -1
  32. package/dist/lib/create-project.spec.js +9 -5
  33. package/dist/lib/create-project.spec.js.map +1 -1
  34. package/dist/lib/init-next.d.ts.map +1 -1
  35. package/dist/lib/init-next.js +2 -1
  36. package/dist/lib/init-next.js.map +1 -1
  37. package/dist/main.d.ts.map +1 -1
  38. package/dist/main.js +4 -0
  39. package/dist/main.js.map +1 -1
  40. package/dist/template/src/payload.config.ts +2 -7
  41. package/dist/types.d.ts +3 -2
  42. package/dist/types.d.ts.map +1 -1
  43. package/dist/types.js.map +1 -1
  44. package/dist/utils/log.d.ts.map +1 -1
  45. package/dist/utils/log.js +3 -1
  46. package/dist/utils/log.js.map +1 -1
  47. package/package.json +6 -3
  48. package/dist/lib/replacements.d.ts +0 -27
  49. package/dist/lib/replacements.d.ts.map +0 -1
  50. package/dist/lib/replacements.js +0 -104
  51. package/dist/lib/replacements.js.map +0 -1
@@ -0,0 +1,18 @@
1
+ export const ALL_DATABASE_ADAPTERS = [
2
+ 'mongodb',
3
+ 'postgres',
4
+ 'sqlite',
5
+ 'vercel-postgres',
6
+ 'd1-sqlite'
7
+ ];
8
+ export const ALL_STORAGE_ADAPTERS = [
9
+ 'azureStorage',
10
+ 'gcsStorage',
11
+ 'localDisk',
12
+ 'r2Storage',
13
+ 's3Storage',
14
+ 'uploadthingStorage',
15
+ 'vercelBlobStorage'
16
+ ];
17
+
18
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/lib/ast/types.ts"],"sourcesContent":["import type {\n ArrayLiteralExpression,\n CallExpression,\n ImportDeclaration,\n PropertyAssignment,\n SourceFile,\n} from 'ts-morph'\n\n// Result types for import utility functions\nexport type ImportRemovalResult = {\n removedIndex?: number\n sourceFile: SourceFile\n}\n\nexport type NamedImportRemovalResult = {\n fullyRemoved: boolean\n index?: number\n sourceFile: SourceFile\n}\n\nexport type ImportCleanupResult = {\n kept: string[]\n removed: string[]\n sourceFile: SourceFile\n}\n\nexport type DetectionError = {\n debugInfo?: Record<string, unknown>\n technicalDetails: string\n userMessage: string\n}\n\nexport type PayloadConfigStructures = {\n buildConfigCall: CallExpression\n dbProperty?: PropertyAssignment\n importStatements: ImportDeclaration[]\n pluginsArray?: ArrayLiteralExpression\n}\n\n/**\n * Detection result with edge case tracking and import source information\n */\nexport type DetectionResult = {\n /** Edge case flags */\n edgeCases?: {\n /** Import uses an alias (e.g., import { buildConfig as bc }) */\n hasImportAlias: boolean\n /** Other Payload imports exist (e.g., CollectionConfig) */\n hasOtherPayloadImports: boolean\n /** Multiple buildConfig calls found in file */\n multipleBuildConfigCalls: boolean\n /** Needs manual intervention (can't be automatically handled) */\n needsManualIntervention: boolean\n }\n error?: DetectionError\n /** Import source tracking */\n importSources?: {\n /** Current database adapter import info */\n dbAdapter?: {\n hasOtherImports: boolean\n importDeclaration: ImportDeclaration\n packageName: string\n }\n /** Current storage adapter import info */\n storageAdapters?: Array<{\n hasOtherImports: boolean\n importDeclaration: ImportDeclaration\n packageName: string\n }>\n }\n /** Source file reference */\n sourceFile?: SourceFile\n /** Detected structures */\n structures?: PayloadConfigStructures\n success: boolean\n}\n\n/**\n * Tracks a single modification made to the AST\n */\nexport type Modification = {\n description: string\n location?: {\n column: number\n line: number\n }\n type:\n | 'function-renamed'\n | 'import-added'\n | 'import-modified'\n | 'import-removed'\n | 'property-added'\n | 'property-removed'\n}\n\n/**\n * Result of transformation operations\n */\nexport type TransformationResult = {\n error?: DetectionError\n modifications: Modification[]\n modified: boolean\n success: boolean\n warnings?: string[]\n}\n\n/**\n * Final result after writing to disk\n */\nexport type ModificationResult = {\n error?: DetectionError\n filePath: string\n formatted?: boolean\n modifications: Modification[]\n success: boolean\n warnings?: string[]\n}\nexport type DatabaseAdapter = (typeof ALL_DATABASE_ADAPTERS)[number]\n\nexport const ALL_DATABASE_ADAPTERS = [\n 'mongodb',\n 'postgres',\n 'sqlite',\n 'vercel-postgres',\n 'd1-sqlite',\n] as const\n\nexport const ALL_STORAGE_ADAPTERS = [\n 'azureStorage',\n 'gcsStorage',\n 'localDisk',\n 'r2Storage',\n 's3Storage',\n 'uploadthingStorage',\n 'vercelBlobStorage',\n] as const\n\nexport type StorageAdapter = (typeof ALL_STORAGE_ADAPTERS)[number]\n\nexport type TransformOptions = {\n databaseAdapter?: {\n envVarName?: string\n type: DatabaseAdapter\n }\n removeSharp?: boolean\n storageAdapter?: {\n type: StorageAdapter\n }\n}\n\nexport type WriteOptions = {\n formatWithPrettier?: boolean\n validateStructure?: boolean\n}\n\nexport type WriteResult = {\n error?: DetectionError\n success: boolean\n}\n\nexport type ConfigureOptions = {\n db?: {\n envVarName?: string\n type: DatabaseAdapter\n }\n removeSharp?: boolean\n storage?: StorageAdapter\n} & WriteOptions\n"],"names":["ALL_DATABASE_ADAPTERS","ALL_STORAGE_ADAPTERS"],"mappings":"AAuHA,OAAO,MAAMA,wBAAwB;IACnC;IACA;IACA;IACA;IACA;CACD,CAAS;AAEV,OAAO,MAAMC,uBAAuB;IAClC;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAAS"}
@@ -0,0 +1,48 @@
1
+ import type { ImportDeclaration, SourceFile } from 'ts-morph';
2
+ import type { DetectionError, ImportCleanupResult, ImportRemovalResult, NamedImportRemovalResult } from './types.js';
3
+ export declare function findImportDeclaration({ moduleSpecifier, sourceFile, }: {
4
+ moduleSpecifier: string;
5
+ sourceFile: SourceFile;
6
+ }): ImportDeclaration | undefined;
7
+ type FormatErrorOptions = {
8
+ actual: string;
9
+ context: string;
10
+ debugInfo?: Record<string, unknown>;
11
+ expected: string;
12
+ technicalDetails: string;
13
+ };
14
+ export declare function formatError(options: FormatErrorOptions): DetectionError;
15
+ export declare function addImportDeclaration({ defaultImport, insertIndex, moduleSpecifier, namedImports, sourceFile, }: {
16
+ defaultImport?: string;
17
+ insertIndex?: number;
18
+ moduleSpecifier: string;
19
+ namedImports?: string[];
20
+ sourceFile: SourceFile;
21
+ }): SourceFile;
22
+ export declare function removeImportDeclaration({ moduleSpecifier, sourceFile, }: {
23
+ moduleSpecifier: string;
24
+ sourceFile: SourceFile;
25
+ }): ImportRemovalResult;
26
+ /**
27
+ * Remove specific named imports from an import declaration
28
+ * If all named imports are removed, removes the entire declaration
29
+ */
30
+ export declare function removeNamedImports({ importDeclaration, namedImportsToRemove, sourceFile, }: {
31
+ importDeclaration: ImportDeclaration;
32
+ namedImportsToRemove: string[];
33
+ sourceFile: SourceFile;
34
+ }): NamedImportRemovalResult;
35
+ /**
36
+ * Check if a named import is used in the source file
37
+ */
38
+ export declare function isNamedImportUsed(sourceFile: SourceFile, importName: string, excludeImports?: boolean): boolean;
39
+ /**
40
+ * Clean up orphaned imports - remove imports that are no longer used
41
+ */
42
+ export declare function cleanupOrphanedImports({ importNames, moduleSpecifier, sourceFile: inputSourceFile, }: {
43
+ importNames: string[];
44
+ moduleSpecifier: string;
45
+ sourceFile: SourceFile;
46
+ }): ImportCleanupResult;
47
+ export {};
48
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/lib/ast/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAK7D,OAAO,KAAK,EACV,cAAc,EACd,mBAAmB,EACnB,mBAAmB,EACnB,wBAAwB,EACzB,MAAM,YAAY,CAAA;AAInB,wBAAgB,qBAAqB,CAAC,EACpC,eAAe,EACf,UAAU,GACX,EAAE;IACD,eAAe,EAAE,MAAM,CAAA;IACvB,UAAU,EAAE,UAAU,CAAA;CACvB,GAAG,iBAAiB,GAAG,SAAS,CAIhC;AAED,KAAK,kBAAkB,GAAG;IACxB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACnC,QAAQ,EAAE,MAAM,CAAA;IAChB,gBAAgB,EAAE,MAAM,CAAA;CACzB,CAAA;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,cAAc,CAevE;AAED,wBAAgB,oBAAoB,CAAC,EACnC,aAAa,EACb,WAAW,EACX,eAAe,EACf,YAAY,EACZ,UAAU,GACX,EAAE;IACD,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,eAAe,EAAE,MAAM,CAAA;IACvB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IACvB,UAAU,EAAE,UAAU,CAAA;CACvB,GAAG,UAAU,CA6Cb;AAED,wBAAgB,uBAAuB,CAAC,EACtC,eAAe,EACf,UAAU,GACX,EAAE;IACD,eAAe,EAAE,MAAM,CAAA;IACvB,UAAU,EAAE,UAAU,CAAA;CACvB,GAAG,mBAAmB,CAatB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,iBAAiB,EACjB,oBAAoB,EACpB,UAAU,GACX,EAAE;IACD,iBAAiB,EAAE,iBAAiB,CAAA;IACpC,oBAAoB,EAAE,MAAM,EAAE,CAAA;IAC9B,UAAU,EAAE,UAAU,CAAA;CACvB,GAAG,wBAAwB,CA8B3B;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,UAAU,EACtB,UAAU,EAAE,MAAM,EAClB,cAAc,UAAO,GACpB,OAAO,CAsBT;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,WAAW,EACX,eAAe,EACf,UAAU,EAAE,eAAe,GAC5B,EAAE;IACD,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,eAAe,EAAE,MAAM,CAAA;IACvB,UAAU,EAAE,UAAU,CAAA;CACvB,GAAG,mBAAmB,CAkCtB"}
@@ -0,0 +1,189 @@
1
+ import { debug } from '../../utils/log.js';
2
+ export function findImportDeclaration({ moduleSpecifier, sourceFile }) {
3
+ return sourceFile.getImportDeclarations().find((imp)=>imp.getModuleSpecifierValue() === moduleSpecifier);
4
+ }
5
+ export function formatError(options) {
6
+ const { actual, context, debugInfo, expected, technicalDetails } = options;
7
+ const userMessage = `Your config file doesn't match the expected structure for ${context}.
8
+
9
+ Expected: ${expected}
10
+ Actual: ${actual}
11
+
12
+ Please ensure your config file follows the expected structure.`;
13
+ return {
14
+ technicalDetails,
15
+ userMessage,
16
+ ...debugInfo && {
17
+ debugInfo
18
+ }
19
+ };
20
+ }
21
+ export function addImportDeclaration({ defaultImport, insertIndex, moduleSpecifier, namedImports, sourceFile }) {
22
+ const existingImport = findImportDeclaration({
23
+ moduleSpecifier,
24
+ sourceFile
25
+ });
26
+ if (existingImport) {
27
+ // Add named imports to existing import if they don't exist
28
+ if (namedImports) {
29
+ const existingNamedImports = existingImport.getNamedImports().map((ni)=>ni.getName());
30
+ const newNamedImports = namedImports.filter((ni)=>!existingNamedImports.includes(ni));
31
+ if (newNamedImports.length > 0) {
32
+ existingImport.addNamedImports(newNamedImports);
33
+ debug(`[AST] Added named imports to existing import from '${moduleSpecifier}': ${newNamedImports.join(', ')}`);
34
+ } else {
35
+ debug(`[AST] Import from '${moduleSpecifier}' already has all required named imports`);
36
+ }
37
+ }
38
+ } else {
39
+ // Create new import at specified index or at default position
40
+ const importDeclaration = {
41
+ moduleSpecifier,
42
+ ...namedImports && {
43
+ namedImports
44
+ },
45
+ ...defaultImport && {
46
+ defaultImport
47
+ }
48
+ };
49
+ if (insertIndex !== undefined) {
50
+ sourceFile.insertImportDeclaration(insertIndex, importDeclaration);
51
+ debug(`[AST] Inserted import from '${moduleSpecifier}' at index ${insertIndex}`);
52
+ } else {
53
+ sourceFile.addImportDeclaration(importDeclaration);
54
+ debug(`[AST] Added import from '${moduleSpecifier}' at default position`);
55
+ }
56
+ const parts = [];
57
+ if (defaultImport) {
58
+ parts.push(`default: ${defaultImport}`);
59
+ }
60
+ if (namedImports) {
61
+ parts.push(`named: ${namedImports.join(', ')}`);
62
+ }
63
+ debug(`[AST] Import contents: ${parts.join(', ')}`);
64
+ }
65
+ return sourceFile;
66
+ }
67
+ export function removeImportDeclaration({ moduleSpecifier, sourceFile }) {
68
+ const importDecl = findImportDeclaration({
69
+ moduleSpecifier,
70
+ sourceFile
71
+ });
72
+ if (importDecl) {
73
+ // Get index before removing
74
+ const allImports = sourceFile.getImportDeclarations();
75
+ const index = allImports.indexOf(importDecl);
76
+ importDecl.remove();
77
+ debug(`[AST] Removed import from '${moduleSpecifier}' at index ${index}`);
78
+ return {
79
+ removedIndex: index,
80
+ sourceFile
81
+ };
82
+ } else {
83
+ debug(`[AST] Import from '${moduleSpecifier}' not found (already absent)`);
84
+ return {
85
+ removedIndex: undefined,
86
+ sourceFile
87
+ };
88
+ }
89
+ }
90
+ /**
91
+ * Remove specific named imports from an import declaration
92
+ * If all named imports are removed, removes the entire declaration
93
+ */ export function removeNamedImports({ importDeclaration, namedImportsToRemove, sourceFile }) {
94
+ const namedImports = importDeclaration.getNamedImports();
95
+ const remainingImports = namedImports.filter((ni)=>!namedImportsToRemove.includes(ni.getName()));
96
+ const moduleSpecifier = importDeclaration.getModuleSpecifierValue();
97
+ debug(`[AST] Removing named imports [${namedImportsToRemove.join(', ')}] from '${moduleSpecifier}'`);
98
+ debug(`[AST] Remaining imports: ${remainingImports.length}`);
99
+ if (remainingImports.length === 0 && !importDeclaration.getDefaultImport()) {
100
+ // No imports left, remove entire declaration
101
+ const allImports = sourceFile.getImportDeclarations();
102
+ const index = allImports.indexOf(importDeclaration);
103
+ importDeclaration.remove();
104
+ debug(`[AST] ✓ Removed entire import from '${moduleSpecifier}' (no remaining imports)`);
105
+ return {
106
+ fullyRemoved: true,
107
+ index,
108
+ sourceFile
109
+ };
110
+ } else {
111
+ // Remove specific named imports
112
+ namedImports.forEach((ni)=>{
113
+ if (namedImportsToRemove.includes(ni.getName())) {
114
+ ni.remove();
115
+ }
116
+ });
117
+ debug(`[AST] ✓ Removed named imports, kept ${remainingImports.length} import(s) from '${moduleSpecifier}'`);
118
+ return {
119
+ fullyRemoved: false,
120
+ sourceFile
121
+ };
122
+ }
123
+ }
124
+ /**
125
+ * Check if a named import is used in the source file
126
+ */ export function isNamedImportUsed(sourceFile, importName, excludeImports = true) {
127
+ const fullText = sourceFile.getFullText();
128
+ if (excludeImports) {
129
+ // Remove import declarations from consideration
130
+ const imports = sourceFile.getImportDeclarations();
131
+ let textWithoutImports = fullText;
132
+ imports.forEach((imp)=>{
133
+ const importText = imp.getFullText();
134
+ textWithoutImports = textWithoutImports.replace(importText, '');
135
+ });
136
+ // Check if import name appears in code (not in imports)
137
+ // Use word boundary to avoid partial matches
138
+ const regex = new RegExp(`\\b${importName}\\b`);
139
+ return regex.test(textWithoutImports);
140
+ }
141
+ // Simple check including imports
142
+ const regex = new RegExp(`\\b${importName}\\b`);
143
+ return regex.test(fullText);
144
+ }
145
+ /**
146
+ * Clean up orphaned imports - remove imports that are no longer used
147
+ */ export function cleanupOrphanedImports({ importNames, moduleSpecifier, sourceFile: inputSourceFile }) {
148
+ let sourceFile = inputSourceFile;
149
+ const importDecl = findImportDeclaration({
150
+ moduleSpecifier,
151
+ sourceFile
152
+ });
153
+ if (!importDecl) {
154
+ debug(`[AST] No import found from '${moduleSpecifier}' to clean up`);
155
+ return {
156
+ kept: [],
157
+ removed: [],
158
+ sourceFile
159
+ };
160
+ }
161
+ const removed = [];
162
+ const kept = [];
163
+ for (const importName of importNames){
164
+ const isUsed = isNamedImportUsed(sourceFile, importName);
165
+ if (!isUsed) {
166
+ removed.push(importName);
167
+ debug(`[AST] Import '${importName}' from '${moduleSpecifier}' is orphaned (not used)`);
168
+ } else {
169
+ kept.push(importName);
170
+ debug(`[AST] Import '${importName}' from '${moduleSpecifier}' is still used`);
171
+ }
172
+ }
173
+ if (removed.length > 0) {
174
+ ;
175
+ ({ sourceFile } = removeNamedImports({
176
+ importDeclaration: importDecl,
177
+ namedImportsToRemove: removed,
178
+ sourceFile
179
+ }));
180
+ debug(`[AST] ✓ Cleaned up ${removed.length} orphaned import(s) from '${moduleSpecifier}'`);
181
+ }
182
+ return {
183
+ kept,
184
+ removed,
185
+ sourceFile
186
+ };
187
+ }
188
+
189
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/lib/ast/utils.ts"],"sourcesContent":["import type { ImportDeclaration, SourceFile } from 'ts-morph'\n\nimport { existsSync } from 'fs'\nimport path from 'path'\n\nimport type {\n DetectionError,\n ImportCleanupResult,\n ImportRemovalResult,\n NamedImportRemovalResult,\n} from './types.js'\n\nimport { debug } from '../../utils/log.js'\n\nexport function findImportDeclaration({\n moduleSpecifier,\n sourceFile,\n}: {\n moduleSpecifier: string\n sourceFile: SourceFile\n}): ImportDeclaration | undefined {\n return sourceFile\n .getImportDeclarations()\n .find((imp) => imp.getModuleSpecifierValue() === moduleSpecifier)\n}\n\ntype FormatErrorOptions = {\n actual: string\n context: string\n debugInfo?: Record<string, unknown>\n expected: string\n technicalDetails: string\n}\n\nexport function formatError(options: FormatErrorOptions): DetectionError {\n const { actual, context, debugInfo, expected, technicalDetails } = options\n\n const userMessage = `Your config file doesn't match the expected structure for ${context}.\n\nExpected: ${expected}\nActual: ${actual}\n\nPlease ensure your config file follows the expected structure.`\n\n return {\n technicalDetails,\n userMessage,\n ...(debugInfo && { debugInfo }),\n }\n}\n\nexport function addImportDeclaration({\n defaultImport,\n insertIndex,\n moduleSpecifier,\n namedImports,\n sourceFile,\n}: {\n defaultImport?: string\n insertIndex?: number\n moduleSpecifier: string\n namedImports?: string[]\n sourceFile: SourceFile\n}): SourceFile {\n const existingImport = findImportDeclaration({ moduleSpecifier, sourceFile })\n\n if (existingImport) {\n // Add named imports to existing import if they don't exist\n if (namedImports) {\n const existingNamedImports = existingImport.getNamedImports().map((ni) => ni.getName())\n const newNamedImports = namedImports.filter((ni) => !existingNamedImports.includes(ni))\n\n if (newNamedImports.length > 0) {\n existingImport.addNamedImports(newNamedImports)\n debug(\n `[AST] Added named imports to existing import from '${moduleSpecifier}': ${newNamedImports.join(', ')}`,\n )\n } else {\n debug(`[AST] Import from '${moduleSpecifier}' already has all required named imports`)\n }\n }\n } else {\n // Create new import at specified index or at default position\n const importDeclaration = {\n moduleSpecifier,\n ...(namedImports && { namedImports }),\n ...(defaultImport && { defaultImport }),\n }\n\n if (insertIndex !== undefined) {\n sourceFile.insertImportDeclaration(insertIndex, importDeclaration)\n debug(`[AST] Inserted import from '${moduleSpecifier}' at index ${insertIndex}`)\n } else {\n sourceFile.addImportDeclaration(importDeclaration)\n debug(`[AST] Added import from '${moduleSpecifier}' at default position`)\n }\n\n const parts = []\n if (defaultImport) {\n parts.push(`default: ${defaultImport}`)\n }\n if (namedImports) {\n parts.push(`named: ${namedImports.join(', ')}`)\n }\n debug(`[AST] Import contents: ${parts.join(', ')}`)\n }\n\n return sourceFile\n}\n\nexport function removeImportDeclaration({\n moduleSpecifier,\n sourceFile,\n}: {\n moduleSpecifier: string\n sourceFile: SourceFile\n}): ImportRemovalResult {\n const importDecl = findImportDeclaration({ moduleSpecifier, sourceFile })\n if (importDecl) {\n // Get index before removing\n const allImports = sourceFile.getImportDeclarations()\n const index = allImports.indexOf(importDecl)\n importDecl.remove()\n debug(`[AST] Removed import from '${moduleSpecifier}' at index ${index}`)\n return { removedIndex: index, sourceFile }\n } else {\n debug(`[AST] Import from '${moduleSpecifier}' not found (already absent)`)\n return { removedIndex: undefined, sourceFile }\n }\n}\n\n/**\n * Remove specific named imports from an import declaration\n * If all named imports are removed, removes the entire declaration\n */\nexport function removeNamedImports({\n importDeclaration,\n namedImportsToRemove,\n sourceFile,\n}: {\n importDeclaration: ImportDeclaration\n namedImportsToRemove: string[]\n sourceFile: SourceFile\n}): NamedImportRemovalResult {\n const namedImports = importDeclaration.getNamedImports()\n const remainingImports = namedImports.filter((ni) => !namedImportsToRemove.includes(ni.getName()))\n\n const moduleSpecifier = importDeclaration.getModuleSpecifierValue()\n\n debug(\n `[AST] Removing named imports [${namedImportsToRemove.join(', ')}] from '${moduleSpecifier}'`,\n )\n debug(`[AST] Remaining imports: ${remainingImports.length}`)\n\n if (remainingImports.length === 0 && !importDeclaration.getDefaultImport()) {\n // No imports left, remove entire declaration\n const allImports = sourceFile.getImportDeclarations()\n const index = allImports.indexOf(importDeclaration)\n importDeclaration.remove()\n debug(`[AST] ✓ Removed entire import from '${moduleSpecifier}' (no remaining imports)`)\n return { fullyRemoved: true, index, sourceFile }\n } else {\n // Remove specific named imports\n namedImports.forEach((ni) => {\n if (namedImportsToRemove.includes(ni.getName())) {\n ni.remove()\n }\n })\n debug(\n `[AST] ✓ Removed named imports, kept ${remainingImports.length} import(s) from '${moduleSpecifier}'`,\n )\n return { fullyRemoved: false, sourceFile }\n }\n}\n\n/**\n * Check if a named import is used in the source file\n */\nexport function isNamedImportUsed(\n sourceFile: SourceFile,\n importName: string,\n excludeImports = true,\n): boolean {\n const fullText = sourceFile.getFullText()\n\n if (excludeImports) {\n // Remove import declarations from consideration\n const imports = sourceFile.getImportDeclarations()\n let textWithoutImports = fullText\n\n imports.forEach((imp) => {\n const importText = imp.getFullText()\n textWithoutImports = textWithoutImports.replace(importText, '')\n })\n\n // Check if import name appears in code (not in imports)\n // Use word boundary to avoid partial matches\n const regex = new RegExp(`\\\\b${importName}\\\\b`)\n return regex.test(textWithoutImports)\n }\n\n // Simple check including imports\n const regex = new RegExp(`\\\\b${importName}\\\\b`)\n return regex.test(fullText)\n}\n\n/**\n * Clean up orphaned imports - remove imports that are no longer used\n */\nexport function cleanupOrphanedImports({\n importNames,\n moduleSpecifier,\n sourceFile: inputSourceFile,\n}: {\n importNames: string[]\n moduleSpecifier: string\n sourceFile: SourceFile\n}): ImportCleanupResult {\n let sourceFile = inputSourceFile\n const importDecl = findImportDeclaration({ moduleSpecifier, sourceFile })\n\n if (!importDecl) {\n debug(`[AST] No import found from '${moduleSpecifier}' to clean up`)\n return { kept: [], removed: [], sourceFile }\n }\n\n const removed: string[] = []\n const kept: string[] = []\n\n for (const importName of importNames) {\n const isUsed = isNamedImportUsed(sourceFile, importName)\n\n if (!isUsed) {\n removed.push(importName)\n debug(`[AST] Import '${importName}' from '${moduleSpecifier}' is orphaned (not used)`)\n } else {\n kept.push(importName)\n debug(`[AST] Import '${importName}' from '${moduleSpecifier}' is still used`)\n }\n }\n\n if (removed.length > 0) {\n ;({ sourceFile } = removeNamedImports({\n importDeclaration: importDecl,\n namedImportsToRemove: removed,\n sourceFile,\n }))\n debug(`[AST] ✓ Cleaned up ${removed.length} orphaned import(s) from '${moduleSpecifier}'`)\n }\n\n return { kept, removed, sourceFile }\n}\n"],"names":["debug","findImportDeclaration","moduleSpecifier","sourceFile","getImportDeclarations","find","imp","getModuleSpecifierValue","formatError","options","actual","context","debugInfo","expected","technicalDetails","userMessage","addImportDeclaration","defaultImport","insertIndex","namedImports","existingImport","existingNamedImports","getNamedImports","map","ni","getName","newNamedImports","filter","includes","length","addNamedImports","join","importDeclaration","undefined","insertImportDeclaration","parts","push","removeImportDeclaration","importDecl","allImports","index","indexOf","remove","removedIndex","removeNamedImports","namedImportsToRemove","remainingImports","getDefaultImport","fullyRemoved","forEach","isNamedImportUsed","importName","excludeImports","fullText","getFullText","imports","textWithoutImports","importText","replace","regex","RegExp","test","cleanupOrphanedImports","importNames","inputSourceFile","kept","removed","isUsed"],"mappings":"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"}
@@ -0,0 +1,225 @@
1
+ import { Project } from 'ts-morph';
2
+ import { addImportDeclaration, cleanupOrphanedImports, findImportDeclaration, isNamedImportUsed, removeImportDeclaration, removeNamedImports } from './utils';
3
+ describe('findImportDeclaration', ()=>{
4
+ it('finds import by module specifier', ()=>{
5
+ const project = new Project({
6
+ useInMemoryFileSystem: true
7
+ });
8
+ const sourceFile = project.createSourceFile('test.ts', `import { buildConfig } from 'payload'
9
+ import { mongooseAdapter } from '@payloadcms/db-mongodb'`);
10
+ const result = findImportDeclaration({
11
+ sourceFile,
12
+ moduleSpecifier: '@payloadcms/db-mongodb'
13
+ });
14
+ expect(result).toBeDefined();
15
+ expect(result?.getModuleSpecifierValue()).toBe('@payloadcms/db-mongodb');
16
+ });
17
+ });
18
+ describe('addImportDeclaration', ()=>{
19
+ it('adds new import when not present', ()=>{
20
+ const project = new Project({
21
+ useInMemoryFileSystem: true
22
+ });
23
+ const sourceFile = project.createSourceFile('test.ts', `import { buildConfig } from 'payload'`);
24
+ const result = addImportDeclaration({
25
+ sourceFile,
26
+ moduleSpecifier: '@payloadcms/db-postgres',
27
+ namedImports: [
28
+ 'postgresAdapter'
29
+ ]
30
+ });
31
+ const imports = result.getImportDeclarations();
32
+ expect(imports).toHaveLength(2);
33
+ expect(imports[1].getModuleSpecifierValue()).toBe('@payloadcms/db-postgres');
34
+ expect(imports[1].getNamedImports()[0].getName()).toBe('postgresAdapter');
35
+ });
36
+ it('does not duplicate existing import', ()=>{
37
+ const project = new Project({
38
+ useInMemoryFileSystem: true
39
+ });
40
+ const sourceFile = project.createSourceFile('test.ts', `import { mongooseAdapter } from '@payloadcms/db-mongodb'`);
41
+ const result = addImportDeclaration({
42
+ sourceFile,
43
+ moduleSpecifier: '@payloadcms/db-mongodb',
44
+ namedImports: [
45
+ 'mongooseAdapter'
46
+ ]
47
+ });
48
+ const imports = result.getImportDeclarations();
49
+ expect(imports).toHaveLength(1);
50
+ });
51
+ it('adds named import to existing module import', ()=>{
52
+ const project = new Project({
53
+ useInMemoryFileSystem: true
54
+ });
55
+ const sourceFile = project.createSourceFile('test.ts', `import { buildConfig } from 'payload'`);
56
+ const result = addImportDeclaration({
57
+ sourceFile,
58
+ moduleSpecifier: 'payload',
59
+ namedImports: [
60
+ 'Field'
61
+ ]
62
+ });
63
+ const imports = result.getImportDeclarations();
64
+ expect(imports).toHaveLength(1);
65
+ const namedImports = imports[0].getNamedImports().map((ni)=>ni.getName());
66
+ expect(namedImports).toContain('buildConfig');
67
+ expect(namedImports).toContain('Field');
68
+ });
69
+ });
70
+ describe('removeImportDeclaration', ()=>{
71
+ it('removes import by module specifier', ()=>{
72
+ const project = new Project({
73
+ useInMemoryFileSystem: true
74
+ });
75
+ const sourceFile = project.createSourceFile('test.ts', `import { buildConfig } from 'payload'
76
+ import sharp from 'sharp'`);
77
+ const result = removeImportDeclaration({
78
+ sourceFile,
79
+ moduleSpecifier: 'sharp'
80
+ });
81
+ expect(result.removedIndex).toBe(1);
82
+ const imports = result.sourceFile.getImportDeclarations();
83
+ expect(imports).toHaveLength(1);
84
+ expect(imports[0].getModuleSpecifierValue()).toBe('payload');
85
+ });
86
+ it('returns undefined removedIndex when import not found', ()=>{
87
+ const project = new Project({
88
+ useInMemoryFileSystem: true
89
+ });
90
+ const sourceFile = project.createSourceFile('test.ts', `import { buildConfig } from 'payload'`);
91
+ const result = removeImportDeclaration({
92
+ sourceFile,
93
+ moduleSpecifier: 'sharp'
94
+ });
95
+ expect(result.removedIndex).toBeUndefined();
96
+ });
97
+ });
98
+ describe('removeNamedImports', ()=>{
99
+ it('removes specific named imports', ()=>{
100
+ const project = new Project({
101
+ useInMemoryFileSystem: true
102
+ });
103
+ const sourceFile = project.createSourceFile('test.ts', `import { mongooseAdapter, SomeOtherType } from '@payloadcms/db-mongodb'`);
104
+ const importDecl = findImportDeclaration({
105
+ sourceFile,
106
+ moduleSpecifier: '@payloadcms/db-mongodb'
107
+ });
108
+ const result = removeNamedImports({
109
+ sourceFile,
110
+ importDeclaration: importDecl,
111
+ namedImportsToRemove: [
112
+ 'mongooseAdapter'
113
+ ]
114
+ });
115
+ expect(result.fullyRemoved).toBe(false);
116
+ const imports = result.sourceFile.getImportDeclarations();
117
+ expect(imports).toHaveLength(1);
118
+ const namedImports = imports[0].getNamedImports().map((ni)=>ni.getName());
119
+ expect(namedImports).toEqual([
120
+ 'SomeOtherType'
121
+ ]);
122
+ });
123
+ it('removes entire import when no named imports remain', ()=>{
124
+ const project = new Project({
125
+ useInMemoryFileSystem: true
126
+ });
127
+ const sourceFile = project.createSourceFile('test.ts', `import { buildConfig } from 'payload'
128
+ import { mongooseAdapter } from '@payloadcms/db-mongodb'`);
129
+ const importDecl = findImportDeclaration({
130
+ sourceFile,
131
+ moduleSpecifier: '@payloadcms/db-mongodb'
132
+ });
133
+ const result = removeNamedImports({
134
+ sourceFile,
135
+ importDeclaration: importDecl,
136
+ namedImportsToRemove: [
137
+ 'mongooseAdapter'
138
+ ]
139
+ });
140
+ expect(result.fullyRemoved).toBe(true);
141
+ expect(result.index).toBe(1);
142
+ const imports = result.sourceFile.getImportDeclarations();
143
+ expect(imports).toHaveLength(1);
144
+ expect(imports[0].getModuleSpecifierValue()).toBe('payload');
145
+ });
146
+ });
147
+ describe('isNamedImportUsed', ()=>{
148
+ it('detects used import in code', ()=>{
149
+ const project = new Project({
150
+ useInMemoryFileSystem: true
151
+ });
152
+ const sourceFile = project.createSourceFile('test.ts', `import { mongooseAdapter } from '@payloadcms/db-mongodb'
153
+
154
+ export default buildConfig({
155
+ db: mongooseAdapter({ url: '' })
156
+ })`);
157
+ const isUsed = isNamedImportUsed(sourceFile, 'mongooseAdapter');
158
+ expect(isUsed).toBe(true);
159
+ });
160
+ it('detects unused import', ()=>{
161
+ const project = new Project({
162
+ useInMemoryFileSystem: true
163
+ });
164
+ const sourceFile = project.createSourceFile('test.ts', `import { mongooseAdapter } from '@payloadcms/db-mongodb'
165
+ import { postgresAdapter } from '@payloadcms/db-postgres'
166
+
167
+ export default buildConfig({
168
+ db: postgresAdapter({ url: '' })
169
+ })`);
170
+ const isUsed = isNamedImportUsed(sourceFile, 'mongooseAdapter');
171
+ expect(isUsed).toBe(false);
172
+ });
173
+ });
174
+ describe('cleanupOrphanedImports', ()=>{
175
+ it('removes unused imports', ()=>{
176
+ const project = new Project({
177
+ useInMemoryFileSystem: true
178
+ });
179
+ const sourceFile = project.createSourceFile('test.ts', `import { mongooseAdapter } from '@payloadcms/db-mongodb'
180
+ import { postgresAdapter } from '@payloadcms/db-postgres'
181
+
182
+ export default buildConfig({
183
+ db: postgresAdapter({ url: '' })
184
+ })`);
185
+ const result = cleanupOrphanedImports({
186
+ sourceFile,
187
+ moduleSpecifier: '@payloadcms/db-mongodb',
188
+ importNames: [
189
+ 'mongooseAdapter'
190
+ ]
191
+ });
192
+ expect(result.removed).toEqual([
193
+ 'mongooseAdapter'
194
+ ]);
195
+ expect(result.kept).toEqual([]);
196
+ const imports = result.sourceFile.getImportDeclarations();
197
+ expect(imports).toHaveLength(1);
198
+ expect(imports[0].getModuleSpecifierValue()).toBe('@payloadcms/db-postgres');
199
+ });
200
+ it('keeps used imports', ()=>{
201
+ const project = new Project({
202
+ useInMemoryFileSystem: true
203
+ });
204
+ const sourceFile = project.createSourceFile('test.ts', `import { mongooseAdapter } from '@payloadcms/db-mongodb'
205
+
206
+ export default buildConfig({
207
+ db: mongooseAdapter({ url: '' })
208
+ })`);
209
+ const result = cleanupOrphanedImports({
210
+ sourceFile,
211
+ moduleSpecifier: '@payloadcms/db-mongodb',
212
+ importNames: [
213
+ 'mongooseAdapter'
214
+ ]
215
+ });
216
+ expect(result.removed).toEqual([]);
217
+ expect(result.kept).toEqual([
218
+ 'mongooseAdapter'
219
+ ]);
220
+ const imports = result.sourceFile.getImportDeclarations();
221
+ expect(imports).toHaveLength(1);
222
+ });
223
+ });
224
+
225
+ //# sourceMappingURL=utils.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/lib/ast/utils.spec.ts"],"sourcesContent":["import { Project } from 'ts-morph'\nimport {\n addImportDeclaration,\n cleanupOrphanedImports,\n findImportDeclaration,\n isNamedImportUsed,\n removeImportDeclaration,\n removeNamedImports,\n} from './utils'\n\ndescribe('findImportDeclaration', () => {\n it('finds import by module specifier', () => {\n const project = new Project({ useInMemoryFileSystem: true })\n const sourceFile = project.createSourceFile(\n 'test.ts',\n `import { buildConfig } from 'payload'\nimport { mongooseAdapter } from '@payloadcms/db-mongodb'`,\n )\n\n const result = findImportDeclaration({ sourceFile, moduleSpecifier: '@payloadcms/db-mongodb' })\n\n expect(result).toBeDefined()\n expect(result?.getModuleSpecifierValue()).toBe('@payloadcms/db-mongodb')\n })\n})\n\ndescribe('addImportDeclaration', () => {\n it('adds new import when not present', () => {\n const project = new Project({ useInMemoryFileSystem: true })\n const sourceFile = project.createSourceFile('test.ts', `import { buildConfig } from 'payload'`)\n\n const result = addImportDeclaration({\n sourceFile,\n moduleSpecifier: '@payloadcms/db-postgres',\n namedImports: ['postgresAdapter'],\n })\n\n const imports = result.getImportDeclarations()\n expect(imports).toHaveLength(2)\n expect(imports[1].getModuleSpecifierValue()).toBe('@payloadcms/db-postgres')\n expect(imports[1].getNamedImports()[0].getName()).toBe('postgresAdapter')\n })\n\n it('does not duplicate existing import', () => {\n const project = new Project({ useInMemoryFileSystem: true })\n const sourceFile = project.createSourceFile(\n 'test.ts',\n `import { mongooseAdapter } from '@payloadcms/db-mongodb'`,\n )\n\n const result = addImportDeclaration({\n sourceFile,\n moduleSpecifier: '@payloadcms/db-mongodb',\n namedImports: ['mongooseAdapter'],\n })\n\n const imports = result.getImportDeclarations()\n expect(imports).toHaveLength(1)\n })\n\n it('adds named import to existing module import', () => {\n const project = new Project({ useInMemoryFileSystem: true })\n const sourceFile = project.createSourceFile('test.ts', `import { buildConfig } from 'payload'`)\n\n const result = addImportDeclaration({\n sourceFile,\n moduleSpecifier: 'payload',\n namedImports: ['Field'],\n })\n\n const imports = result.getImportDeclarations()\n expect(imports).toHaveLength(1)\n const namedImports = imports[0].getNamedImports().map((ni) => ni.getName())\n expect(namedImports).toContain('buildConfig')\n expect(namedImports).toContain('Field')\n })\n})\n\ndescribe('removeImportDeclaration', () => {\n it('removes import by module specifier', () => {\n const project = new Project({ useInMemoryFileSystem: true })\n const sourceFile = project.createSourceFile(\n 'test.ts',\n `import { buildConfig } from 'payload'\nimport sharp from 'sharp'`,\n )\n\n const result = removeImportDeclaration({ sourceFile, moduleSpecifier: 'sharp' })\n\n expect(result.removedIndex).toBe(1)\n const imports = result.sourceFile.getImportDeclarations()\n expect(imports).toHaveLength(1)\n expect(imports[0].getModuleSpecifierValue()).toBe('payload')\n })\n\n it('returns undefined removedIndex when import not found', () => {\n const project = new Project({ useInMemoryFileSystem: true })\n const sourceFile = project.createSourceFile('test.ts', `import { buildConfig } from 'payload'`)\n\n const result = removeImportDeclaration({ sourceFile, moduleSpecifier: 'sharp' })\n\n expect(result.removedIndex).toBeUndefined()\n })\n})\n\ndescribe('removeNamedImports', () => {\n it('removes specific named imports', () => {\n const project = new Project({ useInMemoryFileSystem: true })\n const sourceFile = project.createSourceFile(\n 'test.ts',\n `import { mongooseAdapter, SomeOtherType } from '@payloadcms/db-mongodb'`,\n )\n\n const importDecl = findImportDeclaration({\n sourceFile,\n moduleSpecifier: '@payloadcms/db-mongodb',\n })!\n\n const result = removeNamedImports({\n sourceFile,\n importDeclaration: importDecl,\n namedImportsToRemove: ['mongooseAdapter'],\n })\n\n expect(result.fullyRemoved).toBe(false)\n const imports = result.sourceFile.getImportDeclarations()\n expect(imports).toHaveLength(1)\n const namedImports = imports[0].getNamedImports().map((ni) => ni.getName())\n expect(namedImports).toEqual(['SomeOtherType'])\n })\n\n it('removes entire import when no named imports remain', () => {\n const project = new Project({ useInMemoryFileSystem: true })\n const sourceFile = project.createSourceFile(\n 'test.ts',\n `import { buildConfig } from 'payload'\nimport { mongooseAdapter } from '@payloadcms/db-mongodb'`,\n )\n\n const importDecl = findImportDeclaration({\n sourceFile,\n moduleSpecifier: '@payloadcms/db-mongodb',\n })!\n\n const result = removeNamedImports({\n sourceFile,\n importDeclaration: importDecl,\n namedImportsToRemove: ['mongooseAdapter'],\n })\n\n expect(result.fullyRemoved).toBe(true)\n expect(result.index).toBe(1)\n const imports = result.sourceFile.getImportDeclarations()\n expect(imports).toHaveLength(1)\n expect(imports[0].getModuleSpecifierValue()).toBe('payload')\n })\n})\n\ndescribe('isNamedImportUsed', () => {\n it('detects used import in code', () => {\n const project = new Project({ useInMemoryFileSystem: true })\n const sourceFile = project.createSourceFile(\n 'test.ts',\n `import { mongooseAdapter } from '@payloadcms/db-mongodb'\n\nexport default buildConfig({\n db: mongooseAdapter({ url: '' })\n})`,\n )\n\n const isUsed = isNamedImportUsed(sourceFile, 'mongooseAdapter')\n\n expect(isUsed).toBe(true)\n })\n\n it('detects unused import', () => {\n const project = new Project({ useInMemoryFileSystem: true })\n const sourceFile = project.createSourceFile(\n 'test.ts',\n `import { mongooseAdapter } from '@payloadcms/db-mongodb'\nimport { postgresAdapter } from '@payloadcms/db-postgres'\n\nexport default buildConfig({\n db: postgresAdapter({ url: '' })\n})`,\n )\n\n const isUsed = isNamedImportUsed(sourceFile, 'mongooseAdapter')\n\n expect(isUsed).toBe(false)\n })\n})\n\ndescribe('cleanupOrphanedImports', () => {\n it('removes unused imports', () => {\n const project = new Project({ useInMemoryFileSystem: true })\n const sourceFile = project.createSourceFile(\n 'test.ts',\n `import { mongooseAdapter } from '@payloadcms/db-mongodb'\nimport { postgresAdapter } from '@payloadcms/db-postgres'\n\nexport default buildConfig({\n db: postgresAdapter({ url: '' })\n})`,\n )\n\n const result = cleanupOrphanedImports({\n sourceFile,\n moduleSpecifier: '@payloadcms/db-mongodb',\n importNames: ['mongooseAdapter'],\n })\n\n expect(result.removed).toEqual(['mongooseAdapter'])\n expect(result.kept).toEqual([])\n const imports = result.sourceFile.getImportDeclarations()\n expect(imports).toHaveLength(1)\n expect(imports[0].getModuleSpecifierValue()).toBe('@payloadcms/db-postgres')\n })\n\n it('keeps used imports', () => {\n const project = new Project({ useInMemoryFileSystem: true })\n const sourceFile = project.createSourceFile(\n 'test.ts',\n `import { mongooseAdapter } from '@payloadcms/db-mongodb'\n\nexport default buildConfig({\n db: mongooseAdapter({ url: '' })\n})`,\n )\n\n const result = cleanupOrphanedImports({\n sourceFile,\n moduleSpecifier: '@payloadcms/db-mongodb',\n importNames: ['mongooseAdapter'],\n })\n\n expect(result.removed).toEqual([])\n expect(result.kept).toEqual(['mongooseAdapter'])\n const imports = result.sourceFile.getImportDeclarations()\n expect(imports).toHaveLength(1)\n })\n})\n"],"names":["Project","addImportDeclaration","cleanupOrphanedImports","findImportDeclaration","isNamedImportUsed","removeImportDeclaration","removeNamedImports","describe","it","project","useInMemoryFileSystem","sourceFile","createSourceFile","result","moduleSpecifier","expect","toBeDefined","getModuleSpecifierValue","toBe","namedImports","imports","getImportDeclarations","toHaveLength","getNamedImports","getName","map","ni","toContain","removedIndex","toBeUndefined","importDecl","importDeclaration","namedImportsToRemove","fullyRemoved","toEqual","index","isUsed","importNames","removed","kept"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"configure-payload-config.d.ts","sourceRoot":"","sources":["../../src/lib/configure-payload-config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAK7D,gEAAgE;AAChE,wBAAsB,sBAAsB,CAAC,IAAI,EAAE;IACjD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE;QACT,KAAK,EAAE,MAAM,CAAA;KACd,CAAA;IACD,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,sBAAsB,EAAE;QAAE,iBAAiB,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,CAAA;IAC9E,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,cAAc,CAAC,EAAE,kBAAkB,CAAA;CACpC,GAAG,OAAO,CAAC,IAAI,CAAC,CA8HhB"}
1
+ {"version":3,"file":"configure-payload-config.d.ts","sourceRoot":"","sources":["../../src/lib/configure-payload-config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAkB7D,gEAAgE;AAChE,wBAAsB,sBAAsB,CAAC,IAAI,EAAE;IACjD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE;QACT,KAAK,EAAE,MAAM,CAAA;KACd,CAAA;IACD,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,sBAAsB,EAAE;QAAE,iBAAiB,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,CAAA;IAC9E,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,cAAc,CAAC,EAAE,kBAAkB,CAAA;CACpC,GAAG,OAAO,CAAC,IAAI,CAAC,CAgEhB"}