qunitx-cli 0.5.7 → 0.6.0

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/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",
@@ -13,9 +14,7 @@
13
14
  "npm:esbuild@~0.27.3": "0.27.4",
14
15
  "npm:express@^5.2.1": "5.2.1",
15
16
  "npm:js-yaml@*": "4.1.1",
16
- "npm:js-yaml@^4.1.1": "4.1.1",
17
17
  "npm:kleur@*": "4.1.5",
18
- "npm:kleur@^4.1.5": "4.1.5",
19
18
  "npm:picomatch@*": "4.0.3",
20
19
  "npm:picomatch@^4.0.3": "4.0.3",
21
20
  "npm:prettier@^3.8.1": "3.8.1",
@@ -24,10 +23,14 @@
24
23
  "npm:qunit@^2.25.0": "2.25.0",
25
24
  "npm:qunitx@1": "1.0.0",
26
25
  "npm:recursive-lookup@*": "1.1.0",
27
- "npm:recursive-lookup@1.1.0": "1.1.0",
28
26
  "npm:ws@*": "8.19.0",
29
27
  "npm:ws@^8.19.0": "8.19.0"
30
28
  },
29
+ "jsr": {
30
+ "@std/fmt@1.0.9": {
31
+ "integrity": "2487343e8899fb2be5d0e3d35013e54477ada198854e52dd05ed0422eddcabe0"
32
+ }
33
+ },
31
34
  "npm": {
32
35
  "@babel/code-frame@7.29.0": {
33
36
  "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
@@ -1326,14 +1329,11 @@
1326
1329
  "npm:cors@^2.8.6",
1327
1330
  "npm:esbuild@~0.27.3",
1328
1331
  "npm:express@^5.2.1",
1329
- "npm:js-yaml@^4.1.1",
1330
- "npm:kleur@^4.1.5",
1331
1332
  "npm:picomatch@^4.0.3",
1332
1333
  "npm:prettier@^3.8.1",
1333
1334
  "npm:puppeteer@^24.38.0",
1334
1335
  "npm:qunit@^2.25.0",
1335
1336
  "npm:qunitx@1",
1336
- "npm:recursive-lookup@1.1.0",
1337
1337
  "npm:ws@^8.19.0"
1338
1338
  ]
1339
1339
  }
@@ -1,5 +1,5 @@
1
1
  import fs from 'node:fs/promises';
2
- import kleur from 'kleur';
2
+ import { green } from '../utils/color.js';
3
3
  import findProjectRoot from '../utils/find-project-root.js';
4
4
  import pathExists from '../utils/path-exists.js';
5
5
  import readBoilerplate from '../utils/read-boilerplate.js';
@@ -29,5 +29,5 @@ export default async function generateTestFiles() {
29
29
  await fs.mkdir(targetFolderPaths.join('/'), { recursive: true });
30
30
  await fs.writeFile(path, testJSContent.replace('{{moduleName}}', moduleName));
31
31
 
32
- console.log(kleur.green(`${path} written`));
32
+ console.log(green(`${path} written`));
33
33
  }
@@ -1,8 +1,8 @@
1
- import kleur from 'kleur';
1
+ import { blue, magenta } from '../utils/color.js';
2
2
  import pkg from '../../package.json' with { type: 'json' };
3
3
 
4
- const highlight = (text) => kleur.magenta().bold(text);
5
- const color = (text) => kleur.blue(text);
4
+ const highlight = (text) => magenta().bold(text);
5
+ const color = (text) => blue(text);
6
6
 
7
7
  /** Prints qunitx-cli usage information to stdout. */
