@elench/testkit 0.1.96 → 0.1.97
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/lib/cli/agents/providers/claude.mjs +41 -3
- package/lib/cli/agents/providers/codex.mjs +33 -14
- package/lib/cli/agents/providers/shared.mjs +6 -2
- package/lib/cli/assistant/app.mjs +24 -11
- package/lib/cli/assistant/composer.mjs +19 -1
- package/lib/cli/assistant/context-pack.mjs +6 -5
- package/lib/cli/assistant/model-discovery.mjs +243 -0
- package/lib/cli/assistant/prompt-builder.mjs +1 -4
- package/lib/cli/assistant/session.mjs +26 -1
- package/lib/cli/assistant/slash-commands.mjs +8 -2
- package/lib/cli/assistant/state.mjs +99 -6
- package/lib/cli/assistant/tool-registry.mjs +44 -24
- package/lib/database/template-steps.mjs +3 -3
- package/lib/runner/runtime-preparation.mjs +36 -0
- package/lib/runner/template.mjs +24 -3
- package/node_modules/@elench/next-analysis/package.json +1 -1
- package/node_modules/@elench/testkit-bridge/package.json +2 -2
- package/node_modules/@elench/testkit-protocol/package.json +1 -1
- package/node_modules/@elench/ts-analysis/package.json +1 -1
- package/package.json +7 -6
- package/lib/cli/assistant/command-plan.mjs +0 -227
|
@@ -6,6 +6,11 @@ import { parseSlashCommand, formatSlashHelpLines } from "./slash-commands.mjs";
|
|
|
6
6
|
import { executeAssistantTool } from "./tool-registry.mjs";
|
|
7
7
|
import { runAssistantConversationTurn } from "./session.mjs";
|
|
8
8
|
import { prepareAssistantContextPack } from "./context-pack.mjs";
|
|
9
|
+
import {
|
|
10
|
+
discoverAssistantModels,
|
|
11
|
+
formatModelChoices,
|
|
12
|
+
getModelProviderMismatch,
|
|
13
|
+
} from "./model-discovery.mjs";
|
|
9
14
|
import {
|
|
10
15
|
DEFAULT_ASSISTANT_SETTINGS,
|
|
11
16
|
loadAssistantSettings,
|
|
@@ -57,7 +62,15 @@ export function createAssistantState({
|
|
|
57
62
|
}
|
|
58
63
|
);
|
|
59
64
|
let resolvedProviderName = resolveInitialProvider(settings.provider, env);
|
|
65
|
+
const sanitizedStartup = sanitizeSettingsForResolvedProvider({
|
|
66
|
+
productDir,
|
|
67
|
+
settings,
|
|
68
|
+
resolvedProvider: resolvedProviderName,
|
|
69
|
+
});
|
|
70
|
+
settings = sanitizedStartup.settings;
|
|
71
|
+
if (sanitizedStartup.notice) notice = sanitizedStartup.notice;
|
|
60
72
|
let activeStatus = null;
|
|
73
|
+
let startupNoticeEmitted = false;
|
|
61
74
|
let contextUsage = buildContextUsage({
|
|
62
75
|
provider: resolvedProviderName || settings.provider,
|
|
63
76
|
model: settings.model,
|
|
@@ -176,12 +189,20 @@ export function createAssistantState({
|
|
|
176
189
|
|
|
177
190
|
setProvider(nextProvider) {
|
|
178
191
|
settings = mergeAssistantSettings(settings, { provider: nextProvider || DEFAULT_ASSISTANT_SETTINGS.provider });
|
|
179
|
-
resolvedProviderName =
|
|
192
|
+
resolvedProviderName = resolveInitialProvider(settings.provider, env);
|
|
193
|
+
if (settings.model && getModelProviderMismatch(resolvedProviderName, settings.model)) {
|
|
194
|
+
settings = mergeAssistantSettings(settings, { model: null });
|
|
195
|
+
}
|
|
180
196
|
saveAssistantSettings(productDir, settings);
|
|
181
197
|
notify();
|
|
182
198
|
},
|
|
183
199
|
|
|
184
|
-
setModel(nextModel) {
|
|
200
|
+
setModel(nextModel, { custom = false } = {}) {
|
|
201
|
+
const resolvedProvider = resolveInitialProvider(settings.provider, env);
|
|
202
|
+
const mismatch = getModelProviderMismatch(resolvedProvider, nextModel);
|
|
203
|
+
if (mismatch && !custom) {
|
|
204
|
+
throw new Error(mismatch);
|
|
205
|
+
}
|
|
185
206
|
settings = mergeAssistantSettings(settings, { model: nextModel || null });
|
|
186
207
|
saveAssistantSettings(productDir, settings);
|
|
187
208
|
notify();
|
|
@@ -229,6 +250,10 @@ export function createAssistantState({
|
|
|
229
250
|
async submitInput(input) {
|
|
230
251
|
const trimmed = String(input || "").trim();
|
|
231
252
|
if (!trimmed) return;
|
|
253
|
+
if (notice && !startupNoticeEmitted) {
|
|
254
|
+
startupNoticeEmitted = true;
|
|
255
|
+
appendMessage({ role: "system", text: notice });
|
|
256
|
+
}
|
|
232
257
|
appendMessage({ role: "user", text: trimmed });
|
|
233
258
|
|
|
234
259
|
const slash = parseSlashCommandSafe(trimmed);
|
|
@@ -258,6 +283,29 @@ export function createAssistantState({
|
|
|
258
283
|
return;
|
|
259
284
|
}
|
|
260
285
|
|
|
286
|
+
const routedSlash = routeLocalIntent(trimmed);
|
|
287
|
+
if (routedSlash) {
|
|
288
|
+
try {
|
|
289
|
+
await executeSlashCommand({
|
|
290
|
+
slash: routedSlash,
|
|
291
|
+
state,
|
|
292
|
+
productDir,
|
|
293
|
+
settings,
|
|
294
|
+
configs,
|
|
295
|
+
env,
|
|
296
|
+
appendMessage,
|
|
297
|
+
});
|
|
298
|
+
} catch (error) {
|
|
299
|
+
appendMessage({
|
|
300
|
+
role: "system",
|
|
301
|
+
text: error instanceof Error ? error.message : String(error),
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
refreshContextPack();
|
|
305
|
+
notify();
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
261
309
|
try {
|
|
262
310
|
setBusy(true, `Thinking with ${settings.provider === "auto" ? "provider" : settings.provider}...`);
|
|
263
311
|
const emitted = await runAssistantConversationTurn({
|
|
@@ -372,10 +420,17 @@ async function executeSlashCommand({
|
|
|
372
420
|
return;
|
|
373
421
|
}
|
|
374
422
|
if (slash.type === "model") {
|
|
375
|
-
state.setModel(slash.model);
|
|
423
|
+
state.setModel(slash.model, { custom: slash.custom });
|
|
376
424
|
appendMessage({ role: "assistant", text: slash.model ? `Model set to ${slash.model}.` : "Model reset to provider default." });
|
|
377
425
|
return;
|
|
378
426
|
}
|
|
427
|
+
if (slash.type === "model-list") {
|
|
428
|
+
const snapshot = state.getSnapshot();
|
|
429
|
+
const provider = snapshot.resolvedProvider || resolveInitialProvider(snapshot.provider, env) || snapshot.provider;
|
|
430
|
+
const discovery = await discoverAssistantModels({ provider, productDir, env });
|
|
431
|
+
appendMessage({ role: "assistant", text: formatModelChoices(discovery, { currentModel: snapshot.model }) });
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
379
434
|
if (slash.type === "effort") {
|
|
380
435
|
state.setEffort(slash.effort);
|
|
381
436
|
appendMessage({ role: "assistant", text: slash.effort ? `Effort set to ${slash.effort}.` : "Effort reset to provider default." });
|
|
@@ -496,9 +551,16 @@ async function executeSlashTool(slash, context) {
|
|
|
496
551
|
}
|
|
497
552
|
|
|
498
553
|
function buildRunSlashCommand(options = {}) {
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
554
|
+
const types = options.type || [];
|
|
555
|
+
const parts = ["testkit", "run"];
|
|
556
|
+
if (types.length === 1) {
|
|
557
|
+
parts.push(types[0]);
|
|
558
|
+
}
|
|
559
|
+
parts.push("--dir", ".");
|
|
560
|
+
if (types.length !== 1) {
|
|
561
|
+
for (const type of types) {
|
|
562
|
+
parts.push("--type", type);
|
|
563
|
+
}
|
|
502
564
|
}
|
|
503
565
|
for (const suite of options.suite || []) {
|
|
504
566
|
parts.push("--suite", suite);
|
|
@@ -526,3 +588,34 @@ function parseSlashCommandSafe(input) {
|
|
|
526
588
|
};
|
|
527
589
|
}
|
|
528
590
|
}
|
|
591
|
+
|
|
592
|
+
function sanitizeSettingsForResolvedProvider({ productDir, settings, resolvedProvider }) {
|
|
593
|
+
const mismatch = getModelProviderMismatch(resolvedProvider, settings.model);
|
|
594
|
+
if (!mismatch) return { settings, notice: null };
|
|
595
|
+
const previousModel = settings.model;
|
|
596
|
+
const sanitized = mergeAssistantSettings(settings, { model: null });
|
|
597
|
+
saveAssistantSettings(productDir, sanitized);
|
|
598
|
+
return {
|
|
599
|
+
settings: sanitized,
|
|
600
|
+
notice: `Cleared incompatible saved model "${previousModel}" for ${resolvedProvider}.`,
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function routeLocalIntent(input) {
|
|
605
|
+
const normalized = String(input || "").trim().toLowerCase();
|
|
606
|
+
const runMatch = normalized.match(/^run\s+(int|e2e|scenario|dal|load|pw|all)(?:\s+tests?)?$/);
|
|
607
|
+
if (runMatch) {
|
|
608
|
+
return {
|
|
609
|
+
type: "run",
|
|
610
|
+
options: {
|
|
611
|
+
type: [runMatch[1]],
|
|
612
|
+
suite: [],
|
|
613
|
+
file: [],
|
|
614
|
+
service: null,
|
|
615
|
+
},
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
if (/^(show\s+)?latest\s+summary$/.test(normalized)) return { type: "status" };
|
|
619
|
+
if (/^list\s+test\s+files$/.test(normalized)) return { type: "discover" };
|
|
620
|
+
return null;
|
|
621
|
+
}
|
|
@@ -5,7 +5,6 @@ import { loadCurrentRunArtifact, loadLatestRunArtifact, resolveFileSubject } fro
|
|
|
5
5
|
import {
|
|
6
6
|
readContextContent,
|
|
7
7
|
} from "../context-resources.mjs";
|
|
8
|
-
import { extractShellCommand, planShellCommand } from "./command-plan.mjs";
|
|
9
8
|
|
|
10
9
|
const COMMAND_OUTPUT_LIMIT = 14_000;
|
|
11
10
|
const COMMAND_LINE_LIMIT = 220;
|
|
@@ -15,7 +14,7 @@ export function listAssistantTools() {
|
|
|
15
14
|
return [
|
|
16
15
|
{
|
|
17
16
|
name: "shell_exec",
|
|
18
|
-
description: "Execute a shell command inside the repository.
|
|
17
|
+
description: "Execute a shell command inside the repository. Prefer real repo commands such as npm, npx, and testkit.",
|
|
19
18
|
},
|
|
20
19
|
{
|
|
21
20
|
name: "read_context",
|
|
@@ -49,10 +48,10 @@ export async function executeAssistantTool(name, argumentsObject, context) {
|
|
|
49
48
|
}
|
|
50
49
|
|
|
51
50
|
async function shellExecTool(args, context) {
|
|
52
|
-
const command =
|
|
51
|
+
const command = String(args.command || "").trim();
|
|
53
52
|
if (!command) throw new Error("shell_exec requires a command string");
|
|
54
53
|
|
|
55
|
-
const shellCommand =
|
|
54
|
+
const shellCommand = classifyShellCommand(command);
|
|
56
55
|
const commandId = `cmd-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
57
56
|
context.commandLog?.appendCommandLog({
|
|
58
57
|
type: "command_start",
|
|
@@ -60,22 +59,17 @@ async function shellExecTool(args, context) {
|
|
|
60
59
|
commandId,
|
|
61
60
|
cwd: context.productDir,
|
|
62
61
|
raw: command,
|
|
63
|
-
executable: shellCommand.executableCommand,
|
|
64
|
-
normalized: shellCommand.normalized,
|
|
65
62
|
});
|
|
66
63
|
context.onEvent?.({
|
|
67
64
|
type: "tool-start",
|
|
68
65
|
tool: "shell_exec",
|
|
69
|
-
command
|
|
70
|
-
rawCommand: command,
|
|
66
|
+
command,
|
|
71
67
|
title: shellCommand.title,
|
|
72
68
|
testkitRelated: shellCommand.testkitRelated,
|
|
73
|
-
message: shellCommand.
|
|
74
|
-
? `Running ${shellCommand.displayCommand} (${shellCommand.normalizationReason})`
|
|
75
|
-
: `Running ${shellCommand.displayCommand}`,
|
|
69
|
+
message: `Running ${shellCommand.display}`,
|
|
76
70
|
});
|
|
77
71
|
|
|
78
|
-
const result = await execaCommand(
|
|
72
|
+
const result = await execaCommand(command, {
|
|
79
73
|
cwd: context.productDir,
|
|
80
74
|
reject: false,
|
|
81
75
|
shell: true,
|
|
@@ -93,21 +87,18 @@ async function shellExecTool(args, context) {
|
|
|
93
87
|
commandId,
|
|
94
88
|
cwd: context.productDir,
|
|
95
89
|
raw: command,
|
|
96
|
-
executable: shellCommand.executableCommand,
|
|
97
|
-
normalized: shellCommand.normalized,
|
|
98
90
|
code: result.exitCode ?? 0,
|
|
99
91
|
signal: result.signal ?? null,
|
|
100
92
|
});
|
|
101
93
|
context.onEvent?.({
|
|
102
94
|
type: "tool-exit",
|
|
103
95
|
tool: "shell_exec",
|
|
104
|
-
command
|
|
105
|
-
rawCommand: command,
|
|
96
|
+
command,
|
|
106
97
|
title: shellCommand.title,
|
|
107
98
|
testkitRelated: shellCommand.testkitRelated,
|
|
108
99
|
code: result.exitCode ?? 0,
|
|
109
100
|
signal: result.signal ?? null,
|
|
110
|
-
message: `${shellCommand.
|
|
101
|
+
message: `${shellCommand.display} exited ${result.exitCode ?? 0}`,
|
|
111
102
|
});
|
|
112
103
|
|
|
113
104
|
if (shellCommand.testkitRelated) {
|
|
@@ -115,15 +106,13 @@ async function shellExecTool(args, context) {
|
|
|
115
106
|
}
|
|
116
107
|
context.commandLog?.refresh?.();
|
|
117
108
|
|
|
118
|
-
const lines = formatCommandResult(result, shellCommand);
|
|
109
|
+
const lines = formatCommandResult(command, result, shellCommand);
|
|
119
110
|
return {
|
|
120
111
|
ok: (result.exitCode ?? 0) === 0,
|
|
121
112
|
title: shellCommand.title,
|
|
122
113
|
text: lines.join("\n"),
|
|
123
114
|
data: {
|
|
124
115
|
command,
|
|
125
|
-
executableCommand: shellCommand.executableCommand,
|
|
126
|
-
normalizedCommand: shellCommand.normalized,
|
|
127
116
|
stdout: result.stdout || "",
|
|
128
117
|
stderr: result.stderr || "",
|
|
129
118
|
exitCode: result.exitCode ?? 0,
|
|
@@ -227,11 +216,42 @@ async function searchRepoTool(args, context) {
|
|
|
227
216
|
};
|
|
228
217
|
}
|
|
229
218
|
|
|
230
|
-
function
|
|
231
|
-
const
|
|
232
|
-
if (
|
|
233
|
-
|
|
219
|
+
function classifyShellCommand(command) {
|
|
220
|
+
const normalized = command.trim();
|
|
221
|
+
if (/^(testkit)\b/.test(normalized)) {
|
|
222
|
+
return {
|
|
223
|
+
command: "testkit",
|
|
224
|
+
display: normalized,
|
|
225
|
+
title: "testkit command",
|
|
226
|
+
testkitRelated: true,
|
|
227
|
+
};
|
|
234
228
|
}
|
|
229
|
+
if (/^(npx)\s+testkit\b/.test(normalized)) {
|
|
230
|
+
return {
|
|
231
|
+
command: "npx testkit",
|
|
232
|
+
display: normalized,
|
|
233
|
+
title: "npx testkit",
|
|
234
|
+
testkitRelated: true,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
if (/^(npm)\s+run\s+testkit\b/.test(normalized) || /^(npm)\s+run\s+testkit:/.test(normalized)) {
|
|
238
|
+
return {
|
|
239
|
+
command: "npm run testkit",
|
|
240
|
+
display: normalized,
|
|
241
|
+
title: "npm testkit script",
|
|
242
|
+
testkitRelated: true,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
command: normalized.split(/\s+/)[0] || "command",
|
|
247
|
+
display: normalized,
|
|
248
|
+
title: "Shell command",
|
|
249
|
+
testkitRelated: false,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function formatCommandResult(command, result, shellCommand) {
|
|
254
|
+
const lines = [`$ ${command}`];
|
|
235
255
|
const stdout = (result.stdout || "").trim();
|
|
236
256
|
const stderr = (result.stderr || "").trim();
|
|
237
257
|
const merged = [];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { execa } from "execa";
|
|
4
|
-
import {
|
|
4
|
+
import { buildTemplateExecutionEnv } from "../runner/template.mjs";
|
|
5
5
|
import {
|
|
6
6
|
collectConfiguredInputs,
|
|
7
7
|
runConfiguredSteps,
|
|
@@ -13,7 +13,7 @@ export async function runTemplateStage(config, stageName, databaseUrl, options =
|
|
|
13
13
|
if (steps.length === 0) return;
|
|
14
14
|
|
|
15
15
|
const env = {
|
|
16
|
-
...
|
|
16
|
+
...buildTemplateExecutionEnv(config, {}, process.env),
|
|
17
17
|
DATABASE_URL: databaseUrl,
|
|
18
18
|
};
|
|
19
19
|
|
|
@@ -54,7 +54,7 @@ export async function captureTemplateSnapshot(config, outputPath, databaseUrl, o
|
|
|
54
54
|
{
|
|
55
55
|
cwd: config.productDir,
|
|
56
56
|
env: {
|
|
57
|
-
...
|
|
57
|
+
...buildTemplateExecutionEnv(config, {}, process.env),
|
|
58
58
|
DATABASE_URL: templateDbUrl,
|
|
59
59
|
},
|
|
60
60
|
stdout: "pipe",
|
|
@@ -127,6 +127,7 @@ export async function computeRuntimePrepareFingerprint(config) {
|
|
|
127
127
|
: null,
|
|
128
128
|
})
|
|
129
129
|
);
|
|
130
|
+
hash.update(JSON.stringify(collectRuntimeDatabaseFingerprintInputs(config)));
|
|
130
131
|
|
|
131
132
|
for (const envFile of config.testkit.envFiles || []) {
|
|
132
133
|
appendFileToHash(hash, config.productDir, resolveServiceCwd(config.productDir, envFile));
|
|
@@ -138,6 +139,41 @@ export async function computeRuntimePrepareFingerprint(config) {
|
|
|
138
139
|
return hash.digest("hex");
|
|
139
140
|
}
|
|
140
141
|
|
|
142
|
+
function collectRuntimeDatabaseFingerprintInputs(config) {
|
|
143
|
+
const inputs = [];
|
|
144
|
+
const ownDatabaseUrl = readDatabaseUrl(config.stateDir);
|
|
145
|
+
if (ownDatabaseUrl) {
|
|
146
|
+
inputs.push({ service: config.name, url: ownDatabaseUrl });
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const referencedServices = new Set();
|
|
150
|
+
for (const value of Object.values(config.testkit.serviceEnv || {})) {
|
|
151
|
+
collectDatabasePlaceholderServices(value, referencedServices, config.name);
|
|
152
|
+
}
|
|
153
|
+
for (const value of Object.values(config.testkit.local?.env || {})) {
|
|
154
|
+
collectDatabasePlaceholderServices(value, referencedServices, config.name);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const stateDirByService = config.testkit.templateContext?.stateDirByService;
|
|
158
|
+
for (const serviceName of [...referencedServices].sort()) {
|
|
159
|
+
const stateDir = stateDirByService?.get?.(serviceName);
|
|
160
|
+
const databaseUrl = stateDir ? readDatabaseUrl(stateDir) : null;
|
|
161
|
+
inputs.push({ service: serviceName, url: databaseUrl || null });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return inputs;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function collectDatabasePlaceholderServices(value, out, defaultServiceName) {
|
|
168
|
+
if (typeof value !== "string") return;
|
|
169
|
+
const matcher = /\{db(?:Url|Host|Port|Name|User|Password)(?::([a-zA-Z0-9_-]+))?\}/g;
|
|
170
|
+
let match = matcher.exec(value);
|
|
171
|
+
while (match) {
|
|
172
|
+
out.add(match[1] || defaultServiceName);
|
|
173
|
+
match = matcher.exec(value);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
141
177
|
function appendResolvedInputToHash(hash, productDir, absPath) {
|
|
142
178
|
const relative = path.relative(productDir, absPath);
|
|
143
179
|
appendInputToHash(hash, productDir, relative);
|
package/lib/runner/template.mjs
CHANGED
|
@@ -204,17 +204,29 @@ export function buildExecutionEnv(config, extraEnv = {}, processEnv = process.en
|
|
|
204
204
|
return buildExecutionEnvWithContext(config, null, extraEnv, processEnv);
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
export function buildTemplateExecutionEnv(config, extraEnv = {}, processEnv = process.env) {
|
|
208
|
+
return buildExecutionEnvWithContext(config, null, extraEnv, processEnv, {
|
|
209
|
+
omitRuntimeDatabaseBindings: true,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
207
213
|
export function buildTaskExecutionEnv(config, lease, extraEnv = {}, processEnv = process.env) {
|
|
208
214
|
return buildExecutionEnvWithContext(config, lease, extraEnv, processEnv);
|
|
209
215
|
}
|
|
210
216
|
|
|
211
|
-
function buildExecutionEnvWithContext(config, lease, extraEnv, processEnv) {
|
|
217
|
+
function buildExecutionEnvWithContext(config, lease, extraEnv, processEnv, options = {}) {
|
|
212
218
|
const inheritedEnv = { ...processEnv };
|
|
213
219
|
const templateContext = buildTemplateContext(config, lease);
|
|
220
|
+
const serviceEnv = options.omitRuntimeDatabaseBindings
|
|
221
|
+
? omitRuntimeDatabaseBindings(config.testkit.serviceEnv || {})
|
|
222
|
+
: config.testkit.serviceEnv || {};
|
|
223
|
+
const localEnv = options.omitRuntimeDatabaseBindings
|
|
224
|
+
? omitRuntimeDatabaseBindings(config.testkit.local?.env || {})
|
|
225
|
+
: config.testkit.local?.env || {};
|
|
214
226
|
const env = {
|
|
215
227
|
...inheritedEnv,
|
|
216
|
-
...resolveEnvTemplates(
|
|
217
|
-
...resolveEnvTemplates(
|
|
228
|
+
...resolveEnvTemplates(serviceEnv, templateContext),
|
|
229
|
+
...resolveEnvTemplates(localEnv, templateContext),
|
|
218
230
|
...resolveEnvTemplates(extraEnv, templateContext),
|
|
219
231
|
TESTKIT_ACTIVE: "1",
|
|
220
232
|
...(config.runtimeId ? { TESTKIT_RUNTIME_ID: String(config.runtimeId) } : {}),
|
|
@@ -340,6 +352,15 @@ function resolveEnvTemplates(values, templateContext) {
|
|
|
340
352
|
);
|
|
341
353
|
}
|
|
342
354
|
|
|
355
|
+
function omitRuntimeDatabaseBindings(values = {}) {
|
|
356
|
+
return Object.fromEntries(
|
|
357
|
+
Object.entries(values).filter(([_key, value]) => {
|
|
358
|
+
if (typeof value !== "string") return true;
|
|
359
|
+
return !/\{db(?:Url|Host|Port|Name|User|Password)(?::[a-zA-Z0-9_-]+)?\}/.test(value);
|
|
360
|
+
})
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
|
|
343
364
|
function finalizeRuntimePrepare(prepare, context) {
|
|
344
365
|
if (!prepare) {
|
|
345
366
|
return {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elench/testkit-bridge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.97",
|
|
4
4
|
"description": "Browser bridge helpers for testkit",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@elench/testkit-protocol": "0.1.
|
|
25
|
+
"@elench/testkit-protocol": "0.1.97"
|
|
26
26
|
},
|
|
27
27
|
"private": false
|
|
28
28
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elench/testkit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.97",
|
|
4
4
|
"description": "Assistant-first CLI for running, inspecting, and debugging local testkit suites",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"workspaces": [
|
|
@@ -62,7 +62,8 @@
|
|
|
62
62
|
"test:audit": "node scripts/test-boundary-audit.mjs",
|
|
63
63
|
"test:unit": "npm run build:packages && npm run test:audit && vitest run --config vitest.unit.config.mjs",
|
|
64
64
|
"test:integration": "npm run build:packages && vitest run test/integration",
|
|
65
|
-
"test:system": "npm run build:packages && vitest run test/system --passWithNoTests"
|
|
65
|
+
"test:system": "npm run build:packages && vitest run test/system --passWithNoTests",
|
|
66
|
+
"test:live-providers": "npm run build:packages && vitest run --config vitest.live.config.mjs --passWithNoTests"
|
|
66
67
|
},
|
|
67
68
|
"files": [
|
|
68
69
|
"bin/",
|
|
@@ -83,10 +84,10 @@
|
|
|
83
84
|
},
|
|
84
85
|
"dependencies": {
|
|
85
86
|
"@babel/code-frame": "^7.29.0",
|
|
86
|
-
"@elench/next-analysis": "0.1.
|
|
87
|
-
"@elench/testkit-bridge": "0.1.
|
|
88
|
-
"@elench/testkit-protocol": "0.1.
|
|
89
|
-
"@elench/ts-analysis": "0.1.
|
|
87
|
+
"@elench/next-analysis": "0.1.97",
|
|
88
|
+
"@elench/testkit-bridge": "0.1.97",
|
|
89
|
+
"@elench/testkit-protocol": "0.1.97",
|
|
90
|
+
"@elench/ts-analysis": "0.1.97",
|
|
90
91
|
"@oclif/core": "^4.10.6",
|
|
91
92
|
"esbuild": "^0.25.11",
|
|
92
93
|
"execa": "^9.5.0",
|