@webpresso/agent-kit 0.25.0 → 0.26.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.
Files changed (38) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/dist/esm/audit/toolchain-isolation.d.ts +3 -0
  4. package/dist/esm/audit/toolchain-isolation.js +134 -0
  5. package/dist/esm/cli/cli.d.ts +1 -1
  6. package/dist/esm/cli/cli.js +6 -0
  7. package/dist/esm/cli/commands/audit-core.d.ts +1 -1
  8. package/dist/esm/cli/commands/audit.js +1 -0
  9. package/dist/esm/cli/commands/deploy.d.ts +4 -0
  10. package/dist/esm/cli/commands/deploy.js +35 -0
  11. package/dist/esm/cli/commands/init/gitignore-patcher.d.ts +17 -0
  12. package/dist/esm/cli/commands/init/gitignore-patcher.js +42 -0
  13. package/dist/esm/cli/commands/init/index.js +11 -1
  14. package/dist/esm/cli/commands/init/scaffold-base-kit.js +44 -17
  15. package/dist/esm/deploy/index.d.ts +5 -0
  16. package/dist/esm/deploy/index.js +5 -0
  17. package/dist/esm/deploy/load-adapter.d.ts +6 -0
  18. package/dist/esm/deploy/load-adapter.js +57 -0
  19. package/dist/esm/deploy/run.d.ts +12 -0
  20. package/dist/esm/deploy/run.js +52 -0
  21. package/dist/esm/deploy/schema.d.ts +6 -0
  22. package/dist/esm/deploy/schema.js +61 -0
  23. package/dist/esm/deploy/types.d.ts +43 -0
  24. package/dist/esm/deploy/types.js +2 -0
  25. package/dist/esm/e2e/command-builder.js +37 -18
  26. package/dist/esm/e2e/config.d.ts +2 -0
  27. package/dist/esm/e2e/config.js +6 -1
  28. package/dist/esm/hooks/pretool-guard/dev-routing.d.ts +6 -0
  29. package/dist/esm/hooks/pretool-guard/dev-routing.js +3 -2
  30. package/dist/esm/hooks/pretool-guard/runner.js +11 -6
  31. package/dist/esm/mcp/tools/_shared/runner-failure.d.ts +30 -0
  32. package/dist/esm/mcp/tools/_shared/runner-failure.js +45 -0
  33. package/dist/esm/mcp/tools/typecheck.js +20 -3
  34. package/dist/esm/package.json +3 -1
  35. package/dist/esm/test/command-builder.d.ts +1 -0
  36. package/dist/esm/test/command-builder.js +9 -5
  37. package/dist/esm/tool-runtime/resolve-runner.js +38 -10
  38. package/package.json +32 -18
