@outputai/cli 0.1.2 → 0.1.3-dev.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.
- package/dist/api/generated/api.d.ts +820 -0
- package/dist/api/generated/api.js +226 -0
- package/dist/api/http_client.d.ts +27 -0
- package/dist/api/http_client.js +71 -0
- package/dist/api/orval_post_process.d.ts +11 -0
- package/dist/api/orval_post_process.js +46 -0
- package/dist/api/parser.d.ts +17 -0
- package/dist/api/parser.js +68 -0
- package/dist/assets/config/costs.yml +309 -0
- package/dist/assets/docker/docker-compose-dev.yml +146 -0
- package/dist/commands/credentials/edit.d.ts +10 -0
- package/dist/commands/credentials/edit.js +67 -0
- package/dist/commands/credentials/edit.spec.d.ts +1 -0
- package/dist/commands/credentials/edit.spec.js +73 -0
- package/dist/commands/credentials/get.d.ts +13 -0
- package/dist/commands/credentials/get.js +46 -0
- package/dist/commands/credentials/get.spec.d.ts +1 -0
- package/dist/commands/credentials/get.spec.js +74 -0
- package/dist/commands/credentials/init.d.ts +11 -0
- package/dist/commands/credentials/init.js +45 -0
- package/dist/commands/credentials/init.spec.d.ts +1 -0
- package/dist/commands/credentials/init.spec.js +68 -0
- package/dist/commands/credentials/show.d.ts +10 -0
- package/dist/commands/credentials/show.js +33 -0
- package/dist/commands/credentials/show.spec.d.ts +1 -0
- package/dist/commands/credentials/show.spec.js +57 -0
- package/dist/commands/dev/eject.d.ts +11 -0
- package/dist/commands/dev/eject.js +58 -0
- package/dist/commands/dev/eject.spec.d.ts +1 -0
- package/dist/commands/dev/eject.spec.js +109 -0
- package/dist/commands/dev/index.d.ts +14 -0
- package/dist/commands/dev/index.js +173 -0
- package/dist/commands/dev/index.spec.d.ts +1 -0
- package/dist/commands/dev/index.spec.js +239 -0
- package/dist/commands/init.d.ts +12 -0
- package/dist/commands/init.js +37 -0
- package/dist/commands/init.spec.d.ts +1 -0
- package/dist/commands/init.spec.js +100 -0
- package/dist/commands/update.d.ts +14 -0
- package/dist/commands/update.js +120 -0
- package/dist/commands/update.spec.d.ts +1 -0
- package/dist/commands/update.spec.js +178 -0
- package/dist/commands/workflow/cost.d.ts +16 -0
- package/dist/commands/workflow/cost.js +71 -0
- package/dist/commands/workflow/cost.spec.d.ts +1 -0
- package/dist/commands/workflow/cost.spec.js +47 -0
- package/dist/commands/workflow/dataset/generate.d.ts +22 -0
- package/dist/commands/workflow/dataset/generate.js +143 -0
- package/dist/commands/workflow/dataset/list.d.ts +12 -0
- package/dist/commands/workflow/dataset/list.js +87 -0
- package/dist/commands/workflow/debug.d.ts +16 -0
- package/dist/commands/workflow/debug.js +60 -0
- package/dist/commands/workflow/debug.spec.d.ts +1 -0
- package/dist/commands/workflow/debug.spec.js +34 -0
- package/dist/commands/workflow/generate.d.ts +17 -0
- package/dist/commands/workflow/generate.js +85 -0
- package/dist/commands/workflow/generate.spec.d.ts +1 -0
- package/dist/commands/workflow/generate.spec.js +115 -0
- package/dist/commands/workflow/list.d.ts +22 -0
- package/dist/commands/workflow/list.js +152 -0
- package/dist/commands/workflow/list.spec.d.ts +1 -0
- package/dist/commands/workflow/list.spec.js +99 -0
- package/dist/commands/workflow/plan.d.ts +12 -0
- package/dist/commands/workflow/plan.js +66 -0
- package/dist/commands/workflow/plan.spec.d.ts +1 -0
- package/dist/commands/workflow/plan.spec.js +341 -0
- package/dist/commands/workflow/reset.d.ts +14 -0
- package/dist/commands/workflow/reset.js +51 -0
- package/dist/commands/workflow/result.d.ts +13 -0
- package/dist/commands/workflow/result.js +46 -0
- package/dist/commands/workflow/result.spec.d.ts +1 -0
- package/dist/commands/workflow/result.spec.js +23 -0
- package/dist/commands/workflow/run.d.ts +16 -0
- package/dist/commands/workflow/run.js +97 -0
- package/dist/commands/workflow/run.spec.d.ts +1 -0
- package/dist/commands/workflow/run.spec.js +110 -0
- package/dist/commands/workflow/runs/list.d.ts +14 -0
- package/dist/commands/workflow/runs/list.js +104 -0
- package/dist/commands/workflow/start.d.ts +15 -0
- package/dist/commands/workflow/start.js +62 -0
- package/dist/commands/workflow/start.spec.d.ts +1 -0
- package/dist/commands/workflow/start.spec.js +28 -0
- package/dist/commands/workflow/status.d.ts +13 -0
- package/dist/commands/workflow/status.js +57 -0
- package/dist/commands/workflow/status.spec.d.ts +1 -0
- package/dist/commands/workflow/status.spec.js +33 -0
- package/dist/commands/workflow/stop.d.ts +10 -0
- package/dist/commands/workflow/stop.js +31 -0
- package/dist/commands/workflow/stop.spec.d.ts +1 -0
- package/dist/commands/workflow/stop.spec.js +17 -0
- package/dist/commands/workflow/terminate.d.ts +13 -0
- package/dist/commands/workflow/terminate.js +39 -0
- package/dist/commands/workflow/test_eval.d.ts +20 -0
- package/dist/commands/workflow/test_eval.js +151 -0
- package/dist/config.d.ts +47 -0
- package/dist/config.js +47 -0
- package/dist/generated/framework_version.json +3 -0
- package/dist/hooks/init.d.ts +3 -0
- package/dist/hooks/init.js +30 -0
- package/dist/hooks/init.spec.d.ts +1 -0
- package/dist/hooks/init.spec.js +54 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.spec.d.ts +1 -0
- package/dist/index.spec.js +6 -0
- package/dist/services/claude_client.d.ts +30 -0
- package/dist/services/claude_client.integration.test.d.ts +1 -0
- package/dist/services/claude_client.integration.test.js +43 -0
- package/dist/services/claude_client.js +215 -0
- package/dist/services/claude_client.spec.d.ts +1 -0
- package/dist/services/claude_client.spec.js +145 -0
- package/dist/services/coding_agents.d.ts +36 -0
- package/dist/services/coding_agents.js +236 -0
- package/dist/services/coding_agents.spec.d.ts +1 -0
- package/dist/services/coding_agents.spec.js +256 -0
- package/dist/services/copy_assets.spec.d.ts +1 -0
- package/dist/services/copy_assets.spec.js +22 -0
- package/dist/services/cost_calculator.d.ts +18 -0
- package/dist/services/cost_calculator.js +359 -0
- package/dist/services/cost_calculator.spec.d.ts +1 -0
- package/dist/services/cost_calculator.spec.js +540 -0
- package/dist/services/credentials_service.d.ts +12 -0
- package/dist/services/credentials_service.integration.test.d.ts +1 -0
- package/dist/services/credentials_service.integration.test.js +66 -0
- package/dist/services/credentials_service.js +64 -0
- package/dist/services/credentials_service.spec.d.ts +1 -0
- package/dist/services/credentials_service.spec.js +106 -0
- package/dist/services/datasets.d.ts +20 -0
- package/dist/services/datasets.js +132 -0
- package/dist/services/docker.d.ts +39 -0
- package/dist/services/docker.js +160 -0
- package/dist/services/docker.spec.d.ts +1 -0
- package/dist/services/docker.spec.js +124 -0
- package/dist/services/env_configurator.d.ts +15 -0
- package/dist/services/env_configurator.js +163 -0
- package/dist/services/env_configurator.spec.d.ts +1 -0
- package/dist/services/env_configurator.spec.js +192 -0
- package/dist/services/generate_plan_name@v1.prompt +24 -0
- package/dist/services/messages.d.ts +9 -0
- package/dist/services/messages.js +338 -0
- package/dist/services/messages.spec.d.ts +1 -0
- package/dist/services/messages.spec.js +55 -0
- package/dist/services/npm_update_service.d.ts +6 -0
- package/dist/services/npm_update_service.js +87 -0
- package/dist/services/npm_update_service.spec.d.ts +1 -0
- package/dist/services/npm_update_service.spec.js +104 -0
- package/dist/services/project_scaffold.d.ts +31 -0
- package/dist/services/project_scaffold.js +212 -0
- package/dist/services/project_scaffold.spec.d.ts +1 -0
- package/dist/services/project_scaffold.spec.js +122 -0
- package/dist/services/s3_trace_downloader.d.ts +12 -0
- package/dist/services/s3_trace_downloader.js +57 -0
- package/dist/services/template_processor.d.ts +14 -0
- package/dist/services/template_processor.js +57 -0
- package/dist/services/trace_reader.d.ts +16 -0
- package/dist/services/trace_reader.js +57 -0
- package/dist/services/trace_reader.spec.d.ts +1 -0
- package/dist/services/trace_reader.spec.js +78 -0
- package/dist/services/version_check.d.ts +6 -0
- package/dist/services/version_check.js +52 -0
- package/dist/services/version_check.spec.d.ts +1 -0
- package/dist/services/version_check.spec.js +106 -0
- package/dist/services/workflow_builder.d.ts +16 -0
- package/dist/services/workflow_builder.js +86 -0
- package/dist/services/workflow_builder.spec.d.ts +1 -0
- package/dist/services/workflow_builder.spec.js +165 -0
- package/dist/services/workflow_generator.d.ts +5 -0
- package/dist/services/workflow_generator.js +40 -0
- package/dist/services/workflow_generator.spec.d.ts +1 -0
- package/dist/services/workflow_generator.spec.js +77 -0
- package/dist/services/workflow_planner.d.ts +15 -0
- package/dist/services/workflow_planner.js +48 -0
- package/dist/services/workflow_planner.spec.d.ts +1 -0
- package/dist/services/workflow_planner.spec.js +122 -0
- package/dist/services/workflow_runs.d.ts +14 -0
- package/dist/services/workflow_runs.js +25 -0
- package/dist/templates/agent_instructions/CLAUDE.md.template +19 -0
- package/dist/templates/agent_instructions/dotclaude/settings.json.template +29 -0
- package/dist/templates/project/.env.example.template +9 -0
- package/dist/templates/project/.gitignore.template +35 -0
- package/dist/templates/project/README.md.template +100 -0
- package/dist/templates/project/config/costs.yml.template +29 -0
- package/dist/templates/project/package.json.template +25 -0
- package/dist/templates/project/src/clients/jina.ts.template +30 -0
- package/dist/templates/project/src/shared/utils/string.ts.template +3 -0
- package/dist/templates/project/src/shared/utils/url.ts.template +15 -0
- package/dist/templates/project/src/workflows/blog_evaluator/evaluators.ts.template +23 -0
- package/dist/templates/project/src/workflows/blog_evaluator/prompts/signal_noise@v1.prompt.template +26 -0
- package/dist/templates/project/src/workflows/blog_evaluator/scenarios/paulgraham_hwh.json.template +3 -0
- package/dist/templates/project/src/workflows/blog_evaluator/steps.ts.template +27 -0
- package/dist/templates/project/src/workflows/blog_evaluator/types.ts.template +30 -0
- package/dist/templates/project/src/workflows/blog_evaluator/utils.ts.template +15 -0
- package/dist/templates/project/src/workflows/blog_evaluator/workflow.ts.template +27 -0
- package/dist/templates/project/tsconfig.json.template +20 -0
- package/dist/templates/workflow/README.md.template +216 -0
- package/dist/templates/workflow/evaluators.ts.template +21 -0
- package/dist/templates/workflow/prompts/example@v1.prompt.template +15 -0
- package/dist/templates/workflow/scenarios/test_input.json.template +3 -0
- package/dist/templates/workflow/steps.ts.template +20 -0
- package/dist/templates/workflow/types.ts.template +13 -0
- package/dist/templates/workflow/workflow.ts.template +23 -0
- package/dist/test_helpers/mocks.d.ts +38 -0
- package/dist/test_helpers/mocks.js +77 -0
- package/dist/types/cost.d.ts +149 -0
- package/dist/types/cost.js +6 -0
- package/dist/types/domain.d.ts +20 -0
- package/dist/types/domain.js +4 -0
- package/dist/types/errors.d.ts +68 -0
- package/dist/types/errors.js +100 -0
- package/dist/types/errors.spec.d.ts +1 -0
- package/dist/types/errors.spec.js +18 -0
- package/dist/types/generator.d.ts +26 -0
- package/dist/types/generator.js +1 -0
- package/dist/types/trace.d.ts +161 -0
- package/dist/types/trace.js +18 -0
- package/dist/utils/claude.d.ts +5 -0
- package/dist/utils/claude.js +19 -0
- package/dist/utils/claude.spec.d.ts +1 -0
- package/dist/utils/claude.spec.js +119 -0
- package/dist/utils/constants.d.ts +5 -0
- package/dist/utils/constants.js +4 -0
- package/dist/utils/cost_formatter.d.ts +5 -0
- package/dist/utils/cost_formatter.js +218 -0
- package/dist/utils/date_formatter.d.ts +23 -0
- package/dist/utils/date_formatter.js +49 -0
- package/dist/utils/env_loader.d.ts +1 -0
- package/dist/utils/env_loader.js +22 -0
- package/dist/utils/env_loader.spec.d.ts +1 -0
- package/dist/utils/env_loader.spec.js +43 -0
- package/dist/utils/error_handler.d.ts +8 -0
- package/dist/utils/error_handler.js +71 -0
- package/dist/utils/error_utils.d.ts +24 -0
- package/dist/utils/error_utils.js +87 -0
- package/dist/utils/file_system.d.ts +3 -0
- package/dist/utils/file_system.js +33 -0
- package/dist/utils/format_workflow_result.d.ts +5 -0
- package/dist/utils/format_workflow_result.js +18 -0
- package/dist/utils/format_workflow_result.spec.d.ts +1 -0
- package/dist/utils/format_workflow_result.spec.js +81 -0
- package/dist/utils/framework_version.d.ts +4 -0
- package/dist/utils/framework_version.js +4 -0
- package/dist/utils/framework_version.spec.d.ts +1 -0
- package/dist/utils/framework_version.spec.js +13 -0
- package/dist/utils/header_utils.d.ts +12 -0
- package/dist/utils/header_utils.js +29 -0
- package/dist/utils/header_utils.spec.d.ts +1 -0
- package/dist/utils/header_utils.spec.js +52 -0
- package/dist/utils/input_parser.d.ts +1 -0
- package/dist/utils/input_parser.js +19 -0
- package/dist/utils/output_formatter.d.ts +2 -0
- package/dist/utils/output_formatter.js +11 -0
- package/dist/utils/paths.d.ts +25 -0
- package/dist/utils/paths.js +36 -0
- package/dist/utils/process.d.ts +4 -0
- package/dist/utils/process.js +50 -0
- package/dist/utils/resolve_input.d.ts +1 -0
- package/dist/utils/resolve_input.js +22 -0
- package/dist/utils/scenario_resolver.d.ts +9 -0
- package/dist/utils/scenario_resolver.js +93 -0
- package/dist/utils/scenario_resolver.spec.d.ts +1 -0
- package/dist/utils/scenario_resolver.spec.js +214 -0
- package/dist/utils/secret_sanitizer.d.ts +1 -0
- package/dist/utils/secret_sanitizer.js +29 -0
- package/dist/utils/sleep.d.ts +5 -0
- package/dist/utils/sleep.js +5 -0
- package/dist/utils/template.d.ts +9 -0
- package/dist/utils/template.js +30 -0
- package/dist/utils/template.spec.d.ts +1 -0
- package/dist/utils/template.spec.js +77 -0
- package/dist/utils/trace_extractor.d.ts +27 -0
- package/dist/utils/trace_extractor.js +53 -0
- package/dist/utils/trace_formatter.d.ts +11 -0
- package/dist/utils/trace_formatter.js +402 -0
- package/dist/utils/validation.d.ts +13 -0
- package/dist/utils/validation.js +25 -0
- package/dist/utils/validation.spec.d.ts +1 -0
- package/dist/utils/validation.spec.js +140 -0
- package/package.json +4 -4
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { resolveScenarioPath, getScenarioNotFoundMessage, extractWorkflowRelativePath, listScenariosForWorkflow } from './scenario_resolver.js';
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import * as api from '#api/generated/api.js';
|
|
5
|
+
vi.mock('node:fs', () => ({
|
|
6
|
+
existsSync: vi.fn(),
|
|
7
|
+
readdirSync: vi.fn()
|
|
8
|
+
}));
|
|
9
|
+
vi.mock('#api/generated/api.js', () => ({
|
|
10
|
+
getWorkflowCatalog: vi.fn()
|
|
11
|
+
}));
|
|
12
|
+
function mockCatalog(workflows) {
|
|
13
|
+
vi.mocked(api.getWorkflowCatalog).mockResolvedValue({
|
|
14
|
+
data: { workflows },
|
|
15
|
+
status: 200,
|
|
16
|
+
headers: new Headers()
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function mockCatalogFailure() {
|
|
20
|
+
vi.mocked(api.getWorkflowCatalog).mockRejectedValue(new Error('API unavailable'));
|
|
21
|
+
}
|
|
22
|
+
describe('extractWorkflowRelativePath', () => {
|
|
23
|
+
it('should extract relative path from workflow.js path', () => {
|
|
24
|
+
expect(extractWorkflowRelativePath('/app/dist/workflows/basic_research/workflow.js'))
|
|
25
|
+
.toBe('basic_research');
|
|
26
|
+
});
|
|
27
|
+
it('should extract nested relative path', () => {
|
|
28
|
+
expect(extractWorkflowRelativePath('/app/dist/workflows/viz_examples/01_simple_linear/workflow.js'))
|
|
29
|
+
.toBe('viz_examples/01_simple_linear');
|
|
30
|
+
});
|
|
31
|
+
it('should handle workflow.ts extension', () => {
|
|
32
|
+
expect(extractWorkflowRelativePath('/src/workflows/my_flow/workflow.ts'))
|
|
33
|
+
.toBe('my_flow');
|
|
34
|
+
});
|
|
35
|
+
it('should return null for non-matching paths', () => {
|
|
36
|
+
expect(extractWorkflowRelativePath('/app/dist/other/workflow.js')).toBeNull();
|
|
37
|
+
expect(extractWorkflowRelativePath('/app/dist/workflows/')).toBeNull();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
describe('resolveScenarioPath', () => {
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
vi.resetAllMocks();
|
|
43
|
+
});
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
vi.restoreAllMocks();
|
|
46
|
+
});
|
|
47
|
+
describe('when API returns workflow with path', () => {
|
|
48
|
+
it('should resolve scenario using catalog path', async () => {
|
|
49
|
+
mockCatalog([{ name: 'my_workflow', path: '/app/dist/workflows/my_workflow/workflow.js' }]);
|
|
50
|
+
vi.mocked(fs.existsSync).mockImplementation(path => {
|
|
51
|
+
return String(path).includes('src/workflows/my_workflow/scenarios/test_scenario.json');
|
|
52
|
+
});
|
|
53
|
+
const result = await resolveScenarioPath('my_workflow', 'test_scenario', '/project');
|
|
54
|
+
expect(result.found).toBe(true);
|
|
55
|
+
expect(result.path).toContain('src/workflows/my_workflow/scenarios/test_scenario.json');
|
|
56
|
+
});
|
|
57
|
+
it('should resolve when workflow name differs from folder name', async () => {
|
|
58
|
+
mockCatalog([{ name: 'simpleLinear', path: '/app/dist/workflows/viz_examples/01_simple_linear/workflow.js' }]);
|
|
59
|
+
vi.mocked(fs.existsSync).mockImplementation(path => {
|
|
60
|
+
return String(path).includes('src/workflows/viz_examples/01_simple_linear/scenarios/basic.json');
|
|
61
|
+
});
|
|
62
|
+
const result = await resolveScenarioPath('simpleLinear', 'basic', '/project');
|
|
63
|
+
expect(result.found).toBe(true);
|
|
64
|
+
expect(result.path).toContain('src/workflows/viz_examples/01_simple_linear/scenarios/basic.json');
|
|
65
|
+
});
|
|
66
|
+
it('should handle nested workflow directories', async () => {
|
|
67
|
+
mockCatalog([{ name: 'deep_flow', path: '/app/dist/workflows/category/sub/deep_flow/workflow.js' }]);
|
|
68
|
+
vi.mocked(fs.existsSync).mockImplementation(path => {
|
|
69
|
+
return String(path).includes('src/workflows/category/sub/deep_flow/scenarios/test.json');
|
|
70
|
+
});
|
|
71
|
+
const result = await resolveScenarioPath('deep_flow', 'test', '/project');
|
|
72
|
+
expect(result.found).toBe(true);
|
|
73
|
+
expect(result.path).toContain('src/workflows/category/sub/deep_flow/scenarios/test.json');
|
|
74
|
+
});
|
|
75
|
+
it('should return not found when scenario file does not exist', async () => {
|
|
76
|
+
mockCatalog([{ name: 'my_workflow', path: '/app/dist/workflows/my_workflow/workflow.js' }]);
|
|
77
|
+
vi.mocked(fs.existsSync).mockReturnValue(false);
|
|
78
|
+
const result = await resolveScenarioPath('my_workflow', 'missing', '/project');
|
|
79
|
+
expect(result.found).toBe(false);
|
|
80
|
+
expect(result.searchedPaths.length).toBeGreaterThanOrEqual(2);
|
|
81
|
+
});
|
|
82
|
+
it('should fall back to convention when catalog path has no scenario but convention does', async () => {
|
|
83
|
+
mockCatalog([{ name: 'renamedFlow', path: '/app/dist/workflows/actual_folder/workflow.js' }]);
|
|
84
|
+
vi.mocked(fs.existsSync).mockImplementation(path => {
|
|
85
|
+
// Not found at catalog-resolved path, but found at convention path
|
|
86
|
+
return String(path).includes('src/workflows/renamedFlow/scenarios/test.json');
|
|
87
|
+
});
|
|
88
|
+
const result = await resolveScenarioPath('renamedFlow', 'test', '/project');
|
|
89
|
+
expect(result.found).toBe(true);
|
|
90
|
+
expect(result.path).toContain('src/workflows/renamedFlow/scenarios/test.json');
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
describe('when API is unavailable', () => {
|
|
94
|
+
it('should fall back to convention-based lookup', async () => {
|
|
95
|
+
mockCatalogFailure();
|
|
96
|
+
vi.mocked(fs.existsSync).mockImplementation(path => {
|
|
97
|
+
return String(path).includes('src/workflows/my_workflow/scenarios/test_scenario.json');
|
|
98
|
+
});
|
|
99
|
+
const result = await resolveScenarioPath('my_workflow', 'test_scenario', '/project');
|
|
100
|
+
expect(result.found).toBe(true);
|
|
101
|
+
expect(result.path).toContain('src/workflows/my_workflow/scenarios/test_scenario.json');
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
describe('when workflow is not in catalog', () => {
|
|
105
|
+
it('should fall back to convention-based lookup', async () => {
|
|
106
|
+
mockCatalog([{ name: 'other_workflow', path: '/app/dist/workflows/other/workflow.js' }]);
|
|
107
|
+
vi.mocked(fs.existsSync).mockImplementation(path => {
|
|
108
|
+
return String(path).includes('src/workflows/my_workflow/scenarios/test.json');
|
|
109
|
+
});
|
|
110
|
+
const result = await resolveScenarioPath('my_workflow', 'test', '/project');
|
|
111
|
+
expect(result.found).toBe(true);
|
|
112
|
+
expect(result.path).toContain('src/workflows/my_workflow/scenarios/test.json');
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
describe('json extension normalization', () => {
|
|
116
|
+
it('should handle scenario name with .json extension', async () => {
|
|
117
|
+
mockCatalogFailure();
|
|
118
|
+
vi.mocked(fs.existsSync).mockImplementation(path => {
|
|
119
|
+
return String(path).includes('src/workflows/my_workflow/scenarios/test_scenario.json');
|
|
120
|
+
});
|
|
121
|
+
const result = await resolveScenarioPath('my_workflow', 'test_scenario.json', '/project');
|
|
122
|
+
expect(result.found).toBe(true);
|
|
123
|
+
expect(result.path).toContain('test_scenario.json');
|
|
124
|
+
expect(result.path).not.toContain('test_scenario.json.json');
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
describe('workflows fallback directory', () => {
|
|
128
|
+
it('should find scenario in workflows/ fallback path', async () => {
|
|
129
|
+
mockCatalogFailure();
|
|
130
|
+
vi.mocked(fs.existsSync).mockImplementation(path => {
|
|
131
|
+
return String(path).includes('workflows/my_workflow/scenarios/test_scenario.json') &&
|
|
132
|
+
!String(path).includes('src/workflows');
|
|
133
|
+
});
|
|
134
|
+
const result = await resolveScenarioPath('my_workflow', 'test_scenario', '/project');
|
|
135
|
+
expect(result.found).toBe(true);
|
|
136
|
+
expect(result.path).toContain('workflows/my_workflow/scenarios/test_scenario.json');
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
describe('subdirectory scenarios', () => {
|
|
140
|
+
it('should support subdirectory paths in scenario name', async () => {
|
|
141
|
+
mockCatalogFailure();
|
|
142
|
+
vi.mocked(fs.existsSync).mockImplementation(path => {
|
|
143
|
+
return String(path).includes('src/workflows/my_workflow/scenarios/complex/deep_test.json');
|
|
144
|
+
});
|
|
145
|
+
const result = await resolveScenarioPath('my_workflow', 'complex/deep_test', '/project');
|
|
146
|
+
expect(result.found).toBe(true);
|
|
147
|
+
expect(result.path).toContain('complex/deep_test.json');
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
describe('listScenariosForWorkflow', () => {
|
|
152
|
+
beforeEach(() => {
|
|
153
|
+
vi.resetAllMocks();
|
|
154
|
+
});
|
|
155
|
+
it('should return scenario names from scenarios directory', () => {
|
|
156
|
+
vi.mocked(fs.existsSync).mockImplementation(path => String(path).includes('src/workflows/my_workflow/scenarios'));
|
|
157
|
+
vi.mocked(fs.readdirSync).mockReturnValue(['basic.json', 'advanced.json', 'README.md']);
|
|
158
|
+
const result = listScenariosForWorkflow('my_workflow', undefined, '/project');
|
|
159
|
+
expect(result).toEqual(['basic', 'advanced']);
|
|
160
|
+
});
|
|
161
|
+
it('should use workflowPath from catalog to derive directory', () => {
|
|
162
|
+
vi.mocked(fs.existsSync).mockImplementation(path => String(path).includes('src/workflows/viz_examples/01_simple_linear/scenarios'));
|
|
163
|
+
vi.mocked(fs.readdirSync).mockReturnValue(['test.json']);
|
|
164
|
+
const result = listScenariosForWorkflow('simpleLinear', '/app/dist/workflows/viz_examples/01_simple_linear/workflow.js', '/project');
|
|
165
|
+
expect(result).toEqual(['test']);
|
|
166
|
+
});
|
|
167
|
+
it('should fall back to workflowName when workflowPath is undefined', () => {
|
|
168
|
+
vi.mocked(fs.existsSync).mockImplementation(path => String(path).includes('src/workflows/my_workflow/scenarios'));
|
|
169
|
+
vi.mocked(fs.readdirSync).mockReturnValue(['scenario_a.json']);
|
|
170
|
+
const result = listScenariosForWorkflow('my_workflow', undefined, '/project');
|
|
171
|
+
expect(result).toEqual(['scenario_a']);
|
|
172
|
+
});
|
|
173
|
+
it('should fall back to workflowName when path extraction returns null', () => {
|
|
174
|
+
vi.mocked(fs.existsSync).mockImplementation(path => String(path).includes('src/workflows/my_workflow/scenarios'));
|
|
175
|
+
vi.mocked(fs.readdirSync).mockReturnValue(['test.json']);
|
|
176
|
+
const result = listScenariosForWorkflow('my_workflow', '/invalid/path.js', '/project');
|
|
177
|
+
expect(result).toEqual(['test']);
|
|
178
|
+
});
|
|
179
|
+
it('should return empty array when no scenarios directory exists', () => {
|
|
180
|
+
vi.mocked(fs.existsSync).mockReturnValue(false);
|
|
181
|
+
const result = listScenariosForWorkflow('my_workflow', undefined, '/project');
|
|
182
|
+
expect(result).toEqual([]);
|
|
183
|
+
});
|
|
184
|
+
it('should try workflows/ fallback when src/workflows/ does not exist', () => {
|
|
185
|
+
vi.mocked(fs.existsSync).mockImplementation(path => {
|
|
186
|
+
const p = String(path);
|
|
187
|
+
return p.includes('workflows/my_workflow/scenarios') && !p.includes('src/workflows');
|
|
188
|
+
});
|
|
189
|
+
vi.mocked(fs.readdirSync).mockReturnValue(['fallback.json']);
|
|
190
|
+
const result = listScenariosForWorkflow('my_workflow', undefined, '/project');
|
|
191
|
+
expect(result).toEqual(['fallback']);
|
|
192
|
+
});
|
|
193
|
+
it('should return empty array for empty scenarios directory', () => {
|
|
194
|
+
vi.mocked(fs.existsSync).mockReturnValue(true);
|
|
195
|
+
vi.mocked(fs.readdirSync).mockReturnValue([]);
|
|
196
|
+
const result = listScenariosForWorkflow('my_workflow', undefined, '/project');
|
|
197
|
+
expect(result).toEqual([]);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
describe('getScenarioNotFoundMessage', () => {
|
|
201
|
+
it('should return a helpful error message', () => {
|
|
202
|
+
const searchedPaths = [
|
|
203
|
+
'/project/src/workflows/my_workflow/scenarios/test.json',
|
|
204
|
+
'/project/workflows/my_workflow/scenarios/test.json'
|
|
205
|
+
];
|
|
206
|
+
const message = getScenarioNotFoundMessage('my_workflow', 'test', searchedPaths);
|
|
207
|
+
expect(message).toContain('Scenario \'test\' not found for workflow \'my_workflow\'');
|
|
208
|
+
expect(message).toContain('Searched in:');
|
|
209
|
+
expect(message).toContain(searchedPaths[0]);
|
|
210
|
+
expect(message).toContain(searchedPaths[1]);
|
|
211
|
+
expect(message).toContain('Tip:');
|
|
212
|
+
expect(message).toContain('--input');
|
|
213
|
+
});
|
|
214
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function sanitizeSecrets(value: unknown): unknown;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { DeepRedact } from '@hackylabs/deep-redact/index.ts';
|
|
2
|
+
const redactor = new DeepRedact({
|
|
3
|
+
blacklistedKeys: [
|
|
4
|
+
/^(.*_)?(secret|password|passwd|credential|private_key)(_.*)?$/i,
|
|
5
|
+
/^(.*_)?(api_key|apikey|access_key|auth_token)(_.*)?$/i
|
|
6
|
+
],
|
|
7
|
+
stringTests: [
|
|
8
|
+
{
|
|
9
|
+
pattern: /sk-[a-zA-Z0-9_-]{20,}/g,
|
|
10
|
+
replacer: (value, pattern) => value.replace(pattern, 'sk-***REDACTED***')
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
pattern: /AKIA[A-Z0-9]{16}/g,
|
|
14
|
+
replacer: (value, pattern) => value.replace(pattern, 'AKIA***REDACTED***')
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
pattern: /eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}/g,
|
|
18
|
+
replacer: (value, pattern) => value.replace(pattern, '***JWT_REDACTED***')
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
pattern: /Bearer\s+[a-zA-Z0-9_.-]{20,}/g,
|
|
22
|
+
replacer: (value, pattern) => value.replace(pattern, 'Bearer ***REDACTED***')
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
serialize: false
|
|
26
|
+
});
|
|
27
|
+
export function sanitizeSecrets(value) {
|
|
28
|
+
return redactor.redact(value);
|
|
29
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process a template string with variables
|
|
3
|
+
*/
|
|
4
|
+
export declare function processTemplate(templateContent: string, variables: Record<string, string>): string;
|
|
5
|
+
/**
|
|
6
|
+
* Prepare template variables from workflow name and description
|
|
7
|
+
*/
|
|
8
|
+
export declare function prepareTemplateVariables(workflowName: string, description: string): Record<string, string>;
|
|
9
|
+
export { camelCase, pascalCase } from 'change-case';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import Handlebars from 'handlebars';
|
|
2
|
+
import { camelCase, pascalCase } from 'change-case';
|
|
3
|
+
/**
|
|
4
|
+
* Create a Handlebars compiler with custom helpers
|
|
5
|
+
*/
|
|
6
|
+
function createCompiler() {
|
|
7
|
+
const compiler = Handlebars.create();
|
|
8
|
+
compiler.registerHelper('camelCase', (str) => camelCase(str));
|
|
9
|
+
compiler.registerHelper('pascalCase', (str) => pascalCase(str));
|
|
10
|
+
return compiler;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Process a template string with variables
|
|
14
|
+
*/
|
|
15
|
+
export function processTemplate(templateContent, variables) {
|
|
16
|
+
const compiler = createCompiler();
|
|
17
|
+
const template = compiler.compile(templateContent);
|
|
18
|
+
return template(variables);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Prepare template variables from workflow name and description
|
|
22
|
+
*/
|
|
23
|
+
export function prepareTemplateVariables(workflowName, description) {
|
|
24
|
+
return {
|
|
25
|
+
workflowName: camelCase(workflowName),
|
|
26
|
+
WorkflowName: pascalCase(workflowName),
|
|
27
|
+
description: description || `A ${workflowName} workflow`
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export { camelCase, pascalCase } from 'change-case';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { camelCase, pascalCase, processTemplate, prepareTemplateVariables } from './template.js';
|
|
3
|
+
describe('Template Utilities', () => {
|
|
4
|
+
describe('pascalCase', () => {
|
|
5
|
+
it('should convert kebab-case to PascalCase', () => {
|
|
6
|
+
expect(pascalCase('my-workflow-name')).toBe('MyWorkflowName');
|
|
7
|
+
});
|
|
8
|
+
it('should convert snake_case to PascalCase', () => {
|
|
9
|
+
expect(pascalCase('my_workflow_name')).toBe('MyWorkflowName');
|
|
10
|
+
});
|
|
11
|
+
it('should handle single word', () => {
|
|
12
|
+
expect(pascalCase('workflow')).toBe('Workflow');
|
|
13
|
+
});
|
|
14
|
+
it('should handle mixed separators', () => {
|
|
15
|
+
expect(pascalCase('my-workflow_name')).toBe('MyWorkflowName');
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
describe('camelCase', () => {
|
|
19
|
+
it('should convert kebab-case to camelCase', () => {
|
|
20
|
+
expect(camelCase('my-workflow-name')).toBe('myWorkflowName');
|
|
21
|
+
});
|
|
22
|
+
it('should convert snake_case to camelCase', () => {
|
|
23
|
+
expect(camelCase('my_workflow_name')).toBe('myWorkflowName');
|
|
24
|
+
});
|
|
25
|
+
it('should handle single word', () => {
|
|
26
|
+
expect(camelCase('workflow')).toBe('workflow');
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
describe('processTemplate', () => {
|
|
30
|
+
it('should replace single variable', () => {
|
|
31
|
+
const template = 'Hello {{name}}!';
|
|
32
|
+
const variables = { name: 'World' };
|
|
33
|
+
expect(processTemplate(template, variables)).toBe('Hello World!');
|
|
34
|
+
});
|
|
35
|
+
it('should replace multiple variables', () => {
|
|
36
|
+
const template = 'Workflow {{workflowName}} - {{description}}';
|
|
37
|
+
const variables = {
|
|
38
|
+
workflowName: 'myWorkflow',
|
|
39
|
+
description: 'A test workflow'
|
|
40
|
+
};
|
|
41
|
+
expect(processTemplate(template, variables))
|
|
42
|
+
.toBe('Workflow myWorkflow - A test workflow');
|
|
43
|
+
});
|
|
44
|
+
it('should replace multiple occurrences of same variable', () => {
|
|
45
|
+
const template = '{{name}} says hello to {{name}}';
|
|
46
|
+
const variables = { name: 'Alice' };
|
|
47
|
+
expect(processTemplate(template, variables))
|
|
48
|
+
.toBe('Alice says hello to Alice');
|
|
49
|
+
});
|
|
50
|
+
it('should use Handlebars helpers for case transformations', () => {
|
|
51
|
+
const template = '{{camelCase name}} and {{pascalCase name}}';
|
|
52
|
+
const variables = { name: 'my-workflow-name' };
|
|
53
|
+
expect(processTemplate(template, variables))
|
|
54
|
+
.toBe('myWorkflowName and MyWorkflowName');
|
|
55
|
+
});
|
|
56
|
+
it('should preserve escaped curly braces for Liquid.js examples', () => {
|
|
57
|
+
const template = 'Use Liquid: \\{{ variable }} and {% if %}...{% endif %}';
|
|
58
|
+
const variables = {};
|
|
59
|
+
expect(processTemplate(template, variables))
|
|
60
|
+
.toBe('Use Liquid: {{ variable }} and {% if %}...{% endif %}');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
describe('prepareTemplateVariables', () => {
|
|
64
|
+
it('should prepare variables from workflow name and description', () => {
|
|
65
|
+
const variables = prepareTemplateVariables('my-test-workflow', 'A test workflow for testing');
|
|
66
|
+
expect(variables).toEqual({
|
|
67
|
+
workflowName: 'myTestWorkflow',
|
|
68
|
+
WorkflowName: 'MyTestWorkflow',
|
|
69
|
+
description: 'A test workflow for testing'
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
it('should provide default description if not provided', () => {
|
|
73
|
+
const variables = prepareTemplateVariables('test-workflow', '');
|
|
74
|
+
expect(variables.description).toBe('A test-workflow workflow');
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface ExtractedDataset {
|
|
2
|
+
input: Record<string, unknown>;
|
|
3
|
+
output: unknown;
|
|
4
|
+
executionTimeMs?: number;
|
|
5
|
+
}
|
|
6
|
+
interface TraceFile {
|
|
7
|
+
input?: unknown;
|
|
8
|
+
output?: unknown;
|
|
9
|
+
startedAt?: number;
|
|
10
|
+
endedAt?: number;
|
|
11
|
+
duration?: number;
|
|
12
|
+
root?: {
|
|
13
|
+
startTime?: number;
|
|
14
|
+
endTime?: number;
|
|
15
|
+
duration?: number;
|
|
16
|
+
};
|
|
17
|
+
events?: Array<{
|
|
18
|
+
phase: string;
|
|
19
|
+
details?: unknown;
|
|
20
|
+
}>;
|
|
21
|
+
children?: Array<{
|
|
22
|
+
input?: unknown;
|
|
23
|
+
output?: unknown;
|
|
24
|
+
}>;
|
|
25
|
+
}
|
|
26
|
+
export declare function extractDatasetFromTrace(traceData: TraceFile): ExtractedDataset;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
function isRecord(value) {
|
|
2
|
+
return value !== null && value !== undefined && typeof value === 'object';
|
|
3
|
+
}
|
|
4
|
+
function unwrapOutput(output) {
|
|
5
|
+
if (isRecord(output) && 'output' in output && 'trace' in output) {
|
|
6
|
+
return output.output;
|
|
7
|
+
}
|
|
8
|
+
return output;
|
|
9
|
+
}
|
|
10
|
+
function findInput(trace) {
|
|
11
|
+
if (isRecord(trace.input)) {
|
|
12
|
+
return trace.input;
|
|
13
|
+
}
|
|
14
|
+
const startEvent = trace.events?.find(e => e.phase === 'start');
|
|
15
|
+
const eventInput = startEvent?.details?.input;
|
|
16
|
+
if (isRecord(eventInput)) {
|
|
17
|
+
return eventInput;
|
|
18
|
+
}
|
|
19
|
+
const childInput = trace.children?.[0]?.input;
|
|
20
|
+
return isRecord(childInput) ? childInput : undefined;
|
|
21
|
+
}
|
|
22
|
+
function findOutput(trace) {
|
|
23
|
+
if (trace.output !== undefined) {
|
|
24
|
+
return unwrapOutput(trace.output);
|
|
25
|
+
}
|
|
26
|
+
const endEvent = trace.events ?
|
|
27
|
+
[...trace.events].reverse().find(e => e.phase === 'end') :
|
|
28
|
+
undefined;
|
|
29
|
+
const eventOutput = endEvent?.details?.output;
|
|
30
|
+
if (eventOutput !== undefined) {
|
|
31
|
+
return unwrapOutput(eventOutput);
|
|
32
|
+
}
|
|
33
|
+
const childOutput = trace.children?.[0]?.output;
|
|
34
|
+
return childOutput !== undefined ? unwrapOutput(childOutput) : undefined;
|
|
35
|
+
}
|
|
36
|
+
function computeExecutionTime(trace) {
|
|
37
|
+
if (trace.startedAt && trace.endedAt) {
|
|
38
|
+
return trace.endedAt - trace.startedAt;
|
|
39
|
+
}
|
|
40
|
+
if (trace.root?.startTime && trace.root?.endTime) {
|
|
41
|
+
return trace.root.endTime - trace.root.startTime;
|
|
42
|
+
}
|
|
43
|
+
return trace.duration ?? trace.root?.duration ?? undefined;
|
|
44
|
+
}
|
|
45
|
+
export function extractDatasetFromTrace(traceData) {
|
|
46
|
+
const input = findInput(traceData);
|
|
47
|
+
if (!input) {
|
|
48
|
+
throw new Error('Could not extract input from trace data. Trace may be incomplete.');
|
|
49
|
+
}
|
|
50
|
+
const output = findOutput(traceData);
|
|
51
|
+
const executionTimeMs = computeExecutionTime(traceData);
|
|
52
|
+
return { input, output, executionTimeMs };
|
|
53
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { formatDuration } from '#utils/date_formatter.js';
|
|
2
|
+
export { formatDuration };
|
|
3
|
+
export declare function format(traceData: string | object, outputFormat?: 'json' | 'text'): string;
|
|
4
|
+
export declare function getSummary(traceData: string | object): {
|
|
5
|
+
totalDuration: number;
|
|
6
|
+
totalEvents: number;
|
|
7
|
+
totalSteps: number;
|
|
8
|
+
totalActivities: number;
|
|
9
|
+
hasErrors: boolean;
|
|
10
|
+
};
|
|
11
|
+
export declare function displayDebugTree(node: unknown): string;
|