@workbench-ai/workbench 0.0.66 → 0.0.68

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.
Files changed (42) hide show
  1. package/dist/dev-open/client.css +37 -249
  2. package/dist/dev-open/client.js +225 -266
  3. package/dist/index.d.ts +2 -6
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +1273 -5000
  6. package/dist/open-server.d.ts +12 -0
  7. package/dist/open-server.d.ts.map +1 -0
  8. package/dist/open-server.js +180 -0
  9. package/package.json +5 -5
  10. package/dist/adapter-command-env.d.ts +0 -8
  11. package/dist/adapter-command-env.d.ts.map +0 -1
  12. package/dist/adapter-command-env.js +0 -80
  13. package/dist/adapter-project.d.ts +0 -29
  14. package/dist/adapter-project.d.ts.map +0 -1
  15. package/dist/adapter-project.js +0 -332
  16. package/dist/benchmark-fingerprint.d.ts +0 -6
  17. package/dist/benchmark-fingerprint.d.ts.map +0 -1
  18. package/dist/benchmark-fingerprint.js +0 -42
  19. package/dist/command-model.d.ts +0 -5
  20. package/dist/command-model.d.ts.map +0 -1
  21. package/dist/command-model.js +0 -537
  22. package/dist/dev-open-server.d.ts +0 -18
  23. package/dist/dev-open-server.d.ts.map +0 -1
  24. package/dist/dev-open-server.js +0 -304
  25. package/dist/init-scaffold.d.ts +0 -22
  26. package/dist/init-scaffold.d.ts.map +0 -1
  27. package/dist/init-scaffold.js +0 -30
  28. package/dist/init-template-pack.d.ts +0 -19
  29. package/dist/init-template-pack.d.ts.map +0 -1
  30. package/dist/init-template-pack.js +0 -262
  31. package/dist/local-archive.d.ts +0 -48
  32. package/dist/local-archive.d.ts.map +0 -1
  33. package/dist/local-archive.js +0 -833
  34. package/dist/local-inspection.d.ts +0 -9
  35. package/dist/local-inspection.d.ts.map +0 -1
  36. package/dist/local-inspection.js +0 -317
  37. package/dist/project-source.d.ts +0 -63
  38. package/dist/project-source.d.ts.map +0 -1
  39. package/dist/project-source.js +0 -682
  40. package/dist/workspace-snapshot.d.ts +0 -10
  41. package/dist/workspace-snapshot.d.ts.map +0 -1
  42. package/dist/workspace-snapshot.js +0 -81
