qunitx-cli 0.5.7 → 0.5.8

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/.dockerignore ADDED
@@ -0,0 +1,15 @@
1
+ .claude
2
+ .env
3
+ .github
4
+ .gitignore
5
+ benches
6
+ CHANGELOG.md
7
+ cliff.toml
8
+ docs
9
+ flake.lock
10
+ flake.nix
11
+ Makefile
12
+ scripts
13
+ test
14
+ tmp
15
+ TODO
package/README.md CHANGED
@@ -133,6 +133,34 @@ qunitx some-test.js --debug
133
133
  qunitx some-test.ts
134
134
  ```
135
135
 
136
+ ## Configuration
137
+
138
+ All CLI flags can also be set in `package.json` under the `qunitx` key, so you don't have to repeat them on every invocation:
139
+
140
+ ```json
141
+ {
142
+ "qunitx": {
143
+ "inputs": ["test/**/*-test.js", "test/**/*-test.ts"],
144
+ "extensions": ["js", "ts"],
145
+ "output": "tmp",
146
+ "timeout": 20000,
147
+ "failFast": false,
148
+ "port": 1234
149
+ }
150
+ }
151
+ ```
152
+
153
+ | Key | Default | Description |
154
+ |-----|---------|-------------|
155
+ | `inputs` | `[]` | Glob patterns, file paths, or directories to use as test entry points. Merged with any paths given on the CLI. |
156
+ | `extensions` | `["js", "ts"]` | File extensions tracked for test discovery (directory scans) and watch-mode rebuild triggers. Add `"mjs"`, `"cjs"`, or any other extension your project uses. |
157
+ | `output` | `"tmp"` | Directory where compiled test bundles are written. |
158
+ | `timeout` | `20000` | Maximum milliseconds to wait for the full test suite before timing out. |
159
+ | `failFast` | `false` | Stop the run after the first failing test. |
160
+ | `port` | `1234` | Preferred HTTP server port. qunitx auto-selects a free port if this one is taken. |
161
+
162
+ CLI flags always override `package.json` values when both are present.
163
+
136
164
  ## CLI Reference
137
165
 
138
166
  ```
@@ -144,6 +172,7 @@ Options:
144
172
  --debug Print the server URL; pipe browser console to stdout
145
173
  --timeout=<ms> Max ms to wait for the suite to finish [default: 20000]
146
174
  --output=<dir> Directory for compiled test assets [default: ./tmp]
175
+ --extensions=<...> Comma-separated file extensions to track [default: js,ts]
147
176
  --before=<file> Script to run (and optionally await) before tests start
148
177
  --after=<file> Script to run (and optionally await) after tests finish
149
178
  --port=<n> HTTP server port (auto-selects a free port if taken)
package/deno.json CHANGED
@@ -11,6 +11,11 @@
11
11
  "recursive-lookup": "npm:recursive-lookup",
12
12
  "ws": "npm:ws"
13
13
  },
