@adonisjs/assembler 6.1.3-4 → 6.1.3-6

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.
@@ -0,0 +1,339 @@
1
+ /*
2
+ * @adonisjs/assembler
3
+ *
4
+ * (c) AdonisJS
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+
10
+ import picomatch from 'picomatch'
11
+ import type tsStatic from 'typescript'
12
+ import { type ExecaChildProcess } from 'execa'
13
+ import { cliui, type Logger } from '@poppinss/cliui'
14
+ import type { Watcher } from '@poppinss/chokidar-ts'
15
+
16
+ import type { TestRunnerOptions } from './types.js'
17
+ import { AssetsDevServer } from './assets_dev_server.js'
18
+ import { getPort, isDotEnvFile, runNode, watch } from './helpers.js'
19
+
20
+ /**
21
+ * Instance of CLIUI
22
+ */
23
+ const ui = cliui()
24
+
25
+ /**
26
+ * Exposes the API to start the development. Optionally, the watch API can be
27
+ * used to watch for file changes and restart the development server.
28
+ *
29
+ * The Dev server performs the following actions
30
+ *
31
+ * - Assigns a random PORT, when PORT inside .env file is in use
32
+ * - Uses tsconfig.json file to collect a list of files to watch.
33
+ * - Uses metaFiles from .adonisrc.json file to collect a list of files to watch.
34
+ * - Restart HTTP server on every file change.
35
+ */
36
+ export class TestRunner {
37
+ #cwd: URL
38
+ #logger = ui.logger
39
+ #options: TestRunnerOptions
40
+ #scriptFile: string = 'bin/test.js'
41
+ #isMetaFile: picomatch.Matcher
42
+ #isTestFile: picomatch.Matcher
43
+
44
+ /**
45
+ * In watch mode, after a file is changed, we wait for the current
46
+ * set of tests to finish before triggering a re-run. Therefore,
47
+ * we use this flag to know if we are already busy in running
48
+ * tests and ignore file-changes.
49
+ */
50
+ #isBusy: boolean = false
51
+
52
+ #onError?: (error: any) => any
53
+ #onClose?: (exitCode: number) => any
54
+
55
+ #testScript?: ExecaChildProcess<string>
56
+ #watcher?: ReturnType<Watcher['watch']>
57
+ #assetsServer?: AssetsDevServer
58
+
59
+ /**
60
+ * Getting reference to colors library from logger
61
+ */
62
+ get #colors() {
63
+ return this.#logger.getColors()
64
+ }
65
+
66
+ constructor(cwd: URL, options: TestRunnerOptions) {
67
+ this.#cwd = cwd
68
+ this.#options = options
69
+ this.#isMetaFile = picomatch((this.#options.metaFiles || []).map(({ pattern }) => pattern))
70
+ this.#isTestFile = picomatch(
71
+ this.#options.suites
72
+ .filter((suite) => {
73
+ if (this.#options.filters.suites) {
74
+ this.#options.filters.suites.includes(suite.name)
75
+ }
76
+
77
+ return true
78
+ })
79
+ .map((suite) => suite.files)
80
+ .flat(1)
81
+ )
82
+ }
83
+
84
+ /**
85
+ * Converts all known filters to CLI args.
86
+ *
87
+ * The following code snippet may seem like repetitive code. But, it
88
+ * is done intentionally to have visibility around how each filter
89
+ * is converted to an arg.
90
+ */
91
+ #convertFiltersToArgs(filters: TestRunnerOptions['filters']): string[] {
92
+ const args: string[] = []
93
+
94
+ if (filters.suites) {
95
+ args.push(...filters.suites)
96
+ }
97
+
98
+ if (filters.files) {
99
+ args.push('--files')
100
+ args.push(filters.files.join(','))
101
+ }
102
+
103
+ if (filters.groups) {
104
+ args.push('--groups')
105
+ args.push(filters.groups.join(','))
106
+ }
107
+
108
+ if (filters.tags) {
109
+ args.push('--tags')
110
+ args.push(filters.tags.join(','))
111
+ }
112
+
113
+ if (filters.ignoreTags) {
114
+ args.push('--ignore-tags')
115
+ args.push(filters.ignoreTags.join(','))
116
+ }
117
+
118
+ if (filters.tests) {
119
+ args.push('--ignore-tests')
120
+ args.push(filters.tests.join(','))
121
+ }
122
+
123
+ if (filters.match) {
124
+ args.push('--match')
125
+ args.push(filters.match.join(','))
126
+ }
127
+
128
+ return args
129
+ }
130
+
131
+ /**
132
+ * Conditionally clear the terminal screen
133
+ */
134
+ #clearScreen() {
135
+ if (this.#options.clearScreen) {
136
+ process.stdout.write('\u001Bc')
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Runs tests
142
+ */
143
+ #runTests(port: string, filtersArgs: string[], mode: 'blocking' | 'nonblocking') {
144
+ this.#isBusy = true
145
+
146
+ this.#testScript = runNode(this.#cwd, {
147
+ script: this.#scriptFile,
148
+ env: { PORT: port, ...this.#options.env },
149
+ nodeArgs: this.#options.nodeArgs,
150
+ scriptArgs: filtersArgs.concat(this.#options.scriptArgs),
151
+ })
152
+
153
+ this.#testScript
154
+ .then((result) => {
155
+ if (mode === 'nonblocking') {
156
+ this.#onClose?.(result.exitCode)
157
+ this.#watcher?.close()
158
+ this.#assetsServer?.stop()
159
+ }
160
+ })
161
+ .catch((error) => {
162
+ this.#logger.warning(`unable to run test script "${this.#scriptFile}"`)
163
+ this.#logger.fatal(error)
164
+ this.#onError?.(error)
165
+ this.#watcher?.close()
166
+ this.#assetsServer?.stop()
167
+ })
168
+ .finally(() => {
169
+ this.#isBusy = false
170
+ })
171
+ }
172
+
173
+ /**
174
+ * Starts the assets server
175
+ */
176
+ #startAssetsServer() {
177
+ this.#assetsServer = new AssetsDevServer(this.#cwd, this.#options.assets)
178
+ this.#assetsServer.setLogger(this.#logger)
179
+ this.#assetsServer.start()
180
+ }
181
+
182
+ /**
183
+ * Handles a non TypeScript file change
184
+ */
185
+ #handleFileChange(action: string, port: string, filters: string[], relativePath: string) {
186
+ if (this.#isBusy) {
187
+ return
188
+ }
189
+
190
+ if (isDotEnvFile(relativePath) || this.#isMetaFile(relativePath)) {
191
+ this.#clearScreen()
192
+ this.#logger.log(`${this.#colors.green(action)} ${relativePath}`)
193
+ this.#runTests(port, filters, 'blocking')
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Handles TypeScript source file change
199
+ */
200
+ #handleSourceFileChange(action: string, port: string, filters: string[], relativePath: string) {
201
+ if (this.#isBusy) {
202
+ return
203
+ }
204
+
205
+ this.#clearScreen()
206
+ this.#logger.log(`${this.#colors.green(action)} ${relativePath}`)
207
+
208
+ /**
209
+ * If changed file is a test file after considering the initial filters,
210
+ * then only run that file
211
+ */
212
+ if (this.#isTestFile(relativePath)) {
213
+ this.#runTests(
214
+ port,
215
+ this.#convertFiltersToArgs({
216
+ ...this.#options.filters,
217
+ files: [relativePath],
218
+ }),
219
+ 'blocking'
220
+ )
221
+ return
222
+ }
223
+
224
+ this.#runTests(port, filters, 'blocking')
225
+ }
226
+
227
+ /**
228
+ * Set a custom CLI UI logger
229
+ */
230
+ setLogger(logger: Logger) {
231
+ this.#logger = logger
232
+ this.#assetsServer?.setLogger(logger)
233
+ return this
234
+ }
235
+
236
+ /**
237
+ * Add listener to get notified when dev server is
238
+ * closed
239
+ */
240
+ onClose(callback: (exitCode: number) => any): this {
241
+ this.#onClose = callback
242
+ return this
243
+ }
244
+
245
+ /**
246
+ * Add listener to get notified when dev server exists
247
+ * with an error
248
+ */
249
+ onError(callback: (error: any) => any): this {
250
+ this.#onError = callback
251
+ return this
252
+ }
253
+
254
+ /**
255
+ * Runs tests
256
+ */
257
+ async run() {
258
+ const port = String(await getPort(this.#cwd))
259
+ const initialFilters = this.#convertFiltersToArgs(this.#options.filters)
260
+
261
+ this.#clearScreen()
262
+ this.#startAssetsServer()
263
+
264
+ this.#logger.info('booting application to run tests...')
265
+ this.#runTests(port, initialFilters, 'nonblocking')
266
+ }
267
+
268
+ /**
269
+ * Run tests in watch mode
270
+ */
271
+ async runAndWatch(ts: typeof tsStatic, options?: { poll: boolean }) {
272
+ const port = String(await getPort(this.#cwd))
273
+ const initialFilters = this.#convertFiltersToArgs(this.#options.filters)
274
+
275
+ this.#clearScreen()
276
+ this.#startAssetsServer()
277
+
278
+ this.#logger.info('booting application to run tests...')
279
+ this.#runTests(port, initialFilters, 'blocking')
280
+
281
+ /**
282
+ * Create watcher using tsconfig.json file
283
+ */
284
+ const output = watch(this.#cwd, ts, options || {})
285
+ if (!output) {
286
+ this.#onClose?.(1)
287
+ return
288
+ }
289
+
290
+ /**
291
+ * Storing reference to watcher, so that we can close it
292
+ * when HTTP server exists with error
293
+ */
294
+ this.#watcher = output.chokidar
295
+
296
+ /**
297
+ * Notify the watcher is ready
298
+ */
299
+ output.watcher.on('watcher:ready', () => {
300
+ this.#logger.info('watching file system for changes...')
301
+ })
302
+
303
+ /**
304
+ * Cleanup when watcher dies
305
+ */
306
+ output.chokidar.on('error', (error) => {
307
+ this.#logger.warning('file system watcher failure')
308
+ this.#logger.fatal(error)
309
+ this.#onError?.(error)
310
+ output.chokidar.close()
311
+ })
312
+
313
+ /**
314
+ * Changes in TypeScript source file
315
+ */
316
+ output.watcher.on('source:add', ({ relativePath }) =>
317
+ this.#handleSourceFileChange('add', port, initialFilters, relativePath)
318
+ )
319
+ output.watcher.on('source:change', ({ relativePath }) =>
320
+ this.#handleSourceFileChange('update', port, initialFilters, relativePath)
321
+ )
322
+ output.watcher.on('source:unlink', ({ relativePath }) =>
323
+ this.#handleSourceFileChange('delete', port, initialFilters, relativePath)
324
+ )
325
+
326
+ /**
327
+ * Changes in non-TypeScript source files
328
+ */
329
+ output.watcher.on('add', ({ relativePath }) =>
330
+ this.#handleFileChange('add', port, initialFilters, relativePath)
331
+ )
332
+ output.watcher.on('change', ({ relativePath }) =>
333
+ this.#handleFileChange('update', port, initialFilters, relativePath)
334
+ )
335
+ output.watcher.on('unlink', ({ relativePath }) =>
336
+ this.#handleFileChange('delete', port, initialFilters, relativePath)
337
+ )
338
+ }
339
+ }
package/src/types.ts CHANGED
@@ -33,17 +33,27 @@ export type MetaFile = {
33
33
  reloadServer: boolean
34
34
  }
35
35
 
36
+ /**
37
+ * Test suite defined in ".adonisrc.json" file
38
+ */
39
+ export type Suite = {
40
+ files: string[]
41
+ name: string
42
+ }
43
+
36
44
  /**
37
45
  * Options accepted by assets bundler
38
46
  */
39
47
  export type AssetsBundlerOptions =
40
48
  | {
41
49
  serve: false
50
+ args?: string[]
42
51
  driver?: string
43
52
  cmd?: string
44
53
  }
45
54
  | {
46
55
  serve: true
56
+ args: string[]
47
57
  driver: string
48
58
  cmd: string
49
59
  }
@@ -60,6 +70,37 @@ export type DevServerOptions = {
60
70
  assets?: AssetsBundlerOptions
61
71
  }
62
72
 
73
+ /**
74
+ * Options accepted by the test runner
75
+ */
76
+ export type TestRunnerOptions = {
77
+ /**
78
+ * Filter arguments are provided as a key-value
79
+ * pair, so that we can mutate them (if needed)
80
+ */
81
+ filters: Partial<{
82
+ tests: string[]
83
+ suites: string[]
84
+ groups: string[]
85
+ files: string[]
86
+ match: string[]
87
+ tags: string[]
88
+ ignoreTags: string[]
89
+ }>
90
+
91
+ /**
92
+ * All other tags are provided as a collection of
93
+ * arguments
94
+ */
95
+ scriptArgs: string[]
96
+ nodeArgs: string[]
97
+ clearScreen?: boolean
98
+ env?: NodeJS.ProcessEnv
99
+ metaFiles?: MetaFile[]
100
+ assets?: AssetsBundlerOptions
101
+ suites: Suite[]
102
+ }
103
+
63
104
  /**
64
105
  * Options accepted by the project bundler
65
106
  */
@@ -1,4 +0,0 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
- import type tsStatic from 'typescript';
3
- export declare function parseConfig(cwd: string | URL, ts: typeof tsStatic): tsStatic.ParsedCommandLine | undefined;
4
- //# sourceMappingURL=parse_config.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"parse_config.d.ts","sourceRoot":"","sources":["../../src/parse_config.ts"],"names":[],"mappings":";AASA,OAAO,KAAK,QAAQ,MAAM,YAAY,CAAA;AAOtC,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,EAAE,EAAE,EAAE,OAAO,QAAQ,0CAejE"}
@@ -1,15 +0,0 @@
1
- import { ConfigParser } from '@poppinss/chokidar-ts';
2
- export function parseConfig(cwd, ts) {
3
- const { config, error } = new ConfigParser(cwd, 'tsconfig.json', ts).parse();
4
- if (error) {
5
- const compilerHost = ts.createCompilerHost({});
6
- console.log(ts.formatDiagnosticsWithColorAndContext([error], compilerHost));
7
- return;
8
- }
9
- if (config.errors.length) {
10
- const compilerHost = ts.createCompilerHost({});
11
- console.log(ts.formatDiagnosticsWithColorAndContext(config.errors, compilerHost));
12
- return;
13
- }
14
- return config;
15
- }
@@ -1,5 +0,0 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
- import type { RunOptions } from './types.js';
3
- export declare function runNode(cwd: string | URL, options: RunOptions): import("execa").ExecaChildProcess<string>;
4
- export declare function run(cwd: string | URL, options: Omit<RunOptions, 'nodeArgs'>): import("execa").ExecaChildProcess<string>;
5
- //# sourceMappingURL=run.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/run.ts"],"names":[],"mappings":";AAUA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAkB5C,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,EAAE,OAAO,EAAE,UAAU,6CAgB7D;AAKD,wBAAgB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,EAAE,OAAO,EAAE,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,6CAe3E"}
package/build/src/run.js DELETED
@@ -1,37 +0,0 @@
1
- import { execaNode, execa } from 'execa';
2
- const DEFAULT_NODE_ARGS = [
3
- '--loader=ts-node/esm',
4
- '--no-warnings',
5
- '--experimental-import-meta-resolve',
6
- ];
7
- export function runNode(cwd, options) {
8
- const childProcess = execaNode(options.script, options.scriptArgs, {
9
- nodeOptions: DEFAULT_NODE_ARGS.concat(options.nodeArgs),
10
- preferLocal: true,
11
- windowsHide: false,
12
- localDir: cwd,
13
- cwd,
14
- buffer: false,
15
- stdio: options.stdio || 'inherit',
16
- env: {
17
- ...(options.stdio === 'pipe' ? { FORCE_COLOR: 'true' } : {}),
18
- ...options.env,
19
- },
20
- });
21
- return childProcess;
22
- }
23
- export function run(cwd, options) {
24
- const childProcess = execa(options.script, options.scriptArgs, {
25
- preferLocal: true,
26
- windowsHide: false,
27
- localDir: cwd,
28
- cwd,
29
- buffer: false,
30
- stdio: options.stdio || 'inherit',
31
- env: {
32
- ...(options.stdio === 'pipe' ? { FORCE_COLOR: 'true' } : {}),
33
- ...options.env,
34
- },
35
- });
36
- return childProcess;
37
- }
@@ -1,9 +0,0 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
- import type tsStatic from 'typescript';
3
- import { Watcher } from '@poppinss/chokidar-ts';
4
- import type { WatchOptions } from './types.js';
5
- export declare function watch(cwd: string | URL, ts: typeof tsStatic, options: WatchOptions): {
6
- watcher: Watcher;
7
- chokidar: import("chokidar").FSWatcher;
8
- } | undefined;
9
- //# sourceMappingURL=watch.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../../src/watch.ts"],"names":[],"mappings":";AASA,OAAO,KAAK,QAAQ,MAAM,YAAY,CAAA;AAEtC,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AAE/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAM9C,wBAAgB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,EAAE,EAAE,EAAE,OAAO,QAAQ,EAAE,OAAO,EAAE,YAAY;;;cASlF"}
@@ -1,12 +0,0 @@
1
- import { fileURLToPath } from 'node:url';
2
- import { Watcher } from '@poppinss/chokidar-ts';
3
- import { parseConfig } from './parse_config.js';
4
- export function watch(cwd, ts, options) {
5
- const config = parseConfig(cwd, ts);
6
- if (!config) {
7
- return;
8
- }
9
- const watcher = new Watcher(typeof cwd === 'string' ? cwd : fileURLToPath(cwd), config);
10
- const chokidar = watcher.watch(['.'], { usePolling: options.poll });
11
- return { watcher, chokidar };
12
- }
@@ -1,32 +0,0 @@
1
- /*
2
- * @adonisjs/assembler
3
- *
4
- * (c) AdonisJS
5
- *
6
- * For the full copyright and license information, please view the LICENSE
7
- * file that was distributed with this source code.
8
- */
9
-
10
- import type tsStatic from 'typescript'
11
- import { ConfigParser } from '@poppinss/chokidar-ts'
12
-
13
- /**
14
- * Parses tsconfig.json and prints errors using typescript compiler
15
- * host
16
- */
17
- export function parseConfig(cwd: string | URL, ts: typeof tsStatic) {
18
- const { config, error } = new ConfigParser(cwd, 'tsconfig.json', ts).parse()
19
- if (error) {
20
- const compilerHost = ts.createCompilerHost({})
21
- console.log(ts.formatDiagnosticsWithColorAndContext([error], compilerHost))
22
- return
23
- }
24
-
25
- if (config!.errors.length) {
26
- const compilerHost = ts.createCompilerHost({})
27
- console.log(ts.formatDiagnosticsWithColorAndContext(config!.errors, compilerHost))
28
- return
29
- }
30
-
31
- return config
32
- }
package/src/run.ts DELETED
@@ -1,65 +0,0 @@
1
- /*
2
- * @adonisjs/assembler
3
- *
4
- * (c) AdonisJS
5
- *
6
- * For the full copyright and license information, please view the LICENSE
7
- * file that was distributed with this source code.
8
- */
9
-
10
- import { execaNode, execa } from 'execa'
11
- import type { RunOptions } from './types.js'
12
-
13
- /**
14
- * Default set of args to pass in order to run TypeScript
15
- * source
16
- */
17
- const DEFAULT_NODE_ARGS = [
18
- // Use ts-node/esm loader. The project must install it
19
- '--loader=ts-node/esm',
20
- // Disable annonying warnings
21
- '--no-warnings',
22
- // Enable expiremental meta resolve for cases where someone uses magic import string
23
- '--experimental-import-meta-resolve',
24
- ]
25
-
26
- /**
27
- * Runs a Node.js script as a child process and inherits the stdio streams
28
- */
29
- export function runNode(cwd: string | URL, options: RunOptions) {
30
- const childProcess = execaNode(options.script, options.scriptArgs, {
31
- nodeOptions: DEFAULT_NODE_ARGS.concat(options.nodeArgs),
32
- preferLocal: true,
33
- windowsHide: false,
34
- localDir: cwd,
35
- cwd,
36
- buffer: false,
37
- stdio: options.stdio || 'inherit',
38
- env: {
39
- ...(options.stdio === 'pipe' ? { FORCE_COLOR: 'true' } : {}),
40
- ...options.env,
41
- },
42
- })
43
-
44
- return childProcess
45
- }
46
-
47
- /**
48
- * Runs a script as a child process and inherits the stdio streams
49
- */
50
- export function run(cwd: string | URL, options: Omit<RunOptions, 'nodeArgs'>) {
51
- const childProcess = execa(options.script, options.scriptArgs, {
52
- preferLocal: true,
53
- windowsHide: false,
54
- localDir: cwd,
55
- cwd,
56
- buffer: false,
57
- stdio: options.stdio || 'inherit',
58
- env: {
59
- ...(options.stdio === 'pipe' ? { FORCE_COLOR: 'true' } : {}),
60
- ...options.env,
61
- },
62
- })
63
-
64
- return childProcess
65
- }
package/src/watch.ts DELETED
@@ -1,29 +0,0 @@
1
- /*
2
- * @adonisjs/assembler
3
- *
4
- * (c) AdonisJS
5
- *
6
- * For the full copyright and license information, please view the LICENSE
7
- * file that was distributed with this source code.
8
- */
9
-
10
- import type tsStatic from 'typescript'
11
- import { fileURLToPath } from 'node:url'
12
- import { Watcher } from '@poppinss/chokidar-ts'
13
-
14
- import type { WatchOptions } from './types.js'
15
- import { parseConfig } from './parse_config.js'
16
-
17
- /**
18
- * Watches the file system using tsconfig file
19
- */
20
- export function watch(cwd: string | URL, ts: typeof tsStatic, options: WatchOptions) {
21
- const config = parseConfig(cwd, ts)
22
- if (!config) {
23
- return
24
- }
25
-
26
- const watcher = new Watcher(typeof cwd === 'string' ? cwd : fileURLToPath(cwd), config!)
27
- const chokidar = watcher.watch(['.'], { usePolling: options.poll })
28
- return { watcher, chokidar }
29
- }