@@ -1,682 +0,0 @@
1
- import { promises as fs } from "node:fs";
2
- import { createHash, randomUUID } from "node:crypto";
3
- import { spawn } from "node:child_process";
4
- import path from "node:path";
5
- import { BENCHMARK_SPEC_FILE, CANDIDATE_SPEC_FILE, buildWorkbenchProjectSourceFiles, engineResolveInvocationForSpec, normalizeSurfaceFiles, parseWorkbenchSourceFiles, resolveWorkbenchResolvedSourceYaml, serializeWorkbenchResolvedSourceYaml, validateWorkbenchResolvedSourceYaml, } from "@workbench-ai/workbench-core";
6
- import { assertWorkbenchAdapterOperationSupport, assertWorkbenchAdapterOperationResultOk, collectWorkbenchAdapterInvocations, readWorkbenchAdapterOperationResult, WORKBENCH_ADAPTER_RESULT_FILE, WORKBENCH_ADAPTER_RESULT_PROTOCOL, workbenchAdapterOperationCommand, workbenchAdapterOperationResultPath, } from "@workbench-ai/workbench-protocol";
7
- import { readSnapshotFiles, WorkspaceSnapshotError, } from "./workspace-snapshot.js";
8
- import { defaultAdapterManifests, composeRuntimeDockerfileWithAdapters, resolveDefaultWorkbenchAdapter, resolveProjectAdapterSource, resolveWorkbenchAdaptersForProject, } from "./adapter-project.js";
9
- import { createAdapterCommandEnv } from "./adapter-command-env.js";
10
- import YAML from "yaml";
11
- export const WORKBENCH_BENCHMARK_FILE = BENCHMARK_SPEC_FILE;
12
- export const WORKBENCH_CANDIDATES_DIR = "candidates";
13
- export const WORKBENCH_CANDIDATE_FILE = CANDIDATE_SPEC_FILE;
14
- function rootAdapterInvocations(spec) {
15
- return [
16
- engineResolveInvocationForSpec(spec),
17
- spec.engineRun,
18
- spec.run,
19
- ...(spec.improve ? [spec.improve] : []),
20
- ];
21
- }
22
- export async function readLocalProjectSource(source, options = {}) {
23
- const paths = await resolveLocalProjectSourcePaths(source, options);
24
- const { dir, benchmarkPath, candidateSpecPath, candidateDir, } = paths;
25
- const benchmarkSource = await readRequiredTextFile(benchmarkPath, WORKBENCH_BENCHMARK_FILE);
26
- const candidateSource = await readRequiredTextFile(candidateSpecPath, "candidate YAML");
27
- const normalizedSources = await normalizeSourceYamlForExecution({
28
- dir,
29
- benchmarkPath,
30
- benchmarkSource,
31
- candidateSpecPath,
32
- candidateSource,
33
- });
34
- const resolvedSource = parseWorkbenchSourceFiles({
35
- benchmarkSource: normalizedSources.benchmarkSource,
36
- candidateSource: normalizedSources.candidateSource,
37
- runId: options.runId,
38
- });
39
- const specSource = serializeWorkbenchResolvedSourceYaml(resolvedSource);
40
- const validation = validateWorkbenchResolvedSourceYaml(specSource);
41
- if (!validation.ok) {
42
- throw new WorkspaceSnapshotError(`Benchmark source is invalid:\n${validation.errors.map((entry) => `- ${entry}`).join("\n")}`);
43
- }
44
- const spec = resolveWorkbenchResolvedSourceYaml(specSource);
45
- const dockerfilePath = spec.environment.dockerfile;
46
- const dockerfileSources = await readDockerfileSourcesForSpec(dir, spec, normalizedSources.engineCases);
47
- const dockerfile = dockerfileSources.get(dockerfilePath);
48
- if (dockerfile === undefined) {
49
- throw new WorkspaceSnapshotError(`Dockerfile not found: ${resolveProjectPath(dir, dockerfilePath)}`);
50
- }
51
- const adapters = await resolveWorkbenchAdaptersForProject(dir, spec);
52
- const benchmarkAdapterIds = [
53
- ...new Set(collectWorkbenchAdapterInvocations(rootAdapterInvocations(spec), adapters.map((adapter) => adapter.manifest)).map((invocation) => invocation.use)),
54
- ];
55
- const composedDockerfile = await composeRuntimeDockerfileWithAdapters(dockerfile, adapters);
56
- const adapterFiles = adapterSourceFiles(adapters);
57
- const absoluteCandidateFilesPath = resolveProjectPath(dir, spec.candidate.files.path);
58
- const candidateFilesPath = absoluteCandidateFilesPath;
59
- const candidateFiles = await directoryExists(absoluteCandidateFilesPath)
60
- ? normalizeSurfaceFiles(await readSnapshotFiles(absoluteCandidateFilesPath))
61
- : [];
62
- const rawEngineResolveFiles = engineResolveFilesFromBundles(normalizedSources.engineCases);
63
- const engineResolveFiles = toRemoteFiles(rawEngineResolveFiles);
64
- const engineCases = normalizedSources.engineCases;
65
- if (engineCases.length === 0) {
66
- throw new WorkspaceSnapshotError(`Engine resolver ${normalizedSources.engineResolve.use} did not emit any cases.`);
67
- }
68
- const caseIds = engineCases.map((bundle) => bundle.id);
69
- return {
70
- dir,
71
- specPath: benchmarkPath,
72
- specSource,
73
- spec,
74
- benchmarkPath,
75
- benchmarkSource,
76
- candidateName: path.basename(candidateDir),
77
- candidateDir,
78
- candidateFilesPath,
79
- candidateSpecPath,
80
- candidateSource,
81
- candidateRunId: spec.candidate.selectedRunId,
82
- candidateRunIds: Object.keys(spec.candidate.runs).sort(),
83
- benchmarkAdapterSources: [...resolvedSource.benchmark.adapters],
84
- benchmarkAdapterIds,
85
- dockerfilePath,
86
- dockerfile,
87
- runtimeDockerfile: composedDockerfile,
88
- dockerfileFiles: toRemoteFiles(dockerfileSourceFiles(dockerfileSources)),
89
- candidateFiles: toRemoteFiles(candidateFiles),
90
- engineResolveFiles,
91
- adapters,
92
- adapterFiles: toRemoteFiles(adapterFiles),
93
- caseIds,
94
- engineCases,
95
- engineResolve: normalizedSources.engineResolve,
96
- engineResolveFingerprintPath: normalizedSources.engineResolveFingerprintPath,
97
- ...(normalizedSources.engineResolveEnvironment
98
- ? { engineResolveEnvironment: normalizedSources.engineResolveEnvironment }
99
- : {}),
100
- sourceFiles: buildWorkbenchProjectSourceFiles({
101
- specFiles: [
102
- textSourceFile(toRootRelativePath(dir, benchmarkPath), benchmarkSource),
103
- textSourceFile(toRootRelativePath(dir, candidateSpecPath), candidateSource),
104
- ],
105
- candidateFilesPath: spec.candidate.files.path,
106
- candidateFiles: candidateFiles,
107
- engineResolveFilesPath: normalizedSources.engineResolveFingerprintPath,
108
- engineResolveFiles: rawEngineResolveFiles,
109
- adapterFiles,
110
- dockerfiles: dockerfileSourceFiles(dockerfileSources),
111
- }),
112
- };
113
- }
114
- export async function readLocalAuthoredProjectSource(source, options = {}) {
115
- const { dir, benchmarkPath, candidateSpecPath, candidateDir, } = await resolveLocalProjectSourcePaths(source, options);
116
- const benchmarkSource = await readRequiredTextFile(benchmarkPath, WORKBENCH_BENCHMARK_FILE);
117
- const candidateSource = await readRequiredTextFile(candidateSpecPath, "candidate YAML");
118
- const resolvedSource = parseWorkbenchSourceFiles({
119
- benchmarkSource,
120
- candidateSource,
121
- runId: options.runId,
122
- });
123
- const specSource = serializeWorkbenchResolvedSourceYaml(resolvedSource);
124
- return {
125
- dir,
126
- specPath: benchmarkPath,
127
- specSource,
128
- benchmarkPath,
129
- benchmarkSource,
130
- candidateDir,
131
- candidateSpecPath,
132
- candidateSource,
133
- sourceFiles: [
134
- textSourceFile(toRootRelativePath(dir, benchmarkPath), benchmarkSource),
135
- textSourceFile(toRootRelativePath(dir, candidateSpecPath), candidateSource),
136
- ],
137
- };
138
- }
139
- export function remoteEngineResolveFiles(source) {
140
- return [
141
- ...source.engineResolveFiles,
142
- {
143
- path: WORKBENCH_ADAPTER_RESULT_FILE,
144
- content: `${JSON.stringify({
145
- protocol: WORKBENCH_ADAPTER_RESULT_PROTOCOL,
146
- operation: "engine.resolve",
147
- ok: true,
148
- value: {
149
- cases: source.engineCases,
150
- ...(source.engineResolveEnvironment
151
- ? { environment: source.engineResolveEnvironment }
152
- : {}),
153
- },
154
- feedback: {
155
- path: source.engineResolveFingerprintPath,
156
- },
157
- }, null, 2)}\n`,
158
- },
159
- ];
160
- }
161
- async function resolveLocalProjectSourcePaths(source, options) {
162
- const resolved = path.resolve(source);
163
- const stat = await fs.stat(resolved).catch(() => null);
164
- if (stat?.isFile()) {
165
- const sourceRecord = await readYamlRecordFile(resolved);
166
- if (isCandidateSourceRecord(sourceRecord)) {
167
- const candidateDir = path.dirname(resolved);
168
- const dir = projectRootForCandidateDir(candidateDir);
169
- return {
170
- dir,
171
- benchmarkPath: path.join(dir, WORKBENCH_BENCHMARK_FILE),
172
- candidateDir,
173
- candidateSpecPath: resolved,
174
- };
175
- }
176
- if (isBenchmarkSourceRecord(sourceRecord)) {
177
- const dir = path.dirname(resolved);
178
- const candidatePaths = await resolveCandidatePaths(dir);
179
- return {
180
- dir,
181
- benchmarkPath: resolved,
182
- ...candidatePaths,
183
- };
184
- }
185
- throw new WorkspaceSnapshotError(`Unsupported Workbench YAML source: ${resolved}`);
186
- }
187
- const dir = resolved;
188
- const directoryCandidate = await candidatePathsForCandidateDirectory(dir);
189
- if (directoryCandidate) {
190
- return directoryCandidate;
191
- }
192
- const candidatePaths = await resolveCandidatePaths(dir);
193
- return {
194
- dir,
195
- benchmarkPath: path.join(dir, WORKBENCH_BENCHMARK_FILE),
196
- ...candidatePaths,
197
- };
198
- }
199
- async function resolveCandidatePaths(dir) {
200
- const candidatesDir = path.join(dir, WORKBENCH_CANDIDATES_DIR);
201
- const candidates = await listCandidateManifestFiles(candidatesDir);
202
- if (candidates.length === 1) {
203
- const candidateSpecPath = candidates[0];
204
- const candidateDir = path.dirname(candidateSpecPath);
205
- return {
206
- candidateDir,
207
- candidateSpecPath,
208
- };
209
- }
210
- if (candidates.length > 1) {
211
- throw new WorkspaceSnapshotError(`Multiple candidate directories found under ${candidatesDir}; pass candidates/NAME or candidates/NAME/candidate.yaml explicitly.`);
212
- }
213
- throw new WorkspaceSnapshotError(`No candidate directories found under ${candidatesDir}; create candidates/NAME/candidate.yaml with files.path.`);
214
- }
215
- async function normalizeSourceYamlForExecution(args) {
216
- const benchmark = parseYamlRecord(args.benchmarkSource, args.benchmarkPath);
217
- const candidate = parseYamlRecord(args.candidateSource, args.candidateSpecPath);
218
- const benchmarkDir = path.dirname(args.benchmarkPath);
219
- const candidateDir = path.dirname(args.candidateSpecPath);
220
- normalizeAdapterSourcePaths(args.dir, benchmark, benchmarkDir);
221
- normalizeAdapterSourcePaths(args.dir, candidate, candidateDir);
222
- const engine = yamlRecord(benchmark.engine);
223
- if (!engine || typeof engine.use !== "string" || !engine.use.trim()) {
224
- throw new WorkspaceSnapshotError("benchmark.yaml engine must declare an adapter invocation with use.");
225
- }
226
- normalizeEngineForExecution(args.dir, benchmarkDir, candidateDir, benchmark, candidate);
227
- const authoredEngineResolve = engineResolveInvocationFromRecord(engine);
228
- const engineResolve = await resolveEngineResolveAdapter({
229
- root: args.dir,
230
- yamlDir: benchmarkDir,
231
- benchmark,
232
- value: authoredEngineResolve,
233
- });
234
- const engineResolveFingerprintPath = engineResolve.sourcePath
235
- ?? engineResolvePathForInvocation(args.dir, benchmarkDir, authoredEngineResolve);
236
- applyEngineResolveEnvironment(benchmark, engineResolve.environment);
237
- return {
238
- benchmarkSource: YAML.stringify(benchmark).trimEnd() + "\n",
239
- candidateSource: YAML.stringify(candidate).trimEnd() + "\n",
240
- engineResolveFingerprintPath,
241
- engineResolve: authoredEngineResolve,
242
- ...(engineResolve.environment ? { engineResolveEnvironment: engineResolve.environment } : {}),
243
- engineCases: engineResolve.engineCases,
244
- };
245
- }
246
- function normalizeEngineForExecution(root, benchmarkDir, candidateDir, benchmark, candidate) {
247
- const candidateFiles = yamlRecord(candidate.files);
248
- if (candidateFiles && typeof candidateFiles.path === "string") {
249
- candidateFiles.path = toRootRelativePath(root, resolveYamlReference(candidateDir, candidateFiles.path));
250
- candidate.files = candidateFiles;
251
- }
252
- const engine = yamlRecord(benchmark.engine);
253
- const engineConfig = yamlRecord(engine?.with) ?? {};
254
- const environment = yamlRecord(engineConfig.environment);
255
- if (environment && typeof environment.dockerfile === "string") {
256
- environment.dockerfile = toRootRelativePath(root, resolveYamlReference(benchmarkDir, environment.dockerfile));
257
- engineConfig.environment = environment;
258
- }
259
- if (engine) {
260
- engine.with = engineConfig;
261
- benchmark.engine = engine;
262
- }
263
- }
264
- function applyEngineResolveEnvironment(benchmark, environment) {
265
- if (!environment) {
266
- return;
267
- }
268
- const engine = yamlRecord(benchmark.engine);
269
- const engineConfig = yamlRecord(engine?.with) ?? {};
270
- if (!yamlRecord(engineConfig.environment)) {
271
- engineConfig.environment = environment;
272
- if (engine) {
273
- engine.with = engineConfig;
274
- benchmark.engine = engine;
275
- }
276
- }
277
- }
278
- function engineResolveInvocationFromRecord(record) {
279
- return {
280
- use: record.use,
281
- with: cloneJson((record.with ?? {})),
282
- ...(record.auth !== undefined ? { auth: cloneJson(record.auth) } : {}),
283
- };
284
- }
285
- function cloneJson(value) {
286
- return JSON.parse(JSON.stringify(value));
287
- }
288
- function engineResolvePathForInvocation(root, yamlDir, declaration) {
289
- void root;
290
- void yamlDir;
291
- return `engine-resolve/${safePathSegment(String(declaration.use))}`;
292
- }
293
- function normalizeAdapterSourcePaths(root, record, yamlDir) {
294
- if (!Array.isArray(record.adapters)) {
295
- return;
296
- }
297
- record.adapters = record.adapters.map((entry) => typeof entry === "string" && isPathAdapterSource(entry)
298
- ? toRootRelativePath(root, resolveYamlReference(yamlDir, entry))
299
- : entry);
300
- }
301
- async function resolveEngineResolveAdapter(args) {
302
- const record = yamlRecord(args.value);
303
- if (!record || typeof record.use !== "string") {
304
- throw new WorkspaceSnapshotError("benchmark.yaml engine must be an adapter invocation.");
305
- }
306
- const adapter = await resolveEngineResolveAdapterReference({
307
- root: args.root,
308
- benchmark: args.benchmark,
309
- adapterId: record.use,
310
- });
311
- await assertEngineResolveAdapterOperations({
312
- root: args.root,
313
- benchmark: args.benchmark,
314
- invocation: engineResolveInvocationFromRecord(record),
315
- });
316
- const digest = createHash("sha256")
317
- .update(JSON.stringify({
318
- adapter: adapter.contentHash,
319
- invocation: record,
320
- yamlDir: path.resolve(args.yamlDir),
321
- }))
322
- .digest("hex")
323
- .slice(0, 16);
324
- const generatedRoot = path.join(args.root, ".workbench", "generated", "engine-resolves", safePathSegment(record.use), `${digest}-${randomUUID().replace(/-/gu, "").slice(0, 12)}`);
325
- await fs.mkdir(generatedRoot, { recursive: true });
326
- const requestPath = path.join(generatedRoot, ".workbench", "request.json");
327
- await fs.mkdir(path.dirname(requestPath), { recursive: true });
328
- await fs.writeFile(requestPath, `${JSON.stringify({
329
- protocol: "workbench.adapter.v3",
330
- id: `engine_resolve_${digest}`,
331
- operation: "engine.resolve",
332
- invocation: {
333
- use: record.use,
334
- with: record.with ?? {},
335
- ...(record.auth !== undefined ? { auth: record.auth } : {}),
336
- },
337
- paths: {
338
- workspace: args.root,
339
- output: generatedRoot,
340
- result: workbenchAdapterOperationResultPath(generatedRoot),
341
- },
342
- }, null, 2)}\n`);
343
- await executeEngineResolveAdapter({
344
- adapter,
345
- requestPath,
346
- outputRoot: generatedRoot,
347
- workspaceRoot: args.root,
348
- });
349
- const result = await readEngineResolveResult(generatedRoot, record.use);
350
- const environment = normalizeEngineResolveEnvironment(args.root, result.value.environment);
351
- const sourcePath = engineResolveSourcePathFromFeedback(args.root, args.yamlDir, result.feedback);
352
- return {
353
- engineCases: normalizeEngineCaseEnvironments(args.root, result.value.cases),
354
- ...(sourcePath ? { sourcePath } : {}),
355
- ...(environment ? { environment } : {}),
356
- };
357
- }
358
- async function assertEngineResolveAdapterOperations(args) {
359
- const manifests = await resolveBenchmarkAdapterManifests(args.root, args.benchmark);
360
- try {
361
- assertWorkbenchAdapterOperationSupport([{ invocation: args.invocation, operation: "engine.resolve" }], manifests);
362
- }
363
- catch (error) {
364
- throw new WorkspaceSnapshotError(error instanceof Error ? error.message : String(error));
365
- }
366
- }
367
- async function resolveBenchmarkAdapterManifests(root, benchmark) {
368
- const manifests = new Map(defaultAdapterManifests().map((manifest) => [manifest.id, manifest]));
369
- const sources = Array.isArray(benchmark.adapters)
370
- ? benchmark.adapters.filter((entry) => typeof entry === "string")
371
- : [];
372
- for (const source of sources) {
373
- const adapter = await resolveProjectAdapterSource(root, source);
374
- manifests.set(adapter.manifest.id, adapter.manifest);
375
- }
376
- return [...manifests.values()];
377
- }
378
- async function resolveEngineResolveAdapterReference(args) {
379
- const sources = Array.isArray(args.benchmark.adapters)
380
- ? args.benchmark.adapters.filter((entry) => typeof entry === "string")
381
- : [];
382
- for (const source of sources) {
383
- const adapter = await resolveProjectAdapterSource(args.root, source);
384
- if (adapter.manifest.id === args.adapterId) {
385
- return adapter;
386
- }
387
- }
388
- const defaultAdapter = resolveDefaultWorkbenchAdapter(args.adapterId);
389
- if (defaultAdapter) {
390
- return defaultAdapter;
391
- }
392
- throw new WorkspaceSnapshotError(`Workbench engine resolver adapter ${args.adapterId} is not installed. Add its source under benchmark.yaml adapters.`);
393
- }
394
- async function executeEngineResolveAdapter(args) {
395
- const cwd = args.adapter.root && await directoryExists(args.adapter.root)
396
- ? args.adapter.root
397
- : args.adapter.kind === "default"
398
- ? args.workspaceRoot
399
- : await materializeEngineResolveAdapterFiles(args.outputRoot, args.adapter);
400
- await runEngineResolveAdapterCommand({
401
- command: workbenchAdapterOperationCommand(args.adapter.manifest, "engine.resolve"),
402
- cwd,
403
- requestPath: args.requestPath,
404
- outputRoot: args.outputRoot,
405
- workspaceRoot: args.workspaceRoot,
406
- });
407
- }
408
- async function materializeEngineResolveAdapterFiles(outputRoot, adapter) {
409
- const adapterRoot = path.join(outputRoot, ".workbench", "adapter");
410
- await fs.rm(adapterRoot, { recursive: true, force: true }).catch(() => undefined);
411
- await fs.mkdir(adapterRoot, { recursive: true });
412
- for (const file of adapter.files ?? []) {
413
- const target = path.join(adapterRoot, file.path);
414
- await fs.mkdir(path.dirname(target), { recursive: true });
415
- await fs.writeFile(target, file.content);
416
- if (file.executable) {
417
- await fs.chmod(target, 0o755).catch(() => undefined);
418
- }
419
- }
420
- return adapterRoot;
421
- }
422
- async function runEngineResolveAdapterCommand(args) {
423
- const adapterRoot = await fs.realpath(args.cwd).catch(() => args.cwd);
424
- await new Promise((resolve, reject) => {
425
- const child = spawn("sh", ["-c", args.command], {
426
- cwd: args.cwd,
427
- env: createAdapterCommandEnv({
428
- workspaceRoot: args.workspaceRoot,
429
- adapterRoot,
430
- extraEnv: {
431
- WORKBENCH_ADAPTER_REQUEST: args.requestPath,
432
- WORKBENCH_OUTPUT: args.outputRoot,
433
- WORKBENCH_RESULT: workbenchAdapterOperationResultPath(args.outputRoot),
434
- WORKBENCH_WORKSPACE_ROOT: args.workspaceRoot,
435
- WORKBENCH_ADAPTER_ROOT: adapterRoot,
436
- },
437
- }),
438
- stdio: ["ignore", "pipe", "pipe"],
439
- });
440
- const stdout = [];
441
- const stderr = [];
442
- child.stdout?.on("data", (chunk) => stdout.push(chunk));
443
- child.stderr?.on("data", (chunk) => stderr.push(chunk));
444
- child.on("error", reject);
445
- child.on("exit", (code, signal) => {
446
- if (code === 0) {
447
- resolve();
448
- return;
449
- }
450
- const detail = [
451
- Buffer.concat(stderr).toString("utf8").trim(),
452
- Buffer.concat(stdout).toString("utf8").trim(),
453
- ].filter(Boolean).join("\n");
454
- reject(new WorkspaceSnapshotError([
455
- signal
456
- ? `Workbench engine resolver adapter command exited from signal ${signal}.`
457
- : `Workbench engine resolver adapter command exited with status ${code ?? "unknown"}.`,
458
- detail,
459
- ].filter(Boolean).join("\n")));
460
- });
461
- });
462
- }
463
- async function readEngineResolveResult(outputRoot, adapterId) {
464
- return await readWorkbenchAdapterOperationResult(outputRoot, "engine.resolve").then((result) => {
465
- assertWorkbenchAdapterOperationResultOk(result, `Adapter ${adapterId} engine.resolve`);
466
- if (!result.value || typeof result.value !== "object" || Array.isArray(result.value)) {
467
- throw new WorkspaceSnapshotError(`Adapter ${adapterId} engine.resolve did not return engine cases.`);
468
- }
469
- return {
470
- value: result.value,
471
- ...(result.feedback !== undefined ? { feedback: result.feedback } : {}),
472
- };
473
- }).catch((error) => {
474
- if (error.code === "ENOENT") {
475
- throw new WorkspaceSnapshotError(`Adapter ${adapterId} must write workbench-result.json for engine.resolve.`);
476
- }
477
- throw new WorkspaceSnapshotError(error instanceof Error ? error.message : String(error));
478
- });
479
- }
480
- function engineResolveSourcePathFromFeedback(root, yamlDir, feedback) {
481
- const record = yamlRecord(feedback);
482
- if (!record || typeof record.path !== "string" || record.path.trim().length === 0) {
483
- return undefined;
484
- }
485
- const absolute = resolveYamlReference(yamlDir, record.path);
486
- const relative = path.relative(root, absolute);
487
- if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) {
488
- return undefined;
489
- }
490
- return normalizeSnapshotPath(relative);
491
- }
492
- function normalizeEngineResolveEnvironment(root, environment) {
493
- if (!environment) {
494
- return undefined;
495
- }
496
- return {
497
- ...environment,
498
- ...(environment.dockerfile
499
- ? {
500
- dockerfile: toRootRelativePath(root, resolveProjectPath(root, environment.dockerfile)),
501
- }
502
- : {}),
503
- };
504
- }
505
- function normalizeEngineCaseEnvironments(root, engineCases) {
506
- return engineCases.map((engineCase) => {
507
- const environment = normalizeEngineResolveEnvironment(root, engineCase.case.environment);
508
- return {
509
- ...engineCase,
510
- case: {
511
- ...engineCase.case,
512
- ...(environment ? { environment } : {}),
513
- },
514
- };
515
- });
516
- }
517
- function engineResolveFilesFromBundles(engineCases) {
518
- return normalizeSurfaceFiles(engineCases.flatMap((bundle) => {
519
- const buckets = bundle.files;
520
- const files = buckets.source?.length
521
- ? buckets.source
522
- : [...(buckets.public ?? []), ...(buckets.private ?? [])];
523
- return files.map((file) => ({
524
- ...file,
525
- path: normalizeSnapshotPath(`${bundle.id}/${file.path}`),
526
- }));
527
- }));
528
- }
529
- function safePathSegment(value) {
530
- return value.replace(/[^a-zA-Z0-9._-]+/gu, "_").replace(/^_+|_+$/gu, "") || "adapter";
531
- }
532
- function isPathAdapterSource(source) {
533
- return !/^(?:npm|git):/iu.test(source.trim());
534
- }
535
- function isBenchmarkSourceRecord(record) {
536
- return record.engine !== undefined;
537
- }
538
- function isCandidateSourceRecord(record) {
539
- return record.runs !== undefined;
540
- }
541
- async function readYamlRecordFile(filePath) {
542
- return parseYamlRecord(await readRequiredTextFile(filePath, path.basename(filePath)), filePath);
543
- }
544
- async function candidatePathsForCandidateDirectory(sourceDir) {
545
- const candidateSpecPath = path.join(sourceDir, WORKBENCH_CANDIDATE_FILE);
546
- if (!(await fileExists(candidateSpecPath))) {
547
- return null;
548
- }
549
- const dir = projectRootForCandidateDir(sourceDir);
550
- return {
551
- dir,
552
- benchmarkPath: path.join(dir, WORKBENCH_BENCHMARK_FILE),
553
- candidateDir: sourceDir,
554
- candidateSpecPath,
555
- };
556
- }
557
- function projectRootForCandidateDir(candidateDir) {
558
- const parent = path.basename(path.dirname(candidateDir));
559
- if (parent !== WORKBENCH_CANDIDATES_DIR) {
560
- throw new WorkspaceSnapshotError(`Candidate directory must be under ${WORKBENCH_CANDIDATES_DIR}/NAME: ${candidateDir}`);
561
- }
562
- return path.dirname(path.dirname(candidateDir));
563
- }
564
- function parseYamlRecord(source, label) {
565
- const parsed = YAML.parse(source);
566
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
567
- throw new WorkspaceSnapshotError(`${label} must be a YAML object.`);
568
- }
569
- return parsed;
570
- }
571
- function yamlRecord(value) {
572
- return value && typeof value === "object" && !Array.isArray(value)
573
- ? value
574
- : null;
575
- }
576
- function resolveYamlReference(baseDir, value) {
577
- return path.isAbsolute(value) ? path.normalize(value) : path.resolve(baseDir, value);
578
- }
579
- function toRootRelativePath(root, absolutePath) {
580
- const relative = path.relative(root, absolutePath);
581
- if (!relative || relative === "") {
582
- return ".";
583
- }
584
- if (relative.startsWith("..") || path.isAbsolute(relative)) {
585
- return path.normalize(absolutePath);
586
- }
587
- return normalizeSnapshotPath(relative);
588
- }
589
- function normalizeSnapshotPath(filePath) {
590
- return filePath.split(path.sep).join("/");
591
- }
592
- async function fileExists(filePath) {
593
- return await fs.stat(filePath).then((stat) => stat.isFile(), () => false);
594
- }
595
- async function directoryExists(filePath) {
596
- return await fs.stat(filePath).then((stat) => stat.isDirectory(), () => false);
597
- }
598
- async function listCandidateManifestFiles(dir) {
599
- const entries = await fs.readdir(dir, { withFileTypes: true }).catch((error) => {
600
- if (error.code === "ENOENT") {
601
- return [];
602
- }
603
- throw error;
604
- });
605
- const manifests = await Promise.all(entries
606
- .filter((entry) => entry.isDirectory())
607
- .map(async (entry) => {
608
- const manifestPath = path.join(dir, entry.name, WORKBENCH_CANDIDATE_FILE);
609
- return await fileExists(manifestPath) ? manifestPath : null;
610
- }));
611
- return manifests
612
- .filter((entry) => Boolean(entry))
613
- .sort((left, right) => left.localeCompare(right));
614
- }
615
- async function readRequiredTextFile(filePath, label) {
616
- return await fs.readFile(filePath, "utf8").catch((error) => {
617
- if (error.code === "ENOENT") {
618
- throw new WorkspaceSnapshotError(`${label} not found: ${filePath}`);
619
- }
620
- throw error;
621
- });
622
- }
623
- async function readDockerfileSourcesForSpec(dir, spec, engineCases = []) {
624
- const dockerfilePaths = new Set([spec.environment.dockerfile]);
625
- for (const engineCase of engineCases) {
626
- const dockerfile = engineCase.case.environment?.dockerfile;
627
- if (dockerfile) {
628
- dockerfilePaths.add(dockerfile);
629
- }
630
- }
631
- const sources = new Map();
632
- for (const dockerfilePath of [...dockerfilePaths].sort()) {
633
- const absoluteDockerfilePath = resolveProjectPath(dir, dockerfilePath);
634
- const source = await fs.readFile(absoluteDockerfilePath, "utf8").catch((error) => {
635
- if (error.code === "ENOENT") {
636
- throw new WorkspaceSnapshotError(`Dockerfile not found: ${absoluteDockerfilePath}`);
637
- }
638
- throw error;
639
- });
640
- sources.set(dockerfilePath, source);
641
- }
642
- return sources;
643
- }
644
- function resolveProjectPath(root, filePath) {
645
- return path.isAbsolute(filePath) ? filePath : path.join(root, filePath);
646
- }
647
- function dockerfileSourceFiles(sources) {
648
- return [...sources.entries()].map(([filePath, content]) => textSourceFile(filePath, content));
649
- }
650
- function textSourceFile(filePath, content) {
651
- return {
652
- path: filePath,
653
- kind: "text",
654
- encoding: "utf8",
655
- content,
656
- executable: false,
657
- };
658
- }
659
- function adapterSourceFiles(adapters) {
660
- return adapters.flatMap((adapter) => adapter.kind === "default"
661
- ? []
662
- : (adapter.files ?? []).map((file) => ({
663
- path: adapterSourceSnapshotPath(adapter, file.path),
664
- kind: "text",
665
- encoding: "utf8",
666
- content: file.content,
667
- executable: file.executable,
668
- })));
669
- }
670
- function adapterSourceSnapshotPath(adapter, filePath) {
671
- return adapter.kind === "path"
672
- ? `${adapter.source}/${filePath}`
673
- : `adapters/${adapter.manifest.id}/${filePath}`;
674
- }
675
- function toRemoteFiles(files) {
676
- return files.map((file) => ({
677
- path: file.path,
678
- content: file.content,
679
- ...(file.encoding === "base64" ? { encoding: file.encoding } : {}),
680
- ...(file.executable ? { executable: true } : {}),
681
- }));
682
- }
@@ -1,10 +0,0 @@
1
- export interface WorkspaceSnapshotFile {
2
- path: string;
3
- content: string;
4
- encoding?: "utf8" | "base64";
5
- executable?: boolean;
6
- }
7
- export declare class WorkspaceSnapshotError extends Error {
8
- }
9
- export declare function readSnapshotFiles(inputPath: string): Promise<WorkspaceSnapshotFile[]>;
10
- //# sourceMappingURL=workspace-snapshot.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"workspace-snapshot.d.ts","sourceRoot":"","sources":["../src/workspace-snapshot.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC7B,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,qBAAa,sBAAuB,SAAQ,KAAK;CAAG;AAapD,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAgB3F"}