@adonisjs/assembler 8.0.0-next.22 → 8.0.0-next.23
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/helpers-DDurYRsZ.js +72 -0
- package/build/index.js +916 -1688
- package/build/main-1eXSE5Xo.js +185 -0
- package/build/main-Byxt3AdL.js +240 -0
- package/build/src/code_scanners/routes_scanner/main.js +4 -8
- package/build/src/code_transformer/main.js +276 -622
- package/build/src/debug.d.ts +1 -1
- package/build/src/helpers.js +2 -16
- package/build/src/index_generator/main.js +3 -7
- package/build/src/types/main.js +1 -0
- package/build/virtual_file_system-DM1KRNbk.js +283 -0
- package/package.json +27 -24
- package/build/chunk-JFBQ4OEM.js +0 -434
- package/build/chunk-NAASGAFO.js +0 -478
- package/build/chunk-NR7VMFWO.js +0 -468
- package/build/chunk-TIKQQRMX.js +0 -116
package/build/index.js
CHANGED
|
@@ -1,156 +1,91 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
RoutesScanner
|
|
7
|
-
} from "./chunk-NAASGAFO.js";
|
|
8
|
-
import "./chunk-TIKQQRMX.js";
|
|
9
|
-
import {
|
|
10
|
-
VirtualFileSystem,
|
|
11
|
-
copyFiles,
|
|
12
|
-
debug_default,
|
|
13
|
-
getPort,
|
|
14
|
-
loadHooks,
|
|
15
|
-
memoize,
|
|
16
|
-
parseConfig,
|
|
17
|
-
readTsConfig,
|
|
18
|
-
run,
|
|
19
|
-
runNode,
|
|
20
|
-
throttle,
|
|
21
|
-
watch
|
|
22
|
-
} from "./chunk-JFBQ4OEM.js";
|
|
23
|
-
|
|
24
|
-
// src/bundler.ts
|
|
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-DM1KRNbk.js";
|
|
2
|
+
import { n as FileBuffer, t as IndexGenerator } from "./main-1eXSE5Xo.js";
|
|
3
|
+
import { t as RoutesScanner } from "./main-Byxt3AdL.js";
|
|
4
|
+
import "./helpers-DDurYRsZ.js";
|
|
25
5
|
import dedent from "dedent";
|
|
26
|
-
import fs from "fs/promises";
|
|
6
|
+
import fs, { readFile, unlink } from "node:fs/promises";
|
|
27
7
|
import { cliui } from "@poppinss/cliui";
|
|
28
|
-
import { fileURLToPath } from "url";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
29
9
|
import string from "@poppinss/utils/string";
|
|
30
|
-
import { join, relative } from "path/posix";
|
|
10
|
+
import { join, relative } from "node:path/posix";
|
|
31
11
|
import { detectPackageManager } from "@antfu/install-pkg";
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
12
|
+
import picomatch from "picomatch";
|
|
13
|
+
import prettyHrtime from "pretty-hrtime";
|
|
14
|
+
import { RuntimeException } from "@poppinss/utils/exception";
|
|
15
|
+
const SUPPORTED_PACKAGE_MANAGERS = {
|
|
16
|
+
"npm": {
|
|
17
|
+
packageManagerFiles: ["package-lock.json"],
|
|
18
|
+
installCommand: "npm ci --omit=\"dev\""
|
|
19
|
+
},
|
|
20
|
+
"yarn": {
|
|
21
|
+
packageManagerFiles: ["yarn.lock"],
|
|
22
|
+
installCommand: "yarn install --production"
|
|
23
|
+
},
|
|
24
|
+
"yarn@berry": {
|
|
25
|
+
packageManagerFiles: [
|
|
26
|
+
"yarn.lock",
|
|
27
|
+
".yarn/**/*",
|
|
28
|
+
".yarnrc.yml"
|
|
29
|
+
],
|
|
30
|
+
installCommand: "yarn workspaces focus --production"
|
|
31
|
+
},
|
|
32
|
+
"pnpm": {
|
|
33
|
+
packageManagerFiles: ["pnpm-lock.yaml"],
|
|
34
|
+
installCommand: "pnpm i --prod"
|
|
35
|
+
},
|
|
36
|
+
"bun": {
|
|
37
|
+
packageManagerFiles: ["bun.lockb"],
|
|
38
|
+
installCommand: "bun install --production"
|
|
39
|
+
}
|
|
53
40
|
};
|
|
54
41
|
var Bundler = class {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Cleans up the build directory
|
|
105
|
-
*/
|
|
106
|
-
async #cleanupBuildDirectory(outDir) {
|
|
107
|
-
await fs.rm(outDir, { recursive: true, force: true, maxRetries: 5 });
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Runs tsc command to build the source.
|
|
111
|
-
*/
|
|
112
|
-
async #runTsc(outDir) {
|
|
113
|
-
try {
|
|
114
|
-
await run(this.cwd, {
|
|
115
|
-
stdio: "inherit",
|
|
116
|
-
script: "tsc",
|
|
117
|
-
scriptArgs: ["--outDir", outDir]
|
|
118
|
-
});
|
|
119
|
-
return true;
|
|
120
|
-
} catch {
|
|
121
|
-
return false;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
/**
|
|
125
|
-
* Copy meta files to the output directory
|
|
126
|
-
*/
|
|
127
|
-
async #copyMetaFiles(outDir, additionalFilesToCopy) {
|
|
128
|
-
const metaFiles = (this.options.metaFiles || []).map((file) => file.pattern).concat(additionalFilesToCopy);
|
|
129
|
-
await copyFiles(metaFiles, this.cwdPath, outDir);
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Detect the package manager used by the project
|
|
133
|
-
*/
|
|
134
|
-
async #detectPackageManager() {
|
|
135
|
-
const pkgManager = await detectPackageManager(this.cwdPath);
|
|
136
|
-
if (pkgManager === "deno") {
|
|
137
|
-
return "npm";
|
|
138
|
-
}
|
|
139
|
-
if (pkgManager === "pnpm@6") {
|
|
140
|
-
return "pnpm";
|
|
141
|
-
}
|
|
142
|
-
return pkgManager;
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Rewrite the ace file since the original one
|
|
146
|
-
* is importing ts-node which is not installed
|
|
147
|
-
* in a production environment.
|
|
148
|
-
*/
|
|
149
|
-
async #createAceFile(outDir) {
|
|
150
|
-
const aceFileLocation = join(outDir, "ace.js");
|
|
151
|
-
const aceFileContent = dedent(
|
|
152
|
-
/* JavaScript */
|
|
153
|
-
`
|
|
42
|
+
#ts;
|
|
43
|
+
#hooks;
|
|
44
|
+
#indexGenerator;
|
|
45
|
+
ui = cliui();
|
|
46
|
+
cwd;
|
|
47
|
+
cwdPath;
|
|
48
|
+
options;
|
|
49
|
+
constructor(cwd, ts, options) {
|
|
50
|
+
this.cwd = cwd;
|
|
51
|
+
this.options = options;
|
|
52
|
+
this.cwdPath = string.toUnixSlash(fileURLToPath(this.cwd));
|
|
53
|
+
this.#ts = ts;
|
|
54
|
+
}
|
|
55
|
+
#getRelativeName(filePath) {
|
|
56
|
+
return string.toUnixSlash(relative(this.cwdPath, filePath));
|
|
57
|
+
}
|
|
58
|
+
async #cleanupBuildDirectory(outDir) {
|
|
59
|
+
await fs.rm(outDir, {
|
|
60
|
+
recursive: true,
|
|
61
|
+
force: true,
|
|
62
|
+
maxRetries: 5
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
async #runTsc(outDir) {
|
|
66
|
+
try {
|
|
67
|
+
await run(this.cwd, {
|
|
68
|
+
stdio: "inherit",
|
|
69
|
+
script: "tsc",
|
|
70
|
+
scriptArgs: ["--outDir", outDir]
|
|
71
|
+
});
|
|
72
|
+
return true;
|
|
73
|
+
} catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async #copyMetaFiles(outDir, additionalFilesToCopy) {
|
|
78
|
+
await copyFiles((this.options.metaFiles || []).map((file) => file.pattern).concat(additionalFilesToCopy), this.cwdPath, outDir);
|
|
79
|
+
}
|
|
80
|
+
async #detectPackageManager() {
|
|
81
|
+
const pkgManager = await detectPackageManager(this.cwdPath);
|
|
82
|
+
if (pkgManager === "deno") return "npm";
|
|
83
|
+
if (pkgManager === "pnpm@6") return "pnpm";
|
|
84
|
+
return pkgManager;
|
|
85
|
+
}
|
|
86
|
+
async #createAceFile(outDir) {
|
|
87
|
+
const aceFileLocation = join(outDir, "ace.js");
|
|
88
|
+
const aceFileContent = dedent(`
|
|
154
89
|
/**
|
|
155
90
|
* This file is auto-generated by the build process.
|
|
156
91
|
* If you had any custom code inside this file, then
|
|
@@ -158,1552 +93,845 @@ var Bundler = class {
|
|
|
158
93
|
*/
|
|
159
94
|
|
|
160
95
|
await import('./bin/console.js')
|
|
161
|
-
`
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
)
|
|
206
|
-
);
|
|
207
|
-
this.ui.logger.logError(instructions.prepare());
|
|
208
|
-
return false;
|
|
209
|
-
}
|
|
210
|
-
const pkgFiles = [
|
|
211
|
-
"package.json",
|
|
212
|
-
...SUPPORTED_PACKAGE_MANAGERS[this.packageManager].packageManagerFiles
|
|
213
|
-
];
|
|
214
|
-
this.ui.logger.info("copying meta files to the output directory");
|
|
215
|
-
await this.#copyMetaFiles(outDir, pkgFiles);
|
|
216
|
-
this.ui.logger.success("build completed");
|
|
217
|
-
this.ui.logger.log("");
|
|
218
|
-
const displayMessage = this.ui.instructions().heading("Run the following commands to start the server in production");
|
|
219
|
-
await this.#hooks.runner("buildFinished").run(this, displayMessage);
|
|
220
|
-
displayMessage.add(this.ui.colors.cyan(`cd ${this.#getRelativeName(outDir)}`)).add(this.ui.colors.cyan(SUPPORTED_PACKAGE_MANAGERS[this.packageManager].installCommand)).add(this.ui.colors.cyan("node bin/server.js")).render();
|
|
221
|
-
return true;
|
|
222
|
-
}
|
|
96
|
+
`);
|
|
97
|
+
await fs.writeFile(aceFileLocation, aceFileContent);
|
|
98
|
+
this.ui.logger.info("created ace file", { suffix: this.#getRelativeName(aceFileLocation) });
|
|
99
|
+
}
|
|
100
|
+
async bundle(stopOnError = true, client) {
|
|
101
|
+
this.packageManager = client ?? await this.#detectPackageManager() ?? "npm";
|
|
102
|
+
const config = parseConfig(this.cwd, this.#ts);
|
|
103
|
+
if (!config) return false;
|
|
104
|
+
this.ui.logger.info("loading hooks...");
|
|
105
|
+
this.#hooks = await loadHooks(this.options.hooks, [
|
|
106
|
+
"init",
|
|
107
|
+
"buildStarting",
|
|
108
|
+
"buildFinished"
|
|
109
|
+
]);
|
|
110
|
+
this.#indexGenerator = new IndexGenerator(this.cwdPath, this.ui.logger);
|
|
111
|
+
await this.#hooks.runner("init").run(this, this.#indexGenerator);
|
|
112
|
+
this.#hooks.clear("init");
|
|
113
|
+
this.ui.logger.info("generating indexes...");
|
|
114
|
+
await this.#indexGenerator.generate();
|
|
115
|
+
const outDir = config.options.outDir || fileURLToPath(new URL("build/", this.cwd));
|
|
116
|
+
this.ui.logger.info("cleaning up output directory", { suffix: this.#getRelativeName(outDir) });
|
|
117
|
+
await this.#cleanupBuildDirectory(outDir);
|
|
118
|
+
await this.#hooks.runner("buildStarting").run(this);
|
|
119
|
+
this.ui.logger.info("compiling typescript source", { suffix: "tsc" });
|
|
120
|
+
const buildCompleted = await this.#runTsc(outDir);
|
|
121
|
+
await this.#createAceFile(outDir);
|
|
122
|
+
if (!buildCompleted && stopOnError) {
|
|
123
|
+
await this.#cleanupBuildDirectory(outDir);
|
|
124
|
+
const instructions = this.ui.sticker().fullScreen().drawBorder((borderChar, colors) => colors.red(borderChar));
|
|
125
|
+
instructions.add(this.ui.colors.red("Cannot complete the build process as there are TypeScript errors."));
|
|
126
|
+
instructions.add(this.ui.colors.red("Use \"--ignore-ts-errors\" flag to ignore TypeScript errors and continue the build."));
|
|
127
|
+
this.ui.logger.logError(instructions.prepare());
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
const pkgFiles = ["package.json", ...SUPPORTED_PACKAGE_MANAGERS[this.packageManager].packageManagerFiles];
|
|
131
|
+
this.ui.logger.info("copying meta files to the output directory");
|
|
132
|
+
await this.#copyMetaFiles(outDir, pkgFiles);
|
|
133
|
+
this.ui.logger.success("build completed");
|
|
134
|
+
this.ui.logger.log("");
|
|
135
|
+
const displayMessage = this.ui.instructions().heading("Run the following commands to start the server in production");
|
|
136
|
+
await this.#hooks.runner("buildFinished").run(this, displayMessage);
|
|
137
|
+
displayMessage.add(this.ui.colors.cyan(`cd ${this.#getRelativeName(outDir)}`)).add(this.ui.colors.cyan(SUPPORTED_PACKAGE_MANAGERS[this.packageManager].installCommand)).add(this.ui.colors.cyan("node bin/server.js")).render();
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
223
140
|
};
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
import { relative as relative2 } from "path/posix";
|
|
237
|
-
import string2 from "@poppinss/utils/string";
|
|
238
|
-
var DEFAULT_INCLUDES = ["**/*"];
|
|
239
|
-
var ALWAYS_EXCLUDE = [".git/**", "coverage/**", ".github/**", ".adonisjs/**"];
|
|
240
|
-
var DEFAULT_EXCLUDES = ["node_modules/**", "bower_components/**", "jspm_packages/**"];
|
|
141
|
+
const DEFAULT_INCLUDES = ["**/*"];
|
|
142
|
+
const ALWAYS_EXCLUDE = [
|
|
143
|
+
".git/**",
|
|
144
|
+
"coverage/**",
|
|
145
|
+
".github/**",
|
|
146
|
+
".adonisjs/**"
|
|
147
|
+
];
|
|
148
|
+
const DEFAULT_EXCLUDES = [
|
|
149
|
+
"node_modules/**",
|
|
150
|
+
"bower_components/**",
|
|
151
|
+
"jspm_packages/**"
|
|
152
|
+
];
|
|
241
153
|
var FileSystem = class {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
unixAbsolutePath: absolutePath
|
|
354
|
-
};
|
|
355
|
-
}
|
|
356
|
-
debug_default('ignored file "%s"', relativePath);
|
|
357
|
-
return null;
|
|
358
|
-
});
|
|
359
|
-
/**
|
|
360
|
-
* Determines if a directory should be watched by the file watcher.
|
|
361
|
-
*
|
|
362
|
-
* This method checks if a directory should be monitored for file changes
|
|
363
|
-
* based on the TypeScript configuration includes/excludes patterns.
|
|
364
|
-
* Results are memoized for performance. Chokidar sends absolute Unix paths.
|
|
365
|
-
*
|
|
366
|
-
* Note: Use shouldWatchFile for files and this method for directories only.
|
|
367
|
-
*
|
|
368
|
-
* @param absolutePath - The absolute Unix path to the directory
|
|
369
|
-
* @returns True if the directory should be watched
|
|
370
|
-
*
|
|
371
|
-
* @example
|
|
372
|
-
* const shouldWatch = fileSystem.shouldWatchDirectory('/project/app/controllers')
|
|
373
|
-
* console.log(shouldWatch) // true
|
|
374
|
-
*/
|
|
375
|
-
shouldWatchDirectory = memoize((absolutePath) => {
|
|
376
|
-
if (absolutePath === this.#cwd) {
|
|
377
|
-
debug_default("watching project root");
|
|
378
|
-
return true;
|
|
379
|
-
}
|
|
380
|
-
const relativePath = relative2(this.#cwd, absolutePath);
|
|
381
|
-
if (this.#isExcluded(relativePath)) {
|
|
382
|
-
debug_default('watching "%s"', absolutePath);
|
|
383
|
-
return false;
|
|
384
|
-
}
|
|
385
|
-
return true;
|
|
386
|
-
});
|
|
387
|
-
/**
|
|
388
|
-
* Create a new FileSystem instance
|
|
389
|
-
*
|
|
390
|
-
* @param cwd - The current working directory URL or string path
|
|
391
|
-
* @param tsConfig - Parsed TypeScript configuration
|
|
392
|
-
* @param rcFile - AdonisJS RC file configuration
|
|
393
|
-
*/
|
|
394
|
-
constructor(cwd, tsConfig, rcFile) {
|
|
395
|
-
this.#cwd = cwd;
|
|
396
|
-
this.#tsConfig = tsConfig;
|
|
397
|
-
const files = tsConfig.config.files ?? [];
|
|
398
|
-
const metaFiles = rcFile.metaFiles ?? [];
|
|
399
|
-
const testSuites = rcFile.suites ?? [];
|
|
400
|
-
const outDir = tsConfig.config.compilerOptions?.outDir;
|
|
401
|
-
for (const file of files) {
|
|
402
|
-
this.#scannedTypeScriptFiles.add(string2.toUnixSlash(file));
|
|
403
|
-
}
|
|
404
|
-
this.#includes = tsConfig.config.include || DEFAULT_INCLUDES;
|
|
405
|
-
this.#excludes = ALWAYS_EXCLUDE.concat(
|
|
406
|
-
tsConfig.config.exclude || (outDir ? DEFAULT_EXCLUDES.concat(outDir) : DEFAULT_EXCLUDES)
|
|
407
|
-
);
|
|
408
|
-
const metaFilesWithReloads = [];
|
|
409
|
-
const metaFilesWithoutReloads = [];
|
|
410
|
-
for (const file of metaFiles) {
|
|
411
|
-
if (file.reloadServer) {
|
|
412
|
-
metaFilesWithReloads.push(file.pattern);
|
|
413
|
-
} else {
|
|
414
|
-
metaFilesWithoutReloads.push(file.pattern);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
const testFilePatterns = testSuites.flatMap((suite) => suite.files);
|
|
418
|
-
const picomatcchOptions = { cwd: this.#cwd };
|
|
419
|
-
this.#isMetaFileWithReloadsEnabled = picomatch(metaFilesWithReloads, picomatcchOptions);
|
|
420
|
-
this.#isMetaFileWithReloadsDisabled = picomatch(metaFilesWithoutReloads, picomatcchOptions);
|
|
421
|
-
this.#isTestFile = picomatch(testFilePatterns, picomatcchOptions);
|
|
422
|
-
this.#isIncluded = picomatch(this.#includes, picomatcchOptions);
|
|
423
|
-
this.#isExcluded = picomatch(this.#excludes, picomatcchOptions);
|
|
424
|
-
debug_default("initiating file system %O", {
|
|
425
|
-
includes: this.#includes,
|
|
426
|
-
excludes: this.#excludes,
|
|
427
|
-
outDir,
|
|
428
|
-
files,
|
|
429
|
-
metaFiles,
|
|
430
|
-
testSuites
|
|
431
|
-
});
|
|
432
|
-
}
|
|
433
|
-
/**
|
|
434
|
-
* Determines if a file path represents a script file based on TypeScript configuration.
|
|
435
|
-
*
|
|
436
|
-
* Script files are those that can be processed by the TypeScript compiler:
|
|
437
|
-
* - Files ending with ".ts" or ".tsx" (excluding ".d.ts" declaration files)
|
|
438
|
-
* - Files ending with ".js" when "allowJs" option is enabled in tsconfig
|
|
439
|
-
* - Files ending with ".json" when "resolveJsonModule" option is enabled in tsconfig
|
|
440
|
-
*
|
|
441
|
-
* @param relativePath - The relative file path to check
|
|
442
|
-
* @returns True if the file is a script file
|
|
443
|
-
*/
|
|
444
|
-
#isScriptFile(relativePath) {
|
|
445
|
-
if ((relativePath.endsWith(".ts") || relativePath.endsWith(".tsx")) && !relativePath.endsWith(".d.ts")) {
|
|
446
|
-
return true;
|
|
447
|
-
}
|
|
448
|
-
if (this.#tsConfig.config.compilerOptions?.allowJs && relativePath.endsWith(".js")) {
|
|
449
|
-
return true;
|
|
450
|
-
}
|
|
451
|
-
if (this.#tsConfig.config.compilerOptions?.resolveJsonModule && relativePath.endsWith(".json")) {
|
|
452
|
-
return true;
|
|
453
|
-
}
|
|
454
|
-
return false;
|
|
455
|
-
}
|
|
456
|
-
/**
|
|
457
|
-
* Checks if a file path is part of the backend TypeScript project.
|
|
458
|
-
*
|
|
459
|
-
* Uses TypeScript configuration "includes", "excludes", and "files" paths
|
|
460
|
-
* to determine if a file should be considered part of the project compilation.
|
|
461
|
-
*
|
|
462
|
-
* @param relativePath - The relative file path to check
|
|
463
|
-
* @returns True if the file is part of the backend project
|
|
464
|
-
*/
|
|
465
|
-
#isPartOfBackendProject(relativePath) {
|
|
466
|
-
if (this.#isExcluded(relativePath)) {
|
|
467
|
-
debug_default('excluded by tsconfig "%s"', relativePath);
|
|
468
|
-
return false;
|
|
469
|
-
}
|
|
470
|
-
if (this.#isIncluded(relativePath)) {
|
|
471
|
-
debug_default('included by tsconfig "%s"', relativePath);
|
|
472
|
-
return true;
|
|
473
|
-
}
|
|
474
|
-
return false;
|
|
475
|
-
}
|
|
476
|
-
/**
|
|
477
|
-
* Returns true if the file should be watched. Chokidar sends
|
|
478
|
-
* absolute unix paths to the ignored callback.
|
|
479
|
-
*
|
|
480
|
-
* You must use "shouldWatchDirectory" method for directories and call
|
|
481
|
-
* this method for files only.
|
|
482
|
-
*
|
|
483
|
-
* @param absolutePath - The absolute path to the file
|
|
484
|
-
* @returns True if the file should be watched
|
|
485
|
-
*/
|
|
486
|
-
shouldWatchFile(absolutePath) {
|
|
487
|
-
return this.inspect(absolutePath) !== null;
|
|
488
|
-
}
|
|
154
|
+
#cwd;
|
|
155
|
+
#tsConfig;
|
|
156
|
+
#scannedTypeScriptFiles = /* @__PURE__ */ new Set();
|
|
157
|
+
#isIncluded;
|
|
158
|
+
#isExcluded;
|
|
159
|
+
#isMetaFileWithReloadsEnabled;
|
|
160
|
+
#isMetaFileWithReloadsDisabled;
|
|
161
|
+
#isTestFile;
|
|
162
|
+
#includes;
|
|
163
|
+
#excludes;
|
|
164
|
+
get includes() {
|
|
165
|
+
return this.#includes;
|
|
166
|
+
}
|
|
167
|
+
get excludes() {
|
|
168
|
+
return this.#excludes;
|
|
169
|
+
}
|
|
170
|
+
inspect = memoize((absolutePath, relativePath) => {
|
|
171
|
+
relativePath = relativePath ?? relative(this.#cwd, absolutePath);
|
|
172
|
+
if (this.#isScriptFile(relativePath) && (this.#scannedTypeScriptFiles.has(absolutePath) || this.#isPartOfBackendProject(relativePath))) {
|
|
173
|
+
debug_default("backend project file \"%s\"", relativePath);
|
|
174
|
+
const isTestFile = this.#isTestFile(relativePath);
|
|
175
|
+
return {
|
|
176
|
+
fileType: isTestFile ? "test" : "script",
|
|
177
|
+
reloadServer: !isTestFile,
|
|
178
|
+
unixRelativePath: relativePath,
|
|
179
|
+
unixAbsolutePath: absolutePath
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
if (this.#isMetaFileWithReloadsEnabled(relativePath)) {
|
|
183
|
+
debug_default("meta file \"%s\"", relativePath);
|
|
184
|
+
return {
|
|
185
|
+
fileType: "meta",
|
|
186
|
+
reloadServer: true,
|
|
187
|
+
unixRelativePath: relativePath,
|
|
188
|
+
unixAbsolutePath: absolutePath
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
if (this.#isMetaFileWithReloadsDisabled(relativePath)) {
|
|
192
|
+
debug_default("meta file \"%s\"", relativePath);
|
|
193
|
+
return {
|
|
194
|
+
fileType: "meta",
|
|
195
|
+
reloadServer: false,
|
|
196
|
+
unixRelativePath: relativePath,
|
|
197
|
+
unixAbsolutePath: absolutePath
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
debug_default("ignored file \"%s\"", relativePath);
|
|
201
|
+
return null;
|
|
202
|
+
});
|
|
203
|
+
shouldWatchDirectory = memoize((absolutePath) => {
|
|
204
|
+
if (absolutePath === this.#cwd) {
|
|
205
|
+
debug_default("watching project root");
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
const relativePath = relative(this.#cwd, absolutePath);
|
|
209
|
+
if (this.#isExcluded(relativePath)) {
|
|
210
|
+
debug_default("watching \"%s\"", absolutePath);
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
return true;
|
|
214
|
+
});
|
|
215
|
+
constructor(cwd, tsConfig, rcFile) {
|
|
216
|
+
this.#cwd = cwd;
|
|
217
|
+
this.#tsConfig = tsConfig;
|
|
218
|
+
const files = tsConfig.config.files ?? [];
|
|
219
|
+
const metaFiles = rcFile.metaFiles ?? [];
|
|
220
|
+
const testSuites = rcFile.suites ?? [];
|
|
221
|
+
const outDir = tsConfig.config.compilerOptions?.outDir;
|
|
222
|
+
for (const file of files) this.#scannedTypeScriptFiles.add(string.toUnixSlash(file));
|
|
223
|
+
this.#includes = tsConfig.config.include || DEFAULT_INCLUDES;
|
|
224
|
+
this.#excludes = ALWAYS_EXCLUDE.concat(tsConfig.config.exclude || (outDir ? DEFAULT_EXCLUDES.concat(outDir) : DEFAULT_EXCLUDES));
|
|
225
|
+
const metaFilesWithReloads = [];
|
|
226
|
+
const metaFilesWithoutReloads = [];
|
|
227
|
+
for (const file of metaFiles) if (file.reloadServer) metaFilesWithReloads.push(file.pattern);
|
|
228
|
+
else metaFilesWithoutReloads.push(file.pattern);
|
|
229
|
+
const testFilePatterns = testSuites.flatMap((suite) => suite.files);
|
|
230
|
+
const picomatcchOptions = { cwd: this.#cwd };
|
|
231
|
+
this.#isMetaFileWithReloadsEnabled = picomatch(metaFilesWithReloads, picomatcchOptions);
|
|
232
|
+
this.#isMetaFileWithReloadsDisabled = picomatch(metaFilesWithoutReloads, picomatcchOptions);
|
|
233
|
+
this.#isTestFile = picomatch(testFilePatterns, picomatcchOptions);
|
|
234
|
+
this.#isIncluded = picomatch(this.#includes, picomatcchOptions);
|
|
235
|
+
this.#isExcluded = picomatch(this.#excludes, picomatcchOptions);
|
|
236
|
+
debug_default("initiating file system %O", {
|
|
237
|
+
includes: this.#includes,
|
|
238
|
+
excludes: this.#excludes,
|
|
239
|
+
outDir,
|
|
240
|
+
files,
|
|
241
|
+
metaFiles,
|
|
242
|
+
testSuites
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
#isScriptFile(relativePath) {
|
|
246
|
+
if ((relativePath.endsWith(".ts") || relativePath.endsWith(".tsx")) && !relativePath.endsWith(".d.ts")) return true;
|
|
247
|
+
if (this.#tsConfig.config.compilerOptions?.allowJs && relativePath.endsWith(".js")) return true;
|
|
248
|
+
if (this.#tsConfig.config.compilerOptions?.resolveJsonModule && relativePath.endsWith(".json")) return true;
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
#isPartOfBackendProject(relativePath) {
|
|
252
|
+
if (this.#isExcluded(relativePath)) {
|
|
253
|
+
debug_default("excluded by tsconfig \"%s\"", relativePath);
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
if (this.#isIncluded(relativePath)) {
|
|
257
|
+
debug_default("included by tsconfig \"%s\"", relativePath);
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
shouldWatchFile(absolutePath) {
|
|
263
|
+
return this.inspect(absolutePath) !== null;
|
|
264
|
+
}
|
|
489
265
|
};
|
|
490
|
-
|
|
491
|
-
// src/shortcuts_manager.ts
|
|
492
266
|
var ShortcutsManager = class {
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
*
|
|
564
|
-
* This method enables raw mode on stdin to capture individual keypresses
|
|
565
|
-
* and sets up the event listener for handling keyboard input. Only works
|
|
566
|
-
* in TTY environments.
|
|
567
|
-
*/
|
|
568
|
-
setup() {
|
|
569
|
-
if (!process.stdin.isTTY) {
|
|
570
|
-
return;
|
|
571
|
-
}
|
|
572
|
-
process.stdin.setRawMode(true);
|
|
573
|
-
this.#keyPressHandler = (data) => this.#handleKeyPress(data.toString());
|
|
574
|
-
process.stdin.on("data", this.#keyPressHandler);
|
|
575
|
-
}
|
|
576
|
-
/**
|
|
577
|
-
* Handle key press events and execute corresponding shortcuts
|
|
578
|
-
*
|
|
579
|
-
* Processes individual key presses and matches them against registered
|
|
580
|
-
* shortcuts. Also handles special key combinations like Ctrl+C and Ctrl+D.
|
|
581
|
-
*
|
|
582
|
-
* @param key - The pressed key as a string
|
|
583
|
-
*/
|
|
584
|
-
#handleKeyPress(key) {
|
|
585
|
-
if (key === "" || key === "") {
|
|
586
|
-
return this.#callbacks.onQuit();
|
|
587
|
-
}
|
|
588
|
-
const shortcut = this.#shortcuts.find((s) => s.key === key);
|
|
589
|
-
if (shortcut) {
|
|
590
|
-
shortcut.handler();
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
/**
|
|
594
|
-
* Handle opening browser with the configured server URL
|
|
595
|
-
*
|
|
596
|
-
* Uses the 'open' package to launch the default browser and navigate
|
|
597
|
-
* to the development server URL.
|
|
598
|
-
*/
|
|
599
|
-
async #handleOpenBrowser() {
|
|
600
|
-
this.#logger.log("");
|
|
601
|
-
this.#logger.info(`Opening ${this.#serverUrl}...`);
|
|
602
|
-
const { default: open } = await import("open");
|
|
603
|
-
open(this.#serverUrl);
|
|
604
|
-
}
|
|
605
|
-
/**
|
|
606
|
-
* Show available keyboard shortcuts in the console
|
|
607
|
-
*
|
|
608
|
-
* Displays a formatted list of all available keyboard shortcuts
|
|
609
|
-
* and their descriptions to help users understand what actions
|
|
610
|
-
* are available.
|
|
611
|
-
*/
|
|
612
|
-
showHelp() {
|
|
613
|
-
this.#logger.log("");
|
|
614
|
-
this.#logger.log("Available shortcuts:");
|
|
615
|
-
this.#shortcuts.forEach(({ key, description }) => this.#logger.log(`\xB7 ${key}: ${description}`));
|
|
616
|
-
}
|
|
617
|
-
/**
|
|
618
|
-
* Cleanup keyboard shortcuts and restore terminal state
|
|
619
|
-
*
|
|
620
|
-
* Disables raw mode on stdin, removes event listeners, and restores
|
|
621
|
-
* the terminal to its normal state. Should be called when shutting down
|
|
622
|
-
* the development server.
|
|
623
|
-
*/
|
|
624
|
-
cleanup() {
|
|
625
|
-
if (!process.stdin.isTTY) {
|
|
626
|
-
return;
|
|
627
|
-
}
|
|
628
|
-
process.stdin.setRawMode(false);
|
|
629
|
-
process.stdin.pause();
|
|
630
|
-
process.stdin.unref();
|
|
631
|
-
process.stdin.removeListener("data", this.#keyPressHandler);
|
|
632
|
-
this.#keyPressHandler = void 0;
|
|
633
|
-
}
|
|
267
|
+
#logger;
|
|
268
|
+
#callbacks;
|
|
269
|
+
#serverUrl;
|
|
270
|
+
#keyPressHandler;
|
|
271
|
+
#shortcuts = [
|
|
272
|
+
{
|
|
273
|
+
key: "r",
|
|
274
|
+
description: "restart server",
|
|
275
|
+
handler: () => {
|
|
276
|
+
this.#logger.log("");
|
|
277
|
+
this.#logger.info("Manual restart triggered...");
|
|
278
|
+
this.#callbacks.onRestart();
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
key: "c",
|
|
283
|
+
description: "clear console",
|
|
284
|
+
handler: () => {
|
|
285
|
+
this.#callbacks.onClear();
|
|
286
|
+
this.#logger.info("Console cleared");
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
key: "o",
|
|
291
|
+
description: "open in browser",
|
|
292
|
+
handler: () => this.#handleOpenBrowser()
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
key: "h",
|
|
296
|
+
description: "show this help",
|
|
297
|
+
handler: () => this.showHelp()
|
|
298
|
+
}
|
|
299
|
+
];
|
|
300
|
+
constructor(options) {
|
|
301
|
+
this.#logger = options.logger;
|
|
302
|
+
this.#callbacks = options.callbacks;
|
|
303
|
+
}
|
|
304
|
+
setServerUrl(url) {
|
|
305
|
+
this.#serverUrl = url;
|
|
306
|
+
}
|
|
307
|
+
setup() {
|
|
308
|
+
if (!process.stdin.isTTY) return;
|
|
309
|
+
process.stdin.setRawMode(true);
|
|
310
|
+
this.#keyPressHandler = (data) => this.#handleKeyPress(data.toString());
|
|
311
|
+
process.stdin.on("data", this.#keyPressHandler);
|
|
312
|
+
}
|
|
313
|
+
#handleKeyPress(key) {
|
|
314
|
+
if (key === "" || key === "") return this.#callbacks.onQuit();
|
|
315
|
+
const shortcut = this.#shortcuts.find((s) => s.key === key);
|
|
316
|
+
if (shortcut) shortcut.handler();
|
|
317
|
+
}
|
|
318
|
+
async #handleOpenBrowser() {
|
|
319
|
+
this.#logger.log("");
|
|
320
|
+
this.#logger.info(`Opening ${this.#serverUrl}...`);
|
|
321
|
+
const { default: open } = await import("open");
|
|
322
|
+
open(this.#serverUrl);
|
|
323
|
+
}
|
|
324
|
+
showHelp() {
|
|
325
|
+
this.#logger.log("");
|
|
326
|
+
this.#logger.log("Available shortcuts:");
|
|
327
|
+
this.#shortcuts.forEach(({ key, description }) => this.#logger.log(`· ${key}: ${description}`));
|
|
328
|
+
}
|
|
329
|
+
cleanup() {
|
|
330
|
+
if (!process.stdin.isTTY) return;
|
|
331
|
+
process.stdin.setRawMode(false);
|
|
332
|
+
process.stdin.pause();
|
|
333
|
+
process.stdin.unref();
|
|
334
|
+
process.stdin.removeListener("data", this.#keyPressHandler);
|
|
335
|
+
this.#keyPressHandler = void 0;
|
|
336
|
+
}
|
|
634
337
|
};
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
* processes routes for each domain.
|
|
996
|
-
*
|
|
997
|
-
* @param routesList - Routes organized by domain
|
|
998
|
-
*
|
|
999
|
-
* @example
|
|
1000
|
-
* await devServer.#processRoutes({
|
|
1001
|
-
* 'example.com': [
|
|
1002
|
-
* { pattern: '/', handler: 'HomeController.index' }
|
|
1003
|
-
* ]
|
|
1004
|
-
* })
|
|
1005
|
-
*/
|
|
1006
|
-
#processRoutes = throttle(async (routesFileLocation) => {
|
|
1007
|
-
try {
|
|
1008
|
-
const scanRoutes = this.#hooks.has("routesScanning") || this.#hooks.has("routesScanned");
|
|
1009
|
-
const shareRoutes = this.#hooks.has("routesCommitted");
|
|
1010
|
-
if (!scanRoutes && !shareRoutes) {
|
|
1011
|
-
unlink(routesFileLocation).catch(() => {
|
|
1012
|
-
});
|
|
1013
|
-
return;
|
|
1014
|
-
}
|
|
1015
|
-
const routesJSON = await readFile(routesFileLocation, "utf-8");
|
|
1016
|
-
const routesList = JSON.parse(routesJSON);
|
|
1017
|
-
unlink(routesFileLocation).catch(() => {
|
|
1018
|
-
});
|
|
1019
|
-
if (shareRoutes) {
|
|
1020
|
-
await this.#hooks.runner("routesCommitted").run(this, routesList);
|
|
1021
|
-
}
|
|
1022
|
-
if (scanRoutes) {
|
|
1023
|
-
this.#routesScanner = new RoutesScanner(this.cwdPath, []);
|
|
1024
|
-
await this.#hooks.runner("routesScanning").run(this, this.#routesScanner);
|
|
1025
|
-
for (const domain of Object.keys(routesList)) {
|
|
1026
|
-
await this.#routesScanner.scan(routesList[domain]);
|
|
1027
|
-
}
|
|
1028
|
-
await this.#hooks.runner("routesScanned").run(this, this.#routesScanner);
|
|
1029
|
-
}
|
|
1030
|
-
} catch (error) {
|
|
1031
|
-
this.ui.logger.error("Unable to process and scan routes because of the following error");
|
|
1032
|
-
this.ui.logger.fatal(error);
|
|
1033
|
-
}
|
|
1034
|
-
}, "processRoutes");
|
|
1035
|
-
/**
|
|
1036
|
-
* Registers hooks for file system events and server restart triggers
|
|
1037
|
-
*
|
|
1038
|
-
* Sets up event handlers that respond to file additions, changes, and removals
|
|
1039
|
-
* by regenerating indexes and handling server restarts as needed.
|
|
1040
|
-
*/
|
|
1041
|
-
#registerServerRestartHooks() {
|
|
1042
|
-
this.#hooks.add("fileAdded", (relativePath, absolutePath) => {
|
|
1043
|
-
this.#regenerateIndex(absolutePath, "add");
|
|
1044
|
-
this.#handleFileChange(relativePath, absolutePath, "add");
|
|
1045
|
-
});
|
|
1046
|
-
this.#hooks.add("fileChanged", (relativePath, absolutePath, info) => {
|
|
1047
|
-
if (info.hotReloaded || !info.hotReloaded && !info.fullReload) {
|
|
1048
|
-
this.#reScanRoutes(absolutePath);
|
|
1049
|
-
}
|
|
1050
|
-
this.#handleFileChange(relativePath, absolutePath, "update", info);
|
|
1051
|
-
});
|
|
1052
|
-
this.#hooks.add("fileRemoved", (relativePath, absolutePath) => {
|
|
1053
|
-
this.#regenerateIndex(absolutePath, "delete");
|
|
1054
|
-
this.#handleFileChange(relativePath, absolutePath, "delete");
|
|
1055
|
-
});
|
|
1056
|
-
}
|
|
1057
|
-
/**
|
|
1058
|
-
* Initializes the development server state and executes init hooks
|
|
1059
|
-
*
|
|
1060
|
-
* Parses TypeScript configuration, sets up file system, loads hooks,
|
|
1061
|
-
* initializes the index generator, and prepares the server for the
|
|
1062
|
-
* specified mode (HMR, watch, or static).
|
|
1063
|
-
*
|
|
1064
|
-
* @param ts - TypeScript module reference
|
|
1065
|
-
* @param mode - Server mode (hmr, watch, or static)
|
|
1066
|
-
* @returns True if initialization succeeds, false if tsconfig parsing fails
|
|
1067
|
-
*
|
|
1068
|
-
* @example
|
|
1069
|
-
* const success = await devServer.#init(ts, 'hmr')
|
|
1070
|
-
* if (!success) {
|
|
1071
|
-
* console.error('Failed to initialize dev server')
|
|
1072
|
-
* }
|
|
1073
|
-
*/
|
|
1074
|
-
async #init(mode) {
|
|
1075
|
-
const tsConfig = readTsConfig(this.cwdPath);
|
|
1076
|
-
if (!tsConfig) {
|
|
1077
|
-
this.#onError?.(new RuntimeException("Unable to parse tsconfig file"));
|
|
1078
|
-
return false;
|
|
1079
|
-
}
|
|
1080
|
-
this.#mode = mode;
|
|
1081
|
-
this.#clearScreen();
|
|
1082
|
-
this.ui.logger.info(`starting server in ${this.#mode} mode...`);
|
|
1083
|
-
this.#indexGenerator = new IndexGenerator(this.cwdPath, this.ui.logger);
|
|
1084
|
-
this.#stickyPort = String(await getPort(this.cwd));
|
|
1085
|
-
this.#fileSystem = new FileSystem(this.cwdPath, tsConfig, this.options);
|
|
1086
|
-
this.ui.logger.info("loading hooks...");
|
|
1087
|
-
this.#hooks = await loadHooks(this.options.hooks, [
|
|
1088
|
-
"init",
|
|
1089
|
-
"routesCommitted",
|
|
1090
|
-
"routesScanning",
|
|
1091
|
-
"routesScanned",
|
|
1092
|
-
"devServerStarting",
|
|
1093
|
-
"devServerStarted",
|
|
1094
|
-
"fileAdded",
|
|
1095
|
-
"fileChanged",
|
|
1096
|
-
"fileRemoved"
|
|
1097
|
-
]);
|
|
1098
|
-
this.#registerServerRestartHooks();
|
|
1099
|
-
this.#setupKeyboardShortcuts();
|
|
1100
|
-
await this.#hooks.runner("init").run(this, this.#indexGenerator);
|
|
1101
|
-
this.#hooks.clear("init");
|
|
1102
|
-
this.ui.logger.info("generating indexes...");
|
|
1103
|
-
await this.#indexGenerator.generate();
|
|
1104
|
-
return true;
|
|
1105
|
-
}
|
|
1106
|
-
/**
|
|
1107
|
-
* Starts the HTTP server as a child process
|
|
1108
|
-
*
|
|
1109
|
-
* Creates a new Node.js child process to run the server script with the
|
|
1110
|
-
* specified port and configuration. Sets up message handlers for server
|
|
1111
|
-
* ready notifications, routes sharing, and hot-hook events. Executes
|
|
1112
|
-
* devServerStarting hooks before spawning the process.
|
|
1113
|
-
*
|
|
1114
|
-
* @param port - Port number for the server to listen on
|
|
1115
|
-
*
|
|
1116
|
-
* @example
|
|
1117
|
-
* await devServer.#startHTTPServer('3333')
|
|
1118
|
-
*/
|
|
1119
|
-
async #startHTTPServer(port) {
|
|
1120
|
-
await this.#hooks.runner("devServerStarting").run(this);
|
|
1121
|
-
debug_default('starting http server using "%s" file, options %O', this.scriptFile, this.options);
|
|
1122
|
-
return new Promise((resolve) => {
|
|
1123
|
-
this.#httpServer = runNode(this.cwd, {
|
|
1124
|
-
script: this.scriptFile,
|
|
1125
|
-
env: { PORT: port, ...this.options.env },
|
|
1126
|
-
nodeArgs: this.options.nodeArgs,
|
|
1127
|
-
reject: true,
|
|
1128
|
-
scriptArgs: this.options.scriptArgs
|
|
1129
|
-
});
|
|
1130
|
-
this.#isHttpServerAlive = true;
|
|
1131
|
-
this.#httpServer.on("message", async (message) => {
|
|
1132
|
-
if (this.#isAdonisJSReadyMessage(message)) {
|
|
1133
|
-
debug_default("received http server ready message %O", message);
|
|
1134
|
-
await this.#postServerReady(message);
|
|
1135
|
-
resolve();
|
|
1136
|
-
} else if (this.#isAdonisJSRoutesMessage(message)) {
|
|
1137
|
-
debug_default("received routes location from the server %O", message);
|
|
1138
|
-
await this.#processRoutes(message.routesFileLocation);
|
|
1139
|
-
} else if (this.#mode === "hmr" && this.#isHotHookMessage(message)) {
|
|
1140
|
-
debug_default("received hot-hook message %O", message);
|
|
1141
|
-
if (message.type === "hot-hook:full-reload") {
|
|
1142
|
-
const absolutePath = message.path ? string3.toUnixSlash(message.path) : "";
|
|
1143
|
-
const relativePath = relative3(this.cwdPath, absolutePath);
|
|
1144
|
-
this.#hooks.runner("fileChanged").run(relativePath, absolutePath, _DevServer.#HOT_HOOK_FULL_RELOAD_INFO, this);
|
|
1145
|
-
} else if (message.type === "hot-hook:invalidated") {
|
|
1146
|
-
const absolutePath = message.paths[0] ? string3.toUnixSlash(message.paths[0]) : "";
|
|
1147
|
-
const relativePath = relative3(this.cwdPath, absolutePath);
|
|
1148
|
-
this.#hooks.runner("fileChanged").run(relativePath, absolutePath, _DevServer.#HOT_HOOK_INVALIDATED_INFO, this);
|
|
1149
|
-
}
|
|
1150
|
-
}
|
|
1151
|
-
});
|
|
1152
|
-
this.#httpServer.then((result) => {
|
|
1153
|
-
this.#isHttpServerAlive = false;
|
|
1154
|
-
if (!this.#watcher) {
|
|
1155
|
-
this.#onClose?.(result.exitCode);
|
|
1156
|
-
} else {
|
|
1157
|
-
this.ui.logger.info("Underlying HTTP server closed. Still watching for changes");
|
|
1158
|
-
}
|
|
1159
|
-
}).catch((error) => {
|
|
1160
|
-
this.#isHttpServerAlive = false;
|
|
1161
|
-
if (!this.#watcher) {
|
|
1162
|
-
this.#onError?.(error);
|
|
1163
|
-
} else {
|
|
1164
|
-
this.ui.logger.info("Underlying HTTP server died. Still watching for changes");
|
|
1165
|
-
}
|
|
1166
|
-
}).finally(() => {
|
|
1167
|
-
resolve();
|
|
1168
|
-
});
|
|
1169
|
-
});
|
|
1170
|
-
}
|
|
1171
|
-
/**
|
|
1172
|
-
* Adds listener to get notified when dev server is closed
|
|
1173
|
-
*
|
|
1174
|
-
* Registers a callback function that will be invoked when the development
|
|
1175
|
-
* server's child process exits. The callback receives the exit code.
|
|
1176
|
-
*
|
|
1177
|
-
* @param callback - Function to call when dev server closes
|
|
1178
|
-
* @returns This DevServer instance for method chaining
|
|
1179
|
-
*
|
|
1180
|
-
* @example
|
|
1181
|
-
* devServer.onClose((exitCode) => {
|
|
1182
|
-
* console.log(`Server closed with exit code: ${exitCode}`)
|
|
1183
|
-
* })
|
|
1184
|
-
*/
|
|
1185
|
-
onClose(callback) {
|
|
1186
|
-
this.#onClose = callback;
|
|
1187
|
-
return this;
|
|
1188
|
-
}
|
|
1189
|
-
/**
|
|
1190
|
-
* Adds listener to get notified when dev server encounters an error
|
|
1191
|
-
*
|
|
1192
|
-
* Registers a callback function that will be invoked when the development
|
|
1193
|
-
* server's child process encounters an error or fails to start.
|
|
1194
|
-
*
|
|
1195
|
-
* @param callback - Function to call when dev server encounters an error
|
|
1196
|
-
* @returns This DevServer instance for method chaining
|
|
1197
|
-
*
|
|
1198
|
-
* @example
|
|
1199
|
-
* devServer.onError((error) => {
|
|
1200
|
-
* console.error('Dev server error:', error.message)
|
|
1201
|
-
* })
|
|
1202
|
-
*/
|
|
1203
|
-
onError(callback) {
|
|
1204
|
-
this.#onError = callback;
|
|
1205
|
-
return this;
|
|
1206
|
-
}
|
|
1207
|
-
/**
|
|
1208
|
-
* Closes watchers and terminates the running child process
|
|
1209
|
-
*
|
|
1210
|
-
* Cleans up keyboard shortcuts, stops file system watchers, and kills
|
|
1211
|
-
* the HTTP server child process. This should be called when shutting down
|
|
1212
|
-
* the development server.
|
|
1213
|
-
*
|
|
1214
|
-
* @example
|
|
1215
|
-
* await devServer.close()
|
|
1216
|
-
*/
|
|
1217
|
-
async close() {
|
|
1218
|
-
this.#cleanupKeyboardShortcuts();
|
|
1219
|
-
await this.#watcher?.close();
|
|
1220
|
-
if (this.#httpServer) {
|
|
1221
|
-
this.#httpServer.removeAllListeners();
|
|
1222
|
-
this.#httpServer.kill("SIGKILL");
|
|
1223
|
-
}
|
|
1224
|
-
}
|
|
1225
|
-
/**
|
|
1226
|
-
* Starts the development server in static or HMR mode
|
|
1227
|
-
*
|
|
1228
|
-
* Initializes the server and starts the HTTP server. The mode is determined
|
|
1229
|
-
* by the `hmr` option in DevServerOptions. In HMR mode, hot-hook is configured
|
|
1230
|
-
* to enable hot module reloading.
|
|
1231
|
-
*
|
|
1232
|
-
* @param ts - TypeScript module reference
|
|
1233
|
-
*
|
|
1234
|
-
* @example
|
|
1235
|
-
* const devServer = new DevServer(cwd, { hmr: true, hooks: [] })
|
|
1236
|
-
* await devServer.start(ts)
|
|
1237
|
-
*/
|
|
1238
|
-
async start() {
|
|
1239
|
-
const initiated = await this.#init(this.options.hmr ? "hmr" : "static");
|
|
1240
|
-
if (!initiated) {
|
|
1241
|
-
return;
|
|
1242
|
-
}
|
|
1243
|
-
if (this.#mode === "hmr") {
|
|
1244
|
-
this.options.nodeArgs.push("--import=hot-hook/register");
|
|
1245
|
-
this.options.env = {
|
|
1246
|
-
...this.options.env,
|
|
1247
|
-
HOT_HOOK_WATCH: "false"
|
|
1248
|
-
};
|
|
1249
|
-
}
|
|
1250
|
-
this.ui.logger.info("starting HTTP server...");
|
|
1251
|
-
await this.#startHTTPServer(this.#stickyPort);
|
|
1252
|
-
if (this.#mode !== "hmr") return;
|
|
1253
|
-
this.#watcher = this.#createWatcher();
|
|
1254
|
-
this.#watcher.on("add", (filePath) => {
|
|
1255
|
-
this.#handleHmrWatcherEvent({ filePath, action: "add", displayLabel: "add" });
|
|
1256
|
-
});
|
|
1257
|
-
this.#watcher.on("change", (filePath) => {
|
|
1258
|
-
this.#handleHmrWatcherEvent({ filePath, action: "change", displayLabel: "update" });
|
|
1259
|
-
});
|
|
1260
|
-
this.#watcher.on("unlink", (filePath) => {
|
|
1261
|
-
this.#handleHmrWatcherEvent({ filePath, action: "unlink", displayLabel: "delete" });
|
|
1262
|
-
});
|
|
1263
|
-
}
|
|
1264
|
-
/**
|
|
1265
|
-
* Starts the development server in watch mode and restarts on file changes
|
|
1266
|
-
*
|
|
1267
|
-
* Initializes the server, starts the HTTP server, and sets up a file system
|
|
1268
|
-
* watcher that monitors for changes. When files are added, modified, or deleted,
|
|
1269
|
-
* the server automatically restarts. The watcher respects TypeScript project
|
|
1270
|
-
* configuration and metaFiles settings.
|
|
1271
|
-
*
|
|
1272
|
-
* @param ts - TypeScript module reference
|
|
1273
|
-
* @param options - Watch options including polling mode
|
|
1274
|
-
*
|
|
1275
|
-
* @example
|
|
1276
|
-
* const devServer = new DevServer(cwd, { hooks: [] })
|
|
1277
|
-
* await devServer.startAndWatch(ts, { poll: false })
|
|
1278
|
-
*/
|
|
1279
|
-
async startAndWatch(options) {
|
|
1280
|
-
const initiated = await this.#init("watch");
|
|
1281
|
-
if (!initiated) {
|
|
1282
|
-
return;
|
|
1283
|
-
}
|
|
1284
|
-
this.ui.logger.info("starting HTTP server...");
|
|
1285
|
-
await this.#startHTTPServer(this.#stickyPort);
|
|
1286
|
-
this.#watcher = this.#createWatcher({ poll: options?.poll });
|
|
1287
|
-
this.#watcher.on("add", (filePath) => {
|
|
1288
|
-
const relativePath = string3.toUnixSlash(filePath);
|
|
1289
|
-
const absolutePath = join2(this.cwdPath, relativePath);
|
|
1290
|
-
this.#hooks.runner("fileAdded").run(relativePath, absolutePath, this);
|
|
1291
|
-
});
|
|
1292
|
-
this.#watcher.on("change", (filePath) => {
|
|
1293
|
-
const relativePath = string3.toUnixSlash(filePath);
|
|
1294
|
-
const absolutePath = join2(this.cwdPath, relativePath);
|
|
1295
|
-
this.#hooks.runner("fileChanged").run(relativePath, absolutePath, _DevServer.#WATCHER_INFO, this);
|
|
1296
|
-
});
|
|
1297
|
-
this.#watcher.on("unlink", (filePath) => {
|
|
1298
|
-
const relativePath = string3.toUnixSlash(filePath);
|
|
1299
|
-
const absolutePath = join2(this.cwdPath, relativePath);
|
|
1300
|
-
this.#hooks.runner("fileRemoved").run(relativePath, absolutePath, this);
|
|
1301
|
-
});
|
|
1302
|
-
}
|
|
338
|
+
var DevServer = class DevServer {
|
|
339
|
+
static #HOT_HOOK_FULL_RELOAD_INFO = {
|
|
340
|
+
source: "hot-hook",
|
|
341
|
+
fullReload: true,
|
|
342
|
+
hotReloaded: false
|
|
343
|
+
};
|
|
344
|
+
static #HOT_HOOK_INVALIDATED_INFO = {
|
|
345
|
+
source: "hot-hook",
|
|
346
|
+
fullReload: false,
|
|
347
|
+
hotReloaded: true
|
|
348
|
+
};
|
|
349
|
+
static #WATCHER_INFO = {
|
|
350
|
+
source: "watcher",
|
|
351
|
+
fullReload: true,
|
|
352
|
+
hotReloaded: false
|
|
353
|
+
};
|
|
354
|
+
#onError;
|
|
355
|
+
#onClose;
|
|
356
|
+
#stickyPort;
|
|
357
|
+
#mode = "static";
|
|
358
|
+
#watcher;
|
|
359
|
+
#httpServer;
|
|
360
|
+
#isHttpServerAlive = false;
|
|
361
|
+
#shortcutsManager;
|
|
362
|
+
#fileSystem;
|
|
363
|
+
#indexGenerator;
|
|
364
|
+
#routesScanner;
|
|
365
|
+
#hooks;
|
|
366
|
+
ui = cliui();
|
|
367
|
+
#restartHTTPServer = throttle(async () => {
|
|
368
|
+
if (this.#httpServer) {
|
|
369
|
+
this.#httpServer.removeAllListeners();
|
|
370
|
+
this.#httpServer.kill("SIGKILL");
|
|
371
|
+
}
|
|
372
|
+
await this.#startHTTPServer(this.#stickyPort);
|
|
373
|
+
}, "restartHTTPServer");
|
|
374
|
+
#setupKeyboardShortcuts() {
|
|
375
|
+
this.#shortcutsManager = new ShortcutsManager({
|
|
376
|
+
logger: this.ui.logger,
|
|
377
|
+
callbacks: {
|
|
378
|
+
onRestart: () => this.#restartHTTPServer(),
|
|
379
|
+
onClear: () => this.#clearScreen(),
|
|
380
|
+
onQuit: () => this.close()
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
this.#shortcutsManager.setup();
|
|
384
|
+
}
|
|
385
|
+
#cleanupKeyboardShortcuts() {
|
|
386
|
+
this.#shortcutsManager?.cleanup();
|
|
387
|
+
}
|
|
388
|
+
get mode() {
|
|
389
|
+
return this.#mode;
|
|
390
|
+
}
|
|
391
|
+
scriptFile = "bin/server.ts";
|
|
392
|
+
cwd;
|
|
393
|
+
cwdPath;
|
|
394
|
+
options;
|
|
395
|
+
constructor(cwd, options) {
|
|
396
|
+
this.cwd = cwd;
|
|
397
|
+
this.options = options;
|
|
398
|
+
this.cwdPath = string.toUnixSlash(fileURLToPath(this.cwd));
|
|
399
|
+
}
|
|
400
|
+
#isAdonisJSReadyMessage(message) {
|
|
401
|
+
return message !== null && typeof message === "object" && "isAdonisJS" in message && "environment" in message && message.environment === "web";
|
|
402
|
+
}
|
|
403
|
+
#isAdonisJSRoutesMessage(message) {
|
|
404
|
+
return message !== null && typeof message === "object" && "routesFileLocation" in message;
|
|
405
|
+
}
|
|
406
|
+
async #postServerReady(message) {
|
|
407
|
+
const host = message.host === "0.0.0.0" ? "127.0.0.1" : message.host;
|
|
408
|
+
const info = {
|
|
409
|
+
host,
|
|
410
|
+
port: message.port
|
|
411
|
+
};
|
|
412
|
+
const serverUrl = `http://${host}:${message.port}`;
|
|
413
|
+
this.#shortcutsManager?.setServerUrl(serverUrl);
|
|
414
|
+
const displayMessage = this.ui.sticker().add(`Server address: ${this.ui.colors.cyan(serverUrl)}`).add(`Mode: ${this.ui.colors.cyan(this.mode)}`);
|
|
415
|
+
if (message.duration) displayMessage.add(`Ready in: ${this.ui.colors.cyan(prettyHrtime(message.duration))}`);
|
|
416
|
+
displayMessage.add(`Press ${this.ui.colors.dim("h")} to show help`);
|
|
417
|
+
try {
|
|
418
|
+
await this.#hooks.runner("devServerStarted").run(this, info, displayMessage);
|
|
419
|
+
} catch (error) {
|
|
420
|
+
this.ui.logger.error("One of the \"devServerStarted\" hooks failed");
|
|
421
|
+
this.ui.logger.fatal(error);
|
|
422
|
+
}
|
|
423
|
+
displayMessage.render();
|
|
424
|
+
}
|
|
425
|
+
#isHotHookMessage(message) {
|
|
426
|
+
return message !== null && typeof message === "object" && "type" in message && typeof message.type === "string" && message.type.startsWith("hot-hook:");
|
|
427
|
+
}
|
|
428
|
+
#clearScreen() {
|
|
429
|
+
if (this.options.clearScreen) process.stdout.write("\x1Bc");
|
|
430
|
+
}
|
|
431
|
+
#createWatcher(options) {
|
|
432
|
+
const watcher = watch({
|
|
433
|
+
usePolling: options?.poll ?? false,
|
|
434
|
+
cwd: this.cwdPath,
|
|
435
|
+
ignoreInitial: true,
|
|
436
|
+
ignored: (file, stats) => {
|
|
437
|
+
if (!stats) return false;
|
|
438
|
+
if (file.includes("inertia") && !file.includes("node_modules")) return false;
|
|
439
|
+
if (stats.isFile()) return !this.#fileSystem.shouldWatchFile(file);
|
|
440
|
+
return !this.#fileSystem.shouldWatchDirectory(file);
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
watcher.on("error", (error) => {
|
|
444
|
+
this.ui.logger.warning("file system watcher failure");
|
|
445
|
+
this.ui.logger.fatal(error);
|
|
446
|
+
this.#onError?.(error);
|
|
447
|
+
this.#watcher?.close();
|
|
448
|
+
});
|
|
449
|
+
watcher.on("ready", () => {
|
|
450
|
+
this.ui.logger.info("watching file system for changes...");
|
|
451
|
+
});
|
|
452
|
+
return watcher;
|
|
453
|
+
}
|
|
454
|
+
#handleHmrWatcherEvent(options) {
|
|
455
|
+
const relativePath = string.toUnixSlash(options.filePath);
|
|
456
|
+
const absolutePath = join(this.cwdPath, relativePath);
|
|
457
|
+
if (this.#isHttpServerAlive === false) {
|
|
458
|
+
this.#clearScreen();
|
|
459
|
+
this.ui.logger.log(`${this.ui.colors.green(options.displayLabel)} ${relativePath}`);
|
|
460
|
+
this.#restartHTTPServer();
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
if (options.action === "add") this.#hooks.runner("fileAdded").run(relativePath, absolutePath, this);
|
|
464
|
+
else if (options.action === "unlink") this.#hooks.runner("fileRemoved").run(relativePath, absolutePath, this);
|
|
465
|
+
this.#httpServer?.send({
|
|
466
|
+
type: "hot-hook:file-changed",
|
|
467
|
+
path: absolutePath,
|
|
468
|
+
action: options.action
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
#handleFileChange(relativePath, absolutePath, action, info) {
|
|
472
|
+
if ((action === "add" || action === "delete") && this.mode === "hmr") {
|
|
473
|
+
debug_default("ignoring add and delete actions in HMR mode %s", relativePath);
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
if (info && info.source === "hot-hook" && info.hotReloaded) {
|
|
477
|
+
debug_default("hot reloading %s, info %O", relativePath, info);
|
|
478
|
+
this.ui.logger.log(`${this.ui.colors.green("invalidated")} ${relativePath}`);
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
if (info && !info.fullReload) {
|
|
482
|
+
debug_default("ignoring full reload", relativePath, info);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
const file = this.#fileSystem.inspect(absolutePath, relativePath);
|
|
486
|
+
if (!file) return;
|
|
487
|
+
if (file.reloadServer) {
|
|
488
|
+
this.#clearScreen();
|
|
489
|
+
this.ui.logger.log(`${this.ui.colors.green(action)} ${relativePath}`);
|
|
490
|
+
this.#restartHTTPServer();
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
this.ui.logger.log(`${this.ui.colors.green(action)} ${relativePath}`);
|
|
494
|
+
}
|
|
495
|
+
#regenerateIndex(filePath, action) {
|
|
496
|
+
if (action === "add") return this.#indexGenerator.addFile(filePath);
|
|
497
|
+
return this.#indexGenerator.removeFile(filePath);
|
|
498
|
+
}
|
|
499
|
+
async #reScanRoutes(filePath) {
|
|
500
|
+
if (!this.#routesScanner) return;
|
|
501
|
+
try {
|
|
502
|
+
if (await this.#routesScanner.invalidate(filePath)) await this.#hooks.runner("routesScanned").run(this, this.#routesScanner);
|
|
503
|
+
} catch (error) {
|
|
504
|
+
this.ui.logger.error("Unable to rescan routes because of the following error");
|
|
505
|
+
this.ui.logger.fatal(error);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
#processRoutes = throttle(async (routesFileLocation) => {
|
|
509
|
+
try {
|
|
510
|
+
const scanRoutes = this.#hooks.has("routesScanning") || this.#hooks.has("routesScanned");
|
|
511
|
+
const shareRoutes = this.#hooks.has("routesCommitted");
|
|
512
|
+
if (!scanRoutes && !shareRoutes) {
|
|
513
|
+
unlink(routesFileLocation).catch(() => {});
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
const routesJSON = await readFile(routesFileLocation, "utf-8");
|
|
517
|
+
const routesList = JSON.parse(routesJSON);
|
|
518
|
+
unlink(routesFileLocation).catch(() => {});
|
|
519
|
+
if (shareRoutes) await this.#hooks.runner("routesCommitted").run(this, routesList);
|
|
520
|
+
if (scanRoutes) {
|
|
521
|
+
this.#routesScanner = new RoutesScanner(this.cwdPath, []);
|
|
522
|
+
await this.#hooks.runner("routesScanning").run(this, this.#routesScanner);
|
|
523
|
+
for (const domain of Object.keys(routesList)) await this.#routesScanner.scan(routesList[domain]);
|
|
524
|
+
await this.#hooks.runner("routesScanned").run(this, this.#routesScanner);
|
|
525
|
+
}
|
|
526
|
+
} catch (error) {
|
|
527
|
+
this.ui.logger.error("Unable to process and scan routes because of the following error");
|
|
528
|
+
this.ui.logger.fatal(error);
|
|
529
|
+
}
|
|
530
|
+
}, "processRoutes");
|
|
531
|
+
#registerServerRestartHooks() {
|
|
532
|
+
this.#hooks.add("fileAdded", (relativePath, absolutePath) => {
|
|
533
|
+
this.#regenerateIndex(absolutePath, "add");
|
|
534
|
+
this.#handleFileChange(relativePath, absolutePath, "add");
|
|
535
|
+
});
|
|
536
|
+
this.#hooks.add("fileChanged", (relativePath, absolutePath, info) => {
|
|
537
|
+
if (info.hotReloaded || !info.hotReloaded && !info.fullReload) this.#reScanRoutes(absolutePath);
|
|
538
|
+
this.#handleFileChange(relativePath, absolutePath, "update", info);
|
|
539
|
+
});
|
|
540
|
+
this.#hooks.add("fileRemoved", (relativePath, absolutePath) => {
|
|
541
|
+
this.#regenerateIndex(absolutePath, "delete");
|
|
542
|
+
this.#handleFileChange(relativePath, absolutePath, "delete");
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
async #init(mode) {
|
|
546
|
+
const tsConfig = readTsConfig(this.cwdPath);
|
|
547
|
+
if (!tsConfig) {
|
|
548
|
+
this.#onError?.(new RuntimeException("Unable to parse tsconfig file"));
|
|
549
|
+
return false;
|
|
550
|
+
}
|
|
551
|
+
this.#mode = mode;
|
|
552
|
+
this.#clearScreen();
|
|
553
|
+
this.ui.logger.info(`starting server in ${this.#mode} mode...`);
|
|
554
|
+
this.#indexGenerator = new IndexGenerator(this.cwdPath, this.ui.logger);
|
|
555
|
+
this.#stickyPort = String(await getPort(this.cwd));
|
|
556
|
+
this.#fileSystem = new FileSystem(this.cwdPath, tsConfig, this.options);
|
|
557
|
+
this.ui.logger.info("loading hooks...");
|
|
558
|
+
this.#hooks = await loadHooks(this.options.hooks, [
|
|
559
|
+
"init",
|
|
560
|
+
"routesCommitted",
|
|
561
|
+
"routesScanning",
|
|
562
|
+
"routesScanned",
|
|
563
|
+
"devServerStarting",
|
|
564
|
+
"devServerStarted",
|
|
565
|
+
"fileAdded",
|
|
566
|
+
"fileChanged",
|
|
567
|
+
"fileRemoved"
|
|
568
|
+
]);
|
|
569
|
+
this.#registerServerRestartHooks();
|
|
570
|
+
this.#setupKeyboardShortcuts();
|
|
571
|
+
await this.#hooks.runner("init").run(this, this.#indexGenerator);
|
|
572
|
+
this.#hooks.clear("init");
|
|
573
|
+
this.ui.logger.info("generating indexes...");
|
|
574
|
+
await this.#indexGenerator.generate();
|
|
575
|
+
return true;
|
|
576
|
+
}
|
|
577
|
+
async #startHTTPServer(port) {
|
|
578
|
+
await this.#hooks.runner("devServerStarting").run(this);
|
|
579
|
+
debug_default("starting http server using \"%s\" file, options %O", this.scriptFile, this.options);
|
|
580
|
+
return new Promise((resolve) => {
|
|
581
|
+
this.#httpServer = runNode(this.cwd, {
|
|
582
|
+
script: this.scriptFile,
|
|
583
|
+
env: {
|
|
584
|
+
PORT: port,
|
|
585
|
+
...this.options.env
|
|
586
|
+
},
|
|
587
|
+
nodeArgs: this.options.nodeArgs,
|
|
588
|
+
reject: true,
|
|
589
|
+
scriptArgs: this.options.scriptArgs
|
|
590
|
+
});
|
|
591
|
+
this.#isHttpServerAlive = true;
|
|
592
|
+
this.#httpServer.on("message", async (message) => {
|
|
593
|
+
if (this.#isAdonisJSReadyMessage(message)) {
|
|
594
|
+
debug_default("received http server ready message %O", message);
|
|
595
|
+
await this.#postServerReady(message);
|
|
596
|
+
resolve();
|
|
597
|
+
} else if (this.#isAdonisJSRoutesMessage(message)) {
|
|
598
|
+
debug_default("received routes location from the server %O", message);
|
|
599
|
+
await this.#processRoutes(message.routesFileLocation);
|
|
600
|
+
} else if (this.#mode === "hmr" && this.#isHotHookMessage(message)) {
|
|
601
|
+
debug_default("received hot-hook message %O", message);
|
|
602
|
+
if (message.type === "hot-hook:full-reload") {
|
|
603
|
+
const absolutePath = message.path ? string.toUnixSlash(message.path) : "";
|
|
604
|
+
const relativePath = relative(this.cwdPath, absolutePath);
|
|
605
|
+
this.#hooks.runner("fileChanged").run(relativePath, absolutePath, DevServer.#HOT_HOOK_FULL_RELOAD_INFO, this);
|
|
606
|
+
} else if (message.type === "hot-hook:invalidated") {
|
|
607
|
+
const absolutePath = message.paths[0] ? string.toUnixSlash(message.paths[0]) : "";
|
|
608
|
+
const relativePath = relative(this.cwdPath, absolutePath);
|
|
609
|
+
this.#hooks.runner("fileChanged").run(relativePath, absolutePath, DevServer.#HOT_HOOK_INVALIDATED_INFO, this);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
this.#httpServer.then((result) => {
|
|
614
|
+
this.#isHttpServerAlive = false;
|
|
615
|
+
if (!this.#watcher) this.#onClose?.(result.exitCode);
|
|
616
|
+
else this.ui.logger.info("Underlying HTTP server closed. Still watching for changes");
|
|
617
|
+
}).catch((error) => {
|
|
618
|
+
this.#isHttpServerAlive = false;
|
|
619
|
+
if (!this.#watcher) this.#onError?.(error);
|
|
620
|
+
else this.ui.logger.info("Underlying HTTP server died. Still watching for changes");
|
|
621
|
+
}).finally(() => {
|
|
622
|
+
resolve();
|
|
623
|
+
});
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
onClose(callback) {
|
|
627
|
+
this.#onClose = callback;
|
|
628
|
+
return this;
|
|
629
|
+
}
|
|
630
|
+
onError(callback) {
|
|
631
|
+
this.#onError = callback;
|
|
632
|
+
return this;
|
|
633
|
+
}
|
|
634
|
+
async close() {
|
|
635
|
+
this.#cleanupKeyboardShortcuts();
|
|
636
|
+
await this.#watcher?.close();
|
|
637
|
+
if (this.#httpServer) {
|
|
638
|
+
this.#httpServer.removeAllListeners();
|
|
639
|
+
this.#httpServer.kill("SIGKILL");
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
async start() {
|
|
643
|
+
if (!await this.#init(this.options.hmr ? "hmr" : "static")) return;
|
|
644
|
+
if (this.#mode === "hmr") {
|
|
645
|
+
this.options.nodeArgs.push("--import=hot-hook/register");
|
|
646
|
+
this.options.env = {
|
|
647
|
+
...this.options.env,
|
|
648
|
+
HOT_HOOK_WATCH: "false"
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
this.ui.logger.info("starting HTTP server...");
|
|
652
|
+
await this.#startHTTPServer(this.#stickyPort);
|
|
653
|
+
if (this.#mode !== "hmr") return;
|
|
654
|
+
this.#watcher = this.#createWatcher();
|
|
655
|
+
this.#watcher.on("add", (filePath) => {
|
|
656
|
+
this.#handleHmrWatcherEvent({
|
|
657
|
+
filePath,
|
|
658
|
+
action: "add",
|
|
659
|
+
displayLabel: "add"
|
|
660
|
+
});
|
|
661
|
+
});
|
|
662
|
+
this.#watcher.on("change", (filePath) => {
|
|
663
|
+
this.#handleHmrWatcherEvent({
|
|
664
|
+
filePath,
|
|
665
|
+
action: "change",
|
|
666
|
+
displayLabel: "update"
|
|
667
|
+
});
|
|
668
|
+
});
|
|
669
|
+
this.#watcher.on("unlink", (filePath) => {
|
|
670
|
+
this.#handleHmrWatcherEvent({
|
|
671
|
+
filePath,
|
|
672
|
+
action: "unlink",
|
|
673
|
+
displayLabel: "delete"
|
|
674
|
+
});
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
async startAndWatch(options) {
|
|
678
|
+
if (!await this.#init("watch")) return;
|
|
679
|
+
this.ui.logger.info("starting HTTP server...");
|
|
680
|
+
await this.#startHTTPServer(this.#stickyPort);
|
|
681
|
+
this.#watcher = this.#createWatcher({ poll: options?.poll });
|
|
682
|
+
this.#watcher.on("add", (filePath) => {
|
|
683
|
+
const relativePath = string.toUnixSlash(filePath);
|
|
684
|
+
const absolutePath = join(this.cwdPath, relativePath);
|
|
685
|
+
this.#hooks.runner("fileAdded").run(relativePath, absolutePath, this);
|
|
686
|
+
});
|
|
687
|
+
this.#watcher.on("change", (filePath) => {
|
|
688
|
+
const relativePath = string.toUnixSlash(filePath);
|
|
689
|
+
const absolutePath = join(this.cwdPath, relativePath);
|
|
690
|
+
this.#hooks.runner("fileChanged").run(relativePath, absolutePath, DevServer.#WATCHER_INFO, this);
|
|
691
|
+
});
|
|
692
|
+
this.#watcher.on("unlink", (filePath) => {
|
|
693
|
+
const relativePath = string.toUnixSlash(filePath);
|
|
694
|
+
const absolutePath = join(this.cwdPath, relativePath);
|
|
695
|
+
this.#hooks.runner("fileRemoved").run(relativePath, absolutePath, this);
|
|
696
|
+
});
|
|
697
|
+
}
|
|
1303
698
|
};
|
|
1304
|
-
|
|
1305
|
-
// src/test_runner.ts
|
|
1306
|
-
import { join as join3 } from "path/posix";
|
|
1307
|
-
import { cliui as cliui3 } from "@poppinss/cliui";
|
|
1308
|
-
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
1309
|
-
import string4 from "@poppinss/utils/string";
|
|
1310
|
-
import { RuntimeException as RuntimeException2 } from "@poppinss/utils/exception";
|
|
1311
699
|
var TestRunner = class {
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
this.#regenerateIndex(absolutePath, "add");
|
|
1549
|
-
this.#handleFileChange(relativePath, absolutePath, "update");
|
|
1550
|
-
});
|
|
1551
|
-
this.#hooks.add("fileRemoved", (relativePath, absolutePath) => {
|
|
1552
|
-
this.#regenerateIndex(absolutePath, "delete");
|
|
1553
|
-
this.#handleFileChange(relativePath, absolutePath, "delete");
|
|
1554
|
-
});
|
|
1555
|
-
}
|
|
1556
|
-
/**
|
|
1557
|
-
* Add listener to get notified when test runner is closed
|
|
1558
|
-
*
|
|
1559
|
-
* @param callback - Function to call when test runner closes
|
|
1560
|
-
* @returns This TestRunner instance for method chaining
|
|
1561
|
-
*/
|
|
1562
|
-
onClose(callback) {
|
|
1563
|
-
this.#onClose = callback;
|
|
1564
|
-
return this;
|
|
1565
|
-
}
|
|
1566
|
-
/**
|
|
1567
|
-
* Add listener to get notified when test runner encounters an error
|
|
1568
|
-
*
|
|
1569
|
-
* @param callback - Function to call when test runner encounters an error
|
|
1570
|
-
* @returns This TestRunner instance for method chaining
|
|
1571
|
-
*/
|
|
1572
|
-
onError(callback) {
|
|
1573
|
-
this.#onError = callback;
|
|
1574
|
-
return this;
|
|
1575
|
-
}
|
|
1576
|
-
/**
|
|
1577
|
-
* Close watchers and running child processes
|
|
1578
|
-
*
|
|
1579
|
-
* Cleans up file system watchers and terminates any running test
|
|
1580
|
-
* processes to ensure graceful shutdown.
|
|
1581
|
-
*/
|
|
1582
|
-
async close() {
|
|
1583
|
-
await this.#watcher?.close();
|
|
1584
|
-
if (this.#testsProcess) {
|
|
1585
|
-
this.#testsProcess.removeAllListeners();
|
|
1586
|
-
this.#testsProcess.kill("SIGKILL");
|
|
1587
|
-
}
|
|
1588
|
-
}
|
|
1589
|
-
/**
|
|
1590
|
-
* Runs tests once without watching for file changes
|
|
1591
|
-
*
|
|
1592
|
-
* Executes the test suite a single time and exits. This is the
|
|
1593
|
-
* equivalent of running tests in CI/CD environments.
|
|
1594
|
-
*/
|
|
1595
|
-
async run() {
|
|
1596
|
-
this.#stickyPort = String(await getPort(this.cwd));
|
|
1597
|
-
this.#indexGenerator = new IndexGenerator(this.cwdPath, this.ui.logger);
|
|
1598
|
-
this.#clearScreen();
|
|
1599
|
-
this.ui.logger.info("loading hooks...");
|
|
1600
|
-
this.#hooks = await loadHooks(this.options.hooks, ["init", "testsStarting", "testsFinished"]);
|
|
1601
|
-
await this.#hooks.runner("init").run(this, this.#indexGenerator);
|
|
1602
|
-
this.#hooks.clear("init");
|
|
1603
|
-
this.ui.logger.info("generating indexes...");
|
|
1604
|
-
await this.#indexGenerator.generate();
|
|
1605
|
-
this.ui.logger.info("booting application to run tests...");
|
|
1606
|
-
await this.#runTests(this.#stickyPort);
|
|
1607
|
-
}
|
|
1608
|
-
/**
|
|
1609
|
-
* Run tests in watch mode and re-run them when files change
|
|
1610
|
-
*
|
|
1611
|
-
* Starts the test runner in watch mode, monitoring the file system
|
|
1612
|
-
* for changes and automatically re-running tests when relevant files
|
|
1613
|
-
* are modified. Uses intelligent filtering to run only affected tests
|
|
1614
|
-
* when possible.
|
|
1615
|
-
*
|
|
1616
|
-
* @param ts - TypeScript module reference for parsing configuration
|
|
1617
|
-
* @param options - Watch options including polling mode for file system monitoring
|
|
1618
|
-
*/
|
|
1619
|
-
async runAndWatch(options) {
|
|
1620
|
-
const tsConfig = readTsConfig(this.cwdPath);
|
|
1621
|
-
if (!tsConfig) {
|
|
1622
|
-
this.#onError?.(new RuntimeException2("Unable to parse tsconfig file"));
|
|
1623
|
-
return;
|
|
1624
|
-
}
|
|
1625
|
-
this.#stickyPort = String(await getPort(this.cwd));
|
|
1626
|
-
this.#indexGenerator = new IndexGenerator(this.cwdPath, this.ui.logger);
|
|
1627
|
-
this.#fileSystem = new FileSystem(this.cwdPath, tsConfig, {
|
|
1628
|
-
...this.options,
|
|
1629
|
-
suites: this.options.suites?.filter((suite) => {
|
|
1630
|
-
if (this.options.filters.suites) {
|
|
1631
|
-
return this.options.filters.suites.includes(suite.name);
|
|
1632
|
-
}
|
|
1633
|
-
return true;
|
|
1634
|
-
})
|
|
1635
|
-
});
|
|
1636
|
-
this.#clearScreen();
|
|
1637
|
-
this.ui.logger.info("loading hooks...");
|
|
1638
|
-
this.#hooks = await loadHooks(this.options.hooks, [
|
|
1639
|
-
"init",
|
|
1640
|
-
"testsStarting",
|
|
1641
|
-
"testsFinished",
|
|
1642
|
-
"fileAdded",
|
|
1643
|
-
"fileChanged",
|
|
1644
|
-
"fileRemoved"
|
|
1645
|
-
]);
|
|
1646
|
-
this.#registerServerRestartHooks();
|
|
1647
|
-
await this.#hooks.runner("init").run(this, this.#indexGenerator);
|
|
1648
|
-
this.#hooks.clear("init");
|
|
1649
|
-
this.ui.logger.info("generating indexes...");
|
|
1650
|
-
await this.#indexGenerator.generate();
|
|
1651
|
-
this.ui.logger.info("booting application to run tests...");
|
|
1652
|
-
await this.#runTests(this.#stickyPort);
|
|
1653
|
-
this.#watcher = watch({
|
|
1654
|
-
usePolling: options?.poll ?? false,
|
|
1655
|
-
cwd: this.cwdPath,
|
|
1656
|
-
ignoreInitial: true,
|
|
1657
|
-
ignored: (file, stats) => {
|
|
1658
|
-
if (!stats) {
|
|
1659
|
-
return false;
|
|
1660
|
-
}
|
|
1661
|
-
if (stats.isFile()) {
|
|
1662
|
-
return !this.#fileSystem.shouldWatchFile(file);
|
|
1663
|
-
}
|
|
1664
|
-
return !this.#fileSystem.shouldWatchDirectory(file);
|
|
1665
|
-
}
|
|
1666
|
-
});
|
|
1667
|
-
this.#watcher.on("ready", () => {
|
|
1668
|
-
this.ui.logger.info("watching file system for changes...");
|
|
1669
|
-
});
|
|
1670
|
-
this.#watcher.on("error", (error) => {
|
|
1671
|
-
this.ui.logger.warning("file system watcher failure");
|
|
1672
|
-
this.ui.logger.fatal(error);
|
|
1673
|
-
this.#onError?.(error);
|
|
1674
|
-
this.#watcher?.close();
|
|
1675
|
-
});
|
|
1676
|
-
this.#watcher.on("add", (filePath) => {
|
|
1677
|
-
const relativePath = string4.toUnixSlash(filePath);
|
|
1678
|
-
const absolutePath = join3(this.cwdPath, filePath);
|
|
1679
|
-
this.#hooks.runner("fileAdded").run(relativePath, absolutePath, this);
|
|
1680
|
-
});
|
|
1681
|
-
this.#watcher.on("change", (filePath) => {
|
|
1682
|
-
const relativePath = string4.toUnixSlash(filePath);
|
|
1683
|
-
const absolutePath = join3(this.cwdPath, filePath);
|
|
1684
|
-
this.#hooks.runner("fileChanged").run(
|
|
1685
|
-
relativePath,
|
|
1686
|
-
absolutePath,
|
|
1687
|
-
{
|
|
1688
|
-
source: "watcher",
|
|
1689
|
-
fullReload: true,
|
|
1690
|
-
hotReloaded: false
|
|
1691
|
-
},
|
|
1692
|
-
this
|
|
1693
|
-
);
|
|
1694
|
-
});
|
|
1695
|
-
this.#watcher.on("unlink", (filePath) => {
|
|
1696
|
-
const relativePath = string4.toUnixSlash(filePath);
|
|
1697
|
-
const absolutePath = join3(this.cwdPath, filePath);
|
|
1698
|
-
this.#hooks.runner("fileRemoved").run(relativePath, absolutePath, this);
|
|
1699
|
-
});
|
|
1700
|
-
}
|
|
1701
|
-
};
|
|
1702
|
-
export {
|
|
1703
|
-
Bundler,
|
|
1704
|
-
DevServer,
|
|
1705
|
-
FileBuffer,
|
|
1706
|
-
SUPPORTED_PACKAGE_MANAGERS,
|
|
1707
|
-
TestRunner,
|
|
1708
|
-
VirtualFileSystem
|
|
700
|
+
#onError;
|
|
701
|
+
#onClose;
|
|
702
|
+
#stickyPort;
|
|
703
|
+
#watcher;
|
|
704
|
+
#testsProcess;
|
|
705
|
+
#fileSystem;
|
|
706
|
+
#hooks;
|
|
707
|
+
#indexGenerator;
|
|
708
|
+
ui = cliui();
|
|
709
|
+
#reRunTests = throttle(async (filters) => {
|
|
710
|
+
if (this.#testsProcess) {
|
|
711
|
+
this.#testsProcess.removeAllListeners();
|
|
712
|
+
this.#testsProcess.kill("SIGKILL");
|
|
713
|
+
}
|
|
714
|
+
await this.#runTests(this.#stickyPort, filters);
|
|
715
|
+
}, "reRunTests");
|
|
716
|
+
scriptFile = "bin/test.ts";
|
|
717
|
+
cwd;
|
|
718
|
+
cwdPath;
|
|
719
|
+
options;
|
|
720
|
+
constructor(cwd, options) {
|
|
721
|
+
this.cwd = cwd;
|
|
722
|
+
this.options = options;
|
|
723
|
+
this.cwdPath = string.toUnixSlash(fileURLToPath(this.cwd));
|
|
724
|
+
}
|
|
725
|
+
#convertOptionsToArgs() {
|
|
726
|
+
const args = [];
|
|
727
|
+
if (this.options.reporters) {
|
|
728
|
+
args.push("--reporters");
|
|
729
|
+
args.push(this.options.reporters.join(","));
|
|
730
|
+
}
|
|
731
|
+
if (this.options.timeout !== void 0) {
|
|
732
|
+
args.push("--timeout");
|
|
733
|
+
args.push(String(this.options.timeout));
|
|
734
|
+
}
|
|
735
|
+
if (this.options.failed) args.push("--failed");
|
|
736
|
+
if (this.options.retries !== void 0) {
|
|
737
|
+
args.push("--retries");
|
|
738
|
+
args.push(String(this.options.retries));
|
|
739
|
+
}
|
|
740
|
+
return args;
|
|
741
|
+
}
|
|
742
|
+
#convertFiltersToArgs(filters) {
|
|
743
|
+
const args = [];
|
|
744
|
+
if (filters.suites) args.push(...filters.suites);
|
|
745
|
+
if (filters.files) {
|
|
746
|
+
args.push("--files");
|
|
747
|
+
args.push(filters.files.join(","));
|
|
748
|
+
}
|
|
749
|
+
if (filters.groups) {
|
|
750
|
+
args.push("--groups");
|
|
751
|
+
args.push(filters.groups.join(","));
|
|
752
|
+
}
|
|
753
|
+
if (filters.tags) {
|
|
754
|
+
args.push("--tags");
|
|
755
|
+
args.push(filters.tags.join(","));
|
|
756
|
+
}
|
|
757
|
+
if (filters.tests) {
|
|
758
|
+
args.push("--tests");
|
|
759
|
+
args.push(filters.tests.join(","));
|
|
760
|
+
}
|
|
761
|
+
return args;
|
|
762
|
+
}
|
|
763
|
+
#clearScreen() {
|
|
764
|
+
if (this.options.clearScreen) process.stdout.write("\x1Bc");
|
|
765
|
+
}
|
|
766
|
+
async #runTests(port, filters) {
|
|
767
|
+
await this.#hooks.runner("testsStarting").run(this);
|
|
768
|
+
debug_default("running tests using \"%s\" file, options %O", this.scriptFile, this.options);
|
|
769
|
+
return new Promise(async (resolve) => {
|
|
770
|
+
const mergedFilters = {
|
|
771
|
+
...this.options.filters,
|
|
772
|
+
...filters
|
|
773
|
+
};
|
|
774
|
+
const scriptArgs = [
|
|
775
|
+
...this.#convertOptionsToArgs(),
|
|
776
|
+
...this.options.scriptArgs,
|
|
777
|
+
...this.#convertFiltersToArgs(mergedFilters)
|
|
778
|
+
];
|
|
779
|
+
this.#testsProcess = runNode(this.cwd, {
|
|
780
|
+
script: this.scriptFile,
|
|
781
|
+
reject: true,
|
|
782
|
+
env: {
|
|
783
|
+
PORT: port,
|
|
784
|
+
...this.options.env
|
|
785
|
+
},
|
|
786
|
+
nodeArgs: this.options.nodeArgs,
|
|
787
|
+
scriptArgs
|
|
788
|
+
});
|
|
789
|
+
this.#testsProcess.then((result) => {
|
|
790
|
+
this.#hooks.runner("testsFinished").run(this).catch((error) => {
|
|
791
|
+
this.ui.logger.error("One of the \"testsFinished\" hooks failed");
|
|
792
|
+
this.ui.logger.fatal(error);
|
|
793
|
+
}).finally(() => {
|
|
794
|
+
if (!this.#watcher) {
|
|
795
|
+
this.#onClose?.(result.exitCode);
|
|
796
|
+
this.close();
|
|
797
|
+
}
|
|
798
|
+
});
|
|
799
|
+
}).catch((error) => {
|
|
800
|
+
if (!this.#watcher) {
|
|
801
|
+
this.#onError?.(error);
|
|
802
|
+
this.close();
|
|
803
|
+
} else this.ui.logger.info("Underlying HTTP server died. Still watching for changes");
|
|
804
|
+
}).finally(() => resolve());
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
#handleFileChange(relativePath, absolutePath, action) {
|
|
808
|
+
const file = this.#fileSystem.inspect(absolutePath, relativePath);
|
|
809
|
+
if (!file) return;
|
|
810
|
+
this.#clearScreen();
|
|
811
|
+
this.ui.logger.log(`${this.ui.colors.green(action)} ${relativePath}`);
|
|
812
|
+
if (file.fileType === "test") this.#reRunTests({ files: [relativePath] });
|
|
813
|
+
else this.#reRunTests();
|
|
814
|
+
}
|
|
815
|
+
#regenerateIndex(filePath, action) {
|
|
816
|
+
if (action === "add") return this.#indexGenerator.addFile(filePath);
|
|
817
|
+
return this.#indexGenerator.removeFile(filePath);
|
|
818
|
+
}
|
|
819
|
+
#registerServerRestartHooks() {
|
|
820
|
+
this.#hooks.add("fileAdded", (relativePath, absolutePath) => {
|
|
821
|
+
this.#regenerateIndex(absolutePath, "add");
|
|
822
|
+
this.#handleFileChange(relativePath, absolutePath, "add");
|
|
823
|
+
});
|
|
824
|
+
this.#hooks.add("fileChanged", (relativePath, absolutePath) => {
|
|
825
|
+
this.#regenerateIndex(absolutePath, "add");
|
|
826
|
+
this.#handleFileChange(relativePath, absolutePath, "update");
|
|
827
|
+
});
|
|
828
|
+
this.#hooks.add("fileRemoved", (relativePath, absolutePath) => {
|
|
829
|
+
this.#regenerateIndex(absolutePath, "delete");
|
|
830
|
+
this.#handleFileChange(relativePath, absolutePath, "delete");
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
onClose(callback) {
|
|
834
|
+
this.#onClose = callback;
|
|
835
|
+
return this;
|
|
836
|
+
}
|
|
837
|
+
onError(callback) {
|
|
838
|
+
this.#onError = callback;
|
|
839
|
+
return this;
|
|
840
|
+
}
|
|
841
|
+
async close() {
|
|
842
|
+
await this.#watcher?.close();
|
|
843
|
+
if (this.#testsProcess) {
|
|
844
|
+
this.#testsProcess.removeAllListeners();
|
|
845
|
+
this.#testsProcess.kill("SIGKILL");
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
async run() {
|
|
849
|
+
this.#stickyPort = String(await getPort(this.cwd));
|
|
850
|
+
this.#indexGenerator = new IndexGenerator(this.cwdPath, this.ui.logger);
|
|
851
|
+
this.#clearScreen();
|
|
852
|
+
this.ui.logger.info("loading hooks...");
|
|
853
|
+
this.#hooks = await loadHooks(this.options.hooks, [
|
|
854
|
+
"init",
|
|
855
|
+
"testsStarting",
|
|
856
|
+
"testsFinished"
|
|
857
|
+
]);
|
|
858
|
+
await this.#hooks.runner("init").run(this, this.#indexGenerator);
|
|
859
|
+
this.#hooks.clear("init");
|
|
860
|
+
this.ui.logger.info("generating indexes...");
|
|
861
|
+
await this.#indexGenerator.generate();
|
|
862
|
+
this.ui.logger.info("booting application to run tests...");
|
|
863
|
+
await this.#runTests(this.#stickyPort);
|
|
864
|
+
}
|
|
865
|
+
async runAndWatch(options) {
|
|
866
|
+
const tsConfig = readTsConfig(this.cwdPath);
|
|
867
|
+
if (!tsConfig) {
|
|
868
|
+
this.#onError?.(new RuntimeException("Unable to parse tsconfig file"));
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
this.#stickyPort = String(await getPort(this.cwd));
|
|
872
|
+
this.#indexGenerator = new IndexGenerator(this.cwdPath, this.ui.logger);
|
|
873
|
+
this.#fileSystem = new FileSystem(this.cwdPath, tsConfig, {
|
|
874
|
+
...this.options,
|
|
875
|
+
suites: this.options.suites?.filter((suite) => {
|
|
876
|
+
if (this.options.filters.suites) return this.options.filters.suites.includes(suite.name);
|
|
877
|
+
return true;
|
|
878
|
+
})
|
|
879
|
+
});
|
|
880
|
+
this.#clearScreen();
|
|
881
|
+
this.ui.logger.info("loading hooks...");
|
|
882
|
+
this.#hooks = await loadHooks(this.options.hooks, [
|
|
883
|
+
"init",
|
|
884
|
+
"testsStarting",
|
|
885
|
+
"testsFinished",
|
|
886
|
+
"fileAdded",
|
|
887
|
+
"fileChanged",
|
|
888
|
+
"fileRemoved"
|
|
889
|
+
]);
|
|
890
|
+
this.#registerServerRestartHooks();
|
|
891
|
+
await this.#hooks.runner("init").run(this, this.#indexGenerator);
|
|
892
|
+
this.#hooks.clear("init");
|
|
893
|
+
this.ui.logger.info("generating indexes...");
|
|
894
|
+
await this.#indexGenerator.generate();
|
|
895
|
+
this.ui.logger.info("booting application to run tests...");
|
|
896
|
+
await this.#runTests(this.#stickyPort);
|
|
897
|
+
this.#watcher = watch({
|
|
898
|
+
usePolling: options?.poll ?? false,
|
|
899
|
+
cwd: this.cwdPath,
|
|
900
|
+
ignoreInitial: true,
|
|
901
|
+
ignored: (file, stats) => {
|
|
902
|
+
if (!stats) return false;
|
|
903
|
+
if (stats.isFile()) return !this.#fileSystem.shouldWatchFile(file);
|
|
904
|
+
return !this.#fileSystem.shouldWatchDirectory(file);
|
|
905
|
+
}
|
|
906
|
+
});
|
|
907
|
+
this.#watcher.on("ready", () => {
|
|
908
|
+
this.ui.logger.info("watching file system for changes...");
|
|
909
|
+
});
|
|
910
|
+
this.#watcher.on("error", (error) => {
|
|
911
|
+
this.ui.logger.warning("file system watcher failure");
|
|
912
|
+
this.ui.logger.fatal(error);
|
|
913
|
+
this.#onError?.(error);
|
|
914
|
+
this.#watcher?.close();
|
|
915
|
+
});
|
|
916
|
+
this.#watcher.on("add", (filePath) => {
|
|
917
|
+
const relativePath = string.toUnixSlash(filePath);
|
|
918
|
+
const absolutePath = join(this.cwdPath, filePath);
|
|
919
|
+
this.#hooks.runner("fileAdded").run(relativePath, absolutePath, this);
|
|
920
|
+
});
|
|
921
|
+
this.#watcher.on("change", (filePath) => {
|
|
922
|
+
const relativePath = string.toUnixSlash(filePath);
|
|
923
|
+
const absolutePath = join(this.cwdPath, filePath);
|
|
924
|
+
this.#hooks.runner("fileChanged").run(relativePath, absolutePath, {
|
|
925
|
+
source: "watcher",
|
|
926
|
+
fullReload: true,
|
|
927
|
+
hotReloaded: false
|
|
928
|
+
}, this);
|
|
929
|
+
});
|
|
930
|
+
this.#watcher.on("unlink", (filePath) => {
|
|
931
|
+
const relativePath = string.toUnixSlash(filePath);
|
|
932
|
+
const absolutePath = join(this.cwdPath, filePath);
|
|
933
|
+
this.#hooks.runner("fileRemoved").run(relativePath, absolutePath, this);
|
|
934
|
+
});
|
|
935
|
+
}
|
|
1709
936
|
};
|
|
937
|
+
export { Bundler, DevServer, FileBuffer, SUPPORTED_PACKAGE_MANAGERS, TestRunner, VirtualFileSystem };
|