qunitx-cli 0.9.4 → 0.9.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/bin/qunitx.js +71 -0
- package/dist/cli.js +2017 -0
- package/package.json +17 -8
- package/cli.ts +0 -39
- package/deno.json +0 -18
- package/deno.lock +0 -646
- package/lib/commands/generate.ts +0 -33
- package/lib/commands/help.ts +0 -37
- package/lib/commands/init.ts +0 -77
- package/lib/commands/run/tests-in-browser.ts +0 -221
- package/lib/commands/run.ts +0 -279
- package/lib/servers/http.ts +0 -321
- package/lib/setup/bind-server-to-port.ts +0 -14
- package/lib/setup/browser.ts +0 -101
- package/lib/setup/config.ts +0 -55
- package/lib/setup/default-project-config-values.ts +0 -9
- package/lib/setup/file-watcher.ts +0 -134
- package/lib/setup/fs-tree.ts +0 -60
- package/lib/setup/keyboard-events.ts +0 -38
- package/lib/setup/test-file-paths.ts +0 -74
- package/lib/setup/web-server.ts +0 -274
- package/lib/setup/write-output-static-files.ts +0 -33
- package/lib/tap/display-final-result.ts +0 -25
- package/lib/tap/display-test-result.ts +0 -109
- package/lib/tap/dump-yaml.ts +0 -84
- package/lib/types.ts +0 -61
- package/lib/utils/chromium-args.ts +0 -18
- package/lib/utils/color.ts +0 -66
- package/lib/utils/early-chrome.ts +0 -39
- package/lib/utils/find-chrome.ts +0 -38
- package/lib/utils/find-internal-assets-from-html.ts +0 -18
- package/lib/utils/find-project-root.ts +0 -20
- package/lib/utils/indent-string.ts +0 -24
- package/lib/utils/listen-to-keyboard-key.ts +0 -57
- package/lib/utils/parse-cli-flags.ts +0 -95
- package/lib/utils/path-exists.ts +0 -21
- package/lib/utils/perf-logger.ts +0 -25
- package/lib/utils/pre-launch-chrome.ts +0 -45
- package/lib/utils/read-boilerplate.ts +0 -15
- package/lib/utils/resolve-port-number-for.ts +0 -29
- package/lib/utils/run-user-module.ts +0 -28
- package/lib/utils/search-in-parent-directories.ts +0 -25
- package/lib/utils/time-counter.ts +0 -19
package/lib/types.ts
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import type HTTPServer from './servers/http.ts';
|
|
2
|
-
import type { Browser, Page } from 'playwright-core';
|
|
3
|
-
import type { ChildProcess } from 'node:child_process';
|
|
4
|
-
import type { Buffer } from 'node:buffer';
|
|
5
|
-
|
|
6
|
-
export interface Counter {
|
|
7
|
-
testCount: number;
|
|
8
|
-
failCount: number;
|
|
9
|
-
skipCount: number;
|
|
10
|
-
passCount: number;
|
|
11
|
-
errorCount: number;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export type FSTree = Record<string, null>;
|
|
15
|
-
|
|
16
|
-
export interface CachedContent {
|
|
17
|
-
allTestCode: Buffer | string | null;
|
|
18
|
-
filteredTestCode?: string;
|
|
19
|
-
assets: Set<string>;
|
|
20
|
-
htmlPathsToRunTests: string[];
|
|
21
|
-
mainHTML: { filePath: string | null; html: string | null };
|
|
22
|
-
staticHTMLs: Record<string, string>;
|
|
23
|
-
dynamicContentHTMLs: Record<string, string>;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface Config {
|
|
27
|
-
output: string;
|
|
28
|
-
timeout: number;
|
|
29
|
-
failFast: boolean;
|
|
30
|
-
port: number;
|
|
31
|
-
extensions: string[];
|
|
32
|
-
browser: 'chromium' | 'firefox' | 'webkit';
|
|
33
|
-
projectRoot: string;
|
|
34
|
-
inputs: string[];
|
|
35
|
-
htmlPaths: string[];
|
|
36
|
-
testFileLookupPaths: string[];
|
|
37
|
-
fsTree: FSTree;
|
|
38
|
-
before?: string | false;
|
|
39
|
-
after?: string | false;
|
|
40
|
-
watch?: boolean;
|
|
41
|
-
debug?: boolean;
|
|
42
|
-
COUNTER: Counter;
|
|
43
|
-
lastFailedTestFiles: string[] | null;
|
|
44
|
-
lastRanTestFiles: string[] | null;
|
|
45
|
-
_testRunDone: (() => void) | null;
|
|
46
|
-
_resetTestTimeout: (() => void) | null;
|
|
47
|
-
_groupMode?: boolean;
|
|
48
|
-
_building?: boolean;
|
|
49
|
-
expressApp?: unknown;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export interface Connections {
|
|
53
|
-
server: HTTPServer;
|
|
54
|
-
browser: Browser;
|
|
55
|
-
page: Page;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export interface EarlyChrome {
|
|
59
|
-
proc: ChildProcess;
|
|
60
|
-
cdpEndpoint: string;
|
|
61
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/** Launch args passed to Chromium for both the CDP pre-launch spawn and the playwright fallback launch. */
|
|
2
|
-
export default [
|
|
3
|
-
'--no-sandbox',
|
|
4
|
-
'--disable-gpu',
|
|
5
|
-
'--window-size=1440,900',
|
|
6
|
-
'--disable-extensions',
|
|
7
|
-
'--disable-sync',
|
|
8
|
-
'--no-first-run',
|
|
9
|
-
'--disable-default-apps',
|
|
10
|
-
'--mute-audio',
|
|
11
|
-
'--disable-background-networking',
|
|
12
|
-
'--disable-background-timer-throttling',
|
|
13
|
-
'--disable-renderer-backgrounding',
|
|
14
|
-
'--disable-dev-shm-usage',
|
|
15
|
-
'--disable-translate',
|
|
16
|
-
'--metrics-recording-only',
|
|
17
|
-
'--disable-hang-monitor',
|
|
18
|
-
] as string[];
|
package/lib/utils/color.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
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
|
-
interface MagentaReturn {
|
|
9
|
-
bold: (t: string) => string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
interface MagentaFn {
|
|
13
|
-
(text: string): string;
|
|
14
|
-
(): MagentaReturn;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** Creates a set of ANSI color helpers with coloring enabled or disabled. */
|
|
18
|
-
export function createColors(enabled: boolean) {
|
|
19
|
-
const c = (open: number, close: number) => (text: string) =>
|
|
20
|
-
enabled ? `\x1b[${open}m${text}\x1b[${close}m` : String(text);
|
|
21
|
-
|
|
22
|
-
const red = c(31, 39);
|
|
23
|
-
const green = c(32, 39);
|
|
24
|
-
const yellow = c(33, 39);
|
|
25
|
-
const blue = c(34, 39);
|
|
26
|
-
|
|
27
|
-
/** `magenta(text)` — colored text. `magenta()` — chainable: `.bold(text)`. */
|
|
28
|
-
const magenta = ((text?: string): string | MagentaReturn => {
|
|
29
|
-
if (text !== undefined) return enabled ? `\x1b[35m${text}\x1b[39m` : String(text);
|
|
30
|
-
return { bold: (t: string) => (enabled ? `\x1b[35m\x1b[1m${t}\x1b[22m\x1b[39m` : String(t)) };
|
|
31
|
-
}) as MagentaFn;
|
|
32
|
-
|
|
33
|
-
return { red, green, yellow, blue, magenta };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const enabled =
|
|
37
|
-
!process.env.NODE_DISABLE_COLORS &&
|
|
38
|
-
process.env.NO_COLOR == null &&
|
|
39
|
-
process.env.TERM !== 'dumb' &&
|
|
40
|
-
((process.env.FORCE_COLOR != null && process.env.FORCE_COLOR !== '0') || !!process.stdout?.isTTY);
|
|
41
|
-
|
|
42
|
-
const _c = createColors(enabled);
|
|
43
|
-
|
|
44
|
-
/** ANSI red text. */
|
|
45
|
-
export function red(text: string): string {
|
|
46
|
-
return _c.red(text);
|
|
47
|
-
}
|
|
48
|
-
/** ANSI green text. */
|
|
49
|
-
export function green(text: string): string {
|
|
50
|
-
return _c.green(text);
|
|
51
|
-
}
|
|
52
|
-
/** ANSI yellow text. */
|
|
53
|
-
export function yellow(text: string): string {
|
|
54
|
-
return _c.yellow(text);
|
|
55
|
-
}
|
|
56
|
-
/** ANSI blue text. */
|
|
57
|
-
export function blue(text: string): string {
|
|
58
|
-
return _c.blue(text);
|
|
59
|
-
}
|
|
60
|
-
/** ANSI magenta text. Call without arguments to chain: `magenta().bold(text)`. */
|
|
61
|
-
export function magenta(text: string): string;
|
|
62
|
-
/** ANSI magenta text. Call without arguments to chain: `magenta().bold(text)`. */
|
|
63
|
-
export function magenta(): MagentaReturn;
|
|
64
|
-
export function magenta(text?: string): string | MagentaReturn {
|
|
65
|
-
return _c.magenta(text as string);
|
|
66
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import findChrome from './find-chrome.ts';
|
|
2
|
-
import preLaunchChrome from './pre-launch-chrome.ts';
|
|
3
|
-
import CHROMIUM_ARGS from './chromium-args.ts';
|
|
4
|
-
import { perfLog } from './perf-logger.ts';
|
|
5
|
-
|
|
6
|
-
// This module is statically imported by cli.ts so its module-level code runs
|
|
7
|
-
// at the very start of the process — before the IIFE, before playwright-core loads.
|
|
8
|
-
// For run commands only, Chrome is spawned immediately via CDP so it is ready
|
|
9
|
-
// (or nearly ready) by the time playwright-core finishes loading (~500ms later).
|
|
10
|
-
// For help/init/generate, nothing is spawned and this module costs ~0ms.
|
|
11
|
-
|
|
12
|
-
const NON_RUN_COMMANDS = new Set(['help', 'h', 'p', 'print', 'new', 'n', 'g', 'generate', 'init']);
|
|
13
|
-
const isRunCommand = Boolean(process.argv[2]) && !NON_RUN_COMMANDS.has(process.argv[2]);
|
|
14
|
-
const browserFromArgv =
|
|
15
|
-
process.argv.find((arg) => arg.startsWith('--browser='))?.split('=')[1] || 'chromium';
|
|
16
|
-
|
|
17
|
-
let earlyChromeProcRef = null;
|
|
18
|
-
process.on('exit', () => earlyChromeProcRef?.kill());
|
|
19
|
-
|
|
20
|
-
perfLog('early-chrome.js: module evaluated');
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Resolves to `{ proc, cdpEndpoint }` when Chrome is pre-launched and ready,
|
|
24
|
-
* or `null` if pre-launch was skipped (non-run command or non-chromium browser).
|
|
25
|
-
* @type {Promise<{proc: import('node:child_process').ChildProcess, cdpEndpoint: string} | null>}
|
|
26
|
-
*/
|
|
27
|
-
export const earlyBrowserPromise =
|
|
28
|
-
isRunCommand && browserFromArgv === 'chromium'
|
|
29
|
-
? findChrome()
|
|
30
|
-
.then((chromePath) => {
|
|
31
|
-
perfLog('early-chrome.js: findChrome resolved', chromePath);
|
|
32
|
-
return preLaunchChrome(chromePath, CHROMIUM_ARGS);
|
|
33
|
-
})
|
|
34
|
-
.then((info) => {
|
|
35
|
-
perfLog('early-chrome.js: Chrome CDP ready', info?.cdpEndpoint ?? null);
|
|
36
|
-
if (info) earlyChromeProcRef = info.proc;
|
|
37
|
-
return info;
|
|
38
|
-
})
|
|
39
|
-
: Promise.resolve(null);
|
package/lib/utils/find-chrome.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { accessSync, constants } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
|
|
4
|
-
const CANDIDATES = ['google-chrome-stable', 'google-chrome', 'chromium', 'chromium-browser'];
|
|
5
|
-
const PATH_DIRS = (process.env.PATH || '').split(':').filter(Boolean);
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Synchronously resolves the Chrome/Chromium executable path from `CHROME_BIN` or by probing
|
|
9
|
-
* common binary names in PATH directories using accessSync.
|
|
10
|
-
* Synchronous to avoid I/O saturation during the module-loading phase, which would cause async
|
|
11
|
-
* fs.access/exec-based approaches to be delayed by hundreds of milliseconds.
|
|
12
|
-
* @returns {string|null}
|
|
13
|
-
*/
|
|
14
|
-
function findChromeSync(): string | null {
|
|
15
|
-
if (process.env.CHROME_BIN) return process.env.CHROME_BIN;
|
|
16
|
-
|
|
17
|
-
for (const dir of PATH_DIRS) {
|
|
18
|
-
for (const name of CANDIDATES) {
|
|
19
|
-
const fullPath = join(dir, name);
|
|
20
|
-
try {
|
|
21
|
-
accessSync(fullPath, constants.X_OK);
|
|
22
|
-
return fullPath;
|
|
23
|
-
} catch {
|
|
24
|
-
// not found or not executable, try next
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Resolves the Chrome/Chromium executable path. Returns a Promise for API compatibility
|
|
33
|
-
* with callers, but the resolution is synchronous.
|
|
34
|
-
* @returns {Promise<string|null>}
|
|
35
|
-
*/
|
|
36
|
-
export default function findChrome(): Promise<string | null> {
|
|
37
|
-
return Promise.resolve(findChromeSync());
|
|
38
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
const ABSOLUTE_URL_REGEX = /^(?:[a-z]+:)?\/\//i;
|
|
2
|
-
const SCRIPT_SRC_REGEX = /<script[^>]+\bsrc=['"]([^'"]+)['"]/gi;
|
|
3
|
-
const LINK_HREF_REGEX = /<link[^>]+\bhref=['"]([^'"]+)['"]/gi;
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Parses an HTML string and returns all internal (non-absolute-URL) `<script src>` and `<link href>` paths.
|
|
7
|
-
* @returns {string[]}
|
|
8
|
-
*/
|
|
9
|
-
export default function findInternalAssetsFromHTML(htmlContent: string): string[] {
|
|
10
|
-
const links = [...htmlContent.matchAll(LINK_HREF_REGEX)]
|
|
11
|
-
.map((m) => m[1])
|
|
12
|
-
.filter((uri) => !ABSOLUTE_URL_REGEX.test(uri));
|
|
13
|
-
const scripts = [...htmlContent.matchAll(SCRIPT_SRC_REGEX)]
|
|
14
|
-
.map((m) => m[1])
|
|
15
|
-
.filter((uri) => !ABSOLUTE_URL_REGEX.test(uri));
|
|
16
|
-
|
|
17
|
-
return links.concat(scripts);
|
|
18
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import process from 'node:process';
|
|
2
|
-
import searchInParentDirectories from './search-in-parent-directories.ts';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Walks up parent directories from `cwd` to find the nearest `package.json` and returns its directory path.
|
|
6
|
-
* @returns {Promise<string>}
|
|
7
|
-
*/
|
|
8
|
-
export default async function findProjectRoot(): Promise<string> {
|
|
9
|
-
try {
|
|
10
|
-
const absolutePath = await searchInParentDirectories('.', 'package.json');
|
|
11
|
-
if (!absolutePath!.includes('package.json')) {
|
|
12
|
-
throw new Error('package.json mising');
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
return absolutePath!.replace('/package.json', '');
|
|
16
|
-
} catch (_error) {
|
|
17
|
-
console.log('couldnt find projects package.json, did you run $ npm init ??');
|
|
18
|
-
process.exit(1);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Prepends `count` repetitions of `indent` (default: one space) to each non-empty line of `string`.
|
|
3
|
-
* @example
|
|
4
|
-
* ```js
|
|
5
|
-
* import indentString from './lib/utils/indent-string.ts';
|
|
6
|
-
* console.assert(indentString('hello\nworld', 2) === ' hello\n world');
|
|
7
|
-
* ```
|
|
8
|
-
* @returns {string}
|
|
9
|
-
*/
|
|
10
|
-
export default function indentString(
|
|
11
|
-
string: string,
|
|
12
|
-
count: number = 1,
|
|
13
|
-
options: { indent?: string; includeEmptyLines?: boolean } = {},
|
|
14
|
-
): string {
|
|
15
|
-
const { indent = ' ', includeEmptyLines = false } = options;
|
|
16
|
-
|
|
17
|
-
if (count <= 0) {
|
|
18
|
-
return string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const regex = includeEmptyLines ? /^/gm : /^(?!\s*$)/gm;
|
|
22
|
-
|
|
23
|
-
return string.replace(regex, indent.repeat(count));
|
|
24
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import process from 'node:process';
|
|
2
|
-
|
|
3
|
-
const stdin = process.stdin;
|
|
4
|
-
const targetInputs: Record<string, { caseSensitive: boolean; closure: (input: string) => void }> =
|
|
5
|
-
{};
|
|
6
|
-
const inputs: (string | undefined)[] = [];
|
|
7
|
-
let listenerAdded = false;
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Registers a stdin listener that fires `closure` when the user types `inputString` (case-insensitive by default).
|
|
11
|
-
* @returns {void}
|
|
12
|
-
*/
|
|
13
|
-
export default function listenToKeyboardKey(
|
|
14
|
-
inputString: string,
|
|
15
|
-
closure: (input: string) => void,
|
|
16
|
-
options: { caseSensitive: boolean } = { caseSensitive: false },
|
|
17
|
-
): void {
|
|
18
|
-
if (!stdin.isTTY) return;
|
|
19
|
-
stdin.setRawMode(true);
|
|
20
|
-
stdin.resume();
|
|
21
|
-
stdin.setEncoding('utf8');
|
|
22
|
-
if (!listenerAdded) {
|
|
23
|
-
stdin.on('data', function (key) {
|
|
24
|
-
if (key === '\u0003') {
|
|
25
|
-
process.exit(); // so node process doesnt trap Control-C
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
inputs.shift();
|
|
29
|
-
inputs.push(key);
|
|
30
|
-
|
|
31
|
-
const currentInput = inputs.join('');
|
|
32
|
-
const targetListener = targetInputs[currentInput.toUpperCase()];
|
|
33
|
-
if (targetListener && targetListenerConformsToCase(targetListener, currentInput)) {
|
|
34
|
-
targetListener.closure(currentInput);
|
|
35
|
-
inputs.fill(undefined);
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
listenerAdded = true;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (inputString.length > inputs.length) {
|
|
42
|
-
inputs.length = inputString.length;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
targetInputs[inputString.toUpperCase()] = Object.assign(options, { closure });
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function targetListenerConformsToCase(
|
|
49
|
-
targetListener: { caseSensitive: boolean },
|
|
50
|
-
inputString: string,
|
|
51
|
-
): boolean {
|
|
52
|
-
if (targetListener.caseSensitive) {
|
|
53
|
-
return inputString === inputString.toUpperCase();
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return true;
|
|
57
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
// { inputs: [], debug: true, watch: true, failFast: true, htmlPaths: [], output }
|
|
2
|
-
interface ParsedFlags {
|
|
3
|
-
inputs: string[];
|
|
4
|
-
debug?: boolean;
|
|
5
|
-
watch?: boolean;
|
|
6
|
-
failFast?: boolean;
|
|
7
|
-
timeout?: number;
|
|
8
|
-
output?: string;
|
|
9
|
-
htmlPaths?: string[];
|
|
10
|
-
port?: number;
|
|
11
|
-
extensions?: string[];
|
|
12
|
-
browser?: 'chromium' | 'firefox' | 'webkit';
|
|
13
|
-
before?: string | false;
|
|
14
|
-
after?: string | false;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Parses `process.argv` into a qunitx flag object (`inputs`, `debug`, `watch`, `failFast`, `timeout`, `output`, `port`, `before`, `after`).
|
|
19
|
-
* @returns {object}
|
|
20
|
-
*/
|
|
21
|
-
export default function parseCliFlags(projectRoot: string): ParsedFlags {
|
|
22
|
-
const providedFlags = process.argv.slice(2).reduce(
|
|
23
|
-
(result, arg) => {
|
|
24
|
-
if (arg.startsWith('--debug')) {
|
|
25
|
-
return Object.assign(result, { debug: parseBoolean(arg.split('=')[1]) });
|
|
26
|
-
} else if (arg.startsWith('--watch')) {
|
|
27
|
-
return Object.assign(result, { watch: parseBoolean(arg.split('=')[1]) });
|
|
28
|
-
} else if (arg.startsWith('--failfast') || arg.startsWith('--failFast')) {
|
|
29
|
-
return Object.assign(result, { failFast: parseBoolean(arg.split('=')[1]) });
|
|
30
|
-
} else if (arg.startsWith('--timeout')) {
|
|
31
|
-
return Object.assign(result, { timeout: Number(arg.split('=')[1]) || 10000 });
|
|
32
|
-
} else if (arg.startsWith('--output')) {
|
|
33
|
-
return Object.assign(result, { output: arg.split('=')[1] });
|
|
34
|
-
} else if (arg.endsWith('.html')) {
|
|
35
|
-
if (result.htmlPaths) {
|
|
36
|
-
result.htmlPaths.push(arg);
|
|
37
|
-
} else {
|
|
38
|
-
result.htmlPaths = [arg];
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return result;
|
|
42
|
-
} else if (arg.startsWith('--port')) {
|
|
43
|
-
return Object.assign(result, { port: Number(arg.split('=')[1]) });
|
|
44
|
-
} else if (arg.startsWith('--extensions')) {
|
|
45
|
-
return Object.assign(result, {
|
|
46
|
-
extensions: arg
|
|
47
|
-
.split('=')[1]
|
|
48
|
-
.split(',')
|
|
49
|
-
.map((e) => e.trim()),
|
|
50
|
-
});
|
|
51
|
-
} else if (arg.startsWith('--browser')) {
|
|
52
|
-
const value = arg.split('=')[1];
|
|
53
|
-
if (!['chromium', 'firefox', 'webkit'].includes(value)) {
|
|
54
|
-
console.error(
|
|
55
|
-
`Invalid --browser value: "${value}". Must be one of: chromium, firefox, webkit`,
|
|
56
|
-
);
|
|
57
|
-
process.exit(1);
|
|
58
|
-
}
|
|
59
|
-
return Object.assign(result, { browser: value as 'chromium' | 'firefox' | 'webkit' });
|
|
60
|
-
} else if (arg.startsWith('--before')) {
|
|
61
|
-
return Object.assign(result, { before: parseModule(arg.split('=')[1]) });
|
|
62
|
-
} else if (arg.startsWith('--after')) {
|
|
63
|
-
return Object.assign(result, { after: parseModule(arg.split('=')[1]) });
|
|
64
|
-
} else if (arg === '--trace-perf') {
|
|
65
|
-
return result; // consumed by perf-logger.js at module load time, not stored in config
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// maybe set watch depth via micromatch(so incl metadata)
|
|
69
|
-
result.inputs.add(arg.startsWith(projectRoot) ? arg : `${process.cwd()}/${arg}`);
|
|
70
|
-
|
|
71
|
-
return result;
|
|
72
|
-
},
|
|
73
|
-
{ inputs: new Set<string>([]) } as ParsedFlags & { inputs: Set<string> },
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
return { ...providedFlags, inputs: Array.from(providedFlags.inputs) };
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function parseBoolean(result: string, defaultValue = true): boolean {
|
|
80
|
-
if (result === 'true') {
|
|
81
|
-
return true;
|
|
82
|
-
} else if (result === 'false') {
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return defaultValue;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function parseModule(value: string): string | false {
|
|
90
|
-
if (['false', "'false'", '"false"', ''].includes(value)) {
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return value;
|
|
95
|
-
}
|
package/lib/utils/path-exists.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs/promises';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Returns `true` if the given filesystem path is accessible, `false` otherwise.
|
|
5
|
-
* @example
|
|
6
|
-
* ```js
|
|
7
|
-
* import pathExists from './lib/utils/path-exists.ts';
|
|
8
|
-
* console.assert(await pathExists('/tmp') === true);
|
|
9
|
-
* console.assert(await pathExists('/tmp/nonexistent-qunitx-file') === false);
|
|
10
|
-
* ```
|
|
11
|
-
* @returns {Promise<boolean>}
|
|
12
|
-
*/
|
|
13
|
-
export default async function pathExists(path: string): Promise<boolean> {
|
|
14
|
-
try {
|
|
15
|
-
await fs.access(path);
|
|
16
|
-
|
|
17
|
-
return true;
|
|
18
|
-
} catch {
|
|
19
|
-
return false;
|
|
20
|
-
}
|
|
21
|
-
}
|
package/lib/utils/perf-logger.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Performance tracing logger for qunitx internals.
|
|
3
|
-
*
|
|
4
|
-
* Enable by passing `--trace-perf` to any run command:
|
|
5
|
-
* qunitx test/foo.js --trace-perf
|
|
6
|
-
*
|
|
7
|
-
* All perfLog() calls are zero-overhead no-ops when tracing is disabled —
|
|
8
|
-
* the flag is read once at module load time, no per-call argument evaluation.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
const isPerfTracing = process.argv.includes('--trace-perf');
|
|
12
|
-
|
|
13
|
-
const processStart = isPerfTracing ? Date.now() : 0;
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Writes a timestamped perf trace line to stderr when --trace-perf is active.
|
|
17
|
-
* @param {string} label
|
|
18
|
-
* @param {...*} details
|
|
19
|
-
*/
|
|
20
|
-
export function perfLog(label: string, ...details: unknown[]): void {
|
|
21
|
-
if (!isPerfTracing) return;
|
|
22
|
-
const elapsed = Date.now() - processStart;
|
|
23
|
-
const suffix = details.length ? ' ' + details.join(' ') : '';
|
|
24
|
-
process.stderr.write(`[perf +${elapsed}ms] ${label}${suffix}\n`);
|
|
25
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
import type { EarlyChrome } from '../types.ts';
|
|
3
|
-
|
|
4
|
-
const CDP_URL_REGEX = /DevTools listening on (ws:\/\/[^\s]+)/;
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Spawns a headless Chrome process with remote-debugging-port=0 and resolves once the
|
|
8
|
-
* CDP WebSocket endpoint is printed to stderr. Returns null if Chrome is unavailable or
|
|
9
|
-
* fails to start, so callers can fall back to playwright's normal launch.
|
|
10
|
-
* @returns {Promise<{proc: ChildProcess, cdpEndpoint: string} | null>}
|
|
11
|
-
*/
|
|
12
|
-
export default function preLaunchChrome(
|
|
13
|
-
chromePath: string | null | undefined,
|
|
14
|
-
args: string[],
|
|
15
|
-
): Promise<EarlyChrome | null> {
|
|
16
|
-
if (!chromePath) return Promise.resolve(null);
|
|
17
|
-
|
|
18
|
-
return new Promise((resolve) => {
|
|
19
|
-
const proc = spawn(chromePath, ['--remote-debugging-port=0', '--headless=new', ...args], {
|
|
20
|
-
stdio: ['ignore', 'ignore', 'pipe'],
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
let buffer = '';
|
|
24
|
-
proc.stderr.on('data', (chunk) => {
|
|
25
|
-
buffer += chunk.toString();
|
|
26
|
-
const match = buffer.match(CDP_URL_REGEX);
|
|
27
|
-
if (match) {
|
|
28
|
-
// Unref so Chrome's process + stderr pipe don't keep the Node.js event loop alive
|
|
29
|
-
// after all test work is done. Chrome is still killed via process.on('exit').
|
|
30
|
-
proc.unref();
|
|
31
|
-
proc.stderr.unref();
|
|
32
|
-
resolve({ proc, cdpEndpoint: match[1] });
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
// Resolve null on any startup failure so launchBrowser falls back to chromium.launch().
|
|
37
|
-
// The close handler resolves unconditionally: if Chrome exits before printing its CDP URL
|
|
38
|
-
// (code=0 for a clean exit, code=null for a signal-killed process such as OOM on CI),
|
|
39
|
-
// the original condition `code !== null && code !== 0` would leave the promise pending
|
|
40
|
-
// forever, causing launchBrowser to hang and the event loop to drain silently (exit 0).
|
|
41
|
-
// If Chrome already printed its URL and the promise is resolved, this is a no-op.
|
|
42
|
-
proc.on('error', () => resolve(null));
|
|
43
|
-
proc.on('close', () => resolve(null));
|
|
44
|
-
});
|
|
45
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs/promises';
|
|
2
|
-
import { dirname, join } from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
|
|
5
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Reads a boilerplate file by relative path, using the SEA asset store when running as a Node.js binary.
|
|
9
|
-
* @returns {Promise<string>}
|
|
10
|
-
*/
|
|
11
|
-
export default async function readBoilerplate(relativePath: string): Promise<string> {
|
|
12
|
-
const sea = await import('node:sea').catch(() => null);
|
|
13
|
-
if (sea?.isSea()) return sea.getAsset(relativePath, 'utf8');
|
|
14
|
-
return (await fs.readFile(join(__dirname, '../../templates', relativePath))).toString();
|
|
15
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Returns `portNumber` if it is free, otherwise recursively tries `portNumber + 1` until a free port is found.
|
|
3
|
-
* @returns {Promise<number>}
|
|
4
|
-
*/
|
|
5
|
-
export default async function resolvePortNumberFor(portNumber: number): Promise<number> {
|
|
6
|
-
if (await portIsAvailable(portNumber)) {
|
|
7
|
-
return portNumber;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
return await resolvePortNumberFor(portNumber + 1);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
async function portIsAvailable(portNumber: number): Promise<boolean> {
|
|
14
|
-
const net = await import('net');
|
|
15
|
-
return new Promise((resolve) => {
|
|
16
|
-
const server = net.createServer();
|
|
17
|
-
|
|
18
|
-
server.once('error', function (_err) {
|
|
19
|
-
resolve(false);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
server.once('listening', function () {
|
|
23
|
-
server.close();
|
|
24
|
-
resolve(true);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
server.listen(portNumber);
|
|
28
|
-
});
|
|
29
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { red } from './color.ts';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Dynamically imports `modulePath` and calls its default export with `params`; exits with code 1 on error.
|
|
5
|
-
* @returns {Promise<void>}
|
|
6
|
-
*/
|
|
7
|
-
export default async function runUserModule(
|
|
8
|
-
modulePath: string,
|
|
9
|
-
params: unknown,
|
|
10
|
-
scriptPosition: string,
|
|
11
|
-
): Promise<void> {
|
|
12
|
-
try {
|
|
13
|
-
const func = await import(modulePath);
|
|
14
|
-
if (func) {
|
|
15
|
-
func.default
|
|
16
|
-
? await func.default(params)
|
|
17
|
-
: typeof func === 'function'
|
|
18
|
-
? await func(params)
|
|
19
|
-
: null;
|
|
20
|
-
}
|
|
21
|
-
} catch (error) {
|
|
22
|
-
console.log('#', red(`QUnitX ${scriptPosition} script failed:`));
|
|
23
|
-
console.trace(error);
|
|
24
|
-
console.error(error);
|
|
25
|
-
|
|
26
|
-
return process.exit(1);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import pathExists from './path-exists.ts';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Recursively searches `directory` and its ancestors for a file or folder named `targetEntry`; returns the absolute path or `undefined`.
|
|
5
|
-
* @returns {Promise<string|undefined>}
|
|
6
|
-
*/
|
|
7
|
-
async function searchInParentDirectories(
|
|
8
|
-
directory: string,
|
|
9
|
-
targetEntry: string,
|
|
10
|
-
): Promise<string | undefined> {
|
|
11
|
-
const resolvedDirectory = directory === '.' ? process.cwd() : directory;
|
|
12
|
-
|
|
13
|
-
if (await pathExists(`${resolvedDirectory}/${targetEntry}`)) {
|
|
14
|
-
return `${resolvedDirectory}/${targetEntry}`;
|
|
15
|
-
} else if (resolvedDirectory === '') {
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return await searchInParentDirectories(
|
|
20
|
-
resolvedDirectory.slice(0, resolvedDirectory.lastIndexOf('/')),
|
|
21
|
-
targetEntry,
|
|
22
|
-
);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export default searchInParentDirectories;
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Returns a timer object with a `startTime` Date and a `stop()` method that returns elapsed milliseconds.
|
|
3
|
-
* @example
|
|
4
|
-
* ```js
|
|
5
|
-
* import timeCounter from './lib/utils/time-counter.ts';
|
|
6
|
-
* const t = timeCounter();
|
|
7
|
-
* const ms = t.stop();
|
|
8
|
-
* console.assert(ms >= 0);
|
|
9
|
-
* ```
|
|
10
|
-
* @returns {{ startTime: Date, stop: () => number }}
|
|
11
|
-
*/
|
|
12
|
-
export default function timeCounter(): { startTime: Date; stop: () => number } {
|
|
13
|
-
const startTime = new Date();
|
|
14
|
-
|
|
15
|
-
return {
|
|
16
|
-
startTime: startTime,
|
|
17
|
-
stop: () => +new Date() - +startTime,
|
|
18
|
-
};
|
|
19
|
-
}
|