Skip to content

Commit 42d06c8

Browse files
url and version
1 parent 9024e9a commit 42d06c8

File tree

8 files changed

+194
-56
lines changed

8 files changed

+194
-56
lines changed

.changeset/fix-version-display.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@walkeros/cli': minor
3+
'@walkeros/docker': minor
4+
---
5+
6+
Fix Docker runtime version display - versions now read reliably from
7+
package.json at runtime instead of fragile build-time injection

packages/cli/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@walkeros/cli",
3-
"version": "0.5.1-next.1",
3+
"version": "0.6.0",
44
"description": "walkerOS CLI - Bundle and deploy walkerOS components",
55
"license": "MIT",
66
"type": "module",
@@ -34,7 +34,7 @@
3434
},
3535
"dependencies": {
3636
"@walkeros/core": "^0.5.1-next.0",
37-
"@walkeros/docker": "0.3.1-next.0",
37+
"@walkeros/docker": "0.6.0",
3838
"@walkeros/server-core": "0.5.1-next.0",
3939
"chalk": "^5.6.2",
4040
"commander": "^14.0.2",
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { buildDockerCommand } from '../../core/docker.js';
2+
import { isUrl, downloadFromUrl } from '../../config/utils.js';
3+
import fs from 'fs-extra';
4+
import os from 'os';
5+
6+
describe('Docker URL handling', () => {
7+
describe('URL utilities for Docker pre-download', () => {
8+
it('should detect HTTP URLs', () => {
9+
expect(isUrl('https://example.com/flow.json')).toBe(true);
10+
expect(isUrl('http://example.com/flow.json')).toBe(true);
11+
expect(isUrl('./local-flow.json')).toBe(false);
12+
expect(isUrl('/absolute/path/flow.json')).toBe(false);
13+
});
14+
15+
it('should download URL to temp file with proper cleanup', async () => {
16+
// This tests the pre-download pattern that executeInDocker should use
17+
const testUrl =
18+
'https://raw.githubusercontent.com/elbwalker/walkerOS/main/packages/cli/package.json';
19+
20+
// Download URL to temp file
21+
const tempFile = await downloadFromUrl(testUrl);
22+
23+
// Verify temp file exists and is in temp directory
24+
expect(await fs.pathExists(tempFile)).toBe(true);
25+
expect(tempFile.startsWith(os.tmpdir())).toBe(true);
26+
expect(tempFile).toContain('walkeros-download');
27+
28+
// Verify content is valid JSON
29+
const content = await fs.readJson(tempFile);
30+
expect(content.name).toBe('@walkeros/cli');
31+
32+
// Cleanup
33+
await fs.remove(tempFile);
34+
expect(await fs.pathExists(tempFile)).toBe(false);
35+
});
36+
});
37+
38+
describe('buildDockerCommand', () => {
39+
it('should mount local config file to container', () => {
40+
const cmd = buildDockerCommand(
41+
'bundle',
42+
['flow.json', '--output', 'dist'],
43+
{},
44+
'flow.json',
45+
);
46+
47+
// Should include volume mount for local file
48+
expect(cmd).toContainEqual('-v');
49+
expect(cmd.some((arg) => arg.includes('/config/flow.json:ro'))).toBe(
50+
true,
51+
);
52+
});
53+
54+
it('should NOT mount URLs - pass them through as-is', () => {
55+
const url = 'https://example.com/flow.json';
56+
const cmd = buildDockerCommand(
57+
'bundle',
58+
[url, '--output', 'dist'],
59+
{},
60+
url,
61+
);
62+
63+
// Should NOT include config volume mount for URLs
64+
const volumeArgs = cmd.filter(
65+
(arg) => typeof arg === 'string' && arg.includes('/config/flow.json'),
66+
);
67+
expect(volumeArgs).toHaveLength(0);
68+
69+
// URL should be preserved in args (currently passed through)
70+
expect(cmd).toContain(url);
71+
});
72+
73+
it('should preserve URL in args when no configFile mounting needed', () => {
74+
const url =
75+
'https://raw.githubusercontent.com/elbwalker/walkerOS/main/flow.json';
76+
const cmd = buildDockerCommand(
77+
'bundle',
78+
[url, '--output', 'dist'],
79+
{},
80+
url,
81+
);
82+
83+
// URL should remain in the command args
84+
expect(cmd.includes(url)).toBe(true);
85+
});
86+
});
87+
});

packages/cli/src/core/docker.ts

Lines changed: 57 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,12 @@
66

77
import { spawn } from 'child_process';
88
import path from 'path';
9+
import fs from 'fs-extra';
910
import { VERSION as DOCKER_VERSION } from '@walkeros/docker';
10-
import { isUrl } from '../config/utils.js';
11+
import { VERSION as CLI_VERSION } from '../version.js';
12+
import { isUrl, downloadFromUrl } from '../config/utils.js';
1113
import type { GlobalOptions } from '../types/global.js';
1214

13-
// Version injected at build time via tsup define
14-
declare const __VERSION__: string;
15-
const CLI_VERSION = typeof __VERSION__ !== 'undefined' ? __VERSION__ : '0.0.0';
16-
1715
/**
1816
* Docker image for CLI/build tools (bundle, simulate)
1917
* Uses explicit version by default, can be overridden with env var
@@ -128,10 +126,13 @@ export function buildDockerCommand(
128126
/**
129127
* Execute command in Docker container
130128
*
129+
* For remote URLs, downloads the config to a temp file first since Docker
130+
* containers can't access URLs directly via volume mounting.
131+
*
131132
* @param command - CLI command
132133
* @param args - Command arguments
133134
* @param options - Global options
134-
* @param configFile - Optional config file path to mount in Docker
135+
* @param configFile - Optional config file path or URL to mount in Docker
135136
* @returns Promise that resolves when command completes
136137
*/
137138
export async function executeInDocker(
@@ -140,38 +141,59 @@ export async function executeInDocker(
140141
options: GlobalOptions = {},
141142
configFile?: string,
142143
): Promise<void> {
143-
// Force --local execution inside container to prevent nested Docker attempts
144-
// Architecture: Host CLI decides environment (Docker vs local),
145-
// Container CLI always executes locally (no Docker-in-Docker)
146-
const containerArgs = [...args, '--local'];
147-
148-
const dockerCmd = buildDockerCommand(
149-
command,
150-
containerArgs,
151-
options,
152-
configFile,
153-
);
154-
155-
return new Promise((resolve, reject) => {
156-
const proc = spawn(dockerCmd[0], dockerCmd.slice(1), {
157-
stdio: options.silent ? 'ignore' : 'inherit',
158-
shell: false,
159-
});
144+
let tempFile: string | undefined;
145+
let effectiveConfigFile = configFile;
146+
147+
try {
148+
// Pre-download URL configs to temp file for Docker mounting
149+
// Docker can only mount local files, not remote URLs
150+
if (configFile && isUrl(configFile)) {
151+
tempFile = await downloadFromUrl(configFile);
152+
effectiveConfigFile = tempFile;
153+
}
160154

161-
proc.on('error', (error) => {
162-
reject(new Error(`Docker execution failed: ${error.message}`));
155+
// Force --local execution inside container to prevent nested Docker attempts
156+
// Architecture: Host CLI decides environment (Docker vs local),
157+
// Container CLI always executes locally (no Docker-in-Docker)
158+
const containerArgs = [...args, '--local'];
159+
160+
const dockerCmd = buildDockerCommand(
161+
command,
162+
containerArgs,
163+
options,
164+
effectiveConfigFile,
165+
);
166+
167+
return await new Promise((resolve, reject) => {
168+
const proc = spawn(dockerCmd[0], dockerCmd.slice(1), {
169+
stdio: options.silent ? 'ignore' : 'inherit',
170+
shell: false,
171+
});
172+
173+
proc.on('error', (error) => {
174+
reject(new Error(`Docker execution failed: ${error.message}`));
175+
});
176+
177+
proc.on('exit', (code) => {
178+
if (code === 0) {
179+
resolve();
180+
} else {
181+
// Docker already logged the error via stdio inherit
182+
// Just exit with same code - no duplicate message
183+
process.exit(code || 1);
184+
}
185+
});
163186
});
164-
165-
proc.on('exit', (code) => {
166-
if (code === 0) {
167-
resolve();
168-
} else {
169-
// Docker already logged the error via stdio inherit
170-
// Just exit with same code - no duplicate message
171-
process.exit(code || 1);
187+
} finally {
188+
// Clean up temp file if we created one
189+
if (tempFile) {
190+
try {
191+
await fs.remove(tempFile);
192+
} catch {
193+
// Ignore cleanup errors
172194
}
173-
});
174-
});
195+
}
196+
}
175197
}
176198

177199
/**

packages/cli/src/index.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,12 @@
11
import { Command } from 'commander';
2-
import { readFileSync } from 'fs';
3-
import { fileURLToPath } from 'url';
4-
import { dirname, join } from 'path';
52
import { VERSION as DOCKER_VERSION } from '@walkeros/docker';
3+
import { VERSION } from './version.js';
64
import { bundleCommand } from './commands/bundle/index.js';
75
import { simulateCommand } from './commands/simulate/index.js';
86
import { pushCommand } from './commands/push/index.js';
97
import { runCommand } from './commands/run/index.js';
108
import { registerCacheCommand } from './commands/cache.js';
119

12-
// Get package version dynamically
13-
const __filename = fileURLToPath(import.meta.url);
14-
const __dirname = dirname(__filename);
15-
const packageJson = JSON.parse(
16-
readFileSync(join(__dirname, '../package.json'), 'utf-8'),
17-
);
18-
const VERSION = packageJson.version;
19-
2010
// === CLI Commands ===
2111
// Export CLI command handlers
2212
export { bundleCommand, simulateCommand, pushCommand, runCommand };

packages/cli/src/version.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { readFileSync } from 'fs';
2+
import { fileURLToPath } from 'url';
3+
import { dirname, join } from 'path';
4+
5+
const versionFilename = fileURLToPath(import.meta.url);
6+
const versionDirname = dirname(versionFilename);
7+
8+
/**
9+
* Find package.json in parent directories
10+
* Handles both source (src/) and bundled (dist/) contexts
11+
*/
12+
function findPackageJson(): string {
13+
const paths = [
14+
join(versionDirname, '../package.json'), // dist/ or src/
15+
join(versionDirname, '../../package.json'), // src/core/ (not used, but safe)
16+
];
17+
for (const p of paths) {
18+
try {
19+
return readFileSync(p, 'utf-8');
20+
} catch {
21+
// Continue to next path
22+
}
23+
}
24+
return JSON.stringify({ version: '0.0.0' });
25+
}
26+
27+
/** CLI package version */
28+
export const VERSION: string = JSON.parse(findPackageJson()).version;

packages/docker/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@walkeros/docker",
33
"description": "Pure runtime Docker container for walkerOS - executes pre-built flows",
4-
"version": "0.3.1-next.0",
4+
"version": "0.6.0",
55
"type": "module",
66
"main": "./dist/index.mjs",
77
"types": "./dist/index.d.ts",

packages/docker/src/version.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
// Version injected at build time via tsup define (from buildModules)
2-
declare const __VERSION__: string;
1+
import { readFileSync } from 'fs';
2+
import { fileURLToPath } from 'url';
3+
import { dirname, join } from 'path';
34

4-
/**
5-
* Package version - exported for use by @walkeros/cli and internal services
6-
*/
7-
export const VERSION =
8-
typeof __VERSION__ !== 'undefined' ? __VERSION__ : '0.0.0';
5+
const versionFilename = fileURLToPath(import.meta.url);
6+
const versionDirname = dirname(versionFilename);
7+
const versionPackageJson = JSON.parse(
8+
readFileSync(join(versionDirname, '../package.json'), 'utf-8'),
9+
);
10+
11+
/** Package version - exported for use by @walkeros/cli and internal services */
12+
export const VERSION: string = versionPackageJson.version;

0 commit comments

Comments
 (0)