@tracelane/cli 0.1.0-alpha.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.
- package/NOTICE +11 -0
- package/README.md +104 -0
- package/dist/commands/init.d.ts +44 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +383 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +80 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/detect.d.ts +59 -0
- package/dist/lib/detect.d.ts.map +1 -0
- package/dist/lib/detect.js +140 -0
- package/dist/lib/detect.js.map +1 -0
- package/dist/lib/gitignore.d.ts +17 -0
- package/dist/lib/gitignore.d.ts.map +1 -0
- package/dist/lib/gitignore.js +40 -0
- package/dist/lib/gitignore.js.map +1 -0
- package/dist/lib/prompt.d.ts +8 -0
- package/dist/lib/prompt.d.ts.map +1 -0
- package/dist/lib/prompt.js +28 -0
- package/dist/lib/prompt.js.map +1 -0
- package/dist/lib/wdio-editor.d.ts +159 -0
- package/dist/lib/wdio-editor.d.ts.map +1 -0
- package/dist/lib/wdio-editor.js +508 -0
- package/dist/lib/wdio-editor.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +14 -0
- package/dist/version.js.map +1 -0
- package/package.json +42 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// `tracelane` CLI entry. v0.1 ships with a single `init` subcommand that
|
|
3
|
+
// detects the user's test runner + package manager and wires @tracelane/wdio
|
|
4
|
+
// into their wdio.conf.* in one shot. No-op coming-soon paths for Playwright
|
|
5
|
+
// and Cypress.
|
|
6
|
+
//
|
|
7
|
+
// Arg parsing uses the built-in node:util.parseArgs at each command boundary
|
|
8
|
+
// (no CLI framework dependency) — this top-level dispatcher just routes on
|
|
9
|
+
// the first positional so the subcommand owns its own option schema, matching
|
|
10
|
+
// the peek-cli pattern.
|
|
11
|
+
import { realpathSync } from 'node:fs';
|
|
12
|
+
import { INIT_HELP, runInit } from './commands/init.js';
|
|
13
|
+
import { CLI_VERSION } from './version.js';
|
|
14
|
+
const HELP = `tracelane ${CLI_VERSION} - drop-in test-failure replay reporter scaffolding
|
|
15
|
+
|
|
16
|
+
Usage: npx tracelane <command> [options]
|
|
17
|
+
|
|
18
|
+
Commands:
|
|
19
|
+
init Detect runner + wire @tracelane/wdio into the project
|
|
20
|
+
|
|
21
|
+
Run \`npx tracelane <command> --help\` for command-specific options.
|
|
22
|
+
|
|
23
|
+
Docs: https://github.com/Cubenest/rrweb-stack/tree/main/packages/tracelane-wdio
|
|
24
|
+
`;
|
|
25
|
+
export async function run(argv) {
|
|
26
|
+
const [command, ...rest] = argv;
|
|
27
|
+
switch (command) {
|
|
28
|
+
case 'init':
|
|
29
|
+
return runInit(rest);
|
|
30
|
+
case 'version':
|
|
31
|
+
case '--version':
|
|
32
|
+
case '-v':
|
|
33
|
+
process.stdout.write(`${CLI_VERSION}\n`);
|
|
34
|
+
return 0;
|
|
35
|
+
case 'help':
|
|
36
|
+
case '--help':
|
|
37
|
+
case '-h':
|
|
38
|
+
process.stdout.write(HELP);
|
|
39
|
+
return 0;
|
|
40
|
+
case undefined:
|
|
41
|
+
// Bare `npx tracelane` with no subcommand: print usage + exit 0 (per
|
|
42
|
+
// the v0.1 spec — friendlier than the peek-cli convention of exit 1
|
|
43
|
+
// for missing command, because tracelane has a single subcommand and
|
|
44
|
+
// most users discovering the package via `npx tracelane` haven't
|
|
45
|
+
// typed `init` yet).
|
|
46
|
+
process.stdout.write(HELP);
|
|
47
|
+
return 0;
|
|
48
|
+
default:
|
|
49
|
+
process.stderr.write(`tracelane: unknown command '${command}'\n\n`);
|
|
50
|
+
process.stdout.write(HELP);
|
|
51
|
+
return 1;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Helper for tests that want the init help string. */
|
|
55
|
+
export { INIT_HELP };
|
|
56
|
+
async function main() {
|
|
57
|
+
const code = await run(process.argv.slice(2));
|
|
58
|
+
process.exitCode = code;
|
|
59
|
+
}
|
|
60
|
+
// Only run as a CLI when invoked directly as the `tracelane` bin. When this
|
|
61
|
+
// module is imported (tests or another package consuming `run`) an ESM
|
|
62
|
+
// `import` has no side effects. Mirror peek-cli's guard so symlink/realpath
|
|
63
|
+
// resolution doesn't break npx invocations.
|
|
64
|
+
const invokedDirectly = process.argv[1] !== undefined &&
|
|
65
|
+
(import.meta.url === `file://${process.argv[1]}` ||
|
|
66
|
+
(() => {
|
|
67
|
+
try {
|
|
68
|
+
return import.meta.url === `file://${realpathSync(process.argv[1])}`;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
})());
|
|
74
|
+
if (invokedDirectly) {
|
|
75
|
+
main().catch((err) => {
|
|
76
|
+
process.stderr.write(`tracelane: fatal - ${err instanceof Error ? (err.stack ?? err.message) : String(err)}\n`);
|
|
77
|
+
process.exitCode = 1;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,yEAAyE;AACzE,6EAA6E;AAC7E,6EAA6E;AAC7E,eAAe;AACf,EAAE;AACF,6EAA6E;AAC7E,2EAA2E;AAC3E,8EAA8E;AAC9E,wBAAwB;AAExB,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,MAAM,IAAI,GAAG,aAAa,WAAW;;;;;;;;;;CAUpC,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,GAAG,CAAC,IAAuB;IAC/C,MAAM,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IAEhC,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,MAAM;YACT,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC;QACvB,KAAK,SAAS,CAAC;QACf,KAAK,WAAW,CAAC;QACjB,KAAK,IAAI;YACP,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,WAAW,IAAI,CAAC,CAAC;YACzC,OAAO,CAAC,CAAC;QACX,KAAK,MAAM,CAAC;QACZ,KAAK,QAAQ,CAAC;QACd,KAAK,IAAI;YACP,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC3B,OAAO,CAAC,CAAC;QACX,KAAK,SAAS;YACZ,qEAAqE;YACrE,oEAAoE;YACpE,qEAAqE;YACrE,iEAAiE;YACjE,qBAAqB;YACrB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC3B,OAAO,CAAC,CAAC;QACX;YACE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,+BAA+B,OAAO,OAAO,CAAC,CAAC;YACpE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC3B,OAAO,CAAC,CAAC;IACb,CAAC;AACH,CAAC;AAED,uDAAuD;AACvD,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9C,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;AAC1B,CAAC;AAED,4EAA4E;AAC5E,uEAAuE;AACvE,4EAA4E;AAC5E,4CAA4C;AAC5C,MAAM,eAAe,GACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,SAAS;IAC7B,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,UAAU,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE;QAC9C,CAAC,GAAG,EAAE;YACJ,IAAI,CAAC;gBACH,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,UAAU,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACvE,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC,CAAC,EAAE,CAAC,CAAC;AACV,IAAI,eAAe,EAAE,CAAC;IACpB,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,sBAAsB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAC1F,CAAC;QACF,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACvB,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/** The test runners tracelane recognises. Only WDIO is fully wired in v0.1. */
|
|
2
|
+
export type Runner = 'wdio' | 'playwright' | 'cypress';
|
|
3
|
+
/** The package managers tracelane recognises (lockfile-driven detection). */
|
|
4
|
+
export type PackageManager = 'pnpm' | 'yarn' | 'npm' | 'bun';
|
|
5
|
+
/** One detected runner + the config file that triggered the detection. */
|
|
6
|
+
export interface DetectedRunner {
|
|
7
|
+
readonly runner: Runner;
|
|
8
|
+
/** Absolute path to the config file we matched on. */
|
|
9
|
+
readonly configPath: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Detect the test runner in `cwd`. Scans only the project root (not
|
|
13
|
+
* recursive) and returns the highest-priority match per RUNNER_SPECS. Returns
|
|
14
|
+
* `undefined` if nothing matched — the caller prints the "no runner detected"
|
|
15
|
+
* message and exits 1.
|
|
16
|
+
*
|
|
17
|
+
* `fileExists` is injected so unit tests can stub it; production callers pass
|
|
18
|
+
* the real `existsSync`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function detectRunner(cwd: string, fileExists?: (path: string) => boolean): DetectedRunner | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* Detect runner where the user passed `--runner` explicitly. We still try to
|
|
23
|
+
* find a matching config file (so the caller can edit it later) — if none of
|
|
24
|
+
* the runner's known config-file shapes exists, we return the runner with a
|
|
25
|
+
* `configPath` of undefined and let the caller decide whether that's fatal.
|
|
26
|
+
*
|
|
27
|
+
* For the wdio happy path the editor requires `configPath`, so the caller
|
|
28
|
+
* should print a helpful "no wdio.conf.* found in {cwd}" message and exit 1.
|
|
29
|
+
*/
|
|
30
|
+
export declare function findRunnerConfig(cwd: string, runner: Runner, fileExists?: (path: string) => boolean): string | undefined;
|
|
31
|
+
/** Result of package-manager detection. */
|
|
32
|
+
export interface DetectedPackageManager {
|
|
33
|
+
readonly manager: PackageManager;
|
|
34
|
+
/** All lockfiles that were present (for the multiple-lockfiles warning). */
|
|
35
|
+
readonly lockfilesFound: readonly string[];
|
|
36
|
+
/** True if multiple lockfiles were present — caller should warn. */
|
|
37
|
+
readonly multipleLockfiles: boolean;
|
|
38
|
+
/** True if we fell back to `npm` because no lockfile was present. */
|
|
39
|
+
readonly fallback: boolean;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Detect the package manager in `cwd` from lockfile presence. If multiple
|
|
43
|
+
* lockfiles are present, prefers pnpm > yarn > npm > bun and flags it on the
|
|
44
|
+
* result. If none are present, defaults to `npm` with `fallback: true`.
|
|
45
|
+
*/
|
|
46
|
+
export declare function detectPackageManager(cwd: string, fileExists?: (path: string) => boolean): DetectedPackageManager;
|
|
47
|
+
/**
|
|
48
|
+
* Build the package-manager install command for adding `@tracelane/wdio` as
|
|
49
|
+
* a devDependency. Returned as `[program, ...args]` for `spawnSync`, NOT a
|
|
50
|
+
* single shell string — `sh -c` is avoided to dodge injection on Windows and
|
|
51
|
+
* paths-with-spaces. Each manager has its own dev-flag spelling:
|
|
52
|
+
*
|
|
53
|
+
* pnpm add -D @tracelane/wdio
|
|
54
|
+
* yarn add -D @tracelane/wdio
|
|
55
|
+
* npm install --save-dev @tracelane/wdio
|
|
56
|
+
* bun add -d @tracelane/wdio
|
|
57
|
+
*/
|
|
58
|
+
export declare function installCommand(manager: PackageManager, pkg?: string): readonly string[];
|
|
59
|
+
//# sourceMappingURL=detect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../../src/lib/detect.ts"],"names":[],"mappings":"AAcA,+EAA+E;AAC/E,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,YAAY,GAAG,SAAS,CAAC;AAEvD,6EAA6E;AAC7E,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,CAAC;AAoC7D,0EAA0E;AAC1E,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,sDAAsD;IACtD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,MAAM,EACX,UAAU,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAoB,GACjD,cAAc,GAAG,SAAS,CAQ5B;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EACd,UAAU,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAoB,GACjD,MAAM,GAAG,SAAS,CAQpB;AAiBD,2CAA2C;AAC3C,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,4EAA4E;IAC5E,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,oEAAoE;IACpE,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC,qEAAqE;IACrE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,MAAM,EACX,UAAU,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAoB,GACjD,sBAAsB,CAsBxB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,cAAc,EACvB,GAAG,SAAoB,GACtB,SAAS,MAAM,EAAE,CAWnB"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// Pure detection helpers for `tracelane init`. Given a project root, figure
|
|
2
|
+
// out (a) which test runner this is and (b) which package manager to use to
|
|
3
|
+
// add the tracelane dependency. Everything in this file is a pure function of
|
|
4
|
+
// (cwd, injected fileExists probe) → result, so it tests cheaply against
|
|
5
|
+
// fixture trees and tempdirs.
|
|
6
|
+
//
|
|
7
|
+
// Runner detection looks ONLY at the project root, not recursively — a
|
|
8
|
+
// wdio.conf inside node_modules is not the user's project. Priority order
|
|
9
|
+
// when multiple match is WDIO > Playwright > Cypress (WDIO is the only path
|
|
10
|
+
// the v0.1 CLI fully wires).
|
|
11
|
+
import { existsSync } from 'node:fs';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
// Priority order: WDIO first because it's the only runner the CLI can wire
|
|
14
|
+
// end-to-end today. Playwright + Cypress detection drives a no-op "support
|
|
15
|
+
// coming Q3/Q4 2026" branch — they're listed so a user with both a Playwright
|
|
16
|
+
// and a Cypress config still gets routed to the more-mature path.
|
|
17
|
+
const RUNNER_SPECS = [
|
|
18
|
+
{
|
|
19
|
+
runner: 'wdio',
|
|
20
|
+
configFiles: ['wdio.conf.ts', 'wdio.conf.js', 'wdio.conf.mjs', 'wdio.conf.cjs'],
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
runner: 'playwright',
|
|
24
|
+
configFiles: [
|
|
25
|
+
'playwright.config.ts',
|
|
26
|
+
'playwright.config.js',
|
|
27
|
+
'playwright.config.mjs',
|
|
28
|
+
'playwright.config.cjs',
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
runner: 'cypress',
|
|
33
|
+
configFiles: [
|
|
34
|
+
'cypress.config.ts',
|
|
35
|
+
'cypress.config.js',
|
|
36
|
+
'cypress.config.mjs',
|
|
37
|
+
'cypress.config.cjs',
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
/**
|
|
42
|
+
* Detect the test runner in `cwd`. Scans only the project root (not
|
|
43
|
+
* recursive) and returns the highest-priority match per RUNNER_SPECS. Returns
|
|
44
|
+
* `undefined` if nothing matched — the caller prints the "no runner detected"
|
|
45
|
+
* message and exits 1.
|
|
46
|
+
*
|
|
47
|
+
* `fileExists` is injected so unit tests can stub it; production callers pass
|
|
48
|
+
* the real `existsSync`.
|
|
49
|
+
*/
|
|
50
|
+
export function detectRunner(cwd, fileExists = existsSync) {
|
|
51
|
+
for (const spec of RUNNER_SPECS) {
|
|
52
|
+
for (const f of spec.configFiles) {
|
|
53
|
+
const p = join(cwd, f);
|
|
54
|
+
if (fileExists(p))
|
|
55
|
+
return { runner: spec.runner, configPath: p };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Detect runner where the user passed `--runner` explicitly. We still try to
|
|
62
|
+
* find a matching config file (so the caller can edit it later) — if none of
|
|
63
|
+
* the runner's known config-file shapes exists, we return the runner with a
|
|
64
|
+
* `configPath` of undefined and let the caller decide whether that's fatal.
|
|
65
|
+
*
|
|
66
|
+
* For the wdio happy path the editor requires `configPath`, so the caller
|
|
67
|
+
* should print a helpful "no wdio.conf.* found in {cwd}" message and exit 1.
|
|
68
|
+
*/
|
|
69
|
+
export function findRunnerConfig(cwd, runner, fileExists = existsSync) {
|
|
70
|
+
const spec = RUNNER_SPECS.find((s) => s.runner === runner);
|
|
71
|
+
if (!spec)
|
|
72
|
+
return undefined;
|
|
73
|
+
for (const f of spec.configFiles) {
|
|
74
|
+
const p = join(cwd, f);
|
|
75
|
+
if (fileExists(p))
|
|
76
|
+
return p;
|
|
77
|
+
}
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
// Priority order: pnpm > yarn > npm > bun, matching most monorepos' actual
|
|
81
|
+
// preference. This is only consulted when MULTIPLE lockfiles exist — a
|
|
82
|
+
// pathological repo state we warn about but don't refuse.
|
|
83
|
+
const PM_SPECS = [
|
|
84
|
+
{ manager: 'pnpm', lockfile: 'pnpm-lock.yaml' },
|
|
85
|
+
{ manager: 'yarn', lockfile: 'yarn.lock' },
|
|
86
|
+
{ manager: 'npm', lockfile: 'package-lock.json' },
|
|
87
|
+
{ manager: 'bun', lockfile: 'bun.lockb' },
|
|
88
|
+
];
|
|
89
|
+
/**
|
|
90
|
+
* Detect the package manager in `cwd` from lockfile presence. If multiple
|
|
91
|
+
* lockfiles are present, prefers pnpm > yarn > npm > bun and flags it on the
|
|
92
|
+
* result. If none are present, defaults to `npm` with `fallback: true`.
|
|
93
|
+
*/
|
|
94
|
+
export function detectPackageManager(cwd, fileExists = existsSync) {
|
|
95
|
+
const present = PM_SPECS.filter((s) => fileExists(join(cwd, s.lockfile)));
|
|
96
|
+
if (present.length === 0) {
|
|
97
|
+
return {
|
|
98
|
+
manager: 'npm',
|
|
99
|
+
lockfilesFound: [],
|
|
100
|
+
multipleLockfiles: false,
|
|
101
|
+
fallback: true,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
const top = present[0];
|
|
105
|
+
if (!top) {
|
|
106
|
+
// Unreachable — `present.length === 0` is handled above — but the
|
|
107
|
+
// noUncheckedIndexedAccess rule wants the explicit guard.
|
|
108
|
+
return { manager: 'npm', lockfilesFound: [], multipleLockfiles: false, fallback: true };
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
manager: top.manager,
|
|
112
|
+
lockfilesFound: present.map((p) => p.lockfile),
|
|
113
|
+
multipleLockfiles: present.length > 1,
|
|
114
|
+
fallback: false,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Build the package-manager install command for adding `@tracelane/wdio` as
|
|
119
|
+
* a devDependency. Returned as `[program, ...args]` for `spawnSync`, NOT a
|
|
120
|
+
* single shell string — `sh -c` is avoided to dodge injection on Windows and
|
|
121
|
+
* paths-with-spaces. Each manager has its own dev-flag spelling:
|
|
122
|
+
*
|
|
123
|
+
* pnpm add -D @tracelane/wdio
|
|
124
|
+
* yarn add -D @tracelane/wdio
|
|
125
|
+
* npm install --save-dev @tracelane/wdio
|
|
126
|
+
* bun add -d @tracelane/wdio
|
|
127
|
+
*/
|
|
128
|
+
export function installCommand(manager, pkg = '@tracelane/wdio') {
|
|
129
|
+
switch (manager) {
|
|
130
|
+
case 'pnpm':
|
|
131
|
+
return ['pnpm', 'add', '-D', pkg];
|
|
132
|
+
case 'yarn':
|
|
133
|
+
return ['yarn', 'add', '-D', pkg];
|
|
134
|
+
case 'npm':
|
|
135
|
+
return ['npm', 'install', '--save-dev', pkg];
|
|
136
|
+
case 'bun':
|
|
137
|
+
return ['bun', 'add', '-d', pkg];
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=detect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect.js","sourceRoot":"","sources":["../../src/lib/detect.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** The .gitignore entry we add (with a leading explanation comment). */
|
|
2
|
+
export declare const TRACELANE_GITIGNORE_BLOCK = "\n# tracelane test-failure replay reports\ntracelane-reports/\n";
|
|
3
|
+
/**
|
|
4
|
+
* True if the existing .gitignore content already covers `tracelane-reports/`.
|
|
5
|
+
* Matches the exact line OR an unanchored variant (`tracelane-reports`
|
|
6
|
+
* without the trailing slash) since git treats both the same way for an
|
|
7
|
+
* untracked directory.
|
|
8
|
+
*/
|
|
9
|
+
export declare function hasTracelaneEntry(existing: string): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Produce the new .gitignore content after adding the tracelane entry. If the
|
|
12
|
+
* entry is already present (per `hasTracelaneEntry`), return the input
|
|
13
|
+
* unchanged. Inserts a leading newline if the file is non-empty and doesn't
|
|
14
|
+
* already end with one, so the comment doesn't glue onto a previous entry.
|
|
15
|
+
*/
|
|
16
|
+
export declare function mergeGitignore(existing: string): string;
|
|
17
|
+
//# sourceMappingURL=gitignore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitignore.d.ts","sourceRoot":"","sources":["../../src/lib/gitignore.ts"],"names":[],"mappings":"AAIA,wEAAwE;AACxE,eAAO,MAAM,yBAAyB,oEAC6B,CAAC;AAKpE;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAO3D;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAQvD"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Pure helpers for adding `tracelane-reports/` to a project's .gitignore.
|
|
2
|
+
// All file I/O lives in the command shell (init.ts) so we can test the merge
|
|
3
|
+
// logic against in-memory strings.
|
|
4
|
+
/** The .gitignore entry we add (with a leading explanation comment). */
|
|
5
|
+
export const TRACELANE_GITIGNORE_BLOCK = '\n# tracelane test-failure replay reports\ntracelane-reports/\n';
|
|
6
|
+
/** The bare entry line we look for when deciding "is this already covered?" */
|
|
7
|
+
const TRACELANE_GITIGNORE_LINE = 'tracelane-reports/';
|
|
8
|
+
/**
|
|
9
|
+
* True if the existing .gitignore content already covers `tracelane-reports/`.
|
|
10
|
+
* Matches the exact line OR an unanchored variant (`tracelane-reports`
|
|
11
|
+
* without the trailing slash) since git treats both the same way for an
|
|
12
|
+
* untracked directory.
|
|
13
|
+
*/
|
|
14
|
+
export function hasTracelaneEntry(existing) {
|
|
15
|
+
for (const raw of existing.split(/\r?\n/)) {
|
|
16
|
+
const line = raw.trim();
|
|
17
|
+
if (line === TRACELANE_GITIGNORE_LINE)
|
|
18
|
+
return true;
|
|
19
|
+
if (line === 'tracelane-reports')
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Produce the new .gitignore content after adding the tracelane entry. If the
|
|
26
|
+
* entry is already present (per `hasTracelaneEntry`), return the input
|
|
27
|
+
* unchanged. Inserts a leading newline if the file is non-empty and doesn't
|
|
28
|
+
* already end with one, so the comment doesn't glue onto a previous entry.
|
|
29
|
+
*/
|
|
30
|
+
export function mergeGitignore(existing) {
|
|
31
|
+
if (hasTracelaneEntry(existing))
|
|
32
|
+
return existing;
|
|
33
|
+
if (existing.length === 0) {
|
|
34
|
+
// Brand-new .gitignore: drop the leading newline.
|
|
35
|
+
return TRACELANE_GITIGNORE_BLOCK.replace(/^\n/, '');
|
|
36
|
+
}
|
|
37
|
+
const sep = existing.endsWith('\n') ? '' : '\n';
|
|
38
|
+
return `${existing}${sep}${TRACELANE_GITIGNORE_BLOCK.replace(/^\n/, '')}`;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=gitignore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitignore.js","sourceRoot":"","sources":["../../src/lib/gitignore.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,6EAA6E;AAC7E,mCAAmC;AAEnC,wEAAwE;AACxE,MAAM,CAAC,MAAM,yBAAyB,GACpC,iEAAiE,CAAC;AAEpE,+EAA+E;AAC/E,MAAM,wBAAwB,GAAG,oBAAoB,CAAC;AAEtD;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAgB;IAChD,KAAK,MAAM,GAAG,IAAI,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QACxB,IAAI,IAAI,KAAK,wBAAwB;YAAE,OAAO,IAAI,CAAC;QACnD,IAAI,IAAI,KAAK,mBAAmB;YAAE,OAAO,IAAI,CAAC;IAChD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,QAAgB;IAC7C,IAAI,iBAAiB,CAAC,QAAQ,CAAC;QAAE,OAAO,QAAQ,CAAC;IACjD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,kDAAkD;QAClD,OAAO,yBAAyB,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACtD,CAAC;IACD,MAAM,GAAG,GAAG,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAChD,OAAO,GAAG,QAAQ,GAAG,GAAG,GAAG,yBAAyB,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,CAAC;AAC5E,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Yes/no confirm. `defaultYes` controls the [Y/n] vs [y/N] hint and the
|
|
3
|
+
* answer when the user just hits Enter on an empty line. Closes the readline
|
|
4
|
+
* interface in a `finally` so a Ctrl-C during the prompt doesn't leave stdin
|
|
5
|
+
* raw.
|
|
6
|
+
*/
|
|
7
|
+
export declare function confirm(message: string, defaultYes?: boolean): Promise<boolean>;
|
|
8
|
+
//# sourceMappingURL=prompt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../../src/lib/prompt.ts"],"names":[],"mappings":"AAWA;;;;;GAKG;AACH,wBAAsB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,UAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAUlF"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Tiny zero-dependency `confirm` prompt over node:readline. Mirrors the
|
|
2
|
+
// pattern used by peek-cli/src/lib/prompt.ts but pared down to the one form
|
|
3
|
+
// `tracelane init` needs (yes/no with a default). No multi-select or
|
|
4
|
+
// free-text prompts in v0.1.
|
|
5
|
+
import { createInterface } from 'node:readline';
|
|
6
|
+
function ask(rl, question) {
|
|
7
|
+
return new Promise((resolve) => rl.question(question, resolve));
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Yes/no confirm. `defaultYes` controls the [Y/n] vs [y/N] hint and the
|
|
11
|
+
* answer when the user just hits Enter on an empty line. Closes the readline
|
|
12
|
+
* interface in a `finally` so a Ctrl-C during the prompt doesn't leave stdin
|
|
13
|
+
* raw.
|
|
14
|
+
*/
|
|
15
|
+
export async function confirm(message, defaultYes = true) {
|
|
16
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
17
|
+
try {
|
|
18
|
+
const suffix = defaultYes ? '[Y/n]' : '[y/N]';
|
|
19
|
+
const answer = (await ask(rl, `${message} ${suffix} `)).trim().toLowerCase();
|
|
20
|
+
if (answer.length === 0)
|
|
21
|
+
return defaultYes;
|
|
22
|
+
return answer === 'y' || answer === 'yes';
|
|
23
|
+
}
|
|
24
|
+
finally {
|
|
25
|
+
rl.close();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../src/lib/prompt.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,4EAA4E;AAC5E,qEAAqE;AACrE,6BAA6B;AAE7B,OAAO,EAAkB,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhE,SAAS,GAAG,CAAC,EAAa,EAAE,QAAgB;IAC1C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;AAClE,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,OAAe,EAAE,UAAU,GAAG,IAAI;IAC9D,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7E,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;QAC9C,MAAM,MAAM,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,EAAE,GAAG,OAAO,IAAI,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC7E,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,UAAU,CAAC;QAC3C,OAAO,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,KAAK,CAAC;IAC5C,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/** The import line we add at the top of the user's wdio.conf. */
|
|
2
|
+
export declare const TRACELANE_IMPORT = "import TraceLaneService from '@tracelane/wdio';";
|
|
3
|
+
/** The services-array entry we add. Tuple form: [Service, options]. */
|
|
4
|
+
export declare const TRACELANE_SERVICE_TUPLE = "[TraceLaneService, { mode: 'failed' }]";
|
|
5
|
+
/**
|
|
6
|
+
* Sanity bounds for the post-edit byte-count delta. The lower bound is
|
|
7
|
+
* different for the "added both import + entry" path vs the "entry only
|
|
8
|
+
* (idempotent import already present)" path — the import line alone is
|
|
9
|
+
* already ~50 bytes, so an entry-only edit can legitimately grow by less.
|
|
10
|
+
*/
|
|
11
|
+
export declare const EDIT_DELTA_MIN = 80;
|
|
12
|
+
export declare const EDIT_DELTA_MIN_ENTRY_ONLY = 30;
|
|
13
|
+
export declare const EDIT_DELTA_MAX = 400;
|
|
14
|
+
/** A `services: [...]` literal block we found inside the source. */
|
|
15
|
+
interface ServicesBlock {
|
|
16
|
+
/** Absolute character offset of the opening `[`. */
|
|
17
|
+
readonly openIndex: number;
|
|
18
|
+
/** Absolute character offset of the matching closing `]`. */
|
|
19
|
+
readonly closeIndex: number;
|
|
20
|
+
/** Substring between `[` and `]` (exclusive). */
|
|
21
|
+
readonly inner: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Produce a same-length copy of `src` where every string literal and every
|
|
25
|
+
* comment is replaced with a same-length space-padded run. Newlines inside
|
|
26
|
+
* line comments and block comments are preserved (so regex anchors like
|
|
27
|
+
* `^...$` with the `m` flag still see the same line structure).
|
|
28
|
+
*
|
|
29
|
+
* Inside template literals (backtick), `${...}` interpolation BODIES are
|
|
30
|
+
* treated as code — we re-enter normal mode for the interpolated expression
|
|
31
|
+
* so a `services:` mention there isn't shadowed. This is rare in practice
|
|
32
|
+
* (configs don't usually compute the services array via template literals)
|
|
33
|
+
* but it's cheap to do right.
|
|
34
|
+
*/
|
|
35
|
+
export declare function stripStringsAndComments(src: string): string;
|
|
36
|
+
/**
|
|
37
|
+
* Locate the `services:` array literal in a wdio.conf source string. Returns
|
|
38
|
+
* the bracket indices + the substring inside.
|
|
39
|
+
*
|
|
40
|
+
* The match runs against `stripStringsAndComments(source)` so a `services:`
|
|
41
|
+
* inside a comment or a string can never be selected. Among the remaining
|
|
42
|
+
* candidates, we prefer the SHALLOWEST brace+bracket depth — this rejects
|
|
43
|
+
* `capabilities: [{ services: ['safari'] }]` (depth 3) in favour of the
|
|
44
|
+
* outer testrunner `services:` (depth 1). The depth-1 preference matches
|
|
45
|
+
* the WDIO config schema (services live directly on the config object).
|
|
46
|
+
*
|
|
47
|
+
* Returns `undefined` if no `services:` key with an array literal is present
|
|
48
|
+
* outside strings/comments.
|
|
49
|
+
*/
|
|
50
|
+
export declare function findServicesArray(source: string): ServicesBlock | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* Insert the tracelane import after the LAST `import ... from '...';` line in
|
|
53
|
+
* the source. If no imports exist (which should never happen for a real WDIO
|
|
54
|
+
* conf) we insert at the top.
|
|
55
|
+
*
|
|
56
|
+
* We don't attempt to deduplicate or sort — the user's editor + Biome will
|
|
57
|
+
* fold our line in on the next save. Idempotency is enforced by the caller
|
|
58
|
+
* (it checks `hasTracelaneImport(source)` before invoking this).
|
|
59
|
+
*
|
|
60
|
+
* Match runs against the stripped buffer so a top-of-file block comment
|
|
61
|
+
* mentioning `import` cannot shadow the real import section.
|
|
62
|
+
*/
|
|
63
|
+
export declare function insertTracelaneImport(source: string): string;
|
|
64
|
+
/**
|
|
65
|
+
* True if the source already has an `@tracelane/wdio` import. Matches both
|
|
66
|
+
* default and named import forms (`import TraceLaneService from ...` /
|
|
67
|
+
* `import { TraceLaneService } from ...`) and either quote style. Runs
|
|
68
|
+
* against the stripped buffer so a commented-out example
|
|
69
|
+
* // import TraceLaneService from '@tracelane/wdio';
|
|
70
|
+
* doesn't false-positive — but the stripped buffer preserves the import
|
|
71
|
+
* keyword itself, so a real import is still detected.
|
|
72
|
+
*
|
|
73
|
+
* Wait — we DO want to match the literal '@tracelane/wdio' inside the
|
|
74
|
+
* `from '...'` clause. The strip replaces string CONTENT with spaces, so
|
|
75
|
+
* `from '@tracelane/wdio'` becomes `from ' '` in the stripped
|
|
76
|
+
* buffer. We need to recognise the real import by structure: the keyword
|
|
77
|
+
* `import`, the binding, `from`, a string literal of any contents. We test
|
|
78
|
+
* the structural shape on the stripped buffer, then verify the string
|
|
79
|
+
* literal's contents against the RAW source at the matched offsets.
|
|
80
|
+
*/
|
|
81
|
+
export declare function hasTracelaneImport(source: string): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* True if the source already mentions the TraceLaneService in a services
|
|
84
|
+
* array entry — either bare (`TraceLaneService`) or tuple
|
|
85
|
+
* (`[TraceLaneService, ...]`). Runs against the stripped buffer to ignore
|
|
86
|
+
* comment + string mentions.
|
|
87
|
+
*/
|
|
88
|
+
export declare function hasTracelaneServiceEntry(source: string): boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Insert `[TraceLaneService, { mode: 'failed' }]` as the LAST element of an
|
|
91
|
+
* existing services array. Three input shapes covered:
|
|
92
|
+
*
|
|
93
|
+
* services: [] → services: [[Service, opts]]
|
|
94
|
+
* services: ['devtools'] → services: ['devtools', [Service, opts]]
|
|
95
|
+
* services: [['devtools', {}]] → services: [['devtools', {}], [Service, opts]]
|
|
96
|
+
*
|
|
97
|
+
* Returns the new full source. Caller has already located `block` via
|
|
98
|
+
* `findServicesArray`.
|
|
99
|
+
*/
|
|
100
|
+
export declare function appendToServicesArray(source: string, block: ServicesBlock): string;
|
|
101
|
+
/**
|
|
102
|
+
* Insert a `services: [[TraceLaneService, ...]]` line into a config object
|
|
103
|
+
* that has no `services:` key at all.
|
|
104
|
+
*
|
|
105
|
+
* Strategy: find the FIRST top-level `export const config` / `export default`
|
|
106
|
+
* config object literal, locate its outermost `{`/`}`, and insert
|
|
107
|
+
* services: [[TraceLaneService, { mode: 'failed' }]],
|
|
108
|
+
* just before the closing `}`. If we can't find the object literal, return
|
|
109
|
+
* undefined and let the caller back out to the manual-snippet path.
|
|
110
|
+
*
|
|
111
|
+
* Runs against the stripped buffer so an `export default { ... }` string in
|
|
112
|
+
* a doc-comment header can't be matched.
|
|
113
|
+
*/
|
|
114
|
+
export declare function insertServicesKey(source: string): string | undefined;
|
|
115
|
+
/** Result shape for `applyWdioEdit`. */
|
|
116
|
+
export type WdioEditResult = {
|
|
117
|
+
readonly ok: true;
|
|
118
|
+
/** The new source content to write back. */
|
|
119
|
+
readonly source: string;
|
|
120
|
+
/** True if the source already had the import + service entry — no-op. */
|
|
121
|
+
readonly alreadyConfigured: boolean;
|
|
122
|
+
/** True if we ADDED the import (false on idempotent re-run). */
|
|
123
|
+
readonly addedImport: boolean;
|
|
124
|
+
/** True if we ADDED the service-array entry. */
|
|
125
|
+
readonly addedServiceEntry: boolean;
|
|
126
|
+
} | {
|
|
127
|
+
readonly ok: false;
|
|
128
|
+
/** The reason the editor backed out. Surfaced verbatim to the user. */
|
|
129
|
+
readonly reason: string;
|
|
130
|
+
/** The snippet the user should paste manually (always populated). */
|
|
131
|
+
readonly manualSnippet: string;
|
|
132
|
+
};
|
|
133
|
+
/**
|
|
134
|
+
* Manual-paste snippet shown when the editor backs out. The user pastes this
|
|
135
|
+
* at the top of their conf and adds the tuple to `services:` themselves.
|
|
136
|
+
*
|
|
137
|
+
* This is the single source of truth — `init.ts`'s "restored from backup"
|
|
138
|
+
* path also references this constant so the snippet copy doesn't drift.
|
|
139
|
+
*/
|
|
140
|
+
export declare const MANUAL_SNIPPET = "import TraceLaneService from '@tracelane/wdio';\n\n// Inside your config object's `services` array:\n// services: [[TraceLaneService, { mode: 'failed' }]],\n";
|
|
141
|
+
/**
|
|
142
|
+
* The single high-level entrypoint. Given the current source of a
|
|
143
|
+
* wdio.conf.*, produce the new source (with the import + service tuple
|
|
144
|
+
* added) — or fail cleanly with a manual snippet for the user to paste.
|
|
145
|
+
*
|
|
146
|
+
* Steps:
|
|
147
|
+
* 1. If both the import + the service entry are already present, no-op.
|
|
148
|
+
* 2. Add the import if it's missing.
|
|
149
|
+
* 3. Append to the existing `services: [...]` array, OR insert a new
|
|
150
|
+
* `services:` key if none exists.
|
|
151
|
+
* 4. Sanity-check the byte-count delta + presence of the marker strings.
|
|
152
|
+
*
|
|
153
|
+
* The sanity check at the END is the safety net: if our regex went off the
|
|
154
|
+
* rails (e.g. landed on an unusual shape) the delta is wildly wrong and we
|
|
155
|
+
* back out instead of writing a corrupt file.
|
|
156
|
+
*/
|
|
157
|
+
export declare function applyWdioEdit(originalSource: string): WdioEditResult;
|
|
158
|
+
export {};
|
|
159
|
+
//# sourceMappingURL=wdio-editor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wdio-editor.d.ts","sourceRoot":"","sources":["../../src/lib/wdio-editor.ts"],"names":[],"mappings":"AAuCA,iEAAiE;AACjE,eAAO,MAAM,gBAAgB,oDAAoD,CAAC;AAElF,uEAAuE;AACvE,eAAO,MAAM,uBAAuB,2CAA2C,CAAC;AAEhF;;;;;GAKG;AACH,eAAO,MAAM,cAAc,KAAK,CAAC;AACjC,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAC5C,eAAO,MAAM,cAAc,MAAM,CAAC;AAElC,oEAAoE;AACpE,UAAU,aAAa;IACrB,oDAAoD;IACpD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,6DAA6D;IAC7D,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,iDAAiD;IACjD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAiBD;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAqG3D;AAwED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CA6B3E;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAgB5D;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAgB1D;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAGhE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,MAAM,CAoBlF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAUpE;AAED,wCAAwC;AACxC,MAAM,MAAM,cAAc,GACtB;IACE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,4CAA4C;IAC5C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC,gEAAgE;IAChE,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,gDAAgD;IAChD,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;CACrC,GACD;IACE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,uEAAuE;IACvE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,qEAAqE;IACrE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC,CAAC;AAEN;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,oKAI1B,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,cAAc,EAAE,MAAM,GAAG,cAAc,CA6EpE"}
|