14
+ "tasks": {
15
+ "bench": "deno bench --allow-all benches/esbuild.bench.ts benches/server.bench.ts benches/tap.bench.ts benches/e2e.bench.ts",
16
+ "bench:check": "deno bench --allow-all --json benches/esbuild.bench.ts benches/server.bench.ts benches/tap.bench.ts benches/e2e.bench.ts | deno run --allow-all scripts/check-benchmarks.ts",
17
+ "bench:update": "deno bench --allow-all --json benches/esbuild.bench.ts benches/server.bench.ts benches/tap.bench.ts benches/e2e.bench.ts | deno run --allow-all scripts/check-benchmarks.ts --save"
18
+ },
14
19
  "lint": {
15
20
  "rules": {
16
21
  "exclude": ["no-process-global", "no-node-globals", "no-window"]
package/deno.lock CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "version": "5",
3
3
  "specifiers": {
4
+ "jsr:@std/fmt@*": "1.0.9",
4
5
  "npm:@types/js-yaml@*": "4.0.9",
5
6
  "npm:@types/picomatch@*": "4.0.2",
6
7
  "npm:@types/ws@*": "8.18.1",
@@ -24,10 +25,14 @@
24
25
  "npm:qunit@^2.25.0": "2.25.0",
25
26
  "npm:qunitx@1": "1.0.0",
26
27
  "npm:recursive-lookup@*": "1.1.0",
27
- "npm:recursive-lookup@1.1.0": "1.1.0",
28
28
  "npm:ws@*": "8.19.0",
29
29
  "npm:ws@^8.19.0": "8.19.0"
30
30
  },
31
+ "jsr": {
32
+ "@std/fmt@1.0.9": {
33
+ "integrity": "2487343e8899fb2be5d0e3d35013e54477ada198854e52dd05ed0422eddcabe0"
34
+ }
35
+ },
31
36
  "npm": {
32
37
  "@babel/code-frame@7.29.0": {
33
38
  "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
@@ -1333,7 +1338,6 @@
1333
1338
  "npm:puppeteer@^24.38.0",
1334
1339
  "npm:qunit@^2.25.0",
1335
1340
  "npm:qunitx@1",
1336
- "npm:recursive-lookup@1.1.0",
1337
1341
  "npm:ws@^8.19.0"
1338
1342
  ]
1339
1343
  }
@@ -23,6 +23,7 @@ ${color('--timeout')} : change default timeout per test case
23
23
  ${color('--output')} : folder to distribute built qunitx html and js that a webservers can run[default: tmp]
24
24
  ${color('--failFast')} : run the target file or folders with immediate abort if a single test fails
25
25
  ${color('--port')} : HTTP server port (auto-selects a free port if the given port is taken)[default: 1234]
26
+ ${color('--extensions')} : comma-separated file extensions to track for discovery and watch-mode rebuilds[default: js,ts]
26
27
  ${color('--before')} : run a script before the tests(i.e start a new web server before tests)
27
28
  ${color('--after')} : run a script after the tests(i.e save test results to a file)
28
29
 
@@ -1,7 +1,8 @@
1
- /** Default qunitx config values: build output directory, test timeout (ms), fail-fast flag, and HTTP server port. */
1
+ /** Default qunitx config values: build output directory, test timeout (ms), fail-fast flag, HTTP server port, and tracked file extensions. */
2
2
  export default {
3
3
  output: 'tmp',
4
4
  timeout: 20000,
5
5
  failFast: false,
6
6
  port: 1234,
7
+ extensions: ['js', 'ts'],
7
8
  };
@@ -6,52 +6,14 @@ import kleur from 'kleur';
6
6
  * @returns {object}
7
7
  */
8
8
  export default function setupFileWatchers(testFileLookupPaths, config, onEventFunc, onFinishFunc) {
9
- const extensions = ['js', 'ts'];
9
+ const extensions = config.extensions || ['js', 'ts'];
10
10
  const fileWatchers = testFileLookupPaths.reduce((watcher, watchPath) => {
11
11
  return Object.assign(watcher, {
12
- [watchPath]: chokidar.watch(watchPath, { ignoreInitial: true }).on('all', (event, path) => {
13
- if (extensions.some((extension) => path.endsWith(extension))) {
14
- mutateFSTree(config.fsTree, event, path);
15
-
16
- console.log(
17
- '#',
18
- kleur
19
- .magenta()
20
- .bold('=================================================================='),
21
- );
22
- console.log('#', getEventColor(event), path.split(config.projectRoot)[1]);
23
- console.log(
24
- '#',
25
- kleur
26
- .magenta()
27
- .bold('=================================================================='),
28
- );
29
-
30
- if (!global.chokidarBuild) {
31
- global.chokidarBuild = true;
32
-
33
- const result = extensions.some((extension) => path.endsWith(extension))
34
- ? onEventFunc(event, path)
35
- : null;
36
-
37
- if (!(result instanceof Promise)) {
38
- global.chokidarBuild = false;
39
-
40
- return result;
41
- }
42
-
43
- result
44
- .then(() => {
45
- onFinishFunc ? onFinishFunc(event, path) : null;
46
- })
47
- .catch(() => {
48
- // TODO: make an index.html to display the error
49
- // error type has to be derived from the error!
50
- })
51
- .finally(() => (global.chokidarBuild = false));
52
- }
53
- }
54
- }),
12
+ [watchPath]: chokidar
13
+ .watch(watchPath, { ignoreInitial: true })
14
+ .on('all', (event, filePath) =>
15
+ handleWatchEvent(config, extensions, event, filePath, onEventFunc, onFinishFunc),
16
+ ),
55
17
  });
56
18
  }, {});
57
19
 
@@ -65,6 +27,51 @@ export default function setupFileWatchers(testFileLookupPaths, config, onEventFu
65
27
  };
66
28
  }
67
29
 
30
+ /**
31
+ * Routes a chokidar event to fsTree mutation and optional rebuild trigger.
32
+ * `unlinkDir` bypasses the extension filter so deleted directories always clean up fsTree.
33
+ * @returns {void}
34
+ */
35
+ export function handleWatchEvent(config, extensions, event, filePath, onEventFunc, onFinishFunc) {
36
+ const isFileEvent = extensions.some((ext) => filePath.endsWith(`.${ext}`));
37
+
38
+ if (!isFileEvent && event !== 'unlinkDir') return;
39
+
40
+ mutateFSTree(config.fsTree, event, filePath);
41
+
42
+ console.log(
43
+ '#',
44
+ kleur.magenta().bold('=================================================================='),
45
+ );
46
+ console.log('#', getEventColor(event), filePath.split(config.projectRoot)[1]);
47
+ console.log(
48
+ '#',
49
+ kleur.magenta().bold('=================================================================='),
50
+ );
51
+
52
+ if (!config._building) {
53
+ config._building = true;
54
+
55
+ const result = onEventFunc(event, filePath);
56
+
57
+ if (!(result instanceof Promise)) {
58
+ config._building = false;
59
+
60
+ return result;
61
+ }
62
+
63
+ result
64
+ .then(() => {
65
+ onFinishFunc ? onFinishFunc(event, filePath) : null;
66
+ })
67
+ .catch(() => {
68
+ // TODO: make an index.html to display the error
69
+ // error type has to be derived from the error!
70
+ })
71
+ .finally(() => (config._building = false));
72
+ }
73
+ }
74
+
68
75
  /**
69
76
  * Mutates `fsTree` in place based on a chokidar file-system event.
70
77
  * @returns {void}
@@ -1,15 +1,21 @@
1
1
  import fs from 'node:fs/promises';
2
+ import path from 'node:path';
2
3
  // @deno-types="npm:@types/picomatch"
3
4
  import picomatch from 'picomatch';
4
- // @deno-types="./recursive-lookup.d.ts"
5
- import recursiveLookup from 'recursive-lookup';
5
+
6
+ async function readDirRecursive(dir, filter) {
7
+ const entries = await fs.readdir(dir, { recursive: true, withFileTypes: true });
8
+ return entries
9
+ .filter((e) => e.isFile() && filter(e.name))
10
+ .map((e) => path.join(e.parentPath, e.name));
11
+ }
6
12
 
7
13
  /**
8
14
  * Resolves an array of file paths, directories, or glob patterns into a flat `{ absolutePath: null }` map.
9
15
  * @returns {Promise<object>}
10
16
  */
11
- export default async function buildFSTree(fileAbsolutePaths, _config = {}) {
12
- const targetExtensions = ['js', 'ts'];
17
+ export default async function buildFSTree(fileAbsolutePaths, config = {}) {
18
+ const targetExtensions = config.extensions || ['js', 'ts'];
13
19
  const fsTree = {};
14
20
 
15
21
  await Promise.all(
@@ -20,8 +26,8 @@ export default async function buildFSTree(fileAbsolutePaths, _config = {}) {
20
26
 
21
27
  try {
22
28
  if (glob.isGlob) {
23
- const fileNames = await recursiveLookup(glob.base, (path) => {
24
- return targetExtensions.some((extension) => path.endsWith(extension));
29
+ const fileNames = await readDirRecursive(glob.base, (name) => {
30
+ return targetExtensions.some((extension) => name.endsWith(`.${extension}`));
25
31
  });
26
32
 
27
33
  fileNames.forEach((fileName) => {
@@ -35,8 +41,8 @@ export default async function buildFSTree(fileAbsolutePaths, _config = {}) {
35
41
  if (entry.isFile()) {
36
42
  fsTree[fileAbsolutePath] = null;
37
43
  } else if (entry.isDirectory()) {
38
- const fileNames = await recursiveLookup(glob.base, (path) => {
39
- return targetExtensions.some((extension) => path.endsWith(extension));
44
+ const fileNames = await readDirRecursive(glob.base, (name) => {
45
+ return targetExtensions.some((extension) => name.endsWith(`.${extension}`));
40
46
  });
41
47
 
42
48
  fileNames.forEach((fileName) => {
@@ -26,6 +26,13 @@ export default function parseCliFlags(projectRoot) {
26
26
  return result;
27
27
  } else if (arg.startsWith('--port')) {
28
28
  return Object.assign(result, { port: Number(arg.split('=')[1]) });
29
+ } else if (arg.startsWith('--extensions')) {
30
+ return Object.assign(result, {
31
+ extensions: arg
32
+ .split('=')[1]
33
+ .split(',')
34
+ .map((e) => e.trim()),
35
+ });
29
36
  } else if (arg.startsWith('--before')) {
30
37
  return Object.assign(result, { before: parseModule(arg.split('=')[1]) });
31
38
  } else if (arg.startsWith('--after')) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "qunitx-cli",
3
3
  "type": "module",
4
- "version": "0.5.7",
4
+ "version": "0.5.8",
5
5
  "description": "Browser runner for QUnitx: run your qunitx tests in google-chrome",
6
6
  "main": "cli.js",
7
7
  "author": "Izel Nakri",
@@ -50,7 +50,6 @@
50
50
  "kleur": "^4.1.5",
51
51
  "picomatch": "^4.0.3",
52
52
  "puppeteer": "^24.38.0",
53
- "recursive-lookup": "1.1.0",
54
53
  "ws": "^8.19.0"
55
54
  },
56
55
  "devDependencies": {
@@ -1,7 +0,0 @@
1
- /** Recursively lists files under `folderPath`, optionally filtered by a predicate. */
2
- declare function recursiveLookup(
3
- folderPath: string,
4
- filter?: (name: string) => boolean,
5
- ): Promise<string[]>;
6
-
7
- export default recursiveLookup;