@outputai/cli 0.1.1 → 0.1.2
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/package.json +7 -7
- package/dist/api/generated/api.d.ts +0 -820
- package/dist/api/generated/api.js +0 -226
- package/dist/api/http_client.d.ts +0 -27
- package/dist/api/http_client.js +0 -71
- package/dist/api/orval_post_process.d.ts +0 -11
- package/dist/api/orval_post_process.js +0 -46
- package/dist/api/parser.d.ts +0 -17
- package/dist/api/parser.js +0 -68
- package/dist/assets/config/costs.yml +0 -309
- package/dist/assets/docker/docker-compose-dev.yml +0 -146
- package/dist/commands/credentials/edit.d.ts +0 -10
- package/dist/commands/credentials/edit.js +0 -67
- package/dist/commands/credentials/edit.spec.d.ts +0 -1
- package/dist/commands/credentials/edit.spec.js +0 -73
- package/dist/commands/credentials/get.d.ts +0 -13
- package/dist/commands/credentials/get.js +0 -46
- package/dist/commands/credentials/get.spec.d.ts +0 -1
- package/dist/commands/credentials/get.spec.js +0 -74
- package/dist/commands/credentials/init.d.ts +0 -11
- package/dist/commands/credentials/init.js +0 -45
- package/dist/commands/credentials/init.spec.d.ts +0 -1
- package/dist/commands/credentials/init.spec.js +0 -68
- package/dist/commands/credentials/show.d.ts +0 -10
- package/dist/commands/credentials/show.js +0 -33
- package/dist/commands/credentials/show.spec.d.ts +0 -1
- package/dist/commands/credentials/show.spec.js +0 -57
- package/dist/commands/dev/eject.d.ts +0 -11
- package/dist/commands/dev/eject.js +0 -58
- package/dist/commands/dev/eject.spec.d.ts +0 -1
- package/dist/commands/dev/eject.spec.js +0 -109
- package/dist/commands/dev/index.d.ts +0 -14
- package/dist/commands/dev/index.js +0 -173
- package/dist/commands/dev/index.spec.d.ts +0 -1
- package/dist/commands/dev/index.spec.js +0 -239
- package/dist/commands/init.d.ts +0 -12
- package/dist/commands/init.js +0 -37
- package/dist/commands/init.spec.d.ts +0 -1
- package/dist/commands/init.spec.js +0 -100
- package/dist/commands/update.d.ts +0 -14
- package/dist/commands/update.js +0 -120
- package/dist/commands/update.spec.d.ts +0 -1
- package/dist/commands/update.spec.js +0 -178
- package/dist/commands/workflow/cost.d.ts +0 -16
- package/dist/commands/workflow/cost.js +0 -71
- package/dist/commands/workflow/cost.spec.d.ts +0 -1
- package/dist/commands/workflow/cost.spec.js +0 -47
- package/dist/commands/workflow/dataset/generate.d.ts +0 -22
- package/dist/commands/workflow/dataset/generate.js +0 -143
- package/dist/commands/workflow/dataset/list.d.ts +0 -12
- package/dist/commands/workflow/dataset/list.js +0 -87
- package/dist/commands/workflow/debug.d.ts +0 -16
- package/dist/commands/workflow/debug.js +0 -60
- package/dist/commands/workflow/debug.spec.d.ts +0 -1
- package/dist/commands/workflow/debug.spec.js +0 -34
- package/dist/commands/workflow/generate.d.ts +0 -17
- package/dist/commands/workflow/generate.js +0 -85
- package/dist/commands/workflow/generate.spec.d.ts +0 -1
- package/dist/commands/workflow/generate.spec.js +0 -115
- package/dist/commands/workflow/list.d.ts +0 -22
- package/dist/commands/workflow/list.js +0 -152
- package/dist/commands/workflow/list.spec.d.ts +0 -1
- package/dist/commands/workflow/list.spec.js +0 -99
- package/dist/commands/workflow/plan.d.ts +0 -12
- package/dist/commands/workflow/plan.js +0 -66
- package/dist/commands/workflow/plan.spec.d.ts +0 -1
- package/dist/commands/workflow/plan.spec.js +0 -341
- package/dist/commands/workflow/reset.d.ts +0 -14
- package/dist/commands/workflow/reset.js +0 -51
- package/dist/commands/workflow/result.d.ts +0 -13
- package/dist/commands/workflow/result.js +0 -46
- package/dist/commands/workflow/result.spec.d.ts +0 -1
- package/dist/commands/workflow/result.spec.js +0 -23
- package/dist/commands/workflow/run.d.ts +0 -16
- package/dist/commands/workflow/run.js +0 -97
- package/dist/commands/workflow/run.spec.d.ts +0 -1
- package/dist/commands/workflow/run.spec.js +0 -110
- package/dist/commands/workflow/runs/list.d.ts +0 -14
- package/dist/commands/workflow/runs/list.js +0 -104
- package/dist/commands/workflow/start.d.ts +0 -15
- package/dist/commands/workflow/start.js +0 -62
- package/dist/commands/workflow/start.spec.d.ts +0 -1
- package/dist/commands/workflow/start.spec.js +0 -28
- package/dist/commands/workflow/status.d.ts +0 -13
- package/dist/commands/workflow/status.js +0 -57
- package/dist/commands/workflow/status.spec.d.ts +0 -1
- package/dist/commands/workflow/status.spec.js +0 -33
- package/dist/commands/workflow/stop.d.ts +0 -10
- package/dist/commands/workflow/stop.js +0 -31
- package/dist/commands/workflow/stop.spec.d.ts +0 -1
- package/dist/commands/workflow/stop.spec.js +0 -17
- package/dist/commands/workflow/terminate.d.ts +0 -13
- package/dist/commands/workflow/terminate.js +0 -39
- package/dist/commands/workflow/test_eval.d.ts +0 -20
- package/dist/commands/workflow/test_eval.js +0 -151
- package/dist/config.d.ts +0 -47
- package/dist/config.js +0 -47
- package/dist/generated/framework_version.json +0 -3
- package/dist/hooks/init.d.ts +0 -3
- package/dist/hooks/init.js +0 -30
- package/dist/hooks/init.spec.d.ts +0 -1
- package/dist/hooks/init.spec.js +0 -54
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/index.spec.d.ts +0 -1
- package/dist/index.spec.js +0 -6
- package/dist/services/claude_client.d.ts +0 -30
- package/dist/services/claude_client.integration.test.d.ts +0 -1
- package/dist/services/claude_client.integration.test.js +0 -43
- package/dist/services/claude_client.js +0 -215
- package/dist/services/claude_client.spec.d.ts +0 -1
- package/dist/services/claude_client.spec.js +0 -145
- package/dist/services/coding_agents.d.ts +0 -36
- package/dist/services/coding_agents.js +0 -236
- package/dist/services/coding_agents.spec.d.ts +0 -1
- package/dist/services/coding_agents.spec.js +0 -256
- package/dist/services/copy_assets.spec.d.ts +0 -1
- package/dist/services/copy_assets.spec.js +0 -22
- package/dist/services/cost_calculator.d.ts +0 -18
- package/dist/services/cost_calculator.js +0 -359
- package/dist/services/cost_calculator.spec.d.ts +0 -1
- package/dist/services/cost_calculator.spec.js +0 -540
- package/dist/services/credentials_service.d.ts +0 -12
- package/dist/services/credentials_service.integration.test.d.ts +0 -1
- package/dist/services/credentials_service.integration.test.js +0 -66
- package/dist/services/credentials_service.js +0 -64
- package/dist/services/credentials_service.spec.d.ts +0 -1
- package/dist/services/credentials_service.spec.js +0 -106
- package/dist/services/datasets.d.ts +0 -20
- package/dist/services/datasets.js +0 -132
- package/dist/services/docker.d.ts +0 -39
- package/dist/services/docker.js +0 -160
- package/dist/services/docker.spec.d.ts +0 -1
- package/dist/services/docker.spec.js +0 -124
- package/dist/services/env_configurator.d.ts +0 -15
- package/dist/services/env_configurator.js +0 -163
- package/dist/services/env_configurator.spec.d.ts +0 -1
- package/dist/services/env_configurator.spec.js +0 -192
- package/dist/services/generate_plan_name@v1.prompt +0 -24
- package/dist/services/messages.d.ts +0 -9
- package/dist/services/messages.js +0 -338
- package/dist/services/messages.spec.d.ts +0 -1
- package/dist/services/messages.spec.js +0 -55
- package/dist/services/npm_update_service.d.ts +0 -6
- package/dist/services/npm_update_service.js +0 -87
- package/dist/services/npm_update_service.spec.d.ts +0 -1
- package/dist/services/npm_update_service.spec.js +0 -104
- package/dist/services/project_scaffold.d.ts +0 -31
- package/dist/services/project_scaffold.js +0 -212
- package/dist/services/project_scaffold.spec.d.ts +0 -1
- package/dist/services/project_scaffold.spec.js +0 -122
- package/dist/services/s3_trace_downloader.d.ts +0 -12
- package/dist/services/s3_trace_downloader.js +0 -57
- package/dist/services/template_processor.d.ts +0 -14
- package/dist/services/template_processor.js +0 -57
- package/dist/services/trace_reader.d.ts +0 -16
- package/dist/services/trace_reader.js +0 -57
- package/dist/services/trace_reader.spec.d.ts +0 -1
- package/dist/services/trace_reader.spec.js +0 -78
- package/dist/services/version_check.d.ts +0 -6
- package/dist/services/version_check.js +0 -52
- package/dist/services/version_check.spec.d.ts +0 -1
- package/dist/services/version_check.spec.js +0 -106
- package/dist/services/workflow_builder.d.ts +0 -16
- package/dist/services/workflow_builder.js +0 -86
- package/dist/services/workflow_builder.spec.d.ts +0 -1
- package/dist/services/workflow_builder.spec.js +0 -165
- package/dist/services/workflow_generator.d.ts +0 -5
- package/dist/services/workflow_generator.js +0 -40
- package/dist/services/workflow_generator.spec.d.ts +0 -1
- package/dist/services/workflow_generator.spec.js +0 -77
- package/dist/services/workflow_planner.d.ts +0 -15
- package/dist/services/workflow_planner.js +0 -48
- package/dist/services/workflow_planner.spec.d.ts +0 -1
- package/dist/services/workflow_planner.spec.js +0 -122
- package/dist/services/workflow_runs.d.ts +0 -14
- package/dist/services/workflow_runs.js +0 -25
- package/dist/templates/agent_instructions/CLAUDE.md.template +0 -19
- package/dist/templates/agent_instructions/dotclaude/settings.json.template +0 -29
- package/dist/templates/project/.env.example.template +0 -9
- package/dist/templates/project/.gitignore.template +0 -35
- package/dist/templates/project/README.md.template +0 -100
- package/dist/templates/project/config/costs.yml.template +0 -29
- package/dist/templates/project/package.json.template +0 -25
- package/dist/templates/project/src/clients/jina.ts.template +0 -30
- package/dist/templates/project/src/shared/utils/string.ts.template +0 -3
- package/dist/templates/project/src/shared/utils/url.ts.template +0 -15
- package/dist/templates/project/src/workflows/blog_evaluator/evaluators.ts.template +0 -23
- package/dist/templates/project/src/workflows/blog_evaluator/prompts/signal_noise@v1.prompt.template +0 -26
- package/dist/templates/project/src/workflows/blog_evaluator/scenarios/paulgraham_hwh.json.template +0 -3
- package/dist/templates/project/src/workflows/blog_evaluator/steps.ts.template +0 -27
- package/dist/templates/project/src/workflows/blog_evaluator/types.ts.template +0 -30
- package/dist/templates/project/src/workflows/blog_evaluator/utils.ts.template +0 -15
- package/dist/templates/project/src/workflows/blog_evaluator/workflow.ts.template +0 -27
- package/dist/templates/project/tsconfig.json.template +0 -20
- package/dist/templates/workflow/README.md.template +0 -216
- package/dist/templates/workflow/evaluators.ts.template +0 -21
- package/dist/templates/workflow/prompts/example@v1.prompt.template +0 -15
- package/dist/templates/workflow/scenarios/test_input.json.template +0 -3
- package/dist/templates/workflow/steps.ts.template +0 -20
- package/dist/templates/workflow/types.ts.template +0 -13
- package/dist/templates/workflow/workflow.ts.template +0 -23
- package/dist/test_helpers/mocks.d.ts +0 -38
- package/dist/test_helpers/mocks.js +0 -77
- package/dist/types/cost.d.ts +0 -149
- package/dist/types/cost.js +0 -6
- package/dist/types/domain.d.ts +0 -20
- package/dist/types/domain.js +0 -4
- package/dist/types/errors.d.ts +0 -68
- package/dist/types/errors.js +0 -100
- package/dist/types/errors.spec.d.ts +0 -1
- package/dist/types/errors.spec.js +0 -18
- package/dist/types/generator.d.ts +0 -26
- package/dist/types/generator.js +0 -1
- package/dist/types/trace.d.ts +0 -161
- package/dist/types/trace.js +0 -18
- package/dist/utils/claude.d.ts +0 -5
- package/dist/utils/claude.js +0 -19
- package/dist/utils/claude.spec.d.ts +0 -1
- package/dist/utils/claude.spec.js +0 -119
- package/dist/utils/constants.d.ts +0 -5
- package/dist/utils/constants.js +0 -4
- package/dist/utils/cost_formatter.d.ts +0 -5
- package/dist/utils/cost_formatter.js +0 -218
- package/dist/utils/date_formatter.d.ts +0 -23
- package/dist/utils/date_formatter.js +0 -49
- package/dist/utils/env_loader.d.ts +0 -1
- package/dist/utils/env_loader.js +0 -22
- package/dist/utils/env_loader.spec.d.ts +0 -1
- package/dist/utils/env_loader.spec.js +0 -43
- package/dist/utils/error_handler.d.ts +0 -8
- package/dist/utils/error_handler.js +0 -71
- package/dist/utils/error_utils.d.ts +0 -24
- package/dist/utils/error_utils.js +0 -87
- package/dist/utils/file_system.d.ts +0 -3
- package/dist/utils/file_system.js +0 -33
- package/dist/utils/format_workflow_result.d.ts +0 -5
- package/dist/utils/format_workflow_result.js +0 -18
- package/dist/utils/format_workflow_result.spec.d.ts +0 -1
- package/dist/utils/format_workflow_result.spec.js +0 -81
- package/dist/utils/framework_version.d.ts +0 -4
- package/dist/utils/framework_version.js +0 -4
- package/dist/utils/framework_version.spec.d.ts +0 -1
- package/dist/utils/framework_version.spec.js +0 -13
- package/dist/utils/header_utils.d.ts +0 -12
- package/dist/utils/header_utils.js +0 -29
- package/dist/utils/header_utils.spec.d.ts +0 -1
- package/dist/utils/header_utils.spec.js +0 -52
- package/dist/utils/input_parser.d.ts +0 -1
- package/dist/utils/input_parser.js +0 -19
- package/dist/utils/output_formatter.d.ts +0 -2
- package/dist/utils/output_formatter.js +0 -11
- package/dist/utils/paths.d.ts +0 -25
- package/dist/utils/paths.js +0 -36
- package/dist/utils/process.d.ts +0 -4
- package/dist/utils/process.js +0 -50
- package/dist/utils/resolve_input.d.ts +0 -1
- package/dist/utils/resolve_input.js +0 -22
- package/dist/utils/scenario_resolver.d.ts +0 -9
- package/dist/utils/scenario_resolver.js +0 -93
- package/dist/utils/scenario_resolver.spec.d.ts +0 -1
- package/dist/utils/scenario_resolver.spec.js +0 -214
- package/dist/utils/secret_sanitizer.d.ts +0 -1
- package/dist/utils/secret_sanitizer.js +0 -29
- package/dist/utils/sleep.d.ts +0 -5
- package/dist/utils/sleep.js +0 -5
- package/dist/utils/template.d.ts +0 -9
- package/dist/utils/template.js +0 -30
- package/dist/utils/template.spec.d.ts +0 -1
- package/dist/utils/template.spec.js +0 -77
- package/dist/utils/trace_extractor.d.ts +0 -27
- package/dist/utils/trace_extractor.js +0 -53
- package/dist/utils/trace_formatter.d.ts +0 -11
- package/dist/utils/trace_formatter.js +0 -402
- package/dist/utils/validation.d.ts +0 -13
- package/dist/utils/validation.js +0 -25
- package/dist/utils/validation.spec.d.ts +0 -1
- package/dist/utils/validation.spec.js +0 -140
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
import { input, confirm } from '@inquirer/prompts';
|
|
2
|
-
import { ux } from '@oclif/core';
|
|
3
|
-
import { kebabCase, pascalCase } from 'change-case';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import { fileURLToPath } from 'node:url';
|
|
6
|
-
import { FolderAlreadyExistsError, UserCancelledError, DirectoryCreationError } from '#types/errors.js';
|
|
7
|
-
import { createDirectory } from '#utils/file_system.js';
|
|
8
|
-
import { executeCommand, executeCommandWithMessages } from '#utils/process.js';
|
|
9
|
-
import { getFrameworkVersion } from '#utils/framework_version.js';
|
|
10
|
-
import { getErrorMessage, getErrorCode } from '#utils/error_utils.js';
|
|
11
|
-
import { isDockerInstalled } from '#services/docker.js';
|
|
12
|
-
import { isClaudeCliAvailable } from '#utils/claude.js';
|
|
13
|
-
import { configureEnvironmentVariables } from './env_configurator.js';
|
|
14
|
-
import { getTemplateFiles, processTemplateFile } from './template_processor.js';
|
|
15
|
-
import { initializeAgentConfig } from './coding_agents.js';
|
|
16
|
-
import { getProjectSuccessMessage } from './messages.js';
|
|
17
|
-
/**
|
|
18
|
-
* Check for required dependencies (Docker and Claude CLI)
|
|
19
|
-
* Prompts user to continue if dependencies are missing
|
|
20
|
-
* @throws UserCancelledError if user declines to proceed without dependencies
|
|
21
|
-
*/
|
|
22
|
-
export async function checkDependencies() {
|
|
23
|
-
const dockerInstalled = isDockerInstalled();
|
|
24
|
-
const claudeAvailable = isClaudeCliAvailable();
|
|
25
|
-
if (dockerInstalled && claudeAvailable) {
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
const missingDeps = [];
|
|
29
|
-
if (!dockerInstalled) {
|
|
30
|
-
missingDeps.push('Docker (https://docs.docker.com/)');
|
|
31
|
-
}
|
|
32
|
-
if (!claudeAvailable) {
|
|
33
|
-
missingDeps.push('Claude CLI (https://code.claude.com/)');
|
|
34
|
-
}
|
|
35
|
-
const depList = missingDeps.join('\n - ');
|
|
36
|
-
const message = `The following dependencies are missing:\n - ${depList}\n\n` +
|
|
37
|
-
'Some features may not work correctly without these dependencies.';
|
|
38
|
-
ux.warn(message);
|
|
39
|
-
try {
|
|
40
|
-
const shouldProceed = await confirm({
|
|
41
|
-
message: 'Would you like to proceed anyway?',
|
|
42
|
-
default: false
|
|
43
|
-
});
|
|
44
|
-
if (!shouldProceed) {
|
|
45
|
-
throw new UserCancelledError();
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
catch {
|
|
49
|
-
throw new UserCancelledError();
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
const promptForFolderName = async (projectName) => {
|
|
53
|
-
return await input({
|
|
54
|
-
message: 'What folder name should be used?',
|
|
55
|
-
default: kebabCase(projectName)
|
|
56
|
-
}) || kebabCase(projectName);
|
|
57
|
-
};
|
|
58
|
-
const promptForProjectName = async (defaultProjectName) => {
|
|
59
|
-
return await input({
|
|
60
|
-
message: 'What is your project name?',
|
|
61
|
-
default: defaultProjectName
|
|
62
|
-
}) || defaultProjectName;
|
|
63
|
-
};
|
|
64
|
-
const generateProjectDescription = (projectName) => {
|
|
65
|
-
return `AI Agents & Workflows built with Output.ai for ${kebabCase(projectName)}`;
|
|
66
|
-
};
|
|
67
|
-
/**
|
|
68
|
-
* Get project configuration from user input
|
|
69
|
-
* @param userFolderNameArg - Optional folder name to skip folder name prompt
|
|
70
|
-
*/
|
|
71
|
-
export const getProjectConfig = async (userFolderNameArg) => {
|
|
72
|
-
const defaultProjectName = 'my-outputai-workflows';
|
|
73
|
-
try {
|
|
74
|
-
const projectName = userFolderNameArg ?
|
|
75
|
-
userFolderNameArg :
|
|
76
|
-
await promptForProjectName(defaultProjectName);
|
|
77
|
-
const folderName = userFolderNameArg ?
|
|
78
|
-
userFolderNameArg :
|
|
79
|
-
await promptForFolderName(projectName);
|
|
80
|
-
const description = generateProjectDescription(projectName);
|
|
81
|
-
return {
|
|
82
|
-
projectName,
|
|
83
|
-
folderName,
|
|
84
|
-
projectPath: path.resolve(process.cwd(), folderName),
|
|
85
|
-
description
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
catch {
|
|
89
|
-
throw new UserCancelledError();
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
async function scaffoldProjectFiles(projectPath, projectName, description) {
|
|
93
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
94
|
-
const __dirname = path.dirname(__filename);
|
|
95
|
-
const templatesDir = path.join(__dirname, '..', 'templates', 'project');
|
|
96
|
-
// Get framework version for dynamic template injection
|
|
97
|
-
const frameworkVersion = await getFrameworkVersion();
|
|
98
|
-
const templateVars = {
|
|
99
|
-
projectName: kebabCase(projectName),
|
|
100
|
-
ProjectName: pascalCase(projectName),
|
|
101
|
-
description: description || `An Output.ai workflow for ${projectName}`,
|
|
102
|
-
frameworkVersion: frameworkVersion.framework
|
|
103
|
-
};
|
|
104
|
-
const templateFiles = await getTemplateFiles(templatesDir);
|
|
105
|
-
await Promise.all(templateFiles.map(templateFile => processTemplateFile(templateFile, projectPath, templateVars)));
|
|
106
|
-
return templateFiles.map(f => f.outputName);
|
|
107
|
-
}
|
|
108
|
-
async function executeNpmInstall(projectPath) {
|
|
109
|
-
await executeCommand('npm', ['install'], projectPath);
|
|
110
|
-
}
|
|
111
|
-
async function initializeAgents(projectPath) {
|
|
112
|
-
await initializeAgentConfig({ projectRoot: projectPath, force: false });
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Format error message for init errors
|
|
116
|
-
* Single responsibility: only format error messages, no cleanup logic
|
|
117
|
-
*/
|
|
118
|
-
function formatInitError(error, projectPath) {
|
|
119
|
-
if (error instanceof UserCancelledError) {
|
|
120
|
-
return error.message;
|
|
121
|
-
}
|
|
122
|
-
if (error instanceof FolderAlreadyExistsError) {
|
|
123
|
-
return error.message;
|
|
124
|
-
}
|
|
125
|
-
const errorCode = getErrorCode(error);
|
|
126
|
-
const pathSuffix = projectPath ? ` at ${projectPath}` : '';
|
|
127
|
-
switch (errorCode) {
|
|
128
|
-
case 'EEXIST': return 'Folder already exists';
|
|
129
|
-
case 'EACCES': return `Permission denied${pathSuffix}`;
|
|
130
|
-
case 'ENOSPC': return `Not enough disk space${pathSuffix}`;
|
|
131
|
-
case 'EPERM': return `Operation not permitted${pathSuffix}`;
|
|
132
|
-
case 'ENOENT': return `Required file or directory not found${pathSuffix}`;
|
|
133
|
-
default: {
|
|
134
|
-
const originalMessage = error instanceof Error ? error.message : String(error);
|
|
135
|
-
return `Failed to create project${pathSuffix}: ${originalMessage}`;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Create a SIGINT handler for cleanup during init
|
|
141
|
-
* Exits immediately without prompting to avoid race conditions
|
|
142
|
-
* @param projectPath - Path to the project folder
|
|
143
|
-
* @param folderCreated - Whether the folder has been created
|
|
144
|
-
*/
|
|
145
|
-
export function createSigintHandler(projectPath, folderCreated) {
|
|
146
|
-
return () => {
|
|
147
|
-
ux.stdout('\n');
|
|
148
|
-
if (folderCreated) {
|
|
149
|
-
ux.warn(`Incomplete project folder may exist at: ${projectPath}`);
|
|
150
|
-
ux.warn(`Run: rm -rf "${projectPath}" to clean up`);
|
|
151
|
-
}
|
|
152
|
-
process.exit(130);
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
function handleRunInitError(error, projectPath, projectFolderCreated) {
|
|
156
|
-
const errorMessage = formatInitError(error, projectPath);
|
|
157
|
-
if (projectFolderCreated && projectPath) {
|
|
158
|
-
ux.warn(`Incomplete project folder may exist at: ${projectPath}`);
|
|
159
|
-
ux.warn(`Run: rm -rf "${projectPath}" to clean up`);
|
|
160
|
-
}
|
|
161
|
-
throw new Error(errorMessage);
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* Run the init command workflow
|
|
165
|
-
* @param skipEnv - Whether to skip environment configuration prompts
|
|
166
|
-
* @param folderName - Optional folder name to skip folder name prompt
|
|
167
|
-
*/
|
|
168
|
-
export async function runInit(skipEnv = false, folderName) {
|
|
169
|
-
// Track state for SIGINT cleanup using an object to avoid let
|
|
170
|
-
const state = {
|
|
171
|
-
projectFolderCreated: false,
|
|
172
|
-
projectPath: ''
|
|
173
|
-
};
|
|
174
|
-
// Create and register SIGINT handler
|
|
175
|
-
const sigintHandler = () => {
|
|
176
|
-
const handler = createSigintHandler(state.projectPath, state.projectFolderCreated);
|
|
177
|
-
handler();
|
|
178
|
-
};
|
|
179
|
-
process.on('SIGINT', sigintHandler);
|
|
180
|
-
try {
|
|
181
|
-
// Check dependencies first
|
|
182
|
-
await checkDependencies();
|
|
183
|
-
const config = await getProjectConfig(folderName);
|
|
184
|
-
state.projectPath = config.projectPath;
|
|
185
|
-
try {
|
|
186
|
-
createDirectory(config.projectPath);
|
|
187
|
-
state.projectFolderCreated = true;
|
|
188
|
-
}
|
|
189
|
-
catch (error) {
|
|
190
|
-
throw new DirectoryCreationError(getErrorMessage(error), config.projectPath);
|
|
191
|
-
}
|
|
192
|
-
ux.stdout(`Created project folder: ${config.folderName}`);
|
|
193
|
-
const filesCreated = await scaffoldProjectFiles(config.projectPath, config.projectName, config.description);
|
|
194
|
-
ux.stdout(`Created ${filesCreated.length} project files`);
|
|
195
|
-
const envConfigured = await configureEnvironmentVariables(config.projectPath, skipEnv);
|
|
196
|
-
if (envConfigured) {
|
|
197
|
-
ux.stdout('Environment variables configured in .env');
|
|
198
|
-
}
|
|
199
|
-
await executeCommandWithMessages(() => initializeAgents(config.projectPath), 'Initializing agent system...', 'Agent system initialized');
|
|
200
|
-
const installSuccess = await executeCommandWithMessages(() => executeNpmInstall(config.projectPath), 'Installing dependencies...', 'Dependencies installed');
|
|
201
|
-
const nextSteps = getProjectSuccessMessage(config.folderName, installSuccess, envConfigured);
|
|
202
|
-
ux.stdout('Project created successfully!');
|
|
203
|
-
ux.stdout(nextSteps);
|
|
204
|
-
}
|
|
205
|
-
catch (error) {
|
|
206
|
-
handleRunInitError(error, state.projectPath || null, state.projectFolderCreated);
|
|
207
|
-
}
|
|
208
|
-
finally {
|
|
209
|
-
// Remove SIGINT handler on completion (success or error)
|
|
210
|
-
process.removeListener('SIGINT', sigintHandler);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { getProjectConfig, checkDependencies, createSigintHandler } from './project_scaffold.js';
|
|
3
|
-
import { UserCancelledError } from '#types/errors.js';
|
|
4
|
-
// Mock the framework version utility
|
|
5
|
-
vi.mock('#utils/framework_version.js', () => ({
|
|
6
|
-
getFrameworkVersion: vi.fn().mockResolvedValue({
|
|
7
|
-
framework: '0.1.1'
|
|
8
|
-
})
|
|
9
|
-
}));
|
|
10
|
-
// Mock other dependencies
|
|
11
|
-
vi.mock('@inquirer/prompts', () => ({
|
|
12
|
-
input: vi.fn(),
|
|
13
|
-
confirm: vi.fn()
|
|
14
|
-
}));
|
|
15
|
-
vi.mock('#utils/file_system.js');
|
|
16
|
-
vi.mock('#utils/process.js');
|
|
17
|
-
vi.mock('./env_configurator.js', () => ({
|
|
18
|
-
configureEnvironmentVariables: vi.fn().mockResolvedValue(false)
|
|
19
|
-
}));
|
|
20
|
-
vi.mock('./template_processor.js');
|
|
21
|
-
vi.mock('./coding_agents.js');
|
|
22
|
-
vi.mock('#services/docker.js', () => ({
|
|
23
|
-
isDockerInstalled: vi.fn().mockReturnValue(true)
|
|
24
|
-
}));
|
|
25
|
-
vi.mock('#utils/claude.js', () => ({
|
|
26
|
-
isClaudeCliAvailable: vi.fn().mockReturnValue(true)
|
|
27
|
-
}));
|
|
28
|
-
vi.mock('@oclif/core', () => ({
|
|
29
|
-
ux: {
|
|
30
|
-
stdout: vi.fn(),
|
|
31
|
-
warn: vi.fn()
|
|
32
|
-
}
|
|
33
|
-
}));
|
|
34
|
-
describe('project_scaffold', () => {
|
|
35
|
-
beforeEach(() => {
|
|
36
|
-
vi.clearAllMocks();
|
|
37
|
-
});
|
|
38
|
-
describe('getProjectConfig', () => {
|
|
39
|
-
it('should skip all prompts when folderName is provided', async () => {
|
|
40
|
-
const { input } = await import('@inquirer/prompts');
|
|
41
|
-
const config = await getProjectConfig('my-project');
|
|
42
|
-
expect(config.folderName).toBe('my-project');
|
|
43
|
-
expect(config.projectName).toBe('my-project');
|
|
44
|
-
expect(input).not.toHaveBeenCalled();
|
|
45
|
-
});
|
|
46
|
-
it('should auto-generate description when folderName provided', async () => {
|
|
47
|
-
const config = await getProjectConfig('test-folder');
|
|
48
|
-
expect(config.description).toBe('AI Agents & Workflows built with Output.ai for test-folder');
|
|
49
|
-
});
|
|
50
|
-
it('should prompt for project name and folder name when not provided', async () => {
|
|
51
|
-
const { input } = await import('@inquirer/prompts');
|
|
52
|
-
vi.mocked(input)
|
|
53
|
-
.mockResolvedValueOnce('Test Project')
|
|
54
|
-
.mockResolvedValueOnce('test-project');
|
|
55
|
-
const config = await getProjectConfig();
|
|
56
|
-
expect(config.projectName).toBe('Test Project');
|
|
57
|
-
expect(config.folderName).toBe('test-project');
|
|
58
|
-
expect(config.description).toBe('AI Agents & Workflows built with Output.ai for test-project');
|
|
59
|
-
expect(input).toHaveBeenCalledTimes(2);
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
describe('checkDependencies', () => {
|
|
63
|
-
it('should not prompt when all dependencies are available', async () => {
|
|
64
|
-
const { isDockerInstalled } = await import('#services/docker.js');
|
|
65
|
-
const { isClaudeCliAvailable } = await import('#utils/claude.js');
|
|
66
|
-
const { confirm } = await import('@inquirer/prompts');
|
|
67
|
-
vi.mocked(isDockerInstalled).mockReturnValue(true);
|
|
68
|
-
vi.mocked(isClaudeCliAvailable).mockReturnValue(true);
|
|
69
|
-
await checkDependencies();
|
|
70
|
-
expect(confirm).not.toHaveBeenCalled();
|
|
71
|
-
});
|
|
72
|
-
it('should prompt user when docker is missing', async () => {
|
|
73
|
-
const { isDockerInstalled } = await import('#services/docker.js');
|
|
74
|
-
const { isClaudeCliAvailable } = await import('#utils/claude.js');
|
|
75
|
-
const { confirm } = await import('@inquirer/prompts');
|
|
76
|
-
vi.mocked(isDockerInstalled).mockReturnValue(false);
|
|
77
|
-
vi.mocked(isClaudeCliAvailable).mockReturnValue(true);
|
|
78
|
-
vi.mocked(confirm).mockResolvedValue(true);
|
|
79
|
-
await checkDependencies();
|
|
80
|
-
expect(confirm).toHaveBeenCalledWith(expect.objectContaining({
|
|
81
|
-
message: expect.stringContaining('proceed')
|
|
82
|
-
}));
|
|
83
|
-
});
|
|
84
|
-
it('should throw UserCancelledError when user declines to proceed', async () => {
|
|
85
|
-
const { isDockerInstalled } = await import('#services/docker.js');
|
|
86
|
-
const { isClaudeCliAvailable } = await import('#utils/claude.js');
|
|
87
|
-
const { confirm } = await import('@inquirer/prompts');
|
|
88
|
-
vi.mocked(isDockerInstalled).mockReturnValue(false);
|
|
89
|
-
vi.mocked(isClaudeCliAvailable).mockReturnValue(true);
|
|
90
|
-
vi.mocked(confirm).mockResolvedValue(false);
|
|
91
|
-
await expect(checkDependencies()).rejects.toThrow(UserCancelledError);
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
describe('SIGINT handler', () => {
|
|
95
|
-
it('should show cleanup message when project folder was created', async () => {
|
|
96
|
-
const { ux } = await import('@oclif/core');
|
|
97
|
-
const handler = createSigintHandler('/test/project', true);
|
|
98
|
-
const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined);
|
|
99
|
-
handler();
|
|
100
|
-
expect(ux.warn).toHaveBeenCalledWith(expect.stringContaining('/test/project'));
|
|
101
|
-
expect(ux.warn).toHaveBeenCalledWith(expect.stringContaining('rm -rf'));
|
|
102
|
-
expect(exitSpy).toHaveBeenCalledWith(130);
|
|
103
|
-
exitSpy.mockRestore();
|
|
104
|
-
});
|
|
105
|
-
it('should exit immediately without warning when folder not created', async () => {
|
|
106
|
-
const { ux } = await import('@oclif/core');
|
|
107
|
-
const handler = createSigintHandler('/nonexistent', false);
|
|
108
|
-
const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined);
|
|
109
|
-
handler();
|
|
110
|
-
expect(ux.warn).not.toHaveBeenCalled();
|
|
111
|
-
expect(exitSpy).toHaveBeenCalledWith(130);
|
|
112
|
-
exitSpy.mockRestore();
|
|
113
|
-
});
|
|
114
|
-
it('should exit with code 130 (SIGINT convention)', async () => {
|
|
115
|
-
const handler = createSigintHandler('/test/project', true);
|
|
116
|
-
const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined);
|
|
117
|
-
handler();
|
|
118
|
-
expect(exitSpy).toHaveBeenCalledWith(130);
|
|
119
|
-
exitSpy.mockRestore();
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
});
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { TraceData } from '#types/trace.js';
|
|
2
|
-
interface RemoteTraceInfo {
|
|
3
|
-
key: string;
|
|
4
|
-
lastModified?: Date;
|
|
5
|
-
size?: number;
|
|
6
|
-
}
|
|
7
|
-
export declare function listRemoteTraces(workflowName: string, options?: {
|
|
8
|
-
limit?: number;
|
|
9
|
-
since?: Date;
|
|
10
|
-
}): Promise<RemoteTraceInfo[]>;
|
|
11
|
-
export declare function downloadRemoteTrace(key: string): Promise<TraceData>;
|
|
12
|
-
export {};
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { S3Client, ListObjectsV2Command, GetObjectCommand } from '@aws-sdk/client-s3';
|
|
2
|
-
import { config } from '#config.js';
|
|
3
|
-
function getS3Config() {
|
|
4
|
-
const { bucket, region, accessKeyId, secretAccessKey } = config.s3;
|
|
5
|
-
if (!bucket || !region || !accessKeyId || !secretAccessKey) {
|
|
6
|
-
throw new Error('Missing S3 configuration. Set OUTPUT_TRACE_REMOTE_S3_BUCKET, OUTPUT_AWS_REGION, ' +
|
|
7
|
-
'OUTPUT_AWS_ACCESS_KEY_ID, and OUTPUT_AWS_SECRET_ACCESS_KEY environment variables.');
|
|
8
|
-
}
|
|
9
|
-
return { bucket, region, accessKeyId, secretAccessKey };
|
|
10
|
-
}
|
|
11
|
-
function createS3Client(s3Config) {
|
|
12
|
-
return new S3Client({
|
|
13
|
-
region: s3Config.region,
|
|
14
|
-
credentials: {
|
|
15
|
-
accessKeyId: s3Config.accessKeyId,
|
|
16
|
-
secretAccessKey: s3Config.secretAccessKey
|
|
17
|
-
}
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
export async function listRemoteTraces(workflowName, options = {}) {
|
|
21
|
-
const s3Config = getS3Config();
|
|
22
|
-
const client = createS3Client(s3Config);
|
|
23
|
-
const limit = options.limit ?? 20;
|
|
24
|
-
const command = new ListObjectsV2Command({
|
|
25
|
-
Bucket: s3Config.bucket,
|
|
26
|
-
Prefix: `${workflowName}/`,
|
|
27
|
-
MaxKeys: limit
|
|
28
|
-
});
|
|
29
|
-
const response = await client.send(command);
|
|
30
|
-
const contents = response.Contents ?? [];
|
|
31
|
-
return contents
|
|
32
|
-
.filter(obj => {
|
|
33
|
-
if (!obj.Key) {
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
|
-
if (options.since && obj.LastModified && obj.LastModified < options.since) {
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
return true;
|
|
40
|
-
})
|
|
41
|
-
.map(obj => ({
|
|
42
|
-
key: obj.Key,
|
|
43
|
-
lastModified: obj.LastModified,
|
|
44
|
-
size: obj.Size
|
|
45
|
-
}));
|
|
46
|
-
}
|
|
47
|
-
export async function downloadRemoteTrace(key) {
|
|
48
|
-
const s3Config = getS3Config();
|
|
49
|
-
const client = createS3Client(s3Config);
|
|
50
|
-
const command = new GetObjectCommand({ Bucket: s3Config.bucket, Key: key });
|
|
51
|
-
const response = await client.send(command);
|
|
52
|
-
const body = await response.Body?.transformToString();
|
|
53
|
-
if (!body) {
|
|
54
|
-
throw new Error(`Empty response for S3 object: ${key}`);
|
|
55
|
-
}
|
|
56
|
-
return JSON.parse(body);
|
|
57
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { TemplateFile } from '#types/generator.js';
|
|
2
|
-
/**
|
|
3
|
-
* Get list of template files from a directory
|
|
4
|
-
* Automatically discovers all .template files (including in subdirectories) and derives output names
|
|
5
|
-
*/
|
|
6
|
-
export declare function getTemplateFiles(templatesDir: string): Promise<TemplateFile[]>;
|
|
7
|
-
/**
|
|
8
|
-
* Process a single template file
|
|
9
|
-
*/
|
|
10
|
-
export declare function processTemplateFile(templateFile: TemplateFile, targetDir: string, variables: Record<string, string>): Promise<void>;
|
|
11
|
-
/**
|
|
12
|
-
* Process all template files
|
|
13
|
-
*/
|
|
14
|
-
export declare function processAllTemplates(templateFiles: TemplateFile[], targetDir: string, variables: Record<string, string>): Promise<string[]>;
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import * as fs from 'node:fs/promises';
|
|
2
|
-
import * as path from 'node:path';
|
|
3
|
-
import { processTemplate } from '#utils/template.js';
|
|
4
|
-
const TEMPLATE_EXTENSION = '.template';
|
|
5
|
-
const isTemplateFile = (file) => file.endsWith(TEMPLATE_EXTENSION);
|
|
6
|
-
const fileToTemplateFile = (file, templatesDir, relativePath = '') => {
|
|
7
|
-
const fullPath = relativePath ? path.join(relativePath, file) : file;
|
|
8
|
-
return {
|
|
9
|
-
name: file,
|
|
10
|
-
path: path.join(templatesDir, relativePath, file),
|
|
11
|
-
outputName: fullPath.replace(TEMPLATE_EXTENSION, '')
|
|
12
|
-
};
|
|
13
|
-
};
|
|
14
|
-
const processEntry = async (entry, templatesDir, relativePath, getFilesRecursively) => {
|
|
15
|
-
if (entry.isDirectory()) {
|
|
16
|
-
const subDir = relativePath ? path.join(relativePath, entry.name) : entry.name;
|
|
17
|
-
return getFilesRecursively(templatesDir, subDir);
|
|
18
|
-
}
|
|
19
|
-
if (entry.isFile() && isTemplateFile(entry.name)) {
|
|
20
|
-
return [fileToTemplateFile(entry.name, templatesDir, relativePath)];
|
|
21
|
-
}
|
|
22
|
-
return [];
|
|
23
|
-
};
|
|
24
|
-
async function getTemplateFilesRecursive(templatesDir, relativePath = '') {
|
|
25
|
-
const fullPath = relativePath ? path.join(templatesDir, relativePath) : templatesDir;
|
|
26
|
-
const entries = await fs.readdir(fullPath, { withFileTypes: true });
|
|
27
|
-
const results = await Promise.all(entries.map(entry => processEntry(entry, templatesDir, relativePath, getTemplateFilesRecursive)));
|
|
28
|
-
return results.flatMap(x => x);
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Get list of template files from a directory
|
|
32
|
-
* Automatically discovers all .template files (including in subdirectories) and derives output names
|
|
33
|
-
*/
|
|
34
|
-
export async function getTemplateFiles(templatesDir) {
|
|
35
|
-
return getTemplateFilesRecursive(templatesDir);
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Process a single template file
|
|
39
|
-
*/
|
|
40
|
-
export async function processTemplateFile(templateFile, targetDir, variables) {
|
|
41
|
-
const templateContent = await fs.readFile(templateFile.path, 'utf-8');
|
|
42
|
-
const processedContent = processTemplate(templateContent, variables);
|
|
43
|
-
const outputPath = path.join(targetDir, templateFile.outputName);
|
|
44
|
-
// Create parent directories if they don't exist
|
|
45
|
-
const outputDir = path.dirname(outputPath);
|
|
46
|
-
await fs.mkdir(outputDir, { recursive: true });
|
|
47
|
-
await fs.writeFile(outputPath, processedContent, 'utf-8');
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Process all template files
|
|
51
|
-
*/
|
|
52
|
-
export async function processAllTemplates(templateFiles, targetDir, variables) {
|
|
53
|
-
return Promise.all(templateFiles.map(async (templateFile) => {
|
|
54
|
-
await processTemplateFile(templateFile, targetDir, variables);
|
|
55
|
-
return templateFile.outputName;
|
|
56
|
-
}));
|
|
57
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { TraceData } from '#types/trace.js';
|
|
2
|
-
export type { TraceData };
|
|
3
|
-
export interface TraceLocation {
|
|
4
|
-
path: string;
|
|
5
|
-
isRemote: boolean;
|
|
6
|
-
}
|
|
7
|
-
export interface TraceResult {
|
|
8
|
-
data: TraceData;
|
|
9
|
-
location: TraceLocation;
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Get trace data from workflow ID using the API
|
|
13
|
-
* The API handles S3 fetching - CLI only needs to read local files when necessary
|
|
14
|
-
* @returns Both the trace data and the location it was fetched from
|
|
15
|
-
*/
|
|
16
|
-
export declare function getTrace(workflowId: string): Promise<TraceResult>;
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { readFile } from 'node:fs/promises';
|
|
2
|
-
import { getWorkflowIdTraceLog } from '#api/generated/api.js';
|
|
3
|
-
import { getErrorCode } from '#utils/error_utils.js';
|
|
4
|
-
/**
|
|
5
|
-
* Read and parse trace file from local path
|
|
6
|
-
*/
|
|
7
|
-
async function readLocalTraceFile(path) {
|
|
8
|
-
try {
|
|
9
|
-
const content = await readFile(path, 'utf-8');
|
|
10
|
-
return JSON.parse(content);
|
|
11
|
-
}
|
|
12
|
-
catch (error) {
|
|
13
|
-
if (getErrorCode(error) === 'ENOENT') {
|
|
14
|
-
throw new Error(`Trace file not found at path: ${path}`);
|
|
15
|
-
}
|
|
16
|
-
if (error instanceof SyntaxError) {
|
|
17
|
-
throw new Error(`Invalid JSON in trace file: ${path}`);
|
|
18
|
-
}
|
|
19
|
-
throw error;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Get trace data from workflow ID using the API
|
|
24
|
-
* The API handles S3 fetching - CLI only needs to read local files when necessary
|
|
25
|
-
* @returns Both the trace data and the location it was fetched from
|
|
26
|
-
*/
|
|
27
|
-
export async function getTrace(workflowId) {
|
|
28
|
-
const response = await getWorkflowIdTraceLog(workflowId);
|
|
29
|
-
if (response.status === 404) {
|
|
30
|
-
throw new Error(`Workflow not found or no trace available: ${workflowId}`);
|
|
31
|
-
}
|
|
32
|
-
if (response.status === 500) {
|
|
33
|
-
const errorData = response.data;
|
|
34
|
-
const errorMessage = errorData?.error || 'Failed to fetch trace from API';
|
|
35
|
-
throw new Error(`API error (500): ${errorMessage}`);
|
|
36
|
-
}
|
|
37
|
-
if (response.status !== 200) {
|
|
38
|
-
const errorResponse = response;
|
|
39
|
-
throw new Error(`Unexpected API response status: ${errorResponse.status}`);
|
|
40
|
-
}
|
|
41
|
-
const data = response.data;
|
|
42
|
-
if (data.source === 'remote') {
|
|
43
|
-
return {
|
|
44
|
-
data: data.data,
|
|
45
|
-
location: { path: 'remote', isRemote: true }
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
if (data.source === 'local') {
|
|
49
|
-
const localPath = data.localPath;
|
|
50
|
-
const traceData = await readLocalTraceFile(localPath);
|
|
51
|
-
return {
|
|
52
|
-
data: traceData,
|
|
53
|
-
location: { path: localPath, isRemote: false }
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
throw new Error('Invalid trace log response format');
|
|
57
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest';
|
|
2
|
-
const mockGetWorkflowIdTraceLog = vi.fn();
|
|
3
|
-
vi.mock('#api/generated/api.js', () => ({
|
|
4
|
-
getWorkflowIdTraceLog: (...args) => mockGetWorkflowIdTraceLog(...args)
|
|
5
|
-
}));
|
|
6
|
-
const mockReadFile = vi.fn();
|
|
7
|
-
vi.mock('node:fs/promises', () => ({
|
|
8
|
-
readFile: (...args) => mockReadFile(...args)
|
|
9
|
-
}));
|
|
10
|
-
describe('trace_reader', () => {
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
mockGetWorkflowIdTraceLog.mockReset();
|
|
13
|
-
mockReadFile.mockReset();
|
|
14
|
-
});
|
|
15
|
-
afterEach(() => {
|
|
16
|
-
vi.clearAllMocks();
|
|
17
|
-
});
|
|
18
|
-
describe('getTrace', () => {
|
|
19
|
-
it('should return trace data directly for remote source', async () => {
|
|
20
|
-
const mockTraceData = {
|
|
21
|
-
root: { workflowName: 'test', workflowId: 'wf-123', startTime: Date.now() },
|
|
22
|
-
children: []
|
|
23
|
-
};
|
|
24
|
-
mockGetWorkflowIdTraceLog.mockResolvedValue({
|
|
25
|
-
status: 200,
|
|
26
|
-
data: {
|
|
27
|
-
source: 'remote',
|
|
28
|
-
data: mockTraceData
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
const { getTrace } = await import('./trace_reader.js');
|
|
32
|
-
const result = await getTrace('wf-123');
|
|
33
|
-
expect(result.data).toEqual(mockTraceData);
|
|
34
|
-
expect(result.location.isRemote).toBe(true);
|
|
35
|
-
expect(mockReadFile).not.toHaveBeenCalled();
|
|
36
|
-
});
|
|
37
|
-
it('should read local file for local source', async () => {
|
|
38
|
-
const mockTraceData = {
|
|
39
|
-
root: { workflowName: 'test', workflowId: 'wf-123', startTime: Date.now() },
|
|
40
|
-
children: []
|
|
41
|
-
};
|
|
42
|
-
mockGetWorkflowIdTraceLog.mockResolvedValue({
|
|
43
|
-
status: 200,
|
|
44
|
-
data: {
|
|
45
|
-
source: 'local',
|
|
46
|
-
localPath: '/path/to/trace.json'
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
mockReadFile.mockResolvedValue(JSON.stringify(mockTraceData));
|
|
50
|
-
const { getTrace } = await import('./trace_reader.js');
|
|
51
|
-
const result = await getTrace('wf-123');
|
|
52
|
-
expect(result.data).toEqual(mockTraceData);
|
|
53
|
-
expect(result.location.isRemote).toBe(false);
|
|
54
|
-
expect(result.location.path).toBe('/path/to/trace.json');
|
|
55
|
-
expect(mockReadFile).toHaveBeenCalledWith('/path/to/trace.json', 'utf-8');
|
|
56
|
-
});
|
|
57
|
-
it('should throw error when API returns 404', async () => {
|
|
58
|
-
mockGetWorkflowIdTraceLog.mockResolvedValue({
|
|
59
|
-
status: 404,
|
|
60
|
-
data: { error: 'Not found' }
|
|
61
|
-
});
|
|
62
|
-
const { getTrace } = await import('./trace_reader.js');
|
|
63
|
-
await expect(getTrace('wf-123'))
|
|
64
|
-
.rejects
|
|
65
|
-
.toThrow('Workflow not found or no trace available: wf-123');
|
|
66
|
-
});
|
|
67
|
-
it('should throw error when API returns 500', async () => {
|
|
68
|
-
mockGetWorkflowIdTraceLog.mockResolvedValue({
|
|
69
|
-
status: 500,
|
|
70
|
-
data: { error: 'S3 access denied' }
|
|
71
|
-
});
|
|
72
|
-
const { getTrace } = await import('./trace_reader.js');
|
|
73
|
-
await expect(getTrace('wf-123'))
|
|
74
|
-
.rejects
|
|
75
|
-
.toThrow('S3 access denied');
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
});
|