@adonisjs/assembler 8.0.0 → 8.1.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/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-DEUzrVBq.js +565 -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 +9 -9
- package/build/source-dVeugJ0e.js +0 -166
- package/build/validator_extractor-Ccio_Ndi.js +0 -82
|
@@ -37,7 +37,7 @@ export declare class FileSystem {
|
|
|
37
37
|
*
|
|
38
38
|
* Following patterns are always ignored
|
|
39
39
|
*
|
|
40
|
-
* '.git/**', 'coverage/**', '.github/**'
|
|
40
|
+
* '.git/**', 'coverage/**', '.github/**', '.adonisjs/**', 'tmp/**', 'storage/**', 'build/**'
|
|
41
41
|
*/
|
|
42
42
|
get excludes(): string[];
|
|
43
43
|
/**
|
package/build/src/helpers.js
CHANGED
|
@@ -1,9 +1,37 @@
|
|
|
1
|
+
import "../chunk-DF48asd8.js";
|
|
1
2
|
import { parseImports } from "parse-imports";
|
|
3
|
+
//#region src/helpers.ts
|
|
4
|
+
/**
|
|
5
|
+
* Finds an import reference inside a code snippet by analyzing the import statements
|
|
6
|
+
* and matching against the provided import reference identifier.
|
|
7
|
+
*
|
|
8
|
+
* The function searches through all import statements in the code and matches
|
|
9
|
+
* against default, namespace, or named imports based on the import reference.
|
|
10
|
+
*
|
|
11
|
+
* @param code - The code snippet to search for imports
|
|
12
|
+
* @param importReference - The import reference identifier to find (can include dot notation)
|
|
13
|
+
* @returns Promise that resolves to an Import object or null if not found
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* const importInfo = await findImport(code, 'validateUser')
|
|
17
|
+
* if (importInfo) {
|
|
18
|
+
* console.log(importInfo.specifier) // '@/validators/user'
|
|
19
|
+
* }
|
|
20
|
+
*/
|
|
2
21
|
async function findImport(code, importReference) {
|
|
3
22
|
const importIdentifier = importReference.split(".")[0];
|
|
4
23
|
for (const $import of await parseImports(code, {})) {
|
|
24
|
+
/**
|
|
25
|
+
* An import without any clause
|
|
26
|
+
*/
|
|
5
27
|
if (!$import.importClause) continue;
|
|
28
|
+
/**
|
|
29
|
+
* An import without any clause
|
|
30
|
+
*/
|
|
6
31
|
if (!$import.moduleSpecifier.value) continue;
|
|
32
|
+
/**
|
|
33
|
+
* Import identifier matches a default import
|
|
34
|
+
*/
|
|
7
35
|
if ($import.importClause.default === importIdentifier) return {
|
|
8
36
|
specifier: $import.moduleSpecifier.value,
|
|
9
37
|
isConstant: $import.moduleSpecifier.isConstant,
|
|
@@ -12,6 +40,9 @@ async function findImport(code, importReference) {
|
|
|
12
40
|
value: importIdentifier
|
|
13
41
|
}
|
|
14
42
|
};
|
|
43
|
+
/**
|
|
44
|
+
* Import identifier matches a namespace import
|
|
45
|
+
*/
|
|
15
46
|
if ($import.importClause.namespace === importIdentifier) return {
|
|
16
47
|
specifier: $import.moduleSpecifier.value,
|
|
17
48
|
isConstant: $import.moduleSpecifier.isConstant,
|
|
@@ -23,6 +54,9 @@ async function findImport(code, importReference) {
|
|
|
23
54
|
const namedImport = $import.importClause.named.find(({ binding }) => {
|
|
24
55
|
return binding === importIdentifier;
|
|
25
56
|
});
|
|
57
|
+
/**
|
|
58
|
+
* Import identifier matches a named import
|
|
59
|
+
*/
|
|
26
60
|
if (namedImport) return {
|
|
27
61
|
specifier: $import.moduleSpecifier.value,
|
|
28
62
|
isConstant: $import.moduleSpecifier.isConstant,
|
|
@@ -35,12 +69,64 @@ async function findImport(code, importReference) {
|
|
|
35
69
|
}
|
|
36
70
|
return null;
|
|
37
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Returns a node that represents a TypeScript class or null
|
|
74
|
+
* when unable to find the class.
|
|
75
|
+
*
|
|
76
|
+
* This function searches for class declarations within the provided AST node
|
|
77
|
+
* using ast-grep's pattern matching capabilities.
|
|
78
|
+
*
|
|
79
|
+
* @param node - The AST node to search within for class declarations
|
|
80
|
+
* @returns The SgNode representing the class or null if not found
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* const classNode = inspectClass(rootNode)
|
|
84
|
+
* if (classNode) {
|
|
85
|
+
* console.log('Found class:', classNode.text())
|
|
86
|
+
* }
|
|
87
|
+
*/
|
|
38
88
|
function inspectClass(node) {
|
|
39
89
|
return node.find({ rule: { kind: "class_declaration" } });
|
|
40
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Returns an array of SgNodes for class methods. The input node
|
|
93
|
+
* must represent a class.
|
|
94
|
+
*
|
|
95
|
+
* This function finds all method definitions within a class node,
|
|
96
|
+
* including both regular methods and static methods.
|
|
97
|
+
*
|
|
98
|
+
* @param node - The AST node representing a class to search for methods
|
|
99
|
+
* @returns Array of SgNodes representing method definitions within the class
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* const classNode = inspectClass(rootNode)
|
|
103
|
+
* if (classNode) {
|
|
104
|
+
* const methods = inspectClassMethods(classNode)
|
|
105
|
+
* methods.forEach(method => console.log('Method:', method.text()))
|
|
106
|
+
* }
|
|
107
|
+
*/
|
|
41
108
|
function inspectClassMethods(node) {
|
|
42
109
|
return node.findAll({ rule: { kind: "method_definition" } });
|
|
43
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Converts an SgNode to plain text by removing whitespaces,
|
|
113
|
+
* indentation and comments in between. Tested with the following
|
|
114
|
+
* children nodes only.
|
|
115
|
+
*
|
|
116
|
+
* - MemberExpression
|
|
117
|
+
* - Identifier
|
|
118
|
+
*
|
|
119
|
+
* This function recursively traverses the AST node and extracts only
|
|
120
|
+
* the meaningful text content without any formatting or whitespace.
|
|
121
|
+
*
|
|
122
|
+
* @param node - The AST node to convert to plain text
|
|
123
|
+
* @returns String representation of the node without formatting
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* const memberExpression = node.find({ rule: { kind: 'member_expression' } })
|
|
127
|
+
* const plainText = nodeToPlainText(memberExpression)
|
|
128
|
+
* console.log(plainText) // 'user.validate'
|
|
129
|
+
*/
|
|
44
130
|
function nodeToPlainText(node) {
|
|
45
131
|
let out = [];
|
|
46
132
|
function toText(one) {
|
|
@@ -51,6 +137,26 @@ function nodeToPlainText(node) {
|
|
|
51
137
|
toText(node);
|
|
52
138
|
return out.join("");
|
|
53
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Inspects arguments for one or more method calls. If you want to
|
|
142
|
+
* scope the search within a specific context, then make sure to
|
|
143
|
+
* first narrow down the AST and pass a specific SgNode.
|
|
144
|
+
*
|
|
145
|
+
* For example: In case of validators, we will first find the Controller
|
|
146
|
+
* method for which we want the validation method calls.
|
|
147
|
+
*
|
|
148
|
+
* This function searches for call expressions that match the provided method
|
|
149
|
+
* names and returns their argument nodes for further analysis.
|
|
150
|
+
*
|
|
151
|
+
* @param node - The AST node to search within for method calls
|
|
152
|
+
* @param methodCalls - Array of method call names to search for (supports dot notation)
|
|
153
|
+
* @returns Array of SgNodes representing the arguments of matching method calls
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* const controllerMethod = classNode.find({ rule: { kind: 'method_definition' } })
|
|
157
|
+
* const validatorArgs = inspectMethodArguments(controllerMethod, ['validate', 'request.validate'])
|
|
158
|
+
* validatorArgs.forEach(arg => console.log('Validator argument:', arg.text()))
|
|
159
|
+
*/
|
|
54
160
|
function inspectMethodArguments(node, methodCalls) {
|
|
55
161
|
return node.findAll({ rule: { any: methodCalls.map((methodCall) => {
|
|
56
162
|
return { pattern: {
|
|
@@ -61,12 +167,39 @@ function inspectMethodArguments(node, methodCalls) {
|
|
|
61
167
|
return matchingExpression.findAll({ rule: { kind: "arguments" } });
|
|
62
168
|
});
|
|
63
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* Inspect the validator direct usage code snippets. A member expression
|
|
172
|
+
* calling the ".validate" method is considered as direct usage of
|
|
173
|
+
* the validator.
|
|
174
|
+
*
|
|
175
|
+
* This function specifically looks for patterns where validators are called
|
|
176
|
+
* directly with the `.validate()` method, which is common in AdonisJS applications.
|
|
177
|
+
*
|
|
178
|
+
* @param node - The AST node to search within for validator direct usage
|
|
179
|
+
* @returns Array of SgNodes representing validator expressions that call validate method
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* const methodNode = classNode.find({ rule: { kind: 'method_definition' } })
|
|
183
|
+
* const validators = searchValidatorDirectUsage(methodNode)
|
|
184
|
+
* validators.forEach(validator => {
|
|
185
|
+
* console.log('Direct validator usage:', nodeToPlainText(validator))
|
|
186
|
+
* })
|
|
187
|
+
*/
|
|
64
188
|
function searchValidatorDirectUsage(node) {
|
|
65
189
|
return node.findAll({ rule: { pattern: {
|
|
66
190
|
context: "$$$VALIDATOR.validate($$$)",
|
|
67
191
|
selector: "call_expression"
|
|
68
192
|
} } }).flatMap((expression) => {
|
|
193
|
+
/**
|
|
194
|
+
* Since we capture all "$$$.validate" calls, the first node
|
|
195
|
+
* within the call expression will be a member expression
|
|
196
|
+
* and its property identifier will be "validate".
|
|
197
|
+
*
|
|
198
|
+
* What we need is the text of this member expression without the
|
|
199
|
+
* comments
|
|
200
|
+
*/
|
|
69
201
|
return expression.getMultipleMatches("VALIDATOR");
|
|
70
202
|
});
|
|
71
203
|
}
|
|
204
|
+
//#endregion
|
|
72
205
|
export { findImport, inspectClass, inspectClassMethods, inspectMethodArguments, nodeToPlainText, searchValidatorDirectUsage };
|
|
@@ -1,29 +1,4 @@
|
|
|
1
|
-
import "../../
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
appRoot;
|
|
5
|
-
#sources = {};
|
|
6
|
-
#cliLogger;
|
|
7
|
-
constructor(appRoot, cliLogger) {
|
|
8
|
-
this.appRoot = appRoot;
|
|
9
|
-
this.#cliLogger = cliLogger;
|
|
10
|
-
}
|
|
11
|
-
add(name, config) {
|
|
12
|
-
this.#sources[name] = new IndexGeneratorSource(name, this.appRoot, this.#cliLogger, config);
|
|
13
|
-
return this;
|
|
14
|
-
}
|
|
15
|
-
async addFile(filePath) {
|
|
16
|
-
const sources = Object.values(this.#sources);
|
|
17
|
-
for (let source of sources) await source.addFile(filePath);
|
|
18
|
-
}
|
|
19
|
-
async removeFile(filePath) {
|
|
20
|
-
const sources = Object.values(this.#sources);
|
|
21
|
-
for (let source of sources) await source.removeFile(filePath);
|
|
22
|
-
}
|
|
23
|
-
async generate() {
|
|
24
|
-
const sources = Object.values(this.#sources);
|
|
25
|
-
for (let source of sources) await source.generate();
|
|
26
|
-
if (sources.length) this.#cliLogger.info(`codegen: created ${sources.length} file(s)`);
|
|
27
|
-
}
|
|
28
|
-
};
|
|
1
|
+
import "../../chunk-DF48asd8.js";
|
|
2
|
+
import "../../virtual_file_system-dzfXNwEp.js";
|
|
3
|
+
import { t as IndexGenerator } from "../../main-INOi9swJ.js";
|
|
29
4
|
export { IndexGenerator };
|
package/build/src/types/main.js
CHANGED
package/build/src/utils.d.ts
CHANGED
|
@@ -40,7 +40,6 @@ export declare function runNode(cwd: string | URL, options: RunScriptOptions): i
|
|
|
40
40
|
buffer: false;
|
|
41
41
|
stdio: "pipe" | "inherit";
|
|
42
42
|
env: {
|
|
43
|
-
TZ?: string | undefined;
|
|
44
43
|
FORCE_COLOR?: string | undefined;
|
|
45
44
|
};
|
|
46
45
|
}>;
|
|
@@ -62,7 +61,6 @@ export declare function run(cwd: string | URL, options: Omit<RunScriptOptions, '
|
|
|
62
61
|
buffer: false;
|
|
63
62
|
stdio: "pipe" | "inherit";
|
|
64
63
|
env: {
|
|
65
|
-
TZ?: string | undefined;
|
|
66
64
|
FORCE_COLOR?: string | undefined;
|
|
67
65
|
};
|
|
68
66
|
}>;
|
|
@@ -19,8 +19,40 @@ import { fdir } from "fdir";
|
|
|
19
19
|
import lodash from "@poppinss/utils/lodash";
|
|
20
20
|
import { Lang, parse } from "@ast-grep/napi";
|
|
21
21
|
import picomatch from "picomatch";
|
|
22
|
+
//#region src/debug.ts
|
|
23
|
+
/**
|
|
24
|
+
* Debug logger for the AdonisJS assembler package.
|
|
25
|
+
*
|
|
26
|
+
* This debug instance is configured to log messages under the 'adonisjs:assembler'
|
|
27
|
+
* namespace. Debug messages are only shown when the NODE_DEBUG environment variable
|
|
28
|
+
* includes 'adonisjs:assembler'.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* // Enable debug logging
|
|
32
|
+
* // NODE_DEBUG=adonisjs:assembler node ace serve
|
|
33
|
+
* debug('Starting development server...')
|
|
34
|
+
*/
|
|
22
35
|
var debug_default = debuglog("adonisjs:assembler");
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/utils.ts
|
|
38
|
+
/**
|
|
39
|
+
* Default set of args to pass in order to run TypeScript
|
|
40
|
+
* source. Used by "run" and "runNode" scripts
|
|
41
|
+
*/
|
|
23
42
|
const DEFAULT_NODE_ARGS = ["--import=@poppinss/ts-exec", "--enable-source-maps"];
|
|
43
|
+
/**
|
|
44
|
+
* Parses tsconfig.json and prints errors using typescript compiler host
|
|
45
|
+
*
|
|
46
|
+
* This function reads and parses the tsconfig.json file from the given directory,
|
|
47
|
+
* handling diagnostic errors and returning a parsed configuration that can be
|
|
48
|
+
* used by other TypeScript operations.
|
|
49
|
+
*
|
|
50
|
+
* @deprecated While we are experimenting with the readTsConfig method
|
|
51
|
+
*
|
|
52
|
+
* @param cwd - The current working directory URL or string path
|
|
53
|
+
* @param ts - TypeScript module reference
|
|
54
|
+
* @returns Parsed TypeScript configuration or undefined if parsing failed
|
|
55
|
+
*/
|
|
24
56
|
function parseConfig(cwd, ts) {
|
|
25
57
|
const cwdPath = typeof cwd === "string" ? cwd : fileURLToPath(cwd);
|
|
26
58
|
const configFile = join$1(cwdPath, "tsconfig.json");
|
|
@@ -70,6 +102,17 @@ function readTsConfig(cwd) {
|
|
|
70
102
|
return null;
|
|
71
103
|
}
|
|
72
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Runs a Node.js script as a child process and inherits the stdio streams
|
|
107
|
+
*
|
|
108
|
+
* This function spawns a Node.js child process with TypeScript support enabled
|
|
109
|
+
* by default through ts-exec. It's primarily used for running development
|
|
110
|
+
* servers and test scripts.
|
|
111
|
+
*
|
|
112
|
+
* @param cwd - The current working directory URL or string path
|
|
113
|
+
* @param options - Script execution options including args, environment, etc.
|
|
114
|
+
* @returns Child process instance from execa
|
|
115
|
+
*/
|
|
73
116
|
function runNode(cwd, options) {
|
|
74
117
|
return execaNode(options.script, options.scriptArgs, {
|
|
75
118
|
nodeOptions: DEFAULT_NODE_ARGS.concat(options.nodeArgs),
|
|
@@ -86,6 +129,16 @@ function runNode(cwd, options) {
|
|
|
86
129
|
}
|
|
87
130
|
});
|
|
88
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Runs a script as a child process and inherits the stdio streams
|
|
134
|
+
*
|
|
135
|
+
* This function spawns a generic child process for running any executable
|
|
136
|
+
* script. Unlike runNode, this doesn't include TypeScript-specific Node.js arguments.
|
|
137
|
+
*
|
|
138
|
+
* @param cwd - The current working directory URL or string path
|
|
139
|
+
* @param options - Script execution options (excluding nodeArgs)
|
|
140
|
+
* @returns Child process instance from execa
|
|
141
|
+
*/
|
|
89
142
|
function run(cwd, options) {
|
|
90
143
|
return execa(options.script, options.scriptArgs, {
|
|
91
144
|
preferLocal: true,
|
|
@@ -100,24 +153,81 @@ function run(cwd, options) {
|
|
|
100
153
|
}
|
|
101
154
|
});
|
|
102
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* Watches the file system using chokidar with the provided options
|
|
158
|
+
*
|
|
159
|
+
* Creates a file system watcher that monitors the current directory
|
|
160
|
+
* for changes, supporting various chokidar options for customization.
|
|
161
|
+
*
|
|
162
|
+
* @param options - Chokidar watch options
|
|
163
|
+
* @returns Chokidar FSWatcher instance
|
|
164
|
+
*/
|
|
103
165
|
function watch(options) {
|
|
104
166
|
return chokidar.watch(["."], options);
|
|
105
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Returns the port to use after inspecting the dot-env files inside
|
|
170
|
+
* a given directory.
|
|
171
|
+
*
|
|
172
|
+
* A random port is used when the specified port is in use. Following
|
|
173
|
+
* is the logic for finding a specified port:
|
|
174
|
+
*
|
|
175
|
+
* - The "process.env.PORT" value is used if exists.
|
|
176
|
+
* - The dot-env files are loaded using the "EnvLoader" and the PORT
|
|
177
|
+
* value is used by iterating over all the loaded files. The
|
|
178
|
+
* iteration stops after first find.
|
|
179
|
+
* - Falls back to port 3333 if no PORT is found in environment files.
|
|
180
|
+
*
|
|
181
|
+
* @param cwd - The current working directory URL
|
|
182
|
+
* @returns Promise resolving to an available port number
|
|
183
|
+
*/
|
|
106
184
|
async function getPort(cwd) {
|
|
185
|
+
/**
|
|
186
|
+
* Use existing port if exists
|
|
187
|
+
*/
|
|
107
188
|
if (process.env.PORT) return getRandomPort({ port: Number(process.env.PORT) });
|
|
189
|
+
/**
|
|
190
|
+
* Loop over files and use the port from their contents. Stops
|
|
191
|
+
* after first match
|
|
192
|
+
*/
|
|
108
193
|
const files = await new EnvLoader(cwd).load();
|
|
109
194
|
for (let file of files) {
|
|
110
195
|
const envVariables = await new EnvParser(file.contents, cwd).parse();
|
|
111
196
|
if (envVariables.PORT) return getRandomPort({ port: Number(envVariables.PORT) });
|
|
112
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* Use 3333 as the port
|
|
200
|
+
*/
|
|
113
201
|
return getRandomPort({ port: 3333 });
|
|
114
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Helper function to copy files from relative paths or glob patterns
|
|
205
|
+
*
|
|
206
|
+
* This function handles copying files and directories while preserving
|
|
207
|
+
* directory structure. It supports both direct file paths and glob patterns,
|
|
208
|
+
* and automatically filters out junk files.
|
|
209
|
+
*
|
|
210
|
+
* @param files - Array of file paths or glob patterns to copy
|
|
211
|
+
* @param cwd - Source directory path
|
|
212
|
+
* @param outDir - Destination directory path
|
|
213
|
+
* @returns Promise resolving when all files are copied
|
|
214
|
+
*/
|
|
115
215
|
async function copyFiles(files, cwd, outDir) {
|
|
216
|
+
/**
|
|
217
|
+
* Looping over files and create a new collection with paths
|
|
218
|
+
* and glob patterns
|
|
219
|
+
*/
|
|
116
220
|
const { paths, patterns } = files.reduce((result, file) => {
|
|
221
|
+
/**
|
|
222
|
+
* If file is a glob pattern, then push it to patterns
|
|
223
|
+
*/
|
|
117
224
|
if (fastGlob.isDynamicPattern(file)) {
|
|
118
225
|
result.patterns.push(file);
|
|
119
226
|
return result;
|
|
120
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* Otherwise, check if file exists and push it to paths to copy
|
|
230
|
+
*/
|
|
121
231
|
if (existsSync(join$1(cwd, file))) result.paths.push(file);
|
|
122
232
|
return result;
|
|
123
233
|
}, {
|
|
@@ -125,12 +235,19 @@ async function copyFiles(files, cwd, outDir) {
|
|
|
125
235
|
paths: []
|
|
126
236
|
});
|
|
127
237
|
debug_default("copyFiles inputs: %O, paths: %O, patterns: %O", files, paths, patterns);
|
|
238
|
+
/**
|
|
239
|
+
* Getting list of relative paths from glob patterns
|
|
240
|
+
*/
|
|
128
241
|
const filePaths = paths.concat(await fastGlob(patterns, {
|
|
129
242
|
cwd,
|
|
130
243
|
dot: true
|
|
131
244
|
})).filter((file) => {
|
|
132
245
|
return !isJunk(basename(file));
|
|
133
246
|
});
|
|
247
|
+
/**
|
|
248
|
+
* Finally copy files to the destination by keeping the same
|
|
249
|
+
* directory structure and ignoring junk files
|
|
250
|
+
*/
|
|
134
251
|
debug_default("copying files %O to destination \"%s\"", filePaths, outDir);
|
|
135
252
|
const copyPromises = filePaths.map(async (file) => {
|
|
136
253
|
const src = isAbsolute(file) ? file : join$1(cwd, file);
|
|
@@ -140,6 +257,17 @@ async function copyFiles(files, cwd, outDir) {
|
|
|
140
257
|
});
|
|
141
258
|
return await Promise.all(copyPromises);
|
|
142
259
|
}
|
|
260
|
+
/**
|
|
261
|
+
* Memoize a function using an LRU cache. The function must accept
|
|
262
|
+
* only one argument as a string value.
|
|
263
|
+
*
|
|
264
|
+
* This utility provides caching for expensive function calls to improve
|
|
265
|
+
* performance by storing results in memory.
|
|
266
|
+
*
|
|
267
|
+
* @param fn - Function to memoize (only first argument is considered for memoization)
|
|
268
|
+
* @param maxKeys - Optional maximum number of cached keys
|
|
269
|
+
* @returns Memoized version of the function
|
|
270
|
+
*/
|
|
143
271
|
function memoize(fn, maxKeys) {
|
|
144
272
|
const cache = new Cache({ max: maxKeys });
|
|
145
273
|
return (input, ...args) => {
|
|
@@ -147,9 +275,28 @@ function memoize(fn, maxKeys) {
|
|
|
147
275
|
return fn(input, ...args);
|
|
148
276
|
};
|
|
149
277
|
}
|
|
278
|
+
/**
|
|
279
|
+
* Returns a boolean telling if the path value is a relative
|
|
280
|
+
* path starting with "./" or "../"
|
|
281
|
+
*
|
|
282
|
+
* @param pathValue - The path string to check
|
|
283
|
+
* @returns True if the path is relative, false otherwise
|
|
284
|
+
*/
|
|
150
285
|
function isRelative(pathValue) {
|
|
151
286
|
return pathValue.startsWith("./") || pathValue.startsWith("../");
|
|
152
287
|
}
|
|
288
|
+
/**
|
|
289
|
+
* Imports a selected set of lazy hooks and creates an instance of the
|
|
290
|
+
* Hooks class
|
|
291
|
+
*
|
|
292
|
+
* This function dynamically imports and initializes hooks based on the
|
|
293
|
+
* provided configuration, supporting different types of hooks for various
|
|
294
|
+
* assembler operations.
|
|
295
|
+
*
|
|
296
|
+
* @param rcFileHooks - Hook configuration from the RC file
|
|
297
|
+
* @param names - Array of hook names to load
|
|
298
|
+
* @returns Promise resolving to configured Hooks instance
|
|
299
|
+
*/
|
|
153
300
|
async function loadHooks(rcFileHooks, names) {
|
|
154
301
|
const groups = names.map((name) => {
|
|
155
302
|
return {
|
|
@@ -162,6 +309,18 @@ async function loadHooks(rcFileHooks, names) {
|
|
|
162
309
|
else hooks.add(group, await importDefault(item));
|
|
163
310
|
return hooks;
|
|
164
311
|
}
|
|
312
|
+
/**
|
|
313
|
+
* Wraps a function inside another function that throttles the concurrent
|
|
314
|
+
* executions of a function. If the function is called too quickly, then
|
|
315
|
+
* it may result in two invocations at max.
|
|
316
|
+
*
|
|
317
|
+
* This utility prevents overwhelming the system with rapid successive calls
|
|
318
|
+
* by ensuring only one execution happens at a time, with at most one queued call.
|
|
319
|
+
*
|
|
320
|
+
* @param fn - Function to throttle
|
|
321
|
+
* @param name - Optional name for debugging purposes
|
|
322
|
+
* @returns Throttled version of the function
|
|
323
|
+
*/
|
|
165
324
|
function throttle(fn, name) {
|
|
166
325
|
name = name || "throttled";
|
|
167
326
|
let isBusy = false;
|
|
@@ -187,28 +346,83 @@ function throttle(fn, name) {
|
|
|
187
346
|
}
|
|
188
347
|
return throttled;
|
|
189
348
|
}
|
|
349
|
+
/**
|
|
350
|
+
* Removes the file extension from a file path
|
|
351
|
+
*
|
|
352
|
+
* @param filePath - The file path with extension
|
|
353
|
+
* @returns The file path without extension
|
|
354
|
+
*/
|
|
190
355
|
function removeExtension(filePath) {
|
|
191
356
|
return filePath.substring(0, filePath.lastIndexOf("."));
|
|
192
357
|
}
|
|
358
|
+
//#endregion
|
|
359
|
+
//#region src/virtual_file_system.ts
|
|
193
360
|
const BYPASS_FN = (input) => input;
|
|
194
361
|
const DEFAULT_GLOB = [
|
|
195
362
|
"**/!(*.d).ts",
|
|
196
363
|
"**/*.tsx",
|
|
197
364
|
"**/*.js"
|
|
198
365
|
];
|
|
366
|
+
/**
|
|
367
|
+
* Virtual file system for managing and tracking files with AST parsing capabilities.
|
|
368
|
+
*
|
|
369
|
+
* The VirtualFileSystem provides an abstraction layer over the physical file system,
|
|
370
|
+
* allowing efficient file scanning, filtering, and AST parsing with caching. It's
|
|
371
|
+
* designed to work with TypeScript/JavaScript files and provides various output
|
|
372
|
+
* formats for different use cases.
|
|
373
|
+
*
|
|
374
|
+
* @example
|
|
375
|
+
* const vfs = new VirtualFileSystem('/src', { glob: ['**\/*.ts'] })
|
|
376
|
+
* await vfs.scan()
|
|
377
|
+
* const fileTree = vfs.asTree()
|
|
378
|
+
* const astNode = await vfs.get('/src/app.ts')
|
|
379
|
+
*/
|
|
199
380
|
var VirtualFileSystem = class {
|
|
381
|
+
/**
|
|
382
|
+
* Absolute path to the source directory from where to read the files.
|
|
383
|
+
* Additionally, a glob pattern or a filter could be specified to
|
|
384
|
+
* narrow down the scanned files list.
|
|
385
|
+
*/
|
|
200
386
|
#source;
|
|
387
|
+
/**
|
|
388
|
+
* Filesystem options
|
|
389
|
+
*/
|
|
201
390
|
#options;
|
|
391
|
+
/**
|
|
392
|
+
* Files collected from the initial scan with pre-computed relative paths
|
|
393
|
+
*/
|
|
202
394
|
#files = /* @__PURE__ */ new Map();
|
|
395
|
+
/**
|
|
396
|
+
* LRU cache storing parsed AST nodes by file path with size limit
|
|
397
|
+
*/
|
|
203
398
|
#astCache = new Cache({ max: 60 });
|
|
399
|
+
/**
|
|
400
|
+
* Matcher is defined when glob is defined via the options
|
|
401
|
+
*/
|
|
204
402
|
#matcher;
|
|
403
|
+
/**
|
|
404
|
+
* Picomatch options used for file pattern matching
|
|
405
|
+
*/
|
|
205
406
|
#picoMatchOptions;
|
|
407
|
+
/**
|
|
408
|
+
* Create a new VirtualFileSystem instance
|
|
409
|
+
*
|
|
410
|
+
* @param source - Absolute path to the source directory
|
|
411
|
+
* @param options - Optional configuration for file filtering and processing
|
|
412
|
+
*/
|
|
206
413
|
constructor(source, options) {
|
|
207
414
|
this.#source = source;
|
|
208
415
|
this.#options = options ?? {};
|
|
209
416
|
this.#picoMatchOptions = { cwd: this.#source };
|
|
210
417
|
this.#matcher = picomatch(this.#options.glob ?? DEFAULT_GLOB, this.#picoMatchOptions);
|
|
211
418
|
}
|
|
419
|
+
/**
|
|
420
|
+
* Scans the filesystem to collect the files. Newly files must
|
|
421
|
+
* be added via the ".add" method.
|
|
422
|
+
*
|
|
423
|
+
* This method performs an initial scan of the source directory using
|
|
424
|
+
* the configured glob patterns and populates the internal file list.
|
|
425
|
+
*/
|
|
212
426
|
async scan() {
|
|
213
427
|
debug_default("fetching entities from source \"%s\"", this.#source);
|
|
214
428
|
const crawler = new fdir().globWithOptions(this.#options.glob ?? DEFAULT_GLOB, this.#picoMatchOptions).withFullPaths();
|
|
@@ -223,11 +437,37 @@ var VirtualFileSystem = class {
|
|
|
223
437
|
this.#files.set(filePath, relativePath);
|
|
224
438
|
}
|
|
225
439
|
}
|
|
440
|
+
/**
|
|
441
|
+
* Check if a given file is part of the virtual file system. The method
|
|
442
|
+
* checks for the scanned files as well as glob pattern matches.
|
|
443
|
+
*
|
|
444
|
+
* @param filePath - Absolute file path to check
|
|
445
|
+
* @returns True if the file is tracked or matches the configured patterns
|
|
446
|
+
*/
|
|
226
447
|
has(filePath) {
|
|
448
|
+
/**
|
|
449
|
+
* File is already tracked as part of the initial scan
|
|
450
|
+
*/
|
|
227
451
|
if (this.#files.has(filePath)) return true;
|
|
452
|
+
/**
|
|
453
|
+
* Return false if file is not within the source dir
|
|
454
|
+
*/
|
|
228
455
|
if (!filePath.startsWith(this.#source)) return false;
|
|
456
|
+
/**
|
|
457
|
+
* If a match exists, then check if the file matches via the
|
|
458
|
+
* matcher test
|
|
459
|
+
*/
|
|
229
460
|
return this.#matcher(filePath);
|
|
230
461
|
}
|
|
462
|
+
/**
|
|
463
|
+
* Returns the files as a flat list of key-value pairs
|
|
464
|
+
*
|
|
465
|
+
* Converts the tracked files into a flat object where keys are relative
|
|
466
|
+
* paths (without extensions) and values are absolute file paths.
|
|
467
|
+
*
|
|
468
|
+
* @param options - Optional transformation functions for keys and values
|
|
469
|
+
* @returns Object with file mappings
|
|
470
|
+
*/
|
|
231
471
|
asList(options) {
|
|
232
472
|
const list = {};
|
|
233
473
|
const transformKey = options?.transformKey ?? BYPASS_FN;
|
|
@@ -235,6 +475,15 @@ var VirtualFileSystem = class {
|
|
|
235
475
|
for (const [filePath, relativePath] of this.#files) list[transformKey(relativePath)] = transformValue(filePath);
|
|
236
476
|
return list;
|
|
237
477
|
}
|
|
478
|
+
/**
|
|
479
|
+
* Returns the files as a nested tree structure
|
|
480
|
+
*
|
|
481
|
+
* Converts the tracked files into a hierarchical object structure that
|
|
482
|
+
* mirrors the directory structure of the source files.
|
|
483
|
+
*
|
|
484
|
+
* @param options - Optional transformation functions for keys and values
|
|
485
|
+
* @returns Nested object representing the file tree
|
|
486
|
+
*/
|
|
238
487
|
asTree(options) {
|
|
239
488
|
const list = {};
|
|
240
489
|
const transformKey = options?.transformKey ?? BYPASS_FN;
|
|
@@ -245,6 +494,13 @@ var VirtualFileSystem = class {
|
|
|
245
494
|
}
|
|
246
495
|
return list;
|
|
247
496
|
}
|
|
497
|
+
/**
|
|
498
|
+
* Add a new file to the virtual file system. File is only added when it
|
|
499
|
+
* matches the pre-defined filters.
|
|
500
|
+
*
|
|
501
|
+
* @param filePath - Absolute path of the file to add
|
|
502
|
+
* @returns True if the file was added, false if it doesn't match filters
|
|
503
|
+
*/
|
|
248
504
|
add(filePath) {
|
|
249
505
|
if (this.has(filePath)) {
|
|
250
506
|
debug_default("adding new \"%s\" file to the virtual file system", filePath);
|
|
@@ -254,10 +510,26 @@ var VirtualFileSystem = class {
|
|
|
254
510
|
}
|
|
255
511
|
return false;
|
|
256
512
|
}
|
|
513
|
+
/**
|
|
514
|
+
* Remove a file from the virtual file system
|
|
515
|
+
*
|
|
516
|
+
* @param filePath - Absolute path of the file to remove
|
|
517
|
+
* @returns True if the file was removed, false if it wasn't tracked
|
|
518
|
+
*/
|
|
257
519
|
remove(filePath) {
|
|
258
520
|
debug_default("removing \"%s\" file from virtual file system", filePath);
|
|
259
521
|
return this.#files.delete(filePath);
|
|
260
522
|
}
|
|
523
|
+
/**
|
|
524
|
+
* Returns the file contents as AST-grep node and caches it
|
|
525
|
+
* forever. Use the "invalidate" method to remove it from the cache.
|
|
526
|
+
*
|
|
527
|
+
* This method reads the file content, parses it into an AST using ast-grep,
|
|
528
|
+
* and caches the result for future requests to improve performance.
|
|
529
|
+
*
|
|
530
|
+
* @param filePath - The absolute path to the file to parse
|
|
531
|
+
* @returns Promise resolving to the AST-grep node
|
|
532
|
+
*/
|
|
261
533
|
async get(filePath) {
|
|
262
534
|
const cached = this.#astCache.get(filePath);
|
|
263
535
|
if (cached) {
|
|
@@ -269,6 +541,14 @@ var VirtualFileSystem = class {
|
|
|
269
541
|
this.#astCache.set(filePath, parse(Lang.TypeScript, fileContents).root());
|
|
270
542
|
return this.#astCache.get(filePath);
|
|
271
543
|
}
|
|
544
|
+
/**
|
|
545
|
+
* Invalidates AST cache for a single file or all files
|
|
546
|
+
*
|
|
547
|
+
* Use this method when files have been modified to ensure fresh
|
|
548
|
+
* AST parsing on subsequent get() calls.
|
|
549
|
+
*
|
|
550
|
+
* @param filePath - Optional file path to clear. If omitted, clears entire cache
|
|
551
|
+
*/
|
|
272
552
|
invalidate(filePath) {
|
|
273
553
|
if (filePath) {
|
|
274
554
|
debug_default("invalidate AST cache \"%s\"", filePath);
|
|
@@ -278,8 +558,15 @@ var VirtualFileSystem = class {
|
|
|
278
558
|
this.#astCache.clear();
|
|
279
559
|
}
|
|
280
560
|
}
|
|
561
|
+
/**
|
|
562
|
+
* Clear all scanned files from memory
|
|
563
|
+
*
|
|
564
|
+
* Removes all tracked files from the internal file list, effectively
|
|
565
|
+
* resetting the virtual file system.
|
|
566
|
+
*/
|
|
281
567
|
clear() {
|
|
282
568
|
this.#files.clear();
|
|
283
569
|
}
|
|
284
570
|
};
|
|
571
|
+
//#endregion
|
|
285
572
|
export { loadHooks as a, readTsConfig as c, runNode as d, throttle as f, isRelative as i, removeExtension as l, debug_default as m, copyFiles as n, memoize as o, watch as p, getPort as r, parseConfig as s, VirtualFileSystem as t, run as u };
|