devharness 0.9.16 → 0.9.17
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/build/helpers/env-file.d.ts +35 -0
- package/build/helpers/env-file.d.ts.map +1 -0
- package/build/helpers/env-file.js +53 -0
- package/build/helpers/env-file.js.map +1 -0
- package/build/tools/interpolation.d.ts +20 -3
- package/build/tools/interpolation.d.ts.map +1 -1
- package/build/tools/interpolation.js +52 -14
- package/build/tools/interpolation.js.map +1 -1
- package/build/tools/replay-env-file.test.d.ts +2 -0
- package/build/tools/replay-env-file.test.d.ts.map +1 -0
- package/build/tools/replay-env-file.test.js +137 -0
- package/build/tools/replay-env-file.test.js.map +1 -0
- package/build/tools/replay-env-token.test.d.ts +2 -0
- package/build/tools/replay-env-token.test.d.ts.map +1 -0
- package/build/tools/replay-env-token.test.js +113 -0
- package/build/tools/replay-env-token.test.js.map +1 -0
- package/build/tools/replay-executor.d.ts +18 -0
- package/build/tools/replay-executor.d.ts.map +1 -1
- package/build/tools/replay-executor.js +10 -6
- package/build/tools/replay-executor.js.map +1 -1
- package/build/tools/replay-nested-variables.test.d.ts +2 -0
- package/build/tools/replay-nested-variables.test.d.ts.map +1 -0
- package/build/tools/replay-nested-variables.test.js +139 -0
- package/build/tools/replay-nested-variables.test.js.map +1 -0
- package/build/tools/replay-tools.d.ts +4 -0
- package/build/tools/replay-tools.d.ts.map +1 -1
- package/build/tools/replay-tools.js +186 -7
- package/build/tools/replay-tools.js.map +1 -1
- package/build/tools/replay-typed-text-variables.test.d.ts +2 -0
- package/build/tools/replay-typed-text-variables.test.d.ts.map +1 -0
- package/build/tools/replay-typed-text-variables.test.js +119 -0
- package/build/tools/replay-typed-text-variables.test.js.map +1 -0
- package/build/tools/replay-variable-key-validation.test.d.ts +2 -0
- package/build/tools/replay-variable-key-validation.test.d.ts.map +1 -0
- package/build/tools/replay-variable-key-validation.test.js +124 -0
- package/build/tools/replay-variable-key-validation.test.js.map +1 -0
- package/docs/instructions.md +3 -0
- package/docs/replay.md +82 -2
- package/package.json +1 -1
- package/plugin/skills/devharness/SKILL.md +2 -2
- package/plugin/skills/devharness/references/sequences.md +33 -3
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a `KEY=value` environment file for a single replay run.
|
|
3
|
+
*
|
|
4
|
+
* A run reads its credentials from a file the caller names rather than from
|
|
5
|
+
* the ambient process environment, so the values never enter the sequence
|
|
6
|
+
* file, never travel in the tool call, and never need the MCP client
|
|
7
|
+
* restarted to change - the environment of a running server is fixed at the
|
|
8
|
+
* moment it starts.
|
|
9
|
+
*
|
|
10
|
+
* The parse is deliberately narrow. There is no `$VAR` expansion inside
|
|
11
|
+
* values: a password containing a `$` is ordinary, and expanding it would
|
|
12
|
+
* substitute something else silently and type the wrong credential.
|
|
13
|
+
*/
|
|
14
|
+
/** One line's worth of syntax the parser refuses, with the line number. */
|
|
15
|
+
export interface EnvFileProblem {
|
|
16
|
+
line: number;
|
|
17
|
+
text: string;
|
|
18
|
+
}
|
|
19
|
+
export interface ParsedEnvFile {
|
|
20
|
+
values: Record<string, string>;
|
|
21
|
+
problems: EnvFileProblem[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Read `KEY=value` pairs out of the file's text.
|
|
25
|
+
*
|
|
26
|
+
* Accepted per line: a blank line, a `#` comment, or `NAME=value` with an
|
|
27
|
+
* optional `export ` prefix. Surrounding single or double quotes are stripped
|
|
28
|
+
* from the value; everything else in it is kept verbatim, trailing whitespace
|
|
29
|
+
* included where it was quoted. A name that does not match
|
|
30
|
+
* `[A-Za-z_][A-Za-z0-9_]*` is reported rather than stored, because
|
|
31
|
+
* {{env:NAME}} could never reference it and a silently skipped line reads as
|
|
32
|
+
* a set variable.
|
|
33
|
+
*/
|
|
34
|
+
export declare function parseEnvFile(text: string): ParsedEnvFile;
|
|
35
|
+
//# sourceMappingURL=env-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-file.d.ts","sourceRoot":"","sources":["../../src/helpers/env-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,2EAA2E;AAC3E,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,QAAQ,EAAE,cAAc,EAAE,CAAC;CAC5B;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CA8BxD"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a `KEY=value` environment file for a single replay run.
|
|
3
|
+
*
|
|
4
|
+
* A run reads its credentials from a file the caller names rather than from
|
|
5
|
+
* the ambient process environment, so the values never enter the sequence
|
|
6
|
+
* file, never travel in the tool call, and never need the MCP client
|
|
7
|
+
* restarted to change - the environment of a running server is fixed at the
|
|
8
|
+
* moment it starts.
|
|
9
|
+
*
|
|
10
|
+
* The parse is deliberately narrow. There is no `$VAR` expansion inside
|
|
11
|
+
* values: a password containing a `$` is ordinary, and expanding it would
|
|
12
|
+
* substitute something else silently and type the wrong credential.
|
|
13
|
+
*/
|
|
14
|
+
const NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
15
|
+
/**
|
|
16
|
+
* Read `KEY=value` pairs out of the file's text.
|
|
17
|
+
*
|
|
18
|
+
* Accepted per line: a blank line, a `#` comment, or `NAME=value` with an
|
|
19
|
+
* optional `export ` prefix. Surrounding single or double quotes are stripped
|
|
20
|
+
* from the value; everything else in it is kept verbatim, trailing whitespace
|
|
21
|
+
* included where it was quoted. A name that does not match
|
|
22
|
+
* `[A-Za-z_][A-Za-z0-9_]*` is reported rather than stored, because
|
|
23
|
+
* {{env:NAME}} could never reference it and a silently skipped line reads as
|
|
24
|
+
* a set variable.
|
|
25
|
+
*/
|
|
26
|
+
export function parseEnvFile(text) {
|
|
27
|
+
const values = {};
|
|
28
|
+
const problems = [];
|
|
29
|
+
text.split(/\r?\n/).forEach((raw, i) => {
|
|
30
|
+
const line = raw.trim();
|
|
31
|
+
if (line === '' || line.startsWith('#'))
|
|
32
|
+
return;
|
|
33
|
+
const body = line.startsWith('export ') ? line.slice('export '.length).trim() : line;
|
|
34
|
+
const eq = body.indexOf('=');
|
|
35
|
+
if (eq <= 0) {
|
|
36
|
+
problems.push({ line: i + 1, text: line });
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const name = body.slice(0, eq).trim();
|
|
40
|
+
if (!NAME_RE.test(name)) {
|
|
41
|
+
problems.push({ line: i + 1, text: line });
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
let value = body.slice(eq + 1).trim();
|
|
45
|
+
const quoted = (value.startsWith('"') && value.endsWith('"') && value.length >= 2)
|
|
46
|
+
|| (value.startsWith("'") && value.endsWith("'") && value.length >= 2);
|
|
47
|
+
if (quoted)
|
|
48
|
+
value = value.slice(1, -1);
|
|
49
|
+
values[name] = value;
|
|
50
|
+
});
|
|
51
|
+
return { values, problems };
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=env-file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-file.js","sourceRoot":"","sources":["../../src/helpers/env-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAaH,MAAM,OAAO,GAAG,0BAA0B,CAAC;AAE3C;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,MAAM,QAAQ,GAAqB,EAAE,CAAC;IAEtC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE;QACrC,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QACxB,IAAI,IAAI,KAAK,EAAE,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO;QAEhD,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QACrF,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;YACZ,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YAC3C,OAAO;QACT,CAAC;QAED,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACtC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YAC3C,OAAO;QACT,CAAC;QAED,IAAI,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACtC,MAAM,MAAM,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;eAC7E,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;QACzE,IAAI,MAAM;YAAE,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAEvC,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AAC9B,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Sequence-step parameter interpolation.
|
|
3
|
-
* Resolves {{var:name.path.to.field}} and {{
|
|
4
|
-
* command's params before the executor runs it.
|
|
3
|
+
* Resolves {{var:name.path.to.field}}, {{timestamp}} and {{env:NAME}} tokens
|
|
4
|
+
* inside a command's params before the executor runs it.
|
|
5
5
|
*
|
|
6
6
|
* Whole-string-token rule: if a string param's entire value is exactly one
|
|
7
7
|
* token (e.g. params.right === "{{var:r.body.available}}"), the resolved
|
|
@@ -16,7 +16,22 @@
|
|
|
16
16
|
* {{var:...}} paths are plain dot-separated segments (a.b.c) except where a
|
|
17
17
|
* real key contains a literal dot (e.g. a response field keyed 'exec.t1.s2')
|
|
18
18
|
* - use bracket notation for that segment: a.b['exec.t1.s2'].c
|
|
19
|
+
*
|
|
20
|
+
* {{env:NAME}} reads the run's environment at run time - the file named by
|
|
21
|
+
* `envFile` first, then process.env. It is how a credential is kept
|
|
22
|
+
* OUT of the sequence file: the file holds the token, the value lives in the
|
|
23
|
+
* environment, and neither the file nor the tool call carries the secret. An
|
|
24
|
+
* unset or empty variable fails the step rather than resolving to '' - a blank
|
|
25
|
+
* password submits the form and surfaces as a confusing downstream failure
|
|
26
|
+
* instead of the missing configuration that caused it. Environment values are
|
|
27
|
+
* strings, so a whole-string {{env:PORT}} yields "3000", not 3000.
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* Whether a string carries any interpolation token. A step already
|
|
31
|
+
* parameterised this way needs no `variables` prompt: its value is supplied at
|
|
32
|
+
* run time by definition.
|
|
19
33
|
*/
|
|
34
|
+
export declare function hasTemplateToken(value: string): boolean;
|
|
20
35
|
export declare class InterpolationError extends Error {
|
|
21
36
|
token: string;
|
|
22
37
|
constructor(token: string, reason: string);
|
|
@@ -26,5 +41,7 @@ export declare class InterpolationError extends Error {
|
|
|
26
41
|
* runTimestamp must be computed once per sequence run (not per call) so
|
|
27
42
|
* {{timestamp}} is stable across multiple steps in the same run.
|
|
28
43
|
*/
|
|
29
|
-
export declare function interpolateParams(params: Record<string, any>, store: Record<string, any>, runTimestamp: number
|
|
44
|
+
export declare function interpolateParams(params: Record<string, any>, store: Record<string, any>, runTimestamp: number,
|
|
45
|
+
/** The run's `envFile` values, checked before process.env by {{env:NAME}}. */
|
|
46
|
+
runEnv?: Record<string, string>): Record<string, any>;
|
|
30
47
|
//# sourceMappingURL=interpolation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interpolation.d.ts","sourceRoot":"","sources":["../../src/tools/interpolation.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"interpolation.d.ts","sourceRoot":"","sources":["../../src/tools/interpolation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAMH;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAGvD;AAED,qBAAa,kBAAmB,SAAQ,KAAK;IACxB,KAAK,EAAE,MAAM;gBAAb,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAGjD;AA8ID;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC1B,YAAY,EAAE,MAAM;AACpB,8EAA8E;AAC9E,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9B,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAErB"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Sequence-step parameter interpolation.
|
|
3
|
-
* Resolves {{var:name.path.to.field}} and {{
|
|
4
|
-
* command's params before the executor runs it.
|
|
3
|
+
* Resolves {{var:name.path.to.field}}, {{timestamp}} and {{env:NAME}} tokens
|
|
4
|
+
* inside a command's params before the executor runs it.
|
|
5
5
|
*
|
|
6
6
|
* Whole-string-token rule: if a string param's entire value is exactly one
|
|
7
7
|
* token (e.g. params.right === "{{var:r.body.available}}"), the resolved
|
|
@@ -16,9 +16,28 @@
|
|
|
16
16
|
* {{var:...}} paths are plain dot-separated segments (a.b.c) except where a
|
|
17
17
|
* real key contains a literal dot (e.g. a response field keyed 'exec.t1.s2')
|
|
18
18
|
* - use bracket notation for that segment: a.b['exec.t1.s2'].c
|
|
19
|
+
*
|
|
20
|
+
* {{env:NAME}} reads the run's environment at run time - the file named by
|
|
21
|
+
* `envFile` first, then process.env. It is how a credential is kept
|
|
22
|
+
* OUT of the sequence file: the file holds the token, the value lives in the
|
|
23
|
+
* environment, and neither the file nor the tool call carries the secret. An
|
|
24
|
+
* unset or empty variable fails the step rather than resolving to '' - a blank
|
|
25
|
+
* password submits the form and surfaces as a confusing downstream failure
|
|
26
|
+
* instead of the missing configuration that caused it. Environment values are
|
|
27
|
+
* strings, so a whole-string {{env:PORT}} yields "3000", not 3000.
|
|
28
|
+
*/
|
|
29
|
+
const TOKEN_BODY = String.raw `var:[^}]+|timestamp(?:[+-]\d+)?|env:[A-Za-z_][A-Za-z0-9_]*`;
|
|
30
|
+
const TOKEN_RE = new RegExp(String.raw `\{\{\s*(${TOKEN_BODY})\s*\}\}`, 'g');
|
|
31
|
+
const WHOLE_TOKEN_RE = new RegExp(String.raw `^\{\{\s*(${TOKEN_BODY})\s*\}\}$`);
|
|
32
|
+
/**
|
|
33
|
+
* Whether a string carries any interpolation token. A step already
|
|
34
|
+
* parameterised this way needs no `variables` prompt: its value is supplied at
|
|
35
|
+
* run time by definition.
|
|
19
36
|
*/
|
|
20
|
-
|
|
21
|
-
|
|
37
|
+
export function hasTemplateToken(value) {
|
|
38
|
+
TOKEN_RE.lastIndex = 0;
|
|
39
|
+
return TOKEN_RE.test(value);
|
|
40
|
+
}
|
|
22
41
|
export class InterpolationError extends Error {
|
|
23
42
|
token;
|
|
24
43
|
constructor(token, reason) {
|
|
@@ -56,12 +75,29 @@ function tokenizePath(path) {
|
|
|
56
75
|
/**
|
|
57
76
|
* Resolve a single token (without the {{ }} wrapper) against the variable store.
|
|
58
77
|
*/
|
|
59
|
-
function resolveToken(token, store, runTimestamp) {
|
|
78
|
+
function resolveToken(token, store, runTimestamp, runEnv) {
|
|
60
79
|
if (token.startsWith('timestamp')) {
|
|
61
80
|
const offsetMatch = token.match(/^timestamp([+-]\d+)?$/);
|
|
62
81
|
const offset = offsetMatch?.[1] ? parseInt(offsetMatch[1], 10) : 0;
|
|
63
82
|
return runTimestamp + offset;
|
|
64
83
|
}
|
|
84
|
+
if (token.startsWith('env:')) {
|
|
85
|
+
const name = token.slice('env:'.length);
|
|
86
|
+
// The named file wins over the ambient environment. The caller named this
|
|
87
|
+
// file for this run; a stale variable in the server's own environment
|
|
88
|
+
// shadowing it would substitute a different credential with nothing in the
|
|
89
|
+
// output to say so.
|
|
90
|
+
const value = runEnv && name in runEnv ? runEnv[name] : process.env[name];
|
|
91
|
+
// The reason is the variable NAME and its state, never its value: this
|
|
92
|
+
// message reaches the run output and the debug log.
|
|
93
|
+
if (value === undefined) {
|
|
94
|
+
throw new InterpolationError(token, `${name} is not set${runEnv ? ' in the run\'s envFile or' : ' in'} this process's environment - add it to the envFile, or export it before the run (the sequence file deliberately holds no value for it)`);
|
|
95
|
+
}
|
|
96
|
+
if (value === '') {
|
|
97
|
+
throw new InterpolationError(token, `${name} is set but empty - an empty value would be typed or sent as-is, so the step fails here instead`);
|
|
98
|
+
}
|
|
99
|
+
return value;
|
|
100
|
+
}
|
|
65
101
|
const path = token.slice('var:'.length);
|
|
66
102
|
let parts;
|
|
67
103
|
try {
|
|
@@ -101,10 +137,10 @@ function resolveToken(token, store, runTimestamp) {
|
|
|
101
137
|
* Whole-string token -> typed value passthrough.
|
|
102
138
|
* Embedded token(s) -> String-coerced substitution.
|
|
103
139
|
*/
|
|
104
|
-
function resolveString(value, store, runTimestamp) {
|
|
140
|
+
function resolveString(value, store, runTimestamp, runEnv) {
|
|
105
141
|
const wholeMatch = value.match(WHOLE_TOKEN_RE);
|
|
106
142
|
if (wholeMatch) {
|
|
107
|
-
return resolveToken(wholeMatch[1], store, runTimestamp);
|
|
143
|
+
return resolveToken(wholeMatch[1], store, runTimestamp, runEnv);
|
|
108
144
|
}
|
|
109
145
|
TOKEN_RE.lastIndex = 0;
|
|
110
146
|
if (!TOKEN_RE.test(value)) {
|
|
@@ -112,21 +148,21 @@ function resolveString(value, store, runTimestamp) {
|
|
|
112
148
|
}
|
|
113
149
|
TOKEN_RE.lastIndex = 0;
|
|
114
150
|
return value.replace(TOKEN_RE, (_match, token) => {
|
|
115
|
-
const resolved = resolveToken(token, store, runTimestamp);
|
|
151
|
+
const resolved = resolveToken(token, store, runTimestamp, runEnv);
|
|
116
152
|
return typeof resolved === 'object' ? JSON.stringify(resolved) : String(resolved);
|
|
117
153
|
});
|
|
118
154
|
}
|
|
119
|
-
function walk(value, store, runTimestamp) {
|
|
155
|
+
function walk(value, store, runTimestamp, runEnv) {
|
|
120
156
|
if (typeof value === 'string') {
|
|
121
|
-
return resolveString(value, store, runTimestamp);
|
|
157
|
+
return resolveString(value, store, runTimestamp, runEnv);
|
|
122
158
|
}
|
|
123
159
|
if (Array.isArray(value)) {
|
|
124
|
-
return value.map(item => walk(item, store, runTimestamp));
|
|
160
|
+
return value.map(item => walk(item, store, runTimestamp, runEnv));
|
|
125
161
|
}
|
|
126
162
|
if (value !== null && typeof value === 'object') {
|
|
127
163
|
const result = {};
|
|
128
164
|
for (const key of Object.keys(value)) {
|
|
129
|
-
result[key] = walk(value[key], store, runTimestamp);
|
|
165
|
+
result[key] = walk(value[key], store, runTimestamp, runEnv);
|
|
130
166
|
}
|
|
131
167
|
return result;
|
|
132
168
|
}
|
|
@@ -137,7 +173,9 @@ function walk(value, store, runTimestamp) {
|
|
|
137
173
|
* runTimestamp must be computed once per sequence run (not per call) so
|
|
138
174
|
* {{timestamp}} is stable across multiple steps in the same run.
|
|
139
175
|
*/
|
|
140
|
-
export function interpolateParams(params, store, runTimestamp
|
|
141
|
-
|
|
176
|
+
export function interpolateParams(params, store, runTimestamp,
|
|
177
|
+
/** The run's `envFile` values, checked before process.env by {{env:NAME}}. */
|
|
178
|
+
runEnv) {
|
|
179
|
+
return walk(params, store, runTimestamp, runEnv);
|
|
142
180
|
}
|
|
143
181
|
//# sourceMappingURL=interpolation.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interpolation.js","sourceRoot":"","sources":["../../src/tools/interpolation.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"interpolation.js","sourceRoot":"","sources":["../../src/tools/interpolation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replay-env-file.test.d.ts","sourceRoot":"","sources":["../../src/tools/replay-env-file.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `envFile` names a KEY=value file for one run, so a credential lives neither
|
|
3
|
+
* in the sequence file nor in the tool call, and changing it needs no MCP
|
|
4
|
+
* client restart - the environment of a running server is fixed when it starts.
|
|
5
|
+
*
|
|
6
|
+
* Two rules the alternatives get wrong. The file WINS over the server's own
|
|
7
|
+
* environment: the caller named this file for this run, and a stale ambient
|
|
8
|
+
* variable shadowing it would substitute a different credential with nothing
|
|
9
|
+
* in the output to say so. And process.env is never written: concurrent
|
|
10
|
+
* background runs may name different files, so a global write would let one
|
|
11
|
+
* run's credentials resolve inside the other.
|
|
12
|
+
*/
|
|
13
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
14
|
+
import { promises as fs } from 'fs';
|
|
15
|
+
import { tmpdir } from 'os';
|
|
16
|
+
import { join } from 'path';
|
|
17
|
+
import { parseEnvFile } from '../helpers/env-file.js';
|
|
18
|
+
import { CommandRecorder } from '../command-recorder.js';
|
|
19
|
+
import { createReplayTools } from './replay-tools.js';
|
|
20
|
+
import { getProjectDir } from '../helpers/paths.js';
|
|
21
|
+
import { productionShaped } from '../test-support/fake-execute-tool-call.js';
|
|
22
|
+
describe('parseEnvFile', () => {
|
|
23
|
+
it('reads KEY=value, skipping blanks and # comments', () => {
|
|
24
|
+
const { values, problems } = parseEnvFile('# a comment\n\nAPP_PASSWORD=hunter2\nAPP_USER=alice\n');
|
|
25
|
+
expect(values).toEqual({ APP_PASSWORD: 'hunter2', APP_USER: 'alice' });
|
|
26
|
+
expect(problems).toEqual([]);
|
|
27
|
+
});
|
|
28
|
+
it('accepts an export prefix and strips surrounding quotes', () => {
|
|
29
|
+
const { values } = parseEnvFile('export A="one two"\nB=\'three\'\n');
|
|
30
|
+
expect(values).toEqual({ A: 'one two', B: 'three' });
|
|
31
|
+
});
|
|
32
|
+
it('keeps a value containing = and $ verbatim, with no expansion', () => {
|
|
33
|
+
const { values } = parseEnvFile('TOKEN=ab=cd$HOME$\n');
|
|
34
|
+
expect(values).toEqual({ TOKEN: 'ab=cd$HOME$' });
|
|
35
|
+
});
|
|
36
|
+
it('reports a line that is not NAME=value rather than skipping it', () => {
|
|
37
|
+
const { values, problems } = parseEnvFile('GOOD=1\njust some words\n2BAD=x\n');
|
|
38
|
+
expect(values).toEqual({ GOOD: '1' });
|
|
39
|
+
expect(problems.map(p => p.line)).toEqual([2, 3]);
|
|
40
|
+
});
|
|
41
|
+
it('keeps an empty value, which is a set-but-empty variable', () => {
|
|
42
|
+
expect(parseEnvFile('EMPTY=\n').values).toEqual({ EMPTY: '' });
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
describe('envFile through a run', () => {
|
|
46
|
+
let dir;
|
|
47
|
+
let recorder;
|
|
48
|
+
let replay;
|
|
49
|
+
let typed;
|
|
50
|
+
const original = { ...process.env };
|
|
51
|
+
beforeEach(async () => {
|
|
52
|
+
dir = await fs.mkdtemp(join(tmpdir(), 'cdp-envfile-'));
|
|
53
|
+
recorder = new CommandRecorder();
|
|
54
|
+
typed = [];
|
|
55
|
+
vi.spyOn(recorder, 'getSequencesDir').mockReturnValue(dir);
|
|
56
|
+
const fieldValues = new Map();
|
|
57
|
+
({ replay } = createReplayTools(recorder, vi.fn(productionShaped(async (tool, params) => {
|
|
58
|
+
if (tool === 'input' && params.action === 'type') {
|
|
59
|
+
typed.push(String(params.text));
|
|
60
|
+
if (params.selector)
|
|
61
|
+
fieldValues.set(String(params.selector), String(params.text));
|
|
62
|
+
}
|
|
63
|
+
if (tool === 'inspect' && params.action === 'evaluateExpression') {
|
|
64
|
+
const selector = String(params.expression).match(/querySelector\('([^']*)'\)/)?.[1] ?? '';
|
|
65
|
+
return { content: [{ type: 'text', text: '```json\n' + JSON.stringify(fieldValues.get(selector) ?? '') + '\n```' }] };
|
|
66
|
+
}
|
|
67
|
+
return { content: [{ type: 'text', text: '' }] };
|
|
68
|
+
})), async () => null, async () => 9222, undefined));
|
|
69
|
+
await fs.writeFile(join(dir, 'login.json'), JSON.stringify({
|
|
70
|
+
id: 'seq-login', name: 'login', createdAt: 1,
|
|
71
|
+
commands: [{ tool: 'input', params: { action: 'type', selector: '#password', text: '{{env:APP_PASSWORD}}', connectionReason: 'app' } }],
|
|
72
|
+
}));
|
|
73
|
+
await recorder.loadSequenceFromDisk(join(dir, 'login.json'));
|
|
74
|
+
});
|
|
75
|
+
afterEach(async () => {
|
|
76
|
+
process.env = { ...original };
|
|
77
|
+
recorder.stopSequenceWatch();
|
|
78
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
79
|
+
});
|
|
80
|
+
const run = (extra) => replay.handler({ action: 'run', wait: true, name: 'login', connectionReason: 'app', ...extra });
|
|
81
|
+
const text = (res) => res.content.map((c) => c.text).join('\n');
|
|
82
|
+
it('resolves a token from the file at an absolute path', async () => {
|
|
83
|
+
const envPath = join(dir, 'sequences.env');
|
|
84
|
+
await fs.writeFile(envPath, 'APP_PASSWORD=from-file\n');
|
|
85
|
+
const res = await run({ envFile: envPath });
|
|
86
|
+
expect(res.isError).toBeUndefined();
|
|
87
|
+
expect(typed).toEqual(['from-file']);
|
|
88
|
+
});
|
|
89
|
+
it('resolves a relative path against the project directory', async () => {
|
|
90
|
+
const rel = 'test-sequences.env';
|
|
91
|
+
const abs = join(getProjectDir(), rel);
|
|
92
|
+
await fs.writeFile(abs, 'APP_PASSWORD=from-relative\n');
|
|
93
|
+
try {
|
|
94
|
+
await run({ envFile: rel });
|
|
95
|
+
expect(typed).toEqual(['from-relative']);
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
await fs.rm(abs, { force: true });
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
it('wins over a value already in the process environment', async () => {
|
|
102
|
+
process.env.APP_PASSWORD = 'from-ambient';
|
|
103
|
+
const envPath = join(dir, 'sequences.env');
|
|
104
|
+
await fs.writeFile(envPath, 'APP_PASSWORD=from-file\n');
|
|
105
|
+
await run({ envFile: envPath });
|
|
106
|
+
expect(typed).toEqual(['from-file']);
|
|
107
|
+
});
|
|
108
|
+
it('falls through to the process environment for a name the file omits', async () => {
|
|
109
|
+
process.env.APP_PASSWORD = 'from-ambient';
|
|
110
|
+
const envPath = join(dir, 'sequences.env');
|
|
111
|
+
await fs.writeFile(envPath, 'SOMETHING_ELSE=x\n');
|
|
112
|
+
await run({ envFile: envPath });
|
|
113
|
+
expect(typed).toEqual(['from-ambient']);
|
|
114
|
+
});
|
|
115
|
+
it('does not write the file values into process.env', async () => {
|
|
116
|
+
delete process.env.APP_PASSWORD;
|
|
117
|
+
const envPath = join(dir, 'sequences.env');
|
|
118
|
+
await fs.writeFile(envPath, 'APP_PASSWORD=from-file\n');
|
|
119
|
+
await run({ envFile: envPath });
|
|
120
|
+
expect(process.env.APP_PASSWORD).toBeUndefined();
|
|
121
|
+
});
|
|
122
|
+
it('fails as a parameter error before any step runs when the file is missing', async () => {
|
|
123
|
+
const res = await run({ envFile: join(dir, 'nope.env') });
|
|
124
|
+
expect(res.isError).toBe(true);
|
|
125
|
+
expect(text(res)).toContain('no such file');
|
|
126
|
+
expect(typed).toEqual([]);
|
|
127
|
+
});
|
|
128
|
+
it('fails before any step runs on a malformed line, naming the line number', async () => {
|
|
129
|
+
const envPath = join(dir, 'bad.env');
|
|
130
|
+
await fs.writeFile(envPath, 'APP_PASSWORD=ok\njust some words\n');
|
|
131
|
+
const res = await run({ envFile: envPath });
|
|
132
|
+
expect(res.isError).toBe(true);
|
|
133
|
+
expect(text(res)).toContain('line 2');
|
|
134
|
+
expect(typed).toEqual([]);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
//# sourceMappingURL=replay-env-file.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replay-env-file.test.js","sourceRoot":"","sources":["../../src/tools/replay-env-file.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replay-env-token.test.d.ts","sourceRoot":"","sources":["../../src/tools/replay-env-token.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `{{env:NAME}}` keeps a credential OUT of the sequence file: the file holds
|
|
3
|
+
* the token, the value lives in the environment, and neither the file nor the
|
|
4
|
+
* tool call carries the secret.
|
|
5
|
+
*
|
|
6
|
+
* Two behaviours the alternative gets wrong. An unset or empty variable FAILS
|
|
7
|
+
* the step - resolving to '' would submit a blank password and surface as a
|
|
8
|
+
* confusing downstream failure instead of the missing configuration that
|
|
9
|
+
* caused it. And a token-bearing step does not hold `run` open for a
|
|
10
|
+
* `variables` answer: its value arrives at run time by definition.
|
|
11
|
+
*/
|
|
12
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
13
|
+
import { promises as fs } from 'fs';
|
|
14
|
+
import { tmpdir } from 'os';
|
|
15
|
+
import { join } from 'path';
|
|
16
|
+
import { interpolateParams, hasTemplateToken, InterpolationError } from './interpolation.js';
|
|
17
|
+
import { CommandRecorder } from '../command-recorder.js';
|
|
18
|
+
import { createReplayTools } from './replay-tools.js';
|
|
19
|
+
import { productionShaped } from '../test-support/fake-execute-tool-call.js';
|
|
20
|
+
describe('{{env:NAME}} resolution', () => {
|
|
21
|
+
const original = { ...process.env };
|
|
22
|
+
afterEach(() => { process.env = { ...original }; });
|
|
23
|
+
it('resolves from the process environment', () => {
|
|
24
|
+
process.env.APP_PASSWORD = 'from-env';
|
|
25
|
+
expect(interpolateParams({ text: '{{env:APP_PASSWORD}}' }, {}, 0)).toEqual({ text: 'from-env' });
|
|
26
|
+
});
|
|
27
|
+
it('substitutes inside a larger string', () => {
|
|
28
|
+
process.env.APP_TOKEN = 'abc';
|
|
29
|
+
expect(interpolateParams({ h: 'Bearer {{env:APP_TOKEN}}' }, {}, 0)).toEqual({ h: 'Bearer abc' });
|
|
30
|
+
});
|
|
31
|
+
it('fails the step when the variable is unset, naming the variable and not a value', () => {
|
|
32
|
+
delete process.env.APP_PASSWORD;
|
|
33
|
+
expect(() => interpolateParams({ text: '{{env:APP_PASSWORD}}' }, {}, 0))
|
|
34
|
+
.toThrow(InterpolationError);
|
|
35
|
+
expect(() => interpolateParams({ text: '{{env:APP_PASSWORD}}' }, {}, 0))
|
|
36
|
+
.toThrow(/APP_PASSWORD is not set/);
|
|
37
|
+
});
|
|
38
|
+
it('fails on an empty variable rather than typing an empty value', () => {
|
|
39
|
+
process.env.APP_PASSWORD = '';
|
|
40
|
+
expect(() => interpolateParams({ text: '{{env:APP_PASSWORD}}' }, {}, 0))
|
|
41
|
+
.toThrow(/APP_PASSWORD is set but empty/);
|
|
42
|
+
});
|
|
43
|
+
it('yields a string for a whole-string token, since environment values are strings', () => {
|
|
44
|
+
process.env.APP_PORT = '3000';
|
|
45
|
+
expect(interpolateParams({ port: '{{env:APP_PORT}}' }, {}, 0)).toEqual({ port: '3000' });
|
|
46
|
+
});
|
|
47
|
+
it('leaves the other token kinds working alongside it', () => {
|
|
48
|
+
process.env.APP_USER = 'alice';
|
|
49
|
+
const out = interpolateParams({ a: '{{env:APP_USER}}', b: '{{var:r.id}}', c: '{{timestamp}}' }, { r: { id: 7 } }, 1234);
|
|
50
|
+
expect(out).toEqual({ a: 'alice', b: 7, c: 1234 });
|
|
51
|
+
});
|
|
52
|
+
it('does not treat a lowercase or hyphenated name as a token', () => {
|
|
53
|
+
expect(hasTemplateToken('{{env:app-password}}')).toBe(false);
|
|
54
|
+
expect(interpolateParams({ t: '{{env:app-password}}' }, {}, 0)).toEqual({ t: '{{env:app-password}}' });
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
describe('a token-bearing step does not prompt for variables', () => {
|
|
58
|
+
let dir;
|
|
59
|
+
let recorder;
|
|
60
|
+
let replay;
|
|
61
|
+
let typed;
|
|
62
|
+
const original = { ...process.env };
|
|
63
|
+
beforeEach(async () => {
|
|
64
|
+
dir = await fs.mkdtemp(join(tmpdir(), 'cdp-env-token-'));
|
|
65
|
+
recorder = new CommandRecorder();
|
|
66
|
+
typed = [];
|
|
67
|
+
vi.spyOn(recorder, 'getSequencesDir').mockReturnValue(dir);
|
|
68
|
+
const fieldValues = new Map();
|
|
69
|
+
({ replay } = createReplayTools(recorder, vi.fn(productionShaped(async (tool, params) => {
|
|
70
|
+
if (tool === 'input' && params.action === 'type') {
|
|
71
|
+
typed.push(String(params.text));
|
|
72
|
+
if (params.selector)
|
|
73
|
+
fieldValues.set(String(params.selector), String(params.text));
|
|
74
|
+
}
|
|
75
|
+
if (tool === 'inspect' && params.action === 'evaluateExpression') {
|
|
76
|
+
const selector = String(params.expression).match(/querySelector\('([^']*)'\)/)?.[1] ?? '';
|
|
77
|
+
return { content: [{ type: 'text', text: '```json\n' + JSON.stringify(fieldValues.get(selector) ?? '') + '\n```' }] };
|
|
78
|
+
}
|
|
79
|
+
return { content: [{ type: 'text', text: '' }] };
|
|
80
|
+
})), async () => null, async () => 9222, undefined));
|
|
81
|
+
await fs.writeFile(join(dir, 'login.json'), JSON.stringify({
|
|
82
|
+
id: 'seq-login', name: 'login', createdAt: 1,
|
|
83
|
+
commands: [{ tool: 'input', params: { action: 'type', selector: '#password', text: '{{env:APP_PASSWORD}}', connectionReason: 'suite' } }],
|
|
84
|
+
}));
|
|
85
|
+
await recorder.loadSequenceFromDisk(join(dir, 'login.json'));
|
|
86
|
+
});
|
|
87
|
+
afterEach(async () => {
|
|
88
|
+
process.env = { ...original };
|
|
89
|
+
recorder.stopSequenceWatch();
|
|
90
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
91
|
+
});
|
|
92
|
+
it('runs without variables and types the environment value', async () => {
|
|
93
|
+
process.env.APP_PASSWORD = 'from-env';
|
|
94
|
+
const res = await replay.handler({ action: 'run', wait: true, name: 'login', connectionReason: 'suite' });
|
|
95
|
+
expect(res._meta?.replay?.prompted).toBeUndefined();
|
|
96
|
+
expect(typed).toEqual(['from-env']);
|
|
97
|
+
});
|
|
98
|
+
it('lets an explicitly supplied value win over the environment', async () => {
|
|
99
|
+
process.env.APP_PASSWORD = 'from-env';
|
|
100
|
+
await replay.handler({
|
|
101
|
+
action: 'run', wait: true, name: 'login', connectionReason: 'suite',
|
|
102
|
+
variables: { var_0__password: 'from-caller' },
|
|
103
|
+
});
|
|
104
|
+
expect(typed).toEqual(['from-caller']);
|
|
105
|
+
});
|
|
106
|
+
it('fails the run when the variable is unset', async () => {
|
|
107
|
+
delete process.env.APP_PASSWORD;
|
|
108
|
+
const res = await replay.handler({ action: 'run', wait: true, name: 'login', connectionReason: 'suite' });
|
|
109
|
+
expect(res._meta?.replay?.success).toBe(false);
|
|
110
|
+
expect(typed).toEqual([]);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
//# sourceMappingURL=replay-env-token.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replay-env-token.test.js","sourceRoot":"","sources":["../../src/tools/replay-env-token.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -52,6 +52,24 @@ export interface ExecutionContext {
|
|
|
52
52
|
* suite drives two origins at once.
|
|
53
53
|
*/
|
|
54
54
|
rebaseOrigin?: string;
|
|
55
|
+
/**
|
|
56
|
+
* Recorded-typed-text substitutions for this run, from `run`/`runAll`'s
|
|
57
|
+
* `variables`. Inherited by nested sequences (a `conditional`'s `then`, a
|
|
58
|
+
* `forEach`'s `do`), which load from the recorder in their recorded form:
|
|
59
|
+
* without it the credential a caller supplied stops at the top-level
|
|
60
|
+
* sequence and the shared login helper types its RECORDED password into the
|
|
61
|
+
* live app, with the run reporting success.
|
|
62
|
+
*/
|
|
63
|
+
variables?: Record<string, string>;
|
|
64
|
+
/**
|
|
65
|
+
* Values from the run's `envFile`, checked by {{env:NAME}} before
|
|
66
|
+
* process.env. Inherited by nested sequences, so a shared login helper
|
|
67
|
+
* reached by a `conditional` resolves against the same file. The server's
|
|
68
|
+
* own process.env is never mutated: two background runs may name different
|
|
69
|
+
* files, and a global write would let one run's credentials resolve inside
|
|
70
|
+
* the other.
|
|
71
|
+
*/
|
|
72
|
+
runEnv?: Record<string, string>;
|
|
55
73
|
}
|
|
56
74
|
export interface StepResult {
|
|
57
75
|
step: number;
|