@adonisjs/assembler 8.0.0-next.31 → 8.0.0-next.33

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/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { a as loadHooks, c as readTsConfig, d as runNode, f as throttle, m as debug_default, n as copyFiles, o as memoize, p as watch, r as getPort, s as parseConfig, t as VirtualFileSystem, u as run } from "./virtual_file_system-bGeoWsK-.js";
2
- import { n as FileBuffer, t as IndexGenerator } from "./main-CknPN3rJ.js";
3
- import { t as RoutesScanner } from "./main-BeV45LeF.js";
4
- import "./helpers-DDurYRsZ.js";
5
- import { t as CodemodException } from "./codemod_exception-vyN1VXuX.js";
2
+ import { n as FileBuffer } from "./source-dVeugJ0e.js";
3
+ import { IndexGenerator } from "./src/index_generator/main.js";
4
+ import "./validator_extractor-Ccio_Ndi.js";
5
+ import { RoutesScanner } from "./src/code_scanners/routes_scanner/main.js";
6
+ import { t as CodemodException } from "./codemod_exception-CzQgXAAf.js";
6
7
  import dedent from "dedent";
7
8
  import fs, { readFile, unlink } from "node:fs/promises";
8
9
  import { cliui } from "@poppinss/cliui";
@@ -556,7 +557,7 @@ var DevServer = class DevServer {
556
557
  this.ui.logger.info(`starting server in ${this.#mode} mode...`);
557
558
  this.#indexGenerator = new IndexGenerator(this.cwdPath, this.ui.logger);
558
559
  this.#stickyPort = String(await getPort(this.cwd));
559
- this.#stickyHmrPort = String(getRandomPort({ port: 24678 }));
560
+ this.#stickyHmrPort = String(await getRandomPort({ port: 24678 }));
560
561
  this.#fileSystem = new FileSystem(this.cwdPath, tsConfig, this.options);
561
562
  this.ui.logger.info("loading hooks...");
562
563
  this.#hooks = await loadHooks(this.options.hooks, [
@@ -855,7 +856,7 @@ var TestRunner = class {
855
856
  }
856
857
  async run() {
857
858
  this.#stickyPort = String(await getPort(this.cwd));
858
- this.#stickyHmrPort = String(getRandomPort({ port: 24678 }));
859
+ this.#stickyHmrPort = String(await getRandomPort({ port: 24678 }));
859
860
  this.#indexGenerator = new IndexGenerator(this.cwdPath, this.ui.logger);
860
861
  this.#clearScreen();
861
862
  this.ui.logger.info("loading hooks...");
@@ -59,6 +59,7 @@ var IndexGeneratorSource = class {
59
59
  #cliLogger;
60
60
  #generateOutput = throttle(async () => {
61
61
  const buffer = new FileBuffer().eol(true);
62
+ if (this.#config.comment) buffer.writeLine(this.#textToComment(typeof this.#config.comment === "string" ? this.#config.comment : `This file is automatically generated.\nDO NOT EDIT manually`));
62
63
  if (this.#config.as === "barrelFile") this.#asBarrelFile(this.#vfs, buffer, this.#config.exportName, this.#config.disableLazyImports);
63
64
  else this.#config.as(this.#vfs, buffer, this.#config, { toImportPath: this.#createBarrelFileImportGenerator(this.#source, this.#outputDirname, this.#config) });
64
65
  await mkdir(dirname(this.#output), { recursive: true });
@@ -89,6 +90,9 @@ var IndexGeneratorSource = class {
89
90
  }
90
91
  });
91
92
  }
93
+ #textToComment(text) {
94
+ return `/**\n * ${text.split("\n").join("\n * ")}\n */`;
95
+ }
92
96
  #createBarrelFileKeyGenerator(config) {
93
97
  return function(key) {
94
98
  let paths = key.split("/");
@@ -159,30 +163,4 @@ var IndexGeneratorSource = class {
159
163
  await this.#generateOutput();
160
164
  }
161
165
  };
162
- var IndexGenerator = class {
163
- appRoot;
164
- #sources = {};
165
- #cliLogger;
166
- constructor(appRoot, cliLogger) {
167
- this.appRoot = appRoot;
168
- this.#cliLogger = cliLogger;
169
- }
170
- add(name, config) {
171
- this.#sources[name] = new IndexGeneratorSource(name, this.appRoot, this.#cliLogger, config);
172
- return this;
173
- }
174
- async addFile(filePath) {
175
- const sources = Object.values(this.#sources);
176
- for (let source of sources) await source.addFile(filePath);
177
- }
178
- async removeFile(filePath) {
179
- const sources = Object.values(this.#sources);
180
- for (let source of sources) await source.removeFile(filePath);
181
- }
182
- async generate() {
183
- const sources = Object.values(this.#sources);
184
- for (let source of sources) await source.generate();
185
- if (sources.length) this.#cliLogger.info(`codegen: created ${sources.length} file(s)`);
186
- }
187
- };
188
- export { FileBuffer as n, IndexGenerator as t };
166
+ export { FileBuffer as n, IndexGeneratorSource as t };
@@ -1,4 +1,172 @@
1
- import "../../../virtual_file_system-bGeoWsK-.js";
2
- import { t as RoutesScanner } from "../../../main-BeV45LeF.js";
3
- import "../../../helpers-DDurYRsZ.js";
1
+ import { m as debug_default, t as VirtualFileSystem } from "../../../virtual_file_system-bGeoWsK-.js";
2
+ import { n as PathsResolver, t as extractValidators } from "../../../validator_extractor-Ccio_Ndi.js";
3
+ import { cliui } from "@poppinss/cliui";
4
+ import string from "@poppinss/utils/string";
5
+ import StringBuilder from "@poppinss/utils/string_builder";
6
+ import { parseImports } from "parse-imports";
7
+ var RoutesScanner = class {
8
+ #filter;
9
+ #appRoot;
10
+ #controllerRoutes = {};
11
+ #scannedRoutes = [];
12
+ #computeResponseTypes;
13
+ #computeRequestTypes;
14
+ #extractValidators;
15
+ ui = cliui();
16
+ pathsResolver;
17
+ rules = {
18
+ request: {},
19
+ response: {}
20
+ };
21
+ constructor(appRoot, rulesCollection) {
22
+ this.#appRoot = appRoot;
23
+ this.pathsResolver = new PathsResolver(appRoot);
24
+ rulesCollection.forEach((rules) => {
25
+ Object.assign(this.rules.request, rules.request);
26
+ Object.assign(this.rules.response, rules.response);
27
+ });
28
+ }
29
+ #shouldSkipRoute(route) {
30
+ if (this.#filter) return !this.#filter(route);
31
+ return false;
32
+ }
33
+ #prepareRequestTypes(route) {
34
+ if (!route.validators) return;
35
+ return {
36
+ type: route.validators.reduce((result, validator) => {
37
+ const validatorExport = validator.import.type === "default" ? ".default" : validator.import.type === "named" ? `.${validator.import.value}` : "";
38
+ const [, ...segments] = validator.name.split(".");
39
+ const namespace = segments.map((segment) => `['${segment}']`).join("");
40
+ result.push(`InferInput<(typeof import('${validator.import.specifier}')${validatorExport})${namespace}>`);
41
+ return result;
42
+ }, []).join("|"),
43
+ imports: [`import { InferInput } from '@vinejs/vine/types'`]
44
+ };
45
+ }
46
+ async #inspectControllerSpecifier(importExpression, method) {
47
+ const importedModule = [...await parseImports(importExpression)].find(($import) => $import.isDynamicImport && $import.moduleSpecifier.value);
48
+ if (!importedModule || importedModule.moduleSpecifier.type !== "package" || !importedModule.moduleSpecifier.value) return null;
49
+ const specifier = importedModule.moduleSpecifier.value;
50
+ const name = new StringBuilder(specifier.split("/").pop()).removeSuffix("Controller").pascalCase().suffix("Controller").toString();
51
+ return {
52
+ name,
53
+ method: method ?? "handle",
54
+ path: string.toUnixSlash(this.pathsResolver.resolve(specifier, true)),
55
+ import: {
56
+ specifier,
57
+ type: "default",
58
+ value: name
59
+ }
60
+ };
61
+ }
62
+ async #setResponse(route, controller) {
63
+ route.response = await this.#computeResponseTypes?.(route, controller, this) ?? {
64
+ type: `ReturnType<import('${controller.import.specifier}').default['${controller.method}']>`,
65
+ imports: []
66
+ };
67
+ debug_default("computed route \"%s\" response %O", route.name, route.response);
68
+ }
69
+ async #setRequest(route, controller, vfs) {
70
+ route.validators = await this.#extractValidators?.(route, controller, this) ?? await extractValidators(this.#appRoot, vfs, controller);
71
+ debug_default("computed route \"%s\" validators %O", route.name, route.validators);
72
+ route.request = await this.#computeRequestTypes?.(route, controller, this) ?? this.#prepareRequestTypes(route);
73
+ debug_default("computed route \"%s\" request input %O", route.name, route.request);
74
+ }
75
+ #processRouteWithoutController(route) {
76
+ if (!route.name) {
77
+ debug_default(`skipping route "%s" as it does not have a name`, route.pattern);
78
+ return;
79
+ }
80
+ const scannedRoute = {
81
+ name: route.name,
82
+ domain: route.domain,
83
+ methods: route.methods,
84
+ pattern: route.pattern,
85
+ tokens: route.tokens,
86
+ request: this.rules.request[route.name],
87
+ response: this.rules.response[route.name]
88
+ };
89
+ debug_default("scanned route without controller %O", scannedRoute);
90
+ this.#scannedRoutes.push(scannedRoute);
91
+ }
92
+ async #processRouteWithController(route, vfs) {
93
+ if (!route.handler || !route.handler.importExpression) return this.#processRouteWithoutController(route);
94
+ const controller = await this.#inspectControllerSpecifier(route.handler.importExpression, route.handler.method);
95
+ if (!controller) return this.#processRouteWithoutController(route);
96
+ debug_default("processing route \"%s\" with inspected controller %O", route.name, controller);
97
+ route.name = route.name ?? new StringBuilder(controller.name).removeSuffix("Controller").snakeCase().suffix(`.${string.snakeCase(controller.method)}`).toString();
98
+ if (this.#shouldSkipRoute(route)) return;
99
+ const scannedRoute = {
100
+ name: route.name,
101
+ domain: route.domain,
102
+ methods: route.methods,
103
+ pattern: route.pattern,
104
+ tokens: route.tokens,
105
+ request: this.rules.request[route.name],
106
+ response: this.rules.response[route.name],
107
+ controller
108
+ };
109
+ debug_default("scanned route %O", scannedRoute);
110
+ this.#scannedRoutes.push(scannedRoute);
111
+ if (!scannedRoute.request || !scannedRoute.response) {
112
+ debug_default("tracking controller for rescanning %O", scannedRoute);
113
+ this.#controllerRoutes[controller.path] ??= [];
114
+ this.#controllerRoutes[controller.path].push(scannedRoute);
115
+ if (!scannedRoute.response) await this.#setResponse(scannedRoute, controller);
116
+ if (!scannedRoute.request) await this.#setRequest(scannedRoute, controller, vfs);
117
+ }
118
+ }
119
+ async #processRoute(route, vfs) {
120
+ if (route.name && this.#shouldSkipRoute(route)) {
121
+ debug_default("route skipped route: %O, rules: %O", route, this.rules);
122
+ return;
123
+ }
124
+ if (typeof route.handler === "function") {
125
+ this.#processRouteWithoutController(route);
126
+ return;
127
+ }
128
+ await this.#processRouteWithController(route, vfs);
129
+ }
130
+ defineResponse(callback) {
131
+ this.#computeResponseTypes = callback;
132
+ return this;
133
+ }
134
+ defineRequest(callback) {
135
+ this.#computeRequestTypes = callback;
136
+ return this;
137
+ }
138
+ extractValidators(callback) {
139
+ this.#extractValidators = callback;
140
+ return this;
141
+ }
142
+ getScannedRoutes() {
143
+ return this.#scannedRoutes;
144
+ }
145
+ getControllers() {
146
+ return Object.keys(this.#controllerRoutes);
147
+ }
148
+ async invalidate(controllerPath) {
149
+ const controllerRoutes = this.#controllerRoutes[controllerPath];
150
+ if (!controllerRoutes || !controllerRoutes.length) {
151
+ debug_default("\"%s\" controllers is not part of scanned controllers %O", controllerPath, this.#controllerRoutes);
152
+ return false;
153
+ }
154
+ for (let scannedRoute of controllerRoutes) if (scannedRoute.controller) {
155
+ debug_default("invalidating route %O", scannedRoute);
156
+ const vfs = new VirtualFileSystem(this.#appRoot);
157
+ await this.#setResponse(scannedRoute, scannedRoute.controller);
158
+ await this.#setRequest(scannedRoute, scannedRoute.controller, vfs);
159
+ }
160
+ return true;
161
+ }
162
+ filter(filterFn) {
163
+ this.#filter = filterFn;
164
+ return this;
165
+ }
166
+ async scan(routes) {
167
+ const vfs = new VirtualFileSystem(this.#appRoot);
168
+ for (const route of routes) await this.#processRoute(route, vfs);
169
+ vfs.invalidate();
170
+ }
171
+ };
4
172
  export { RoutesScanner };
@@ -1,4 +1,4 @@
1
- import { t as CodemodException } from "../../codemod_exception-vyN1VXuX.js";
1
+ import { t as CodemodException } from "../../codemod_exception-CzQgXAAf.js";
2
2
  import { fileURLToPath } from "node:url";
3
3
  import { detectPackageManager, installPackage } from "@antfu/install-pkg";
4
4
  import { ImportsBag } from "@poppinss/utils";
@@ -1,2 +1,72 @@
1
- import { a as nodeToPlainText, i as inspectMethodArguments, n as inspectClass, o as searchValidatorDirectUsage, r as inspectClassMethods, t as findImport } from "../helpers-DDurYRsZ.js";
1
+ import { parseImports } from "parse-imports";
2
+ async function findImport(code, importReference) {
3
+ const importIdentifier = importReference.split(".")[0];
4
+ for (const $import of await parseImports(code, {})) {
5
+ if (!$import.importClause) continue;
6
+ if (!$import.moduleSpecifier.value) continue;
7
+ if ($import.importClause.default === importIdentifier) return {
8
+ specifier: $import.moduleSpecifier.value,
9
+ isConstant: $import.moduleSpecifier.isConstant,
10
+ clause: {
11
+ type: "default",
12
+ value: importIdentifier
13
+ }
14
+ };
15
+ if ($import.importClause.namespace === importIdentifier) return {
16
+ specifier: $import.moduleSpecifier.value,
17
+ isConstant: $import.moduleSpecifier.isConstant,
18
+ clause: {
19
+ type: "namespace",
20
+ value: importIdentifier
21
+ }
22
+ };
23
+ const namedImport = $import.importClause.named.find(({ binding }) => {
24
+ return binding === importIdentifier;
25
+ });
26
+ if (namedImport) return {
27
+ specifier: $import.moduleSpecifier.value,
28
+ isConstant: $import.moduleSpecifier.isConstant,
29
+ clause: {
30
+ type: "named",
31
+ value: namedImport.specifier,
32
+ ...namedImport.binding !== namedImport.specifier ? { alias: namedImport.binding } : {}
33
+ }
34
+ };
35
+ }
36
+ return null;
37
+ }
38
+ function inspectClass(node) {
39
+ return node.find({ rule: { kind: "class_declaration" } });
40
+ }
41
+ function inspectClassMethods(node) {
42
+ return node.findAll({ rule: { kind: "method_definition" } });
43
+ }
44
+ function nodeToPlainText(node) {
45
+ let out = [];
46
+ function toText(one) {
47
+ const children = one.children();
48
+ if (!children.length) out.push(one.text());
49
+ else children.forEach((child) => toText(child));
50
+ }
51
+ toText(node);
52
+ return out.join("");
53
+ }
54
+ function inspectMethodArguments(node, methodCalls) {
55
+ return node.findAll({ rule: { any: methodCalls.map((methodCall) => {
56
+ return { pattern: {
57
+ context: `${methodCall}($$$ARGUMENTS)`,
58
+ selector: "call_expression"
59
+ } };
60
+ }) } }).flatMap((matchingExpression) => {
61
+ return matchingExpression.findAll({ rule: { kind: "arguments" } });
62
+ });
63
+ }
64
+ function searchValidatorDirectUsage(node) {
65
+ return node.findAll({ rule: { pattern: {
66
+ context: "$$$VALIDATOR.validate($$$)",
67
+ selector: "call_expression"
68
+ } } }).flatMap((expression) => {
69
+ return expression.getMultipleMatches("VALIDATOR");
70
+ });
71
+ }
2
72
  export { findImport, inspectClass, inspectClassMethods, inspectMethodArguments, nodeToPlainText, searchValidatorDirectUsage };
@@ -1,3 +1,29 @@
1
1
  import "../../virtual_file_system-bGeoWsK-.js";
2
- import { t as IndexGenerator } from "../../main-CknPN3rJ.js";
2
+ import { t as IndexGeneratorSource } from "../../source-dVeugJ0e.js";
3
+ var IndexGenerator = class {
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
+ };
3
29
  export { IndexGenerator };
@@ -40,6 +40,7 @@ export type IndexGeneratorSourceConfig = ({
40
40
  importAlias?: string;
41
41
  removeSuffix?: string;
42
42
  skipSegments?: string[];
43
+ comment?: string | boolean;
43
44
  } & VirtualFileSystemOptions;
44
45
  /**
45
46
  * Marks a given optional property as required
@@ -0,0 +1,82 @@
1
+ import { i as isRelative, m as debug_default } from "./virtual_file_system-bGeoWsK-.js";
2
+ import { findImport, inspectClass, inspectClassMethods, inspectMethodArguments, nodeToPlainText, searchValidatorDirectUsage } from "./src/helpers.js";
3
+ import { fileURLToPath, pathToFileURL } from "node:url";
4
+ import string from "@poppinss/utils/string";
5
+ import { join, relative } from "node:path";
6
+ import { resolve } from "import-meta-resolve";
7
+ var PathsResolver = class {
8
+ #appRoot;
9
+ #resolvedPaths = {};
10
+ #resolver = (specifier, parentPath) => resolve(specifier, parentPath);
11
+ constructor(appRoot) {
12
+ this.#appRoot = pathToFileURL(join(appRoot, "index.js")).href;
13
+ }
14
+ use(resolver) {
15
+ this.#resolver = resolver;
16
+ }
17
+ resolve(specifier, rewriteAliasImportExtension = false) {
18
+ if (isRelative(specifier)) throw new Error("Cannot resolve relative paths using PathsResolver");
19
+ const cacheKey = specifier;
20
+ const cached = this.#resolvedPaths[cacheKey];
21
+ if (cached) return cached;
22
+ let resolvedPath = fileURLToPath(this.#resolver(specifier, this.#appRoot));
23
+ if (rewriteAliasImportExtension && specifier.startsWith("#") && resolvedPath.endsWith(".js")) resolvedPath = resolvedPath.replace(/\.js$/, ".ts");
24
+ this.#resolvedPaths[cacheKey] = resolvedPath;
25
+ return this.#resolvedPaths[cacheKey];
26
+ }
27
+ };
28
+ async function extractValidators(appRoot, vfs, controller) {
29
+ const root = await vfs.get(controller.path);
30
+ const fileContents = root.text();
31
+ const controllerClass = inspectClass(root);
32
+ if (!controllerClass) {
33
+ debug_default(`No class defined within the "%s"`, controller.import.specifier);
34
+ return;
35
+ }
36
+ const method = inspectClassMethods(controllerClass).find((methodNode) => {
37
+ return methodNode.find({ rule: { kind: "property_identifier" } })?.text() === controller.method;
38
+ });
39
+ if (!method) {
40
+ debug_default(`Unable to find "%s" method in "%s"`, controller.method, controller.import.specifier);
41
+ return;
42
+ }
43
+ const validationCalls = inspectMethodArguments(method, [
44
+ "request.validateUsing",
45
+ "$CTX.request.validateUsing",
46
+ "vine.validate"
47
+ ]).map((node) => {
48
+ const firstArg = node.find({ rule: { any: [{ kind: "identifier" }, { kind: "member_expression" }] } });
49
+ if (!firstArg) return;
50
+ return nodeToPlainText(firstArg);
51
+ }).filter((node) => node !== void 0).concat(searchValidatorDirectUsage(method).map((node) => nodeToPlainText(node)));
52
+ if (!validationCalls.length) {
53
+ debug_default(`Unable to detect any validation calls in "%s.%s" method`, controller.import.specifier, controller.method);
54
+ return;
55
+ }
56
+ const controllerName = controllerClass.find({ rule: { kind: "type_identifier" } }).text();
57
+ const controllerURL = pathToFileURL(controller.path);
58
+ return (await Promise.all(validationCalls.map(async (validationCall) => {
59
+ if (validationCall.split(".")[0] === controllerName) return {
60
+ name: validationCall,
61
+ import: {
62
+ specifier: controller.import.specifier,
63
+ type: "default",
64
+ value: controllerName
65
+ }
66
+ };
67
+ const importCall = await findImport(fileContents, validationCall);
68
+ if (!importCall) {
69
+ debug_default("Unable to find import for \"%s\" used by \"%s.%s\" method", validationCall, controller.import.specifier, controller.method);
70
+ return null;
71
+ }
72
+ return {
73
+ name: validationCall,
74
+ import: {
75
+ specifier: isRelative(importCall.specifier) ? string.toUnixSlash(relative(appRoot, fileURLToPath(new URL(importCall.specifier, controllerURL)))) : importCall.specifier,
76
+ type: importCall.clause.type,
77
+ value: importCall.clause.value
78
+ }
79
+ };
80
+ }))).filter((value) => !!value);
81
+ }
82
+ export { PathsResolver as n, extractValidators as t };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@adonisjs/assembler",
3
3
  "description": "Provides utilities to run AdonisJS development server and build project for production",
4
- "version": "8.0.0-next.31",
4
+ "version": "8.0.0-next.33",
5
5
  "engines": {
6
6
  "node": ">=24.0.0"
7
7
  },
@@ -44,9 +44,9 @@
44
44
  "@japa/file-system": "^3.0.0",
45
45
  "@japa/runner": "^5.3.0",
46
46
  "@japa/snapshot": "^2.0.10",
47
- "@poppinss/ts-exec": "^1.4.2",
48
- "@release-it/conventional-changelog": "^10.0.4",
49
- "@types/node": "^25.0.10",
47
+ "@poppinss/ts-exec": "^1.4.4",
48
+ "@release-it/conventional-changelog": "^10.0.5",
49
+ "@types/node": "^25.2.3",
50
50
  "@types/picomatch": "^4.0.2",
51
51
  "@types/pretty-hrtime": "^1.0.3",
52
52
  "c8": "^10.1.3",
@@ -57,7 +57,7 @@
57
57
  "p-event": "^7.1.0",
58
58
  "prettier": "^3.8.1",
59
59
  "release-it": "^19.2.4",
60
- "tsdown": "^0.19.0",
60
+ "tsdown": "^0.20.3",
61
61
  "typedoc": "^0.28.16",
62
62
  "typescript": "^5.9.3"
63
63
  },
@@ -65,16 +65,16 @@
65
65
  "@adonisjs/env": "^7.0.0-next.3",
66
66
  "@antfu/install-pkg": "^1.1.0",
67
67
  "@ast-grep/napi": "^0.40.5",
68
- "@poppinss/cliui": "^6.6.0",
68
+ "@poppinss/cliui": "^6.7.0",
69
69
  "@poppinss/hooks": "^7.3.0",
70
- "@poppinss/utils": "^7.0.0-next.5",
70
+ "@poppinss/utils": "^7.0.0-next.7",
71
71
  "chokidar": "^5.0.0",
72
72
  "dedent": "^1.7.1",
73
73
  "execa": "^9.6.1",
74
74
  "fast-glob": "^3.3.3",
75
75
  "fdir": "^6.5.0",
76
76
  "get-port": "^7.1.0",
77
- "get-tsconfig": "^4.13.0",
77
+ "get-tsconfig": "^4.13.6",
78
78
  "import-meta-resolve": "^4.2.0",
79
79
  "junk": "^4.0.1",
80
80
  "open": "^11.0.0",
@@ -1,72 +0,0 @@
1
- import { parseImports } from "parse-imports";
2
- async function findImport(code, importReference) {
3
- const importIdentifier = importReference.split(".")[0];
4
- for (const $import of await parseImports(code, {})) {
5
- if (!$import.importClause) continue;
6
- if (!$import.moduleSpecifier.value) continue;
7
- if ($import.importClause.default === importIdentifier) return {
8
- specifier: $import.moduleSpecifier.value,
9
- isConstant: $import.moduleSpecifier.isConstant,
10
- clause: {
11
- type: "default",
12
- value: importIdentifier
13
- }
14
- };
15
- if ($import.importClause.namespace === importIdentifier) return {
16
- specifier: $import.moduleSpecifier.value,
17
- isConstant: $import.moduleSpecifier.isConstant,
18
- clause: {
19
- type: "namespace",
20
- value: importIdentifier
21
- }
22
- };
23
- const namedImport = $import.importClause.named.find(({ binding }) => {
24
- return binding === importIdentifier;
25
- });
26
- if (namedImport) return {
27
- specifier: $import.moduleSpecifier.value,
28
- isConstant: $import.moduleSpecifier.isConstant,
29
- clause: {
30
- type: "named",
31
- value: namedImport.specifier,
32
- ...namedImport.binding !== namedImport.specifier ? { alias: namedImport.binding } : {}
33
- }
34
- };
35
- }
36
- return null;
37
- }
38
- function inspectClass(node) {
39
- return node.find({ rule: { kind: "class_declaration" } });
40
- }
41
- function inspectClassMethods(node) {
42
- return node.findAll({ rule: { kind: "method_definition" } });
43
- }
44
- function nodeToPlainText(node) {
45
- let out = [];
46
- function toText(one) {
47
- const children = one.children();
48
- if (!children.length) out.push(one.text());
49
- else children.forEach((child) => toText(child));
50
- }
51
- toText(node);
52
- return out.join("");
53
- }
54
- function inspectMethodArguments(node, methodCalls) {
55
- return node.findAll({ rule: { any: methodCalls.map((methodCall) => {
56
- return { pattern: {
57
- context: `${methodCall}($$$ARGUMENTS)`,
58
- selector: "call_expression"
59
- } };
60
- }) } }).flatMap((matchingExpression) => {
61
- return matchingExpression.findAll({ rule: { kind: "arguments" } });
62
- });
63
- }
64
- function searchValidatorDirectUsage(node) {
65
- return node.findAll({ rule: { pattern: {
66
- context: "$$$VALIDATOR.validate($$$)",
67
- selector: "call_expression"
68
- } } }).flatMap((expression) => {
69
- return expression.getMultipleMatches("VALIDATOR");
70
- });
71
- }
72
- export { nodeToPlainText as a, inspectMethodArguments as i, inspectClass as n, searchValidatorDirectUsage as o, inspectClassMethods as r, findImport as t };
@@ -1,246 +0,0 @@
1
- import { i as isRelative, m as debug_default, t as VirtualFileSystem } from "./virtual_file_system-bGeoWsK-.js";
2
- import { a as nodeToPlainText, i as inspectMethodArguments, n as inspectClass, o as searchValidatorDirectUsage, r as inspectClassMethods, t as findImport } from "./helpers-DDurYRsZ.js";
3
- import { cliui } from "@poppinss/cliui";
4
- import { fileURLToPath, pathToFileURL } from "node:url";
5
- import string from "@poppinss/utils/string";
6
- import { join, relative } from "node:path";
7
- import StringBuilder from "@poppinss/utils/string_builder";
8
- import { parseImports } from "parse-imports";
9
- import { resolve } from "import-meta-resolve";
10
- var PathsResolver = class {
11
- #appRoot;
12
- #resolvedPaths = {};
13
- #resolver = (specifier, parentPath) => resolve(specifier, parentPath);
14
- constructor(appRoot) {
15
- this.#appRoot = pathToFileURL(join(appRoot, "index.js")).href;
16
- }
17
- use(resolver) {
18
- this.#resolver = resolver;
19
- }
20
- resolve(specifier, rewriteAliasImportExtension = false) {
21
- if (isRelative(specifier)) throw new Error("Cannot resolve relative paths using PathsResolver");
22
- const cacheKey = specifier;
23
- const cached = this.#resolvedPaths[cacheKey];
24
- if (cached) return cached;
25
- let resolvedPath = fileURLToPath(this.#resolver(specifier, this.#appRoot));
26
- if (rewriteAliasImportExtension && specifier.startsWith("#") && resolvedPath.endsWith(".js")) resolvedPath = resolvedPath.replace(/\.js$/, ".ts");
27
- this.#resolvedPaths[cacheKey] = resolvedPath;
28
- return this.#resolvedPaths[cacheKey];
29
- }
30
- };
31
- async function extractValidators(appRoot, vfs, controller) {
32
- const root = await vfs.get(controller.path);
33
- const fileContents = root.text();
34
- const controllerClass = inspectClass(root);
35
- if (!controllerClass) {
36
- debug_default(`No class defined within the "%s"`, controller.import.specifier);
37
- return;
38
- }
39
- const method = inspectClassMethods(controllerClass).find((methodNode) => {
40
- return methodNode.find({ rule: { kind: "property_identifier" } })?.text() === controller.method;
41
- });
42
- if (!method) {
43
- debug_default(`Unable to find "%s" method in "%s"`, controller.method, controller.import.specifier);
44
- return;
45
- }
46
- const validationCalls = inspectMethodArguments(method, ["request.validateUsing", "vine.validate"]).map((node) => {
47
- const firstArg = node.find({ rule: { any: [{ kind: "identifier" }, { kind: "member_expression" }] } });
48
- if (!firstArg) return;
49
- return nodeToPlainText(firstArg);
50
- }).filter((node) => node !== void 0).concat(searchValidatorDirectUsage(method).map((node) => nodeToPlainText(node)));
51
- if (!validationCalls.length) {
52
- debug_default(`Unable to detect any validation calls in "%s.%s" method`, controller.import.specifier, controller.method);
53
- return;
54
- }
55
- const controllerName = controllerClass.find({ rule: { kind: "type_identifier" } }).text();
56
- const controllerURL = pathToFileURL(controller.path);
57
- return (await Promise.all(validationCalls.map(async (validationCall) => {
58
- if (validationCall.split(".")[0] === controllerName) return {
59
- name: validationCall,
60
- import: {
61
- specifier: controller.import.specifier,
62
- type: "default",
63
- value: controllerName
64
- }
65
- };
66
- const importCall = await findImport(fileContents, validationCall);
67
- if (!importCall) {
68
- debug_default("Unable to find import for \"%s\" used by \"%s.%s\" method", validationCall, controller.import.specifier, controller.method);
69
- return null;
70
- }
71
- return {
72
- name: validationCall,
73
- import: {
74
- specifier: isRelative(importCall.specifier) ? string.toUnixSlash(relative(appRoot, fileURLToPath(new URL(importCall.specifier, controllerURL)))) : importCall.specifier,
75
- type: importCall.clause.type,
76
- value: importCall.clause.value
77
- }
78
- };
79
- }))).filter((value) => !!value);
80
- }
81
- var RoutesScanner = class {
82
- #filter;
83
- #appRoot;
84
- #controllerRoutes = {};
85
- #scannedRoutes = [];
86
- #computeResponseTypes;
87
- #computeRequestTypes;
88
- #extractValidators;
89
- ui = cliui();
90
- pathsResolver;
91
- rules = {
92
- request: {},
93
- response: {}
94
- };
95
- constructor(appRoot, rulesCollection) {
96
- this.#appRoot = appRoot;
97
- this.pathsResolver = new PathsResolver(appRoot);
98
- rulesCollection.forEach((rules) => {
99
- Object.assign(this.rules.request, rules.request);
100
- Object.assign(this.rules.response, rules.response);
101
- });
102
- }
103
- #shouldSkipRoute(route) {
104
- if (this.#filter) return !this.#filter(route);
105
- return false;
106
- }
107
- #prepareRequestTypes(route) {
108
- if (!route.validators) return;
109
- return {
110
- type: route.validators.reduce((result, validator) => {
111
- const validatorExport = validator.import.type === "default" ? ".default" : validator.import.type === "named" ? `.${validator.import.value}` : "";
112
- const [, ...segments] = validator.name.split(".");
113
- const namespace = segments.map((segment) => `['${segment}']`).join("");
114
- result.push(`InferInput<(typeof import('${validator.import.specifier}')${validatorExport})${namespace}>`);
115
- return result;
116
- }, []).join("|"),
117
- imports: [`import { InferInput } from '@vinejs/vine/types'`]
118
- };
119
- }
120
- async #inspectControllerSpecifier(importExpression, method) {
121
- const importedModule = [...await parseImports(importExpression)].find(($import) => $import.isDynamicImport && $import.moduleSpecifier.value);
122
- if (!importedModule || importedModule.moduleSpecifier.type !== "package" || !importedModule.moduleSpecifier.value) return null;
123
- const specifier = importedModule.moduleSpecifier.value;
124
- const name = new StringBuilder(specifier.split("/").pop()).removeSuffix("Controller").pascalCase().suffix("Controller").toString();
125
- return {
126
- name,
127
- method: method ?? "handle",
128
- path: string.toUnixSlash(this.pathsResolver.resolve(specifier, true)),
129
- import: {
130
- specifier,
131
- type: "default",
132
- value: name
133
- }
134
- };
135
- }
136
- async #setResponse(route, controller) {
137
- route.response = await this.#computeResponseTypes?.(route, controller, this) ?? {
138
- type: `ReturnType<import('${controller.import.specifier}').default['${controller.method}']>`,
139
- imports: []
140
- };
141
- debug_default("computed route \"%s\" response %O", route.name, route.response);
142
- }
143
- async #setRequest(route, controller, vfs) {
144
- route.validators = await this.#extractValidators?.(route, controller, this) ?? await extractValidators(this.#appRoot, vfs, controller);
145
- debug_default("computed route \"%s\" validators %O", route.name, route.validators);
146
- route.request = await this.#computeRequestTypes?.(route, controller, this) ?? this.#prepareRequestTypes(route);
147
- debug_default("computed route \"%s\" request input %O", route.name, route.request);
148
- }
149
- #processRouteWithoutController(route) {
150
- if (!route.name) {
151
- debug_default(`skipping route "%s" as it does not have a name`, route.pattern);
152
- return;
153
- }
154
- const scannedRoute = {
155
- name: route.name,
156
- domain: route.domain,
157
- methods: route.methods,
158
- pattern: route.pattern,
159
- tokens: route.tokens,
160
- request: this.rules.request[route.name],
161
- response: this.rules.response[route.name]
162
- };
163
- debug_default("scanned route without controller %O", scannedRoute);
164
- this.#scannedRoutes.push(scannedRoute);
165
- }
166
- async #processRouteWithController(route, vfs) {
167
- if (!route.handler || !route.handler.importExpression) return this.#processRouteWithoutController(route);
168
- const controller = await this.#inspectControllerSpecifier(route.handler.importExpression, route.handler.method);
169
- if (!controller) return this.#processRouteWithoutController(route);
170
- debug_default("processing route \"%s\" with inspected controller %O", route.name, controller);
171
- route.name = route.name ?? new StringBuilder(controller.name).removeSuffix("Controller").snakeCase().suffix(`.${string.snakeCase(controller.method)}`).toString();
172
- if (this.#shouldSkipRoute(route)) return;
173
- const scannedRoute = {
174
- name: route.name,
175
- domain: route.domain,
176
- methods: route.methods,
177
- pattern: route.pattern,
178
- tokens: route.tokens,
179
- request: this.rules.request[route.name],
180
- response: this.rules.response[route.name],
181
- controller
182
- };
183
- debug_default("scanned route %O", scannedRoute);
184
- this.#scannedRoutes.push(scannedRoute);
185
- if (!scannedRoute.request || !scannedRoute.response) {
186
- debug_default("tracking controller for rescanning %O", scannedRoute);
187
- this.#controllerRoutes[controller.path] ??= [];
188
- this.#controllerRoutes[controller.path].push(scannedRoute);
189
- if (!scannedRoute.response) await this.#setResponse(scannedRoute, controller);
190
- if (!scannedRoute.request) await this.#setRequest(scannedRoute, controller, vfs);
191
- }
192
- }
193
- async #processRoute(route, vfs) {
194
- if (route.name && this.#shouldSkipRoute(route)) {
195
- debug_default("route skipped route: %O, rules: %O", route, this.rules);
196
- return;
197
- }
198
- if (typeof route.handler === "function") {
199
- this.#processRouteWithoutController(route);
200
- return;
201
- }
202
- await this.#processRouteWithController(route, vfs);
203
- }
204
- defineResponse(callback) {
205
- this.#computeResponseTypes = callback;
206
- return this;
207
- }
208
- defineRequest(callback) {
209
- this.#computeRequestTypes = callback;
210
- return this;
211
- }
212
- extractValidators(callback) {
213
- this.#extractValidators = callback;
214
- return this;
215
- }
216
- getScannedRoutes() {
217
- return this.#scannedRoutes;
218
- }
219
- getControllers() {
220
- return Object.keys(this.#controllerRoutes);
221
- }
222
- async invalidate(controllerPath) {
223
- const controllerRoutes = this.#controllerRoutes[controllerPath];
224
- if (!controllerRoutes || !controllerRoutes.length) {
225
- debug_default("\"%s\" controllers is not part of scanned controllers %O", controllerPath, this.#controllerRoutes);
226
- return false;
227
- }
228
- for (let scannedRoute of controllerRoutes) if (scannedRoute.controller) {
229
- debug_default("invalidating route %O", scannedRoute);
230
- const vfs = new VirtualFileSystem(this.#appRoot);
231
- await this.#setResponse(scannedRoute, scannedRoute.controller);
232
- await this.#setRequest(scannedRoute, scannedRoute.controller, vfs);
233
- }
234
- return true;
235
- }
236
- filter(filterFn) {
237
- this.#filter = filterFn;
238
- return this;
239
- }
240
- async scan(routes) {
241
- const vfs = new VirtualFileSystem(this.#appRoot);
242
- for (const route of routes) await this.#processRoute(route, vfs);
243
- vfs.invalidate();
244
- }
245
- };
246
- export { RoutesScanner as t };