qunitx-cli 0.17.0 → 0.17.5
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/bin/qunitx.js +13 -4
- package/dist/cli.js +65 -31
- package/package.json +2 -2
package/bin/qunitx.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// (qunitx-cli-linux-x64, qunitx-cli-darwin-arm64, etc.) when available.
|
|
5
5
|
// Falls back to the bundled JS CLI (dist/cli.js) which requires Node.js + node_modules.
|
|
6
6
|
import { spawn } from 'node:child_process';
|
|
7
|
-
import { access, constants } from 'node:fs/promises';
|
|
7
|
+
import { access, constants, readFile } from 'node:fs/promises';
|
|
8
8
|
import { createRequire } from 'node:module';
|
|
9
9
|
import { fileURLToPath } from 'node:url';
|
|
10
10
|
import { dirname, join } from 'node:path';
|
|
@@ -12,6 +12,10 @@ import { dirname, join } from 'node:path';
|
|
|
12
12
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
13
|
const require = createRequire(import.meta.url);
|
|
14
14
|
|
|
15
|
+
const currentVersion = JSON.parse(
|
|
16
|
+
await readFile(join(__dirname, '../package.json'), 'utf8'),
|
|
17
|
+
).version;
|
|
18
|
+
|
|
15
19
|
const platformMap = {
|
|
16
20
|
'linux-x64': { seaPkg: 'qunitx-cli-linux-x64', esbuildPkg: '@esbuild/linux-x64', bin: 'qunitx' },
|
|
17
21
|
'linux-arm64': {
|
|
@@ -41,7 +45,10 @@ const target = platformMap[`${process.platform}-${process.arch}`];
|
|
|
41
45
|
async function trySeaBinary() {
|
|
42
46
|
if (!target) return false;
|
|
43
47
|
try {
|
|
44
|
-
const
|
|
48
|
+
const pkgJsonPath = require.resolve(`${target.seaPkg}/package.json`);
|
|
49
|
+
const seaVersion = JSON.parse(await readFile(pkgJsonPath, 'utf8')).version;
|
|
50
|
+
if (seaVersion !== currentVersion) return false;
|
|
51
|
+
const pkgDir = dirname(pkgJsonPath);
|
|
45
52
|
const binaryPath = join(pkgDir, 'bin', target.bin);
|
|
46
53
|
await access(binaryPath, constants.X_OK);
|
|
47
54
|
|
|
@@ -52,10 +59,12 @@ async function trySeaBinary() {
|
|
|
52
59
|
`${target.esbuildPkg}/bin/esbuild${process.platform === 'win32' ? '.exe' : ''}`,
|
|
53
60
|
);
|
|
54
61
|
env = { ...env, ESBUILD_BINARY_PATH: esbuildBin };
|
|
55
|
-
} catch (
|
|
62
|
+
} catch (_e) {
|
|
63
|
+
// esbuild binary not found in optional package — env stays as-is
|
|
64
|
+
}
|
|
56
65
|
}
|
|
57
66
|
|
|
58
|
-
await new Promise((
|
|
67
|
+
await new Promise((_resolve, reject) => {
|
|
59
68
|
const child = spawn(binaryPath, process.argv.slice(2), { stdio: 'inherit', env });
|
|
60
69
|
child.on('close', (code) => process.exit(code ?? 1));
|
|
61
70
|
child.on('error', reject);
|
package/dist/cli.js
CHANGED
|
@@ -37,6 +37,22 @@ var init_find_chrome = __esm({
|
|
|
37
37
|
}
|
|
38
38
|
});
|
|
39
39
|
|
|
40
|
+
// lib/utils/kill-process-group.ts
|
|
41
|
+
function killProcessGroup(pid) {
|
|
42
|
+
try {
|
|
43
|
+
if (process.platform === "win32") {
|
|
44
|
+
process.kill(pid, "SIGKILL");
|
|
45
|
+
} else {
|
|
46
|
+
process.kill(-pid, "SIGKILL");
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
var init_kill_process_group = __esm({
|
|
52
|
+
"lib/utils/kill-process-group.ts"() {
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
40
56
|
// lib/utils/pre-launch-chrome.ts
|
|
41
57
|
import { spawn } from "node:child_process";
|
|
42
58
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
@@ -45,16 +61,20 @@ import path from "node:path";
|
|
|
45
61
|
async function preLaunchChrome(chromePath, args, headless = true) {
|
|
46
62
|
if (!chromePath) return null;
|
|
47
63
|
const userDataDir = await mkdtemp(path.join(os.tmpdir(), "qunitx-chrome-"));
|
|
48
|
-
const cleanup = () => rm(userDataDir, { recursive: true, force: true }).catch(() => {
|
|
49
|
-
});
|
|
50
64
|
const headlessArgs = headless ? ["--headless=new"] : [];
|
|
51
65
|
const proc = spawn(
|
|
52
66
|
chromePath,
|
|
53
67
|
["--remote-debugging-port=0", `--user-data-dir=${userDataDir}`, ...headlessArgs, ...args],
|
|
54
|
-
|
|
68
|
+
// detached: true puts Chrome in its own process group (PGID = proc.pid).
|
|
69
|
+
// This lets shutdown() kill the entire group (main process + all renderer/GPU/utility
|
|
70
|
+
// children) with process.kill(-proc.pid, 'SIGKILL'), preventing orphaned Chrome
|
|
71
|
+
// subprocesses from accumulating inotify watches across test runs.
|
|
72
|
+
{ stdio: ["ignore", "ignore", "pipe"], detached: true }
|
|
55
73
|
);
|
|
74
|
+
let cdpConnected = false;
|
|
56
75
|
proc.on("close", () => {
|
|
57
|
-
|
|
76
|
+
if (!cdpConnected) rm(userDataDir, { recursive: true, force: true }).catch(() => {
|
|
77
|
+
});
|
|
58
78
|
resolveWith(null);
|
|
59
79
|
});
|
|
60
80
|
proc.on("error", () => resolveWith(null));
|
|
@@ -66,6 +86,7 @@ async function preLaunchChrome(chromePath, args, headless = true) {
|
|
|
66
86
|
buffer += chunk.toString();
|
|
67
87
|
const match = buffer.match(CDP_URL_REGEX);
|
|
68
88
|
if (!match) return;
|
|
89
|
+
cdpConnected = true;
|
|
69
90
|
proc.unref();
|
|
70
91
|
proc.stderr.unref();
|
|
71
92
|
resolve({
|
|
@@ -84,25 +105,25 @@ async function preLaunchChrome(chromePath, args, headless = true) {
|
|
|
84
105
|
}
|
|
85
106
|
proc.once("close", resolve);
|
|
86
107
|
});
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
await closed.then(() => cleanup());
|
|
108
|
+
if (proc.exitCode === null) killProcessGroup(proc.pid);
|
|
109
|
+
await closed;
|
|
110
|
+
await rm(userDataDir, { recursive: true, force: true }).catch(() => {
|
|
111
|
+
});
|
|
92
112
|
}
|
|
93
113
|
}
|
|
94
114
|
var CDP_URL_REGEX;
|
|
95
115
|
var init_pre_launch_chrome = __esm({
|
|
96
116
|
"lib/utils/pre-launch-chrome.ts"() {
|
|
117
|
+
init_kill_process_group();
|
|
97
118
|
CDP_URL_REGEX = /DevTools listening on (ws:\/\/[^\s]+)/;
|
|
98
119
|
}
|
|
99
120
|
});
|
|
100
121
|
|
|
101
122
|
// lib/utils/chromium-args.ts
|
|
102
|
-
var
|
|
123
|
+
var CHROMIUM_ARGS;
|
|
103
124
|
var init_chromium_args = __esm({
|
|
104
125
|
"lib/utils/chromium-args.ts"() {
|
|
105
|
-
|
|
126
|
+
CHROMIUM_ARGS = [
|
|
106
127
|
// ── Sandbox / rendering ──────────────────────────────────────────────────────
|
|
107
128
|
"--no-sandbox",
|
|
108
129
|
// required in most CI/container environments
|
|
@@ -202,6 +223,7 @@ var init_early_chrome = __esm({
|
|
|
202
223
|
"lib/utils/early-chrome.ts"() {
|
|
203
224
|
init_find_chrome();
|
|
204
225
|
init_pre_launch_chrome();
|
|
226
|
+
init_kill_process_group();
|
|
205
227
|
init_chromium_args();
|
|
206
228
|
init_perf_logger();
|
|
207
229
|
NON_RUN_COMMANDS = /* @__PURE__ */ new Set(["help", "h", "p", "print", "new", "n", "g", "generate", "init"]);
|
|
@@ -220,16 +242,13 @@ var init_early_chrome = __esm({
|
|
|
220
242
|
if (!openWatchMode) {
|
|
221
243
|
process.on("exit", () => {
|
|
222
244
|
if (!earlyChrome) return;
|
|
223
|
-
|
|
224
|
-
earlyChrome.proc.kill("SIGKILL");
|
|
225
|
-
} catch {
|
|
226
|
-
}
|
|
245
|
+
killProcessGroup(earlyChrome.proc.pid);
|
|
227
246
|
});
|
|
228
247
|
}
|
|
229
248
|
perfLog("early-chrome.js: module evaluated");
|
|
230
249
|
earlyBrowserPromise = isRunCommand && browserFromArgv === "chromium" ? findChrome().then((chromePath) => {
|
|
231
250
|
perfLog("early-chrome.js: findChrome resolved", chromePath);
|
|
232
|
-
return preLaunchChrome(chromePath,
|
|
251
|
+
return preLaunchChrome(chromePath, CHROMIUM_ARGS, !openWatchMode);
|
|
233
252
|
}).then((info) => {
|
|
234
253
|
perfLog("early-chrome.js: Chrome CDP ready", info?.cdpEndpoint ?? null);
|
|
235
254
|
if (info) earlyChrome = info;
|
|
@@ -611,8 +630,12 @@ var init_http = __esm({
|
|
|
611
630
|
* @returns {Promise<void>}
|
|
612
631
|
*/
|
|
613
632
|
close() {
|
|
633
|
+
this.wss.clients.forEach((client) => client.terminate());
|
|
634
|
+
const wssClose = new Promise((resolve) => this.wss.close(() => resolve()));
|
|
614
635
|
this._server.closeAllConnections?.();
|
|
615
|
-
|
|
636
|
+
const serverClose = new Promise((resolve) => this._server.close(resolve));
|
|
637
|
+
return Promise.all([wssClose, serverClose]).then(() => {
|
|
638
|
+
});
|
|
616
639
|
}
|
|
617
640
|
/** Registers a GET route handler. */
|
|
618
641
|
get(path6, handler) {
|
|
@@ -1118,7 +1141,7 @@ async function launchBrowser(config) {
|
|
|
1118
1141
|
}
|
|
1119
1142
|
const executablePath = await findChrome();
|
|
1120
1143
|
const launchOptions = {
|
|
1121
|
-
args:
|
|
1144
|
+
args: CHROMIUM_ARGS,
|
|
1122
1145
|
headless: true,
|
|
1123
1146
|
// Disable Playwright's async SIGTERM/SIGHUP handlers. When the CLI is killed by an
|
|
1124
1147
|
// external signal (e.g. exec() timeout in tests), those handlers start an async browser
|
|
@@ -1281,6 +1304,7 @@ async function buildTestBundle(config, cachedContent) {
|
|
|
1281
1304
|
return;
|
|
1282
1305
|
}
|
|
1283
1306
|
const outfile = `${projectRoot}/${output}/tests.js`;
|
|
1307
|
+
await fs8.mkdir(`${projectRoot}/${output}`, { recursive: true });
|
|
1284
1308
|
const sourcemap = config.debug ? "inline" : config.watch ? "linked" : false;
|
|
1285
1309
|
const needsDisk = true;
|
|
1286
1310
|
const [allTestCode] = await Promise.all([
|
|
@@ -1824,7 +1848,8 @@ var init_write_output_static_files = __esm({
|
|
|
1824
1848
|
// lib/commands/run.ts
|
|
1825
1849
|
var run_exports = {};
|
|
1826
1850
|
__export(run_exports, {
|
|
1827
|
-
default: () => run
|
|
1851
|
+
default: () => run,
|
|
1852
|
+
run: () => run
|
|
1828
1853
|
});
|
|
1829
1854
|
import fs11 from "node:fs/promises";
|
|
1830
1855
|
import { normalize } from "node:path";
|
|
@@ -2096,7 +2121,7 @@ init_color();
|
|
|
2096
2121
|
var package_default = {
|
|
2097
2122
|
name: "qunitx-cli",
|
|
2098
2123
|
type: "module",
|
|
2099
|
-
version: "0.17.
|
|
2124
|
+
version: "0.17.5",
|
|
2100
2125
|
description: "Browser runner for QUnitx: run your qunitx tests in google-chrome",
|
|
2101
2126
|
author: "Izel Nakri",
|
|
2102
2127
|
license: "MIT",
|
|
@@ -2119,7 +2144,7 @@ var package_default = {
|
|
|
2119
2144
|
prepublishOnly: "npm run build",
|
|
2120
2145
|
format: 'prettier --check "lib/**/*.ts" "test/**/*.ts" "scripts/**/*.js" "bin/**/*.js" "*.ts" "package.json" ".github/**/*.yml"',
|
|
2121
2146
|
"format:fix": 'prettier --write "lib/**/*.ts" "test/**/*.ts" "scripts/**/*.js" "bin/**/*.js" "*.ts" "package.json" ".github/**/*.yml"',
|
|
2122
|
-
lint: "deno lint lib/ cli.ts",
|
|
2147
|
+
lint: "deno lint lib/ bin/ cli.ts",
|
|
2123
2148
|
"lint:docs": "node scripts/lint-docs.js",
|
|
2124
2149
|
docs: `deno doc --html --name="qunitx-cli" --output=docs/lib 'lib/**/*.ts' README.md`,
|
|
2125
2150
|
"changelog:unreleased": "git-cliff --unreleased --strip all",
|
|
@@ -2226,12 +2251,11 @@ async function searchInParentDirectories(directory, targetEntry) {
|
|
|
2226
2251
|
targetEntry
|
|
2227
2252
|
);
|
|
2228
2253
|
}
|
|
2229
|
-
var search_in_parent_directories_default = searchInParentDirectories;
|
|
2230
2254
|
|
|
2231
2255
|
// lib/utils/find-project-root.ts
|
|
2232
2256
|
async function findProjectRoot() {
|
|
2233
2257
|
try {
|
|
2234
|
-
const absolutePath = await
|
|
2258
|
+
const absolutePath = await searchInParentDirectories(".", "package.json");
|
|
2235
2259
|
if (!absolutePath.includes("package.json")) {
|
|
2236
2260
|
throw new Error("package.json mising");
|
|
2237
2261
|
}
|
|
@@ -2246,7 +2270,7 @@ async function findProjectRoot() {
|
|
|
2246
2270
|
init_path_exists();
|
|
2247
2271
|
|
|
2248
2272
|
// lib/setup/default-project-config-values.ts
|
|
2249
|
-
var
|
|
2273
|
+
var defaultProjectConfigValues = {
|
|
2250
2274
|
output: "tmp",
|
|
2251
2275
|
timeout: 2e4,
|
|
2252
2276
|
failFast: false,
|
|
@@ -2262,7 +2286,7 @@ async function initializeProject() {
|
|
|
2262
2286
|
const oldPackageJSON = JSON.parse(await fs3.readFile(`${projectRoot}/package.json`));
|
|
2263
2287
|
const existingQunitx = oldPackageJSON.qunitx || {};
|
|
2264
2288
|
const cliHtmlPaths = process.argv.slice(2).filter((arg) => arg.endsWith(".html"));
|
|
2265
|
-
const config = Object.assign({},
|
|
2289
|
+
const config = Object.assign({}, defaultProjectConfigValues, existingQunitx, {
|
|
2266
2290
|
htmlPaths: cliHtmlPaths.length > 0 ? cliHtmlPaths : existingQunitx.htmlPaths || ["test/tests.html"]
|
|
2267
2291
|
});
|
|
2268
2292
|
await Promise.all([
|
|
@@ -2313,9 +2337,22 @@ init_color();
|
|
|
2313
2337
|
import fs4 from "node:fs/promises";
|
|
2314
2338
|
init_path_exists();
|
|
2315
2339
|
init_read_boilerplate();
|
|
2340
|
+
|
|
2341
|
+
// lib/utils/convert-to-pascal-case.ts
|
|
2342
|
+
function convertToPascalCase(str) {
|
|
2343
|
+
return str.split(/[-_]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
// lib/commands/generate.ts
|
|
2347
|
+
function pathToModuleName(filePath) {
|
|
2348
|
+
const withoutExt = filePath.replace(/\.(js|ts)$/, "");
|
|
2349
|
+
const segments = withoutExt.split("/");
|
|
2350
|
+
const targetNames = segments[0] === "test" || segments[0] === "tests" ? segments.slice(1) : segments;
|
|
2351
|
+
return targetNames.map(convertToPascalCase).join(" | ");
|
|
2352
|
+
}
|
|
2316
2353
|
async function generateTestFiles() {
|
|
2317
2354
|
const projectRoot = await findProjectRoot();
|
|
2318
|
-
const moduleName = process.argv[3];
|
|
2355
|
+
const moduleName = pathToModuleName(process.argv[3]);
|
|
2319
2356
|
const path6 = process.argv[3].endsWith(".js") || process.argv[3].endsWith(".ts") ? `${projectRoot}/${process.argv[3]}` : `${projectRoot}/${process.argv[3]}.js`;
|
|
2320
2357
|
if (await pathExists(path6)) {
|
|
2321
2358
|
console.log(`${path6} already exists!`);
|
|
@@ -2523,7 +2560,7 @@ async function setupConfig() {
|
|
|
2523
2560
|
const projectPackageJSON = await readConfigFromPackageJSON(projectRoot);
|
|
2524
2561
|
const inputs2 = cliConfigFlags.inputs.concat(readInputsFromPackageJSON(projectPackageJSON));
|
|
2525
2562
|
const config = {
|
|
2526
|
-
...
|
|
2563
|
+
...defaultProjectConfigValues,
|
|
2527
2564
|
htmlPaths: [],
|
|
2528
2565
|
...projectPackageJSON.qunitx || {},
|
|
2529
2566
|
...cliConfigFlags,
|
|
@@ -2568,10 +2605,7 @@ process4.title = "qunitx";
|
|
|
2568
2605
|
} else if (["init"].includes(process4.argv[2])) {
|
|
2569
2606
|
return await initializeProject();
|
|
2570
2607
|
}
|
|
2571
|
-
const [config, {
|
|
2572
|
-
setupConfig(),
|
|
2573
|
-
Promise.resolve().then(() => (init_run(), run_exports))
|
|
2574
|
-
]);
|
|
2608
|
+
const [config, { run: run2 }] = await Promise.all([setupConfig(), Promise.resolve().then(() => (init_run(), run_exports))]);
|
|
2575
2609
|
try {
|
|
2576
2610
|
return await run2(config);
|
|
2577
2611
|
} catch (error) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qunitx-cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.17.
|
|
4
|
+
"version": "0.17.5",
|
|
5
5
|
"description": "Browser runner for QUnitx: run your qunitx tests in google-chrome",
|
|
6
6
|
"author": "Izel Nakri",
|
|
7
7
|
"license": "MIT",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"prepublishOnly": "npm run build",
|
|
25
25
|
"format": "prettier --check \"lib/**/*.ts\" \"test/**/*.ts\" \"scripts/**/*.js\" \"bin/**/*.js\" \"*.ts\" \"package.json\" \".github/**/*.yml\"",
|
|
26
26
|
"format:fix": "prettier --write \"lib/**/*.ts\" \"test/**/*.ts\" \"scripts/**/*.js\" \"bin/**/*.js\" \"*.ts\" \"package.json\" \".github/**/*.yml\"",
|
|
27
|
-
"lint": "deno lint lib/ cli.ts",
|
|
27
|
+
"lint": "deno lint lib/ bin/ cli.ts",
|
|
28
28
|
"lint:docs": "node scripts/lint-docs.js",
|
|
29
29
|
"docs": "deno doc --html --name=\"qunitx-cli\" --output=docs/lib 'lib/**/*.ts' README.md",
|
|
30
30
|
"changelog:unreleased": "git-cliff --unreleased --strip all",
|