@ryanfw/prompt-orchestration-pipeline 0.3.0 → 0.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ryanfw/prompt-orchestration-pipeline",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "A Prompt-orchestration pipeline (POP) is a framework for building, running, and experimenting with complex chains of LLM tasks.",
5
5
  "type": "module",
6
6
  "main": "src/ui/server.js",
@@ -26,6 +26,7 @@
26
26
  "ui": "NODE_ENV=development nodemon src/ui/server.js",
27
27
  "ui:dev": "vite",
28
28
  "ui:build": "vite build",
29
+ "prepack": "npm run ui:build",
29
30
  "ui:prod": "node src/ui/server.js",
30
31
  "demo:ui": "NODE_ENV=production PO_ROOT=demo node src/ui/server.js",
31
32
  "demo:orchestrator": "PO_ROOT=demo NODE_ENV=production node -e \"import('./src/core/orchestrator.js').then(m => m.startOrchestrator({ dataDir: process.env.PO_ROOT || 'demo' })).catch(err => { console.error(err); process.exit(1) })\"",
package/src/cli/index.js CHANGED
@@ -4,9 +4,14 @@ import { submitJobWithValidation } from "../api/index.js";
4
4
  import { PipelineOrchestrator } from "../api/index.js";
5
5
  import fs from "node:fs/promises";
6
6
  import path from "node:path";
7
+ import { fileURLToPath } from "node:url";
7
8
  import { spawn } from "node:child_process";
8
9
  import { updatePipelineJson } from "./update-pipeline-json.js";
9
10
 
11
+ // Derive package root for resolving internal paths regardless of host CWD
12
+ const currentFile = fileURLToPath(import.meta.url);
13
+ const PKG_ROOT = path.dirname(path.dirname(path.dirname(currentFile)));
14
+
10
15
  // Canonical stage names that must match src/core/task-runner.js
11
16
  const STAGE_NAMES = [
12
17
  "ingestion",
@@ -140,46 +145,29 @@ program
140
145
  });
141
146
 
