@adonisjs/assembler 8.0.0 → 8.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/chunk-DF48asd8.js +9 -0
- package/build/{codemod_exception-CzQgXAAf.js → codemod_exception-BMNJZ0i1.js} +143 -0
- package/build/index.js +934 -7
- package/build/main-Cpfvmdw6.js +562 -0
- package/build/main-INOi9swJ.js +471 -0
- package/build/src/code_scanners/routes_scanner/main.js +3 -171
- package/build/src/code_transformer/main.js +481 -2
- package/build/src/file_system.d.ts +1 -1
- package/build/src/helpers.js +133 -0
- package/build/src/index_generator/main.js +3 -28
- package/build/src/types/main.js +1 -0
- package/build/src/utils.d.ts +0 -2
- package/build/{virtual_file_system-bGeoWsK-.js → virtual_file_system-dzfXNwEp.js} +287 -0
- package/package.json +8 -8
- package/build/source-dVeugJ0e.js +0 -166
- package/build/validator_extractor-Ccio_Ndi.js +0 -82
|
@@ -1,18 +1,43 @@
|
|
|
1
|
-
import
|
|
1
|
+
import "../../chunk-DF48asd8.js";
|
|
2
|
+
import { t as CodemodException } from "../../codemod_exception-BMNJZ0i1.js";
|
|
2
3
|
import { fileURLToPath } from "node:url";
|
|
3
4
|
import { detectPackageManager, installPackage } from "@antfu/install-pkg";
|
|
4
5
|
import { ImportsBag } from "@poppinss/utils";
|
|
5
6
|
import { join } from "node:path";
|
|
6
7
|
import { Node, Project, QuoteKind, SyntaxKind } from "ts-morph";
|
|
8
|
+
//#region src/code_transformer/rc_file_transformer.ts
|
|
7
9
|
const ALLOWED_ENVIRONMENTS = [
|
|
8
10
|
"web",
|
|
9
11
|
"console",
|
|
10
12
|
"test",
|
|
11
13
|
"repl"
|
|
12
14
|
];
|
|
15
|
+
/**
|
|
16
|
+
* RcFileTransformer is used to transform the `adonisrc.ts` file
|
|
17
|
+
* for adding new commands, providers, meta files etc.
|
|
18
|
+
*
|
|
19
|
+
* This class provides a fluent API for modifying the AdonisJS configuration
|
|
20
|
+
* file (adonisrc.ts) by adding various types of entries like providers,
|
|
21
|
+
* commands, preloaded files, meta files, and test suites.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* const transformer = new RcFileTransformer(cwd, project)
|
|
25
|
+
* transformer.addProvider('#providers/app_provider')
|
|
26
|
+
* transformer.addCommand('#commands/make_controller')
|
|
27
|
+
* await transformer.save()
|
|
28
|
+
*/
|
|
13
29
|
var RcFileTransformer = class {
|
|
30
|
+
/**
|
|
31
|
+
* The current working directory URL
|
|
32
|
+
*/
|
|
14
33
|
#cwd;
|
|
34
|
+
/**
|
|
35
|
+
* The TsMorph project instance
|
|
36
|
+
*/
|
|
15
37
|
#project;
|
|
38
|
+
/**
|
|
39
|
+
* Settings to use when persisting files
|
|
40
|
+
*/
|
|
16
41
|
#editorSettings = {
|
|
17
42
|
indentSize: 2,
|
|
18
43
|
convertTabsToSpaces: true,
|
|
@@ -21,10 +46,22 @@ var RcFileTransformer = class {
|
|
|
21
46
|
indentStyle: 2,
|
|
22
47
|
semicolons: "remove"
|
|
23
48
|
};
|
|
49
|
+
/**
|
|
50
|
+
* Create a new RcFileTransformer instance
|
|
51
|
+
*
|
|
52
|
+
* @param cwd - The current working directory URL
|
|
53
|
+
* @param project - The TsMorph project instance
|
|
54
|
+
*/
|
|
24
55
|
constructor(cwd, project) {
|
|
25
56
|
this.#cwd = cwd;
|
|
26
57
|
this.#project = project;
|
|
27
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Get the `adonisrc.ts` source file
|
|
61
|
+
*
|
|
62
|
+
* @returns The adonisrc.ts source file
|
|
63
|
+
* @throws CodemodException if the file cannot be found
|
|
64
|
+
*/
|
|
28
65
|
#getRcFileOrThrow() {
|
|
29
66
|
const filePath = "adonisrc.ts";
|
|
30
67
|
const rcFileUrl = fileURLToPath(new URL(`./${filePath}`, this.#cwd));
|
|
@@ -36,10 +73,23 @@ export default defineConfig({
|
|
|
36
73
|
})`);
|
|
37
74
|
return file;
|
|
38
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Check if environments array has a subset of available environments
|
|
78
|
+
*
|
|
79
|
+
* @param environments - Optional array of environment names
|
|
80
|
+
* @returns True if the provided environments are a subset of available ones
|
|
81
|
+
*/
|
|
39
82
|
#isInSpecificEnvironment(environments) {
|
|
40
83
|
if (!environments) return false;
|
|
41
84
|
return !!ALLOWED_ENVIRONMENTS.find((env) => !environments.includes(env));
|
|
42
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* Locate the `defineConfig` call inside the `adonisrc.ts` file
|
|
88
|
+
*
|
|
89
|
+
* @param file - The source file to search in
|
|
90
|
+
* @returns The defineConfig call expression
|
|
91
|
+
* @throws CodemodException if defineConfig call cannot be found
|
|
92
|
+
*/
|
|
43
93
|
#locateDefineConfigCallOrThrow(file) {
|
|
44
94
|
const call = file.getDescendantsOfKind(SyntaxKind.CallExpression).find((statement) => statement.getExpression().getText() === "defineConfig");
|
|
45
95
|
if (!call) throw CodemodException.invalidRcFile("adonisrc.ts", `import { defineConfig } from '@adonisjs/core/app'
|
|
@@ -49,9 +99,24 @@ export default defineConfig({
|
|
|
49
99
|
})`, "Could not locate the defineConfig call.");
|
|
50
100
|
return call;
|
|
51
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Return the ObjectLiteralExpression of the defineConfig call
|
|
104
|
+
*
|
|
105
|
+
* @param defineConfigCall - The defineConfig call expression
|
|
106
|
+
* @returns The configuration object literal expression
|
|
107
|
+
* @throws Error if the object literal cannot be found
|
|
108
|
+
*/
|
|
52
109
|
#getDefineConfigObjectOrThrow(defineConfigCall) {
|
|
53
110
|
return defineConfigCall.getArguments()[0].asKindOrThrow(SyntaxKind.ObjectLiteralExpression);
|
|
54
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Check if the defineConfig() call has the property assignment
|
|
114
|
+
* inside it or not. If not, it will create one and return it.
|
|
115
|
+
*
|
|
116
|
+
* @param propertyName - The name of the property to find or create
|
|
117
|
+
* @param initializer - The initial value if the property needs to be created
|
|
118
|
+
* @returns The property assignment node
|
|
119
|
+
*/
|
|
55
120
|
#getPropertyAssignmentInDefineConfigCall(propertyName, initializer) {
|
|
56
121
|
const file = this.#getRcFileOrThrow();
|
|
57
122
|
const defineConfigCall = this.#locateDefineConfigCallOrThrow(file);
|
|
@@ -66,12 +131,35 @@ export default defineConfig({
|
|
|
66
131
|
}
|
|
67
132
|
return property;
|
|
68
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Extract list of imported modules from an ArrayLiteralExpression
|
|
136
|
+
*
|
|
137
|
+
* It assumes that the array can have two types of elements:
|
|
138
|
+
*
|
|
139
|
+
* - Simple lazy imported modules: [() => import('path/to/file')]
|
|
140
|
+
* - Or an object entry: [{ file: () => import('path/to/file'), environment: ['web', 'console'] }]
|
|
141
|
+
* where the `file` property is a lazy imported module.
|
|
142
|
+
*/
|
|
69
143
|
#extractModulesFromArray(array) {
|
|
70
144
|
return array.getElements().map((element) => {
|
|
145
|
+
/**
|
|
146
|
+
* Simple lazy imported module
|
|
147
|
+
*/
|
|
71
148
|
if (Node.isArrowFunction(element)) return element.getFirstDescendantByKindOrThrow(SyntaxKind.CallExpression).getFirstDescendantByKindOrThrow(SyntaxKind.StringLiteral).getLiteralValue();
|
|
149
|
+
/**
|
|
150
|
+
* Object entry
|
|
151
|
+
*/
|
|
72
152
|
if (Node.isObjectLiteralExpression(element)) return element.getPropertyOrThrow("file").getFirstDescendantByKindOrThrow(SyntaxKind.ArrowFunction).getFirstDescendantByKindOrThrow(SyntaxKind.CallExpression).getFirstDescendantByKindOrThrow(SyntaxKind.StringLiteral).getLiteralValue();
|
|
73
153
|
}).filter(Boolean);
|
|
74
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* Extract a specific property from an ArrayLiteralExpression
|
|
157
|
+
* that contains object entries.
|
|
158
|
+
*
|
|
159
|
+
* This function is mainly used for extractring the `pattern` property
|
|
160
|
+
* when adding a new meta files entry, or the `name` property when
|
|
161
|
+
* adding a new test suite.
|
|
162
|
+
*/
|
|
75
163
|
#extractPropertyFromArray(array, propertyName) {
|
|
76
164
|
return array.getElements().map((el) => {
|
|
77
165
|
if (!Node.isObjectLiteralExpression(el)) return;
|
|
@@ -80,6 +168,10 @@ export default defineConfig({
|
|
|
80
168
|
return nameProp.getInitializerIfKindOrThrow(SyntaxKind.StringLiteral).getLiteralValue();
|
|
81
169
|
}).filter(Boolean);
|
|
82
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Build a new module entry for the preloads and providers array
|
|
173
|
+
* based upon the environments specified
|
|
174
|
+
*/
|
|
83
175
|
#buildNewModuleEntry(modulePath, environments) {
|
|
84
176
|
if (!this.#isInSpecificEnvironment(environments)) return `() => import('${modulePath}')`;
|
|
85
177
|
return `{
|
|
@@ -87,34 +179,83 @@ export default defineConfig({
|
|
|
87
179
|
environment: [${environments?.map((env) => `'${env}'`).join(", ")}],
|
|
88
180
|
}`;
|
|
89
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* Add a new command to the rcFile
|
|
184
|
+
*
|
|
185
|
+
* @param commandPath - The path to the command file
|
|
186
|
+
* @returns This RcFileTransformer instance for method chaining
|
|
187
|
+
*/
|
|
90
188
|
addCommand(commandPath) {
|
|
91
189
|
const commandsArray = this.#getPropertyAssignmentInDefineConfigCall("commands", "[]").getInitializerIfKindOrThrow(SyntaxKind.ArrayLiteralExpression);
|
|
92
190
|
const commandString = `() => import('${commandPath}')`;
|
|
191
|
+
/**
|
|
192
|
+
* If the command already exists, do nothing
|
|
193
|
+
*/
|
|
93
194
|
if (commandsArray.getElements().some((el) => el.getText() === commandString)) return this;
|
|
195
|
+
/**
|
|
196
|
+
* Add the command to the array
|
|
197
|
+
*/
|
|
94
198
|
commandsArray.addElement(commandString);
|
|
95
199
|
return this;
|
|
96
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* Add a new preloaded file to the rcFile
|
|
203
|
+
*
|
|
204
|
+
* @param modulePath - The path to the preload file
|
|
205
|
+
* @param environments - Optional array of environments where this preload should run
|
|
206
|
+
* @returns This RcFileTransformer instance for method chaining
|
|
207
|
+
*/
|
|
97
208
|
addPreloadFile(modulePath, environments) {
|
|
98
209
|
const preloadsArray = this.#getPropertyAssignmentInDefineConfigCall("preloads", "[]").getInitializerIfKindOrThrow(SyntaxKind.ArrayLiteralExpression);
|
|
99
210
|
if (this.#extractModulesFromArray(preloadsArray).includes(modulePath)) return this;
|
|
211
|
+
/**
|
|
212
|
+
* Add the preloaded file to the array
|
|
213
|
+
*/
|
|
100
214
|
preloadsArray.addElement(this.#buildNewModuleEntry(modulePath, environments));
|
|
101
215
|
return this;
|
|
102
216
|
}
|
|
217
|
+
/**
|
|
218
|
+
* Add a new provider to the rcFile
|
|
219
|
+
*
|
|
220
|
+
* @param providerPath - The path to the provider file
|
|
221
|
+
* @param environments - Optional array of environments where this provider should run
|
|
222
|
+
* @returns This RcFileTransformer instance for method chaining
|
|
223
|
+
*/
|
|
103
224
|
addProvider(providerPath, environments) {
|
|
104
225
|
const providersArray = this.#getPropertyAssignmentInDefineConfigCall("providers", "[]").getInitializerIfKindOrThrow(SyntaxKind.ArrayLiteralExpression);
|
|
105
226
|
if (this.#extractModulesFromArray(providersArray).includes(providerPath)) return this;
|
|
227
|
+
/**
|
|
228
|
+
* Add the provider to the array
|
|
229
|
+
*/
|
|
106
230
|
providersArray.addElement(this.#buildNewModuleEntry(providerPath, environments));
|
|
107
231
|
return this;
|
|
108
232
|
}
|
|
233
|
+
/**
|
|
234
|
+
* Add a new meta file to the rcFile
|
|
235
|
+
*
|
|
236
|
+
* @param globPattern - The glob pattern for the meta file
|
|
237
|
+
* @param reloadServer - Whether the server should reload when this file changes
|
|
238
|
+
* @returns This RcFileTransformer instance for method chaining
|
|
239
|
+
*/
|
|
109
240
|
addMetaFile(globPattern, reloadServer = false) {
|
|
110
241
|
const metaFilesArray = this.#getPropertyAssignmentInDefineConfigCall("metaFiles", "[]").getInitializerIfKindOrThrow(SyntaxKind.ArrayLiteralExpression);
|
|
111
242
|
if (this.#extractPropertyFromArray(metaFilesArray, "pattern").includes(globPattern)) return this;
|
|
243
|
+
/**
|
|
244
|
+
* Add the meta file to the array
|
|
245
|
+
*/
|
|
112
246
|
metaFilesArray.addElement(`{
|
|
113
247
|
pattern: '${globPattern}',
|
|
114
248
|
reloadServer: ${reloadServer},
|
|
115
249
|
}`);
|
|
116
250
|
return this;
|
|
117
251
|
}
|
|
252
|
+
/**
|
|
253
|
+
* Set directory name and path in the directories configuration
|
|
254
|
+
*
|
|
255
|
+
* @param key - The directory key (e.g., 'controllers', 'models')
|
|
256
|
+
* @param value - The directory path
|
|
257
|
+
* @returns This RcFileTransformer instance for method chaining
|
|
258
|
+
*/
|
|
118
259
|
setDirectory(key, value) {
|
|
119
260
|
this.#getPropertyAssignmentInDefineConfigCall("directories", "{}").getInitializerIfKindOrThrow(SyntaxKind.ObjectLiteralExpression).addPropertyAssignment({
|
|
120
261
|
name: key,
|
|
@@ -122,6 +263,13 @@ export default defineConfig({
|
|
|
122
263
|
});
|
|
123
264
|
return this;
|
|
124
265
|
}
|
|
266
|
+
/**
|
|
267
|
+
* Set command alias in the command aliases configuration
|
|
268
|
+
*
|
|
269
|
+
* @param alias - The alias name
|
|
270
|
+
* @param command - The full command name
|
|
271
|
+
* @returns This RcFileTransformer instance for method chaining
|
|
272
|
+
*/
|
|
125
273
|
setCommandAlias(alias, command) {
|
|
126
274
|
this.#getPropertyAssignmentInDefineConfigCall("commandsAliases", "{}").getInitializerIfKindOrThrow(SyntaxKind.ObjectLiteralExpression).addPropertyAssignment({
|
|
127
275
|
name: alias,
|
|
@@ -129,9 +277,20 @@ export default defineConfig({
|
|
|
129
277
|
});
|
|
130
278
|
return this;
|
|
131
279
|
}
|
|
280
|
+
/**
|
|
281
|
+
* Add a new test suite to the rcFile
|
|
282
|
+
*
|
|
283
|
+
* @param suiteName - The name of the test suite
|
|
284
|
+
* @param files - File patterns for the test suite (string or array)
|
|
285
|
+
* @param timeout - Optional timeout in milliseconds (defaults to 2000)
|
|
286
|
+
* @returns This RcFileTransformer instance for method chaining
|
|
287
|
+
*/
|
|
132
288
|
addSuite(suiteName, files, timeout) {
|
|
133
289
|
const suitesArray = this.#getPropertyAssignmentInDefineConfigCall("tests", `{ suites: [], forceExit: true, timeout: 2000 }`).getInitializerIfKindOrThrow(SyntaxKind.ObjectLiteralExpression).getPropertyOrThrow("suites").getInitializerIfKindOrThrow(SyntaxKind.ArrayLiteralExpression);
|
|
134
290
|
if (this.#extractPropertyFromArray(suitesArray, "name").includes(suiteName)) return this;
|
|
291
|
+
/**
|
|
292
|
+
* Add the suite to the array
|
|
293
|
+
*/
|
|
135
294
|
const filesArray = Array.isArray(files) ? files : [files];
|
|
136
295
|
suitesArray.addElement(`{
|
|
137
296
|
name: '${suiteName}',
|
|
@@ -140,6 +299,15 @@ export default defineConfig({
|
|
|
140
299
|
}`);
|
|
141
300
|
return this;
|
|
142
301
|
}
|
|
302
|
+
/**
|
|
303
|
+
* Add a new assembler hook
|
|
304
|
+
* The format `thunk` write `() => import(path)`.
|
|
305
|
+
*
|
|
306
|
+
* @param type - The type of hook to add
|
|
307
|
+
* @param value - The path to the hook file or value to write
|
|
308
|
+
* @param raw - Wether to write a thunk import or as raw value
|
|
309
|
+
* @returns This RcFileTransformer instance for method chaining
|
|
310
|
+
*/
|
|
143
311
|
addAssemblerHook(type, value, raw = false) {
|
|
144
312
|
const hooks = this.#getPropertyAssignmentInDefineConfigCall("hooks", "{}").getInitializerIfKindOrThrow(SyntaxKind.ObjectLiteralExpression);
|
|
145
313
|
let hookArray = hooks.getProperty(type);
|
|
@@ -158,6 +326,13 @@ export default defineConfig({
|
|
|
158
326
|
}
|
|
159
327
|
return this;
|
|
160
328
|
}
|
|
329
|
+
/**
|
|
330
|
+
* Add a named import
|
|
331
|
+
*
|
|
332
|
+
* @param specifier - The module specifier to import
|
|
333
|
+
* @param names - Names to import from the module
|
|
334
|
+
* @returns This RcFileTransformer instance for method chaining
|
|
335
|
+
*/
|
|
161
336
|
addNamedImport(specifier, names) {
|
|
162
337
|
this.#getRcFileOrThrow().addImportDeclaration({
|
|
163
338
|
moduleSpecifier: specifier,
|
|
@@ -165,6 +340,13 @@ export default defineConfig({
|
|
|
165
340
|
});
|
|
166
341
|
return this;
|
|
167
342
|
}
|
|
343
|
+
/**
|
|
344
|
+
* Add a default import
|
|
345
|
+
*
|
|
346
|
+
* @param specifier - The module specifier to import
|
|
347
|
+
* @param name - Name of the default import
|
|
348
|
+
* @returns This RcFileTransformer instance for method chaining
|
|
349
|
+
*/
|
|
168
350
|
addDefaultImport(specifier, name) {
|
|
169
351
|
this.#getRcFileOrThrow().addImportDeclaration({
|
|
170
352
|
moduleSpecifier: specifier,
|
|
@@ -172,6 +354,13 @@ export default defineConfig({
|
|
|
172
354
|
});
|
|
173
355
|
return this;
|
|
174
356
|
}
|
|
357
|
+
/**
|
|
358
|
+
* Get a directory value from the directories configuration.
|
|
359
|
+
*
|
|
360
|
+
* @param key - The directory key to retrieve
|
|
361
|
+
* @param defaultValue - The default value if not configured
|
|
362
|
+
* @returns The configured directory path or the default value
|
|
363
|
+
*/
|
|
175
364
|
getDirectory(key, defaultValue) {
|
|
176
365
|
try {
|
|
177
366
|
const file = this.#getRcFileOrThrow();
|
|
@@ -189,18 +378,62 @@ export default defineConfig({
|
|
|
189
378
|
return defaultValue;
|
|
190
379
|
}
|
|
191
380
|
}
|
|
381
|
+
/**
|
|
382
|
+
* Save the adonisrc.ts file with all applied transformations
|
|
383
|
+
*
|
|
384
|
+
* Formats the file according to editor settings and saves it to disk.
|
|
385
|
+
*
|
|
386
|
+
* @returns Promise that resolves when the file is saved
|
|
387
|
+
*/
|
|
192
388
|
save() {
|
|
193
389
|
const file = this.#getRcFileOrThrow();
|
|
194
390
|
file.formatText(this.#editorSettings);
|
|
195
391
|
return file.save();
|
|
196
392
|
}
|
|
197
393
|
};
|
|
394
|
+
//#endregion
|
|
395
|
+
//#region src/code_transformer/main.ts
|
|
396
|
+
/**
|
|
397
|
+
* This class is responsible for transforming AdonisJS project code,
|
|
398
|
+
* including updating middleware, environment validations, and other
|
|
399
|
+
* code generation tasks.
|
|
400
|
+
*
|
|
401
|
+
* The CodeTransformer provides methods for modifying various AdonisJS
|
|
402
|
+
* configuration files and code structures using AST manipulation through
|
|
403
|
+
* ts-morph. It can update middleware stacks, add environment validations,
|
|
404
|
+
* register plugins, and modify RC file configurations.
|
|
405
|
+
*
|
|
406
|
+
* @example
|
|
407
|
+
* const transformer = new CodeTransformer(cwd)
|
|
408
|
+
* await transformer.addMiddlewareToStack('server', [{
|
|
409
|
+
* path: '#middleware/cors_middleware',
|
|
410
|
+
* position: 'before'
|
|
411
|
+
* }])
|
|
412
|
+
*/
|
|
198
413
|
var CodeTransformer = class {
|
|
414
|
+
/**
|
|
415
|
+
* Utility function for installing packages
|
|
416
|
+
*/
|
|
199
417
|
installPackage = installPackage;
|
|
418
|
+
/**
|
|
419
|
+
* Utility function for detecting the package manager
|
|
420
|
+
*/
|
|
200
421
|
detectPackageManager = detectPackageManager;
|
|
422
|
+
/**
|
|
423
|
+
* Directory of the adonisjs project
|
|
424
|
+
*/
|
|
201
425
|
#cwd;
|
|
426
|
+
/**
|
|
427
|
+
* String path version of the current working directory
|
|
428
|
+
*/
|
|
202
429
|
#cwdPath;
|
|
430
|
+
/**
|
|
431
|
+
* The TsMorph project instance for AST manipulation
|
|
432
|
+
*/
|
|
203
433
|
project;
|
|
434
|
+
/**
|
|
435
|
+
* Settings to use when persisting files
|
|
436
|
+
*/
|
|
204
437
|
#editorSettings = {
|
|
205
438
|
indentSize: 2,
|
|
206
439
|
convertTabsToSpaces: true,
|
|
@@ -209,6 +442,11 @@ var CodeTransformer = class {
|
|
|
209
442
|
indentStyle: 2,
|
|
210
443
|
semicolons: "remove"
|
|
211
444
|
};
|
|
445
|
+
/**
|
|
446
|
+
* Create a new CodeTransformer instance
|
|
447
|
+
*
|
|
448
|
+
* @param cwd - The current working directory URL
|
|
449
|
+
*/
|
|
212
450
|
constructor(cwd) {
|
|
213
451
|
this.#cwd = cwd;
|
|
214
452
|
this.#cwdPath = fileURLToPath(this.#cwd);
|
|
@@ -217,6 +455,14 @@ var CodeTransformer = class {
|
|
|
217
455
|
manipulationSettings: { quoteKind: QuoteKind.Single }
|
|
218
456
|
});
|
|
219
457
|
}
|
|
458
|
+
/**
|
|
459
|
+
* Get directories configured in adonisrc.ts, with defaults fallback.
|
|
460
|
+
*
|
|
461
|
+
* This method reads the adonisrc.ts file and extracts the directories
|
|
462
|
+
* configuration. If a directory is not configured, the default value is used.
|
|
463
|
+
*
|
|
464
|
+
* @returns Object containing directory paths
|
|
465
|
+
*/
|
|
220
466
|
getDirectories() {
|
|
221
467
|
const rcFileTransformer = new RcFileTransformer(this.#cwd, this.project);
|
|
222
468
|
return {
|
|
@@ -228,15 +474,38 @@ var CodeTransformer = class {
|
|
|
228
474
|
controllers: rcFileTransformer.getDirectory("controllers", "app/controllers")
|
|
229
475
|
};
|
|
230
476
|
}
|
|
477
|
+
/**
|
|
478
|
+
* Add a new middleware to the middleware array of the given file
|
|
479
|
+
*
|
|
480
|
+
* This method locates middleware stack calls (like server.use or router.use)
|
|
481
|
+
* and adds the middleware entry to the appropriate position in the array.
|
|
482
|
+
*
|
|
483
|
+
* @param file - The source file to modify
|
|
484
|
+
* @param target - The target method call (e.g., 'server.use', 'router.use')
|
|
485
|
+
* @param middlewareEntry - The middleware entry to add
|
|
486
|
+
*/
|
|
231
487
|
#addToMiddlewareArray(file, target, middlewareEntry) {
|
|
232
488
|
const callExpressions = file.getDescendantsOfKind(SyntaxKind.CallExpression).filter((statement) => statement.getExpression().getText() === target);
|
|
233
489
|
if (!callExpressions.length) throw new Error(`Cannot find ${target} statement in the file.`);
|
|
234
490
|
const arrayLiteralExpression = callExpressions[0].getArguments()[0];
|
|
235
491
|
if (!arrayLiteralExpression || !Node.isArrayLiteralExpression(arrayLiteralExpression)) throw new Error(`Cannot find middleware array in ${target} statement.`);
|
|
236
492
|
const middleware = `() => import('${middlewareEntry.path}')`;
|
|
237
|
-
if (arrayLiteralExpression.getElements().findIndex((element) => element.getText() === middleware) === -1)
|
|
493
|
+
if (arrayLiteralExpression.getElements().findIndex((element) => element.getText() === middleware) === -1)
|
|
494
|
+
/**
|
|
495
|
+
* Add the middleware to the top or bottom of the array
|
|
496
|
+
*/
|
|
497
|
+
if (middlewareEntry.position === "before") arrayLiteralExpression.insertElement(0, middleware);
|
|
238
498
|
else arrayLiteralExpression.addElement(middleware);
|
|
239
499
|
}
|
|
500
|
+
/**
|
|
501
|
+
* Add a new middleware to the named middleware of the given file
|
|
502
|
+
*
|
|
503
|
+
* This method adds middleware entries to the named middleware object,
|
|
504
|
+
* typically used for route-specific middleware registration.
|
|
505
|
+
*
|
|
506
|
+
* @param file - The source file to modify
|
|
507
|
+
* @param middlewareEntry - The middleware entry to add (must have a name)
|
|
508
|
+
*/
|
|
240
509
|
#addToNamedMiddleware(file, middlewareEntry) {
|
|
241
510
|
if (!middlewareEntry.name) throw new Error("Named middleware requires a name.");
|
|
242
511
|
const callArguments = file.getVariableDeclarationOrThrow("middleware").getInitializerIfKindOrThrow(SyntaxKind.CallExpression).getArguments();
|
|
@@ -244,10 +513,22 @@ var CodeTransformer = class {
|
|
|
244
513
|
const namedMiddlewareObject = callArguments[0];
|
|
245
514
|
if (!Node.isObjectLiteralExpression(namedMiddlewareObject)) throw new Error("The argument of the named middleware call is not an object literal.");
|
|
246
515
|
if (!namedMiddlewareObject.getProperty(middlewareEntry.name)) {
|
|
516
|
+
/**
|
|
517
|
+
* Add the named middleware
|
|
518
|
+
*/
|
|
247
519
|
const middleware = `${middlewareEntry.name}: () => import('${middlewareEntry.path}')`;
|
|
248
520
|
namedMiddlewareObject.insertProperty(0, middleware);
|
|
249
521
|
}
|
|
250
522
|
}
|
|
523
|
+
/**
|
|
524
|
+
* Add a policy to the list of pre-registered policies
|
|
525
|
+
*
|
|
526
|
+
* This method adds bouncer policy entries to the policies object,
|
|
527
|
+
* allowing them to be used in route authorization.
|
|
528
|
+
*
|
|
529
|
+
* @param file - The source file to modify
|
|
530
|
+
* @param policyEntry - The policy entry to add
|
|
531
|
+
*/
|
|
251
532
|
#addToPoliciesList(file, policyEntry) {
|
|
252
533
|
const policiesObject = file.getVariableDeclarationOrThrow("policies").getInitializerIfKindOrThrow(SyntaxKind.ObjectLiteralExpression);
|
|
253
534
|
if (!policiesObject.getProperty(policyEntry.name)) {
|
|
@@ -255,13 +536,26 @@ var CodeTransformer = class {
|
|
|
255
536
|
policiesObject.insertProperty(0, policy);
|
|
256
537
|
}
|
|
257
538
|
}
|
|
539
|
+
/**
|
|
540
|
+
* Add the given import declarations to the source file
|
|
541
|
+
* and merge named imports with the existing import
|
|
542
|
+
*/
|
|
258
543
|
#addImportDeclarations(file, importDeclarations) {
|
|
259
544
|
importDeclarations.forEach((importDeclaration) => {
|
|
260
545
|
const existingImport = file.getImportDeclarations().find((mod) => mod.getModuleSpecifierValue() === importDeclaration.module);
|
|
546
|
+
/**
|
|
547
|
+
* Add a new named import to existing import for the
|
|
548
|
+
* same module
|
|
549
|
+
*/
|
|
261
550
|
if (existingImport && importDeclaration.isNamed) {
|
|
262
551
|
if (!existingImport.getNamedImports().find((namedImport) => namedImport.getName() === importDeclaration.identifier)) existingImport.addNamedImport(importDeclaration.identifier);
|
|
263
552
|
return;
|
|
264
553
|
}
|
|
554
|
+
/**
|
|
555
|
+
* Ignore default import when the same module is already imported.
|
|
556
|
+
* The chances are the existing default import and the importDeclaration
|
|
557
|
+
* identifiers are not the same. But we should not modify existing source
|
|
558
|
+
*/
|
|
265
559
|
if (existingImport) return;
|
|
266
560
|
file.addImportDeclaration({
|
|
267
561
|
...importDeclaration.isNamed ? { namedImports: [importDeclaration.identifier] } : { defaultImport: importDeclaration.identifier },
|
|
@@ -269,6 +563,9 @@ var CodeTransformer = class {
|
|
|
269
563
|
});
|
|
270
564
|
});
|
|
271
565
|
}
|
|
566
|
+
/**
|
|
567
|
+
* Convert ImportInfo array to import declarations and add them to the file
|
|
568
|
+
*/
|
|
272
569
|
#addImportsFromImportInfo(file, imports) {
|
|
273
570
|
const importsBag = new ImportsBag();
|
|
274
571
|
for (const importInfo of imports) importsBag.add(importInfo);
|
|
@@ -287,23 +584,50 @@ var CodeTransformer = class {
|
|
|
287
584
|
});
|
|
288
585
|
this.#addImportDeclarations(file, importDeclarations);
|
|
289
586
|
}
|
|
587
|
+
/**
|
|
588
|
+
* Write a leading comment
|
|
589
|
+
*/
|
|
290
590
|
#addLeadingComment(writer, comment) {
|
|
291
591
|
if (!comment) return writer.blankLine();
|
|
292
592
|
return writer.blankLine().writeLine("/*").writeLine(`|----------------------------------------------------------`).writeLine(`| ${comment}`).writeLine(`|----------------------------------------------------------`).writeLine(`*/`);
|
|
293
593
|
}
|
|
594
|
+
/**
|
|
595
|
+
* Add new env variable validation in the `env.ts` file
|
|
596
|
+
*
|
|
597
|
+
* @param definition - Environment validation definition containing variables and comment
|
|
598
|
+
*/
|
|
294
599
|
async defineEnvValidations(definition) {
|
|
295
600
|
const filePath = `${this.getDirectories().start}/env.ts`;
|
|
601
|
+
/**
|
|
602
|
+
* Get the env.ts source file
|
|
603
|
+
*/
|
|
296
604
|
const envUrl = join(this.#cwdPath, `./${filePath}`);
|
|
297
605
|
const file = this.project.getSourceFile(envUrl);
|
|
298
606
|
if (!file) throw CodemodException.missingEnvFile(filePath, definition);
|
|
607
|
+
/**
|
|
608
|
+
* Get the `Env.create` call expression
|
|
609
|
+
*/
|
|
299
610
|
const callExpressions = file.getDescendantsOfKind(SyntaxKind.CallExpression).filter((statement) => statement.getExpression().getText() === "Env.create");
|
|
300
611
|
if (!callExpressions.length) throw CodemodException.missingEnvCreate(filePath, definition);
|
|
301
612
|
const objectLiteralExpression = callExpressions[0].getArguments()[1];
|
|
302
613
|
if (!Node.isObjectLiteralExpression(objectLiteralExpression)) throw CodemodException.invalidEnvCreate(filePath, definition);
|
|
303
614
|
let shouldAddComment = true;
|
|
615
|
+
/**
|
|
616
|
+
* Add each variable validation
|
|
617
|
+
*/
|
|
304
618
|
for (const [variable, validation] of Object.entries(definition.variables)) {
|
|
619
|
+
/**
|
|
620
|
+
* Check if the variable is already defined. If so, remove it
|
|
621
|
+
*/
|
|
305
622
|
const existingProperty = objectLiteralExpression.getProperty(variable);
|
|
623
|
+
/**
|
|
624
|
+
* Do not add leading comment if one or more properties
|
|
625
|
+
* already exists
|
|
626
|
+
*/
|
|
306
627
|
if (existingProperty) shouldAddComment = false;
|
|
628
|
+
/**
|
|
629
|
+
* Add property only when the property does not exist
|
|
630
|
+
*/
|
|
307
631
|
if (!existingProperty) objectLiteralExpression.addPropertyAssignment({
|
|
308
632
|
name: variable,
|
|
309
633
|
initializer: validation,
|
|
@@ -317,11 +641,27 @@ var CodeTransformer = class {
|
|
|
317
641
|
file.formatText(this.#editorSettings);
|
|
318
642
|
await file.save();
|
|
319
643
|
}
|
|
644
|
+
/**
|
|
645
|
+
* Define new middlewares inside the `start/kernel.ts` file
|
|
646
|
+
*
|
|
647
|
+
* This function is highly based on some assumptions
|
|
648
|
+
* and will not work if you significantly tweaked
|
|
649
|
+
* your `start/kernel.ts` file.
|
|
650
|
+
*
|
|
651
|
+
* @param stack - The middleware stack to add to ('server', 'router', or 'named')
|
|
652
|
+
* @param middleware - Array of middleware entries to add
|
|
653
|
+
*/
|
|
320
654
|
async addMiddlewareToStack(stack, middleware) {
|
|
321
655
|
const filePath = `${this.getDirectories().start}/kernel.ts`;
|
|
656
|
+
/**
|
|
657
|
+
* Get the kernel.ts source file
|
|
658
|
+
*/
|
|
322
659
|
const kernelUrl = join(this.#cwdPath, `./${filePath}`);
|
|
323
660
|
const file = this.project.getSourceFile(kernelUrl);
|
|
324
661
|
if (!file) throw CodemodException.missingKernelFile(filePath, stack, middleware);
|
|
662
|
+
/**
|
|
663
|
+
* Process each middleware entry
|
|
664
|
+
*/
|
|
325
665
|
try {
|
|
326
666
|
for (const middlewareEntry of middleware) if (stack === "named") this.#addToNamedMiddleware(file, middlewareEntry);
|
|
327
667
|
else this.#addToMiddlewareArray(file, `${stack}.use`, middlewareEntry);
|
|
@@ -332,35 +672,81 @@ var CodeTransformer = class {
|
|
|
332
672
|
file.formatText(this.#editorSettings);
|
|
333
673
|
await file.save();
|
|
334
674
|
}
|
|
675
|
+
/**
|
|
676
|
+
* Update the `adonisrc.ts` file using the provided callback
|
|
677
|
+
*
|
|
678
|
+
* @param callback - Function that receives the RcFileTransformer for modifications
|
|
679
|
+
*/
|
|
335
680
|
async updateRcFile(callback) {
|
|
336
681
|
const rcFileTransformer = new RcFileTransformer(this.#cwd, this.project);
|
|
337
682
|
callback(rcFileTransformer);
|
|
338
683
|
await rcFileTransformer.save();
|
|
339
684
|
}
|
|
685
|
+
/**
|
|
686
|
+
* Add a new Japa plugin in the `tests/bootstrap.ts` file
|
|
687
|
+
*
|
|
688
|
+
* @param pluginCall - The plugin function call to add
|
|
689
|
+
* @param importDeclarations - Import declarations needed for the plugin
|
|
690
|
+
*/
|
|
340
691
|
async addJapaPlugin(pluginCall, importDeclarations) {
|
|
341
692
|
const filePath = `${this.getDirectories().tests}/bootstrap.ts`;
|
|
693
|
+
/**
|
|
694
|
+
* Get the bootstrap.ts source file
|
|
695
|
+
*/
|
|
342
696
|
const testBootstrapUrl = join(this.#cwdPath, `./${filePath}`);
|
|
343
697
|
const file = this.project.getSourceFile(testBootstrapUrl);
|
|
344
698
|
if (!file) throw CodemodException.missingJapaBootstrap(filePath, pluginCall, importDeclarations);
|
|
699
|
+
/**
|
|
700
|
+
* Add the import declarations
|
|
701
|
+
*/
|
|
345
702
|
this.#addImportDeclarations(file, importDeclarations);
|
|
703
|
+
/**
|
|
704
|
+
* Insert the plugin call in the `plugins` array
|
|
705
|
+
*/
|
|
346
706
|
const pluginsArray = file.getVariableDeclaration("plugins")?.getInitializerIfKind(SyntaxKind.ArrayLiteralExpression);
|
|
707
|
+
/**
|
|
708
|
+
* Add plugin call to the plugins array
|
|
709
|
+
*/
|
|
347
710
|
if (pluginsArray) {
|
|
348
711
|
if (!pluginsArray.getElements().find((element) => element.getText() === pluginCall)) pluginsArray.addElement(pluginCall);
|
|
349
712
|
}
|
|
350
713
|
file.formatText(this.#editorSettings);
|
|
351
714
|
await file.save();
|
|
352
715
|
}
|
|
716
|
+
/**
|
|
717
|
+
* Add a new Vite plugin to the `vite.config.ts` file
|
|
718
|
+
*
|
|
719
|
+
* @param pluginCall - The plugin function call to add
|
|
720
|
+
* @param importDeclarations - Import declarations needed for the plugin
|
|
721
|
+
*/
|
|
353
722
|
async addVitePlugin(pluginCall, importDeclarations) {
|
|
354
723
|
const filePath = "vite.config.ts";
|
|
724
|
+
/**
|
|
725
|
+
* Get the `vite.config.ts` source file
|
|
726
|
+
*/
|
|
355
727
|
const viteConfigTsUrl = join(this.#cwdPath, `./${filePath}`);
|
|
356
728
|
const file = this.project.getSourceFile(viteConfigTsUrl);
|
|
357
729
|
if (!file) throw CodemodException.missingViteConfig(filePath, pluginCall, importDeclarations);
|
|
358
730
|
try {
|
|
731
|
+
/**
|
|
732
|
+
* Add the import declarations
|
|
733
|
+
*/
|
|
359
734
|
this.#addImportDeclarations(file, importDeclarations);
|
|
735
|
+
/**
|
|
736
|
+
* Get the default export options
|
|
737
|
+
*/
|
|
360
738
|
const defaultExport = file.getDefaultExportSymbol();
|
|
361
739
|
if (!defaultExport) throw new Error("Cannot find the default export in vite.config.ts");
|
|
740
|
+
/**
|
|
741
|
+
* Get the options object
|
|
742
|
+
* - Either the first argument of `defineConfig` call : `export default defineConfig({})`
|
|
743
|
+
* - Or child literal expression of the default export : `export default {}`
|
|
744
|
+
*/
|
|
362
745
|
const declaration = defaultExport.getDeclarations()[0];
|
|
363
746
|
const pluginsArray = (declaration.getChildrenOfKind(SyntaxKind.ObjectLiteralExpression)[0] || declaration.getChildrenOfKind(SyntaxKind.CallExpression)[0].getArguments()[0]).getPropertyOrThrow("plugins").getFirstChildByKindOrThrow(SyntaxKind.ArrayLiteralExpression);
|
|
747
|
+
/**
|
|
748
|
+
* Add plugin call to the plugins array
|
|
749
|
+
*/
|
|
364
750
|
if (!pluginsArray.getElements().find((element) => element.getText() === pluginCall)) pluginsArray.addElement(pluginCall);
|
|
365
751
|
} catch (error) {
|
|
366
752
|
if (error instanceof CodemodException) throw error;
|
|
@@ -370,11 +756,23 @@ var CodeTransformer = class {
|
|
|
370
756
|
file.formatText(this.#editorSettings);
|
|
371
757
|
await file.save();
|
|
372
758
|
}
|
|
759
|
+
/**
|
|
760
|
+
* Adds a policy to the list of `policies` object configured
|
|
761
|
+
* inside the `app/policies/main.ts` file.
|
|
762
|
+
*
|
|
763
|
+
* @param policies - Array of bouncer policy entries to add
|
|
764
|
+
*/
|
|
373
765
|
async addPolicies(policies) {
|
|
374
766
|
const filePath = `${this.getDirectories().policies}/main.ts`;
|
|
767
|
+
/**
|
|
768
|
+
* Get the policies/main.ts source file
|
|
769
|
+
*/
|
|
375
770
|
const policiesUrl = join(this.#cwdPath, `./${filePath}`);
|
|
376
771
|
const file = this.project.getSourceFile(policiesUrl);
|
|
377
772
|
if (!file) throw CodemodException.missingPoliciesFile(filePath, policies);
|
|
773
|
+
/**
|
|
774
|
+
* Process each policy entry
|
|
775
|
+
*/
|
|
378
776
|
try {
|
|
379
777
|
for (const policy of policies) this.#addToPoliciesList(file, policy);
|
|
380
778
|
} catch (error) {
|
|
@@ -386,11 +784,20 @@ var CodeTransformer = class {
|
|
|
386
784
|
}
|
|
387
785
|
async addValidator(definition) {
|
|
388
786
|
const filePath = `${this.getDirectories().validators}/${definition.validatorFileName}`;
|
|
787
|
+
/**
|
|
788
|
+
* Get the validator file URL
|
|
789
|
+
*/
|
|
389
790
|
const validatorFileUrl = join(this.#cwdPath, `./${filePath}`);
|
|
390
791
|
let file = this.project.getSourceFile(validatorFileUrl);
|
|
792
|
+
/**
|
|
793
|
+
* Try to load the file from disk if not already in the project
|
|
794
|
+
*/
|
|
391
795
|
if (!file) try {
|
|
392
796
|
file = this.project.addSourceFileAtPath(validatorFileUrl);
|
|
393
797
|
} catch {}
|
|
798
|
+
/**
|
|
799
|
+
* If the file does not exist, create it
|
|
800
|
+
*/
|
|
394
801
|
if (!file) {
|
|
395
802
|
file = this.project.createSourceFile(validatorFileUrl, definition.contents);
|
|
396
803
|
file.formatText(this.#editorSettings);
|
|
@@ -398,17 +805,29 @@ var CodeTransformer = class {
|
|
|
398
805
|
return;
|
|
399
806
|
}
|
|
400
807
|
if (file.getVariableDeclaration(definition.exportName)) return;
|
|
808
|
+
/**
|
|
809
|
+
* Add the validator to the existing file
|
|
810
|
+
*/
|
|
401
811
|
file.addStatements(`\n${definition.contents}`);
|
|
402
812
|
file.formatText(this.#editorSettings);
|
|
403
813
|
await file.save();
|
|
404
814
|
}
|
|
405
815
|
async addLimiter(definition) {
|
|
406
816
|
const filePath = `${this.getDirectories().start}/${definition.limiterFileName}`;
|
|
817
|
+
/**
|
|
818
|
+
* Get the limiter file URL
|
|
819
|
+
*/
|
|
407
820
|
const limiterFileUrl = join(this.#cwdPath, `./${filePath}`);
|
|
408
821
|
let file = this.project.getSourceFile(limiterFileUrl);
|
|
822
|
+
/**
|
|
823
|
+
* Try to load the file from disk if not already in the project
|
|
824
|
+
*/
|
|
409
825
|
if (!file) try {
|
|
410
826
|
file = this.project.addSourceFileAtPath(limiterFileUrl);
|
|
411
827
|
} catch {}
|
|
828
|
+
/**
|
|
829
|
+
* If the file does not exist, create it
|
|
830
|
+
*/
|
|
412
831
|
if (!file) {
|
|
413
832
|
file = this.project.createSourceFile(limiterFileUrl, definition.contents);
|
|
414
833
|
file.formatText(this.#editorSettings);
|
|
@@ -416,25 +835,40 @@ var CodeTransformer = class {
|
|
|
416
835
|
return;
|
|
417
836
|
}
|
|
418
837
|
if (file.getVariableDeclaration(definition.exportName)) return;
|
|
838
|
+
/**
|
|
839
|
+
* Add the limiter to the existing file
|
|
840
|
+
*/
|
|
419
841
|
file.addStatements(`\n${definition.contents}`);
|
|
420
842
|
file.formatText(this.#editorSettings);
|
|
421
843
|
await file.save();
|
|
422
844
|
}
|
|
423
845
|
async addModelMixins(modelFileName, mixins) {
|
|
424
846
|
const filePath = `${this.getDirectories().models}/${modelFileName}`;
|
|
847
|
+
/**
|
|
848
|
+
* Get the model file URL
|
|
849
|
+
*/
|
|
425
850
|
const modelFileUrl = join(this.#cwdPath, `./${filePath}`);
|
|
426
851
|
let file = this.project.getSourceFile(modelFileUrl);
|
|
852
|
+
/**
|
|
853
|
+
* Try to load the file from disk if not already in the project
|
|
854
|
+
*/
|
|
427
855
|
if (!file) try {
|
|
428
856
|
file = this.project.addSourceFileAtPath(modelFileUrl);
|
|
429
857
|
} catch {
|
|
430
858
|
throw new Error(`Could not find source file at path: "${filePath}"`);
|
|
431
859
|
}
|
|
860
|
+
/**
|
|
861
|
+
* Get the default export class declaration
|
|
862
|
+
*/
|
|
432
863
|
const defaultExportSymbol = file.getDefaultExportSymbol();
|
|
433
864
|
if (!defaultExportSymbol) throw new Error(`Could not find default export in "${filePath}". The model must have a default export class.`);
|
|
434
865
|
const declarations = defaultExportSymbol.getDeclarations();
|
|
435
866
|
if (declarations.length === 0) throw new Error(`Could not find default export declaration in "${filePath}".`);
|
|
436
867
|
const declaration = declarations[0];
|
|
437
868
|
if (!Node.isClassDeclaration(declaration)) throw new Error(`Default export in "${filePath}" is not a class. The model must be exported as a class.`);
|
|
869
|
+
/**
|
|
870
|
+
* Add import declarations for the mixins
|
|
871
|
+
*/
|
|
438
872
|
const mixinImports = mixins.map((mixin) => {
|
|
439
873
|
if (mixin.importType === "named") return {
|
|
440
874
|
source: mixin.importPath,
|
|
@@ -446,21 +880,40 @@ var CodeTransformer = class {
|
|
|
446
880
|
};
|
|
447
881
|
});
|
|
448
882
|
this.#addImportsFromImportInfo(file, mixinImports);
|
|
883
|
+
/**
|
|
884
|
+
* Get the heritage clause (extends clause)
|
|
885
|
+
*/
|
|
449
886
|
const heritageClause = declaration.getHeritageClauseByKind(SyntaxKind.ExtendsKeyword);
|
|
450
887
|
if (!heritageClause) throw new Error(`Could not find extends clause in "${filePath}".`);
|
|
451
888
|
const extendsExpression = heritageClause.getTypeNodes()[0];
|
|
452
889
|
if (!extendsExpression) throw new Error(`Could not find extends expression in "${filePath}".`);
|
|
890
|
+
/**
|
|
891
|
+
* Get the expression that the class extends
|
|
892
|
+
*/
|
|
453
893
|
const extendsExpressionNode = extendsExpression.getExpression();
|
|
894
|
+
/**
|
|
895
|
+
* Check if the class already uses compose
|
|
896
|
+
*/
|
|
454
897
|
let composeCall;
|
|
455
898
|
if (Node.isCallExpression(extendsExpressionNode)) {
|
|
456
899
|
if (extendsExpressionNode.getExpression().getText() === "compose") composeCall = extendsExpressionNode;
|
|
457
900
|
}
|
|
901
|
+
/**
|
|
902
|
+
* Build the mixin calls
|
|
903
|
+
*/
|
|
458
904
|
const mixinCalls = mixins.map((mixin) => {
|
|
459
905
|
const args = mixin.args && mixin.args.length > 0 ? mixin.args.join(", ") : "";
|
|
460
906
|
return `${mixin.name}(${args})`;
|
|
461
907
|
});
|
|
908
|
+
/**
|
|
909
|
+
* If the class is already using compose, add the mixins to the compose call
|
|
910
|
+
*/
|
|
462
911
|
if (composeCall && Node.isCallExpression(composeCall)) {
|
|
463
912
|
const existingArgsText = composeCall.getArguments().map((arg) => arg.getText());
|
|
913
|
+
/**
|
|
914
|
+
* Filter out mixins that are already applied by checking if a call
|
|
915
|
+
* to the mixin function already exists in the compose arguments
|
|
916
|
+
*/
|
|
464
917
|
const newMixinCalls = mixinCalls.filter((mixinCall) => {
|
|
465
918
|
const mixinFunctionName = mixinCall.split("(")[0];
|
|
466
919
|
return !existingArgsText.some((existingArg) => {
|
|
@@ -470,6 +923,10 @@ var CodeTransformer = class {
|
|
|
470
923
|
const newArgs = [...existingArgsText, ...newMixinCalls];
|
|
471
924
|
composeCall.replaceWithText(`compose(${newArgs.join(", ")})`);
|
|
472
925
|
} else {
|
|
926
|
+
/**
|
|
927
|
+
* If the class is not using compose, wrap the extends expression in compose
|
|
928
|
+
* and add import for compose
|
|
929
|
+
*/
|
|
473
930
|
this.#addImportDeclarations(file, [{
|
|
474
931
|
isNamed: true,
|
|
475
932
|
module: "@adonisjs/core/helpers",
|
|
@@ -483,21 +940,36 @@ var CodeTransformer = class {
|
|
|
483
940
|
}
|
|
484
941
|
async addControllerMethod(definition) {
|
|
485
942
|
const filePath = `${this.getDirectories().controllers}/${definition.controllerFileName}`;
|
|
943
|
+
/**
|
|
944
|
+
* Get the controller file URL
|
|
945
|
+
*/
|
|
486
946
|
const controllerFileUrl = join(this.#cwdPath, `./${filePath}`);
|
|
487
947
|
let file = this.project.getSourceFile(controllerFileUrl);
|
|
948
|
+
/**
|
|
949
|
+
* Try to load the file from disk if not already in the project
|
|
950
|
+
*/
|
|
488
951
|
if (!file) try {
|
|
489
952
|
file = this.project.addSourceFileAtPath(controllerFileUrl);
|
|
490
953
|
} catch {}
|
|
954
|
+
/**
|
|
955
|
+
* If the file does not exist, create it with the controller class and method
|
|
956
|
+
*/
|
|
491
957
|
if (!file) {
|
|
492
958
|
const contents = `export default class ${definition.className} {
|
|
493
959
|
${definition.contents}
|
|
494
960
|
}`;
|
|
495
961
|
file = this.project.createSourceFile(controllerFileUrl, contents);
|
|
962
|
+
/**
|
|
963
|
+
* Add imports if specified
|
|
964
|
+
*/
|
|
496
965
|
if (definition.imports) this.#addImportsFromImportInfo(file, definition.imports);
|
|
497
966
|
file.formatText(this.#editorSettings);
|
|
498
967
|
await file.save();
|
|
499
968
|
return;
|
|
500
969
|
}
|
|
970
|
+
/**
|
|
971
|
+
* Get the default export class declaration
|
|
972
|
+
*/
|
|
501
973
|
const defaultExportSymbol = file.getDefaultExportSymbol();
|
|
502
974
|
if (!defaultExportSymbol) throw new Error(`Could not find default export in "${filePath}". The controller must have a default export class.`);
|
|
503
975
|
const declarations = defaultExportSymbol.getDeclarations();
|
|
@@ -505,10 +977,17 @@ var CodeTransformer = class {
|
|
|
505
977
|
const declaration = declarations[0];
|
|
506
978
|
if (!Node.isClassDeclaration(declaration)) throw new Error(`Default export in "${filePath}" is not a class. The controller must be exported as a class.`);
|
|
507
979
|
if (declaration.getMethod(definition.name)) return;
|
|
980
|
+
/**
|
|
981
|
+
* Add imports if specified
|
|
982
|
+
*/
|
|
508
983
|
if (definition.imports) this.#addImportsFromImportInfo(file, definition.imports);
|
|
984
|
+
/**
|
|
985
|
+
* Add the method to the class by inserting the raw method text
|
|
986
|
+
*/
|
|
509
987
|
declaration.addMember(definition.contents);
|
|
510
988
|
file.formatText(this.#editorSettings);
|
|
511
989
|
await file.save();
|
|
512
990
|
}
|
|
513
991
|
};
|
|
992
|
+
//#endregion
|
|
514
993
|
export { CodeTransformer };
|