@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/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"}