create-payload-app 3.67.0-internal.87c53da → 3.67.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 +4 -2
  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,655 @@
1
+ import { execSync } from 'child_process';
2
+ import path from 'path';
3
+ import { Project, QuoteKind, SyntaxKind } from 'ts-morph';
4
+ import { debug } from '../../utils/log.js';
5
+ import { DB_ADAPTER_CONFIG, STORAGE_ADAPTER_CONFIG } from './adapter-config.js';
6
+ import { addImportDeclaration, cleanupOrphanedImports, formatError, removeImportDeclaration } from './utils.js';
7
+ export function detectPayloadConfigStructure(sourceFile) {
8
+ debug(`[AST] Detecting payload config structure in ${sourceFile.getFilePath()}`);
9
+ // First find the actual name being used (might be aliased)
10
+ const payloadImport = sourceFile.getImportDeclarations().find((imp)=>imp.getModuleSpecifierValue() === 'payload');
11
+ const buildConfigImportSpec = payloadImport?.getNamedImports().find((spec)=>spec.getName() === 'buildConfig');
12
+ const aliasNode = buildConfigImportSpec?.getAliasNode();
13
+ const buildConfigName = aliasNode ? aliasNode.getText() : 'buildConfig';
14
+ debug(`[AST] Looking for function call: ${buildConfigName}`);
15
+ // Find buildConfig call expression (using actual name in code)
16
+ const buildConfigCall = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).find((call)=>{
17
+ const expression = call.getExpression();
18
+ return expression.getText() === buildConfigName;
19
+ });
20
+ if (!buildConfigCall) {
21
+ debug(`[AST] ✗ ${buildConfigName} call not found`);
22
+ return {
23
+ error: formatError({
24
+ actual: `No ${buildConfigName} call found in file`,
25
+ context: 'buildConfig call',
26
+ expected: `export default ${buildConfigName}({ ... })`,
27
+ technicalDetails: `Could not find CallExpression with identifier "${buildConfigName}"`
28
+ }),
29
+ success: false
30
+ };
31
+ }
32
+ debug(`[AST] ✓ ${buildConfigName} call found`);
33
+ // Get import statements
34
+ const importStatements = sourceFile.getImportDeclarations();
35
+ debug(`[AST] Found ${importStatements.length} import statements`);
36
+ // Find db property if it exists
37
+ const configObject = buildConfigCall.getArguments()[0];
38
+ let dbProperty;
39
+ if (configObject && configObject.getKind() === SyntaxKind.ObjectLiteralExpression) {
40
+ dbProperty = configObject.asKindOrThrow(SyntaxKind.ObjectLiteralExpression).getProperty('db')?.asKind(SyntaxKind.PropertyAssignment);
41
+ }
42
+ debug(`[AST] db property: ${dbProperty ? '✓ found' : '✗ not found'}`);
43
+ // Find plugins array if it exists
44
+ let pluginsArray;
45
+ if (configObject && configObject.getKind() === SyntaxKind.ObjectLiteralExpression) {
46
+ const objLiteral = configObject.asKindOrThrow(SyntaxKind.ObjectLiteralExpression);
47
+ const pluginsProperty = objLiteral.getProperty('plugins');
48
+ // Handle PropertyAssignment (e.g., plugins: [...])
49
+ const propertyAssignment = pluginsProperty?.asKind(SyntaxKind.PropertyAssignment);
50
+ if (propertyAssignment) {
51
+ const initializer = propertyAssignment.getInitializer();
52
+ if (initializer?.getKind() === SyntaxKind.ArrayLiteralExpression) {
53
+ pluginsArray = initializer.asKind(SyntaxKind.ArrayLiteralExpression);
54
+ }
55
+ }
56
+ // For ShorthandPropertyAssignment (e.g., plugins), we can't get the array directly
57
+ // but we'll detect it in addStorageAdapter
58
+ }
59
+ debug(`[AST] plugins array: ${pluginsArray ? '✓ found' : '✗ not found'}`);
60
+ // Find all buildConfig calls for edge case detection (using actual name)
61
+ const allBuildConfigCalls = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).filter((call)=>{
62
+ const expression = call.getExpression();
63
+ return expression.getText() === buildConfigName;
64
+ });
65
+ const hasImportAlias = !!aliasNode;
66
+ // Check for other Payload imports
67
+ const payloadImports = payloadImport?.getNamedImports() || [];
68
+ const hasOtherPayloadImports = payloadImports.length > 1 || payloadImports.some((imp)=>imp.getName() !== 'buildConfig');
69
+ // Track database adapter imports
70
+ let dbAdapterImportInfo;
71
+ for (const [, config] of Object.entries(DB_ADAPTER_CONFIG)){
72
+ const importDecl = sourceFile.getImportDeclarations().find((imp)=>imp.getModuleSpecifierValue() === config.packageName);
73
+ if (importDecl) {
74
+ const namedImports = importDecl.getNamedImports();
75
+ dbAdapterImportInfo = {
76
+ hasOtherImports: namedImports.length > 1,
77
+ importDeclaration: importDecl,
78
+ packageName: config.packageName
79
+ };
80
+ break;
81
+ }
82
+ }
83
+ // Track storage adapter imports
84
+ const storageAdapterImports = [];
85
+ for (const [, config] of Object.entries(STORAGE_ADAPTER_CONFIG)){
86
+ if (!config.packageName || !config.adapterName) {
87
+ continue;
88
+ }
89
+ const importDecl = sourceFile.getImportDeclarations().find((imp)=>imp.getModuleSpecifierValue() === config.packageName);
90
+ if (importDecl) {
91
+ const namedImports = importDecl.getNamedImports();
92
+ storageAdapterImports.push({
93
+ hasOtherImports: namedImports.length > 1,
94
+ importDeclaration: importDecl,
95
+ packageName: config.packageName
96
+ });
97
+ }
98
+ }
99
+ const needsManualIntervention = hasImportAlias || allBuildConfigCalls.length > 2;
100
+ debug(`[AST] Edge cases: alias=${hasImportAlias}, multiple=${allBuildConfigCalls.length > 1}, otherImports=${hasOtherPayloadImports}, manual=${needsManualIntervention}`);
101
+ return {
102
+ edgeCases: {
103
+ hasImportAlias,
104
+ hasOtherPayloadImports,
105
+ multipleBuildConfigCalls: allBuildConfigCalls.length > 1,
106
+ needsManualIntervention
107
+ },
108
+ importSources: {
109
+ dbAdapter: dbAdapterImportInfo,
110
+ storageAdapters: storageAdapterImports.length > 0 ? storageAdapterImports : undefined
111
+ },
112
+ sourceFile,
113
+ structures: {
114
+ buildConfigCall,
115
+ dbProperty,
116
+ importStatements,
117
+ pluginsArray
118
+ },
119
+ success: true
120
+ };
121
+ }
122
+ export function addDatabaseAdapter({ adapter, envVarName = 'DATABASE_URI', sourceFile }) {
123
+ debug(`[AST] Adding database adapter: ${adapter} (envVar: ${envVarName})`);
124
+ const modifications = [];
125
+ const detection = detectPayloadConfigStructure(sourceFile);
126
+ if (!detection.success || !detection.structures) {
127
+ return {
128
+ error: detection.error,
129
+ modifications: [],
130
+ modified: false,
131
+ success: false
132
+ };
133
+ }
134
+ const { buildConfigCall, dbProperty } = detection.structures;
135
+ const config = DB_ADAPTER_CONFIG[adapter];
136
+ // Remove old db adapter imports and track position for replacement
137
+ const oldAdapters = Object.values(DB_ADAPTER_CONFIG);
138
+ const removedAdapters = [];
139
+ let importInsertIndex;
140
+ oldAdapters.forEach((oldConfig)=>{
141
+ if (oldConfig.packageName !== config.packageName) {
142
+ const { removedIndex } = removeImportDeclaration({
143
+ moduleSpecifier: oldConfig.packageName,
144
+ sourceFile
145
+ });
146
+ if (removedIndex !== undefined) {
147
+ // Use the first removed adapter's position
148
+ if (importInsertIndex === undefined) {
149
+ importInsertIndex = removedIndex;
150
+ }
151
+ removedAdapters.push(oldConfig.packageName);
152
+ modifications.push({
153
+ type: 'import-removed',
154
+ description: `Removed import from '${oldConfig.packageName}'`
155
+ });
156
+ }
157
+ }
158
+ });
159
+ if (removedAdapters.length > 0) {
160
+ debug(`[AST] Removed old adapter imports: ${removedAdapters.join(', ')}`);
161
+ }
162
+ // Add new import at the position of the removed one (or default position)
163
+ addImportDeclaration({
164
+ insertIndex: importInsertIndex,
165
+ moduleSpecifier: config.packageName,
166
+ namedImports: [
167
+ config.adapterName
168
+ ],
169
+ sourceFile
170
+ });
171
+ modifications.push({
172
+ type: 'import-added',
173
+ description: `Added import: { ${config.adapterName} } from '${config.packageName}'`
174
+ });
175
+ // Add special imports for specific adapters
176
+ if (adapter === 'd1-sqlite') {
177
+ debug('[AST] Adding special import: ./db/migrations');
178
+ addImportDeclaration({
179
+ defaultImport: 'migrations',
180
+ moduleSpecifier: './db/migrations',
181
+ sourceFile
182
+ });
183
+ modifications.push({
184
+ type: 'import-added',
185
+ description: `Added import: migrations from './db/migrations'`
186
+ });
187
+ }
188
+ // Get config object
189
+ const configObject = buildConfigCall.getArguments()[0];
190
+ if (!configObject || configObject.getKind() !== SyntaxKind.ObjectLiteralExpression) {
191
+ return {
192
+ error: formatError({
193
+ actual: 'buildConfig has no object literal argument',
194
+ context: 'database adapter configuration',
195
+ expected: 'buildConfig({ ... })',
196
+ technicalDetails: 'buildConfig call must have an object literal as first argument'
197
+ }),
198
+ modifications: [],
199
+ modified: false,
200
+ success: false
201
+ };
202
+ }
203
+ const objLiteral = configObject.asKindOrThrow(SyntaxKind.ObjectLiteralExpression);
204
+ const newDbCode = `db: ${config.configTemplate(envVarName)}`;
205
+ if (dbProperty) {
206
+ // Replace existing db property
207
+ // NOTE: Using replaceWithText() instead of remove() + insertPropertyAssignment()
208
+ // to avoid double comma issues. When remove() is called, ts-morph doesn't always
209
+ // clean up trailing commas correctly, which can result in syntax like "},," when
210
+ // inserting a new property at that position. replaceWithText() preserves the
211
+ // surrounding punctuation correctly.
212
+ debug(`[AST] Replacing existing db property`);
213
+ dbProperty.replaceWithText(newDbCode);
214
+ modifications.push({
215
+ type: 'property-added',
216
+ description: `Replaced db property with ${adapter} adapter`
217
+ });
218
+ } else {
219
+ // No existing db property - insert at end
220
+ const insertIndex = objLiteral.getProperties().length;
221
+ debug(`[AST] Adding db property at index ${insertIndex}`);
222
+ objLiteral.insertPropertyAssignment(insertIndex, {
223
+ name: 'db',
224
+ initializer: config.configTemplate(envVarName)
225
+ });
226
+ modifications.push({
227
+ type: 'property-added',
228
+ description: `Added db property with ${adapter} adapter`
229
+ });
230
+ }
231
+ debug(`[AST] ✓ Database adapter ${adapter} added successfully`);
232
+ return {
233
+ modifications,
234
+ modified: true,
235
+ success: true
236
+ };
237
+ }
238
+ export function addStorageAdapter({ adapter, sourceFile }) {
239
+ debug(`[AST] Adding storage adapter: ${adapter}`);
240
+ const modifications = [];
241
+ const detection = detectPayloadConfigStructure(sourceFile);
242
+ if (!detection.success || !detection.structures) {
243
+ return {
244
+ error: detection.error,
245
+ modifications: [],
246
+ modified: false,
247
+ success: false
248
+ };
249
+ }
250
+ const config = STORAGE_ADAPTER_CONFIG[adapter];
251
+ // Local disk doesn't need any imports or plugins
252
+ if (adapter === 'localDisk') {
253
+ debug('[AST] localDisk storage adapter - no imports or plugins needed');
254
+ return {
255
+ modifications: [],
256
+ modified: false,
257
+ success: true
258
+ };
259
+ }
260
+ // Add import
261
+ if (config.packageName && config.adapterName) {
262
+ addImportDeclaration({
263
+ moduleSpecifier: config.packageName,
264
+ namedImports: [
265
+ config.adapterName
266
+ ],
267
+ sourceFile
268
+ });
269
+ modifications.push({
270
+ type: 'import-added',
271
+ description: `Added import: { ${config.adapterName} } from '${config.packageName}'`
272
+ });
273
+ }
274
+ const { buildConfigCall } = detection.structures;
275
+ const configObject = buildConfigCall.getArguments()[0];
276
+ if (!configObject || configObject.getKind() !== SyntaxKind.ObjectLiteralExpression) {
277
+ return {
278
+ error: formatError({
279
+ actual: 'buildConfig has no object literal argument',
280
+ context: 'storage adapter configuration',
281
+ expected: 'buildConfig({ ... })',
282
+ technicalDetails: 'buildConfig call must have an object literal as first argument'
283
+ }),
284
+ modifications: [],
285
+ modified: false,
286
+ success: false
287
+ };
288
+ }
289
+ const objLiteral = configObject.asKindOrThrow(SyntaxKind.ObjectLiteralExpression);
290
+ // Find or create plugins array
291
+ const pluginsPropertyRaw = objLiteral.getProperty('plugins');
292
+ let pluginsProperty = pluginsPropertyRaw?.asKind(SyntaxKind.PropertyAssignment);
293
+ // Check if it's a shorthand property (e.g., `plugins` referencing an imported variable)
294
+ const shorthandProperty = pluginsPropertyRaw?.asKind(SyntaxKind.ShorthandPropertyAssignment);
295
+ if (shorthandProperty) {
296
+ debug('[AST] Found shorthand plugins property, converting to long form with spread');
297
+ // Get the identifier name (usually 'plugins')
298
+ const identifierName = shorthandProperty.getName();
299
+ // Find insert position before removing
300
+ const allProperties = objLiteral.getProperties();
301
+ const insertIndex = allProperties.indexOf(shorthandProperty);
302
+ // Remove the shorthand property
303
+ shorthandProperty.remove();
304
+ // Create new property with spread operator: plugins: [...plugins, newAdapter]
305
+ objLiteral.insertPropertyAssignment(insertIndex, {
306
+ name: 'plugins',
307
+ initializer: `[...${identifierName}]`
308
+ });
309
+ pluginsProperty = objLiteral.getProperty('plugins')?.asKind(SyntaxKind.PropertyAssignment);
310
+ modifications.push({
311
+ type: 'property-added',
312
+ description: `Converted shorthand plugins property to array with spread syntax`
313
+ });
314
+ } else if (!pluginsProperty) {
315
+ debug('[AST] Creating new plugins array');
316
+ // Create plugins array
317
+ objLiteral.addPropertyAssignment({
318
+ name: 'plugins',
319
+ initializer: '[]'
320
+ });
321
+ pluginsProperty = objLiteral.getProperty('plugins')?.asKind(SyntaxKind.PropertyAssignment);
322
+ modifications.push({
323
+ type: 'property-added',
324
+ description: `Created plugins array`
325
+ });
326
+ } else {
327
+ debug('[AST] Reusing existing plugins array');
328
+ }
329
+ if (!pluginsProperty) {
330
+ return {
331
+ error: formatError({
332
+ actual: 'Failed to create or find plugins property',
333
+ context: 'storage adapter configuration',
334
+ expected: 'plugins array property',
335
+ technicalDetails: 'Could not create or access plugins property'
336
+ }),
337
+ modifications: [],
338
+ modified: false,
339
+ success: false
340
+ };
341
+ }
342
+ const initializer = pluginsProperty.getInitializer();
343
+ if (!initializer || initializer.getKind() !== SyntaxKind.ArrayLiteralExpression) {
344
+ return {
345
+ error: formatError({
346
+ actual: 'plugins property is not an array',
347
+ context: 'storage adapter configuration',
348
+ expected: 'plugins: [...]',
349
+ technicalDetails: 'plugins property must be an array literal expression'
350
+ }),
351
+ modifications: [],
352
+ modified: false,
353
+ success: false
354
+ };
355
+ }
356
+ const pluginsArray = initializer.asKindOrThrow(SyntaxKind.ArrayLiteralExpression);
357
+ // Add storage adapter call
358
+ const configText = config.configTemplate();
359
+ if (configText) {
360
+ pluginsArray.addElement(configText);
361
+ modifications.push({
362
+ type: 'property-added',
363
+ description: `Added ${adapter} to plugins array`
364
+ });
365
+ }
366
+ debug(`[AST] ✓ Storage adapter ${adapter} added successfully`);
367
+ return {
368
+ modifications,
369
+ modified: true,
370
+ success: true
371
+ };
372
+ }
373
+ export function removeSharp(sourceFile) {
374
+ debug('[AST] Removing sharp import and property');
375
+ const modifications = [];
376
+ // Remove import
377
+ const { removedIndex } = removeImportDeclaration({
378
+ moduleSpecifier: 'sharp',
379
+ sourceFile
380
+ });
381
+ if (removedIndex !== undefined) {
382
+ modifications.push({
383
+ type: 'import-removed',
384
+ description: `Removed import from 'sharp'`
385
+ });
386
+ }
387
+ // Find and remove sharp property from buildConfig
388
+ const detection = detectPayloadConfigStructure(sourceFile);
389
+ if (!detection.success || !detection.structures) {
390
+ // If detection failed but we removed import, still count as partial success
391
+ if (modifications.length > 0) {
392
+ return {
393
+ modifications,
394
+ modified: true,
395
+ success: true,
396
+ warnings: [
397
+ 'Could not detect config structure to remove sharp property'
398
+ ]
399
+ };
400
+ }
401
+ return {
402
+ error: detection.error,
403
+ modifications: [],
404
+ modified: false,
405
+ success: false
406
+ };
407
+ }
408
+ const { buildConfigCall } = detection.structures;
409
+ const configObject = buildConfigCall.getArguments()[0];
410
+ if (!configObject || configObject.getKind() !== SyntaxKind.ObjectLiteralExpression) {
411
+ return {
412
+ modifications,
413
+ modified: modifications.length > 0,
414
+ success: true,
415
+ warnings: [
416
+ 'buildConfig has no object literal argument - could not remove sharp property'
417
+ ]
418
+ };
419
+ }
420
+ const objLiteral = configObject.asKindOrThrow(SyntaxKind.ObjectLiteralExpression);
421
+ const sharpProperty = objLiteral.getProperty('sharp');
422
+ if (sharpProperty) {
423
+ sharpProperty.remove();
424
+ modifications.push({
425
+ type: 'property-removed',
426
+ description: `Removed sharp property from config`
427
+ });
428
+ debug('[AST] ✓ Sharp property removed from config');
429
+ } else {
430
+ debug('[AST] Sharp property not found (already absent)');
431
+ }
432
+ return {
433
+ modifications,
434
+ modified: modifications.length > 0,
435
+ success: true
436
+ };
437
+ }
438
+ /** This shouldn't be necessary once the templates are updated. Can't hurt to keep in, though */ export function removeCommentMarkers(sourceFile) {
439
+ // Get the full text and replace comment markers
440
+ let text = sourceFile.getFullText();
441
+ // Remove inline comment markers from imports
442
+ text = text.replace(/\s*\/\/\s*database-adapter-import\s*$/gm, '');
443
+ text = text.replace(/\s*\/\/\s*storage-adapter-import-placeholder\s*$/gm, '');
444
+ // Remove standalone comment lines
445
+ text = text.replace(/^\s*\/\/\s*database-adapter-config-start\s*\n/gm, '');
446
+ text = text.replace(/^\s*\/\/\s*database-adapter-config-end\s*\n/gm, '');
447
+ text = text.replace(/^\s*\/\/\s*storage-adapter-placeholder\s*\n/gm, '');
448
+ // Also remove the placeholder line from template (storage-adapter-import-placeholder at top)
449
+ text = text.replace(/^\/\/\s*storage-adapter-import-placeholder\s*\n/gm, '');
450
+ // Replace the entire source file content
451
+ sourceFile.replaceWithText(text);
452
+ return sourceFile;
453
+ }
454
+ /**
455
+ * Validates payload config structure has required elements after transformation.
456
+ * Checks that buildConfig() call exists and has a db property configured.
457
+ */ export function validateStructure(sourceFile) {
458
+ debug('[AST] Validating payload config structure');
459
+ const detection = detectPayloadConfigStructure(sourceFile);
460
+ if (!detection.success) {
461
+ debug('[AST] ✗ Validation failed: detection unsuccessful');
462
+ return {
463
+ error: detection.error,
464
+ success: false
465
+ };
466
+ }
467
+ const { structures } = detection;
468
+ // Validate db property exists
469
+ if (!structures?.dbProperty) {
470
+ debug('[AST] ✗ Validation failed: db property missing');
471
+ return {
472
+ error: formatError({
473
+ actual: 'No db property found',
474
+ context: 'database configuration',
475
+ expected: 'buildConfig must have a db property',
476
+ technicalDetails: 'PropertyAssignment with name "db" not found in buildConfig object'
477
+ }),
478
+ success: false
479
+ };
480
+ }
481
+ debug('[AST] ✓ Validation passed');
482
+ return {
483
+ success: true
484
+ };
485
+ }
486
+ export async function writeTransformedFile(sourceFile, options = {}) {
487
+ const { formatWithPrettier = true, validateStructure: shouldValidate = true } = options;
488
+ debug(`[AST] Writing transformed file: ${sourceFile.getFilePath()}`);
489
+ // Validate if requested
490
+ if (shouldValidate) {
491
+ const validation = validateStructure(sourceFile);
492
+ if (!validation.success) {
493
+ return validation;
494
+ }
495
+ }
496
+ // Get file path and save to disk
497
+ const filePath = sourceFile.getFilePath();
498
+ // Format with ts-morph before saving (fixes trailing commas, indentation)
499
+ debug('[AST] Formatting with ts-morph');
500
+ sourceFile.formatText();
501
+ // Write file
502
+ debug('[AST] Writing file to disk');
503
+ await sourceFile.save();
504
+ // Format with prettier if requested
505
+ if (formatWithPrettier) {
506
+ debug('[AST] Running prettier formatting via CLI');
507
+ try {
508
+ // Detect project directory (go up from file until we find package.json or use dirname)
509
+ const projectDir = path.dirname(filePath);
510
+ // Run prettier via CLI (avoids Jest/ESM compatibility issues)
511
+ const prettierCmd = `npx prettier --write "${filePath}"`;
512
+ debug(`[AST] Executing: ${prettierCmd}`);
513
+ execSync(prettierCmd, {
514
+ cwd: projectDir,
515
+ stdio: 'pipe'
516
+ });
517
+ debug('[AST] ✓ Prettier formatting successful');
518
+ } catch (error) {
519
+ // Log but don't fail if prettier fails (might not be installed)
520
+ debug(`[AST] ⚠ Prettier formatting failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
521
+ debug('[AST] Continuing with unformatted output');
522
+ }
523
+ } else {
524
+ debug('[AST] Skipping prettier formatting (disabled)');
525
+ }
526
+ debug('[AST] ✓ File written successfully');
527
+ return {
528
+ success: true
529
+ };
530
+ }
531
+ export async function configurePayloadConfig(filePath, options = {}) {
532
+ debug(`[AST] Configuring payload config: ${filePath}`);
533
+ debug(`[AST] Options: db=${options.db?.type}, storage=${options.storage}, removeSharp=${options.removeSharp}`);
534
+ const allModifications = [];
535
+ const allWarnings = [];
536
+ try {
537
+ // Create Project and load source file with proper settings
538
+ const project = new Project({
539
+ manipulationSettings: {
540
+ quoteKind: QuoteKind.Single
541
+ }
542
+ });
543
+ let sourceFile = project.addSourceFileAtPath(filePath);
544
+ // Run detection
545
+ const detection = detectPayloadConfigStructure(sourceFile);
546
+ if (!detection.success) {
547
+ return detection;
548
+ }
549
+ // Apply transformations based on options
550
+ if (options.db) {
551
+ debug('[AST] Applying database adapter transformation');
552
+ const result = addDatabaseAdapter({
553
+ adapter: options.db.type,
554
+ envVarName: options.db.envVarName,
555
+ sourceFile
556
+ });
557
+ if (!result.success) {
558
+ return {
559
+ error: result.error,
560
+ success: false
561
+ };
562
+ }
563
+ allModifications.push(...result.modifications);
564
+ if (result.warnings) {
565
+ allWarnings.push(...result.warnings);
566
+ }
567
+ }
568
+ if (options.storage) {
569
+ debug('[AST] Applying storage adapter transformation');
570
+ const result = addStorageAdapter({
571
+ adapter: options.storage,
572
+ sourceFile
573
+ });
574
+ if (!result.success) {
575
+ return {
576
+ error: result.error,
577
+ success: false
578
+ };
579
+ }
580
+ allModifications.push(...result.modifications);
581
+ if (result.warnings) {
582
+ allWarnings.push(...result.warnings);
583
+ }
584
+ }
585
+ if (options.removeSharp) {
586
+ debug('[AST] Applying sharp removal');
587
+ const result = removeSharp(sourceFile);
588
+ if (!result.success) {
589
+ return {
590
+ error: result.error,
591
+ success: false
592
+ };
593
+ }
594
+ allModifications.push(...result.modifications);
595
+ if (result.warnings) {
596
+ allWarnings.push(...result.warnings);
597
+ }
598
+ }
599
+ // Remove comment markers from template
600
+ sourceFile = removeCommentMarkers(sourceFile);
601
+ // Cleanup orphaned imports after all transformations
602
+ debug('[AST] Cleaning up orphaned imports');
603
+ // Cleanup database adapter imports if db was removed
604
+ for (const [, config] of Object.entries(DB_ADAPTER_CONFIG)){
605
+ if (options.db && config.packageName !== DB_ADAPTER_CONFIG[options.db.type].packageName) {
606
+ const cleanup = cleanupOrphanedImports({
607
+ importNames: [
608
+ config.adapterName
609
+ ],
610
+ moduleSpecifier: config.packageName,
611
+ sourceFile
612
+ });
613
+ if (cleanup.removed.length > 0) {
614
+ cleanup.removed.forEach((importName)=>{
615
+ allModifications.push({
616
+ type: 'import-removed',
617
+ description: `Cleaned up unused import '${importName}' from '${config.packageName}'`
618
+ });
619
+ });
620
+ }
621
+ }
622
+ }
623
+ // Log summary of modifications
624
+ if (allModifications.length > 0) {
625
+ debug(`[AST] Applied ${allModifications.length} modification(s):`);
626
+ allModifications.forEach((mod)=>{
627
+ debug(`[AST] - ${mod.type}: ${mod.description}`);
628
+ });
629
+ }
630
+ if (allWarnings.length > 0) {
631
+ debug(`[AST] ${allWarnings.length} warning(s):`);
632
+ allWarnings.forEach((warning)=>{
633
+ debug(`[AST] - ${warning}`);
634
+ });
635
+ }
636
+ // Write transformed file with validation and formatting
637
+ return await writeTransformedFile(sourceFile, {
638
+ formatWithPrettier: options.formatWithPrettier,
639
+ validateStructure: options.validateStructure ?? true
640
+ });
641
+ } catch (error) {
642
+ debug(`[AST] ✗ Configuration failed: ${error instanceof Error ? error.message : String(error)}`);
643
+ return {
644
+ error: formatError({
645
+ actual: error instanceof Error ? error.message : String(error),
646
+ context: 'configurePayloadConfig',
647
+ expected: 'Successful file transformation',
648
+ technicalDetails: error instanceof Error ? error.stack || error.message : String(error)
649
+ }),
650
+ success: false
651
+ };
652
+ }
653
+ }
654
+
655
+ //# sourceMappingURL=payload-config.js.map