e2e 0.16.0 → 0.18.0
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/README.md +10 -0
- package/dist/.build.json +1 -1
- package/dist/agent/act.js +7 -4
- package/dist/agent/action-dispatcher.js +4 -5
- package/dist/agent/actions.d.ts +32 -23
- package/dist/agent/actions.js +38 -24
- package/dist/agent/ai-sdk.js +5 -4
- package/dist/agent/error.js +3 -3
- package/dist/agent/invocation.js +3 -4
- package/dist/agent/model/sdk.js +2 -0
- package/dist/agent/model/tool-choice.d.ts +9 -0
- package/dist/agent/model/tool-choice.js +14 -0
- package/dist/agent/observation-feed.d.ts +5 -6
- package/dist/agent/observation-feed.js +8 -11
- package/dist/agent/observation.d.ts +40 -11
- package/dist/agent/observation.js +109 -59
- package/dist/agent/phases.d.ts +7 -1
- package/dist/agent/phases.js +12 -3
- package/dist/agent/point-tap.d.ts +8 -8
- package/dist/agent/point-tap.js +1 -1
- package/dist/agent/prompts.js +3 -0
- package/dist/agent/replay.d.ts +19 -8
- package/dist/agent/replay.js +29 -29
- package/dist/agent/scroll-target.d.ts +2 -3
- package/dist/agent/step-cache.d.ts +43 -15
- package/dist/agent/step-cache.js +156 -67
- package/dist/agent/tool-loop.js +94 -5
- package/dist/cache/anchors.d.ts +72 -23
- package/dist/cache/anchors.js +233 -60
- package/dist/cache/context.d.ts +25 -14
- package/dist/cache/context.js +50 -8
- package/dist/cache/decide.d.ts +2 -2
- package/dist/cache/decide.js +4 -6
- package/dist/cache/identity.d.ts +18 -4
- package/dist/cache/identity.js +9 -4
- package/dist/cache/recorder.d.ts +15 -1
- package/dist/cache/recorder.js +36 -23
- package/dist/cache/rekeyed.d.ts +46 -0
- package/dist/cache/rekeyed.js +89 -0
- package/dist/cache/relocate.d.ts +14 -14
- package/dist/cache/relocate.js +33 -25
- package/dist/cache/route.d.ts +32 -33
- package/dist/cache/route.js +94 -50
- package/dist/cache/store.d.ts +6 -0
- package/dist/cache/store.js +21 -1
- package/dist/cache/trace.d.ts +53 -14
- package/dist/cache/trace.js +76 -30
- package/dist/cli/bin.js +2 -2
- package/dist/cli/index.js +43 -32
- package/dist/cli/init/engines.d.ts +1 -3
- package/dist/cli/init/engines.js +2 -2
- package/dist/cli/init/gateways.d.ts +1 -1
- package/dist/cli/init/gateways.js +11 -1
- package/dist/cli/init/mcp-config.d.ts +2 -1
- package/dist/cli/init/mcp-config.js +8 -3
- package/dist/cli/init/sibling-versions.json +1 -1
- package/dist/cli/init/versions.d.ts +0 -8
- package/dist/cli/init/versions.js +1 -10
- package/dist/cli/mcp.d.ts +3 -2
- package/dist/cli/mcp.js +52 -16
- package/dist/cli/run-output.d.ts +17 -0
- package/dist/cli/run-output.js +45 -0
- package/dist/collect/collect.js +8 -4
- package/dist/collect/registry.js +27 -16
- package/dist/config/compiled-files.d.ts +36 -0
- package/dist/config/compiled-files.js +50 -0
- package/dist/config/diagnose.js +21 -19
- package/dist/config/esm-hooks.d.ts +39 -44
- package/dist/config/esm-hooks.js +238 -63
- package/dist/config/load.d.ts +1 -7
- package/dist/config/load.js +23 -66
- package/dist/config/register.d.ts +8 -0
- package/dist/config/register.js +9 -0
- package/dist/config/resolve.d.ts +1 -0
- package/dist/config/resolve.js +33 -1
- package/dist/config/specifiers.d.ts +19 -0
- package/dist/config/specifiers.js +33 -0
- package/dist/config/tsconfig.d.ts +56 -0
- package/dist/config/tsconfig.js +115 -0
- package/dist/config/typescript.d.ts +19 -0
- package/dist/config/typescript.js +253 -0
- package/dist/engine/contract.d.ts +1 -0
- package/dist/engine/index.d.ts +4 -2
- package/dist/engine/session.js +2 -2
- package/dist/expect/async.js +4 -2
- package/dist/expect/poll-scope.d.ts +52 -0
- package/dist/expect/poll-scope.js +113 -0
- package/dist/expect/poll.js +33 -6
- package/dist/expect/values.js +11 -4
- package/dist/internal/abandoned.d.ts +11 -0
- package/dist/internal/abandoned.js +23 -0
- package/dist/internal/brands.d.ts +2 -0
- package/dist/internal/brands.js +2 -0
- package/dist/internal/client-identity.d.ts +9 -0
- package/dist/internal/client-identity.js +14 -0
- package/dist/internal/errors.d.ts +10 -3
- package/dist/internal/errors.js +23 -8
- package/dist/internal/node-version.d.ts +16 -7
- package/dist/internal/node-version.js +50 -15
- package/dist/internal/package-manager.js +1 -1
- package/dist/internal/realm-slot.d.ts +5 -4
- package/dist/internal/redact.d.ts +9 -4
- package/dist/internal/redact.js +120 -23
- package/dist/internal/time.d.ts +26 -4
- package/dist/internal/time.js +58 -14
- package/dist/internal/urls.d.ts +9 -5
- package/dist/internal/urls.js +19 -10
- package/dist/locator/engine.d.ts +12 -2
- package/dist/locator/engine.js +55 -14
- package/dist/locator/screen.js +59 -9
- package/dist/mcp/catalog.d.ts +3 -2
- package/dist/mcp/catalog.js +6 -6
- package/dist/mcp/output.d.ts +59 -0
- package/dist/mcp/output.js +129 -0
- package/dist/mcp/server.d.ts +4 -0
- package/dist/mcp/server.js +9 -12
- package/dist/mcp/session.d.ts +8 -1
- package/dist/mcp/session.js +56 -15
- package/dist/mcp/tools.js +7 -5
- package/dist/oauth/chatgpt.js +3 -23
- package/dist/oauth/cli.js +1 -0
- package/dist/oauth/copilot.d.ts +5 -4
- package/dist/oauth/copilot.js +80 -7
- package/dist/oauth/grok.js +2 -1
- package/dist/oauth/models.js +2 -1
- package/dist/oauth/opencode-console.d.ts +10 -0
- package/dist/oauth/opencode-console.js +133 -0
- package/dist/oauth/providers/github-copilot.d.ts +12 -0
- package/dist/oauth/providers/github-copilot.js +77 -23
- package/dist/oauth/providers/opencode-console.d.ts +31 -0
- package/dist/oauth/providers/opencode-console.js +148 -0
- package/dist/oauth/providers.d.ts +2 -6
- package/dist/oauth/providers.js +3 -7
- package/dist/oauth/responses.d.ts +9 -0
- package/dist/oauth/responses.js +23 -0
- package/dist/report/build.d.ts +26 -0
- package/dist/report/build.js +16 -8
- package/dist/report/failure-text.d.ts +6 -1
- package/dist/report/failure-text.js +14 -4
- package/dist/report/format.d.ts +10 -5
- package/dist/report/format.js +12 -5
- package/dist/report/junit.js +11 -12
- package/dist/report/list.d.ts +12 -0
- package/dist/report/list.js +61 -24
- package/dist/report/markdown.d.ts +1 -1
- package/dist/report/markdown.js +37 -14
- package/dist/report/repeats.d.ts +6 -1
- package/dist/report/repeats.js +12 -3
- package/dist/run/artifacts.d.ts +22 -4
- package/dist/run/artifacts.js +71 -3
- package/dist/run/execute.d.ts +34 -5
- package/dist/run/execute.js +93 -44
- package/dist/run/extended-fixtures.js +6 -2
- package/dist/run/failure-evidence.js +10 -4
- package/dist/run/fixtures.js +6 -8
- package/dist/run/in-process.d.ts +2 -0
- package/dist/run/in-process.js +52 -10
- package/dist/run/last-run.d.ts +42 -8
- package/dist/run/last-run.js +121 -14
- package/dist/run/process-output.d.ts +73 -0
- package/dist/run/process-output.js +130 -0
- package/dist/run/provision.d.ts +2 -0
- package/dist/run/provision.js +1 -0
- package/dist/run/realm.d.ts +4 -1
- package/dist/run/realm.js +20 -6
- package/dist/run/records.d.ts +20 -4
- package/dist/run/records.js +26 -0
- package/dist/run/retry.d.ts +10 -1
- package/dist/run/retry.js +30 -12
- package/dist/run/runner.d.ts +9 -1
- package/dist/run/runner.js +101 -39
- package/dist/run/scheduler.js +255 -19
- package/dist/run/secrecy.d.ts +22 -19
- package/dist/run/secrecy.js +22 -18
- package/dist/run/serial.d.ts +75 -2
- package/dist/run/serial.js +243 -59
- package/dist/run/standalone.js +1 -1
- package/dist/run/steps.d.ts +0 -6
- package/dist/run/steps.js +3 -20
- package/dist/run/trace-redaction.d.ts +5 -6
- package/dist/run/trace-redaction.js +5 -6
- package/dist/run/worker/entry.js +40 -18
- package/dist/run/worker/handle.js +10 -5
- package/dist/run/worker/protocol.d.ts +85 -3
- package/dist/run/worker/session.d.ts +2 -0
- package/dist/run/worker/session.js +29 -0
- package/dist/telemetry/environment.d.ts +2 -1
- package/dist/telemetry/environment.js +12 -2
- package/dist/telemetry/events.js +1 -0
- package/dist/telemetry/project.d.ts +5 -6
- package/dist/telemetry/project.js +6 -11
- package/dist/telemetry/store.d.ts +5 -5
- package/dist/telemetry/store.js +5 -7
- package/dist/telemetry/telemetry.d.ts +2 -3
- package/dist/telemetry/telemetry.js +2 -4
- package/dist/types.d.ts +15 -6
- package/docs/agent-steps.mdx +1 -0
- package/docs/agents.mdx +7 -3
- package/docs/api-testing.mdx +2 -1
- package/docs/authentication.mdx +6 -3
- package/docs/browser.mdx +49 -12
- package/docs/bug-bash.mdx +2 -1
- package/docs/cache.mdx +66 -19
- package/docs/ci.mdx +14 -9
- package/docs/coding-agents.mdx +2 -1
- package/docs/core-concepts.mdx +208 -0
- package/docs/debugging.mdx +25 -12
- package/docs/decision-models.mdx +142 -0
- package/docs/example-projects.mdx +32 -0
- package/docs/executors.mdx +7 -2
- package/docs/explore.mdx +1 -0
- package/docs/github.mdx +14 -5
- package/docs/index.mdx +9 -5
- package/docs/integrations/eas.mdx +1 -0
- package/docs/integrations/index.mdx +1 -0
- package/docs/integrations/kernel.mdx +3 -2
- package/docs/migrate/cypress.mdx +26 -3
- package/docs/migrate/detox.mdx +24 -1
- package/docs/migrate/maestro.mdx +28 -4
- package/docs/migrate/playwright.mdx +44 -18
- package/docs/migrate/selenium.mdx +27 -3
- package/docs/mobile-ci.mdx +6 -5
- package/docs/mobile.mdx +96 -8
- package/docs/models.mdx +60 -9
- package/docs/quickstart.mdx +28 -16
- package/docs/reference/agent.mdx +13 -3
- package/docs/reference/app.mdx +2 -1
- package/docs/reference/cli.mdx +41 -18
- package/docs/reference/config.mdx +67 -13
- package/docs/reference/engine.mdx +25 -13
- package/docs/reference/environment.mdx +10 -2
- package/docs/reference/errors.mdx +13 -10
- package/docs/reference/expect.mdx +36 -13
- package/docs/reference/mcp.mdx +21 -8
- package/docs/reference/mobile.mdx +24 -13
- package/docs/reference/reporters.mdx +9 -2
- package/docs/reference/screen.mdx +49 -16
- package/docs/reference/test.mdx +32 -5
- package/docs/reference/web.mdx +133 -23
- package/docs/security.mdx +58 -42
- package/docs/subscriptions.mdx +92 -12
- package/docs/telemetry.mdx +9 -6
- package/docs/tools.mdx +1 -0
- package/docs/{starting-your-app.mdx → web.mdx} +157 -11
- package/docs/writing-an-engine.mdx +2 -1
- package/package.json +25 -8
- package/schema/report-v1.schema.json +14 -1
- package/skills/e2e/SKILL.md +5 -3
- package/skills/e2e/references/agent.md +33 -11
- package/skills/e2e/references/debugging.md +4 -4
- package/skills/e2e/references/mcp.md +8 -3
- package/skills/e2e/references/running.md +15 -9
- package/skills/e2e/references/setup.md +45 -16
- package/skills/e2e/references/writing-tests.md +35 -18
- package/docs/viewports.mdx +0 -146
- package/docs/writing-tests.mdx +0 -256
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `e2e run` process's stdout and stderr. User code runs in this process
|
|
3
|
+
* too (the config's top-level code, test files while they are collected,
|
|
4
|
+
* reporters) and may print a secret, so every write to either stream goes
|
|
5
|
+
* through a `RunnerOutput`, redacted, until the process exits; only the list
|
|
6
|
+
* reporter writes to the terminal past it.
|
|
7
|
+
*/
|
|
8
|
+
import { RunnerOutput } from '../run/process-output.ts';
|
|
9
|
+
/**
|
|
10
|
+
* Takes over `process.stdout` and `process.stderr` for the rest of the
|
|
11
|
+
* process, once: a reporter the run stopped waiting for, or a timer the
|
|
12
|
+
* config or a test file set, can still print after the run. The caller ends
|
|
13
|
+
* the run's output with `end()`, which flushes what is held and stops
|
|
14
|
+
* showing output through the list reporter; later writes stay redacted, and
|
|
15
|
+
* their last unfinished piece prints at exit.
|
|
16
|
+
*/
|
|
17
|
+
export declare function claimRunnerOutput(): RunnerOutput;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `e2e run` process's stdout and stderr. User code runs in this process
|
|
3
|
+
* too (the config's top-level code, test files while they are collected,
|
|
4
|
+
* reporters) and may print a secret, so every write to either stream goes
|
|
5
|
+
* through a `RunnerOutput`, redacted, until the process exits; only the list
|
|
6
|
+
* reporter writes to the terminal past it.
|
|
7
|
+
*/
|
|
8
|
+
import { RunnerOutput } from '../run/process-output.js';
|
|
9
|
+
import { processSecrets } from '../run/secrecy.js';
|
|
10
|
+
/**
|
|
11
|
+
* Encodings in which a string spells bytes rather than text: the write puts
|
|
12
|
+
* those bytes on the terminal, so they are what is redacted. A string in any
|
|
13
|
+
* other encoding is the text itself, redacted and written as UTF-8.
|
|
14
|
+
*/
|
|
15
|
+
const BYTE_ENCODINGS = new Set(['hex', 'base64', 'base64url']);
|
|
16
|
+
/** The process's claimed output, once claimed: the wrappers stay for the rest of the process. */
|
|
17
|
+
let claimed;
|
|
18
|
+
/**
|
|
19
|
+
* Takes over `process.stdout` and `process.stderr` for the rest of the
|
|
20
|
+
* process, once: a reporter the run stopped waiting for, or a timer the
|
|
21
|
+
* config or a test file set, can still print after the run. The caller ends
|
|
22
|
+
* the run's output with `end()`, which flushes what is held and stops
|
|
23
|
+
* showing output through the list reporter; later writes stay redacted, and
|
|
24
|
+
* their last unfinished piece prints at exit.
|
|
25
|
+
*/
|
|
26
|
+
export function claimRunnerOutput() {
|
|
27
|
+
if (claimed !== undefined)
|
|
28
|
+
return claimed;
|
|
29
|
+
const streams = { stdout: process.stdout, stderr: process.stderr };
|
|
30
|
+
const terminal = {
|
|
31
|
+
stdout: streams.stdout.write.bind(streams.stdout),
|
|
32
|
+
stderr: streams.stderr.write.bind(streams.stderr),
|
|
33
|
+
};
|
|
34
|
+
const output = new RunnerOutput(terminal, processSecrets);
|
|
35
|
+
for (const name of ['stdout', 'stderr']) {
|
|
36
|
+
streams[name].write = ((chunk, encoding, callback) => {
|
|
37
|
+
const done = typeof encoding === 'function' ? encoding : callback;
|
|
38
|
+
const bytes = typeof chunk === 'string' && typeof encoding === 'string' && BYTE_ENCODINGS.has(encoding);
|
|
39
|
+
return output.write(name, bytes ? Buffer.from(chunk, encoding) : chunk, done);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
process.once('exit', () => output.end());
|
|
43
|
+
claimed = output;
|
|
44
|
+
return output;
|
|
45
|
+
}
|
package/dist/collect/collect.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { realpathSync, statSync } from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { compileGlob, compileGlobList, discoverFiles, GLOB_SYNTAX, literalPrefix, matchesGlob } from '../internal/globs.js';
|
|
5
|
-
import { CollectionError } from '../internal/errors.js';
|
|
5
|
+
import { CollectionError, isE2EError } from '../internal/errors.js';
|
|
6
6
|
import { setupTestId, testId } from '../internal/ids.js';
|
|
7
7
|
import { explainModuleError } from '../config/diagnose.js';
|
|
8
8
|
import { importModule } from '../config/load.js';
|
|
@@ -338,6 +338,10 @@ export async function collect(config, positionals = []) {
|
|
|
338
338
|
uncollected,
|
|
339
339
|
};
|
|
340
340
|
}
|
|
341
|
+
/** A `CollectionError` from this module copy or the one a test file's imports loaded. */
|
|
342
|
+
function isCollectionError(cause) {
|
|
343
|
+
return isE2EError(cause) && cause.code === 'COLLECTION_ERROR';
|
|
344
|
+
}
|
|
341
345
|
/**
|
|
342
346
|
* Imports each discovered file once in the collection realm. A collection
|
|
343
347
|
* error in a selected file, or in any file of a run nothing narrowed, throws;
|
|
@@ -356,11 +360,11 @@ async function collectFiles(config, discovered, isSelected, narrowed, lines) {
|
|
|
356
360
|
}
|
|
357
361
|
catch (cause) {
|
|
358
362
|
if (skippable) {
|
|
359
|
-
uncollected.push({ file, reason: cause
|
|
363
|
+
uncollected.push({ file, reason: isCollectionError(cause) ? cause.message : explainModuleError(cause, absolutePath) });
|
|
360
364
|
continue;
|
|
361
365
|
}
|
|
362
366
|
// A registration error names the option but not the module it came from.
|
|
363
|
-
if (cause
|
|
367
|
+
if (isCollectionError(cause))
|
|
364
368
|
throw new CollectionError(`${file}: ${cause.message}`, { cause });
|
|
365
369
|
throw new CollectionError(`failed to collect ${file}: ${explainModuleError(cause, absolutePath)}`, { cause });
|
|
366
370
|
}
|
|
@@ -368,7 +372,7 @@ async function collectFiles(config, discovered, isSelected, narrowed, lines) {
|
|
|
368
372
|
files.push(collectFromRegistration(config.projectRoot, absolutePath, registration, isSelected(file), lines.get(file)));
|
|
369
373
|
}
|
|
370
374
|
catch (cause) {
|
|
371
|
-
if (!skippable || !(cause
|
|
375
|
+
if (!skippable || !isCollectionError(cause))
|
|
372
376
|
throw cause;
|
|
373
377
|
uncollected.push({ file, reason: cause.message });
|
|
374
378
|
}
|
package/dist/collect/registry.js
CHANGED
|
@@ -318,8 +318,9 @@ function validateDescribeOptions(options, parent) {
|
|
|
318
318
|
}
|
|
319
319
|
}
|
|
320
320
|
/**
|
|
321
|
-
* The active collector lives on globalThis because test
|
|
322
|
-
*
|
|
321
|
+
* The active collector lives on globalThis because a test module may import a
|
|
322
|
+
* copy of e2e other than the runner's and must still reach the runner's
|
|
323
|
+
* collector instance.
|
|
323
324
|
*/
|
|
324
325
|
const collectorSlot = realmSlot('e2e.activeCollector.v1');
|
|
325
326
|
/**
|
|
@@ -355,12 +356,10 @@ function requireCollector(api) {
|
|
|
355
356
|
const PACKAGE_ROOT = path.dirname(path.dirname(path.dirname(fileURLToPath(import.meta.url))));
|
|
356
357
|
/**
|
|
357
358
|
* The runner's own source roots, whose frames are never a test's location.
|
|
358
|
-
* `dist/` is where the published module runs; `src/` is where
|
|
359
|
-
*
|
|
359
|
+
* `dist/` is where the published module runs; `src/` is where it runs in this
|
|
360
|
+
* repository.
|
|
360
361
|
*/
|
|
361
362
|
const RUNNER_ROOTS = ['src', 'dist'].map((dir) => `${path.join(PACKAGE_ROOT, dir)}${path.sep}`);
|
|
362
|
-
/** `at name (file:line:column)` or `at file:line:column`, with or without a `file://` scheme. */
|
|
363
|
-
const STACK_FRAME = /\(?(?:file:\/\/)?([^()\s]+?):(\d+):(\d+)\)?$/;
|
|
364
363
|
const NODE_MODULES_SEGMENT = /[\\/]node_modules[\\/]/;
|
|
365
364
|
/** The path with symlinks resolved, or the path itself when it cannot be resolved. */
|
|
366
365
|
function realPath(file) {
|
|
@@ -371,6 +370,26 @@ function realPath(file) {
|
|
|
371
370
|
return file;
|
|
372
371
|
}
|
|
373
372
|
}
|
|
373
|
+
/** `at name (location:line:column)` or `at location:line:column`, either one after `async` for an awaiting caller. */
|
|
374
|
+
const STACK_FRAME = /^at (?:async )?(?:[^(]*? \()?(.+?):(\d+):(\d+)\)?$/;
|
|
375
|
+
/**
|
|
376
|
+
* Every frame of the current stack as a source location, the async frames
|
|
377
|
+
* of awaiting callers included (a test file that awaits a helper declaring
|
|
378
|
+
* tests is one), at the source positions Node.js's source maps give them.
|
|
379
|
+
* Read from the stack text: `util.getCallSites` leaves the async frames out,
|
|
380
|
+
* and with source maps on Node.js does not hand `Error.prepareStackTrace`
|
|
381
|
+
* the call sites at all.
|
|
382
|
+
*/
|
|
383
|
+
function stackLocations() {
|
|
384
|
+
return (new Error().stack ?? '').split('\n').slice(1).flatMap((line) => {
|
|
385
|
+
const match = STACK_FRAME.exec(line.trim());
|
|
386
|
+
if (match === null)
|
|
387
|
+
return [];
|
|
388
|
+
// An ES module names itself by URL, the loader's cache-busting query included; a mapped frame or CommonJS by path.
|
|
389
|
+
const file = match[1].startsWith('file:') ? fileURLToPath(match[1]) : match[1];
|
|
390
|
+
return [{ file, line: Number(match[2]), column: Number(match[3]) }];
|
|
391
|
+
});
|
|
392
|
+
}
|
|
374
393
|
/**
|
|
375
394
|
* Where a test was declared, read off the stack of its `test()` call. The
|
|
376
395
|
* innermost frame in the module being collected wins, so a test declared
|
|
@@ -382,19 +401,11 @@ function realPath(file) {
|
|
|
382
401
|
* package's frames are never a test's location.
|
|
383
402
|
*/
|
|
384
403
|
function captureSource(moduleFile) {
|
|
385
|
-
const stack = new Error().stack;
|
|
386
|
-
if (stack === undefined)
|
|
387
|
-
return undefined;
|
|
388
404
|
let outsideModule;
|
|
389
|
-
for (const
|
|
390
|
-
const
|
|
391
|
-
if (match === null)
|
|
392
|
-
continue;
|
|
393
|
-
// The loader imports every module with a cache-busting query, which is not part of the file.
|
|
394
|
-
const file = decodeURIComponent(match[1]).replace(/[?#].*$/, '');
|
|
405
|
+
for (const location of stackLocations()) {
|
|
406
|
+
const { file } = location;
|
|
395
407
|
if (RUNNER_ROOTS.some((root) => file.startsWith(root)) || file.startsWith('node:'))
|
|
396
408
|
continue;
|
|
397
|
-
const location = { file, line: Number(match[2]), column: Number(match[3]) };
|
|
398
409
|
if (moduleFile !== undefined && (file === moduleFile || realPath(file) === moduleFile)) {
|
|
399
410
|
return location;
|
|
400
411
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The files e2e's loader compiles, and the one policy both of its hooks
|
|
3
|
+
* follow: TypeScript and JSX are compiled wherever they are; the project's
|
|
4
|
+
* tsconfig.json governs a file the project owns (outside `node_modules`),
|
|
5
|
+
* while an installed package's source compiles with defaults, as its author
|
|
6
|
+
* shipped it without the project's settings.
|
|
7
|
+
*/
|
|
8
|
+
/** The module system a compiled file runs under. */
|
|
9
|
+
export type ModuleFormat = 'module' | 'commonjs';
|
|
10
|
+
/** What the loader knows about one compiled extension. */
|
|
11
|
+
export interface CompiledExtension {
|
|
12
|
+
/** The syntax oxc parses. */
|
|
13
|
+
readonly lang: 'ts' | 'tsx' | 'jsx';
|
|
14
|
+
readonly format: ModuleFormat;
|
|
15
|
+
}
|
|
16
|
+
/** Whether the `file:` URL `url` is the project's own, outside `node_modules`. */
|
|
17
|
+
export declare function isProjectFile(url: URL): boolean;
|
|
18
|
+
/** A file the loader compiles. */
|
|
19
|
+
export interface CompiledSource {
|
|
20
|
+
readonly file: string;
|
|
21
|
+
readonly kind: CompiledExtension;
|
|
22
|
+
/** Whether the project's tsconfig.json governs it; false for an installed package's source. */
|
|
23
|
+
readonly project: boolean;
|
|
24
|
+
}
|
|
25
|
+
/** The compiled file `url` names, or undefined for any other module. */
|
|
26
|
+
export declare function compiledSource(url: string | undefined): CompiledSource | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* The files TypeScript tries, in order, for an import that wrote
|
|
29
|
+
* `extension` (`x.ts`, `x.tsx`, `x.js`, `x.jsx` for `./x.js`). Empty for an
|
|
30
|
+
* extension TypeScript does not swap.
|
|
31
|
+
*/
|
|
32
|
+
export declare function writtenCandidates(extension: string): readonly string[];
|
|
33
|
+
/** Tried, in order, after an extensionless path and after a directory's `index`: what `./x.js` would find, then JSON. */
|
|
34
|
+
export declare const IMPLIED_EXTENSIONS: readonly string[];
|
|
35
|
+
/** The `globalThis` symbol key of the `require` compiled CommonJS uses where Node.js's skips resolve hooks. */
|
|
36
|
+
export declare const HOOKED_REQUIRE_KEY = "e2e.hooked-require.v1";
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The files e2e's loader compiles, and the one policy both of its hooks
|
|
3
|
+
* follow: TypeScript and JSX are compiled wherever they are; the project's
|
|
4
|
+
* tsconfig.json governs a file the project owns (outside `node_modules`),
|
|
5
|
+
* while an installed package's source compiles with defaults, as its author
|
|
6
|
+
* shipped it without the project's settings.
|
|
7
|
+
*/
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
/** Every extension the loader compiles. */
|
|
11
|
+
const COMPILED = {
|
|
12
|
+
'.ts': { lang: 'ts', format: 'module' },
|
|
13
|
+
'.tsx': { lang: 'tsx', format: 'module' },
|
|
14
|
+
'.jsx': { lang: 'jsx', format: 'module' },
|
|
15
|
+
'.mts': { lang: 'ts', format: 'module' },
|
|
16
|
+
'.cts': { lang: 'ts', format: 'commonjs' },
|
|
17
|
+
};
|
|
18
|
+
/** The files TypeScript tries, in order, for an import that writes each JavaScript extension. */
|
|
19
|
+
const WRITTEN = {
|
|
20
|
+
'.js': ['.ts', '.tsx', '.js', '.jsx'],
|
|
21
|
+
'.jsx': ['.tsx', '.ts', '.jsx', '.js'],
|
|
22
|
+
'.mjs': ['.mts', '.mjs'],
|
|
23
|
+
'.cjs': ['.cts', '.cjs'],
|
|
24
|
+
};
|
|
25
|
+
/** A file inside an installed package. */
|
|
26
|
+
const INSTALLED = /\/node_modules\//;
|
|
27
|
+
/** Whether the `file:` URL `url` is the project's own, outside `node_modules`. */
|
|
28
|
+
export function isProjectFile(url) {
|
|
29
|
+
return url.protocol === 'file:' && !INSTALLED.test(url.pathname);
|
|
30
|
+
}
|
|
31
|
+
/** The compiled file `url` names, or undefined for any other module. */
|
|
32
|
+
export function compiledSource(url) {
|
|
33
|
+
if (url?.startsWith('file:') !== true)
|
|
34
|
+
return undefined;
|
|
35
|
+
const parsed = new URL(url);
|
|
36
|
+
const kind = COMPILED[path.extname(parsed.pathname)];
|
|
37
|
+
return kind === undefined ? undefined : { file: fileURLToPath(parsed), kind, project: isProjectFile(parsed) };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The files TypeScript tries, in order, for an import that wrote
|
|
41
|
+
* `extension` (`x.ts`, `x.tsx`, `x.js`, `x.jsx` for `./x.js`). Empty for an
|
|
42
|
+
* extension TypeScript does not swap.
|
|
43
|
+
*/
|
|
44
|
+
export function writtenCandidates(extension) {
|
|
45
|
+
return WRITTEN[extension] ?? [];
|
|
46
|
+
}
|
|
47
|
+
/** Tried, in order, after an extensionless path and after a directory's `index`: what `./x.js` would find, then JSON. */
|
|
48
|
+
export const IMPLIED_EXTENSIONS = [...writtenCandidates('.js'), '.json'];
|
|
49
|
+
/** The `globalThis` symbol key of the `require` compiled CommonJS uses where Node.js's skips resolve hooks. */
|
|
50
|
+
export const HOOKED_REQUIRE_KEY = 'e2e.hooked-require.v1';
|
package/dist/config/diagnose.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { existsSync, readFileSync } from 'node:fs';
|
|
8
8
|
import path from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
9
10
|
import { withHint } from '../internal/errors.js';
|
|
10
11
|
import { addDevDependencyCommand, detectPackageManager } from '../internal/package-manager.js';
|
|
11
12
|
import { didYouMean, suggest } from '../internal/suggest.js';
|
|
@@ -44,18 +45,6 @@ const REMOVED_EXPORTS = {
|
|
|
44
45
|
toolAppliesTo: 'toolAppliesTo was removed from e2e/agent: the built-in agent offers a defined tool only on the platforms its annotations name',
|
|
45
46
|
},
|
|
46
47
|
};
|
|
47
|
-
/**
|
|
48
|
-
* The hint for TypeScript an installed package ships under a CommonJS scope:
|
|
49
|
-
* the file is there, but it loads through `require`, which the TypeScript
|
|
50
|
-
* loader does not hook, so Node reports it missing under the loader's query.
|
|
51
|
-
*/
|
|
52
|
-
function commonJsTypeScriptHint(file) {
|
|
53
|
-
let dir = path.dirname(file);
|
|
54
|
-
while (!existsSync(path.join(dir, 'package.json')) && path.dirname(dir) !== dir)
|
|
55
|
-
dir = path.dirname(dir);
|
|
56
|
-
const manifest = path.join(dir, 'package.json');
|
|
57
|
-
return `${file} exists: ${manifest} declares no "type": "module", so the file loads as CommonJS, which e2e's TypeScript loader does not transform; import the package's compiled JavaScript, or have the package declare "type": "module"`;
|
|
58
|
-
}
|
|
59
48
|
/**
|
|
60
49
|
* The message of a failed import of `importer`, extended with what to do
|
|
61
50
|
* about it when the cause is one the runner recognizes. Any other cause keeps
|
|
@@ -63,13 +52,23 @@ function commonJsTypeScriptHint(file) {
|
|
|
63
52
|
*/
|
|
64
53
|
export function explainModuleError(cause, importer) {
|
|
65
54
|
const message = cause instanceof Error ? cause.message : String(cause);
|
|
66
|
-
return withHint(message, moduleErrorHint(message, cause?.code, importer));
|
|
55
|
+
return withHint(`${message}${syntaxErrorLocation(cause, message)}`, moduleErrorHint(message, cause?.code, importer));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* ` (file:line)` for a SyntaxError Node.js raised linking or parsing a
|
|
59
|
+
* module, which names the line only in the first line of its stack, not in
|
|
60
|
+
* its message; empty for any other error.
|
|
61
|
+
*/
|
|
62
|
+
function syntaxErrorLocation(cause, message) {
|
|
63
|
+
if (!(cause instanceof SyntaxError))
|
|
64
|
+
return '';
|
|
65
|
+
const located = /^(.+?):(\d+)\n/.exec(cause.stack ?? '');
|
|
66
|
+
if (located === null || message.includes(located[1]))
|
|
67
|
+
return '';
|
|
68
|
+
const file = located[1].startsWith('file:') ? fileURLToPath(located[1]) : located[1];
|
|
69
|
+
return ` (${file}:${located[2]})`;
|
|
67
70
|
}
|
|
68
71
|
function moduleErrorHint(message, code, importer) {
|
|
69
|
-
if (code === 'MODULE_NOT_FOUND') {
|
|
70
|
-
const loaded = /Cannot find module '([^']+)\?namespace=[^']*'/.exec(message)?.[1];
|
|
71
|
-
return loaded !== undefined && existsSync(loaded) ? commonJsTypeScriptHint(loaded) : '';
|
|
72
|
-
}
|
|
73
72
|
if (code === 'ERR_MODULE_NOT_FOUND') {
|
|
74
73
|
const missing = /Cannot find (?:package|module) '([^']+)'/.exec(message)?.[1];
|
|
75
74
|
if (missing !== undefined && !missing.startsWith('.') && !path.isAbsolute(missing)) {
|
|
@@ -117,8 +116,11 @@ function missingExportHint(specifier, exportName) {
|
|
|
117
116
|
const removed = REMOVED_EXPORTS[specifier]?.[exportName];
|
|
118
117
|
if (removed !== undefined)
|
|
119
118
|
return removed;
|
|
120
|
-
if (specifier !== 'e2e')
|
|
121
|
-
|
|
119
|
+
if (specifier !== 'e2e') {
|
|
120
|
+
if (!/^[A-Z]/.test(exportName))
|
|
121
|
+
return '';
|
|
122
|
+
return `if ${exportName} is a type (an interface or a type alias), import it with import type { ${exportName} }: e2e compiles each file on its own, without type information, so an import of a type has to say so, as under TypeScript's isolatedModules; with emitDecoratorMetadata, the same holds for a type a decorated member's annotation names`;
|
|
123
|
+
}
|
|
122
124
|
const suggestion = suggest(exportName, RUNTIME_EXPORTS);
|
|
123
125
|
if (suggestion !== undefined)
|
|
124
126
|
return `did you mean "${suggestion}"?`;
|
|
@@ -1,56 +1,51 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* e2e's TypeScript loader: synchronous module customization hooks
|
|
3
|
+
* (`module.registerHooks`) that run config, test, and helper TypeScript and
|
|
4
|
+
* JSX on Node.js, for `import` and `require` alike. `compiled-files.ts` says
|
|
5
|
+
* which files it compiles and which tsconfig.json governs them.
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
7
|
+
* Resolve: an import written in a compiled file follows TypeScript's rules;
|
|
8
|
+
* every other import, JavaScript's included, resolves as Node.js resolves it.
|
|
9
|
+
* - A relative or absolute path: `./x.js` names the first of `x.ts`,
|
|
10
|
+
* `x.tsx`, `x.js`, and `x.jsx` that exists (`./x.jsx` tries `x.tsx`, `x.ts`,
|
|
11
|
+
* `x.jsx`, `x.js`; `./x.mjs` names `x.mts`, `./x.cjs` names `x.cts`), an
|
|
12
|
+
* extensionless `./x` names `x.ts`, `x.tsx`, `x.js`, `x.jsx`, or `x.json`,
|
|
13
|
+
* and a directory names its index the same way.
|
|
14
|
+
* - A bare specifier the project's tsconfig.json maps through `paths` or
|
|
15
|
+
* `baseUrl` names the mapped file, before any package of that name.
|
|
16
|
+
* - A `#` import or package export that names a missing `./x.js` (or `.jsx`,
|
|
17
|
+
* `.mjs`, `.cjs`) names the TypeScript file behind it, for an `import`.
|
|
13
18
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* `.cjs` helper requires, keeps its format, so Node strips its types; forced
|
|
21
|
-
* to ESM there, nothing would. An installed package under `node_modules`
|
|
22
|
-
* keeps the format its own manifest declares; `.cts` files stay CommonJS.
|
|
19
|
+
* Format: `.ts`, `.mts`, `.tsx`, and `.jsx` are ES modules wherever they are
|
|
20
|
+
* and whatever the nearest package.json says, so a Next.js app, or any
|
|
21
|
+
* package without `"type": "module"`, keeps its module type and still runs
|
|
22
|
+
* its TypeScript tests, whether it is imported or a CommonJS file requires it;
|
|
23
|
+
* `.cts` is CommonJS. A JSON file imported without `with { type: 'json' }`
|
|
24
|
+
* loads as a module whose default export is the parsed file.
|
|
23
25
|
*
|
|
24
|
-
*
|
|
25
|
-
* file it reaches by path or `#` import outside `node_modules`,
|
|
26
|
-
* (a config and the base config or targets file
|
|
27
|
-
* with it, while packages stay one shared
|
|
26
|
+
* Freshness: a module imported with the `e2e-graph` query parameter hands it
|
|
27
|
+
* on to every file it reaches by path or `#` import outside `node_modules`,
|
|
28
|
+
* so the project's own graph (a config and the base config or targets file
|
|
29
|
+
* it imports) evaluates afresh with it, while packages stay one shared
|
|
30
|
+
* instance. The graph's tsconfig.json and file lookups are its own too
|
|
31
|
+
* (`tsconfig.ts`).
|
|
32
|
+
*
|
|
33
|
+
* This module, and everything it imports from e2e, is loaded by Node.js's
|
|
34
|
+
* own type stripping when a worker runs from source (`register.ts`), so it
|
|
35
|
+
* holds to erasable TypeScript: no enums, namespaces, or parameter
|
|
36
|
+
* properties.
|
|
28
37
|
*/
|
|
29
|
-
import
|
|
30
|
-
type Resolution = ReturnType<ResolveHookSync>;
|
|
31
|
-
/** The tsx namespace e2e registers; tsx tags every URL it loads with it. */
|
|
32
|
-
export declare const TSX_NAMESPACE = "e2e";
|
|
38
|
+
import { type LoadHookSync, type ResolveHookSync } from 'node:module';
|
|
33
39
|
/**
|
|
34
40
|
* A URL that evaluates `absolutePath` afresh under `key`; with `graph`,
|
|
35
41
|
* every project file it imports by path or `#` import is fresh too.
|
|
36
42
|
*/
|
|
37
43
|
export declare function freshModuleURL(absolutePath: string, key: string, graph: boolean): string;
|
|
44
|
+
export declare const resolve: ResolveHookSync;
|
|
45
|
+
export declare const load: LoadHookSync;
|
|
38
46
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*/
|
|
43
|
-
export declare function asModule(specifier: string, context: Pick<ResolveHookContext, 'parentURL'>, resolution: Resolution): Resolution;
|
|
44
|
-
/**
|
|
45
|
-
* The resolution, joined to its importer's fresh graph: a project file
|
|
46
|
-
* reached by path or by a `#` import from a module that carries
|
|
47
|
-
* `GRAPH_PARAM` carries it too. A package reached by name keeps its shared
|
|
48
|
-
* instance, even a workspace package that resolves outside `node_modules`: a
|
|
49
|
-
* second instance of it would import a second instance of e2e itself.
|
|
47
|
+
* Registers the hooks once per process, and has stack traces follow the
|
|
48
|
+
* source maps the compiled files carry. Refuses a Node.js whose module hooks
|
|
49
|
+
* the loader cannot rely on (`node-version.ts`).
|
|
50
50
|
*/
|
|
51
|
-
export declare function
|
|
52
|
-
/** For `module.registerHooks`. */
|
|
53
|
-
export declare const resolveSync: ResolveHookSync;
|
|
54
|
-
/** For `module.register`, which reads this export from the hook module. */
|
|
55
|
-
export declare const resolve: ResolveHook;
|
|
56
|
-
export {};
|
|
51
|
+
export declare function registerLoader(): void;
|