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.
- package/dist/ast-integration.spec.js +131 -0
- package/dist/ast-integration.spec.js.map +1 -0
- package/dist/lib/ast/adapter-config.d.ts +40 -0
- package/dist/lib/ast/adapter-config.d.ts.map +1 -0
- package/dist/lib/ast/adapter-config.js +145 -0
- package/dist/lib/ast/adapter-config.js.map +1 -0
- package/dist/lib/ast/package-json.d.ts +13 -0
- package/dist/lib/ast/package-json.d.ts.map +1 -0
- package/dist/lib/ast/package-json.js +103 -0
- package/dist/lib/ast/package-json.js.map +1 -0
- package/dist/lib/ast/package-json.spec.js +66 -0
- package/dist/lib/ast/package-json.spec.js.map +1 -0
- package/dist/lib/ast/payload-config.d.ts +23 -0
- package/dist/lib/ast/payload-config.d.ts.map +1 -0
- package/dist/lib/ast/payload-config.js +655 -0
- package/dist/lib/ast/payload-config.js.map +1 -0
- package/dist/lib/ast/payload-config.spec.js +364 -0
- package/dist/lib/ast/payload-config.spec.js.map +1 -0
- package/dist/lib/ast/types.d.ts +126 -0
- package/dist/lib/ast/types.d.ts.map +1 -0
- package/dist/lib/ast/types.js +18 -0
- package/dist/lib/ast/types.js.map +1 -0
- package/dist/lib/ast/utils.d.ts +48 -0
- package/dist/lib/ast/utils.d.ts.map +1 -0
- package/dist/lib/ast/utils.js +189 -0
- package/dist/lib/ast/utils.js.map +1 -0
- package/dist/lib/ast/utils.spec.js +225 -0
- package/dist/lib/ast/utils.spec.js.map +1 -0
- package/dist/lib/configure-payload-config.d.ts.map +1 -1
- package/dist/lib/configure-payload-config.js +30 -86
- package/dist/lib/configure-payload-config.js.map +1 -1
- package/dist/lib/create-project.spec.js +9 -5
- package/dist/lib/create-project.spec.js.map +1 -1
- package/dist/lib/init-next.d.ts.map +1 -1
- package/dist/lib/init-next.js +2 -1
- package/dist/lib/init-next.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +4 -0
- package/dist/main.js.map +1 -1
- package/dist/template/src/payload.config.ts +2 -7
- package/dist/types.d.ts +3 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/log.d.ts.map +1 -1
- package/dist/utils/log.js +3 -1
- package/dist/utils/log.js.map +1 -1
- package/package.json +4 -2
- package/dist/lib/replacements.d.ts +0 -27
- package/dist/lib/replacements.d.ts.map +0 -1
- package/dist/lib/replacements.js +0 -104
- 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
|