8
8
  export default function displayHelpOutput() {
@@ -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,5 +1,5 @@
1
1
  import fs from 'node:fs/promises';
2
- import kleur from 'kleur';
2
+ import { blue } from '../../utils/color.js';
3
3
  import esbuild from 'esbuild';
4
4
  import timeCounter from '../../utils/time-counter.js';
5
5
  import runUserModule from '../../utils/run-user-module.js';
@@ -141,7 +141,7 @@ async function runTestInsideHTMLFile(filePath, { page, server, browser }, config
141
141
  let QUNIT_RESULT;
142
142
  let targetError;
143
143
  try {
144
- console.log('#', kleur.blue(`QUnitX running: http://localhost:${config.port}${filePath}`));
144
+ console.log('#', blue(`QUnitX running: http://localhost:${config.port}${filePath}`));
145
145
 
146
146
  const testsDone = new Promise((resolve) => {
147
147
  config._testRunDone = resolve;
@@ -2,7 +2,7 @@ import fs from 'node:fs/promises';
2
2
  import { normalize } from 'node:path';
3
3
  import { availableParallelism } from 'node:os';
4
4
  import Puppeteer from 'puppeteer';
5
- import kleur from 'kleur';
5
+ import { blue, yellow } from '../utils/color.js';
6
6
  import runTestsInBrowser, { buildTestBundle } from './run/tests-in-browser.js';
7
7
  import setupBrowser from '../setup/browser.js';
8
8
  import fileWatcher from '../setup/file-watcher.js';
@@ -170,7 +170,7 @@ async function buildCachedContent(config, htmlPaths) {
170
170
  } else {
171
171
  console.log(
172
172
  '#',
173
- kleur.yellow(
173
+ yellow(
174
174
  `WARNING: Static html file with no {{content}} detected. Therefore ignoring ${filePath}`,
175
175
  ),
176
176
  );
@@ -225,11 +225,11 @@ function splitIntoGroups(files, groupCount) {
225
225
  function logWatcherAndKeyboardShortcutInfo(config, _server) {
226
226
  console.log(
227
227
  '#',
228
- kleur.blue(`Watching files... You can browse the tests on http://localhost:${config.port} ...`),
228
+ blue(`Watching files... You can browse the tests on http://localhost:${config.port} ...`),
229
229
  );
230
230
  console.log(
231
231
  '#',
232
- kleur.blue(
232
+ blue(
233
233
  `Shortcuts: Press "qq" to abort running tests, "qa" to run all the tests, "qf" to run last failing test, "ql" to repeat last test`,
234
234
  ),
235
235
  );
@@ -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
  };
@@ -1,57 +1,19 @@
1
1
  import chokidar from 'chokidar';
2
- import kleur from 'kleur';
2
+ import { green, magenta, red, yellow } from '../utils/color.js';
3
3
 
4
4
  /**
5
5
  * Starts chokidar watchers for each lookup path and calls `onEventFunc` on JS/TS file changes, debounced via a global flag.
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
+ magenta().bold('=================================================================='),
45
+ );
46
+ console.log('#', getEventColor(event), filePath.split(config.projectRoot)[1]);
47
+ console.log(
48
+ '#',
49
+ 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}
@@ -83,10 +90,10 @@ export function mutateFSTree(fsTree, event, path) {
83
90
 
84
91
  function getEventColor(event) {
85
92
  if (event === 'change') {
86
- return kleur.yellow('CHANGED:');
93
+ return yellow('CHANGED:');
87
94
  } else if (event === 'add' || event === 'addDir') {
88
- return kleur.green('ADDED:');
95
+ return green('ADDED:');
89
96
  } else if (event === 'unlink' || event === 'unlinkDir') {
90
- return kleur.red('REMOVED:');
97
+ return red('REMOVED:');
91
98
  }
92
99
  }
@@ -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) => {
@@ -1,4 +1,4 @@
1
- import kleur from 'kleur';
1
+ import { blue } from '../utils/color.js';
2
2
  import listenToKeyboardKey from '../utils/listen-to-keyboard-key.js';
3
3
  import runTestsInBrowser from '../commands/run/tests-in-browser.js';
4
4
 
@@ -16,10 +16,7 @@ export default function setupKeyboardEvents(config, cachedContent, connections)
16
16
  abortBrowserQUnit(config, connections);
17
17
 
18
18
  if (!config.lastFailedTestFiles) {
19
- console.log(
20
- '#',
21
- kleur.blue(`QUnitX: No tests failed so far, so repeating the last test run`),
22
- );
19
+ console.log('#', blue(`QUnitX: No tests failed so far, so repeating the last test run`));
23
20
  return runTestsInBrowser(config, cachedContent, connections, config.lastRanTestFiles);
24
21
  }
25
22
 
@@ -1,5 +1,4 @@
1
- // @deno-types="npm:@types/js-yaml"
2
- import yaml from 'js-yaml';
1
+ import dumpYaml from './dump-yaml.js';
3
2
  import indentString from '../utils/indent-string.js';
4
3
 
5
4
  // tape TAP output: ['operator', 'stack', 'at', 'expected', 'actual']
@@ -32,7 +31,7 @@ export default function TAPDisplayTestResult(COUNTER, details) {
32
31
  console.log(' ---');
33
32
  console.log(
34
33
  indentString(
35
- yaml.dump({
34
+ dumpYaml({
36
35
  name: `Assertion #${index + 1}`, // TODO: check what happens on runtime errors
37
36
  actual: assertion.actual
38
37
  ? JSON.parse(JSON.stringify(assertion.actual, getCircularReplacer()))
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Minimal YAML serializer for TAP assertion failure blocks.
3
+ * Handles the fixed schema: { name, actual, expected, message, stack, at }.
4
+ * Values for actual/expected are pre-sanitized via JSON.parse(JSON.stringify(...)).
5
+ */
6
+
7
+ // Single compiled regex covering all cases where a plain YAML scalar would be misread:
8
+ // - YAML reserved words (null, true, false, ~, yes, no, on, off — case-insensitive)
9
+ // - Starts with a YAML indicator: { [ ! | > ' " % @ `
10
+ // - Block indicators that need a space: - ? : at start of string
11
+ // - Document separator: ---
12
+ // - Looks like a number (integer, float, hex, octal, scientific)
13
+ // - Timestamp-like strings that YAML 1.1 auto-casts to Date
14
+ // - Contains ': ' (key–value) or '#' anywhere (comment)
15
+ // - Empty string
16
+ // Also covers single-letter YAML 1.1 booleans: y/Y → true, n/N → false
17
+ const NEEDS_QUOTING =
18
+ /^$|^(null|true|false|~|yes|no|on|off|y|n)$|^[{[!|>'"#%@`]|^[-?:](\s|$)|^---|^[-+]?(\d|\.\d)|^\d{4}-\d{2}-\d{2}|: |#/i;
19
+
20
+ function needsQuoting(str) {
21
+ return NEEDS_QUOTING.test(str);
22
+ }
23
+
24
+ function dumpString(str, indent) {
25
+ if (str === '') return "''";
26
+ if (str.includes('\n')) {
27
+ // Block scalar |- (strip trailing newline), each line indented by current indent + 2
28
+ return '|-\n' + str.replace(/^/gm, `${indent} `);
29
+ }
30
+ if (needsQuoting(str)) return `'${str.replace(/'/g, "''")}'`;
31
+ return str;
32
+ }
33
+
34
+ function dumpValue(value, indent) {
35
+ if (value === null || value === undefined) return 'null';
36
+ if (typeof value === 'boolean' || typeof value === 'number') return String(value);
37
+ if (typeof value === 'string') return dumpString(value, indent);
38
+ if (Array.isArray(value)) {
39
+ if (value.length === 0) return '[]';
40
+ const next = `${indent} `;
41
+ return '\n' + value.map((v) => `${next}- ${dumpValue(v, next)}`).join('\n');
42
+ }
43
+ // Plain object
44
+ const entries = Object.entries(value);
45
+ if (entries.length === 0) return '{}';
46
+ const next = `${indent} `;
47
+ return '\n' + entries.map(([k, v]) => `${next}${k}: ${dumpValue(v, next)}`).join('\n');
48
+ }
49
+
50
+ // Emits `key: value\n` or `key:\n ...\n` — no trailing space before block scalars.
51
+ function yamlLine(key, value) {
52
+ const v = dumpValue(value, '');
53
+ return v[0] === '\n' ? `${key}:${v}\n` : `${key}: ${v}\n`;
54
+ }
55
+
56
+ /**
57
+ * Serializes the fixed TAP assertion object to a YAML string.
58
+ * Uses a template literal (no Object.entries overhead) for the known top-level keys.
59
+ * @returns {string}
60
+ */
61
+ export default function dumpYaml({ name, actual, expected, message, stack, at }) {
62
+ return (
63
+ `name: ${dumpString(name, '')}\n` +
64
+ yamlLine('actual', actual) +
65
+ yamlLine('expected', expected) +
66
+ yamlLine('message', message) +
67
+ yamlLine('stack', stack) +
68
+ yamlLine('at', at)
69
+ );
70
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Minimal ANSI color helpers. Respects NO_COLOR, NODE_DISABLE_COLORS, FORCE_COLOR, and TTY
3
+ * detection — same logic as kleur.
4
+ *
5
+ * Use `createColors(enabled)` in tests to exercise both enabled and disabled branches directly.
6
+ */
7
+
8
+ export function createColors(enabled) {
9
+ const c = (open, close) => (text) =>
10
+ enabled ? `\x1b[${open}m${text}\x1b[${close}m` : String(text);
11
+
12
+ const red = c(31, 39);
13
+ const green = c(32, 39);
14
+ const yellow = c(33, 39);
15
+ const blue = c(34, 39);
16
+
17
+ /** `magenta(text)` — colored text. `magenta()` — chainable: `.bold(text)`. */
18
+ const magenta = (text) => {
19
+ if (text !== undefined) return enabled ? `\x1b[35m${text}\x1b[39m` : String(text);
20
+ return { bold: (t) => (enabled ? `\x1b[35m\x1b[1m${t}\x1b[22m\x1b[39m` : String(t)) };
21
+ };
22
+
23
+ return { red, green, yellow, blue, magenta };
24
+ }
25
+
26
+ const enabled =
27
+ !process.env.NODE_DISABLE_COLORS &&
28
+ process.env.NO_COLOR == null &&
29
+ process.env.TERM !== 'dumb' &&
30
+ ((process.env.FORCE_COLOR != null && process.env.FORCE_COLOR !== '0') || !!process.stdout?.isTTY);
31
+
32
+ const _c = createColors(enabled);
33
+
34
+ /** ANSI red text. */
35
+ export function red(text) {
36
+ return _c.red(text);
37
+ }
38
+ /** ANSI green text. */
39
+ export function green(text) {
40
+ return _c.green(text);
41
+ }
42
+ /** ANSI yellow text. */
43
+ export function yellow(text) {
44
+ return _c.yellow(text);
45
+ }
46
+ /** ANSI blue text. */
47
+ export function blue(text) {
48
+ return _c.blue(text);
49
+ }
50
+ /** ANSI magenta text. Call without arguments to chain: `magenta().bold(text)`. */
51
+ export function magenta(text) {
52
+ return _c.magenta(text);
53
+ }
@@ -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')) {
@@ -1,4 +1,4 @@
1
- import kleur from 'kleur';
1
+ import { red } from './color.js';
2
2
 
3
3
  /**
4
4
  * Dynamically imports `modulePath` and calls its default export with `params`; exits with code 1 on error.
@@ -15,7 +15,7 @@ export default async function runUserModule(modulePath, params, scriptPosition)
15
15
  : null;
16
16
  }
17
17
  } catch (error) {
18
- console.log('#', kleur.red(`QUnitX ${scriptPosition} script failed:`));
18
+ console.log('#', red(`QUnitX ${scriptPosition} script failed:`));
19
19
  console.trace(error);
20
20
  console.error(error);
21
21
 
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.6.0",
5
5
  "description": "Browser runner for QUnitx: run your qunitx tests in google-chrome",
6
6
  "main": "cli.js",
7
7
  "author": "Izel Nakri",
@@ -46,11 +46,8 @@
46
46
  "cheerio": "^1.2.0",
47
47
  "chokidar": "^5.0.0",
48
48
  "esbuild": "^0.27.3",
49
- "js-yaml": "^4.1.1",
50
- "kleur": "^4.1.5",
51
49
  "picomatch": "^4.0.3",
52
50
  "puppeteer": "^24.38.0",
53
- "recursive-lookup": "1.1.0",
54
51
  "ws": "^8.19.0"
55
52
  },
56
53
  "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;