142
147
  try {
143
- // Step d: Build UI once if dist/ doesn't exist
144
- const distPath = path.join(process.cwd(), "dist");
148
+ // Step d: Check for prebuilt UI assets
149
+ const distPath = path.join(PKG_ROOT, "src/ui/dist");
145
150
  try {
146
151
  await fs.access(distPath);
147
152
  console.log("UI build found, skipping build step");
148
153
  } catch {
149
- console.log("Building UI...");
150
- await new Promise((resolve, reject) => {
151
- const vitePath = path.resolve(
152
- process.cwd(),
153
- "node_modules/vite/bin/vite.js"
154
- );
155
- const buildChild = spawn("node", [vitePath, "build"], {
156
- stdio: "inherit",
157
- env: { ...process.env, NODE_ENV: "development" },
158
- });
159
-
160
- buildChild.on("exit", (code) => {
161
- if (code === 0) {
162
- console.log("UI build completed");
163
- resolve();
164
- } else {
165
- reject(new Error(`UI build failed with code ${code}`));
166
- }
167
- });
168
-
169
- buildChild.on("error", reject);
170
- });
154
+ console.error(
155
+ "UI assets missing. This indicates a source checkout. Run 'npm run ui:build' locally or install dev deps."
156
+ );
157
+ process.exit(1);
171
158
  }
172
159
 
173
160
  // Step e: Spawn UI server
174
161
  console.log("Starting UI server...");
175
- const uiServerPath = path.resolve(process.cwd(), "src/ui/server.js");
162
+ const uiServerPath = path.join(PKG_ROOT, "src/ui/server.js");
176
163
  uiChild = spawn("node", [uiServerPath], {
177
164
  stdio: "pipe",
178
165
  env: {
179
166
  ...process.env,
180
167
  NODE_ENV: "production",
181
168
  PO_ROOT: absoluteRoot,
182
- PO_UI_PORT: port,
169
+ PORT: port,
170
+ PO_UI_PORT: undefined, // Ensure PORT takes precedence
183
171
  },
184
172
  });
185
173
 
@@ -194,8 +182,8 @@ program
194
182
 
195
183
  // Step f: Spawn orchestrator
196
184
  console.log("Starting orchestrator...");
197
- const orchestratorPath = path.resolve(
198
- process.cwd(),
185
+ const orchestratorPath = path.join(
186
+ PKG_ROOT,
199
187
  "src/cli/run-orchestrator.js"
200
188
  );
201
189
  orchestratorChild = spawn("node", [orchestratorPath], {
@@ -220,97 +220,111 @@ export async function startOrchestrator(opts) {
220
220
  * @param {Object} seed - Seed data containing pipeline information
221
221
  */
222
222
  function spawnRunner(jobId, dirs, running, spawn, testMode, seed) {
223
- const runnerPath = path.join(
224
- process.cwd(),
225
- "src",
226
- "core",
227
- "pipeline-runner.js"
228
- );
229
-
230
- const configSnapshot = getConfig();
231
- const availablePipelines = Object.keys(configSnapshot?.pipelines ?? {});
232
- const pipelineSlug = seed?.pipeline;
233
-
234
- console.log("[Orchestrator] spawnRunner invoked", {
235
- jobId,
236
- pipelineSlug: pipelineSlug ?? null,
237
- availablePipelines,
238
- seedKeys: seed ? Object.keys(seed) : null,
239
- });
223
+ // Use path relative to this file to avoid process.cwd() issues
224
+ const orchestratorDir = path.dirname(new URL(import.meta.url).pathname);
225
+ const runnerPath = path.join(orchestratorDir, "pipeline-runner.js");
240
226
 
241
- if (!availablePipelines.length) {
242
- console.warn(
243
- "[Orchestrator] No pipelines registered in config() when spawnRunner invoked"
244
- );
245
- } else if (!availablePipelines.includes(pipelineSlug)) {
246
- console.warn(
247
- "[Orchestrator] Requested pipeline slug missing from registry snapshot",
248
- {
249
- jobId,
250
- pipelineSlug,
251
- availablePipelines,
252
- }
253
- );
254
- }
227
+ // Set PO_ROOT for the orchestrator process to match what the runner will use
228
+ const originalPoRoot = process.env.PO_ROOT;
229
+ const poRoot = path.resolve(dirs.dataDir, "..");
230
+ process.env.PO_ROOT = poRoot;
255
231
 
256
- if (!pipelineSlug) {
257
- console.error("[Orchestrator] Missing pipeline slug in seed", {
232
+ try {
233
+ const configSnapshot = getConfig();
234
+ const availablePipelines = Object.keys(configSnapshot?.pipelines ?? {});
235
+ const pipelineSlug = seed?.pipeline;
236
+
237
+ console.log("[Orchestrator] spawnRunner invoked", {
258
238
  jobId,
259
- seed,
239
+ pipelineSlug: pipelineSlug ?? null,
260
240
  availablePipelines,
241
+ seedKeys: seed ? Object.keys(seed) : null,
261
242
  });
262
- throw new Error(
263
- "Pipeline slug is required in seed data. Include a 'pipeline' field in your seed."
264
- );
265
- }
266
243
 
267
- let pipelineConfig;
268
- try {
269
- pipelineConfig = getPipelineConfig(pipelineSlug);
270
- } catch (error) {
271
- console.error("[Orchestrator] Pipeline lookup failed", {
272
- jobId,
273
- pipelineSlug,
274
- availablePipelines,
244
+ if (!availablePipelines.length) {
245
+ console.warn(
246
+ "[Orchestrator] No pipelines registered in config() when spawnRunner invoked"
247
+ );
248
+ } else if (!availablePipelines.includes(pipelineSlug)) {
249
+ console.warn(
250
+ "[Orchestrator] Requested pipeline slug missing from registry snapshot",
251
+ {
252
+ jobId,
253
+ pipelineSlug,
254
+ availablePipelines,
255
+ }
256
+ );
257
+ }
258
+
259
+ if (!pipelineSlug) {
260
+ console.error("[Orchestrator] Missing pipeline slug in seed", {
261
+ jobId,
262
+ seed,
263
+ availablePipelines,
264
+ });
265
+ throw new Error(
266
+ "Pipeline slug is required in seed data. Include a 'pipeline' field in your seed."
267
+ );
268
+ }
269
+
270
+ let pipelineConfig;
271
+ try {
272
+ pipelineConfig = getPipelineConfig(pipelineSlug);
273
+ } catch (error) {
274
+ console.error("[Orchestrator] Pipeline lookup failed", {
275
+ jobId,
276
+ pipelineSlug,
277
+ availablePipelines,
278
+ });
279
+ throw error;
280
+ }
281
+
282
+ // Use environment variables with explicit slug propagation
283
+ // PO_ROOT should point to the directory containing pipeline-config
284
+ // In our case, it's the parent of pipeline-data directory
285
+ const env = {
286
+ ...process.env,
287
+ PO_ROOT: poRoot,
288
+ PO_DATA_DIR: dirs.dataDir,
289
+ PO_PENDING_DIR: dirs.pending,
290
+ PO_CURRENT_DIR: dirs.current,
291
+ PO_COMPLETE_DIR: dirs.complete,
292
+ PO_PIPELINE_SLUG: pipelineSlug,
293
+ // Force mock provider for testing
294
+ PO_DEFAULT_PROVIDER: "mock",
295
+ };
296
+
297
+ // Always call spawn so tests can capture it
298
+ const child = spawn(process.execPath, [runnerPath, jobId], {
299
+ stdio: ["ignore", "inherit", "inherit"],
300
+ env,
301
+ cwd: process.cwd(),
275
302
  });
276
- throw error;
277
- }
278
303
 
279
- // Use environment variables with explicit slug propagation
280
- const env = {
281
- ...process.env,
282
- PO_DATA_DIR: dirs.dataDir,
283
- PO_PENDING_DIR: dirs.pending,
284
- PO_CURRENT_DIR: dirs.current,
285
- PO_COMPLETE_DIR: dirs.complete,
286
- PO_PIPELINE_SLUG: pipelineSlug,
287
- // Force mock provider for testing
288
- PO_DEFAULT_PROVIDER: "mock",
289
- };
290
-
291
- // Always call spawn so tests can capture it
292
- const child = spawn(process.execPath, [runnerPath, jobId], {
293
- stdio: ["ignore", "inherit", "inherit"],
294
- env,
295
- cwd: process.cwd(),
296
- });
304
+ running.set(jobId, child);
297
305
 
298
- running.set(jobId, child);
306
+ child.on("exit", () => {
307
+ running.delete(jobId);
308
+ });
309
+ child.on("error", () => {
310
+ running.delete(jobId);
311
+ });
299
312
 
300
- child.on("exit", () => {
301
- running.delete(jobId);
302
- });
303
- child.on("error", () => {
304
- running.delete(jobId);
305
- });
313
+ // In test mode: return immediately; in real mode you might await readiness
314
+ if (testMode) {
315
+ return child;
316
+ }
306
317
 
307
- // In test mode: return immediately; in real mode you might await readiness
308
- if (testMode) {
318
+ // Non-test: we can consider "started" immediately for simplicity
309
319
  return child;
320
+ } finally {
321
+ // Restore original PO_ROOT
322
+ if (originalPoRoot) {
323
+ process.env.PO_ROOT = originalPoRoot;
324
+ } else {
325
+ delete process.env.PO_ROOT;
326
+ }
310
327
  }
311
-
312
- // Non-test: we can consider "started" immediately for simplicity
313
- return child;
314
328
  }
315
329
 
316
330
  export default { startOrchestrator };