Skip to content

Commit f617541

Browse files
dynamic file input
1 parent f052d76 commit f617541

File tree

10 files changed

+493
-165
lines changed

10 files changed

+493
-165
lines changed

packages/cli/README.md

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ walkeros bundle flow.json
3333
# Test with simulated events (no real API calls)
3434
walkeros simulate flow.json --event '{"name":"product view"}'
3535

36+
# Or test a pre-built bundle directly
37+
walkeros simulate dist/bundle.mjs --event '{"name":"product view"}'
38+
3639
# Push real events to destinations
3740
walkeros push flow.json --event '{"name":"product view"}'
3841

@@ -78,42 +81,74 @@ The output path uses convention-based defaults: `./dist/bundle.mjs` for server,
7881

7982
### simulate
8083

81-
Test event processing with simulated events.
84+
Test event processing with simulated events. Accepts either a config JSON (which
85+
gets bundled) or a pre-built bundle (executed directly).
8286

8387
```bash
84-
walkeros simulate <config-file> --event '{"name":"page view"}' [options]
88+
walkeros simulate <input> --event '{"name":"page view"}' [options]
8589
```
8690

91+
**Input types:**
92+
93+
- **Config JSON** - Bundled and executed with destination mocking
94+
- **Pre-built bundle** (`.js`/`.mjs`) - Executed directly, no mocking
95+
96+
The CLI auto-detects the input type by attempting to parse as JSON.
97+
8798
**Options:**
8899

89100
- `-e, --event <json>` - Event JSON string (required)
101+
- `-p, --platform <platform>` - Platform override (`web` or `server`)
90102
- `--json` - Output results as JSON
91103
- `-v, --verbose` - Verbose output
92104

93-
**Example:**
105+
**Examples:**
94106

95107
```bash
96-
# Simulate page view
97-
walkeros simulate \
98-
examples/web-serve.json \
108+
# Simulate with config (auto-bundled)
109+
walkeros simulate examples/web-serve.json \
99110
--event '{"name":"page view","data":{"title":"Home"}}' \
100111
--json
112+
113+
# Simulate with pre-built bundle
114+
walkeros simulate dist/bundle.mjs --event '{"name":"page view"}'
115+
116+
# Override platform detection
117+
walkeros simulate dist/bundle.js --platform server --event '{"name":"page view"}'
101118
```
102119

120+
**Platform detection:**
121+
122+
When using pre-built bundles, platform is detected from file extension:
123+
124+
- `.mjs` → server (ESM, Node.js)
125+
- `.js` → web (IIFE, JSDOM)
126+
127+
Use `--platform` to override if extension doesn't match intended runtime.
128+
103129
### push
104130

105131
Execute your flow with real API calls to configured destinations. Unlike
106-
`simulate` which mocks API calls, `push` performs actual HTTP requests.
132+
`simulate` which mocks API calls, `push` performs actual HTTP requests. Accepts
133+
either a config JSON (which gets bundled) or a pre-built bundle.
107134

108135
```bash
109-
walkeros push <config-file> --event '<json>' [options]
136+
walkeros push <input> --event '<json>' [options]
110137
```
111138

139+
**Input types:**
140+
141+
- **Config JSON** - Bundled and executed
142+
- **Pre-built bundle** (`.js`/`.mjs`) - Executed directly
143+
144+
The CLI auto-detects the input type by attempting to parse as JSON.
145+
112146
**Options:**
113147