@@ -0,0 +1,61 @@
1
+ import { z } from 'zod';
2
+ export const DEPLOY_PLAN_SCHEMA_VERSION = 1;
3
+ const deployLaneSchema = z.union([
4
+ z.literal('dev'),
5
+ z.literal('preview_main'),
6
+ z.literal('prd'),
7
+ z.string().regex(/^preview_pr_\d+$/u, 'preview PR lanes must be preview_pr_<n>'),
8
+ ]);
9
+ const envSchema = z.record(z.string(), z.string().optional());
10
+ const commandStepSchema = z
11
+ .object({
12
+ kind: z.literal('command'),
13
+ id: z.string().min(1),
14
+ label: z.string().min(1).optional(),
15
+ command: z.string().min(1),
16
+ args: z.array(z.string()).optional(),
17
+ cwd: z.string().min(1).optional(),
18
+ env: envSchema.optional(),
19
+ })
20
+ .strict();
21
+ const managedToolStepSchema = z
22
+ .object({
23
+ kind: z.literal('managed-tool'),
24
+ id: z.string().min(1),
25
+ label: z.string().min(1).optional(),
26
+ tool: z.string().min(1),
27
+ args: z.array(z.string()).optional(),
28
+ cwd: z.string().min(1).optional(),
29
+ env: envSchema.optional(),
30
+ })
31
+ .strict();
32
+ const deployPlanSchema = z
33
+ .object({
34
+ schemaVersion: z.literal(DEPLOY_PLAN_SCHEMA_VERSION),
35
+ lane: deployLaneSchema,
36
+ provider: z.string().min(1),
37
+ requiredCredentials: z.array(z.string().min(1)),
38
+ steps: z.array(z.union([commandStepSchema, managedToolStepSchema])),
39
+ })
40
+ .strict();
41
+ export function isDeployLane(value) {
42
+ return deployLaneSchema.safeParse(value).success;
43
+ }
44
+ export function parseDeployLane(value) {
45
+ const result = deployLaneSchema.safeParse(value);
46
+ if (!result.success) {
47
+ throw new Error(`Invalid deploy lane "${value}". Use dev, preview_main, preview_pr_<n>, or prd.`);
48
+ }
49
+ return result.data;
50
+ }
51
+ export function validateDeployPlan(plan) {
52
+ const result = deployPlanSchema.safeParse(plan);
53
+ if (!result.success) {
54
+ const issues = result.error.issues
55
+ .map((issue) => ` - ${issue.path.join('.') || '<root>'}: ${issue.message}`)
56
+ .join('\n');
57
+ throw new Error(`Invalid deploy plan:\n${issues}`);
58
+ }
59
+ return result.data;
60
+ }
61
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1,43 @@
1
+ export type DeployLane = 'dev' | 'preview_main' | `preview_pr_${number}` | 'prd';
2
+ export type DeployStep = {
3
+ readonly kind: 'command';
4
+ readonly id: string;
5
+ readonly label?: string;
6
+ readonly command: string;
7
+ readonly args?: readonly string[];
8
+ readonly cwd?: string;
9
+ readonly env?: Readonly<Record<string, string | undefined>>;
10
+ } | {
11
+ readonly kind: 'managed-tool';
12
+ readonly id: string;
13
+ readonly label?: string;
14
+ readonly tool: string;
15
+ readonly args?: readonly string[];
16
+ readonly cwd?: string;
17
+ readonly env?: Readonly<Record<string, string | undefined>>;
18
+ };
19
+ export interface DeployPlan {
20
+ readonly schemaVersion: 1;
21
+ readonly lane: DeployLane;
22
+ readonly provider: string;
23
+ readonly requiredCredentials: readonly string[];
24
+ readonly steps: readonly DeployStep[];
25
+ }
26
+ export interface DeployRequest {
27
+ readonly cwd: string;
28
+ readonly lane: DeployLane;
29
+ readonly dryRun: boolean;
30
+ readonly env: NodeJS.ProcessEnv;
31
+ readonly cloudflare?: unknown;
32
+ }
33
+ export interface DeployAdapter {
34
+ readonly createPlan: (request: DeployRequest) => DeployPlan | Promise<DeployPlan>;
35
+ }
36
+ export interface LoadedDeployAdapter {
37
+ readonly adapter: DeployAdapter;
38
+ readonly config: import('#e2e/config.js').WebpressoConfig;
39
+ readonly configPath: string;
40
+ readonly moduleSpecifier: string;
41
+ readonly exportName: string;
42
+ }
43
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -15,13 +15,18 @@ function buildPlaywrightCommand(options) {
15
15
  if (!step.configPath) {
16
16
  throw new Error(`Step ${step.logName} uses runner "playwright" but does not define configPath.`);
17
17
  }
18
- const { baseDir, configArg, files } = resolveRunnerPaths(step.configPath, options.files ?? []);
18
+ const paths = resolveRunnerPaths(step.configPath, options.files ?? []);
19
19
  const resolution = withBaseDir(getManagedRunner('playwright', {
20
20
  outputPolicy: resolveOutputPolicy(options.outputPolicy, options.filterOutput),
21
- }), baseDir);
22
- const args = [...resolution.args, 'test', '--config', configArg];
21
+ }), paths.baseDir);
22
+ const args = [
23
+ ...resolution.args,
24
+ 'test',
25
+ '--config',
26
+ resolution.usesBaseDir ? paths.relativeConfigArg : paths.rootConfigArg,
27
+ ];
23
28
  appendPlaywrightFlags(args, options);
