@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.
@@ -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.49171f5.0}
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: 3s
111
- retries: 2
112
- start_period: 60s
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, isServiceFailed, DockerComposeConfigNotFoundError, getDefaultDockerComposePath, SERVICE_HEALTH, SERVICE_STATE } from '#services/docker.js';
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(isServiceFailed);
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'),
@@ -1,3 +1,3 @@
1
1
  {
2
- "framework": "0.1.11-next.49171f5.0"
2
+ "framework": "0.1.11-next.c29eb94.0"
3
3
  }
@@ -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;
@@ -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(isServiceHealthy);
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, isServiceHealthy, isServiceFailed } from './docker.js';
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.49171f5.0",
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/credentials": "0.1.11-next.49171f5.0",
37
- "@outputai/llm": "0.1.11-next.49171f5.0",
38
- "@outputai/evals": "0.1.11-next.49171f5.0"
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",