114148
- `-e, --event <source>` - Event to push (JSON string, file path, or URL)
115149
**Required**
116150
- `--flow <name>` - Flow name (for multi-flow configs)
151+
- `-p, --platform <platform>` - Platform override (`web` or `server`)
117152
- `--json` - Output results as JSON
118153
- `-v, --verbose` - Verbose output
119154
- `-s, --silent` - Suppress output (for CI/CD)
@@ -131,6 +166,16 @@ walkeros push flow.json --event ./events/order.json
131166
walkeros push flow.json --event https://example.com/sample-event.json
132167
```
133168

169+
**Bundle input:**
170+
171+
```bash
172+
# Push with pre-built bundle
173+
walkeros push dist/bundle.mjs --event '{"name":"order complete"}'
174+
175+
# Override platform detection
176+
walkeros push dist/bundle.js --platform server --event '{"name":"order complete"}'
177+
```
178+
134179
**Push vs Simulate:**
135180

136181
| Feature | `push` | `simulate` |

packages/cli/src/__tests__/cli-e2e.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ describe('CLI E2E Tests', () => {
8989
const result = await runCLI(['simulate', '/invalid/path.json']);
9090

9191
expect(result.code).not.toBe(0);
92-
expect(result.stderr || result.stdout).toContain('not found');
92+
// Node.js file errors use "no such file or directory" or "ENOENT"
93+
expect(result.stderr || result.stdout).toMatch(/no such file|ENOENT/i);
9394
}, 10000);
9495
});
9596

packages/cli/src/commands/push/index.ts

Lines changed: 130 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import { schemas } from '@walkeros/core/dev';
66
import {
77
createCommandLogger,
88
getErrorMessage,
9+
detectInput,
910
type Logger,
11+
type Platform,
1012
} from '../../core/index.js';
1113
import { loadFlowConfig, loadJsonFromSource } from '../../config/index.js';
1214
import { bundleCore } from '../bundle/bundler.js';
@@ -18,6 +20,7 @@ import type { PushCommandOptions, PushResult } from './types.js';
1820
export async function pushCommand(options: PushCommandOptions): Promise<void> {
1921
const logger = createCommandLogger(options);
2022
const startTime = Date.now();
23+
let tempDir: string | undefined;
2124

2225
try {
2326
// Step 1: Load event
@@ -56,60 +59,36 @@ export async function pushCommand(options: PushCommandOptions): Promise<void> {
5659
);
5760
}
5861

59-
// Step 2: Load config
60-
logger.debug('Loading flow configuration');
61-
const { flowConfig, buildOptions } = await loadFlowConfig(options.config, {
62-
flowName: options.flow,
63-
logger,
64-
});
65-
66-
const platform = getPlatform(flowConfig);
67-
68-
// Step 3: Bundle to temp file in config directory (so Node.js can find node_modules)
69-
logger.debug('Bundling flow configuration');
70-
// buildOptions.configDir already handles URLs (uses cwd) vs local paths
71-
const configDir = buildOptions.configDir || process.cwd();
72-
const tempDir = path.join(
73-
configDir,
74-
'.tmp',
75-
`push-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
76-
);
77-
await fs.ensureDir(tempDir);
78-
const tempPath = path.join(
79-
tempDir,
80-
`bundle.${platform === 'web' ? 'js' : 'mjs'}`,
81-
);
82-
83-
const pushBuildOptions = {
84-
...buildOptions,
85-
output: tempPath,
86-
// Web uses IIFE for browser-like execution, server uses ESM
87-
format: platform === 'web' ? ('iife' as const) : ('esm' as const),
88-
platform: platform === 'web' ? ('browser' as const) : ('node' as const),
89-
...(platform === 'web' && {
90-
windowCollector: 'collector',
91-
windowElb: 'elb',
92-
}),
93-
};
94-
95-
await bundleCore(flowConfig, pushBuildOptions, logger, false);
96-
97-
logger.debug(`Bundle created: ${tempPath}`);
62+
// Step 2: Detect input type (config or bundle)
63+
logger.debug('Detecting input type');
64+
const detected = await detectInput(options.config, options.platform);
9865

99-
// Step 4: Execute based on platform
10066
let result: PushResult;
10167

