@ryanfw/prompt-orchestration-pipeline 0.0.1 → 0.3.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/README.md +415 -24
- package/package.json +45 -8
- package/src/api/files.js +48 -0
- package/src/api/index.js +149 -53
- package/src/api/validators/seed.js +141 -0
- package/src/cli/index.js +456 -29
- package/src/cli/run-orchestrator.js +39 -0
- package/src/cli/update-pipeline-json.js +47 -0
- package/src/components/DAGGrid.jsx +649 -0
- package/src/components/JobCard.jsx +96 -0
- package/src/components/JobDetail.jsx +159 -0
- package/src/components/JobTable.jsx +202 -0
- package/src/components/Layout.jsx +134 -0
- package/src/components/TaskFilePane.jsx +570 -0
- package/src/components/UploadSeed.jsx +239 -0
- package/src/components/ui/badge.jsx +20 -0
- package/src/components/ui/button.jsx +43 -0
- package/src/components/ui/card.jsx +20 -0
- package/src/components/ui/focus-styles.css +60 -0
- package/src/components/ui/progress.jsx +26 -0
- package/src/components/ui/select.jsx +27 -0
- package/src/components/ui/separator.jsx +6 -0
- package/src/config/paths.js +99 -0
- package/src/core/config.js +270 -9
- package/src/core/file-io.js +202 -0
- package/src/core/module-loader.js +157 -0
- package/src/core/orchestrator.js +275 -294
- package/src/core/pipeline-runner.js +95 -41
- package/src/core/progress.js +66 -0
- package/src/core/status-writer.js +331 -0
- package/src/core/task-runner.js +719 -73
- package/src/core/validation.js +120 -1
- package/src/lib/utils.js +6 -0
- package/src/llm/README.md +139 -30
- package/src/llm/index.js +222 -72
- package/src/pages/PipelineDetail.jsx +111 -0
- package/src/pages/PromptPipelineDashboard.jsx +223 -0
- package/src/providers/deepseek.js +3 -15
- package/src/ui/client/adapters/job-adapter.js +258 -0
- package/src/ui/client/bootstrap.js +120 -0
- package/src/ui/client/hooks/useJobDetailWithUpdates.js +619 -0
- package/src/ui/client/hooks/useJobList.js +50 -0
- package/src/ui/client/hooks/useJobListWithUpdates.js +335 -0
- package/src/ui/client/hooks/useTicker.js +26 -0
- package/src/ui/client/index.css +31 -0
- package/src/ui/client/index.html +18 -0
- package/src/ui/client/main.jsx +38 -0
- package/src/ui/config-bridge.browser.js +149 -0
- package/src/ui/config-bridge.js +149 -0
- package/src/ui/config-bridge.node.js +310 -0
- package/src/ui/dist/assets/index-BDABnI-4.js +33399 -0
- package/src/ui/dist/assets/style-Ks8LY8gB.css +28496 -0
- package/src/ui/dist/index.html +19 -0
- package/src/ui/endpoints/job-endpoints.js +300 -0
- package/src/ui/file-reader.js +216 -0
- package/src/ui/job-change-detector.js +83 -0
- package/src/ui/job-index.js +231 -0
- package/src/ui/job-reader.js +274 -0
- package/src/ui/job-scanner.js +188 -0
- package/src/ui/public/app.js +3 -1
- package/src/ui/server.js +1636 -59
- package/src/ui/sse-enhancer.js +149 -0
- package/src/ui/sse.js +204 -0
- package/src/ui/state-snapshot.js +252 -0
- package/src/ui/transformers/list-transformer.js +347 -0
- package/src/ui/transformers/status-transformer.js +307 -0
- package/src/ui/watcher.js +61 -7
- package/src/utils/dag.js +101 -0
- package/src/utils/duration.js +126 -0
- package/src/utils/id-generator.js +30 -0
- package/src/utils/jobs.js +7 -0
- package/src/utils/pipelines.js +44 -0
- package/src/utils/task-files.js +271 -0
- package/src/utils/ui.jsx +76 -0
- package/src/ui/public/index.html +0 -53
- package/src/ui/public/style.css +0 -341
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Universal configuration bridge for UI helpers.
|
|
3
|
+
* Works in both browser and Node environments.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Global constants and contracts for project data display system
|
|
8
|
+
* @namespace Constants
|
|
9
|
+
*/
|
|
10
|
+
export const Constants = {
|
|
11
|
+
/**
|
|
12
|
+
* Job ID validation regex
|
|
13
|
+
* @type {RegExp}
|
|
14
|
+
*/
|
|
15
|
+
JOB_ID_REGEX: /^[A-Za-z0-9-_]+$/,
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Valid task states
|
|
19
|
+
* @type {string[]}
|
|
20
|
+
*/
|
|
21
|
+
TASK_STATES: ["pending", "running", "done", "failed"],
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Valid job locations
|
|
25
|
+
* @type {string[]}
|
|
26
|
+
*/
|
|
27
|
+
JOB_LOCATIONS: ["current", "complete"],
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Status sort order (descending priority)
|
|
31
|
+
* @type {string[]}
|
|
32
|
+
*/
|
|
33
|
+
STATUS_ORDER: ["running", "failed", "pending", "complete"],
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* File size limits for reading
|
|
37
|
+
* @type {Object}
|
|
38
|
+
*/
|
|
39
|
+
FILE_LIMITS: {
|
|
40
|
+
MAX_FILE_SIZE: 5 * 1024 * 1024, // 5MB
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Retry configuration for atomic reads
|
|
45
|
+
* @type {Object}
|
|
46
|
+
*/
|
|
47
|
+
RETRY_CONFIG: {
|
|
48
|
+
MAX_ATTEMPTS: 3,
|
|
49
|
+
DELAY_MS: 1000,
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* SSE debounce configuration
|
|
54
|
+
* @type {Object}
|
|
55
|
+
*/
|
|
56
|
+
SSE_CONFIG: {
|
|
57
|
+
DEBOUNCE_MS: 200,
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Error codes for structured error responses
|
|
62
|
+
* @type {Object}
|
|
63
|
+
*/
|
|
64
|
+
ERROR_CODES: {
|
|
65
|
+
NOT_FOUND: "not_found",
|
|
66
|
+
INVALID_JSON: "invalid_json",
|
|
67
|
+
FS_ERROR: "fs_error",
|
|
68
|
+
JOB_NOT_FOUND: "job_not_found",
|
|
69
|
+
BAD_REQUEST: "bad_request",
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Validates a job ID against the global contract
|
|
75
|
+
* @param {string} jobId - Job ID to validate
|
|
76
|
+
* @returns {boolean} True if valid
|
|
77
|
+
*/
|
|
78
|
+
export function validateJobId(jobId) {
|
|
79
|
+
return Constants.JOB_ID_REGEX.test(jobId);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Validates a task state against the global contract
|
|
84
|
+
* @param {string} state - Task state to validate
|
|
85
|
+
* @returns {boolean} True if valid
|
|
86
|
+
*/
|
|
87
|
+
export function validateTaskState(state) {
|
|
88
|
+
return Constants.TASK_STATES.includes(state);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Gets the status sort priority for a job status
|
|
93
|
+
* @param {string} status - Job status
|
|
94
|
+
* @returns {number} Sort priority (lower number = higher priority)
|
|
95
|
+
*/
|
|
96
|
+
export function getStatusPriority(status) {
|
|
97
|
+
const index = Constants.STATUS_ORDER.indexOf(status);
|
|
98
|
+
return index === -1 ? Constants.STATUS_ORDER.length : index;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Determines job status based on task states
|
|
103
|
+
* @param {Object} tasks - Tasks object from tasks-status.json
|
|
104
|
+
* @returns {string} Job status
|
|
105
|
+
*/
|
|
106
|
+
export function determineJobStatus(tasks = {}) {
|
|
107
|
+
const taskEntries = Object.entries(tasks);
|
|
108
|
+
|
|
109
|
+
if (taskEntries.length === 0) {
|
|
110
|
+
return "pending";
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const taskStates = taskEntries.map(([_, task]) => task.state);
|
|
114
|
+
|
|
115
|
+
if (taskStates.includes("failed")) {
|
|
116
|
+
return "failed";
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (taskStates.includes("running")) {
|
|
120
|
+
return "running";
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (taskStates.every((state) => state === "done")) {
|
|
124
|
+
return "complete";
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return "pending";
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Creates a structured error response
|
|
132
|
+
* @param {string} code - Error code
|
|
133
|
+
* @param {string} message - Error message
|
|
134
|
+
* @param {string} [path] - Optional file path
|
|
135
|
+
* @returns {Object} Structured error object
|
|
136
|
+
*/
|
|
137
|
+
export function createErrorResponse(code, message, path = null) {
|
|
138
|
+
const error = {
|
|
139
|
+
ok: false,
|
|
140
|
+
code,
|
|
141
|
+
message,
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
if (path) {
|
|
145
|
+
error.path = path;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return error;
|
|
149
|
+
}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node-specific configuration bridge for server-side UI helpers.
|
|
3
|
+
* This module contains filesystem and path utilities that rely on Node APIs.
|
|
4
|
+
* It should only be imported by server-side modules.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { promises as fs } from "node:fs";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Global constants and contracts for project data display system
|
|
13
|
+
* @namespace Constants
|
|
14
|
+
*/
|
|
15
|
+
export const Constants = {
|
|
16
|
+
/**
|
|
17
|
+
* Job ID validation regex
|
|
18
|
+
* @type {RegExp}
|
|
19
|
+
*/
|
|
20
|
+
JOB_ID_REGEX: /^[A-Za-z0-9-_]+$/,
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Valid task states
|
|
24
|
+
* @type {string[]}
|
|
25
|
+
*/
|
|
26
|
+
TASK_STATES: ["pending", "running", "done", "failed"],
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Valid job locations
|
|
30
|
+
* @type {string[]}
|
|
31
|
+
*/
|
|
32
|
+
JOB_LOCATIONS: ["current", "complete"],
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Status sort order (descending priority)
|
|
36
|
+
* @type {string[]}
|
|
37
|
+
*/
|
|
38
|
+
STATUS_ORDER: ["running", "failed", "pending", "complete"],
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* File size limits for reading
|
|
42
|
+
* @type {Object}
|
|
43
|
+
*/
|
|
44
|
+
FILE_LIMITS: {
|
|
45
|
+
MAX_FILE_SIZE: 5 * 1024 * 1024, // 5MB
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Retry configuration for atomic reads
|
|
50
|
+
* @type {Object}
|
|
51
|
+
*/
|
|
52
|
+
RETRY_CONFIG: {
|
|
53
|
+
MAX_ATTEMPTS: 3,
|
|
54
|
+
DELAY_MS: process.env.NODE_ENV === "test" ? 10 : 1000,
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* SSE debounce configuration
|
|
59
|
+
* @type {Object}
|
|
60
|
+
*/
|
|
61
|
+
SSE_CONFIG: {
|
|
62
|
+
DEBOUNCE_MS: 200,
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Error codes for structured error responses
|
|
67
|
+
* @type {Object}
|
|
68
|
+
*/
|
|
69
|
+
ERROR_CODES: {
|
|
70
|
+
NOT_FOUND: "not_found",
|
|
71
|
+
INVALID_JSON: "invalid_json",
|
|
72
|
+
FS_ERROR: "fs_error",
|
|
73
|
+
JOB_NOT_FOUND: "job_not_found",
|
|
74
|
+
BAD_REQUEST: "bad_request",
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// Get current directory for path resolution
|
|
79
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
80
|
+
const __dirname = path.dirname(__filename);
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Resolves pipeline data directory roots relative to project root
|
|
84
|
+
* @returns {Object} Object containing resolved directory paths (current/complete/pending/rejected)
|
|
85
|
+
*/
|
|
86
|
+
export function resolvePipelinePaths(root = path.resolve(__dirname, "../..")) {
|
|
87
|
+
const projectRoot = root;
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
current: path.join(projectRoot, "pipeline-data", "current"),
|
|
91
|
+
complete: path.join(projectRoot, "pipeline-data", "complete"),
|
|
92
|
+
pending: path.join(projectRoot, "pipeline-data", "pending"),
|
|
93
|
+
rejected: path.join(projectRoot, "pipeline-data", "rejected"),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Gets absolute path to a job directory
|
|
99
|
+
* @param {string} jobId - Job ID
|
|
100
|
+
* @param {string} [location='current'] - Job location ('current' or 'complete')
|
|
101
|
+
* @returns {string} Absolute path to job directory
|
|
102
|
+
*/
|
|
103
|
+
export function getJobPath(jobId, location = "current") {
|
|
104
|
+
if (!Constants.JOB_LOCATIONS.includes(location)) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
`Invalid location: ${location}. Must be one of: ${Constants.JOB_LOCATIONS.join(", ")}`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!Constants.JOB_ID_REGEX.test(jobId)) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`Invalid job ID: ${jobId}. Must match ${Constants.JOB_ID_REGEX}`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const paths = resolvePipelinePaths();
|
|
117
|
+
return path.join(paths[location], jobId);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Gets path to tasks-status.json for a job
|
|
122
|
+
* @param {string} jobId - Job ID
|
|
123
|
+
* @param {string} [location='current'] - Job location
|
|
124
|
+
* @returns {string} Path to tasks-status.json
|
|
125
|
+
*/
|
|
126
|
+
export function getTasksStatusPath(jobId, location = "current") {
|
|
127
|
+
const jobPath = getJobPath(jobId, location);
|
|
128
|
+
return path.join(jobPath, "tasks-status.json");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Gets path to seed.json for a job
|
|
133
|
+
* @param {string} jobId - Job ID
|
|
134
|
+
* @param {string} [location='current'] - Job location
|
|
135
|
+
* @returns {string} Path to seed.json
|
|
136
|
+
*/
|
|
137
|
+
export function getSeedPath(jobId, location = "current") {
|
|
138
|
+
const jobPath = getJobPath(jobId, location);
|
|
139
|
+
return path.join(jobPath, "seed.json");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Gets path to a task directory
|
|
144
|
+
* @param {string} jobId - Job ID
|
|
145
|
+
* @param {string} taskName - Task name
|
|
146
|
+
* @param {string} [location='current'] - Job location
|
|
147
|
+
* @returns {string} Path to task directory
|
|
148
|
+
*/
|
|
149
|
+
export function getTaskPath(jobId, taskName, location = "current") {
|
|
150
|
+
const jobPath = getJobPath(jobId, location);
|
|
151
|
+
return path.join(jobPath, "tasks", taskName);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Checks if a job directory is locked for writing
|
|
156
|
+
* @param {string} jobDir - Job directory path
|
|
157
|
+
* @returns {Promise<boolean>} True if locked
|
|
158
|
+
*/
|
|
159
|
+
export async function isLocked(jobDir) {
|
|
160
|
+
try {
|
|
161
|
+
const entries = await fs.readdir(jobDir, { withFileTypes: true });
|
|
162
|
+
|
|
163
|
+
for (const entry of entries) {
|
|
164
|
+
if (entry.isFile() && entry.name.endsWith(".lock")) {
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (entry.isDirectory()) {
|
|
169
|
+
const subDirPath = path.join(jobDir, entry.name);
|
|
170
|
+
const subEntries = await fs.readdir(subDirPath, {
|
|
171
|
+
withFileTypes: true,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
for (const subEntry of subEntries) {
|
|
175
|
+
if (subEntry.isFile() && subEntry.name.endsWith(".lock")) {
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return false;
|
|
183
|
+
} catch (error) {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Creates a structured error response
|
|
190
|
+
* @param {string} code - Error code
|
|
191
|
+
* @param {string} message - Error message
|
|
192
|
+
* @param {string} [path] - Optional file path
|
|
193
|
+
* @returns {Object} Structured error object
|
|
194
|
+
*/
|
|
195
|
+
export function createErrorResponse(code, message, path = null) {
|
|
196
|
+
const error = {
|
|
197
|
+
ok: false,
|
|
198
|
+
code,
|
|
199
|
+
message,
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
if (path) {
|
|
203
|
+
error.path = path;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return error;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Validates a job ID against the global contract
|
|
211
|
+
* @param {string} jobId - Job ID to validate
|
|
212
|
+
* @returns {boolean} True if valid
|
|
213
|
+
*/
|
|
214
|
+
export function validateJobId(jobId) {
|
|
215
|
+
return Constants.JOB_ID_REGEX.test(jobId);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Validates a task state against the global contract
|
|
220
|
+
* @param {string} state - Task state to validate
|
|
221
|
+
* @returns {boolean} True if valid
|
|
222
|
+
*/
|
|
223
|
+
export function validateTaskState(state) {
|
|
224
|
+
return Constants.TASK_STATES.includes(state);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Gets the status sort priority for a job status
|
|
229
|
+
* @param {string} status - Job status
|
|
230
|
+
* @returns {number} Sort priority (lower number = higher priority)
|
|
231
|
+
*/
|
|
232
|
+
export function getStatusPriority(status) {
|
|
233
|
+
const index = Constants.STATUS_ORDER.indexOf(status);
|
|
234
|
+
return index === -1 ? Constants.STATUS_ORDER.length : index;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Determines job status based on task states
|
|
239
|
+
* @param {Object} tasks - Tasks object from tasks-status.json
|
|
240
|
+
* @returns {string} Job status
|
|
241
|
+
*/
|
|
242
|
+
export function determineJobStatus(tasks = {}) {
|
|
243
|
+
const taskEntries = Object.entries(tasks);
|
|
244
|
+
|
|
245
|
+
if (taskEntries.length === 0) {
|
|
246
|
+
return "pending";
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const taskStates = taskEntries.map(([_, task]) => task.state);
|
|
250
|
+
|
|
251
|
+
if (taskStates.includes("failed")) {
|
|
252
|
+
return "failed";
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (taskStates.includes("running")) {
|
|
256
|
+
return "running";
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (taskStates.every((state) => state === "done")) {
|
|
260
|
+
return "complete";
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return "pending";
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Export helper to resolve paths lazily for server use
|
|
267
|
+
let _PATHS = null;
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Initialize cached PATHS for a given project root.
|
|
271
|
+
* Callers should use this when they need PATHS tied to a specific root.
|
|
272
|
+
* Returns the resolved paths.
|
|
273
|
+
*/
|
|
274
|
+
export function initPATHS(root) {
|
|
275
|
+
// If root is falsy, resolvePipelinePaths will use the default project root
|
|
276
|
+
_PATHS = resolvePipelinePaths(root || path.resolve(__dirname, "../.."));
|
|
277
|
+
return _PATHS;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Reset cached PATHS so future calls will re-resolve.
|
|
282
|
+
* Useful in tests or server code that needs to change the project root at runtime.
|
|
283
|
+
*/
|
|
284
|
+
export function resetPATHS() {
|
|
285
|
+
_PATHS = null;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Get the cached PATHS. If a root argument is provided, re-initialize the cache
|
|
290
|
+
* for backward-compatible callers that pass a root to getPATHS(root).
|
|
291
|
+
*
|
|
292
|
+
* If not initialized, initialize with the PO_ROOT environment variable if available,
|
|
293
|
+
* otherwise use the default project root (two levels up from this file).
|
|
294
|
+
*/
|
|
295
|
+
export function getPATHS(root) {
|
|
296
|
+
if (root) {
|
|
297
|
+
_PATHS = resolvePipelinePaths(root);
|
|
298
|
+
return _PATHS;
|
|
299
|
+
}
|
|
300
|
+
if (!_PATHS) {
|
|
301
|
+
// Use PO_ROOT environment variable if available, otherwise use default project root
|
|
302
|
+
const effectiveRoot =
|
|
303
|
+
process.env.PO_ROOT || path.resolve(__dirname, "../..");
|
|
304
|
+
_PATHS = resolvePipelinePaths(effectiveRoot);
|
|
305
|
+
}
|
|
306
|
+
return _PATHS;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Convenience export for existing callsites
|
|
310
|
+
export const PATHS = getPATHS();
|