@e2edev/e2e 0.6.1 → 0.7.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 +7 -8
- package/dist/agent/act-validation.d.ts +4 -1
- package/dist/agent/act-validation.d.ts.map +1 -1
- package/dist/agent/act-validation.js +7 -3
- package/dist/agent/act-validation.js.map +1 -1
- package/dist/agent/act.d.ts.map +1 -1
- package/dist/agent/act.js +14 -1
- package/dist/agent/act.js.map +1 -1
- package/dist/agent/actions.d.ts +14 -14
- package/dist/agent/actions.d.ts.map +1 -1
- package/dist/agent/actions.js +12 -2
- package/dist/agent/actions.js.map +1 -1
- package/dist/agent/executor.d.ts +7 -0
- package/dist/agent/executor.d.ts.map +1 -1
- package/dist/agent/executor.js.map +1 -1
- package/dist/agent/model/sdk.js +1 -1
- package/dist/agent/model/sdk.js.map +1 -1
- package/dist/agent/step-cache.d.ts +8 -8
- package/dist/agent/step-cache.d.ts.map +1 -1
- package/dist/agent/step-cache.js +14 -15
- package/dist/agent/step-cache.js.map +1 -1
- package/dist/agent/tool-loop.d.ts.map +1 -1
- package/dist/agent/tool-loop.js +1 -0
- package/dist/agent/tool-loop.js.map +1 -1
- package/dist/cache/anchors.d.ts.map +1 -1
- package/dist/cache/anchors.js +21 -1
- package/dist/cache/anchors.js.map +1 -1
- package/dist/cache/decide.d.ts +9 -0
- package/dist/cache/decide.d.ts.map +1 -1
- package/dist/cache/decide.js +35 -0
- package/dist/cache/decide.js.map +1 -1
- package/dist/cache/relocate.d.ts +11 -1
- package/dist/cache/relocate.d.ts.map +1 -1
- package/dist/cache/relocate.js +48 -17
- package/dist/cache/relocate.js.map +1 -1
- package/dist/cache/store.d.ts.map +1 -1
- package/dist/cache/store.js +3 -14
- package/dist/cache/store.js.map +1 -1
- package/dist/cache/trace.d.ts +12 -0
- package/dist/cache/trace.d.ts.map +1 -1
- package/dist/cache/trace.js +21 -0
- package/dist/cache/trace.js.map +1 -1
- package/dist/cli/docs-url.d.ts +1 -1
- package/dist/cli/docs-url.d.ts.map +1 -1
- package/dist/cli/docs-url.js +1 -1
- package/dist/cli/docs-url.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +58 -9
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init/scaffold.d.ts.map +1 -1
- package/dist/cli/init/scaffold.js +2 -0
- package/dist/cli/init/scaffold.js.map +1 -1
- package/dist/cli/telemetry.d.ts +12 -0
- package/dist/cli/telemetry.d.ts.map +1 -0
- package/dist/cli/telemetry.js +40 -0
- package/dist/cli/telemetry.js.map +1 -0
- package/dist/collect/select.d.ts +2 -0
- package/dist/collect/select.d.ts.map +1 -1
- package/dist/collect/select.js +15 -11
- package/dist/collect/select.js.map +1 -1
- package/dist/config/agent.js +24 -1
- package/dist/config/agent.js.map +1 -1
- package/dist/config/diagnose.d.ts.map +1 -1
- package/dist/config/diagnose.js +1 -0
- package/dist/config/diagnose.js.map +1 -1
- package/dist/config/resolve.d.ts.map +1 -1
- package/dist/config/resolve.js +2 -5
- package/dist/config/resolve.js.map +1 -1
- package/dist/engine/index.d.ts +5 -2
- package/dist/engine/index.d.ts.map +1 -1
- package/dist/engine/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/ai-trace.d.ts +1 -5
- package/dist/internal/ai-trace.d.ts.map +1 -1
- package/dist/internal/ai-trace.js +82 -11
- package/dist/internal/ai-trace.js.map +1 -1
- package/dist/internal/atomic-write.d.ts +17 -0
- package/dist/internal/atomic-write.d.ts.map +1 -0
- package/dist/internal/atomic-write.js +44 -0
- package/dist/internal/atomic-write.js.map +1 -0
- package/dist/internal/env.d.ts +6 -0
- package/dist/internal/env.d.ts.map +1 -0
- package/dist/internal/env.js +12 -0
- package/dist/internal/env.js.map +1 -0
- package/dist/report/build.d.ts.map +1 -1
- package/dist/report/build.js +9 -4
- package/dist/report/build.js.map +1 -1
- package/dist/report/list-model.d.ts +6 -0
- package/dist/report/list-model.d.ts.map +1 -1
- package/dist/report/list.d.ts +47 -3
- package/dist/report/list.d.ts.map +1 -1
- package/dist/report/list.js +117 -15
- package/dist/report/list.js.map +1 -1
- package/dist/report/running-tree.d.ts +2 -2
- package/dist/report/running-tree.d.ts.map +1 -1
- package/dist/report/running-tree.js +5 -1
- package/dist/report/running-tree.js.map +1 -1
- package/dist/report/write.d.ts +1 -1
- package/dist/report/write.d.ts.map +1 -1
- package/dist/report/write.js +3 -6
- package/dist/report/write.js.map +1 -1
- package/dist/run/events.d.ts +59 -8
- package/dist/run/events.d.ts.map +1 -1
- package/dist/run/events.js.map +1 -1
- package/dist/run/execute.d.ts +7 -0
- package/dist/run/execute.d.ts.map +1 -1
- package/dist/run/execute.js +6 -0
- package/dist/run/execute.js.map +1 -1
- package/dist/run/fixtures.d.ts +3 -0
- package/dist/run/fixtures.d.ts.map +1 -1
- package/dist/run/fixtures.js +5 -3
- package/dist/run/fixtures.js.map +1 -1
- package/dist/run/managed-process.d.ts +8 -2
- package/dist/run/managed-process.d.ts.map +1 -1
- package/dist/run/managed-process.js +12 -0
- package/dist/run/managed-process.js.map +1 -1
- package/dist/run/public.d.ts +1 -1
- package/dist/run/public.d.ts.map +1 -1
- package/dist/run/records.d.ts +2 -1
- package/dist/run/records.d.ts.map +1 -1
- package/dist/run/runner.d.ts +3 -2
- package/dist/run/runner.d.ts.map +1 -1
- package/dist/run/runner.js +126 -48
- package/dist/run/runner.js.map +1 -1
- package/dist/run/scheduler.d.ts +2 -0
- package/dist/run/scheduler.d.ts.map +1 -1
- package/dist/run/scheduler.js +4 -0
- package/dist/run/scheduler.js.map +1 -1
- package/dist/run/sessions.d.ts.map +1 -1
- package/dist/run/sessions.js +3 -4
- package/dist/run/sessions.js.map +1 -1
- package/dist/run/steps.d.ts +3 -1
- package/dist/run/steps.d.ts.map +1 -1
- package/dist/run/steps.js +2 -0
- package/dist/run/steps.js.map +1 -1
- package/dist/run/worker/protocol.d.ts +10 -1
- package/dist/run/worker/protocol.d.ts.map +1 -1
- package/dist/run/worker/protocol.js.map +1 -1
- package/dist/run/worker/session.d.ts.map +1 -1
- package/dist/run/worker/session.js +4 -0
- package/dist/run/worker/session.js.map +1 -1
- package/dist/run/worker-models.d.ts +27 -0
- package/dist/run/worker-models.d.ts.map +1 -0
- package/dist/run/worker-models.js +49 -0
- package/dist/run/worker-models.js.map +1 -0
- package/dist/telemetry/environment.d.ts +33 -0
- package/dist/telemetry/environment.d.ts.map +1 -0
- package/dist/telemetry/environment.js +96 -0
- package/dist/telemetry/environment.js.map +1 -0
- package/dist/telemetry/events.d.ts +25 -0
- package/dist/telemetry/events.d.ts.map +1 -0
- package/dist/telemetry/events.js +107 -0
- package/dist/telemetry/events.js.map +1 -0
- package/dist/telemetry/posthog.d.ts +31 -0
- package/dist/telemetry/posthog.d.ts.map +1 -0
- package/dist/telemetry/posthog.js +29 -0
- package/dist/telemetry/posthog.js.map +1 -0
- package/dist/telemetry/project.d.ts +18 -0
- package/dist/telemetry/project.d.ts.map +1 -0
- package/dist/telemetry/project.js +55 -0
- package/dist/telemetry/project.js.map +1 -0
- package/dist/telemetry/store.d.ts +53 -0
- package/dist/telemetry/store.d.ts.map +1 -0
- package/dist/telemetry/store.js +153 -0
- package/dist/telemetry/store.js.map +1 -0
- package/dist/telemetry/telemetry.d.ts +90 -0
- package/dist/telemetry/telemetry.d.ts.map +1 -0
- package/dist/telemetry/telemetry.js +188 -0
- package/dist/telemetry/telemetry.js.map +1 -0
- package/dist/types.d.ts +6 -9
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/skills/e2e/references/agent.md +13 -3
- package/skills/e2e/references/debugging.md +1 -1
- package/skills/e2e/references/running.md +3 -2
- package/skills/e2e/references/setup.md +5 -1
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The anonymous project id. Feature counts need to tell "one project run
|
|
3
|
+
* many times" from "many projects", and a per-machine id cannot: the same
|
|
4
|
+
* repository on two laptops would count twice, and one laptop with ten
|
|
5
|
+
* projects would count once.
|
|
6
|
+
*
|
|
7
|
+
* Inside a git repository the id is the SHA-256 of the repository's root
|
|
8
|
+
* commit, which every clone shares and no one can produce without the
|
|
9
|
+
* repository itself; a commit hash carries no name, path, or remote. A
|
|
10
|
+
* shallow clone has no root to offer: its boundary commits pose as roots and
|
|
11
|
+
* move with every fetch, so it is treated like a directory outside git.
|
|
12
|
+
* There, the project root path is hashed together with the machine's local
|
|
13
|
+
* salt, so the id is stable on that machine and unrecoverable anywhere else.
|
|
14
|
+
* In CI there is no salt, so a runner without a full history has no id: its
|
|
15
|
+
* working directory is not a project.
|
|
16
|
+
*/
|
|
17
|
+
export declare function anonymousProjectId(projectRoot: string, salt: string | undefined): Promise<string | undefined>;
|
|
18
|
+
//# sourceMappingURL=project.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../../src/telemetry/project.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAkCH,wBAAsB,kBAAkB,CACtC,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,GAAG,SAAS,GACvB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAK7B"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The anonymous project id. Feature counts need to tell "one project run
|
|
3
|
+
* many times" from "many projects", and a per-machine id cannot: the same
|
|
4
|
+
* repository on two laptops would count twice, and one laptop with ten
|
|
5
|
+
* projects would count once.
|
|
6
|
+
*
|
|
7
|
+
* Inside a git repository the id is the SHA-256 of the repository's root
|
|
8
|
+
* commit, which every clone shares and no one can produce without the
|
|
9
|
+
* repository itself; a commit hash carries no name, path, or remote. A
|
|
10
|
+
* shallow clone has no root to offer: its boundary commits pose as roots and
|
|
11
|
+
* move with every fetch, so it is treated like a directory outside git.
|
|
12
|
+
* There, the project root path is hashed together with the machine's local
|
|
13
|
+
* salt, so the id is stable on that machine and unrecoverable anywhere else.
|
|
14
|
+
* In CI there is no salt, so a runner without a full history has no id: its
|
|
15
|
+
* working directory is not a project.
|
|
16
|
+
*/
|
|
17
|
+
import { execFile } from 'node:child_process';
|
|
18
|
+
import { promisify } from 'node:util';
|
|
19
|
+
import { sha256Hex } from '../internal/ids.js';
|
|
20
|
+
const execFileAsync = promisify(execFile);
|
|
21
|
+
const COMMIT_HASH = /^[a-f0-9]{40,64}$/u;
|
|
22
|
+
/** One git command's stdout; undefined when git is missing, slow, or refuses. */
|
|
23
|
+
async function git(cwd, args) {
|
|
24
|
+
try {
|
|
25
|
+
const { stdout } = await execFileAsync('git', args, { cwd, timeout: 1_000, windowsHide: true, encoding: 'utf8' });
|
|
26
|
+
return stdout;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** The lexically first root commit reachable from HEAD; undefined outside git and in a shallow clone. */
|
|
33
|
+
async function rootCommit(cwd) {
|
|
34
|
+
const [shallow, roots] = await Promise.all([
|
|
35
|
+
git(cwd, ['rev-parse', '--is-shallow-repository']),
|
|
36
|
+
git(cwd, ['rev-list', '--max-parents=0', 'HEAD']),
|
|
37
|
+
]);
|
|
38
|
+
// Only an explicit `false` counts: a git too old for the flag echoes it back instead.
|
|
39
|
+
if (shallow?.trim() !== 'false' || roots === undefined)
|
|
40
|
+
return undefined;
|
|
41
|
+
return roots
|
|
42
|
+
.split('\n')
|
|
43
|
+
.map((line) => line.trim())
|
|
44
|
+
.filter((line) => COMMIT_HASH.test(line))
|
|
45
|
+
.toSorted()[0];
|
|
46
|
+
}
|
|
47
|
+
export async function anonymousProjectId(projectRoot, salt) {
|
|
48
|
+
const commit = await rootCommit(projectRoot);
|
|
49
|
+
if (commit !== undefined)
|
|
50
|
+
return sha256Hex(`git\n${commit}`);
|
|
51
|
+
if (salt === undefined)
|
|
52
|
+
return undefined;
|
|
53
|
+
return sha256Hex(`path\n${salt}\n${projectRoot}`);
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=project.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project.js","sourceRoot":"","sources":["../../src/telemetry/project.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAC1C,MAAM,WAAW,GAAG,oBAAoB,CAAC;AAEzC,iFAAiF;AACjF,KAAK,UAAU,GAAG,CAAC,GAAW,EAAE,IAAuB;IACrD,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;QAClH,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,yGAAyG;AACzG,KAAK,UAAU,UAAU,CAAC,GAAW;IACnC,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACzC,GAAG,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,yBAAyB,CAAC,CAAC;QAClD,GAAG,CAAC,GAAG,EAAE,CAAC,UAAU,EAAE,iBAAiB,EAAE,MAAM,CAAC,CAAC;KAClD,CAAC,CAAC;IACH,sFAAsF;IACtF,IAAI,OAAO,EAAE,IAAI,EAAE,KAAK,OAAO,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACzE,OAAO,KAAK;SACT,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;SAC1B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;SACxC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC;AACnB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,WAAmB,EACnB,IAAwB;IAExB,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,WAAW,CAAC,CAAC;IAC7C,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC,QAAQ,MAAM,EAAE,CAAC,CAAC;IAC7D,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACzC,OAAO,SAAS,CAAC,SAAS,IAAI,KAAK,WAAW,EAAE,CAAC,CAAC;AACpD,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The user-level telemetry preferences: `~/.config/e2e/telemetry.json`
|
|
3
|
+
* (`$XDG_CONFIG_HOME/e2e` when set, `%APPDATA%\e2e` on Windows). The file
|
|
4
|
+
* holds the opt-out, the random id every event from this machine is
|
|
5
|
+
* attributed to, the salt that keeps a hashed path unrecoverable, and which
|
|
6
|
+
* notice was shown when. Nothing in it names the person or the machine: the
|
|
7
|
+
* id and the salt are random bytes, generated here and never derived.
|
|
8
|
+
*
|
|
9
|
+
* The store fails closed. `open` completes the file in one write when the id
|
|
10
|
+
* or the salt is missing, and when that write cannot happen there is no
|
|
11
|
+
* store: telemetry treats the absence as an opt-out rather than sending
|
|
12
|
+
* events it could neither attribute nor de-duplicate. After `open`, only the
|
|
13
|
+
* user's own choice and the notice mark write, so an ordinary command never
|
|
14
|
+
* rewrites the file while another process may be saving a choice to it.
|
|
15
|
+
*/
|
|
16
|
+
/** The preferences file inside a config directory. */
|
|
17
|
+
export declare function preferencesPath(directory: string): string;
|
|
18
|
+
/** Where the preferences live for this platform and environment. */
|
|
19
|
+
export declare function telemetryConfigDir(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): string;
|
|
20
|
+
export declare class TelemetryStore {
|
|
21
|
+
/** The preferences file, for messages that tell the user where a choice was saved. */
|
|
22
|
+
readonly path: string;
|
|
23
|
+
private preferences;
|
|
24
|
+
private constructor();
|
|
25
|
+
/**
|
|
26
|
+
* Opens the store in `directory`. A complete file is only read, so a
|
|
27
|
+
* read-only preferences file keeps working. A missing file, a file that is
|
|
28
|
+
* not JSON, or one without an id or a salt is completed in one write that
|
|
29
|
+
* also creates the directory; a torn write must not turn into a permanent
|
|
30
|
+
* opt-in or opt-out either way. Returns undefined when that write fails,
|
|
31
|
+
* which the caller reads as off.
|
|
32
|
+
*/
|
|
33
|
+
static open(directory: string): TelemetryStore | undefined;
|
|
34
|
+
/** The user's choice; unset means participating. */
|
|
35
|
+
get enabled(): boolean;
|
|
36
|
+
/** The random per-machine id every event from this machine is attributed to. */
|
|
37
|
+
get anonymousId(): string;
|
|
38
|
+
/** The local salt that keeps a hashed project path unrecoverable; never leaves the machine. */
|
|
39
|
+
get pathSalt(): string;
|
|
40
|
+
/** Saves the choice; false when the file could not be written and the choice stayed in memory. */
|
|
41
|
+
saveEnabled(value: boolean): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Re-reads the file, so a choice another process saved since `open` is
|
|
44
|
+
* seen before anything is sent. An unreadable file changes nothing.
|
|
45
|
+
*/
|
|
46
|
+
reload(): void;
|
|
47
|
+
/** Whether this version of the notice has been shown. */
|
|
48
|
+
wasNotified(version: number): boolean;
|
|
49
|
+
/** Records that the notice was shown; a failed write only means it may show once more. */
|
|
50
|
+
markNotified(version: number, at: string): void;
|
|
51
|
+
private write;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/telemetry/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAwBH,sDAAsD;AACtD,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,oEAAoE;AACpE,wBAAgB,kBAAkB,CAChC,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,MAAM,CAOR;AA8BD,qBAAa,cAAc;IACzB,sFAAsF;IACtF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,WAAW,CAAa;IAEhC,OAAO,eAGN;IAED;;;;;;;OAOG;IACH,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAezD;IAED,oDAAoD;IACpD,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,gFAAgF;IAChF,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,+FAA+F;IAC/F,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,kGAAkG;IAClG,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAGnC;IAED;;;OAGG;IACH,MAAM,IAAI,IAAI,CAQb;IAED,yDAAyD;IACzD,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEpC;IAED,0FAA0F;IAC1F,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAI9C;IAED,OAAO,CAAC,KAAK;CAQd"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The user-level telemetry preferences: `~/.config/e2e/telemetry.json`
|
|
3
|
+
* (`$XDG_CONFIG_HOME/e2e` when set, `%APPDATA%\e2e` on Windows). The file
|
|
4
|
+
* holds the opt-out, the random id every event from this machine is
|
|
5
|
+
* attributed to, the salt that keeps a hashed path unrecoverable, and which
|
|
6
|
+
* notice was shown when. Nothing in it names the person or the machine: the
|
|
7
|
+
* id and the salt are random bytes, generated here and never derived.
|
|
8
|
+
*
|
|
9
|
+
* The store fails closed. `open` completes the file in one write when the id
|
|
10
|
+
* or the salt is missing, and when that write cannot happen there is no
|
|
11
|
+
* store: telemetry treats the absence as an opt-out rather than sending
|
|
12
|
+
* events it could neither attribute nor de-duplicate. After `open`, only the
|
|
13
|
+
* user's own choice and the notice mark write, so an ordinary command never
|
|
14
|
+
* rewrites the file while another process may be saving a choice to it.
|
|
15
|
+
*/
|
|
16
|
+
import { randomBytes } from 'node:crypto';
|
|
17
|
+
import { mkdirSync, readFileSync } from 'node:fs';
|
|
18
|
+
import os from 'node:os';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
import { writeFileAtomicSync } from '../internal/atomic-write.js';
|
|
21
|
+
import { envValue } from '../internal/env.js';
|
|
22
|
+
const FILE_NAME = 'telemetry.json';
|
|
23
|
+
const HEX = /^[a-f0-9]{16,128}$/u;
|
|
24
|
+
/** The preferences file inside a config directory. */
|
|
25
|
+
export function preferencesPath(directory) {
|
|
26
|
+
return path.join(directory, FILE_NAME);
|
|
27
|
+
}
|
|
28
|
+
/** Where the preferences live for this platform and environment. */
|
|
29
|
+
export function telemetryConfigDir(env = process.env, platform = process.platform) {
|
|
30
|
+
if (platform === 'win32') {
|
|
31
|
+
const appData = envValue(env, 'APPDATA') ?? path.join(os.homedir(), 'AppData', 'Roaming');
|
|
32
|
+
return path.join(appData, 'e2e');
|
|
33
|
+
}
|
|
34
|
+
const configHome = envValue(env, 'XDG_CONFIG_HOME') ?? path.join(os.homedir(), '.config');
|
|
35
|
+
return path.join(configHome, 'e2e');
|
|
36
|
+
}
|
|
37
|
+
/** Keeps only the fields this version knows, each with the type it expects. */
|
|
38
|
+
function sanitize(value) {
|
|
39
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
40
|
+
return {};
|
|
41
|
+
const raw = value;
|
|
42
|
+
const preferences = {};
|
|
43
|
+
if (typeof raw['enabled'] === 'boolean')
|
|
44
|
+
preferences.enabled = raw['enabled'];
|
|
45
|
+
if (typeof raw['anonymousId'] === 'string' && HEX.test(raw['anonymousId'])) {
|
|
46
|
+
preferences.anonymousId = raw['anonymousId'];
|
|
47
|
+
}
|
|
48
|
+
if (typeof raw['salt'] === 'string' && HEX.test(raw['salt']))
|
|
49
|
+
preferences.salt = raw['salt'];
|
|
50
|
+
if (typeof raw['notifiedAt'] === 'string')
|
|
51
|
+
preferences.notifiedAt = raw['notifiedAt'];
|
|
52
|
+
if (typeof raw['noticeVersion'] === 'number')
|
|
53
|
+
preferences.noticeVersion = raw['noticeVersion'];
|
|
54
|
+
return preferences;
|
|
55
|
+
}
|
|
56
|
+
/** The file's preferences, or undefined when it is missing or not JSON. */
|
|
57
|
+
function readPreferences(filePath) {
|
|
58
|
+
try {
|
|
59
|
+
return sanitize(JSON.parse(readFileSync(filePath, 'utf8')));
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function randomHex() {
|
|
66
|
+
return randomBytes(16).toString('hex');
|
|
67
|
+
}
|
|
68
|
+
export class TelemetryStore {
|
|
69
|
+
/** The preferences file, for messages that tell the user where a choice was saved. */
|
|
70
|
+
path;
|
|
71
|
+
preferences;
|
|
72
|
+
constructor(filePath, preferences) {
|
|
73
|
+
this.path = filePath;
|
|
74
|
+
this.preferences = preferences;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Opens the store in `directory`. A complete file is only read, so a
|
|
78
|
+
* read-only preferences file keeps working. A missing file, a file that is
|
|
79
|
+
* not JSON, or one without an id or a salt is completed in one write that
|
|
80
|
+
* also creates the directory; a torn write must not turn into a permanent
|
|
81
|
+
* opt-in or opt-out either way. Returns undefined when that write fails,
|
|
82
|
+
* which the caller reads as off.
|
|
83
|
+
*/
|
|
84
|
+
static open(directory) {
|
|
85
|
+
const filePath = preferencesPath(directory);
|
|
86
|
+
const read = readPreferences(filePath);
|
|
87
|
+
const store = new TelemetryStore(filePath, {
|
|
88
|
+
...read,
|
|
89
|
+
anonymousId: read?.anonymousId ?? randomHex(),
|
|
90
|
+
salt: read?.salt ?? randomHex(),
|
|
91
|
+
});
|
|
92
|
+
if (read !== undefined && read.anonymousId !== undefined && read.salt !== undefined)
|
|
93
|
+
return store;
|
|
94
|
+
try {
|
|
95
|
+
mkdirSync(directory, { recursive: true });
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
return store.write() ? store : undefined;
|
|
101
|
+
}
|
|
102
|
+
/** The user's choice; unset means participating. */
|
|
103
|
+
get enabled() {
|
|
104
|
+
return this.preferences.enabled !== false;
|
|
105
|
+
}
|
|
106
|
+
/** The random per-machine id every event from this machine is attributed to. */
|
|
107
|
+
get anonymousId() {
|
|
108
|
+
return this.preferences.anonymousId;
|
|
109
|
+
}
|
|
110
|
+
/** The local salt that keeps a hashed project path unrecoverable; never leaves the machine. */
|
|
111
|
+
get pathSalt() {
|
|
112
|
+
return this.preferences.salt;
|
|
113
|
+
}
|
|
114
|
+
/** Saves the choice; false when the file could not be written and the choice stayed in memory. */
|
|
115
|
+
saveEnabled(value) {
|
|
116
|
+
this.preferences.enabled = value;
|
|
117
|
+
return this.write();
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Re-reads the file, so a choice another process saved since `open` is
|
|
121
|
+
* seen before anything is sent. An unreadable file changes nothing.
|
|
122
|
+
*/
|
|
123
|
+
reload() {
|
|
124
|
+
const read = readPreferences(this.path);
|
|
125
|
+
if (read === undefined)
|
|
126
|
+
return;
|
|
127
|
+
this.preferences = {
|
|
128
|
+
...read,
|
|
129
|
+
anonymousId: read.anonymousId ?? this.preferences.anonymousId,
|
|
130
|
+
salt: read.salt ?? this.preferences.salt,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/** Whether this version of the notice has been shown. */
|
|
134
|
+
wasNotified(version) {
|
|
135
|
+
return this.preferences.notifiedAt !== undefined && (this.preferences.noticeVersion ?? 0) >= version;
|
|
136
|
+
}
|
|
137
|
+
/** Records that the notice was shown; a failed write only means it may show once more. */
|
|
138
|
+
markNotified(version, at) {
|
|
139
|
+
this.preferences.notifiedAt = at;
|
|
140
|
+
this.preferences.noticeVersion = version;
|
|
141
|
+
this.write();
|
|
142
|
+
}
|
|
143
|
+
write() {
|
|
144
|
+
try {
|
|
145
|
+
writeFileAtomicSync(this.path, `${JSON.stringify(this.preferences, null, 2)}\n`, { mode: 0o600 });
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/telemetry/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anonymous usage telemetry for the CLI.
|
|
3
|
+
*
|
|
4
|
+
* One instance lives for one CLI invocation. Commands hand it events; at the
|
|
5
|
+
* end of the invocation `flush` sends them in a single bounded request, so a
|
|
6
|
+
* command never waits on telemetry for more than the flush budget and never
|
|
7
|
+
* fails because of it. Telemetry is a CLI concern only: a host embedding the
|
|
8
|
+
* runner through `@e2edev/e2e/run` never constructs this class, and its runs
|
|
9
|
+
* send nothing.
|
|
10
|
+
*
|
|
11
|
+
* Off means off at every step. `E2E_TELEMETRY_DISABLED`, `DO_NOT_TRACK`, an
|
|
12
|
+
* `e2e telemetry disable`, or a preferences directory that cannot be written
|
|
13
|
+
* each stop events from being queued, and `flush` reads the preferences file
|
|
14
|
+
* again before sending, so a choice saved from another terminal while the
|
|
15
|
+
* command ran wins too. `E2E_TELEMETRY_DEBUG` prints every event to stderr
|
|
16
|
+
* instead of sending it, so anyone can read exactly what would have left the
|
|
17
|
+
* machine.
|
|
18
|
+
*
|
|
19
|
+
* Identity is deliberately weak: a random per-machine id from the preferences
|
|
20
|
+
* file, a random per-invocation session id, and a hashed project id. In CI
|
|
21
|
+
* there is no preferences file and every run is attributed to the CI vendor,
|
|
22
|
+
* so a fleet of ephemeral runners does not masquerade as a crowd of users.
|
|
23
|
+
*/
|
|
24
|
+
import type { TelemetryEvent } from './events.ts';
|
|
25
|
+
import { anonymousProjectId } from './project.ts';
|
|
26
|
+
/** Bumped when what is collected changes enough that the notice must show again. */
|
|
27
|
+
export declare const NOTICE_VERSION = 1;
|
|
28
|
+
export type TelemetryDisabledBy = 'E2E_TELEMETRY_DISABLED' | 'DO_NOT_TRACK' | 'preference' | 'store';
|
|
29
|
+
export interface TelemetryOptions {
|
|
30
|
+
/** The e2e version, sent with every event. */
|
|
31
|
+
readonly version: string;
|
|
32
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
33
|
+
/** The project directory the command runs in; hashed into the project id, never sent. */
|
|
34
|
+
readonly cwd?: string;
|
|
35
|
+
/** Where the preferences file lives; the platform default when absent. */
|
|
36
|
+
readonly configDir?: string;
|
|
37
|
+
readonly fetch?: typeof fetch;
|
|
38
|
+
/** Where the notice and the debug output go; stderr when absent. */
|
|
39
|
+
readonly write?: (text: string) => void;
|
|
40
|
+
/** Resolves the anonymous project id; git and the salted path when absent. */
|
|
41
|
+
readonly projectId?: typeof anonymousProjectId;
|
|
42
|
+
}
|
|
43
|
+
export declare class Telemetry {
|
|
44
|
+
/** Random per-invocation id, so one invocation's events can be grouped. */
|
|
45
|
+
readonly sessionId: string;
|
|
46
|
+
private readonly version;
|
|
47
|
+
private readonly env;
|
|
48
|
+
private readonly cwd;
|
|
49
|
+
private readonly configDir;
|
|
50
|
+
private readonly fetchImpl;
|
|
51
|
+
private readonly write;
|
|
52
|
+
private readonly projectId;
|
|
53
|
+
private readonly ci;
|
|
54
|
+
/** The store once `store()` opened it, whether or not the file could exist. */
|
|
55
|
+
private opened;
|
|
56
|
+
private readonly queue;
|
|
57
|
+
private project;
|
|
58
|
+
constructor(options: TelemetryOptions);
|
|
59
|
+
/** Opened on first use, so `--version` and `--help` never touch the disk; undefined when the file cannot exist. */
|
|
60
|
+
private store;
|
|
61
|
+
/** Why telemetry is off, or undefined when it is on. */
|
|
62
|
+
get disabledBy(): TelemetryDisabledBy | undefined;
|
|
63
|
+
get enabled(): boolean;
|
|
64
|
+
/** `E2E_TELEMETRY_DEBUG`: print every event, send nothing. */
|
|
65
|
+
get debug(): boolean;
|
|
66
|
+
/** The preferences file, whether or not it exists yet. */
|
|
67
|
+
get preferencesPath(): string;
|
|
68
|
+
/**
|
|
69
|
+
* Saves the user's choice. Returns the file it was saved to, or undefined
|
|
70
|
+
* when the file could not be written, in which case telemetry is off anyway
|
|
71
|
+
* because there is no store to attribute events with.
|
|
72
|
+
*/
|
|
73
|
+
setEnabled(value: boolean): string | undefined;
|
|
74
|
+
/**
|
|
75
|
+
* Prints the notice the first time this machine runs a version of it, and
|
|
76
|
+
* records that it did. Nothing in CI, where the output is a log nobody is
|
|
77
|
+
* reading and there is no file to remember it in; nothing when off.
|
|
78
|
+
*/
|
|
79
|
+
notice(): void;
|
|
80
|
+
/** Queues one event for the flush; a no-op when off. */
|
|
81
|
+
record(event: TelemetryEvent): void;
|
|
82
|
+
/**
|
|
83
|
+
* Sends everything queued in one request, or prints it under debug. One
|
|
84
|
+
* deadline covers the project lookup still running and the request; a
|
|
85
|
+
* lost batch is the accepted cost of a command that never waits on
|
|
86
|
+
* telemetry.
|
|
87
|
+
*/
|
|
88
|
+
flush(maxWaitMs?: number): Promise<void>;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=telemetry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../../src/telemetry/telemetry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AASH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAGlD,oFAAoF;AACpF,eAAO,MAAM,cAAc,IAAI,CAAC;AAIhC,MAAM,MAAM,mBAAmB,GAAG,wBAAwB,GAAG,cAAc,GAAG,YAAY,GAAG,OAAO,CAAC;AAErG,MAAM,WAAW,gBAAgB;IAC/B,8CAA8C;IAC9C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACjC,yFAAyF;IACzF,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,0EAA0E;IAC1E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IAC9B,oEAAoE;IACpE,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,8EAA8E;IAC9E,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,kBAAkB,CAAC;CAChD;AAmBD,qBAAa,SAAS;IACpB,2EAA2E;IAC3E,QAAQ,CAAC,SAAS,SAAmC;IAErD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAoB;IACxC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAyB;IAC/C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA4B;IACtD,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAU;IAC7B,+EAA+E;IAC/E,OAAO,CAAC,MAAM,CAA6D;IAC3E,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAwB;IAC9C,OAAO,CAAC,OAAO,CAA0C;IAEzD,YAAY,OAAO,EAAE,gBAAgB,EASpC;IAED,mHAAmH;IACnH,OAAO,CAAC,KAAK;IAKb,wDAAwD;IACxD,IAAI,UAAU,IAAI,mBAAmB,GAAG,SAAS,CAOhD;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,8DAA8D;IAC9D,IAAI,KAAK,IAAI,OAAO,CAEnB;IAED,0DAA0D;IAC1D,IAAI,eAAe,IAAI,MAAM,CAE5B;IAED;;;;OAIG;IACH,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAI7C;IAED;;;;OAIG;IACH,MAAM,IAAI,IAAI,CAMb;IAED,wDAAwD;IACxD,MAAM,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,CAQlC;IAED;;;;;OAKG;IACG,KAAK,CAAC,SAAS,GAAE,MAAyB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkC/D;CACF"}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anonymous usage telemetry for the CLI.
|
|
3
|
+
*
|
|
4
|
+
* One instance lives for one CLI invocation. Commands hand it events; at the
|
|
5
|
+
* end of the invocation `flush` sends them in a single bounded request, so a
|
|
6
|
+
* command never waits on telemetry for more than the flush budget and never
|
|
7
|
+
* fails because of it. Telemetry is a CLI concern only: a host embedding the
|
|
8
|
+
* runner through `@e2edev/e2e/run` never constructs this class, and its runs
|
|
9
|
+
* send nothing.
|
|
10
|
+
*
|
|
11
|
+
* Off means off at every step. `E2E_TELEMETRY_DISABLED`, `DO_NOT_TRACK`, an
|
|
12
|
+
* `e2e telemetry disable`, or a preferences directory that cannot be written
|
|
13
|
+
* each stop events from being queued, and `flush` reads the preferences file
|
|
14
|
+
* again before sending, so a choice saved from another terminal while the
|
|
15
|
+
* command ran wins too. `E2E_TELEMETRY_DEBUG` prints every event to stderr
|
|
16
|
+
* instead of sending it, so anyone can read exactly what would have left the
|
|
17
|
+
* machine.
|
|
18
|
+
*
|
|
19
|
+
* Identity is deliberately weak: a random per-machine id from the preferences
|
|
20
|
+
* file, a random per-invocation session id, and a hashed project id. In CI
|
|
21
|
+
* there is no preferences file and every run is attributed to the CI vendor,
|
|
22
|
+
* so a fleet of ephemeral runners does not masquerade as a crowd of users.
|
|
23
|
+
*/
|
|
24
|
+
import { randomBytes } from 'node:crypto';
|
|
25
|
+
import picocolors from 'picocolors';
|
|
26
|
+
import { DOCS_URL } from '../cli/docs-url.js';
|
|
27
|
+
import { isCiMode } from '../config/resolve.js';
|
|
28
|
+
import { envFlag } from '../internal/env.js';
|
|
29
|
+
import { timestamp } from '../internal/ids.js';
|
|
30
|
+
import { ciName, collectEnvironment } from './environment.js';
|
|
31
|
+
import { postBatch } from './posthog.js';
|
|
32
|
+
import { anonymousProjectId } from './project.js';
|
|
33
|
+
import { preferencesPath, TelemetryStore, telemetryConfigDir } from './store.js';
|
|
34
|
+
/** Bumped when what is collected changes enough that the notice must show again. */
|
|
35
|
+
export const NOTICE_VERSION = 1;
|
|
36
|
+
/** The longest a flush may hold the process; the project lookup and the request share it. */
|
|
37
|
+
const DEFAULT_FLUSH_MS = 2_000;
|
|
38
|
+
/** The one-time notice: what is collected, and the two ways out. */
|
|
39
|
+
function noticeText() {
|
|
40
|
+
return [
|
|
41
|
+
`${picocolors.bold('e2e collects anonymous usage telemetry')} to improve the framework: the command, the versions, the OS, and run counts. Never test names, app data, or credentials.`,
|
|
42
|
+
`Opt out with ${picocolors.cyan('e2e telemetry disable')} or ${picocolors.cyan('E2E_TELEMETRY_DISABLED=1')}. What is sent: ${picocolors.underline(`${DOCS_URL}/telemetry`)}`,
|
|
43
|
+
'',
|
|
44
|
+
'',
|
|
45
|
+
].join('\n');
|
|
46
|
+
}
|
|
47
|
+
/** Resolves to undefined when the signal aborts; whatever loses a race against it is abandoned. */
|
|
48
|
+
function aborted(signal) {
|
|
49
|
+
return new Promise((resolve) => {
|
|
50
|
+
signal.addEventListener('abort', () => resolve(undefined), { once: true });
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
export class Telemetry {
|
|
54
|
+
/** Random per-invocation id, so one invocation's events can be grouped. */
|
|
55
|
+
sessionId = randomBytes(16).toString('hex');
|
|
56
|
+
version;
|
|
57
|
+
env;
|
|
58
|
+
cwd;
|
|
59
|
+
configDir;
|
|
60
|
+
fetchImpl;
|
|
61
|
+
write;
|
|
62
|
+
projectId;
|
|
63
|
+
ci;
|
|
64
|
+
/** The store once `store()` opened it, whether or not the file could exist. */
|
|
65
|
+
opened;
|
|
66
|
+
queue = [];
|
|
67
|
+
project;
|
|
68
|
+
constructor(options) {
|
|
69
|
+
this.version = options.version;
|
|
70
|
+
this.env = options.env ?? process.env;
|
|
71
|
+
this.cwd = options.cwd ?? process.cwd();
|
|
72
|
+
this.configDir = options.configDir ?? telemetryConfigDir(this.env);
|
|
73
|
+
this.fetchImpl = options.fetch ?? fetch;
|
|
74
|
+
this.write = options.write ?? ((text) => void process.stderr.write(text));
|
|
75
|
+
this.projectId = options.projectId ?? anonymousProjectId;
|
|
76
|
+
this.ci = isCiMode(this.env);
|
|
77
|
+
}
|
|
78
|
+
/** Opened on first use, so `--version` and `--help` never touch the disk; undefined when the file cannot exist. */
|
|
79
|
+
store() {
|
|
80
|
+
this.opened ??= { store: TelemetryStore.open(this.configDir) };
|
|
81
|
+
return this.opened.store;
|
|
82
|
+
}
|
|
83
|
+
/** Why telemetry is off, or undefined when it is on. */
|
|
84
|
+
get disabledBy() {
|
|
85
|
+
if (envFlag(this.env, 'E2E_TELEMETRY_DISABLED'))
|
|
86
|
+
return 'E2E_TELEMETRY_DISABLED';
|
|
87
|
+
if (envFlag(this.env, 'DO_NOT_TRACK'))
|
|
88
|
+
return 'DO_NOT_TRACK';
|
|
89
|
+
if (this.ci)
|
|
90
|
+
return undefined;
|
|
91
|
+
const store = this.store();
|
|
92
|
+
if (store === undefined)
|
|
93
|
+
return 'store';
|
|
94
|
+
return store.enabled ? undefined : 'preference';
|
|
95
|
+
}
|
|
96
|
+
get enabled() {
|
|
97
|
+
return this.disabledBy === undefined;
|
|
98
|
+
}
|
|
99
|
+
/** `E2E_TELEMETRY_DEBUG`: print every event, send nothing. */
|
|
100
|
+
get debug() {
|
|
101
|
+
return envFlag(this.env, 'E2E_TELEMETRY_DEBUG');
|
|
102
|
+
}
|
|
103
|
+
/** The preferences file, whether or not it exists yet. */
|
|
104
|
+
get preferencesPath() {
|
|
105
|
+
return preferencesPath(this.configDir);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Saves the user's choice. Returns the file it was saved to, or undefined
|
|
109
|
+
* when the file could not be written, in which case telemetry is off anyway
|
|
110
|
+
* because there is no store to attribute events with.
|
|
111
|
+
*/
|
|
112
|
+
setEnabled(value) {
|
|
113
|
+
const store = this.store();
|
|
114
|
+
if (store === undefined)
|
|
115
|
+
return undefined;
|
|
116
|
+
return store.saveEnabled(value) ? store.path : undefined;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Prints the notice the first time this machine runs a version of it, and
|
|
120
|
+
* records that it did. Nothing in CI, where the output is a log nobody is
|
|
121
|
+
* reading and there is no file to remember it in; nothing when off.
|
|
122
|
+
*/
|
|
123
|
+
notice() {
|
|
124
|
+
if (this.ci || !this.enabled)
|
|
125
|
+
return;
|
|
126
|
+
const store = this.store();
|
|
127
|
+
if (store === undefined || store.wasNotified(NOTICE_VERSION))
|
|
128
|
+
return;
|
|
129
|
+
store.markNotified(NOTICE_VERSION, timestamp());
|
|
130
|
+
this.write(noticeText());
|
|
131
|
+
}
|
|
132
|
+
/** Queues one event for the flush; a no-op when off. */
|
|
133
|
+
record(event) {
|
|
134
|
+
if (!this.enabled)
|
|
135
|
+
return;
|
|
136
|
+
this.queue.push(event);
|
|
137
|
+
// Git is asked for the project id now, while the command runs, so the
|
|
138
|
+
// flush at the end waits on the network alone. In CI there is no store,
|
|
139
|
+
// so a path outside git has nothing to salt it and yields no id.
|
|
140
|
+
const store = this.ci ? undefined : this.store();
|
|
141
|
+
this.project ??= this.projectId(this.cwd, store?.pathSalt);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Sends everything queued in one request, or prints it under debug. One
|
|
145
|
+
* deadline covers the project lookup still running and the request; a
|
|
146
|
+
* lost batch is the accepted cost of a command that never waits on
|
|
147
|
+
* telemetry.
|
|
148
|
+
*/
|
|
149
|
+
async flush(maxWaitMs = DEFAULT_FLUSH_MS) {
|
|
150
|
+
const events = this.queue.splice(0);
|
|
151
|
+
if (events.length === 0)
|
|
152
|
+
return;
|
|
153
|
+
// A choice saved from another process while this command ran wins over the snapshot taken at its start.
|
|
154
|
+
const store = this.ci ? undefined : this.store();
|
|
155
|
+
store?.reload();
|
|
156
|
+
if (!this.enabled)
|
|
157
|
+
return;
|
|
158
|
+
const deadline = AbortSignal.timeout(maxWaitMs);
|
|
159
|
+
const project = await Promise.race([this.project, aborted(deadline)]);
|
|
160
|
+
// Outside CI, `enabled` has just vouched for the store; no store means a CI run, attributed to the vendor.
|
|
161
|
+
const distinctId = store === undefined ? `ci:${ciName(this.env) ?? 'unknown'}` : store.anonymousId;
|
|
162
|
+
const environment = collectEnvironment({ env: this.env, cwd: this.cwd, version: this.version });
|
|
163
|
+
// The debug output and the request body are the same objects, so what
|
|
164
|
+
// `E2E_TELEMETRY_DEBUG` shows is what would have been sent, key for key.
|
|
165
|
+
const items = events.map((event) => ({
|
|
166
|
+
event: event.name,
|
|
167
|
+
timestamp: event.at ?? timestamp(),
|
|
168
|
+
properties: {
|
|
169
|
+
...environment,
|
|
170
|
+
...event.properties,
|
|
171
|
+
distinct_id: distinctId,
|
|
172
|
+
project_id: project ?? null,
|
|
173
|
+
session_id: this.sessionId,
|
|
174
|
+
$lib: 'e2e',
|
|
175
|
+
$lib_version: this.version,
|
|
176
|
+
// Anonymous events: PostHog keeps no person profile for the id.
|
|
177
|
+
$process_person_profile: false,
|
|
178
|
+
},
|
|
179
|
+
}));
|
|
180
|
+
if (this.debug) {
|
|
181
|
+
for (const item of items)
|
|
182
|
+
this.write(`[telemetry] ${JSON.stringify(item)}\n`);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
await postBatch(items, { signal: deadline, fetch: this.fetchImpl });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=telemetry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../../src/telemetry/telemetry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,UAAU,MAAM,YAAY,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAE9D,OAAO,EAAE,SAAS,EAAqB,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEjF,oFAAoF;AACpF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC;AAChC,6FAA6F;AAC7F,MAAM,gBAAgB,GAAG,KAAK,CAAC;AAmB/B,oEAAoE;AACpE,SAAS,UAAU;IACjB,OAAO;QACL,GAAG,UAAU,CAAC,IAAI,CAAC,wCAAwC,CAAC,2HAA2H;QACvL,gBAAgB,UAAU,CAAC,IAAI,CAAC,uBAAuB,CAAC,OAAO,UAAU,CAAC,IAAI,CAAC,0BAA0B,CAAC,mBAAmB,UAAU,CAAC,SAAS,CAAC,GAAG,QAAQ,YAAY,CAAC,EAAE;QAC5K,EAAE;QACF,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,mGAAmG;AACnG,SAAS,OAAO,CAAC,MAAmB;IAClC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7E,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,OAAO,SAAS;IACpB,2EAA2E;IAClE,SAAS,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAEpC,OAAO,CAAS;IAChB,GAAG,CAAoB;IACvB,GAAG,CAAS;IACZ,SAAS,CAAS;IAClB,SAAS,CAAe;IACxB,KAAK,CAAyB;IAC9B,SAAS,CAA4B;IACrC,EAAE,CAAU;IAC7B,+EAA+E;IACvE,MAAM,CAA6D;IAC1D,KAAK,GAAqB,EAAE,CAAC;IACtC,OAAO,CAA0C;IAEzD,YAAY,OAAyB;QACnC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;QAC/B,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;QACtC,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;QACxC,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACnE,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,KAAK,IAAI,KAAK,CAAC;QACxC,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QAC1E,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,kBAAkB,CAAC;QACzD,IAAI,CAAC,EAAE,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/B,CAAC;IAED,mHAAmH;IAC3G,KAAK;QACX,IAAI,CAAC,MAAM,KAAK,EAAE,KAAK,EAAE,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;QAC/D,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;IAC3B,CAAC;IAED,wDAAwD;IACxD,IAAI,UAAU;QACZ,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,wBAAwB,CAAC;YAAE,OAAO,wBAAwB,CAAC;QACjF,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC;YAAE,OAAO,cAAc,CAAC;QAC7D,IAAI,IAAI,CAAC,EAAE;YAAE,OAAO,SAAS,CAAC;QAC9B,MAAM,KAAK,GAA+B,IAAI,CAAC,KAAK,EAAE,CAAC;QACvD,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,OAAO,CAAC;QACxC,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC;IAClD,CAAC;IAED,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,UAAU,KAAK,SAAS,CAAC;IACvC,CAAC;IAED,8DAA8D;IAC9D,IAAI,KAAK;QACP,OAAO,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,qBAAqB,CAAC,CAAC;IAClD,CAAC;IAED,0DAA0D;IAC1D,IAAI,eAAe;QACjB,OAAO,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACzC,CAAC;IAED;;;;OAIG;IACH,UAAU,CAAC,KAAc;QACvB,MAAM,KAAK,GAA+B,IAAI,CAAC,KAAK,EAAE,CAAC;QACvD,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;QAC1C,OAAO,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IAC3D,CAAC;IAED;;;;OAIG;IACH,MAAM;QACJ,IAAI,IAAI,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QACrC,MAAM,KAAK,GAA+B,IAAI,CAAC,KAAK,EAAE,CAAC;QACvD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,WAAW,CAAC,cAAc,CAAC;YAAE,OAAO;QACrE,KAAK,CAAC,YAAY,CAAC,cAAc,EAAE,SAAS,EAAE,CAAC,CAAC;QAChD,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC;IAC3B,CAAC;IAED,wDAAwD;IACxD,MAAM,CAAC,KAAqB;QAC1B,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1B,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACvB,sEAAsE;QACtE,wEAAwE;QACxE,iEAAiE;QACjE,MAAM,KAAK,GAA+B,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;QAC7E,IAAI,CAAC,OAAO,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;IAC7D,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,KAAK,CAAC,SAAS,GAAW,gBAAgB;QAC9C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACpC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAChC,wGAAwG;QACxG,MAAM,KAAK,GAA+B,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;QAC7E,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1B,MAAM,QAAQ,GAAG,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAChD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QACtE,2GAA2G;QAC3G,MAAM,UAAU,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,SAAS,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC;QACnG,MAAM,WAAW,GAAG,kBAAkB,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;QAChG,sEAAsE;QACtE,yEAAyE;QACzE,MAAM,KAAK,GAAmB,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACnD,KAAK,EAAE,KAAK,CAAC,IAAI;YACjB,SAAS,EAAE,KAAK,CAAC,EAAE,IAAI,SAAS,EAAE;YAClC,UAAU,EAAE;gBACV,GAAG,WAAW;gBACd,GAAG,KAAK,CAAC,UAAU;gBACnB,WAAW,EAAE,UAAU;gBACvB,UAAU,EAAE,OAAO,IAAI,IAAI;gBAC3B,UAAU,EAAE,IAAI,CAAC,SAAS;gBAC1B,IAAI,EAAE,KAAK;gBACX,YAAY,EAAE,IAAI,CAAC,OAAO;gBAC1B,gEAAgE;gBAChE,uBAAuB,EAAE,KAAK;aAC/B;SACF,CAAC,CAAC,CAAC;QACJ,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,KAAK,MAAM,IAAI,IAAI,KAAK;gBAAE,IAAI,CAAC,KAAK,CAAC,eAAe,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC9E,OAAO;QACT,CAAC;QACD,MAAM,SAAS,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;IACtE,CAAC;CACF"}
|
package/dist/types.d.ts
CHANGED
|
@@ -101,24 +101,21 @@ export type VisionMode = boolean | 'fallback' | 'only';
|
|
|
101
101
|
export interface VisionOption {
|
|
102
102
|
vision?: VisionMode;
|
|
103
103
|
}
|
|
104
|
-
|
|
104
|
+
/**
|
|
105
|
+
* Per-call budgets for `act`. Structured output and vision are judgment-tier
|
|
106
|
+
* options: `extract` takes `schema`; `assert`, `waitFor`, and `extract`
|
|
107
|
+
* take `vision`.
|
|
108
|
+
*/
|
|
109
|
+
export interface AgentOptions {
|
|
105
110
|
timeout?: number;
|
|
106
111
|
maxSteps?: number;
|
|
107
112
|
maxModelCalls?: number;
|
|
108
113
|
}
|
|
109
|
-
export interface AgentSchemaOptions<Schema extends StandardSchemaV1> extends AgentOptions {
|
|
110
|
-
schema: Schema;
|
|
111
|
-
}
|
|
112
114
|
export interface AgentResult {
|
|
113
115
|
readonly ok: true;
|
|
114
116
|
}
|
|
115
|
-
export interface AgentResultWithData<Output> extends AgentResult {
|
|
116
|
-
readonly data: Output;
|
|
117
|
-
}
|
|
118
117
|
export type AgentErrorCode = 'AUTH_CREDENTIAL_UNAVAILABLE' | 'AUTH_CREDENTIAL_INVALID' | 'AUTHENTICATION_FAILED' | 'ENVIRONMENT_UNAVAILABLE' | 'SEED_DATA_MISSING' | 'TEST_SETUP_FAILED' | 'MODEL_UNAVAILABLE' | 'MODEL_PROVIDER_FAILED' | 'MODEL_OUTPUT_INVALID' | 'APP_UNREACHABLE' | 'APP_ALREADY_RUNNING' | 'APP_NOT_OPEN' | 'LOCATOR_NOT_FOUND' | 'LOCATOR_AMBIGUOUS' | 'ACTION_FAILED' | 'AUTOMATION_UNSUPPORTED' | 'POLICY_DENIED' | 'STEP_BUDGET_EXHAUSTED' | 'STEP_TIMEOUT' | 'STEP_NO_CONCLUSION' | 'ASSERTION_FAILED' | 'CANCELLED';
|
|
119
118
|
export interface Agent {
|
|
120
|
-
/** Plans a flow and validates its structured result with Standard Schema v1. */
|
|
121
|
-
act<Schema extends StandardSchemaV1>(instruction: string, params: AgentParams | undefined, options: AgentSchemaOptions<Schema>): Promise<AgentResultWithData<StandardSchemaV1.InferOutput<Schema>>>;
|
|
122
119
|
/** Plans and executes a bounded multi-action flow. */
|
|
123
120
|
act(instruction: string, params?: AgentParams, options?: AgentOptions): Promise<AgentResult>;
|
|
124
121
|
/** Polls a natural-language condition until true or timed out. */
|