qunitx-cli 0.21.3 → 0.22.1

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.
Files changed (3) hide show
  1. package/README.md +80 -15
  2. package/dist/cli.js +94 -25
  3. package/package.json +6 -3
package/README.md CHANGED
@@ -14,8 +14,9 @@ output to the terminal.
14
14
 
15
15
  ## Features
16
16
 
17
- - Runs `.js` and `.ts` test files in headless Chrome, Firefox, or WebKit (Playwright + esbuild)
18
- - TypeScript works with zero configuration — esbuild handles transpilation
17
+ - Runs `.js`, `.ts`, `.jsx`, and `.tsx` test files in headless Chrome, Firefox, or WebKit (Playwright + esbuild)
18
+ - TypeScript and JSX work with zero configuration — esbuild handles transpilation, including the React 17+ automatic JSX runtime
19
+ - Bring your own esbuild plugins through `package.json` for `.vue`, `.svelte`, and other custom loaders
19
20
  - Inline source maps for accurate stack traces pointing to original source files
20
21
  - Streams TAP-formatted output to the terminal in real time
21
22
  - Concurrent mode (default) splits test files across all CPU cores for fast parallel runs
@@ -165,29 +166,93 @@ All CLI flags can also be set in `package.json` under the `qunitx` key, so you d
165
166
  "qunitx": {
166
167
  "inputs": ["test/**/*-test.js", "test/**/*-test.ts"],
167
168
  "htmlPaths": ["test/tests.html"],
168
- "extensions": ["js", "ts"],
169
+ "extensions": ["js", "ts", "jsx", "tsx"],
169
170
  "output": "tmp",
170
171
  "timeout": 20000,
171
172
  "failFast": false,
172
173
  "port": 1234,
173
- "browser": "chromium"
174
+ "browser": "chromium",
175
+ "plugins": []
174
176
  }
175
177
  }
