@outputai/cli 0.1.11-next.49171f5.0 → 0.1.11-next.c29eb94.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/assets/docker/docker-compose-dev.yml +4 -6
- package/dist/commands/dev/index.js +2 -2
- package/dist/commands/dev/index.spec.js +0 -1
- package/dist/generated/framework_version.json +1 -1
- package/dist/services/docker.d.ts +0 -2
- package/dist/services/docker.js +1 -8
- package/dist/services/docker.spec.js +1 -59
- 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.11-next.
|
|
84
|
+
image: outputai/api:${OUTPUT_API_VERSION:-0.1.11-next.c29eb94.0}
|
|
85
85
|
init: true
|
|
86
86
|
networks:
|
|
87
87
|
- main
|
|
@@ -107,9 +107,9 @@ services:
|
|
|
107
107
|
healthcheck:
|
|
108
108
|
test: [ 'CMD-SHELL', 'npx output-healthcheck' ]
|
|
109
109
|
interval: 3s
|
|
110
|
-
timeout:
|
|
111
|
-
retries:
|
|
112
|
-
start_period:
|
|
110
|
+
timeout: 10s
|
|
111
|
+
retries: 20
|
|
112
|
+
start_period: 30s
|
|
113
113
|
init: true
|
|
114
114
|
networks:
|
|
115
115
|
- main
|
|
@@ -118,7 +118,6 @@ services:
|
|
|
118
118
|
required: false
|
|
119
119
|
environment:
|
|
120
120
|
- NODE_ENV=development
|
|
121
|
-
- COREPACK_ENABLE_DOWNLOAD_PROMPT=0
|
|
122
121
|
- OUTPUT_CATALOG_ID=${OUTPUT_CATALOG_ID:-main}
|
|
123
122
|
- OUTPUT_REDIS_URL=redis://redis:6379
|
|
124
123
|
- OUTPUT_TRACE_LOCAL_ON=${OUTPUT_TRACE_LOCAL_ON:-true}
|
|
@@ -128,7 +127,6 @@ services:
|
|
|
128
127
|
- NODE_OPTIONS=${NODE_OPTIONS:---max-old-space-size=4096}
|
|
129
128
|
command: >
|
|
130
129
|
sh -c "
|
|
131
|
-
corepack enable &&
|
|
132
130
|
npm run output:worker:install &&
|
|
133
131
|
echo 'Installed dependencies' &&
|
|
134
132
|
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'
|
|
@@ -2,7 +2,7 @@ import { Command, Flags } from '@oclif/core';
|
|
|
2
2
|
import fs from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import logUpdate from 'log-update';
|
|
5
|
-
import { validateDockerEnvironment, startDockerCompose, startDockerComposeDetached, stopDockerCompose, getServiceStatus,
|
|
5
|
+
import { validateDockerEnvironment, startDockerCompose, startDockerComposeDetached, stopDockerCompose, getServiceStatus, DockerComposeConfigNotFoundError, getDefaultDockerComposePath, SERVICE_HEALTH, SERVICE_STATE } from '#services/docker.js';
|
|
6
6
|
import { getErrorMessage } from '#utils/error_utils.js';
|
|
7
7
|
import { getDevSuccessMessage } from '#services/messages.js';
|
|
8
8
|
import { ensureClaudePlugin } from '#services/coding_agents.js';
|
|
@@ -43,7 +43,7 @@ const formatService = (service) => {
|
|
|
43
43
|
return ` ${color}${icon}${ANSI.RESET} ${name} ${ANSI.DIM}${statusPadded}${ANSI.RESET} ${ANSI.DIM}${ports}${ANSI.RESET}`;
|
|
44
44
|
};
|
|
45
45
|
const getFailedServicesWarning = (services) => {
|
|
46
|
-
const failedServices = services.filter(
|
|
46
|
+
const failedServices = services.filter(s => s.state === SERVICE_STATE.EXITED);
|
|
47
47
|
if (failedServices.length === 0) {
|
|
48
48
|
return [];
|
|
49
49
|
}
|
|
@@ -15,7 +15,6 @@ vi.mock('#services/docker.js', () => ({
|
|
|
15
15
|
{ name: 'redis', state: 'running', health: 'healthy', ports: ['6379:6379'] },
|
|
16
16
|
{ name: 'temporal', state: 'running', health: 'healthy', ports: ['7233:7233'] }
|
|
17
17
|
]),
|
|
18
|
-
isServiceFailed: vi.fn((s) => s.state === 'exited' || s.health === 'unhealthy'),
|
|
19
18
|
DockerComposeConfigNotFoundError: Error,
|
|
20
19
|
DockerValidationError: Error,
|
|
21
20
|
getDefaultDockerComposePath: vi.fn(() => '/path/to/docker-compose-dev.yml'),
|
|
@@ -27,8 +27,6 @@ export declare function validateDockerEnvironment(): void;
|
|
|
27
27
|
export declare function getDefaultDockerComposePath(): string;
|
|
28
28
|
export declare function parseServiceStatus(jsonOutput: string): ServiceStatus[];
|
|
29
29
|
export declare function getServiceStatus(dockerComposePath: string): Promise<ServiceStatus[]>;
|
|
30
|
-
export declare function isServiceHealthy(service: ServiceStatus): boolean;
|
|
31
|
-
export declare function isServiceFailed(service: ServiceStatus): boolean;
|
|
32
30
|
export declare function waitForServicesHealthy(dockerComposePath: string, timeoutMs?: number, pollIntervalMs?: number): Promise<void>;
|
|
33
31
|
export interface DockerComposeProcess {
|
|
34
32
|
process: ChildProcess;
|
package/dist/services/docker.js
CHANGED
|
@@ -103,18 +103,11 @@ const formatServiceStatus = (services) => services.map(s => {
|
|
|
103
103
|
const status = s.health === SERVICE_HEALTH.NONE ? s.state : s.health;
|
|
104
104
|
return ` ${color}${icon}${ANSI_RESET} ${s.name}: ${status}`;
|
|
105
105
|
}).join('\n');
|
|
106
|
-
export function isServiceHealthy(service) {
|
|
107
|
-
return service.state !== SERVICE_STATE.EXITED &&
|
|
108
|
-
(service.health === SERVICE_HEALTH.HEALTHY || service.health === SERVICE_HEALTH.NONE);
|
|
109
|
-
}
|
|
110
|
-
export function isServiceFailed(service) {
|
|
111
|
-
return service.state === SERVICE_STATE.EXITED || service.health === SERVICE_HEALTH.UNHEALTHY;
|
|
112
|
-
}
|
|
113
106
|
export async function waitForServicesHealthy(dockerComposePath, timeoutMs = 120000, pollIntervalMs = 2000) {
|
|
114
107
|
const startTime = Date.now();
|
|
115
108
|
while (Date.now() - startTime < timeoutMs) {
|
|
116
109
|
const services = await getServiceStatus(dockerComposePath);
|
|
117
|
-
const allHealthy = services.every(
|
|
110
|
+
const allHealthy = services.every(s => s.health === SERVICE_HEALTH.HEALTHY || s.health === SERVICE_HEALTH.NONE);
|
|
118
111
|
if (services.length > 0) {
|
|
119
112
|
const statusLines = formatServiceStatus(services);
|
|
120
113
|
logUpdate(`⏳ Waiting for services to become healthy...\n${statusLines}`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
2
|
import { execFileSync } from 'node:child_process';
|
|
3
|
-
import { parseServiceStatus, getServiceStatus, waitForServicesHealthy
|
|
3
|
+
import { parseServiceStatus, getServiceStatus, waitForServicesHealthy } from './docker.js';
|
|
4
4
|
vi.mock('node:child_process', () => ({
|
|
5
5
|
execSync: vi.fn(),
|
|
6
6
|
execFileSync: vi.fn(),
|
|
@@ -89,46 +89,6 @@ describe('docker service', () => {
|
|
|
89
89
|
await expect(getServiceStatus('/path/to/docker-compose.yml')).rejects.toThrow();
|
|
90
90
|
});
|
|
91
91
|
});
|
|
92
|
-
describe('isServiceHealthy', () => {
|
|
93
|
-
it('should return true for a running service with health: healthy', () => {
|
|
94
|
-
expect(isServiceHealthy({ name: 'redis', state: 'running', health: 'healthy', ports: [] })).toBe(true);
|
|
95
|
-
});
|
|
96
|
-
it('should return true for a running service with no health check (health: none)', () => {
|
|
97
|
-
expect(isServiceHealthy({ name: 'api', state: 'running', health: 'none', ports: [] })).toBe(true);
|
|
98
|
-
});
|
|
99
|
-
it('should return false for a running service with health: unhealthy', () => {
|
|
100
|
-
expect(isServiceHealthy({ name: 'worker', state: 'running', health: 'unhealthy', ports: [] })).toBe(false);
|
|
101
|
-
});
|
|
102
|
-
it('should return false for an exited service with health: none', () => {
|
|
103
|
-
expect(isServiceHealthy({ name: 'worker', state: 'exited', health: 'none', ports: [] })).toBe(false);
|
|
104
|
-
});
|
|
105
|
-
it('should return false for an exited service with health: unhealthy', () => {
|
|
106
|
-
expect(isServiceHealthy({ name: 'worker', state: 'exited', health: 'unhealthy', ports: [] })).toBe(false);
|
|
107
|
-
});
|
|
108
|
-
it('should return false for a service with health: starting', () => {
|
|
109
|
-
expect(isServiceHealthy({ name: 'temporal', state: 'running', health: 'starting', ports: [] })).toBe(false);
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
describe('isServiceFailed', () => {
|
|
113
|
-
it('should return true for an exited service with health: none', () => {
|
|
114
|
-
expect(isServiceFailed({ name: 'worker', state: 'exited', health: 'none', ports: [] })).toBe(true);
|
|
115
|
-
});
|
|
116
|
-
it('should return true for a running service with health: unhealthy', () => {
|
|
117
|
-
expect(isServiceFailed({ name: 'worker', state: 'running', health: 'unhealthy', ports: [] })).toBe(true);
|
|
118
|
-
});
|
|
119
|
-
it('should return true for an exited service with health: unhealthy', () => {
|
|
120
|
-
expect(isServiceFailed({ name: 'worker', state: 'exited', health: 'unhealthy', ports: [] })).toBe(true);
|
|
121
|
-
});
|
|
122
|
-
it('should return false for a running service with health: healthy', () => {
|
|
123
|
-
expect(isServiceFailed({ name: 'redis', state: 'running', health: 'healthy', ports: [] })).toBe(false);
|
|
124
|
-
});
|
|
125
|
-
it('should return false for a running service with health: none', () => {
|
|
126
|
-
expect(isServiceFailed({ name: 'api', state: 'running', health: 'none', ports: [] })).toBe(false);
|
|
127
|
-
});
|
|
128
|
-
it('should return false for a service with health: starting — not a failure, just in progress', () => {
|
|
129
|
-
expect(isServiceFailed({ name: 'temporal', state: 'running', health: 'starting', ports: [] })).toBe(false);
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
92
|
describe('waitForServicesHealthy', () => {
|
|
133
93
|
it('should resolve when all services are healthy', async () => {
|
|
134
94
|
const mockOutput = `{"Service":"redis","State":"running","Health":"healthy","Publishers":[]}
|
|
@@ -148,24 +108,6 @@ describe('docker service', () => {
|
|
|
148
108
|
const promise = waitForServicesHealthy('/path/to/docker-compose.yml', 100);
|
|
149
109
|
await expect(promise).rejects.toThrow('Timeout waiting for services to become healthy');
|
|
150
110
|
}, 10000);
|
|
151
|
-
it('should not resolve when a service has exited with no health check — regression OUT-334', async () => {
|
|
152
|
-
// Exited containers have empty Health which parses to 'none'.
|
|
153
|
-
// Previously, state:exited + health:none was incorrectly treated as healthy.
|
|
154
|
-
const mockOutput = `{"Service":"redis","State":"running","Health":"healthy","Publishers":[]}
|
|
155
|
-
{"Service":"worker","State":"exited","Health":"","Publishers":[]}`;
|
|
156
|
-
vi.mocked(execFileSync).mockReturnValue(mockOutput);
|
|
157
|
-
const promise = waitForServicesHealthy('/path/to/docker-compose.yml', 100, 50);
|
|
158
|
-
await expect(promise).rejects.toThrow('Timeout waiting for services to become healthy');
|
|
159
|
-
}, 10000);
|
|
160
|
-
it('should not resolve when a service is running but unhealthy — regression OUT-334', async () => {
|
|
161
|
-
// Nodemon keeps the container running even when the exec'd command fails,
|
|
162
|
-
// so the unhealthy case is state:running + health:unhealthy.
|
|
163
|
-
const mockOutput = `{"Service":"redis","State":"running","Health":"healthy","Publishers":[]}
|
|
164
|
-
{"Service":"worker","State":"running","Health":"unhealthy","Publishers":[]}`;
|
|
165
|
-
vi.mocked(execFileSync).mockReturnValue(mockOutput);
|
|
166
|
-
const promise = waitForServicesHealthy('/path/to/docker-compose.yml', 100, 50);
|
|
167
|
-
await expect(promise).rejects.toThrow('Timeout waiting for services to become healthy');
|
|
168
|
-
}, 10000);
|
|
169
111
|
it('should poll multiple times until healthy', async () => {
|
|
170
112
|
const callTracker = { count: 0 };
|
|
171
113
|
vi.mocked(execFileSync).mockImplementation(() => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@outputai/cli",
|
|
3
|
-
"version": "0.1.11-next.
|
|
3
|
+
"version": "0.1.11-next.c29eb94.0",
|
|
4
4
|
"description": "CLI for Output.ai workflow generation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -33,9 +33,9 @@
|
|
|
33
33
|
"log-update": "7.2.0",
|
|
34
34
|
"semver": "7.7.4",
|
|
35
35
|
"yaml": "^2.8.3",
|
|
36
|
-
"@outputai/
|
|
37
|
-
"@outputai/
|
|
38
|
-
"@outputai/
|
|
36
|
+
"@outputai/evals": "0.1.11-next.c29eb94.0",
|
|
37
|
+
"@outputai/credentials": "0.1.11-next.c29eb94.0",
|
|
38
|
+
"@outputai/llm": "0.1.11-next.c29eb94.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/cli-progress": "3.11.6",
|