24
- args.push(...(step.fixedArgs ?? []), ...files, ...(options.passthrough ?? []));
29
+ args.push(...(step.fixedArgs ?? []), ...(resolution.usesBaseDir ? paths.relativeFiles : paths.rootFiles), ...(options.passthrough ?? []));
25
30
  return { command: resolution.command, args };
26
31
  }
27
32
  function buildVitestE2eCommand(options) {
@@ -29,15 +34,20 @@ function buildVitestE2eCommand(options) {
29
34
  if (!step.configPath) {
30
35
  throw new Error(`Step ${step.logName} uses runner "vitest" but does not define configPath.`);
31
36
  }
32
- const { baseDir, configArg, files } = resolveRunnerPaths(step.configPath, options.files ?? []);
37
+ const paths = resolveRunnerPaths(step.configPath, options.files ?? []);
33
38
  const resolution = withBaseDir(getManagedRunner('vitest', {
34
39
  outputPolicy: resolveOutputPolicy(options.outputPolicy, options.filterOutput),
35
- }), baseDir);
36
- const args = [...resolution.args, 'run', '--config', configArg];
40
+ }), paths.baseDir);
41
+ const args = [
42
+ ...resolution.args,
43
+ 'run',
44
+ '--config',
45
+ resolution.usesBaseDir ? paths.relativeConfigArg : paths.rootConfigArg,
46
+ ];
37
47
  if (options.workers !== undefined) {
38
48
  args.push('--poolOptions.threads.maxThreads', String(options.workers));
39
49
  }
40
- args.push(...(step.fixedArgs ?? []), ...files, ...(options.passthrough ?? []));
50
+ args.push(...(step.fixedArgs ?? []), ...(resolution.usesBaseDir ? paths.relativeFiles : paths.rootFiles), ...(options.passthrough ?? []));
41
51
  return { command: resolution.command, args };
42
52
  }