176
178
  ```
177
179
 
178
- | Key | Default | Description |
179
- | ------------ | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
180
- | `inputs` | `[]` | Glob patterns, file paths, or directories to use as test entry points. Merged with any paths given on the CLI. |
181
- | `htmlPaths` | `[]` | Optional HTML templates to run tests inside. Any listed `.html` file that contains `{{qunitxScript}}` or other handlebars-style tokens is treated as a test runner template. |
182
- | `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. |
183
- | `output` | `"tmp"` | Directory where compiled test bundles are written. |
184
- | `timeout` | `20000` | Maximum milliseconds to wait for the full test suite before timing out. |
185
- | `failFast` | `false` | Stop the run after the first failing test. |
186
- | `port` | `1234` | Preferred HTTP server port. qunitx auto-selects a free port if this one is taken. |
187
- | `browser` | `"chromium"` | Browser engine to use: `"chromium"`, `"firefox"`, or `"webkit"`. Overridden by `--browser` on the CLI. |
180
+ | Key | Default | Description |
181
+ | ------------ | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
182
+ | `inputs` | `[]` | Glob patterns, file paths, or directories to use as test entry points. Merged with any paths given on the CLI. |
183
+ | `htmlPaths` | `[]` | Optional HTML templates to run tests inside. Any listed `.html` file that contains `{{qunitxScript}}` or other handlebars-style tokens is treated as a test runner template. |
184
+ | `extensions` | `["js", "ts", "jsx", "tsx"]` | File extensions tracked for test discovery (directory scans) and watch-mode rebuild triggers. Add `"mjs"`, `"cjs"`, or any other extension your project uses. |
185
+ | `output` | `"tmp"` | Directory where compiled test bundles are written. |
186
+ | `timeout` | `20000` | Maximum milliseconds to wait for the full test suite before timing out. |
187
+ | `failFast` | `false` | Stop the run after the first failing test. |
188
+ | `port` | `1234` | Preferred HTTP server port. qunitx auto-selects a free port if this one is taken. |
189
+ | `browser` | `"chromium"` | Browser engine to use: `"chromium"`, `"firefox"`, or `"webkit"`. Overridden by `--browser` on the CLI. |
190
+ | `plugins` | `[]` | esbuild plugin specifiers loaded from your `node_modules` and applied to the test bundle. See [esbuild plugins](#esbuild-plugins). |
188
191
 
189
192
  CLI flags always override `package.json` values when both are present.
190
193
 
194
+ ## JSX / TSX
195
+
196
+ `.jsx` and `.tsx` files are picked up automatically — no configuration needed. The bundle uses esbuild's automatic JSX runtime so React 17+ "no `import React`" code just works:
197
+
198
+ ```tsx
199
+ // test/button-test.tsx
200
+ import { module, test } from 'qunitx';
201
+ import { flushSync } from 'react-dom';
202
+ import { createRoot } from 'react-dom/client';
203
+ import { Button } from '../src/button.tsx';
204
+
205
+ module('Button', (hooks) => {
206
+ let container;
207
+ hooks.beforeEach(() => {
208
+ container = document.createElement('div');
209
+ document.body.appendChild(container);
210
+ });
211
+ hooks.afterEach(() => container.remove());
212
+
213
+ test('renders the label', (assert) => {
214
+ flushSync(() => createRoot(container).render(<Button label="Save" />));
215
+ assert.equal(container.querySelector('button').textContent, 'Save');
216
+ });
217
+ });
218
+ ```
219
+
220
+ Vue, Preact, Solid, and other JSX dialects work via a one-line override at the top of each file:
221
+
222
+ ```tsx
223
+ /** @jsxImportSource vue */
224
+ import { createApp } from 'vue';
225
+ // ...JSX uses vue/jsx-runtime instead of react/jsx-runtime
226
+ ```
227
+
228
+ You can also set `compilerOptions.jsxImportSource` in your `tsconfig.json` to apply the override across a directory.
229
+
230
+ ## esbuild plugins
231
+
232
+ For file formats esbuild does not handle natively (e.g. `.vue` SFCs, `.svelte`), declare plugin specifiers in `package.json#qunitx.plugins`. qunitx dynamic-imports each one from your project's `node_modules` and passes it to the build:
233
+
234
+ ```json
235
+ {
236
+ "qunitx": {
237
+ "extensions": ["js", "ts", "jsx", "tsx", "vue"],
238
+ "plugins": [
239
+ "esbuild-plugin-vue-next",
240
+ ["esbuild-svelte", { "compilerOptions": { "css": "injected" } }]
241
+ ]
242
+ }
243
+ }
244
+ ```
245
+
246
+ Each entry is one of:
247
+
248
+ | Form | Behavior |
249
+ | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
250
+ | `"<package-name>"` | Imports the package. If the default export is a function, it's called with no arguments to produce the plugin; otherwise the export is used as the plugin. |
251
+ | `["<package-name>", <options>]` | Same, but the factory is called with `<options>` as its only argument. Use this form to pass plugin-specific configuration. |
252
+ | `"./relative/plugin.js"` | Loads a plugin you wrote yourself. Resolved against the project root (where your `package.json` lives). |
253
+
254
+ Don't forget to add the plugin's file extension(s) to `qunitx.extensions` so directory scans and watch-mode rebuilds pick them up.
255
+
191
256
  ### Environment variables
192
257
 
193
258
  | Variable | Description |
@@ -218,7 +283,7 @@ Options:
218
283
  --debug Print the server URL; pipe browser console to stdout
219
284
  --timeout=<ms> Max ms to wait for the suite to finish [default: 20000]
220
285
  --output=<dir> Directory for compiled test assets [default: ./tmp]
221
- --extensions=<...> Comma-separated file extensions to track [default: js,ts]
286
+ --extensions=<...> Comma-separated file extensions to track [default: js,ts,jsx,tsx]
222
287
  --before=<file> Script to run (and optionally await) before tests start
223
288
  --after=<file> Script to run (and optionally await) after tests finish
224
289
  --open, -o Open output in the test browser as soon as the bundle is ready
package/dist/cli.js CHANGED
@@ -377,6 +377,21 @@ var init_color = __esm({
377
377
  }
378
378
  });
379
379
 
380
+ // lib/setup/default-project-config-values.ts
381
+ var defaultProjectConfigValues;
382
+ var init_default_project_config_values = __esm({
383
+ "lib/setup/default-project-config-values.ts"() {
384
+ defaultProjectConfigValues = {
385
+ output: "tmp",
386
+ timeout: 2e4,
387
+ failFast: false,
388
+ port: 1234,
389
+ extensions: ["js", "ts", "jsx", "tsx"],
390
+ browser: "chromium"
391
+ };
392
+ }
393
+ });
394
+
380
395
  // lib/utils/read-template.ts
381
396
  import fs3 from "node:fs/promises";
