@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,23 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
vi.mock('../../api/generated/api.js', () => ({
|
|
3
|
+
getWorkflowIdResult: vi.fn()
|
|
4
|
+
}));
|
|
5
|
+
describe('workflow result command', () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
vi.clearAllMocks();
|
|
8
|
+
});
|
|
9
|
+
describe('command definition', () => {
|
|
10
|
+
it('should export a valid OCLIF command', async () => {
|
|
11
|
+
const WorkflowResult = (await import('./result.js')).default;
|
|
12
|
+
expect(WorkflowResult).toBeDefined();
|
|
13
|
+
expect(WorkflowResult.description).toContain('Get workflow execution result');
|
|
14
|
+
expect(WorkflowResult.args).toHaveProperty('workflowId');
|
|
15
|
+
expect(WorkflowResult.flags).toHaveProperty('format');
|
|
16
|
+
});
|
|
17
|
+
it('should have correct flag configuration', async () => {
|
|
18
|
+
const WorkflowResult = (await import('./result.js')).default;
|
|
19
|
+
expect(WorkflowResult.flags.format.options).toEqual(['json', 'text']);
|
|
20
|
+
expect(WorkflowResult.flags.format.default).toBe('text');
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class WorkflowRun extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
workflowName: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
scenario: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
8
|
+
};
|
|
9
|
+
static flags: {
|
|
10
|
+
input: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
'task-queue': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
};
|
|
14
|
+
run(): Promise<void>;
|
|
15
|
+
catch(error: Error): Promise<void>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import { postWorkflowRun } from '#api/generated/api.js';
|
|
3
|
+
import { OUTPUT_FORMAT } from '#utils/constants.js';
|
|
4
|
+
import { formatOutput } from '#utils/output_formatter.js';
|
|
5
|
+
import { formatWorkflowResult, ERROR_STATUSES } from '#utils/format_workflow_result.js';
|
|
6
|
+
import { handleApiError } from '#utils/error_handler.js';
|
|
7
|
+
import { resolveInput } from '#utils/resolve_input.js';
|
|
8
|
+
import { getRetryDelayFromResponse } from '#utils/header_utils.js';
|
|
9
|
+
import { sleep } from '#utils/sleep.js';
|
|
10
|
+
import { HttpError } from '#api/http_client.js';
|
|
11
|
+
const MAX_RETRIES = 3;
|
|
12
|
+
async function executeWorkflow(args) {
|
|
13
|
+
const { body, options, log, attempt = 0 } = args;
|
|
14
|
+
try {
|
|
15
|
+
return await postWorkflowRun(body, options);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
if (!(error instanceof HttpError) || attempt >= MAX_RETRIES) {
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
21
|
+
const { response } = error;
|
|
22
|
+
const delay = getRetryDelayFromResponse(response);
|
|
23
|
+
if (delay === null) {
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
log(`Server returned ${response.status} with header "Retry-After". Retrying in ${delay / 1000}s...`);
|
|
27
|
+
await sleep(delay);
|
|
28
|
+
return executeWorkflow({ body, options, log, attempt: attempt + 1 });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export default class WorkflowRun extends Command {
|
|
32
|
+
static description = 'Execute a workflow synchronously and wait for completion';
|
|
33
|
+
static examples = [
|
|
34
|
+
'<%= config.bin %> <%= command.id %> simple basic_input',
|
|
35
|
+
'<%= config.bin %> <%= command.id %> simple my_scenario --format json',
|
|
36
|
+
'<%= config.bin %> <%= command.id %> simple --input \'{"values":[1,2,3]}\'',
|
|
37
|
+
'<%= config.bin %> <%= command.id %> simple --input input.json',
|
|
38
|
+
'<%= config.bin %> <%= command.id %> simple --input \'{"key":"value"}\' --task-queue my-queue'
|
|
39
|
+
];
|
|
40
|
+
static args = {
|
|
41
|
+
workflowName: Args.string({
|
|
42
|
+
description: 'Name of the workflow to execute',
|
|
43
|
+
required: true
|
|
44
|
+
}),
|
|
45
|
+
scenario: Args.string({
|
|
46
|
+
description: 'Scenario name (resolved from the workflow\'s scenarios/ directory)',
|
|
47
|
+
required: false
|
|
48
|
+
})
|
|
49
|
+
};
|
|
50
|
+
static flags = {
|
|
51
|
+
input: Flags.string({
|
|
52
|
+
char: 'i',
|
|
53
|
+
description: 'Workflow input as JSON string or file path (overrides scenario)',
|
|
54
|
+
required: false
|
|
55
|
+
}),
|
|
56
|
+
'task-queue': Flags.string({
|
|
57
|
+
char: 'q',
|
|
58
|
+
description: 'Task queue name for workflow execution'
|
|
59
|
+
}),
|
|
60
|
+
format: Flags.string({
|
|
61
|
+
char: 'f',
|
|
62
|
+
description: 'Output format',
|
|
63
|
+
options: [OUTPUT_FORMAT.JSON, OUTPUT_FORMAT.TEXT],
|
|
64
|
+
default: OUTPUT_FORMAT.TEXT
|
|
65
|
+
})
|
|
66
|
+
};
|
|
67
|
+
async run() {
|
|
68
|
+
const { args, flags } = await this.parse(WorkflowRun);
|
|
69
|
+
const input = await resolveInput(args.workflowName, args.scenario, flags.input, 'run');
|
|
70
|
+
this.log(`Executing workflow: ${args.workflowName}...`);
|
|
71
|
+
const response = await executeWorkflow({
|
|
72
|
+
body: {
|
|
73
|
+
workflowName: args.workflowName,
|
|
74
|
+
input,
|
|
75
|
+
taskQueue: flags['task-queue']
|
|
76
|
+
},
|
|
77
|
+
options: { config: { timeout: 600000 } },
|
|
78
|
+
log: msg => this.log(msg)
|
|
79
|
+
});
|
|
80
|
+
if (!response || !response.data) {
|
|
81
|
+
this.error('API returned invalid response', { exit: 1 });
|
|
82
|
+
}
|
|
83
|
+
const data = response.data;
|
|
84
|
+
const output = formatOutput(data, flags.format, formatWorkflowResult);
|
|
85
|
+
this.log(`\n${output}`);
|
|
86
|
+
if (ERROR_STATUSES.has(data.status)) {
|
|
87
|
+
process.exitCode = 1;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async catch(error) {
|
|
91
|
+
return handleApiError(error, (...args) => this.error(...args), {
|
|
92
|
+
404: 'Workflow not found. Check the workflow name.',
|
|
93
|
+
500: 'Workflow execution failed.',
|
|
94
|
+
503: 'Workflow service temporarily unavailable.'
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
|
+
import { HttpError } from '#api/http_client.js';
|
|
4
|
+
vi.mock('#api/generated/api.js', () => ({
|
|
5
|
+
postWorkflowRun: vi.fn()
|
|
6
|
+
}));
|
|
7
|
+
vi.mock('#utils/resolve_input.js', () => ({
|
|
8
|
+
resolveInput: vi.fn()
|
|
9
|
+
}));
|
|
10
|
+
vi.mock('#utils/sleep.js', () => ({
|
|
11
|
+
sleep: vi.fn().mockResolvedValue(undefined)
|
|
12
|
+
}));
|
|
13
|
+
describe('workflow run command', () => {
|
|
14
|
+
beforeEach(async () => {
|
|
15
|
+
vi.clearAllMocks();
|
|
16
|
+
const { resolveInput } = await import('#utils/resolve_input.js');
|
|
17
|
+
const { sleep } = await import('#utils/sleep.js');
|
|
18
|
+
vi.mocked(resolveInput).mockResolvedValue({});
|
|
19
|
+
vi.mocked(sleep).mockResolvedValue(undefined);
|
|
20
|
+
});
|
|
21
|
+
describe('command definition', () => {
|
|
22
|
+
it('should export a valid OCLIF command', async () => {
|
|
23
|
+
const WorkflowRun = (await import('./run.js')).default;
|
|
24
|
+
expect(WorkflowRun).toBeDefined();
|
|
25
|
+
expect(WorkflowRun.description).toContain('Execute a workflow');
|
|
26
|
+
expect(WorkflowRun.args).toHaveProperty('workflowName');
|
|
27
|
+
expect(WorkflowRun.flags).toHaveProperty('input');
|
|
28
|
+
expect(WorkflowRun.flags).toHaveProperty('format');
|
|
29
|
+
expect(WorkflowRun.flags).toHaveProperty('task-queue');
|
|
30
|
+
});
|
|
31
|
+
it('should have correct flag configuration', async () => {
|
|
32
|
+
const WorkflowRun = (await import('./run.js')).default;
|
|
33
|
+
expect(WorkflowRun.flags.format.options).toEqual(['json', 'text']);
|
|
34
|
+
expect(WorkflowRun.flags.format.default).toBe('text');
|
|
35
|
+
expect(WorkflowRun.flags.input.required).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
it('should have optional scenario argument', async () => {
|
|
38
|
+
const WorkflowRun = (await import('./run.js')).default;
|
|
39
|
+
expect(WorkflowRun.args).toHaveProperty('scenario');
|
|
40
|
+
expect(WorkflowRun.args.scenario.required).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
describe('run()', () => {
|
|
44
|
+
const createCommand = async () => {
|
|
45
|
+
const WorkflowRun = (await import('./run.js')).default;
|
|
46
|
+
const { postWorkflowRun } = await import('#api/generated/api.js');
|
|
47
|
+
const { resolveInput } = await import('#utils/resolve_input.js');
|
|
48
|
+
const cmd = new WorkflowRun(['my_workflow'], {});
|
|
49
|
+
cmd.log = vi.fn();
|
|
50
|
+
cmd.error = vi.fn(() => {
|
|
51
|
+
throw new Error('error called');
|
|
52
|
+
});
|
|
53
|
+
cmd.parse = vi.fn().mockResolvedValue({
|
|
54
|
+
args: { workflowName: 'my_workflow', scenario: undefined },
|
|
55
|
+
flags: { input: undefined, 'task-queue': undefined, format: 'text' }
|
|
56
|
+
});
|
|
57
|
+
return { cmd, postWorkflowRun: vi.mocked(postWorkflowRun), resolveInput: vi.mocked(resolveInput) };
|
|
58
|
+
};
|
|
59
|
+
it('calls postWorkflowRun and logs output on success', async () => {
|
|
60
|
+
const { cmd, postWorkflowRun, resolveInput } = await createCommand();
|
|
61
|
+
resolveInput.mockResolvedValue({ key: 'value' });
|
|
62
|
+
postWorkflowRun.mockResolvedValue({
|
|
63
|
+
data: { status: 'completed', result: { output: 'ok' } },
|
|
64
|
+
status: 200,
|
|
65
|
+
headers: new Headers()
|
|
66
|
+
});
|
|
67
|
+
await cmd.run();
|
|
68
|
+
expect(postWorkflowRun).toHaveBeenCalledTimes(1);
|
|
69
|
+
expect(postWorkflowRun).toHaveBeenCalledWith({ workflowName: 'my_workflow', input: { key: 'value' }, taskQueue: undefined }, expect.objectContaining({ config: { timeout: 600000 } }));
|
|
70
|
+
expect(cmd.log).toHaveBeenCalledWith('Executing workflow: my_workflow...');
|
|
71
|
+
expect(cmd.log).toHaveBeenCalledWith(expect.stringMatching(/\n/));
|
|
72
|
+
});
|
|
73
|
+
it('retries when response has Retry-After and succeeds on second attempt', async () => {
|
|
74
|
+
const { cmd, postWorkflowRun, resolveInput } = await createCommand();
|
|
75
|
+
resolveInput.mockResolvedValue({});
|
|
76
|
+
const headers = new Headers({ 'Retry-After': '1' });
|
|
77
|
+
postWorkflowRun
|
|
78
|
+
.mockRejectedValueOnce(new HttpError('Unavailable', { status: 503, headers }))
|
|
79
|
+
.mockResolvedValueOnce({
|
|
80
|
+
data: { status: 'completed', result: {} },
|
|
81
|
+
status: 200,
|
|
82
|
+
headers: new Headers()
|
|
83
|
+
});
|
|
84
|
+
await cmd.run();
|
|
85
|
+
expect(postWorkflowRun).toHaveBeenCalledTimes(2);
|
|
86
|
+
expect(cmd.log).toHaveBeenCalledWith(expect.stringMatching(/Retry-After.*Retrying in/));
|
|
87
|
+
});
|
|
88
|
+
it('does not retry when response has no Retry-After and throws', async () => {
|
|
89
|
+
const { cmd, postWorkflowRun, resolveInput } = await createCommand();
|
|
90
|
+
resolveInput.mockResolvedValue({});
|
|
91
|
+
postWorkflowRun.mockRejectedValue(new HttpError('Unavailable', { status: 503, headers: new Headers() }));
|
|
92
|
+
await expect(cmd.run()).rejects.toThrow(HttpError);
|
|
93
|
+
expect(postWorkflowRun).toHaveBeenCalledTimes(1);
|
|
94
|
+
});
|
|
95
|
+
it('does not retry on non-503 error and throws', async () => {
|
|
96
|
+
const { cmd, postWorkflowRun, resolveInput } = await createCommand();
|
|
97
|
+
resolveInput.mockResolvedValue({});
|
|
98
|
+
postWorkflowRun.mockRejectedValue(new HttpError('Not found', { status: 404 }));
|
|
99
|
+
await expect(cmd.run()).rejects.toThrow(HttpError);
|
|
100
|
+
expect(postWorkflowRun).toHaveBeenCalledTimes(1);
|
|
101
|
+
});
|
|
102
|
+
it('calls error when API returns no data', async () => {
|
|
103
|
+
const { cmd, postWorkflowRun, resolveInput } = await createCommand();
|
|
104
|
+
resolveInput.mockResolvedValue({});
|
|
105
|
+
postWorkflowRun.mockResolvedValue({ data: undefined, status: 200, headers: new Headers() });
|
|
106
|
+
await expect(cmd.run()).rejects.toThrow('error called');
|
|
107
|
+
expect(cmd.error).toHaveBeenCalledWith('API returned invalid response', { exit: 1 });
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class WorkflowRunsList extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
workflowName: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static flags: {
|
|
9
|
+
limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
};
|
|
12
|
+
run(): Promise<void>;
|
|
13
|
+
catch(error: Error): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import Table from 'cli-table3';
|
|
3
|
+
import { fetchWorkflowRuns } from '#services/workflow_runs.js';
|
|
4
|
+
import { formatDate, formatDurationFromTimestamps } from '#utils/date_formatter.js';
|
|
5
|
+
import { handleApiError } from '#utils/error_handler.js';
|
|
6
|
+
const OUTPUT_FORMAT = {
|
|
7
|
+
TABLE: 'table',
|
|
8
|
+
JSON: 'json',
|
|
9
|
+
TEXT: 'text'
|
|
10
|
+
};
|
|
11
|
+
function createRunsTable(runs) {
|
|
12
|
+
const table = new Table({
|
|
13
|
+
head: ['Workflow ID', 'Type', 'Status', 'Started', 'Duration'],
|
|
14
|
+
colWidths: [null, 20, 12, 22, 10],
|
|
15
|
+
wordWrap: true,
|
|
16
|
+
style: {
|
|
17
|
+
head: ['cyan']
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
runs.forEach(run => {
|
|
21
|
+
table.push([
|
|
22
|
+
run.workflowId || '-',
|
|
23
|
+
run.workflowType || '-',
|
|
24
|
+
run.status || '-',
|
|
25
|
+
formatDate(run.startedAt),
|
|
26
|
+
formatDurationFromTimestamps(run.startedAt || '', run.completedAt)
|
|
27
|
+
]);
|
|
28
|
+
});
|
|
29
|
+
return table.toString();
|
|
30
|
+
}
|
|
31
|
+
function formatRunsAsText(runs) {
|
|
32
|
+
if (runs.length === 0) {
|
|
33
|
+
return 'No workflow runs found.';
|
|
34
|
+
}
|
|
35
|
+
return runs.map(run => {
|
|
36
|
+
const duration = formatDurationFromTimestamps(run.startedAt || '', run.completedAt);
|
|
37
|
+
return `${run.workflowId} (${run.workflowType}) - ${run.status} [${duration}]`;
|
|
38
|
+
}).join('\n');
|
|
39
|
+
}
|
|
40
|
+
function formatRunsAsJson(runs) {
|
|
41
|
+
return JSON.stringify(runs, null, 2);
|
|
42
|
+
}
|
|
43
|
+
function formatRuns(runs, format) {
|
|
44
|
+
if (format === OUTPUT_FORMAT.JSON) {
|
|
45
|
+
return formatRunsAsJson(runs);
|
|
46
|
+
}
|
|
47
|
+
if (format === OUTPUT_FORMAT.TABLE) {
|
|
48
|
+
return createRunsTable(runs);
|
|
49
|
+
}
|
|
50
|
+
return formatRunsAsText(runs);
|
|
51
|
+
}
|
|
52
|
+
export default class WorkflowRunsList extends Command {
|
|
53
|
+
static description = 'List workflow runs with optional filtering by workflow type';
|
|
54
|
+
static examples = [
|
|
55
|
+
'<%= config.bin %> <%= command.id %>',
|
|
56
|
+
'<%= config.bin %> <%= command.id %> simple',
|
|
57
|
+
'<%= config.bin %> <%= command.id %> simple --limit 10',
|
|
58
|
+
'<%= config.bin %> <%= command.id %> --format json',
|
|
59
|
+
'<%= config.bin %> <%= command.id %> --format table'
|
|
60
|
+
];
|
|
61
|
+
static args = {
|
|
62
|
+
workflowName: Args.string({
|
|
63
|
+
description: 'Filter by workflow type/name',
|
|
64
|
+
required: false
|
|
65
|
+
})
|
|
66
|
+
};
|
|
67
|
+
static flags = {
|
|
68
|
+
limit: Flags.integer({
|
|
69
|
+
char: 'l',
|
|
70
|
+
description: 'Maximum number of runs to return',
|
|
71
|
+
default: 100
|
|
72
|
+
}),
|
|
73
|
+
format: Flags.string({
|
|
74
|
+
char: 'f',
|
|
75
|
+
description: 'Output format',
|
|
76
|
+
options: [OUTPUT_FORMAT.TABLE, OUTPUT_FORMAT.JSON, OUTPUT_FORMAT.TEXT],
|
|
77
|
+
default: OUTPUT_FORMAT.TABLE
|
|
78
|
+
})
|
|
79
|
+
};
|
|
80
|
+
async run() {
|
|
81
|
+
const { args, flags } = await this.parse(WorkflowRunsList);
|
|
82
|
+
const { runs, count } = await fetchWorkflowRuns({
|
|
83
|
+
workflowType: args.workflowName,
|
|
84
|
+
limit: flags.limit
|
|
85
|
+
});
|
|
86
|
+
if (runs.length === 0) {
|
|
87
|
+
const filterMsg = args.workflowName ? ` for workflow type "${args.workflowName}"` : '';
|
|
88
|
+
this.log(`No workflow runs found${filterMsg}.`);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const output = formatRuns(runs, flags.format);
|
|
92
|
+
this.log(output);
|
|
93
|
+
if (flags.format !== OUTPUT_FORMAT.JSON) {
|
|
94
|
+
const filterMsg = args.workflowName ? ` of type "${args.workflowName}"` : '';
|
|
95
|
+
this.log(`\nFound ${count} run(s)${filterMsg}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async catch(error) {
|
|
99
|
+
return handleApiError(error, (...args) => this.error(...args), {
|
|
100
|
+
400: 'Invalid parameters provided.',
|
|
101
|
+
503: 'Workflow service temporarily unavailable.'
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class WorkflowStart extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
workflowName: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
scenario: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
8
|
+
};
|
|
9
|
+
static flags: {
|
|
10
|
+
input: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
'task-queue': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<void>;
|
|
14
|
+
catch(error: Error): Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import { postWorkflowStart } from '#api/generated/api.js';
|
|
3
|
+
import { handleApiError } from '#utils/error_handler.js';
|
|
4
|
+
import { resolveInput } from '#utils/resolve_input.js';
|
|
5
|
+
export default class WorkflowStart extends Command {
|
|
6
|
+
static description = 'Start a workflow asynchronously without waiting for completion';
|
|
7
|
+
static examples = [
|
|
8
|
+
'<%= config.bin %> <%= command.id %> simple basic_input',
|
|
9
|
+
'<%= config.bin %> <%= command.id %> simple --input \'{"values":[1,2,3]}\'',
|
|
10
|
+
'<%= config.bin %> <%= command.id %> simple --input input.json',
|
|
11
|
+
'<%= config.bin %> <%= command.id %> simple --input \'{"key":"value"}\' --task-queue my-queue'
|
|
12
|
+
];
|
|
13
|
+
static args = {
|
|
14
|
+
workflowName: Args.string({
|
|
15
|
+
description: 'Name of the workflow to start',
|
|
16
|
+
required: true
|
|
17
|
+
}),
|
|
18
|
+
scenario: Args.string({
|
|
19
|
+
description: 'Scenario name (resolved from the workflow\'s scenarios/ directory)',
|
|
20
|
+
required: false
|
|
21
|
+
})
|
|
22
|
+
};
|
|
23
|
+
static flags = {
|
|
24
|
+
input: Flags.string({
|
|
25
|
+
char: 'i',
|
|
26
|
+
description: 'Workflow input as JSON string or file path (overrides scenario)',
|
|
27
|
+
required: false
|
|
28
|
+
}),
|
|
29
|
+
'task-queue': Flags.string({
|
|
30
|
+
char: 'q',
|
|
31
|
+
description: 'Task queue name for workflow execution'
|
|
32
|
+
})
|
|
33
|
+
};
|
|
34
|
+
async run() {
|
|
35
|
+
const { args, flags } = await this.parse(WorkflowStart);
|
|
36
|
+
const input = await resolveInput(args.workflowName, args.scenario, flags.input, 'start');
|
|
37
|
+
this.log(`Starting workflow: ${args.workflowName}...`);
|
|
38
|
+
const response = await postWorkflowStart({
|
|
39
|
+
workflowName: args.workflowName,
|
|
40
|
+
input,
|
|
41
|
+
taskQueue: flags['task-queue']
|
|
42
|
+
});
|
|
43
|
+
if (!response || !response.data) {
|
|
44
|
+
this.error('API returned invalid response', { exit: 1 });
|
|
45
|
+
}
|
|
46
|
+
const result = response.data;
|
|
47
|
+
const output = [
|
|
48
|
+
'Workflow started successfully',
|
|
49
|
+
'',
|
|
50
|
+
`Workflow ID: ${result.workflowId || 'unknown'}`,
|
|
51
|
+
'',
|
|
52
|
+
`Use "workflow status ${result.workflowId || '<workflow-id>'}" to check the workflow status`,
|
|
53
|
+
`Use "workflow result ${result.workflowId || '<workflow-id>'}" to get the workflow result when complete`
|
|
54
|
+
].join('\n');
|
|
55
|
+
this.log(`\n${output}`);
|
|
56
|
+
}
|
|
57
|
+
async catch(error) {
|
|
58
|
+
return handleApiError(error, (...args) => this.error(...args), {
|
|
59
|
+
404: 'Workflow not found. Check the workflow name.'
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
vi.mock('../../api/generated/api.js', () => ({
|
|
3
|
+
postWorkflowStart: vi.fn()
|
|
4
|
+
}));
|
|
5
|
+
describe('workflow start command', () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
vi.clearAllMocks();
|
|
8
|
+
});
|
|
9
|
+
describe('command definition', () => {
|
|
10
|
+
it('should export a valid OCLIF command', async () => {
|
|
11
|
+
const WorkflowStart = (await import('./start.js')).default;
|
|
12
|
+
expect(WorkflowStart).toBeDefined();
|
|
13
|
+
expect(WorkflowStart.description).toContain('Start a workflow');
|
|
14
|
+
expect(WorkflowStart.args).toHaveProperty('workflowName');
|
|
15
|
+
expect(WorkflowStart.flags).toHaveProperty('input');
|
|
16
|
+
expect(WorkflowStart.flags).toHaveProperty('task-queue');
|
|
17
|
+
});
|
|
18
|
+
it('should have correct flag configuration', async () => {
|
|
19
|
+
const WorkflowStart = (await import('./start.js')).default;
|
|
20
|
+
expect(WorkflowStart.flags.input.required).toBe(false);
|
|
21
|
+
});
|
|
22
|
+
it('should have optional scenario argument', async () => {
|
|
23
|
+
const WorkflowStart = (await import('./start.js')).default;
|
|
24
|
+
expect(WorkflowStart.args).toHaveProperty('scenario');
|
|
25
|
+
expect(WorkflowStart.args.scenario.required).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class WorkflowStatus extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
workflowId: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static flags: {
|
|
9
|
+
format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
catch(error: Error): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import { getWorkflowIdStatus } from '#api/generated/api.js';
|
|
3
|
+
import { OUTPUT_FORMAT } from '#utils/constants.js';
|
|
4
|
+
import { formatOutput } from '#utils/output_formatter.js';
|
|
5
|
+
import { handleApiError } from '#utils/error_handler.js';
|
|
6
|
+
export default class WorkflowStatus extends Command {
|
|
7
|
+
static description = 'Get workflow execution status';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> <%= command.id %> wf-12345',
|
|
10
|
+
'<%= config.bin %> <%= command.id %> wf-12345 --format json'
|
|
11
|
+
];
|
|
12
|
+
static args = {
|
|
13
|
+
workflowId: Args.string({
|
|
14
|
+
description: 'The workflow ID to check status for',
|
|
15
|
+
required: true
|
|
16
|
+
})
|
|
17
|
+
};
|
|
18
|
+
static flags = {
|
|
19
|
+
format: Flags.string({
|
|
20
|
+
char: 'f',
|
|
21
|
+
description: 'Output format',
|
|
22
|
+
options: [OUTPUT_FORMAT.JSON, OUTPUT_FORMAT.TEXT],
|
|
23
|
+
default: OUTPUT_FORMAT.TEXT
|
|
24
|
+
})
|
|
25
|
+
};
|
|
26
|
+
async run() {
|
|
27
|
+
const { args, flags } = await this.parse(WorkflowStatus);
|
|
28
|
+
this.log(`Fetching status for workflow: ${args.workflowId}...`);
|
|
29
|
+
const response = await getWorkflowIdStatus(args.workflowId);
|
|
30
|
+
if (!response || !response.data) {
|
|
31
|
+
this.error('API returned invalid response', { exit: 1 });
|
|
32
|
+
}
|
|
33
|
+
const data = response.data;
|
|
34
|
+
const output = formatOutput(data, flags.format, (result) => {
|
|
35
|
+
const lines = [
|
|
36
|
+
`Workflow ID: ${result.workflowId || 'unknown'}`,
|
|
37
|
+
`Status: ${result.status || 'unknown'}`,
|
|
38
|
+
''
|
|
39
|
+
];
|
|
40
|
+
if (result.startedAt) {
|
|
41
|
+
const startDate = new Date(result.startedAt);
|
|
42
|
+
lines.push(`Started At: ${startDate.toISOString()}`);
|
|
43
|
+
}
|
|
44
|
+
if (result.completedAt) {
|
|
45
|
+
const completedDate = new Date(result.completedAt);
|
|
46
|
+
lines.push(`Completed At: ${completedDate.toISOString()}`);
|
|
47
|
+
}
|
|
48
|
+
return lines.join('\n');
|
|
49
|
+
});
|
|
50
|
+
this.log(`\n${output}`);
|
|
51
|
+
}
|
|
52
|
+
async catch(error) {
|
|
53
|
+
return handleApiError(error, (...args) => this.error(...args), {
|
|
54
|
+
404: 'Workflow not found. Check the workflow ID.'
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
vi.mock('../../api/generated/api.js', () => ({
|
|
3
|
+
getWorkflowIdStatus: vi.fn(),
|
|
4
|
+
GetWorkflowIdStatus200Status: {
|
|
5
|
+
canceled: 'canceled',
|
|
6
|
+
completed: 'completed',
|
|
7
|
+
continued_as_new: 'continued_as_new',
|
|
8
|
+
failed: 'failed',
|
|
9
|
+
running: 'running',
|
|
10
|
+
terminated: 'terminated',
|
|
11
|
+
timed_out: 'timed_out',
|
|
12
|
+
unspecified: 'unspecified'
|
|
13
|
+
}
|
|
14
|
+
}));
|
|
15
|
+
describe('workflow status command', () => {
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
vi.clearAllMocks();
|
|
18
|
+
});
|
|
19
|
+
describe('command definition', () => {
|
|
20
|
+
it('should export a valid OCLIF command', async () => {
|
|
21
|
+
const WorkflowStatus = (await import('./status.js')).default;
|
|
22
|
+
expect(WorkflowStatus).toBeDefined();
|
|
23
|
+
expect(WorkflowStatus.description).toContain('Get workflow execution status');
|
|
24
|
+
expect(WorkflowStatus.args).toHaveProperty('workflowId');
|
|
25
|
+
expect(WorkflowStatus.flags).toHaveProperty('format');
|
|
26
|
+
});
|
|
27
|
+
it('should have correct flag configuration', async () => {
|
|
28
|
+
const WorkflowStatus = (await import('./status.js')).default;
|
|
29
|
+
expect(WorkflowStatus.flags.format.options).toEqual(['json', 'text']);
|
|
30
|
+
expect(WorkflowStatus.flags.format.default).toBe('text');
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class WorkflowStop extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
workflowId: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
run(): Promise<void>;
|
|
9
|
+
catch(error: Error): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Args, Command } from '@oclif/core';
|
|
2
|
+
import { patchWorkflowIdStop } from '#api/generated/api.js';
|
|
3
|
+
import { handleApiError } from '#utils/error_handler.js';
|
|
4
|
+
export default class WorkflowStop extends Command {
|
|
5
|
+
static description = 'Stop a workflow execution';
|
|
6
|
+
static examples = [
|
|
7
|
+
'<%= config.bin %> <%= command.id %> wf-12345'
|
|
8
|
+
];
|
|
9
|
+
static args = {
|
|
10
|
+
workflowId: Args.string({
|
|
11
|
+
description: 'The workflow ID to stop',
|
|
12
|
+
required: true
|
|
13
|
+
})
|
|
14
|
+
};
|
|
15
|
+
async run() {
|
|
16
|
+
const { args } = await this.parse(WorkflowStop);
|
|
17
|
+
this.log(`Stopping workflow: ${args.workflowId}...`);
|
|
18
|
+
await patchWorkflowIdStop(args.workflowId);
|
|
19
|
+
const output = [
|
|
20
|
+
'Workflow stopped successfully',
|
|
21
|
+
'',
|
|
22
|
+
`Workflow ID: ${args.workflowId}`
|
|
23
|
+
].join('\n');
|
|
24
|
+
this.log(`\n${output}`);
|
|
25
|
+
}
|
|
26
|
+
async catch(error) {
|
|
27
|
+
return handleApiError(error, (...args) => this.error(...args), {
|
|
28
|
+
404: 'Workflow not found. Check the workflow ID.'
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
vi.mock('../../api/generated/api.js', () => ({
|
|
3
|
+
patchWorkflowIdStop: vi.fn()
|
|
4
|
+
}));
|
|
5
|
+
describe('workflow stop command', () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
vi.clearAllMocks();
|
|
8
|
+
});
|
|
9
|
+
describe('command definition', () => {
|
|
10
|
+
it('should export a valid OCLIF command', async () => {
|
|
11
|
+
const WorkflowStop = (await import('./stop.js')).default;
|
|
12
|
+
expect(WorkflowStop).toBeDefined();
|
|
13
|
+
expect(WorkflowStop.description).toContain('Stop a workflow execution');
|
|
14
|
+
expect(WorkflowStop.args).toHaveProperty('workflowId');
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
});
|