43
53
  function buildCustomCommand(options) {
@@ -79,19 +89,27 @@ function appendPlaywrightFlags(args, options) {
79
89
  function resolveRunnerPaths(configPath, files) {
80
90
  const normalizedConfigPath = configPath.replace(/\\/gu, '/');
81
91
  const baseDir = path.posix.dirname(normalizedConfigPath);
82
- const configArg = path.posix.basename(normalizedConfigPath);
92
+ const normalizedFiles = files.map((file) => file.replace(/\\/gu, '/'));
83
93
  if (baseDir === '.') {
84
94
  return {
85
95
  baseDir,
86
- configArg: normalizedConfigPath,
87
- files: [...files],
96
+ rootConfigArg: normalizedConfigPath,
97
+ rootFiles: normalizedFiles,
98
+ relativeConfigArg: normalizedConfigPath,
99
+ relativeFiles: normalizedFiles,
88
100
  };
89
101
  }
90
102
  return {
91
103
  baseDir,
92
- configArg,
93
- files: files.map((file) => {
94
- const normalizedFile = file.replace(/\\/gu, '/');
104
+ rootConfigArg: normalizedConfigPath,
105
+ rootFiles: normalizedFiles.map((normalizedFile) => {
106
+ if (path.posix.isAbsolute(normalizedFile) || normalizedFile.startsWith(`${baseDir}/`)) {
107
+ return normalizedFile;
108
+ }
109
+ return `${baseDir}/${normalizedFile}`;
110
+ }),
111
+ relativeConfigArg: path.posix.basename(normalizedConfigPath),
112
+ relativeFiles: normalizedFiles.map((normalizedFile) => {
95
113
  if (path.posix.isAbsolute(normalizedFile) || normalizedFile.startsWith(`${baseDir}/`)) {
96
114
  return path.posix.relative(baseDir, normalizedFile);
97
115
  }
@@ -100,13 +118,13 @@ function resolveRunnerPaths(configPath, files) {
100
118
  };
101
119
  }
102
120
  function withBaseDir(resolution, baseDir) {
103
- if (baseDir === '.') {
104
- return { command: resolution.command, args: [...resolution.args] };
105
- }
121
+ if (baseDir === '.')
122
+ return { command: resolution.command, args: [...resolution.args], usesBaseDir: true };
106
123
  if (resolution.command === 'vp') {
107
124
  return {
108
125
  command: resolution.command,
109
126
  args: ['--dir', baseDir, ...resolution.args],
127
+ usesBaseDir: true,
110
128
  };
111
129
  }
112
130
  const [wrappedCommand, ...wrappedArgs] = resolution.args;
@@ -114,9 +132,10 @@ function withBaseDir(resolution, baseDir) {
114
132
  return {
115
133
  command: resolution.command,
116
134
  args: ['vp', '--dir', baseDir, ...wrappedArgs],
135
+ usesBaseDir: true,
117
136
  };
118
137
  }
119
- return { command: resolution.command, args: [...resolution.args] };
138
+ return { command: resolution.command, args: [...resolution.args], usesBaseDir: false };
120
139
  }
121
140
  function resolveOutputPolicy(outputPolicy, filterOutput) {
122
141
  if (outputPolicy)
@@ -16,6 +16,8 @@ declare const webpressoConfigSchema: z.ZodObject<{
16
16
  hostAdapterExport: z.ZodOptional<z.ZodString>;
17
17
  }, z.core.$strict>>;
18
18
  deploy: z.ZodOptional<z.ZodObject<{
19
+ adapterModule: z.ZodOptional<z.ZodString>;
20
+ adapterExport: z.ZodOptional<z.ZodString>;
19
21
  cloudflare: z.ZodOptional<z.ZodObject<{
20
22
  lanes: z.ZodObject<{
21
23
  dev: z.ZodObject<{
@@ -51,7 +51,10 @@ const cloudflareDurableObjectBindingSchema = z
51
51
  .object({
52
52
  name: z.string().min(1, 'durableObjectBindings[].name must not be empty.'),
53
53
  className: z.string().min(1, 'durableObjectBindings[].className must not be empty.'),
54
- scriptName: z.string().min(1, 'durableObjectBindings[].scriptName must not be empty.').optional(),
54
+ scriptName: z
55
+ .string()
56
+ .min(1, 'durableObjectBindings[].scriptName must not be empty.')
57
+ .optional(),
55
58
  })
56
59
  .strict();
57
60
  const cloudflareTargetSchema = z
@@ -129,6 +132,8 @@ const cloudflareDeployConfigSchema = z
129
132
  .strict();
130
133
  const deployWebpressoConfigSchema = z
131
134
  .object({
135
+ adapterModule: z.string().min(1, 'deploy.adapterModule must not be empty.').optional(),
136
+ adapterExport: z.string().min(1, 'deploy.adapterExport must not be empty.').optional(),
132
137
  cloudflare: cloudflareDeployConfigSchema.optional(),
133
138
  })
134
139
  .strict();
@@ -15,7 +15,13 @@ export interface RouteDecision {
15
15
  export declare function normalizeCommandForRouting(command: string): string;
16
16
  export declare function extractRoutableCommandsFromToolInput(input: {
17
17
  tool_name?: string;
18
+ toolName?: string;
19
+ tool?: string;
20
+ name?: string;
18
21
  tool_input?: Record<string, unknown>;
22
+ toolInput?: Record<string, unknown>;
23
+ input?: Record<string, unknown>;
24
+ arguments?: Record<string, unknown>;
19
25
  }): string[];
20
26
  export declare function routeCommand(command: string, _sessionId?: string): RouteDecision | null;
21
27
  //# sourceMappingURL=dev-routing.d.ts.map
@@ -585,9 +585,10 @@ function isContextModeTool(toolName) {
585
585
  return /(?:^|[._-]|__)ctx_(?:batch_)?execute$/u.test(toolName);
586
586
  }
587
587
  export function extractRoutableCommandsFromToolInput(input) {
588
- if (!isContextModeTool(input.tool_name))
588
+ const toolName = input.tool_name ?? input.toolName ?? input.tool ?? input.name;
589
+ if (!isContextModeTool(toolName))
589
590
  return [];
590
- const toolInput = input.tool_input;
591
+ const toolInput = input.tool_input ?? input.toolInput ?? input.input ?? input.arguments;
591
592
  if (!toolInput || typeof toolInput !== 'object')
592
593
  return [];
593
594
  const commands = [];
@@ -101,23 +101,28 @@ export function processValidation(inputJson) {
101
101
  const input = parseToolInput(inputJson);
102
102
  const command = isBashInput(input) ? getCommand(input) : null;
103
103
  const routableCommands = [
104
- ...(command ? [command] : []),
105
- ...extractRoutableCommandsFromToolInput(input),
104
+ ...(command ? [{ command, alreadySandboxed: false }] : []),
105
+ ...extractRoutableCommandsFromToolInput(input).map((routedCommand) => ({
106
+ command: routedCommand,
107
+ alreadySandboxed: true,
108
+ })),
106
109
  ];
107
110
  for (const routedCommand of routableCommands) {
108
- const decision = routeCommand(routedCommand);
111
+ const decision = routeCommand(routedCommand.command);
109
112
  if (decision !== null) {
110
113
  if (decision.action.action === 'deny') {
111
114
  // Phase 1: Dev-workflow routing — always authoritative (MCP-first)
112
115
  writeDenyDecision(decision.action.guidance);
113
116
  process.exit(0);
114
117
  }
115
- else if (decision.action.action === 'sandbox') {
116
- // Phase 2: Context-mode sandbox routing — always fires
118
+ else if (decision.action.action === 'sandbox' && !routedCommand.alreadySandboxed) {
119
+ // Phase 2: Context-mode sandbox routing — fire only for raw tool calls.
120
+ // Commands already inside ctx_execute/ctx_batch_execute are already in
121
+ // the requested sandbox; re-denying them creates a ctx_execute loop.
117
122
  writeDenyDecision(decision.action.guidance);
118
123
  process.exit(0);
119
124
  }
120
- // 'passthrough' fall through to Phase 3
125
+ // 'passthrough' or already-sandboxed sandbox redirects Phase 3
121
126
  }
122
127
  }
123
128
  // Phase 3: Security validators (existing pipeline)
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Shared runner-failure bounding for the check tools (`wp_typecheck`, `wp_lint`,
3
+ * `wp_test`).
4
+ *
5
+ * A check that exits non-zero but yields ZERO parseable structured results is a
6
+ * runner/launcher failure — e.g. a missing `vp`/`tsc`/test-runner binary
7
+ * printing a Node `MODULE_NOT_FOUND` stack — not a genuine type/lint/test
8
+ * failure. Left alone, that raw output falls through each tool's transform to
9
+ * the generic passthrough (clipped only at 4000 chars), so it can become the
10
+ * leaf's measured `bytes` and blow the compact QA evidence budget (≤800/leaf),
11
+ * shipping an unbounded blob masquerading as tool output.
12
+ *
13
+ * `isRunnerFailure` detects the case from a count the caller already has (tsc
14
+ * errors / oxlint issues / parsed test failures), so it never misclassifies a
15
+ * real failure that produced structured results. `boundRunnerFailureEvidence`
16
+ * clips the evidence well under the budget and persists the full output to a log
17
+ * (truthful, not dropped). The caller keeps `passed: false` so the failure stays
18
+ * loud.
19
+ */
20
+ import type { TransformResult } from '#output-transforms/index';
21
+ export declare function stripTransform(result: TransformResult): Omit<TransformResult, 'transform'>;
22
+ export declare function isRunnerFailure(input: {
23
+ passed: boolean;
24
+ timedOut: boolean;
25
+ aborted: boolean;
26
+ parsedCount: number;
27
+ output: string;
28
+ }): boolean;
29
+ export declare function boundRunnerFailureEvidence(output: string, toolName: string): Omit<TransformResult, 'transform'>;
30
+ //# sourceMappingURL=runner-failure.d.ts.map
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Shared runner-failure bounding for the check tools (`wp_typecheck`, `wp_lint`,
3
+ * `wp_test`).
4
+ *
5
+ * A check that exits non-zero but yields ZERO parseable structured results is a
6
+ * runner/launcher failure — e.g. a missing `vp`/`tsc`/test-runner binary
7
+ * printing a Node `MODULE_NOT_FOUND` stack — not a genuine type/lint/test
8
+ * failure. Left alone, that raw output falls through each tool's transform to
9
+ * the generic passthrough (clipped only at 4000 chars), so it can become the
10
+ * leaf's measured `bytes` and blow the compact QA evidence budget (≤800/leaf),
11
+ * shipping an unbounded blob masquerading as tool output.
12
+ *
13
+ * `isRunnerFailure` detects the case from a count the caller already has (tsc
14
+ * errors / oxlint issues / parsed test failures), so it never misclassifies a
15
+ * real failure that produced structured results. `boundRunnerFailureEvidence`
16
+ * clips the evidence well under the budget and persists the full output to a log
17
+ * (truthful, not dropped). The caller keeps `passed: false` so the failure stays
18
+ * loud.
19
+ */
20
+ import { clipRawOutput } from './result.js';
21
+ const RUNNER_FAILURE_EVIDENCE_BUDGET = 600;
22
+ export function stripTransform(result) {
23
+ const { transform: _transform, ...rest } = result;
24
+ return rest;
25
+ }
26
+ export function isRunnerFailure(input) {
27
+ return (!input.passed &&
28
+ !input.timedOut &&
29
+ !input.aborted &&
30
+ input.parsedCount === 0 &&
31
+ input.output.trim().length > 0);
32
+ }
33
+ export function boundRunnerFailureEvidence(output, toolName) {
34
+ const clipped = clipRawOutput(output, RUNNER_FAILURE_EVIDENCE_BUDGET, { toolName });
35
+ const rawBytes = Buffer.byteLength(output);
36
+ const bytes = Buffer.byteLength(clipped.rawOutput ?? '');
37
+ return {
38
+ ...clipped,
39
+ failures: [],
40
+ tier: 3,
41
+ bytes,
42
+ tokensSaved: Math.max(0, rawBytes - bytes),
43
+ };
44
+ }
45
+ //# sourceMappingURL=runner-failure.js.map
@@ -17,6 +17,7 @@ import { applyOutputTransform } from '#output-transforms/index';
17
17
  import { getManagedRunner } from '#tool-runtime';
18
18
  import { resolveProjectRoot } from './_shared/project-root.js';
19
19
  import { createSummaryOutputSchema, createSummaryResult } from './_shared/result.js';
20
+ import { boundRunnerFailureEvidence, isRunnerFailure, stripTransform, } from './_shared/runner-failure.js';
20
21
  import { isRunFailure, runCommand } from './_shared/run-command.js';
21
22
  const inputSchema = z.object({
22
23
  cwd: z.string().optional(),
@@ -115,6 +116,8 @@ function summarizeTypecheckResult(options) {
115
116
  return 'typecheck timed out';
116
117
  if (options.aborted)
117
118
  return 'typecheck aborted';
119
+ if (options.failedWithoutDiagnostics)
120
+ return 'typecheck failed to run (no diagnostics parsed)';
118
121
  if (options.passed)
119
122
  return 'typecheck passed';
120
123
  return `typecheck failed with ${options.errorCount} error${options.errorCount === 1 ? '' : 's'}`;
@@ -169,12 +172,26 @@ const tool = {
169
172
  const passed = runs.every((r) => r.exitCode === 0);
170
173
  const timedOut = runs.some((r) => r.timedOut);
171
174
  const aborted = runs.some((r) => r.aborted);
172
- const { transform: _transform, ...compact } = applyOutputTransform([combinedStdout, combinedStderr].filter(Boolean).join(''), {
173
- toolName: 'wp_typecheck',
175
+ const combinedOutput = [combinedStdout, combinedStderr].filter(Boolean).join('');
176
+ const failedWithoutDiagnostics = isRunnerFailure({
177
+ passed,
178
+ timedOut,
179
+ aborted,
180
+ parsedCount: errors.length,
181
+ output: combinedOutput,
174
182
  });
183
+ const compact = failedWithoutDiagnostics
184
+ ? boundRunnerFailureEvidence(combinedOutput, 'wp_typecheck')
185
+ : stripTransform(applyOutputTransform(combinedOutput, { toolName: 'wp_typecheck' }));
175
186
  const payload = {
176
187
  passed,
177
- summary: summarizeTypecheckResult({ passed, errorCount: errors.length, timedOut, aborted }),
188
+ summary: summarizeTypecheckResult({
189
+ passed,
190
+ errorCount: errors.length,
191
+ timedOut,
192
+ aborted,
193
+ failedWithoutDiagnostics,
194
+ }),
178
195
  counts: { errorCount: errors.length },
179
196
  details: { errors },
180
197
  ...compact,
@@ -95,6 +95,8 @@
95
95
  "#types/execution-backend": "./blueprint/types/execution-backend.js",
96
96
  "#types/execution-backend.js": "./blueprint/types/execution-backend.js",
97
97
  "#*.js": "./blueprint/*.js",
98
- "#*": "./blueprint/*.js"
98
+ "#*": "./blueprint/*.js",
99
+ "#deploy/*.js": "./deploy/*.js",
100
+ "#deploy/*": "./deploy/*.js"
99
101
  }
100
102
  }
@@ -25,5 +25,6 @@ export interface TestCommandOptions {
25
25
  export declare function buildTestCommand(target: ResolvedTestTarget, options?: TestCommandOptions): CommandConfig;
26
26
  export declare function buildVpTestCommand(filters: readonly string[], options?: TestCommandOptions): CommandConfig;
27
27
  export declare function buildVitestCommand(files: readonly string[], options?: TestCommandOptions): CommandConfig;
28
+ export declare function buildStrykerCommand(options?: TestCommandOptions): CommandConfig;
28
29
  export declare function getVpTestTask(options: Pick<TestCommandOptions, 'mutation' | 'workers' | 'watch'>): string;
29
30
  //# sourceMappingURL=command-builder.d.ts.map
@@ -1,8 +1,8 @@
1
1
  import { getManagedRunner } from '#tool-runtime';
2
- import { getPackageScript, isRecursiveWpScript, packageUsesVitest } from '#cli/package-scripts.js';
2
+ import { getPackageScript, isRecursiveWpScript } from '#cli/package-scripts.js';
3
3
  export function buildTestCommand(target, options = {}) {
4
4
  if (target.type === 'all' && shouldBypassRecursiveWpTest(options.cwd ?? process.cwd())) {
5
- return buildVitestCommand([], options);
5
+ return options.mutation ? buildStrykerCommand(options) : buildVitestCommand([], options);
6
6
  }
7
7
  if (target.type === 'file') {
8
8
  return buildVitestCommand(target.values, options);
@@ -56,6 +56,12 @@ export function buildVitestCommand(files, options = {}) {
56
56
  });
57
57
  return { command: resolution.command, args: [...resolution.args, ...args] };
58
58
  }
59
+ export function buildStrykerCommand(options = {}) {
60
+ const resolution = getManagedRunner('stryker', {
61
+ outputPolicy: resolveOutputPolicy(options.outputPolicy, options.filterOutput),
62
+ });
63
+ return { command: resolution.command, args: [...resolution.args, 'run', 'stryker.config.ts'] };
64
+ }
59
65
  export function getVpTestTask(options) {
60
66
  if (options.mutation)
61
67
  return 'test:mutation';
@@ -116,8 +122,6 @@ function resolveOutputPolicy(outputPolicy, filterOutput) {
116
122
  }
117
123
  function shouldBypassRecursiveWpTest(cwd) {
118
124
  const testScript = getPackageScript(cwd, 'test');
119
- if (!testScript || !isRecursiveWpScript(testScript, 'test'))
120
- return false;
121
- return packageUsesVitest(cwd);
125
+ return Boolean(testScript && isRecursiveWpScript(testScript, 'test'));
122
126
  }
123
127
  //# sourceMappingURL=command-builder.js.map
@@ -1,9 +1,18 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { dirname, resolve } from 'node:path';
3
+ import { createRequire } from 'node:module';
4
+ const require = createRequire(import.meta.url);
1
5
  const MANAGED_TOOL_PREFIX = {
2
- oxfmt: { command: 'vp', args: ['exec', 'oxfmt'] },
3
- playwright: { command: 'vp', args: ['exec', 'playwright'] },
4
- tsc: { command: 'vp', args: ['exec', 'tsc'] },
5
- vitest: { command: 'vp', args: ['exec', 'vitest'] },
6
+ oxfmt: { packageName: 'oxfmt', binName: 'oxfmt' },
7
+ oxlint: { packageName: 'oxlint', binName: 'oxlint' },
8
+ playwright: { packageName: '@playwright/test', binName: 'playwright' },
9
+ stryker: { packageName: '@stryker-mutator/core', binName: 'stryker' },
10
+ tsc: { packageName: 'typescript', binName: 'tsc' },
11
+ tsx: { packageName: 'tsx', binName: 'tsx' },
12
+ vite: { packageName: 'vite', binName: 'vite' },
13
+ vitest: { packageName: 'vitest', binName: 'vitest' },
6
14
  vp: { command: 'vp', args: [] },
15
+ wrangler: { packageName: 'wrangler', binName: 'wrangler' },
7
16
  };
8
17
  function withOptionalRtk(resolution, outputPolicy) {
9
18
  if (outputPolicy !== 'rtk-filtered')
@@ -22,12 +31,7 @@ export function resolveRunner(tool, options = {}) {
22
31
  const outputPolicy = options.outputPolicy ?? (options.filterOutput === false ? 'structured' : 'rtk-filtered');
23
32
  const managed = MANAGED_TOOL_PREFIX[normalized];
24
33
  if (managed) {
25
- return withOptionalRtk({
26
- tool: normalized,
27
- command: managed.command,
28
- args: [...managed.args],
29
- source: 'managed',
30
- }, outputPolicy);
34
+ return withOptionalRtk(resolveManagedTool(normalized, managed), outputPolicy);
31
35
  }
32
36
  if (options.fallbackCommand) {
33
37
  return withOptionalRtk({
@@ -39,4 +43,28 @@ export function resolveRunner(tool, options = {}) {
39
43
  }
40
44
  throw new Error(`No managed runtime runner is defined for tool "${normalized}"`);
41
45
  }
46
+ function resolveManagedTool(tool, spec) {
47
+ if ('command' in spec) {
48
+ return { tool, command: spec.command, args: [...spec.args], source: 'managed' };
49
+ }
50
+ const binPath = resolvePackageBin(spec.packageName, spec.binName);
51
+ if (binPath) {
52
+ return { tool, command: binPath, args: [...(spec.fallbackArgs ?? [])], source: 'managed' };
53
+ }
54
+ return { tool, command: 'vp', args: ['exec', spec.binName], source: 'fallback' };
55
+ }
56
+ function resolvePackageBin(packageName, binName) {
57
+ try {
58
+ const packageJsonPath = require.resolve(`${packageName}/package.json`);
59
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
60
+ const relativeBin = typeof packageJson.bin === 'string' ? packageJson.bin : packageJson.bin?.[binName];
61
+ if (!relativeBin)
62
+ return null;
63
+ const absoluteBin = resolve(dirname(packageJsonPath), relativeBin);
64
+ return existsSync(absoluteBin) ? absoluteBin : null;
65
+ }
66
+ catch {
67
+ return null;
68
+ }
69
+ }
42
70
  //# sourceMappingURL=resolve-runner.js.map