@studio-foundation/runner 0.3.0-beta.1 → 0.3.0-beta.5

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 (78) hide show
  1. package/package.json +6 -3
  2. package/ARCHITECTURE.md +0 -53
  3. package/configs/agents/analyst.agent.yaml +0 -31
  4. package/configs/agents/code-generator.agent.yaml +0 -31
  5. package/configs/agents/generic.agent.yaml +0 -23
  6. package/src/__tests__/script-executor.test.ts +0 -180
  7. package/src/index.ts +0 -68
  8. package/src/integrations/integration-loader.test.ts +0 -88
  9. package/src/integrations/integration-loader.ts +0 -68
  10. package/src/middleware/anonymization.ts +0 -38
  11. package/src/plugins/index.ts +0 -4
  12. package/src/plugins/mcp-client.test.ts +0 -148
  13. package/src/plugins/mcp-client.ts +0 -128
  14. package/src/plugins/oauth-provider.test.ts +0 -167
  15. package/src/plugins/oauth-provider.ts +0 -175
  16. package/src/plugins/plugin-loader.test.ts +0 -114
  17. package/src/plugins/plugin-loader.ts +0 -90
  18. package/src/prompt-builder.test.ts +0 -167
  19. package/src/prompt-builder.ts +0 -332
  20. package/src/providers/anthropic.test.ts +0 -101
  21. package/src/providers/anthropic.ts +0 -135
  22. package/src/providers/mock.ts +0 -57
  23. package/src/providers/ollama.test.ts +0 -166
  24. package/src/providers/ollama.ts +0 -152
  25. package/src/providers/openai-responses.ts +0 -212
  26. package/src/providers/openai.test.ts +0 -67
  27. package/src/providers/openai.ts +0 -139
  28. package/src/providers/provider.ts +0 -54
  29. package/src/providers/registry.ts +0 -77
  30. package/src/runner.test.ts +0 -343
  31. package/src/runner.ts +0 -396
  32. package/src/script-executor.ts +0 -107
  33. package/src/tools/builtin/git.ts +0 -311
  34. package/src/tools/builtin/patch.ts +0 -257
  35. package/src/tools/builtin/repo-manager.ts +0 -142
  36. package/src/tools/builtin/search.ts +0 -108
  37. package/src/tools/builtin/shell.ts +0 -82
  38. package/src/tools/builtin/studio-run.ts +0 -73
  39. package/src/tools/builtin/web-search.test.ts +0 -122
  40. package/src/tools/builtin/web-search.ts +0 -101
  41. package/src/tools/errors.test.ts +0 -12
  42. package/src/tools/errors.ts +0 -6
  43. package/src/tools/plugin-loader.test.ts +0 -130
  44. package/src/tools/plugin-loader.ts +0 -203
  45. package/src/tools/skills/README.md +0 -49
  46. package/src/tools/skills/skill-loader.test.ts +0 -106
  47. package/src/tools/skills/skill-loader.ts +0 -62
  48. package/src/tools/tool-executor.test.ts +0 -88
  49. package/src/tools/tool-executor.ts +0 -84
  50. package/src/tools/tool-registry.ts +0 -130
  51. package/src/tools/yaml-executor.ts +0 -120
  52. package/src/utils/race-signal.test.ts +0 -50
  53. package/src/utils/race-signal.ts +0 -17
  54. package/templates/integrations/linear.integration.yaml +0 -35
  55. package/templates/integrations/slack.integration.yaml +0 -22
  56. package/templates/integrations/webhook.integration.yaml +0 -17
  57. package/templates/tools/git.tool.yaml +0 -80
  58. package/templates/tools/repo-manager.tool.yaml +0 -64
  59. package/templates/tools/search.tool.yaml +0 -22
  60. package/templates/tools/shell.tool.yaml +0 -19
  61. package/templates/tools/web-search.tool.yaml +0 -24
  62. package/tests/anonymization-middleware.test.ts +0 -61
  63. package/tests/anthropic.test.ts +0 -87
  64. package/tests/apply-patch.test.ts +0 -355
  65. package/tests/fixtures/tools/test-builtin.tool.yaml +0 -14
  66. package/tests/fixtures/tools/test-shell.tool.yaml +0 -19
  67. package/tests/mock-provider.test.ts +0 -104
  68. package/tests/openai.test.ts +0 -72
  69. package/tests/plugin-loader.test.ts +0 -54
  70. package/tests/prompt-builder.test.ts +0 -468
  71. package/tests/runner-anonymization.test.ts +0 -89
  72. package/tests/runner.test.ts +0 -885
  73. package/tests/studio-run.test.ts +0 -94
  74. package/tests/tool-executor.test.ts +0 -115
  75. package/tests/tool-registry.test.ts +0 -84
  76. package/tests/yaml-executor.test.ts +0 -76
  77. package/tsconfig.json +0 -20
  78. package/vitest.config.ts +0 -7
