@outputai/cli 0.1.12-next.ecd4dda.0 → 0.1.12
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/assets/config/costs.yml +6 -0
- package/dist/assets/docker/docker-compose-dev.yml +2 -8
- package/dist/commands/fix.d.ts +10 -0
- package/dist/commands/fix.js +58 -0
- package/dist/commands/fix.spec.d.ts +1 -0
- package/dist/commands/fix.spec.js +77 -0
- package/dist/generated/framework_version.json +1 -1
- package/dist/services/fix_package.d.ts +34 -0
- package/dist/services/fix_package.js +105 -0
- package/dist/services/fix_package.spec.d.ts +1 -0
- package/dist/services/fix_package.spec.js +63 -0
- package/dist/templates/agent_instructions/dotclaude/settings.json.template +0 -1
- package/dist/templates/project/README.md.template +2 -2
- package/dist/templates/project/package.json.template +8 -7
- package/dist/views/dev.js +9 -2
- package/package.json +4 -4
|
@@ -81,7 +81,7 @@ services:
|
|
|
81
81
|
condition: service_healthy
|
|
82
82
|
worker:
|
|
83
83
|
condition: service_healthy
|
|
84
|
-
image: outputai/api:${OUTPUT_API_VERSION:-0.1.12
|
|
84
|
+
image: outputai/api:${OUTPUT_API_VERSION:-0.1.12}
|
|
85
85
|
init: true
|
|
86
86
|
networks:
|
|
87
87
|
- main
|
|
@@ -126,13 +126,7 @@ services:
|
|
|
126
126
|
- OUTPUT_TRACE_HTTP_VERBOSE=${OUTPUT_TRACE_HTTP_VERBOSE:-true}
|
|
127
127
|
- TEMPORAL_ADDRESS=temporal:7233
|
|
128
128
|
- NODE_OPTIONS=${NODE_OPTIONS:---max-old-space-size=4096}
|
|
129
|
-
command:
|
|
130
|
-
sh -c "
|
|
131
|
-
corepack enable &&
|
|
132
|
-
npm run output:worker:install &&
|
|
133
|
-
echo 'Installed dependencies' &&
|
|
134
|
-
npx nodemon --watch src --watch package.json --ext ts,js,json,prompt --ignore 'dist/**' --ignore '**/*.test.ts' --ignore '**/*.spec.ts' --exec 'npm run output:worker:install && npm run output:worker:build && npm run output:worker:start'
|
|
135
|
-
"
|
|
129
|
+
command: sh -c "corepack enable && npm run output:worker:watch"
|
|
136
130
|
working_dir: /app/${OUTPUT_WORKFLOWS_DIR:-.}
|
|
137
131
|
volumes:
|
|
138
132
|
- ./:/app
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import { confirm } from '@inquirer/prompts';
|
|
3
|
+
import { applyFix, planFix } from '#services/fix_package.js';
|
|
4
|
+
import { getErrorMessage } from '#utils/error_utils.js';
|
|
5
|
+
const Ansi = {
|
|
6
|
+
GREEN: '\x1b[32m',
|
|
7
|
+
RED: '\x1b[31m',
|
|
8
|
+
YELLOW: '\x1b[33m',
|
|
9
|
+
RESET: '\x1b[0m'
|
|
10
|
+
};
|
|
11
|
+
export default class Fix extends Command {
|
|
12
|
+
static description = 'Fix Output scripts in the package.json (reset overwrites, add missing and remove deprecated)';
|
|
13
|
+
static examples = [
|
|
14
|
+
'<%= config.bin %> <%= command.id %>'
|
|
15
|
+
];
|
|
16
|
+
/**
|
|
17
|
+
* Prints a human-readable diff-style summary
|
|
18
|
+
*/
|
|
19
|
+
printPlan(plan) {
|
|
20
|
+
this.log('\nNecessary changes to package.json:');
|
|
21
|
+
if (plan.scriptsToAdd.length > 0) {
|
|
22
|
+
this.log('\n Scripts to add:');
|
|
23
|
+
plan.scriptsToAdd.forEach(({ key }) => this.log(` ${Ansi.GREEN}+${Ansi.RESET} "${key}"`));
|
|
24
|
+
}
|
|
25
|
+
if (plan.scriptsToReplace.length > 0) {
|
|
26
|
+
this.log('\n Scripts to replace:');
|
|
27
|
+
plan.scriptsToReplace.forEach(({ key }) => this.log(` ${Ansi.YELLOW}~${Ansi.RESET} "${key}"`));
|
|
28
|
+
}
|
|
29
|
+
if (plan.scriptsToRemove.length > 0) {
|
|
30
|
+
this.log('\n Scripts to remove:');
|
|
31
|
+
plan.scriptsToRemove.forEach(({ key }) => this.log(` ${Ansi.RED}-${Ansi.RESET} "${key}"`));
|
|
32
|
+
}
|
|
33
|
+
this.log('');
|
|
34
|
+
}
|
|
35
|
+
async run() {
|
|
36
|
+
await this.parse(Fix);
|
|
37
|
+
try {
|
|
38
|
+
const plan = planFix(process.cwd());
|
|
39
|
+
if (!plan.hasChanges) {
|
|
40
|
+
this.log('Nothing to change, package.json is already properly configured.');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
this.printPlan(plan);
|
|
44
|
+
const shouldApply = await confirm({ message: 'Apply these changes to package.json?', default: true });
|
|
45
|
+
if (!shouldApply) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
applyFix(plan);
|
|
49
|
+
this.log('Done, package.json is properly configured.');
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
// ExitPromptError means Ctrl+C
|
|
53
|
+
if (!(error instanceof Error) || error.constructor.name !== 'ExitPromptError') {
|
|
54
|
+
this.error(getErrorMessage(error));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
3
|
+
import Fix from './fix.js';
|
|
4
|
+
import * as fixService from '#services/fix_package.js';
|
|
5
|
+
import { confirm } from '@inquirer/prompts';
|
|
6
|
+
vi.mock('#services/fix_package.js', () => ({
|
|
7
|
+
planFix: vi.fn(),
|
|
8
|
+
applyFix: vi.fn()
|
|
9
|
+
}));
|
|
10
|
+
vi.mock('@inquirer/prompts', () => ({
|
|
11
|
+
confirm: vi.fn()
|
|
12
|
+
}));
|
|
13
|
+
const basePlan = () => ({
|
|
14
|
+
packageJsonPath: '/tmp/pkg/package.json',
|
|
15
|
+
packageJsonUpdatedContent: '{}',
|
|
16
|
+
hasChanges: true,
|
|
17
|
+
scriptsToRemove: [{ key: 'dev', value: 'old' }],
|
|
18
|
+
scriptsToAdd: [{ key: 'output:new', value: 'echo new' }],
|
|
19
|
+
scriptsToReplace: [{ key: 'output:dev', before: 'old dev', after: 'output dev' }]
|
|
20
|
+
});
|
|
21
|
+
describe('fix command', () => {
|
|
22
|
+
const createTestCommand = () => {
|
|
23
|
+
const cmd = new Fix([], {});
|
|
24
|
+
cmd.log = vi.fn();
|
|
25
|
+
cmd.warn = vi.fn();
|
|
26
|
+
cmd.error = vi.fn();
|
|
27
|
+
cmd.debug = vi.fn();
|
|
28
|
+
cmd.parse = vi.fn().mockResolvedValue({ flags: {}, args: {} });
|
|
29
|
+
return cmd;
|
|
30
|
+
};
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
vi.clearAllMocks();
|
|
33
|
+
});
|
|
34
|
+
it('should have no flags', () => {
|
|
35
|
+
expect(Fix.flags).toBeUndefined();
|
|
36
|
+
});
|
|
37
|
+
it('should skip confirm when no changes are needed', async () => {
|
|
38
|
+
vi.mocked(fixService.planFix).mockReturnValue({
|
|
39
|
+
...basePlan(),
|
|
40
|
+
hasChanges: false
|
|
41
|
+
});
|
|
42
|
+
const cmd = createTestCommand();
|
|
43
|
+
await cmd.run();
|
|
44
|
+
expect(confirm).not.toHaveBeenCalled();
|
|
45
|
+
expect(fixService.applyFix).not.toHaveBeenCalled();
|
|
46
|
+
expect(cmd.log).toHaveBeenCalledWith('Nothing to change, package.json is already properly configured.');
|
|
47
|
+
});
|
|
48
|
+
it('should print summary, confirm, and apply when there are changes', async () => {
|
|
49
|
+
vi.mocked(fixService.planFix).mockReturnValue(basePlan());
|
|
50
|
+
vi.mocked(confirm).mockResolvedValue(true);
|
|
51
|
+
vi.mocked(fixService.applyFix).mockImplementation(() => { });
|
|
52
|
+
const cmd = createTestCommand();
|
|
53
|
+
await cmd.run();
|
|
54
|
+
expect(confirm).toHaveBeenCalledWith(expect.objectContaining({
|
|
55
|
+
message: 'Apply these changes to package.json?',
|
|
56
|
+
default: true
|
|
57
|
+
}));
|
|
58
|
+
expect(cmd.log).toHaveBeenCalledWith(expect.stringContaining('Necessary changes to package.json'));
|
|
59
|
+
expect(fixService.applyFix).toHaveBeenCalledTimes(1);
|
|
60
|
+
expect(cmd.log).toHaveBeenCalledWith('Done, package.json is properly configured.');
|
|
61
|
+
});
|
|
62
|
+
it('should not apply when user declines', async () => {
|
|
63
|
+
vi.mocked(fixService.planFix).mockReturnValue(basePlan());
|
|
64
|
+
vi.mocked(confirm).mockResolvedValue(false);
|
|
65
|
+
const cmd = createTestCommand();
|
|
66
|
+
await cmd.run();
|
|
67
|
+
expect(fixService.applyFix).not.toHaveBeenCalled();
|
|
68
|
+
});
|
|
69
|
+
it('should surface service errors', async () => {
|
|
70
|
+
vi.mocked(fixService.planFix).mockImplementation(() => {
|
|
71
|
+
throw new Error('boom');
|
|
72
|
+
});
|
|
73
|
+
const cmd = createTestCommand();
|
|
74
|
+
await cmd.run();
|
|
75
|
+
expect(cmd.error).toHaveBeenCalledWith('boom');
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Legacy script names from older versions of the project */
|
|
2
|
+
export declare const legacyScripts: readonly ["dev"];
|
|
3
|
+
export interface ScriptToRemove {
|
|
4
|
+
key: string;
|
|
5
|
+
value: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ScriptToAdd {
|
|
8
|
+
key: string;
|
|
9
|
+
value: string;
|
|
10
|
+
}
|
|
11
|
+
export interface ScriptToReplace {
|
|
12
|
+
key: string;
|
|
13
|
+
before: string;
|
|
14
|
+
after: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Plan for aligning `scripts` with the scaffold template (reads only; no write until apply).
|
|
18
|
+
*/
|
|
19
|
+
export interface FixPlan {
|
|
20
|
+
packageJsonPath: string;
|
|
21
|
+
packageJsonUpdatedContent: string;
|
|
22
|
+
hasChanges: boolean;
|
|
23
|
+
scriptsToRemove: ScriptToRemove[];
|
|
24
|
+
scriptsToReplace: ScriptToReplace[];
|
|
25
|
+
scriptsToAdd: ScriptToAdd[];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Computes the package.json rewrite without writing. Use with {@link applyFix}.
|
|
29
|
+
*/
|
|
30
|
+
export declare function planFix(projectRoot: string): FixPlan;
|
|
31
|
+
/**
|
|
32
|
+
* Writes the planned package.json. No-op when {@link FixPlan.hasChanges} is false.
|
|
33
|
+
*/
|
|
34
|
+
export declare function applyFix(plan: FixPlan): void;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { processTemplate } from '#utils/template.js';
|
|
5
|
+
const packageJson = 'package.json';
|
|
6
|
+
const templateRelativePath = path.join('templates', 'project', 'package.json.template');
|
|
7
|
+
/** Legacy script names from older versions of the project */
|
|
8
|
+
export const legacyScripts = ['dev'];
|
|
9
|
+
function getTemplatesPackageJsonPath() {
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = path.dirname(__filename);
|
|
12
|
+
return path.join(__dirname, '..', templateRelativePath);
|
|
13
|
+
}
|
|
14
|
+
function readPackageJsonText(packagePath) {
|
|
15
|
+
try {
|
|
16
|
+
return readFileSync(packagePath, 'utf-8');
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
if (typeof error !== 'object' || error === null) {
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
const code = error.code;
|
|
23
|
+
if (typeof code === 'string' && code === 'ENOENT') {
|
|
24
|
+
throw new Error(`No ${packageJson} found at ${packagePath}. Run this command from your Output project root.`);
|
|
25
|
+
}
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function parsePackageJsonObject(raw, packagePath) {
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(raw);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
throw new Error(`File ${packagePath} is not a valid JSON.`, { cause: error });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function parseTemplatePackageJson(processed) {
|
|
38
|
+
try {
|
|
39
|
+
return JSON.parse(processed);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
throw new Error(`Internal error: failed to parse processed ${templateRelativePath}.`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Computes the package.json rewrite without writing. Use with {@link applyFix}.
|
|
47
|
+
*/
|
|
48
|
+
export function planFix(projectRoot) {
|
|
49
|
+
const packageJsonPath = path.join(projectRoot, packageJson);
|
|
50
|
+
const raw = readPackageJsonText(packageJsonPath);
|
|
51
|
+
const pkg = parsePackageJsonObject(raw, packageJsonPath);
|
|
52
|
+
const originalScripts = typeof pkg.scripts === 'object' && pkg.scripts !== null && !Array.isArray(pkg.scripts) ?
|
|
53
|
+
{ ...pkg.scripts } :
|
|
54
|
+
{};
|
|
55
|
+
const scripts = { ...originalScripts };
|
|
56
|
+
const scriptsToRemove = [];
|
|
57
|
+
for (const key of legacyScripts) {
|
|
58
|
+
if (Object.hasOwn(scripts, key)) {
|
|
59
|
+
scriptsToRemove.push({ key, value: scripts[key] });
|
|
60
|
+
delete scripts[key];
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const templateRaw = readFileSync(getTemplatesPackageJsonPath(), 'utf-8');
|
|
64
|
+
const templateVars = { projectName: '', description: '', frameworkVersion: '' };
|
|
65
|
+
const processed = processTemplate(templateRaw, templateVars);
|
|
66
|
+
const templatePkg = parseTemplatePackageJson(processed);
|
|
67
|
+
const templateScripts = templatePkg.scripts;
|
|
68
|
+
if (!templateScripts || typeof templateScripts !== 'object') {
|
|
69
|
+
throw new Error(`Internal error: ${templateRelativePath} has no scripts object.`);
|
|
70
|
+
}
|
|
71
|
+
const scriptsToAdd = [];
|
|
72
|
+
const scriptsToReplace = [];
|
|
73
|
+
for (const [key, after] of Object.entries(templateScripts)) {
|
|
74
|
+
const before = originalScripts[key];
|
|
75
|
+
if (before !== after) {
|
|
76
|
+
if (Object.hasOwn(originalScripts, key)) {
|
|
77
|
+
scriptsToReplace.push({ key, before: before, after });
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
scriptsToAdd.push({ key, value: after });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
scripts[key] = after;
|
|
84
|
+
}
|
|
85
|
+
pkg.scripts = scripts;
|
|
86
|
+
const packageJsonUpdatedContent = `${JSON.stringify(pkg, null, 2)}\n`;
|
|
87
|
+
const hasChanges = packageJsonUpdatedContent !== raw;
|
|
88
|
+
return {
|
|
89
|
+
packageJsonPath,
|
|
90
|
+
packageJsonUpdatedContent,
|
|
91
|
+
hasChanges,
|
|
92
|
+
scriptsToRemove,
|
|
93
|
+
scriptsToReplace,
|
|
94
|
+
scriptsToAdd
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Writes the planned package.json. No-op when {@link FixPlan.hasChanges} is false.
|
|
99
|
+
*/
|
|
100
|
+
export function applyFix(plan) {
|
|
101
|
+
if (!plan.hasChanges) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
writeFileSync(plan.packageJsonPath, plan.packageJsonUpdatedContent, 'utf-8');
|
|
105
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import * as fs from 'node:fs/promises';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { applyFix, planFix, legacyScripts } from './fix_package.js';
|
|
6
|
+
describe('fix package', () => {
|
|
7
|
+
it('should remove legacy keys and apply template scripts while preserving other scripts', async () => {
|
|
8
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'fix-test-'));
|
|
9
|
+
const pkg = {
|
|
10
|
+
name: 'my-proj',
|
|
11
|
+
version: '1.0.0',
|
|
12
|
+
scripts: {
|
|
13
|
+
dev: 'old dev',
|
|
14
|
+
'custom:test': 'vitest'
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
await fs.writeFile(path.join(tmpDir, 'package.json'), JSON.stringify(pkg, null, 2), 'utf-8');
|
|
18
|
+
const plan = planFix(tmpDir);
|
|
19
|
+
expect(plan.scriptsToRemove.map(r => r.key).sort()).toEqual([...legacyScripts].sort());
|
|
20
|
+
expect(plan.hasChanges).toBe(true);
|
|
21
|
+
expect(plan.scriptsToReplace).toEqual([]);
|
|
22
|
+
expect(plan.scriptsToAdd).toHaveLength(6);
|
|
23
|
+
applyFix(plan);
|
|
24
|
+
const next = JSON.parse(await fs.readFile(path.join(tmpDir, 'package.json'), 'utf-8'));
|
|
25
|
+
expect(next.scripts['dev']).toBeUndefined();
|
|
26
|
+
expect(next.scripts['custom:test']).toBe('vitest');
|
|
27
|
+
expect(next.scripts['output:dev']).toBe('output dev');
|
|
28
|
+
expect(next.scripts['output:worker:start']).toBe('output-worker');
|
|
29
|
+
});
|
|
30
|
+
it('should classify an existing key with a different value as replace, and new keys as add', async () => {
|
|
31
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'fix-test-'));
|
|
32
|
+
const pkg = {
|
|
33
|
+
name: 'my-proj',
|
|
34
|
+
version: '1.0.0',
|
|
35
|
+
scripts: {
|
|
36
|
+
'output:dev': 'bad-dev-command'
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
await fs.writeFile(path.join(tmpDir, 'package.json'), JSON.stringify(pkg, null, 2), 'utf-8');
|
|
40
|
+
const plan = planFix(tmpDir);
|
|
41
|
+
expect(plan.scriptsToRemove).toEqual([]);
|
|
42
|
+
expect(plan.scriptsToReplace).toEqual([
|
|
43
|
+
{ key: 'output:dev', before: 'bad-dev-command', after: 'output dev' }
|
|
44
|
+
]);
|
|
45
|
+
expect(plan.scriptsToAdd).toHaveLength(5);
|
|
46
|
+
expect(plan.scriptsToAdd.map(a => a.key).sort()).toEqual([
|
|
47
|
+
'output:worker',
|
|
48
|
+
'output:worker:build',
|
|
49
|
+
'output:worker:install',
|
|
50
|
+
'output:worker:start',
|
|
51
|
+
'output:worker:watch'
|
|
52
|
+
].sort());
|
|
53
|
+
});
|
|
54
|
+
it('should throw when package.json is missing', async () => {
|
|
55
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'fix-test-'));
|
|
56
|
+
expect(() => planFix(tmpDir)).toThrow(/No package\.json found/);
|
|
57
|
+
});
|
|
58
|
+
it('should throw when package.json is not valid JSON', async () => {
|
|
59
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'fix-test-'));
|
|
60
|
+
await fs.writeFile(path.join(tmpDir, 'package.json'), '{ not json', 'utf-8');
|
|
61
|
+
expect(() => planFix(tmpDir)).toThrow(/not a valid JSON/);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -72,7 +72,7 @@ Add your keys in the editor:
|
|
|
72
72
|
### 3. Start Output Services
|
|
73
73
|
|
|
74
74
|
```bash
|
|
75
|
-
npm run dev
|
|
75
|
+
npm run output:dev
|
|
76
76
|
```
|
|
77
77
|
|
|
78
78
|
This starts:
|
|
@@ -91,7 +91,7 @@ npx output workflow run blog_evaluator paulgraham_hwh
|
|
|
91
91
|
|
|
92
92
|
### 5. Stop Services
|
|
93
93
|
|
|
94
|
-
Press `Ctrl+C` in the terminal running `npm run dev` to stop all services gracefully.
|
|
94
|
+
Press `Ctrl+C` in the terminal running `npm run output:dev` to stop all services gracefully.
|
|
95
95
|
|
|
96
96
|
### 6. View Logs
|
|
97
97
|
|
|
@@ -6,18 +6,19 @@
|
|
|
6
6
|
"main": "dist/worker.js",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"output:worker:install": "npm install",
|
|
9
|
-
"output:worker:build": "rm -rf dist/* && tsc -p ./ &&
|
|
9
|
+
"output:worker:build": "rm -rf dist/* && tsc -p ./ && output-copy-assets",
|
|
10
10
|
"output:worker:start": "output-worker",
|
|
11
|
-
"
|
|
11
|
+
"output:worker": "npm run output:worker:install && npm run output:worker:build && npm run output:worker:start",
|
|
12
|
+
"output:worker:watch": "npx nodemon --watch src --watch package.json --ext ts,js,json,prompt,md --ignore 'dist/**' --ignore '**/*.spec.*' --ignore '**/*.test.*' --exec 'npm run output:worker'",
|
|
13
|
+
"output:dev": "output dev"
|
|
12
14
|
},
|
|
13
15
|
"dependencies": {
|
|
14
|
-
"@outputai/output": "
|
|
16
|
+
"@outputai/output": "{{frameworkVersion}}"
|
|
15
17
|
},
|
|
16
18
|
"devDependencies": {
|
|
17
|
-
"@types/node": "
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"typescript": "^5.7.0"
|
|
19
|
+
"@types/node": "24.5.2",
|
|
20
|
+
"nodemon": "3.1.14",
|
|
21
|
+
"typescript": "5.9.3"
|
|
21
22
|
},
|
|
22
23
|
"engines": {
|
|
23
24
|
"node": ">=24.3.0"
|
package/dist/views/dev.js
CHANGED
|
@@ -79,6 +79,10 @@ const useHealthPolling = (dockerComposePath, enabled, callbacks) => {
|
|
|
79
79
|
callbacksRef.current.onAllHealthy(svcs);
|
|
80
80
|
return 'done';
|
|
81
81
|
}
|
|
82
|
+
if (svcs.length > 0 && svcs.find(isServiceFailed)) {
|
|
83
|
+
callbacksRef.current.onFailure(svcs);
|
|
84
|
+
return 'done';
|
|
85
|
+
}
|
|
82
86
|
return 'continue';
|
|
83
87
|
});
|
|
84
88
|
};
|
|
@@ -115,7 +119,7 @@ const FailureWarning = ({ services }) => {
|
|
|
115
119
|
}
|
|
116
120
|
const failedNames = failed.map(s => s.name).join(', ');
|
|
117
121
|
const hasWorker = failed.some(s => s.name.toLowerCase().includes('worker'));
|
|
118
|
-
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Box, { children: _jsx(Text, { backgroundColor: "red", color: "white", bold: true, children: " \u26A0\uFE0F SERVICE FAILURE DETECTED " }) }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "red", bold: true, children: "Failed services: " }), _jsx(Text, { children: failedNames })] }), hasWorker ? (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: "yellow", bold: true, children: "\u26A1 The worker is not running!" }), _jsx(Text, { color: "yellow", children: ' Workflows will fail until the worker is restarted.' }), _jsx(Box, { marginTop: 1, children:
|
|
122
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Box, { children: _jsx(Text, { backgroundColor: "red", color: "white", bold: true, children: " \u26A0\uFE0F SERVICE FAILURE DETECTED " }) }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "red", bold: true, children: "Failed services: " }), _jsx(Text, { children: failedNames })] }), hasWorker ? (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: "yellow", bold: true, children: "\u26A1 The worker is not running!" }), _jsx(Text, { color: "yellow", children: ' Workflows will fail until the worker is restarted.' }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsxs(Text, { children: ["\uD83D\uDD0D Check the logs with: ", _jsx(Text, { color: "magenta", children: "docker compose logs worker" })] }) }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsxs(Text, { children: ["\uD83D\uDD27 If you just updated ", _jsx(Text, { italic: true, children: "@outputai/cli" }), ", try: ", _jsx(Text, { color: "magenta", children: "output fix" })] }) })] })) : (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: ["\uD83D\uDD0D Check the logs with: ", _jsx(Text, { color: "magenta", children: "docker compose logs <service-name>" })] }) }))] }));
|
|
119
123
|
};
|
|
120
124
|
const DevSuccessMessage = ({ services }) => {
|
|
121
125
|
const divider = '─'.repeat(80);
|
|
@@ -136,9 +140,12 @@ export const DevApp = ({ dockerComposePath, onCleanup }) => {
|
|
|
136
140
|
setSuccessItems([{ id: 'success', services: svcs }]);
|
|
137
141
|
setPhase('running');
|
|
138
142
|
},
|
|
143
|
+
onFailure: () => {
|
|
144
|
+
setPhase('failed');
|
|
145
|
+
},
|
|
139
146
|
onTimeout: () => exit(new Error('Timeout waiting for services to become healthy'))
|
|
140
147
|
});
|
|
141
148
|
useStatusRefresh(dockerComposePath, phase === 'running', setServices);
|
|
142
149
|
useCtrlC(onCleanup);
|
|
143
|
-
return (_jsxs(_Fragment, { children: [_jsx(Static, { items: successItems, children: item => _jsx(DevSuccessMessage, { services: item.services }, item.id) }), phase === 'waiting' && _jsx(WaitingView, { services: services }), phase === 'running' && _jsx(RunningView, { services: services })] }));
|
|
150
|
+
return (_jsxs(_Fragment, { children: [_jsx(Static, { items: successItems, children: item => _jsx(DevSuccessMessage, { services: item.services }, item.id) }), phase === 'waiting' && _jsx(WaitingView, { services: services }), phase === 'running' && _jsx(RunningView, { services: services }), phase === 'failed' && _jsx(RunningView, { services: services })] }));
|
|
144
151
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@outputai/cli",
|
|
3
|
-
"version": "0.1.12
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"description": "CLI for Output.ai workflow generation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
"react": "19.2.4",
|
|
36
36
|
"semver": "7.7.4",
|
|
37
37
|
"yaml": "^2.8.3",
|
|
38
|
-
"@outputai/
|
|
39
|
-
"@outputai/
|
|
40
|
-
"@outputai/llm": "0.1.12
|
|
38
|
+
"@outputai/credentials": "0.1.12",
|
|
39
|
+
"@outputai/evals": "0.1.12",
|
|
40
|
+
"@outputai/llm": "0.1.12"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/cli-progress": "3.11.6",
|