@adonisjs/assembler 6.1.3-1 → 6.1.3-10

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,314 @@
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
+ import picomatch from 'picomatch';
10
+ import { cliui } from '@poppinss/cliui';
11
+ import { AssetsDevServer } from './assets_dev_server.js';
12
+ import { getPort, isDotEnvFile, runNode, watch } from './helpers.js';
13
+ /**
14
+ * Instance of CLIUI
15
+ */
16
+ const ui = cliui();
17
+ /**
18
+ * Exposes the API to start the development. Optionally, the watch API can be
19
+ * used to watch for file changes and restart the development server.
20
+ *
21
+ * The Dev server performs the following actions
22
+ *
23
+ * - Assigns a random PORT, when PORT inside .env file is in use
24
+ * - Uses tsconfig.json file to collect a list of files to watch.
25
+ * - Uses metaFiles from .adonisrc.json file to collect a list of files to watch.
26
+ * - Restart HTTP server on every file change.
27
+ */
28
+ export class TestRunner {
29
+ #cwd;
30
+ #logger = ui.logger;
31
+ #options;
32
+ #scriptFile = 'bin/test.js';
33
+ #isMetaFile;
34
+ #isTestFile;
35
+ #scriptArgs;
36
+ #initialFiltersArgs;
37
+ /**
38
+ * In watch mode, after a file is changed, we wait for the current
39
+ * set of tests to finish before triggering a re-run. Therefore,
40
+ * we use this flag to know if we are already busy in running
41
+ * tests and ignore file-changes.
42
+ */
43
+ #isBusy = false;
44
+ #onError;
45
+ #onClose;
46
+ #testScript;
47
+ #watcher;
48
+ #assetsServer;
49
+ /**
50
+ * Getting reference to colors library from logger
51
+ */
52
+ get #colors() {
53
+ return this.#logger.getColors();
54
+ }
55
+ constructor(cwd, options) {
56
+ this.#cwd = cwd;
57
+ this.#options = options;
58
+ this.#isMetaFile = picomatch((this.#options.metaFiles || []).map(({ pattern }) => pattern));
59
+ this.#isTestFile = picomatch(this.#options.suites
60
+ .filter((suite) => {
61
+ if (this.#options.filters.suites) {
62
+ return this.#options.filters.suites.includes(suite.name);
63
+ }
64
+ return true;
65
+ })
66
+ .map((suite) => suite.files)
67
+ .flat(1));
68
+ this.#scriptArgs = this.#convertOptionsToArgs();
69
+ this.#initialFiltersArgs = this.#convertFiltersToArgs(this.#options.filters);
70
+ }
71
+ /**
72
+ * Converts options to CLI args
73
+ */
74
+ #convertOptionsToArgs() {
75
+ const args = [];
76
+ if (this.#options.reporters) {
77
+ args.push('--reporters');
78
+ args.push(this.#options.reporters.join(','));
79
+ }
80
+ if (this.#options.timeout !== undefined) {
81
+ args.push('--timeout');
82
+ args.push(String(this.#options.timeout));
83
+ }
84
+ if (this.#options.failed) {
85
+ args.push('--failed');
86
+ }
87
+ if (this.#options.retries !== undefined) {
88
+ args.push('--timeout');
89
+ args.push(String(this.#options.retries));
90
+ }
91
+ return args;
92
+ }
93
+ /**
94
+ * Converts all known filters to CLI args.
95
+ *
96
+ * The following code snippet may seem like repetitive code. But, it
97
+ * is done intentionally to have visibility around how each filter
98
+ * is converted to an arg.
99
+ */
100
+ #convertFiltersToArgs(filters) {
101
+ const args = [];
102
+ if (filters.suites) {
103
+ args.push(...filters.suites);
104
+ }
105
+ if (filters.files) {
106
+ args.push('--files');
107
+ args.push(filters.files.join(','));
108
+ }
109
+ if (filters.groups) {
110
+ args.push('--groups');
111
+ args.push(filters.groups.join(','));
112
+ }
113
+ if (filters.tags) {
114
+ args.push('--tags');
115
+ args.push(filters.tags.join(','));
116
+ }
117
+ if (filters.ignoreTags) {
118
+ args.push('--ignore-tags');
119
+ args.push(filters.ignoreTags.join(','));
120
+ }
121
+ if (filters.tests) {
122
+ args.push('--ignore-tests');
123
+ args.push(filters.tests.join(','));
124
+ }
125
+ return args;
126
+ }
127
+ /**
128
+ * Conditionally clear the terminal screen
129
+ */
130
+ #clearScreen() {
131
+ if (this.#options.clearScreen) {
132
+ process.stdout.write('\u001Bc');
133
+ }
134
+ }
135
+ /**
136
+ * Runs tests
137
+ */
138
+ #runTests(port, mode, filters) {
139
+ this.#isBusy = true;
140
+ const scriptArgs = filters
141
+ ? this.#convertFiltersToArgs(filters).concat(this.#scriptArgs)
142
+ : this.#initialFiltersArgs.concat(this.#scriptArgs);
143
+ this.#testScript = runNode(this.#cwd, {
144
+ script: this.#scriptFile,
145
+ env: { PORT: port, ...this.#options.env },
146
+ nodeArgs: this.#options.nodeArgs,
147
+ scriptArgs,
148
+ });
149
+ this.#testScript
150
+ .then((result) => {
151
+ if (mode === 'nonblocking') {
152
+ this.#onClose?.(result.exitCode);
153
+ this.close();
154
+ }
155
+ })
156
+ .catch((error) => {
157
+ if (mode === 'nonblocking') {
158
+ this.#onError?.(error);
159
+ this.close();
160
+ }
161
+ })
162
+ .finally(() => {
163
+ this.#isBusy = false;
164
+ });
165
+ }
166
+ /**
167
+ * Restarts the HTTP server
168
+ */
169
+ #rerunTests(port, filters) {
170
+ if (this.#testScript) {
171
+ this.#testScript.removeAllListeners();
172
+ this.#testScript.kill('SIGKILL');
173
+ }
174
+ this.#runTests(port, 'blocking', filters);
175
+ }
176
+ /**
177
+ * Starts the assets server
178
+ */
179
+ #startAssetsServer() {
180
+ this.#assetsServer = new AssetsDevServer(this.#cwd, this.#options.assets);
181
+ this.#assetsServer.setLogger(this.#logger);
182
+ this.#assetsServer.start();
183
+ }
184
+ /**
185
+ * Handles a non TypeScript file change
186
+ */
187
+ #handleFileChange(action, port, relativePath) {
188
+ if (this.#isBusy) {
189
+ return;
190
+ }
191
+ if (isDotEnvFile(relativePath) || this.#isMetaFile(relativePath)) {
192
+ this.#clearScreen();
193
+ this.#logger.log(`${this.#colors.green(action)} ${relativePath}`);
194
+ this.#rerunTests(port);
195
+ }
196
+ }
197
+ /**
198
+ * Handles TypeScript source file change
199
+ */
200
+ #handleSourceFileChange(action, port, relativePath) {
201
+ if (this.#isBusy) {
202
+ return;
203
+ }
204
+ this.#clearScreen();
205
+ this.#logger.log(`${this.#colors.green(action)} ${relativePath}`);
206
+ /**
207
+ * If changed file is a test file after considering the initial filters,
208
+ * then only run that file
209
+ */
210
+ if (this.#isTestFile(relativePath)) {
211
+ this.#rerunTests(port, {
212
+ ...this.#options.filters,
213
+ files: [relativePath],
214
+ });
215
+ return;
216
+ }
217
+ this.#rerunTests(port);
218
+ }
219
+ /**
220
+ * Set a custom CLI UI logger
221
+ */
222
+ setLogger(logger) {
223
+ this.#logger = logger;
224
+ this.#assetsServer?.setLogger(logger);
225
+ return this;
226
+ }
227
+ /**
228
+ * Add listener to get notified when dev server is
229
+ * closed
230
+ */
231
+ onClose(callback) {
232
+ this.#onClose = callback;
233
+ return this;
234
+ }
235
+ /**
236
+ * Add listener to get notified when dev server exists
237
+ * with an error
238
+ */
239
+ onError(callback) {
240
+ this.#onError = callback;
241
+ return this;
242
+ }
243
+ /**
244
+ * Close watchers and running child processes
245
+ */
246
+ async close() {
247
+ await this.#watcher?.close();
248
+ this.#assetsServer?.stop();
249
+ if (this.#testScript) {
250
+ this.#testScript.removeAllListeners();
251
+ this.#testScript.kill('SIGKILL');
252
+ }
253
+ }
254
+ /**
255
+ * Runs tests
256
+ */
257
+ async run() {
258
+ const port = String(await getPort(this.#cwd));
259
+ this.#clearScreen();
260
+ this.#startAssetsServer();
261
+ this.#logger.info('booting application to run tests...');
262
+ this.#runTests(port, 'nonblocking');
263
+ }
264
+ /**
265
+ * Run tests in watch mode
266
+ */
267
+ async runAndWatch(ts, options) {
268
+ const port = String(await getPort(this.#cwd));
269
+ this.#clearScreen();
270
+ this.#startAssetsServer();
271
+ this.#logger.info('booting application to run tests...');
272
+ this.#runTests(port, 'blocking');
273
+ /**
274
+ * Create watcher using tsconfig.json file
275
+ */
276
+ const output = watch(this.#cwd, ts, options || {});
277
+ if (!output) {
278
+ this.#onClose?.(1);
279
+ return;
280
+ }
281
+ /**
282
+ * Storing reference to watcher, so that we can close it
283
+ * when HTTP server exists with error
284
+ */
285
+ this.#watcher = output.chokidar;
286
+ /**
287
+ * Notify the watcher is ready
288
+ */
289
+ output.watcher.on('watcher:ready', () => {
290
+ this.#logger.info('watching file system for changes...');
291
+ });
292
+ /**
293
+ * Cleanup when watcher dies
294
+ */
295
+ output.chokidar.on('error', (error) => {
296
+ this.#logger.warning('file system watcher failure');
297
+ this.#logger.fatal(error);
298
+ this.#onError?.(error);
299
+ output.chokidar.close();
300
+ });
301
+ /**
302
+ * Changes in TypeScript source file
303
+ */
304
+ output.watcher.on('source:add', ({ relativePath }) => this.#handleSourceFileChange('add', port, relativePath));
305
+ output.watcher.on('source:change', ({ relativePath }) => this.#handleSourceFileChange('update', port, relativePath));
306
+ output.watcher.on('source:unlink', ({ relativePath }) => this.#handleSourceFileChange('delete', port, relativePath));
307
+ /**
308
+ * Changes in non-TypeScript source files
309
+ */
310
+ output.watcher.on('add', ({ relativePath }) => this.#handleFileChange('add', port, relativePath));
311
+ output.watcher.on('change', ({ relativePath }) => this.#handleFileChange('update', port, relativePath));
312
+ output.watcher.on('unlink', ({ relativePath }) => this.#handleFileChange('delete', port, relativePath));
313
+ }
314
+ }
@@ -1,4 +1,7 @@
1
1
  /// <reference types="node" resolution-mode="require"/>
2
+ /**
3
+ * Options needed to run a script file
4
+ */
2
5
  export type RunOptions = {
3
6
  script: string;
4
7
  scriptArgs: string[];
@@ -6,22 +9,43 @@ export type RunOptions = {
6
9
  stdio?: 'pipe' | 'inherit';
7
10
  env?: NodeJS.ProcessEnv;
8
11
  };
12
+ /**
13
+ * Watcher options
14
+ */
9
15
  export type WatchOptions = {
10
16
  poll?: boolean;
11
17
  };
18
+ /**
19
+ * Meta file config defined in ".adonisrc.json" file
20
+ */
12
21
  export type MetaFile = {
13
22
  pattern: string;
14
23
  reloadServer: boolean;
15
24
  };
25
+ /**
26
+ * Test suite defined in ".adonisrc.json" file
27
+ */
28
+ export type Suite = {
29
+ files: string | string[];
30
+ name: string;
31
+ };
32
+ /**
33
+ * Options accepted by assets bundler
34
+ */
16
35
  export type AssetsBundlerOptions = {
17
36
  serve: false;
37
+ args?: string[];
18
38
  driver?: string;
19
39
  cmd?: string;
20
40
  } | {
21
41
  serve: true;
42
+ args: string[];
22
43
  driver: string;
23
44
  cmd: string;
24
45
  };
46
+ /**
47
+ * Options accepted by the dev server
48
+ */
25
49
  export type DevServerOptions = {
26
50
  scriptArgs: string[];
27
51
  nodeArgs: string[];
@@ -30,6 +54,41 @@ export type DevServerOptions = {
30
54
  metaFiles?: MetaFile[];
31
55
  assets?: AssetsBundlerOptions;
32
56
  };
57
+ /**
58
+ * Options accepted by the test runner
59
+ */
60
+ export type TestRunnerOptions = {
61
+ /**
62
+ * Filter arguments are provided as a key-value
63
+ * pair, so that we can mutate them (if needed)
64
+ */
65
+ filters: Partial<{
66
+ tests: string[];
67
+ suites: string[];
68
+ groups: string[];
69
+ files: string[];
70
+ tags: string[];
71
+ ignoreTags: string[];
72
+ }>;
73
+ reporters?: string[];
74
+ timeout?: number;
75
+ retries?: number;
76
+ failed?: boolean;
77
+ /**
78
+ * All other tags are provided as a collection of
79
+ * arguments
80
+ */
81
+ scriptArgs: string[];
82
+ nodeArgs: string[];
83
+ clearScreen?: boolean;
84
+ env?: NodeJS.ProcessEnv;
85
+ metaFiles?: MetaFile[];
86
+ assets?: AssetsBundlerOptions;
87
+ suites: Suite[];
88
+ };
89
+ /**
90
+ * Options accepted by the project bundler
91
+ */
33
92
  export type BundlerOptions = {
34
93
  metaFiles?: MetaFile[];
35
94
  assets?: AssetsBundlerOptions;
@@ -1 +1,9 @@
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
+ */
1
9
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adonisjs/assembler",
3
- "version": "6.1.3-1",
3
+ "version": "6.1.3-10",
4
4
  "description": "Provides utilities to run AdonisJS development server and build project for production",
5
5
  "main": "build/index.js",
6
6
  "type": "module",
@@ -13,11 +13,15 @@
13
13
  ".": "./build/index.js",
14
14
  "./types": "./build/src/types.js"
15
15
  },
16
+ "engines": {
17
+ "node": ">=18.16.0"
18
+ },
16
19
  "scripts": {
17
20
  "pretest": "npm run lint",
18
- "test": "cross-env NODE_DEBUG=chokidar:ts c8 npm run vscode:test",
21
+ "test": "cross-env NODE_DEBUG=chokidar:ts c8 npm run quick:test",
19
22
  "lint": "eslint . --ext=.ts",
20
23
  "clean": "del-cli build",
24
+ "typecheck": "tsc --noEmit",
21
25
  "compile": "npm run lint && npm run clean && tsc",
22
26
  "build": "npm run compile",
23
27
  "release": "np",
@@ -25,7 +29,7 @@
25
29
  "sync-labels": "github-label-sync --labels .github/labels.json adonisjs/assembler",
26
30
  "format": "prettier --write .",
27
31
  "prepublishOnly": "npm run build",
28
- "vscode:test": "node --loader=ts-node/esm bin/test.ts"
32
+ "quick:test": "node --loader=ts-node/esm bin/test.ts"
29
33
  },
30
34
  "keywords": [
31
35
  "adonisjs",
@@ -35,42 +39,38 @@
35
39
  "author": "virk,adonisjs",
36
40
  "license": "MIT",
37
41
  "devDependencies": {
38
- "@commitlint/cli": "^17.4.4",
39
- "@commitlint/config-conventional": "^17.4.4",
40
- "@japa/assert": "^1.4.1",
41
- "@japa/file-system": "^1.0.1",
42
- "@japa/run-failed-tests": "^1.1.1",
43
- "@japa/runner": "^2.5.1",
44
- "@japa/spec-reporter": "^1.3.3",
45
- "@swc/core": "^1.3.39",
46
- "@types/node": "^18.15.0",
47
- "c8": "^7.13.0",
42
+ "@adonisjs/eslint-config": "^1.1.7",
43
+ "@adonisjs/prettier-config": "^1.1.7",
44
+ "@adonisjs/tsconfig": "^1.1.7",
45
+ "@commitlint/cli": "^17.6.6",
46
+ "@commitlint/config-conventional": "^17.6.6",
47
+ "@japa/assert": "^2.0.0-1",
48
+ "@japa/file-system": "^2.0.0-1",
49
+ "@japa/runner": "^3.0.0-3",
50
+ "@swc/core": "^1.3.67",
51
+ "@types/node": "^20.4.1",
52
+ "@types/picomatch": "^2.3.0",
53
+ "c8": "^8.0.0",
48
54
  "cross-env": "^7.0.3",
49
55
  "del-cli": "^5.0.0",
50
- "eslint": "^8.36.0",
51
- "eslint-config-prettier": "^8.7.0",
52
- "eslint-plugin-adonis": "^3.0.3",
53
- "eslint-plugin-prettier": "^4.2.1",
56
+ "eslint": "^8.44.0",
54
57
  "github-label-sync": "^2.3.1",
55
58
  "husky": "^8.0.3",
56
- "np": "^7.6.3",
57
- "p-event": "^5.0.1",
58
- "prettier": "^2.8.4",
59
+ "np": "^8.0.4",
60
+ "p-event": "^6.0.0",
61
+ "prettier": "^2.8.8",
59
62
  "ts-node": "^10.9.1",
60
- "typescript": "^4.9.5"
63
+ "typescript": "^5.1.6"
61
64
  },
62
65
  "dependencies": {
63
- "@adonisjs/env": "^4.2.0-0",
64
- "@poppinss/chokidar-ts": "^4.1.0-1",
65
- "@poppinss/cliui": "^6.1.1-1",
66
- "@types/fs-extra": "^11.0.1",
67
- "@types/picomatch": "^2.3.0",
68
- "cpy": "^9.0.1",
66
+ "@adonisjs/env": "^4.2.0-3",
67
+ "@poppinss/chokidar-ts": "^4.1.0-4",
68
+ "@poppinss/cliui": "^6.1.1-3",
69
+ "cpy": "^8.1.2",
69
70
  "execa": "^7.0.0",
70
- "fs-extra": "^11.1.0",
71
- "get-port": "^6.1.2",
71
+ "get-port": "^7.0.0",
72
72
  "picomatch": "^2.3.1",
73
- "slash": "^5.0.0"
73
+ "slash": "^5.1.0"
74
74
  },
75
75
  "peerDependencies": {
76
76
  "typescript": "^4.0.0 || ^5.0.0"
@@ -83,36 +83,6 @@
83
83
  "url": "https://github.com/adonisjs/assembler/issues"
84
84
  },
85
85
  "homepage": "https://github.com/adonisjs/assembler#readme",
86
- "eslintConfig": {
87
- "extends": [
88
- "plugin:adonis/typescriptPackage",
89
- "prettier"
90
- ],
91
- "plugins": [
92
- "prettier"
93
- ],
94
- "rules": {
95
- "prettier/prettier": [
96
- "error",
97
- {
98
- "endOfLine": "auto"
99
- }
100
- ]
101
- }
102
- },
103
- "eslintIgnore": [
104
- "build"
105
- ],
106
- "prettier": {
107
- "trailingComma": "es5",
108
- "semi": false,
109
- "singleQuote": true,
110
- "useTabs": false,
111
- "quoteProps": "consistent",
112
- "bracketSpacing": true,
113
- "arrowParens": "always",
114
- "printWidth": 100
115
- },
116
86
  "commitlint": {
117
87
  "extends": [
118
88
  "@commitlint/config-conventional"
@@ -136,7 +106,14 @@
136
106
  "exclude": [
137
107
  "tests/**",
138
108
  "build/**",
139
- "examples/**"
109
+ "examples/**",
110
+ "src/dev_server.ts",
111
+ "src/test_runner.ts",
112
+ "src/assets_dev_server.ts"
140
113
  ]
141
- }
114
+ },
115
+ "eslintConfig": {
116
+ "extends": "@adonisjs/eslint-config/package"
117
+ },
118
+ "prettier": "@adonisjs/prettier-config"
142
119
  }
@@ -1,3 +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;
@@ -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,4 +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>;
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,8 +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;
@@ -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
- }