102-
if (platform === 'web') {
103-
logger.debug('Executing in web environment (JSDOM)');
104-
result = await executeWebPush(tempPath, validatedEvent, logger);
105-
} else if (platform === 'server') {
106-
logger.debug('Executing in server environment (Node.js)');
107-
result = await executeServerPush(tempPath, validatedEvent, logger);
68+
if (detected.type === 'config') {
69+
// Config flow: load config, bundle, execute
70+
result = await executeConfigPush(
71+
options,
72+
validatedEvent,
73+
logger,
74+
(dir) => {
75+
tempDir = dir;
76+
},
77+
);
10878
} else {
109-
throw new Error(`Unsupported platform: ${platform}`);
79+
// Bundle flow: execute directly
80+
result = await executeBundlePush(
81+
detected.content,
82+
detected.platform!,
83+
validatedEvent,
84+
logger,
85+
(dir) => {
86+
tempDir = dir;
87+
},
88+
);
11089
}
11190

112-
// Step 5: Output results
91+
// Step 3: Output results
11392
const duration = Date.now() - startTime;
11493

11594
if (options.json) {
@@ -143,13 +122,6 @@ export async function pushCommand(options: PushCommandOptions): Promise<void> {
143122
process.exit(1);
144123
}
145124
}
146-
147-
// Cleanup temp directory
148-
try {
149-
await fs.remove(tempDir);
150-
} catch {
151-
// Ignore cleanup errors
152-
}
153125
} catch (error) {
154126
const duration = Date.now() - startTime;
155127
const errorMessage = getErrorMessage(error);
@@ -165,6 +137,109 @@ export async function pushCommand(options: PushCommandOptions): Promise<void> {
165137
}
166138

167139
process.exit(1);
140+
} finally {
141+
// Cleanup temp directory
142+
if (tempDir) {
143+
await fs.remove(tempDir).catch(() => {
144+
// Ignore cleanup errors
145+
});
146+
}
147+
}
148+
}
149+
150+
/**
151+
* Execute push from config JSON (existing behavior)
152+
*/
153+
async function executeConfigPush(
154+
options: PushCommandOptions,
155+
validatedEvent: { name: string; data: Record<string, unknown> },
156+
logger: Logger,
157+
setTempDir: (dir: string) => void,
158+
): Promise<PushResult> {
159+
// Load config
160+
logger.debug('Loading flow configuration');
161+
const { flowConfig, buildOptions } = await loadFlowConfig(options.config, {
162+
flowName: options.flow,
163+
logger,
164+
});
165+
166+
const platform = getPlatform(flowConfig);
167+
168+
// Bundle to temp file in config directory (so Node.js can find node_modules)
169+
logger.debug('Bundling flow configuration');
170+
const configDir = buildOptions.configDir || process.cwd();
171+
const tempDir = path.join(
172+
configDir,
173+
'.tmp',
174+
`push-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
175+
);
176+
setTempDir(tempDir);
177+
await fs.ensureDir(tempDir);
178+
const tempPath = path.join(
179+
tempDir,
180+
`bundle.${platform === 'web' ? 'js' : 'mjs'}`,
181+
);
182+
183+
const pushBuildOptions = {
184+
...buildOptions,
185+
output: tempPath,
186+
format: platform === 'web' ? ('iife' as const) : ('esm' as const),
187+
platform: platform === 'web' ? ('browser' as const) : ('node' as const),
188+
...(platform === 'web' && {
189+
windowCollector: 'collector',
190+
windowElb: 'elb',
191+
}),
192+
};
193+
194+
await bundleCore(flowConfig, pushBuildOptions, logger, false);
195+
196+
logger.debug(`Bundle created: ${tempPath}`);
197+
198+
// Execute based on platform
199+
if (platform === 'web') {
200+
logger.debug('Executing in web environment (JSDOM)');
201+
return executeWebPush(tempPath, validatedEvent, logger);
202+
} else if (platform === 'server') {
203+
logger.debug('Executing in server environment (Node.js)');
204+
return executeServerPush(tempPath, validatedEvent, logger);
205+
} else {
206+
throw new Error(`Unsupported platform: ${platform}`);
207+
}
208+
}
209+
210+
/**
211+
* Execute push from pre-built bundle
212+
*/
213+
async function executeBundlePush(
214+
bundleContent: string,
215+
platform: Platform,
216+
validatedEvent: { name: string; data: Record<string, unknown> },
217+
logger: Logger,
218+
setTempDir: (dir: string) => void,
219+
): Promise<PushResult> {
220+
// Write bundle to temp file
221+
const tempDir = path.join(
222+
process.cwd(),
223+
'.tmp',
224+
`push-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
225+
);
226+
setTempDir(tempDir);
227+
await fs.ensureDir(tempDir);
228+
const tempPath = path.join(
229+
tempDir,
230+
`bundle.${platform === 'server' ? 'mjs' : 'js'}`,
231+
);
232+
await fs.writeFile(tempPath, bundleContent, 'utf8');
233+
234+
logger.debug(`Bundle written to: ${tempPath}`);
235+
236+
// Execute based on platform
237+
if (platform === 'web') {
238+
logger.debug('Executing in web environment (JSDOM)');
239+
return executeWebPush(tempPath, validatedEvent, logger);
240+
} else {
241+
logger.debug('Executing in server environment (Node.js)');
242+
return executeServerPush(tempPath, validatedEvent, logger);
168243
}
169244
}
170245

packages/cli/src/commands/push/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export interface PushCommandOptions {
1010
json?: boolean;
1111
verbose?: boolean;
1212
silent?: boolean;
13+
platform?: 'web' | 'server';
1314
}
1415

1516
/**

0 commit comments

Comments
 (0)