@@ -1,108 +0,0 @@
1
- /**
2
- * Search tool - codebase search using grep
3
- */
4
-
5
- import { exec } from 'child_process';
6
- import { promisify } from 'util';
7
- import type { Tool } from '../tool-registry.js';
8
-
9
- const execAsync = promisify(exec);
10
-
11
- export function createSearchTools(repoPath: string): Tool[] {
12
- return [
13
- {
14
- name: 'search-search_codebase',
15
- description: 'Search for a pattern in the codebase (like grep)',
16
- parameters: {
17
- type: 'object',
18
- properties: {
19
- pattern: {
20
- type: 'string',
21
- description: 'Search pattern (string or regex)'
22
- },
23
- file_pattern: {
24
- type: 'string',
25
- description: 'File glob pattern to filter (e.g., "*.ts", "*.js")',
26
- }
27
- },
28
- required: ['pattern']
29
- },
30
- execute: async ({ pattern, file_pattern }) => {
31
- try {
32
- const pat = pattern as string;
33
- const filePat = file_pattern as string | undefined;
34
-
35
- // Try ripgrep first, fallback to grep
36
- let command: string;
37
-
38
- // Check if ripgrep is available
39
- try {
40
- await execAsync('which rg', { cwd: repoPath });
41
- // Use ripgrep
42
- command = filePat
43
- ? `rg -n --glob "${filePat}" "${pat}" .`
44
- : `rg -n "${pat}" .`;
45
- } catch {
46
- // Fallback to standard grep
47
- command = filePat
48
- ? `grep -r -n --include="${filePat}" "${pat}" .`
49
- : `grep -r -n "${pat}" .`;
50
- }
51
-
52
- const { stdout } = await execAsync(command, {
53
- cwd: repoPath,
54
- maxBuffer: 1024 * 1024 * 10, // 10MB
55
- timeout: 30000
56
- });
57
-
58
- // Parse results - format is "file:line:content"
59
- const lines = stdout.trim().split('\n').filter(l => l.length > 0);
60
- const matches = lines.slice(0, 50).map(line => {
61
- const parts = line.split(':');
62
- if (parts.length >= 3) {
63
- const [file, lineNum, ...rest] = parts;
64
- return {
65
- file,
66
- line: parseInt(lineNum, 10),
67
- content: rest.join(':').trim()
68
- };
69
- }
70
- return null;
71
- }).filter(m => m !== null);
72
-
73
- return {
74
- success: true,
75
- output: {
76
- pattern,
77
- matches,
78
- count: matches.length,
79
- total_found: lines.length,
80
- truncated: lines.length > 50
81
- }
82
- };
83
- } catch (error: unknown) {
84
- // Exit code 1 typically means no matches found
85
- if (error && typeof error === 'object' && 'code' in error && error.code === 1) {
86
- return {
87
- success: true,
88
- output: {
89
- pattern,
90
- matches: [],
91
- count: 0,
92
- total_found: 0,
93
- truncated: false
94
- }
95
- };
96
- }
97
-
98
- const errorMessage = error instanceof Error ? error.message : String(error);
99
- return {
100
- success: false,
101
- output: null,
102
- error: `Search failed: ${errorMessage}`
103
- };
104
- }
105
- }
106
- }
107
- ];
108
- }
@@ -1,82 +0,0 @@
1
- /**
2
- * Shell tool - command execution with basic safety checks
3
- */
4
-
5
- import { exec } from 'child_process';
6
- import { promisify } from 'util';
7
- import type { Tool } from '../tool-registry.js';
8
-
9
- const execAsync = promisify(exec);
10
-
11
- export function createShellTools(workingDir: string): Tool[] {
12
- return [
13
- {
14
- name: 'shell-run_command',
15
- description: 'Run a shell command in the repository directory',
16
- parameters: {
17
- type: 'object',
18
- properties: {
19
- command: {
20
- type: 'string',
21
- description: 'The shell command to run'
22
- }
23
- },
24
- required: ['command']
25
- },
26
- execute: async ({ command }) => {
27
- try {
28
- const cmd = command as string;
29
-
30
- // Basic sanitization - block obviously dangerous commands
31
- const dangerous = ['rm -rf', 'sudo', 'mkfs', 'dd if=', '> /dev/', 'format'];
32
- const isDangerous = dangerous.some(pattern => cmd.toLowerCase().includes(pattern));
33
-
34
- if (isDangerous) {
35
- return {
36
- success: false,
37
- output: null,
38
- error: 'Dangerous command blocked for safety'
39
- };
40
- }
41
-
42
- // Execute with timeout of 30 seconds
43
- const { stdout, stderr } = await execAsync(cmd, {
44
- cwd: workingDir,
45
- timeout: 30000,
46
- maxBuffer: 1024 * 1024 * 10 // 10MB max output
47
- });
48
-
49
- return {
50
- success: true,
51
- output: {
52
- stdout: stdout.trim(),
53
- stderr: stderr.trim(),
54
- exitCode: 0
55
- }
56
- };
57
- } catch (error: unknown) {
58
- // Execution error (non-zero exit code or timeout)
59
- if (error && typeof error === 'object' && 'stdout' in error && 'stderr' in error) {
60
- const execError = error as { stdout: string; stderr: string; code?: number };
61
- return {
62
- success: false,
63
- output: {
64
- stdout: execError.stdout?.trim() || '',
65
- stderr: execError.stderr?.trim() || '',
66
- exitCode: execError.code || 1
67
- },
68
- error: execError.stderr?.trim() || 'Command execution failed'
69
- };
70
- }
71
-
72
- const errorMessage = error instanceof Error ? error.message : String(error);
73
- return {
74
- success: false,
75
- output: null,
76
- error: `Shell execution error: ${errorMessage}`
77
- };
78
- }
79
- }
80
- }
81
- ];
82
- }
@@ -1,73 +0,0 @@
1
- import type { RunSpawner } from '@studio-foundation/contracts';
2
- import type { Tool } from '../tool-registry.js';
3
-
4
- interface StudioRunContext {
5
- spawner: RunSpawner;
6
- currentRunId: string;
7
- currentDepth: number;
8
- maxDepth: number;
9
- }
10
-
11
- export const STUDIO_RUN_PROMPT_SNIPPET = `
12
- ## studio_run tool
13
-
14
- Use \`studio_run-run_pipeline\` to launch a Studio pipeline run and wait for its result.
15
- The run executes asynchronously but this tool blocks until completion.
16
- Use it to orchestrate sub-pipelines (e.g. generate N items by launching N runs).
17
- `.trim();
18
-
19
- export function createStudioRunTool(ctx: StudioRunContext): Tool[] {
20
- return [
21
- {
22
- name: 'studio_run-run_pipeline',
23
- description: 'Launch a Studio pipeline run and wait for completion. Returns the output of the last stage.',
24
- parameters: {
25
- type: 'object',
26
- properties: {
27
- pipeline: {
28
- type: 'string',
29
- description: 'Name of the pipeline to run (e.g. "recipe-developer")',
30
- },
31
- input: {
32
- type: 'object',
33
- description: 'Input data for the pipeline',
34
- },
35
- wait: {
36
- type: 'boolean',
37
- description: 'Whether to wait for completion before returning (default: true)',
38
- default: true,
39
- },
40
- },
41
- required: ['pipeline', 'input'],
42
- },
43
- async execute(args) {
44
- const pipeline = args['pipeline'] as string;
45
- const input = args['input'] as Record<string, unknown>;
46
- const wait = args['wait'] !== false;
47
-
48
- if (!wait) {
49
- throw new Error('wait: false is not supported in v1. Use wait: true (default).');
50
- }
51
-
52
- if (ctx.currentDepth + 1 > ctx.maxDepth) {
53
- throw new Error(
54
- `studio-run depth limit reached (max: ${ctx.maxDepth}). ` +
55
- `Current depth: ${ctx.currentDepth}. Recursive pipeline spawning is not allowed at this level.`
56
- );
57
- }
58
-
59
- const result = await ctx.spawner.spawnAndWait({
60
- pipeline,
61
- input,
62
- parentRunId: ctx.currentRunId,
63
- depth: ctx.currentDepth + 1,
64
- });
65
-
66
- return {
67
- success: result.status === 'success',
68
- output: result,
69
- };
70
- },
71
- },
72
- ];
73
- }
@@ -1,122 +0,0 @@
1
- /**
2
- * Tests for the web_search builtin tool
3
- */
4
-
5
- import { describe, it, expect, vi, afterEach } from 'vitest';
6
- import { createWebSearchTools, WEB_SEARCH_PROMPT_SNIPPET } from './web-search.js';
7
-
8
- describe('createWebSearchTools', () => {
9
- afterEach(() => {
10
- vi.restoreAllMocks();
11
- delete process.env.TAVILY_API_KEY;
12
- });
13
-
14
- it('returns a single tool named web_search-search', () => {
15
- const tools = createWebSearchTools();
16
- expect(tools).toHaveLength(1);
17
- expect(tools[0].name).toBe('web_search-search');
18
- });
19
-
20
- it('requires query parameter', () => {
21
- const [tool] = createWebSearchTools();
22
- const schema = tool.parameters as { required: string[] };
23
- expect(schema.required).toContain('query');
24
- });
25
-
26
- it('returns error when TAVILY_API_KEY is not set', async () => {
27
- const [tool] = createWebSearchTools();
28
- const result = await tool.execute({ query: 'test' });
29
- expect(result.success).toBe(false);
30
- expect(result.error).toMatch(/TAVILY_API_KEY/);
31
- });
32
-
33
- it('calls Tavily API and returns results', async () => {
34
- process.env.TAVILY_API_KEY = 'test-key';
35
- const mockResponse = {
36
- results: [
37
- { title: 'Result 1', url: 'https://example.com/1', content: 'Content 1', score: 0.9 },
38
- { title: 'Result 2', url: 'https://example.com/2', content: 'Content 2', score: 0.8 },
39
- ],
40
- answer: 'A direct answer'
41
- };
42
-
43
- vi.spyOn(global, 'fetch').mockResolvedValueOnce(
44
- new Response(JSON.stringify(mockResponse), { status: 200 })
45
- );
46
-
47
- const [tool] = createWebSearchTools();
48
- const result = await tool.execute({ query: 'test query', max_results: 2 });
49
-
50
- expect(result.success).toBe(true);
51
- const output = result.output as {
52
- query: string;
53
- results: Array<{ title: string; url: string }>;
54
- answer: string;
55
- count: number;
56
- };
57
- expect(output.query).toBe('test query');
58
- expect(output.results).toHaveLength(2);
59
- expect(output.results[0].title).toBe('Result 1');
60
- expect(output.answer).toBe('A direct answer');
61
- expect(output.count).toBe(2);
62
- });
63
-
64
- it('passes max_results to Tavily API', async () => {
65
- process.env.TAVILY_API_KEY = 'test-key';
66
- vi.spyOn(global, 'fetch').mockResolvedValueOnce(
67
- new Response(JSON.stringify({ results: [], answer: null }), { status: 200 })
68
- );
69
-
70
- const [tool] = createWebSearchTools();
71
- await tool.execute({ query: 'test', max_results: 3 });
72
-
73
- const [, options] = (fetch as ReturnType<typeof vi.fn>).mock.calls[0] as [string, RequestInit];
74
- const body = JSON.parse(options.body as string);
75
- expect(body.max_results).toBe(3);
76
- });
77
-
78
- it('defaults max_results to 5 when not provided', async () => {
79
- process.env.TAVILY_API_KEY = 'test-key';
80
- vi.spyOn(global, 'fetch').mockResolvedValueOnce(
81
- new Response(JSON.stringify({ results: [] }), { status: 200 })
82
- );
83
-
84
- const [tool] = createWebSearchTools();
85
- await tool.execute({ query: 'test' });
86
-
87
- const [, options] = (fetch as ReturnType<typeof vi.fn>).mock.calls[0] as [string, RequestInit];
88
- const body = JSON.parse(options.body as string);
89
- expect(body.max_results).toBe(5);
90
- });
91
-
92
- it('returns error on non-ok API response', async () => {
93
- process.env.TAVILY_API_KEY = 'test-key';
94
- vi.spyOn(global, 'fetch').mockResolvedValueOnce(
95
- new Response('Unauthorized', { status: 401 })
96
- );
97
-
98
- const [tool] = createWebSearchTools();
99
- const result = await tool.execute({ query: 'test' });
100
-
101
- expect(result.success).toBe(false);
102
- expect(result.error).toMatch(/401/);
103
- });
104
-
105
- it('returns error on network failure', async () => {
106
- process.env.TAVILY_API_KEY = 'test-key';
107
- vi.spyOn(global, 'fetch').mockRejectedValueOnce(new Error('Network error'));
108
-
109
- const [tool] = createWebSearchTools();
110
- const result = await tool.execute({ query: 'test' });
111
-
112
- expect(result.success).toBe(false);
113
- expect(result.error).toMatch(/Network error/);
114
- });
115
- });
116
-
117
- describe('WEB_SEARCH_PROMPT_SNIPPET', () => {
118
- it('is a non-empty string', () => {
119
- expect(typeof WEB_SEARCH_PROMPT_SNIPPET).toBe('string');
120
- expect(WEB_SEARCH_PROMPT_SNIPPET.length).toBeGreaterThan(0);
121
- });
122
- });
@@ -1,101 +0,0 @@
1
- /**
2
- * Web search tool - search the web using the Tavily API
3
- */
4
-
5
- import type { Tool } from '../tool-registry.js';
6
-
7
- const TAVILY_API_URL = 'https://api.tavily.com/search';
8
-
9
- export const WEB_SEARCH_PROMPT_SNIPPET = `
10
- ## web_search tool
11
-
12
- Use \`web_search-search\` to search the web for up-to-date information.
13
- Search in the language most relevant to the query (e.g. English for technical docs, French for local content).
14
- Prefer specific, targeted queries over broad ones for better results.
15
- `.trim();
16
-
17
- export function createWebSearchTools(): Tool[] {
18
- return [
19
- {
20
- name: 'web_search-search',
21
- description: 'Search the web using Tavily and return relevant results',
22
- parameters: {
23
- type: 'object',
24
- properties: {
25
- query: {
26
- type: 'string',
27
- description: 'The search query'
28
- },
29
- max_results: {
30
- type: 'number',
31
- description: 'Maximum number of results to return (default: 5)'
32
- }
33
- },
34
- required: ['query']
35
- },
36
- execute: async ({ query, max_results }) => {
37
- const apiKey = process.env.TAVILY_API_KEY;
38
- if (!apiKey) {
39
- return {
40
- success: false,
41
- output: null,
42
- error: 'TAVILY_API_KEY environment variable is not set'
43
- };
44
- }
45
-
46
- const q = query as string;
47
- const limit = (max_results as number | undefined) ?? 5;
48
-
49
- try {
50
- const response = await fetch(TAVILY_API_URL, {
51
- method: 'POST',
52
- headers: {
53
- 'Content-Type': 'application/json',
54
- 'Authorization': `Bearer ${apiKey}`
55
- },
56
- body: JSON.stringify({
57
- query: q,
58
- max_results: limit
59
- })
60
- });
61
-
62
- if (!response.ok) {
63
- const text = await response.text();
64
- return {
65
- success: false,
66
- output: null,
67
- error: `Tavily API error ${response.status}: ${text}`
68
- };
69
- }
70
-
71
- const data = await response.json() as {
72
- results?: Array<{ title: string; url: string; content: string; score?: number }>;
73
- answer?: string;
74
- };
75
-
76
- return {
77
- success: true,
78
- output: {
79
- query: q,
80
- results: (data.results ?? []).map(r => ({
81
- title: r.title,
82
- url: r.url,
83
- content: r.content,
84
- score: r.score
85
- })),
86
- answer: data.answer ?? null,
87
- count: (data.results ?? []).length
88
- }
89
- };
90
- } catch (error: unknown) {
91
- const errorMessage = error instanceof Error ? error.message : String(error);
92
- return {
93
- success: false,
94
- output: null,
95
- error: `Web search failed: ${errorMessage}`
96
- };
97
- }
98
- }
99
- }
100
- ];
101
- }
@@ -1,12 +0,0 @@
1
- // runner/src/tools/errors.test.ts
2
- import { describe, it, expect } from 'vitest';
3
- import { ToolYamlError } from './errors.js';
4
-
5
- describe('ToolYamlError', () => {
6
- it('is an Error with name ToolYamlError', () => {
7
- const err = new ToolYamlError('bad yaml');
8
- expect(err).toBeInstanceOf(Error);
9
- expect(err.name).toBe('ToolYamlError');
10
- expect(err.message).toBe('bad yaml');
11
- });
12
- });
@@ -1,6 +0,0 @@
1
- export class ToolYamlError extends Error {
2
- constructor(message: string) {
3
- super(message);
4
- this.name = 'ToolYamlError';
5
- }
6
- }
@@ -1,130 +0,0 @@
1
- import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2
- import { mkdtemp, mkdir, writeFile, rm } from 'node:fs/promises';
3
- import { join } from 'node:path';
4
- import { tmpdir } from 'node:os';
5
- import { loadProjectTools } from './plugin-loader.js';
6
- import { ToolYamlError } from './errors.js';
7
-
8
- let tmpDir: string;
9
-
10
- beforeAll(async () => {
11
- tmpDir = await mkdtemp(join(tmpdir(), 'studio-tool-loader-test-'));
12
- });
13
-
14
- afterAll(async () => {
15
- await rm(tmpDir, { recursive: true, force: true });
16
- });
17
-
18
- async function writeToolYaml(name: string, content: string): Promise<string> {
19
- const dir = join(tmpDir, name);
20
- await mkdir(dir, { recursive: true });
21
- const toolsDir = join(dir, 'tools');
22
- await mkdir(toolsDir, { recursive: true });
23
- await writeFile(join(toolsDir, `${name}.tool.yaml`), content);
24
- return toolsDir;
25
- }
26
-
27
- describe('loadProjectTools — shell template validation', () => {
28
- it('throws ToolYamlError when template uses undeclared placeholder', async () => {
29
- const toolsDir = await writeToolYaml('bad-search', `
30
- name: bad_search
31
- version: 1
32
- commands:
33
- - name: bad_search-search
34
- description: Search
35
- parameters:
36
- query:
37
- type: string
38
- required: true
39
- execute:
40
- type: shell
41
- command: 'curl "https://api.example.com?q={{search_query}}"'
42
- `);
43
- const promise = loadProjectTools(toolsDir, '/tmp');
44
- await expect(promise).rejects.toThrow(ToolYamlError);
45
- await expect(promise).rejects.toThrow(ToolYamlError);
46
- await expect(promise).rejects.toThrow(
47
- "template uses {{search_query}} but no such parameter is declared"
48
- );
49
- });
50
-
51
- it('loads successfully when all template placeholders are declared', async () => {
52
- const toolsDir = await writeToolYaml('good-search', `
53
- name: good_search
54
- version: 1
55
- commands:
56
- - name: good_search-search
57
- description: Search
58
- parameters:
59
- query:
60
- type: string
61
- required: true
62
- execute:
63
- type: shell
64
- command: 'curl "https://api.example.com?q={{query}}"'
65
- `);
66
- await expect(loadProjectTools(toolsDir, '/tmp')).resolves.toHaveLength(1);
67
- });
68
-
69
- it('does not flag {{else}} as an undeclared placeholder', async () => {
70
- const toolsDir = await writeToolYaml('conditional-tool', `
71
- name: conditional_tool
72
- version: 1
73
- commands:
74
- - name: conditional_tool-run
75
- description: Run with optional flag
76
- parameters:
77
- verbose:
78
- type: boolean
79
- required: false
80
- execute:
81
- type: shell
82
- command: 'echo {{#if verbose}}--verbose{{else}}--quiet{{/if}}'
83
- `);
84
- await expect(loadProjectTools(toolsDir, '/tmp')).resolves.toHaveLength(1);
85
- });
86
-
87
- it('error message includes filename, command name, and declared parameters', async () => {
88
- const toolsDir = await writeToolYaml('err-msg', `
89
- name: err_msg
90
- version: 1
91
- commands:
92
- - name: err_msg-run
93
- description: Run
94
- parameters:
95
- query:
96
- type: string
97
- required: true
98
- execute:
99
- type: shell
100
- command: 'curl {{typo}}'
101
- `);
102
- const promise = loadProjectTools(toolsDir, '/tmp');
103
- await expect(promise).rejects.toThrow(ToolYamlError);
104
- await expect(promise).rejects.toThrow(
105
- "err-msg.tool.yaml › command 'err_msg-run'"
106
- );
107
- await expect(promise).rejects.toThrow(
108
- "Declared parameters: query"
109
- );
110
- });
111
-
112
- it('error message shows (none) when command has no parameters declared', async () => {
113
- const toolsDir = await writeToolYaml('no-params', `
114
- name: no_params
115
- version: 1
116
- commands:
117
- - name: no_params-run
118
- description: Run
119
- parameters: {}
120
- execute:
121
- type: shell
122
- command: 'curl {{typo}}'
123
- `);
124
- const promise = loadProjectTools(toolsDir, '/tmp');
125
- await expect(promise).rejects.toThrow(ToolYamlError);
126
- await expect(promise).rejects.toThrow(
127
- "Declared parameters: (none)"
128
- );
129
- });
130
- });