382
397
  import { dirname, join as join2 } from "node:path";
@@ -1987,10 +2002,10 @@ var init_browser = __esm({
1987
2002
  // lib/utils/open-output-in-browser.ts
1988
2003
  import { spawn as spawn2 } from "node:child_process";
1989
2004
  import path6 from "node:path";
1990
- import { pathToFileURL } from "node:url";
2005
+ import { pathToFileURL as pathToFileURL2 } from "node:url";
1991
2006
  async function openOutputInBrowser(config) {
1992
2007
  try {
1993
- const outputFile = config.watch ? `http://localhost:${config.port}` : pathToFileURL(path6.join(path6.resolve(config.projectRoot, config.output), "index.html")).href;
2008
+ const outputFile = config.watch ? `http://localhost:${config.port}` : pathToFileURL2(path6.join(path6.resolve(config.projectRoot, config.output), "index.html")).href;
1994
2009
  if (typeof config.open === "string") {
1995
2010
  spawnDetached(config.open, [outputFile]);
1996
2011
  return;
@@ -2036,10 +2051,10 @@ var init_time_counter = __esm({
2036
2051
  });
2037
2052
 
2038
2053
  // lib/utils/run-user-module.ts
2039
- import { pathToFileURL as pathToFileURL2 } from "node:url";
2054
+ import { pathToFileURL as pathToFileURL3 } from "node:url";
2040
2055
  async function runUserModule(modulePath, params, scriptPosition) {
2041
2056
  try {
2042
- const func = await import(pathToFileURL2(modulePath).href);
2057
+ const func = await import(pathToFileURL3(modulePath).href);
2043
2058
  if (func) {
2044
2059
  func.default ? await func.default(params) : typeof func === "function" ? await func(params) : null;
2045
2060
  }
@@ -2140,6 +2155,11 @@ async function buildTestBundle(config, cachedContent) {
2140
2155
  legalComments: "none",
2141
2156
  target: esbuildTarget(config.browser),
2142
2157
  sourcemap,
2158
+ // jsx: 'automatic' is a no-op for .ts/.js files (extension-gated by esbuild) and emits
2159
+ // `import { jsx } from 'react/jsx-runtime'` for .tsx/.jsx files. Per-file overrides via
2160
+ // tsconfig's `jsxImportSource` or a `@jsxImportSource <pkg>` pragma cover Vue/Preact/Solid.
2161
+ jsx: "automatic",
2162
+ plugins: config.plugins,
2143
2163
  // Signal the runtime that all test modules are registered. The runtime's maybeStart()
2144
2164
  // waits for both this event and the WebSocket 'open' event before calling QUnit.start().
2145
2165
  // Dispatching from the bundle (rather than from a script onload attr) is reliable across
@@ -2345,7 +2365,10 @@ async function buildAllGroupBundles(groupConfigs, groupCachedContents) {
2345
2365
  in: `group-entry-${slotIndex}`,
2346
2366
  out: `group-${slotIndex}`
2347
2367
  })),
2348
- plugins: [groupEntryPlugin],
2368
+ // groupEntryPlugin must run first — it owns the virtual entry-point modules every other
2369
+ // plugin sees. User plugins follow and apply to the resolved test files just like in
2370
+ // single-group mode (`buildTestBundle`).
2371
+ plugins: [groupEntryPlugin, ...groupConfigs[0].plugins ?? []],
2349
2372
  nodePaths: ANCESTOR_NODE_MODULES,
2350
2373
  bundle: true,
2351
2374
  logLevel: "silent",
@@ -2357,6 +2380,7 @@ async function buildAllGroupBundles(groupConfigs, groupCachedContents) {
2357
2380
  target: esbuildTarget(browser),
2358
2381
  sourcemap,
2359
2382
  write: false,
2383
+ jsx: "automatic",
2360
2384
  footer: { js: 'window.dispatchEvent(new CustomEvent("qunitx:tests-ready"));' }
2361
2385
  };
2362
2386
  const hasSmallOutput = (result) => (result.outputFiles ?? []).some(
@@ -2425,6 +2449,8 @@ function buildFilteredTests(filteredTests, outputPath, config) {
2425
2449
  legalComments: "none",
2426
2450
  target: esbuildTarget(config.browser),
2427
2451
  sourcemap,
2452
+ jsx: "automatic",
2453
+ plugins: config.plugins,
2428
2454
  footer: { js: 'window.dispatchEvent(new CustomEvent("qunitx:tests-ready"));' }
2429
2455
  },
2430
2456
  needsDisk
@@ -2620,7 +2646,8 @@ import fs10 from "node:fs";
2620
2646
  import { readdir, stat, lstat } from "node:fs/promises";
2621
2647
  import path8 from "node:path";
2622
2648
  function setupFileWatchers(testFileLookupPaths, config, onEventFunc, onFinishFunc) {
2623
- const extensions = config.extensions || ["js", "ts"];
2649
+ const extensions = config.extensions || defaultProjectConfigValues.extensions;
2650
+ config._lastBuildEndMs ??= Date.now();
2624
2651
  const readyPromises = [];
2625
2652
  const parentWatchers = [];
2626
2653
  const rescanTimers = [];
@@ -2815,6 +2842,7 @@ async function rescanDirectoryForDelta(watchPath, config, extensions, onEventFun
2815
2842
  const presentPaths = /* @__PURE__ */ new Set();
2816
2843
  const presentDirs = /* @__PURE__ */ new Set();
2817
2844
  presentDirs.add(watchPath);
2845
+ const trackedToRecheck = [];
2818
2846
  for (const entry of entries) {
2819
2847
  if (entry.isDirectory()) {
2820
2848
  presentDirs.add(path8.join(entry.parentPath, entry.name));
@@ -2828,8 +2856,22 @@ async function rescanDirectoryForDelta(watchPath, config, extensions, onEventFun
2828
2856
  if (!(entryPath in config.fsTree)) {
2829
2857
  if (entry.isSymbolicLink()) trackSymlinkFn?.(entryPath);
2830
2858
  handleWatchEvent(config, extensions, "add", entryPath, onEventFunc, onFinishFunc);
2859
+ } else if (config._lastBuildEndMs) {
2860
+ trackedToRecheck.push(entryPath);
2831
2861
  }
2832
2862
  }
2863
+ const buildEndMs = config._lastBuildEndMs ?? 0;
2864
+ await Promise.all(
2865
+ trackedToRecheck.map(async (filePath) => {
2866
+ try {
2867
+ const { mtimeMs } = await stat(filePath);
2868
+ if (mtimeMs > buildEndMs) {
2869
+ handleWatchEvent(config, extensions, "change", filePath, onEventFunc, onFinishFunc);
2870
+ }
2871
+ } catch {
2872
+ }
2873
+ })
2874
+ );
2833
2875
  const watchPrefix = watchPath + path8.sep;
2834
2876
  const firedDirPrefixes = [];
2835
2877
  for (const trackedPath of Object.keys(config.fsTree)) {
@@ -2884,6 +2926,7 @@ var CHANGE_DEDUPE_MS, SYMLINK_POLL_INTERVAL_MS, OVERLAYFS_RENAME_RETRY_MS, RESCA
2884
2926
  var init_file_watcher = __esm({
2885
2927
  "lib/setup/file-watcher.ts"() {
2886
2928
  init_color();
2929
+ init_default_project_config_values();
2887
2930
  CHANGE_DEDUPE_MS = 10;
2888
2931
  SYMLINK_POLL_INTERVAL_MS = 500;
2889
2932
  OVERLAYFS_RENAME_RETRY_MS = 50;
@@ -3004,7 +3047,8 @@ __export(run_exports, {
3004
3047
  run: () => run
3005
3048
  });
3006
3049
  import fs12 from "node:fs/promises";
3007
- import { normalize } from "node:path";
3050
+ import { join as join3, normalize } from "node:path";
3051
+ import { createRequire as createRequire2 } from "node:module";
3008
3052
  import { availableParallelism } from "node:os";
3009
3053
  async function run(config) {
3010
3054
  const browserPromise = config.watch ? null : launchBrowser(config);
@@ -3296,7 +3340,7 @@ async function addCachedContentMainHTML(projectRoot, cachedContent) {
3296
3340
  } else {
3297
3341
  const html = await readTemplate("setup/tests.hbs");
3298
3342
  cachedContent.mainHTML = { filePath: `${projectRoot}/test/tests.html`, html };
3299
- cachedContent.assets.add(`${projectRoot}/node_modules/qunitx/vendor/qunit.css`);
3343
+ cachedContent.assets.add(join3(resolveQunitxRoot(projectRoot), "vendor/qunit.css"));
3300
3344
  }
3301
3345
  return cachedContent;
3302
3346
  }
@@ -3369,6 +3413,12 @@ function normalizeInternalAssetPathFromHTML(projectRoot, assetPath, htmlPath) {
3369
3413
  const currentDirectory = htmlPath ? htmlPath.split("/").slice(0, -1).join("/") : projectRoot;
3370
3414
  return assetPath.startsWith("./") ? normalize(`${currentDirectory}/${assetPath.slice(2)}`) : normalize(`${currentDirectory}/${assetPath}`);
3371
3415
  }
3416
+ function resolveQunitxRoot(projectRoot) {
3417
+ const mainEntry = createRequire2(`${projectRoot}/package.json`).resolve("qunitx");
3418
+ const match = /^(.*[\\/]qunitx)[\\/]/.exec(mainEntry);
3419
+ if (!match) throw new Error(`Could not derive qunitx root from ${mainEntry}`);
3420
+ return match[1];
3421
+ }
3372
3422
  var WATCH_NAV_TIMEOUT_MS, PAGE_CLOSE_GRACE_MS, STDOUT_FLUSH_GRACE_MS, KEEP_ALIVE_INTERVAL_MS, EXIT_CODE_SIGTERM;
3373
3423
  var init_run = __esm({
3374
3424
  "lib/commands/run.ts"() {
@@ -3408,7 +3458,7 @@ init_color();
3408
3458
  var package_default = {
3409
3459
  name: "qunitx-cli",
3410
3460
  type: "module",
3411
- version: "0.21.3",
3461
+ version: "0.22.1",
3412
3462
  description: "Browser runner for QUnitx: run your qunitx tests in google-chrome",
3413
3463
  author: "Izel Nakri",
3414
3464
  license: "MIT",
@@ -3465,8 +3515,11 @@ var package_default = {
3465
3515
  devDependencies: {
3466
3516
  "js-yaml": "^4.1.1",
3467
3517
  prettier: "^3.8.3",
3468
- qunitx: "^1.2.8",
3469
- typescript: "^6.0.3"
3518
+ qunitx: "^1.2.9",
3519
+ react: "^19.2.5",
3520
+ "react-dom": "^19.2.5",
3521
+ typescript: "^6.0.3",
3522
+ vue: "^3.5.33"
3470
3523
  },
3471
3524
  volta: {
3472
3525
  node: "24.14.0"
@@ -3502,7 +3555,7 @@ ${color("--timeout")} : change default timeout per test case
3502
3555
  ${color("--output")} : folder to distribute built qunitx html and js that a webservers can run[default: tmp]
3503
3556
  ${color("--failFast")} : run the target file or folders with immediate abort if a single test fails
3504
3557
  ${color("--port")} : HTTP server port (auto-selects a free port if the given port is taken)[default: 1234]
3505
- ${color("--extensions")} : comma-separated file extensions to track for discovery and watch-mode rebuilds[default: js,ts]
3558
+ ${color("--extensions")} : comma-separated file extensions to track for discovery and watch-mode rebuilds[default: js,ts,jsx,tsx]
3506
3559
  ${color("--browser")} : browser engine to run tests in: chromium, firefox, webkit[default: chromium]
3507
3560
  ${color("--before")} : run a script before the tests(i.e start a new web server before tests)
3508
3561
  ${color("--after")} : run a script after the tests(i.e save test results to a file)
@@ -3561,17 +3614,8 @@ async function findProjectRoot() {
3561
3614
  }
3562
3615
  }
3563
3616
 
3564
- // lib/setup/default-project-config-values.ts
3565
- var defaultProjectConfigValues = {
3566
- output: "tmp",
3567
- timeout: 2e4,
3568
- failFast: false,
3569
- port: 1234,
3570
- extensions: ["js", "ts"],
3571
- browser: "chromium"
3572
- };
3573
-
3574
3617
  // lib/commands/init.ts
3618
+ init_default_project_config_values();
3575
3619
  init_read_template();
3576
3620
  async function initializeProject() {
3577
3621
  const projectRoot = await findProjectRoot();
@@ -3658,13 +3702,17 @@ function pathToModuleName(filePath) {
3658
3702
  }
3659
3703
 
3660
3704
  // lib/setup/config.ts
3705
+ init_default_project_config_values();
3661
3706
  import fs7 from "node:fs/promises";
3707
+ import { createRequire } from "node:module";
3708
+ import { pathToFileURL } from "node:url";
3662
3709
 
3663
3710
  // lib/setup/fs-tree.ts
3711
+ init_default_project_config_values();
3664
3712
  import fs6, { glob as fsGlob } from "node:fs/promises";
3665
3713
  import path3 from "node:path";
3666
3714
  async function buildFSTree(fileAbsolutePaths, config = {}) {
3667
- const targetExtensions = config.extensions || ["js", "ts"];
3715
+ const targetExtensions = config.extensions || defaultProjectConfigValues.extensions;
3668
3716
  const fsTree = {};
3669
3717
  await Promise.all(
3670
3718
  fileAbsolutePaths.map(async (fileAbsolutePath) => {
@@ -3849,11 +3897,13 @@ async function setupConfig() {
3849
3897
  const projectRoot = await findProjectRoot();
3850
3898
  const cliConfigFlags = parseCliFlags(projectRoot);
3851
3899
  const projectPackageJSON = await readConfigFromPackageJSON(projectRoot);
3900
+ const { plugins: rawPlugins, ...userQunitx } = projectPackageJSON.qunitx ?? {};
3901
+ const pluginsPromise = resolvePlugins(rawPlugins, projectRoot);
3852
3902
  const inputs2 = cliConfigFlags.inputs.concat(readInputsFromPackageJSON(projectPackageJSON));
3853
3903
  const config = {
3854
3904
  ...defaultProjectConfigValues,
3855
3905
  htmlPaths: [],
3856
- ...projectPackageJSON.qunitx || {},
3906
+ ...userQunitx,
3857
3907
  ...cliConfigFlags,
3858
3908
  projectRoot,
3859
3909
  inputs: inputs2,
@@ -3874,7 +3924,10 @@ async function setupConfig() {
3874
3924
  _onTestsJsServed: null
3875
3925
  };
3876
3926
  config.htmlPaths = normalizeHTMLPaths(config.projectRoot, config.htmlPaths);
3877
- config.fsTree = await buildFSTree(config.testFileLookupPaths, config);
3927
+ [config.fsTree, config.plugins] = await Promise.all([
3928
+ buildFSTree(config.testFileLookupPaths, config),
3929
+ pluginsPromise
3930
+ ]);
3878
3931
  return config;
3879
3932
  }
3880
3933
  async function readConfigFromPackageJSON(projectRoot) {
@@ -3888,6 +3941,22 @@ function readInputsFromPackageJSON(packageJSON) {
3888
3941
  const qunitx = packageJSON.qunitx;
3889
3942
  return qunitx && qunitx.inputs ? qunitx.inputs : [];
3890
3943
  }
3944
+ function resolvePlugins(raw, projectRoot) {
3945
+ if (raw == null) return Promise.resolve([]);
3946
+ if (!Array.isArray(raw)) {
3947
+ console.error(`# qunitx: package.json#qunitx.plugins must be an array`);
3948
+ process.exit(1);
3949
+ }
3950
+ const projectRequire = createRequire(`${projectRoot}/package.json`);
3951
+ return Promise.all(
3952
+ raw.map(async (entry) => {
3953
+ const [spec, options] = Array.isArray(entry) ? entry : [entry];
3954
+ const mod = await import(pathToFileURL(projectRequire.resolve(spec)).href);
3955
+ const exported = mod.default ?? mod;
3956
+ return typeof exported === "function" ? exported(options) : exported;
3957
+ })
3958
+ );
3959
+ }
3891
3960
 
3892
3961
  // cli.ts
3893
3962
  process4.title = "qunitx";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "qunitx-cli",
3
3
  "type": "module",
4
- "version": "0.21.3",
4
+ "version": "0.22.1",
5
5
  "description": "Browser runner for QUnitx: run your qunitx tests in google-chrome",
6
6
  "author": "Izel Nakri",
7
7
  "license": "MIT",
@@ -58,8 +58,11 @@
58
58
  "devDependencies": {
59
59
  "js-yaml": "^4.1.1",
60
60
  "prettier": "^3.8.3",
61
- "qunitx": "^1.2.8",
62
- "typescript": "^6.0.3"
61
+ "qunitx": "^1.2.9",
62
+ "react": "^19.2.5",
63
+ "react-dom": "^19.2.5",
64
+ "typescript": "^6.0.3",
65
+ "vue": "^3.5.33"
63
66
  },
64
67
  "volta": {
65
68
  "node": "24.14.0"