@pipelab/core-node 1.0.0-beta.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.
Files changed (48) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/LICENSE +110 -0
  3. package/README.md +10 -0
  4. package/dist/index.d.mts +416 -0
  5. package/dist/index.d.mts.map +1 -0
  6. package/dist/index.mjs +52612 -0
  7. package/dist/index.mjs.map +1 -0
  8. package/package.json +64 -0
  9. package/src/api.ts +115 -0
  10. package/src/config.ts +105 -0
  11. package/src/context.ts +68 -0
  12. package/src/handler-func.ts +234 -0
  13. package/src/handlers/agents.ts +32 -0
  14. package/src/handlers/auth.ts +95 -0
  15. package/src/handlers/build-history.ts +381 -0
  16. package/src/handlers/config.ts +109 -0
  17. package/src/handlers/engine.ts +229 -0
  18. package/src/handlers/fs.ts +97 -0
  19. package/src/handlers/history.ts +329 -0
  20. package/src/handlers/index.ts +41 -0
  21. package/src/handlers/shell.ts +57 -0
  22. package/src/handlers/system.ts +18 -0
  23. package/src/handlers.ts +2 -0
  24. package/src/heavy.ts +4 -0
  25. package/src/index.ts +16 -0
  26. package/src/ipc-core.ts +77 -0
  27. package/src/migrations.ts +72 -0
  28. package/src/paths.ts +1 -0
  29. package/src/plugins-registry.ts +78 -0
  30. package/src/presets/c3toSteam.ts +272 -0
  31. package/src/presets/demo.ts +123 -0
  32. package/src/presets/if.ts +69 -0
  33. package/src/presets/list.ts +30 -0
  34. package/src/presets/loop.ts +65 -0
  35. package/src/presets/moreToCome.ts +32 -0
  36. package/src/presets/newProject.ts +31 -0
  37. package/src/presets/preset.model.ts +0 -0
  38. package/src/presets/test-c3-offline.ts +78 -0
  39. package/src/presets/test-c3-unzip.ts +124 -0
  40. package/src/runner.ts +160 -0
  41. package/src/server.ts +99 -0
  42. package/src/types/runner.ts +101 -0
  43. package/src/utils/fs-extras.ts +211 -0
  44. package/src/utils/remote.ts +497 -0
  45. package/src/utils/storage.ts +99 -0
  46. package/src/utils.ts +268 -0
  47. package/src/websocket-server.ts +288 -0
  48. package/tsconfig.json +19 -0
@@ -0,0 +1,229 @@
1
+ import { useAPI, HandleListenerSendFn } from "../ipc-core";
2
+ import { PipelabContext } from "../context";
3
+ import { useLogger } from "@pipelab/shared";
4
+ import { getFinalPlugins, executeGraphWithHistory } from "../utils";
5
+ import { presets } from "../presets/list";
6
+ import { handleActionExecute, handleConditionExecute } from "../handler-func";
7
+ import { generateTempFolder } from "../utils/fs-extras";
8
+ import { tmpdir } from "node:os";
9
+ import { setupConfigFile } from "../config";
10
+ import { AppConfig } from "@pipelab/shared";
11
+
12
+ export const registerEngineHandlers = (context: PipelabContext) => {
13
+ const { handle } = useAPI();
14
+ const { logger } = useLogger();
15
+
16
+ handle("nodes:get", async (_, { send }) => {
17
+ const finalPlugins = getFinalPlugins();
18
+ send({
19
+ type: "end",
20
+ data: {
21
+ type: "success",
22
+ result: {
23
+ nodes: finalPlugins,
24
+ },
25
+ },
26
+ });
27
+ });
28
+
29
+ handle("presets:get", async (_, { send }) => {
30
+ const presetData = await presets();
31
+ send({
32
+ type: "end",
33
+ data: {
34
+ type: "success",
35
+ result: presetData,
36
+ },
37
+ });
38
+ });
39
+
40
+ handle("condition:execute", async (_, { value }) => {
41
+ const { nodeId, params, pluginId } = value;
42
+ const cwd = await generateTempFolder(tmpdir());
43
+ await handleConditionExecute(nodeId, pluginId, params, cwd, context);
44
+ });
45
+
46
+ let abortControllerGraph: undefined | AbortController = undefined;
47
+
48
+ const effectiveActionExecute = async (
49
+ nodeId: string,
50
+ pluginId: string,
51
+ params: Record<string, string>,
52
+ mainWindow: any | undefined,
53
+ send: HandleListenerSendFn<"action:execute">,
54
+ cwd: string,
55
+ cachePath: string,
56
+ ) => {
57
+ try {
58
+ const result = await handleActionExecute(
59
+ nodeId,
60
+ pluginId,
61
+ params,
62
+ mainWindow,
63
+ send,
64
+ abortControllerGraph!.signal,
65
+ cwd,
66
+ cachePath,
67
+ context,
68
+ );
69
+
70
+ await send({
71
+ data: result,
72
+ type: "end",
73
+ });
74
+ } catch (e) {
75
+ console.error("Error during action execution:", e);
76
+ await send({
77
+ type: "end",
78
+ data: {
79
+ ipcError: e instanceof Error ? e.message : "Unknown error",
80
+ type: "error",
81
+ },
82
+ });
83
+ }
84
+ };
85
+
86
+ handle("action:execute", async (event, { send, value }) => {
87
+ const { nodeId, params, pluginId } = value;
88
+ const settings = await setupConfigFile<AppConfig>("settings", { context });
89
+ const config = await settings.getConfig();
90
+
91
+ const cachePath = config?.cacheFolder || tmpdir();
92
+ const cwd = await generateTempFolder(cachePath);
93
+
94
+ const mainWindow = undefined;
95
+ abortControllerGraph = new AbortController();
96
+
97
+ const signalPromise = new Promise((resolve, reject) => {
98
+ abortControllerGraph!.signal.addEventListener("abort", async () => {
99
+ await send({
100
+ type: "end",
101
+ data: {
102
+ ipcError: "Action aborted",
103
+ type: "error",
104
+ },
105
+ });
106
+ return reject(new Error("Action interrupted"));
107
+ });
108
+ });
109
+
110
+ await Promise.race([
111
+ signalPromise,
112
+ effectiveActionExecute(nodeId, pluginId, params, mainWindow, send, cwd, cachePath),
113
+ ]);
114
+ });
115
+
116
+ handle("constants:get", async (_, { send }) => {
117
+ const userData = context.userDataPath;
118
+ send({
119
+ type: "end",
120
+ data: {
121
+ type: "success",
122
+ result: {
123
+ result: {
124
+ userData,
125
+ },
126
+ },
127
+ },
128
+ });
129
+ });
130
+
131
+ handle("action:cancel", async (_, { send }) => {
132
+ if (abortControllerGraph) {
133
+ abortControllerGraph.abort("Interrupted by user");
134
+ }
135
+ send({
136
+ type: "end",
137
+ data: {
138
+ type: "success",
139
+ result: {
140
+ result: "ok",
141
+ },
142
+ },
143
+ });
144
+ });
145
+
146
+ handle("graph:execute", async (event, { send, value }) => {
147
+ const { graph, variables, projectName, projectPath, pipelineId } = value;
148
+ const settings = await setupConfigFile<AppConfig>("settings", { context });
149
+ const config = await settings.getConfig();
150
+
151
+ const effectiveProjectName = projectName || "Unnamed Project";
152
+ const effectiveProjectPath = projectPath || "";
153
+ const effectivePipelineId = pipelineId || "unknown";
154
+ const effectiveCachePath = config?.cacheFolder || tmpdir();
155
+
156
+ const mainWindow = undefined;
157
+ abortControllerGraph = new AbortController();
158
+
159
+ try {
160
+ const { result, buildId } = await executeGraphWithHistory({
161
+ graph,
162
+ variables,
163
+ projectName: effectiveProjectName,
164
+ projectPath: effectiveProjectPath,
165
+ pipelineId: effectivePipelineId,
166
+ mainWindow,
167
+ onNodeEnter: (node) => {
168
+ send({
169
+ type: "node-enter",
170
+ data: {
171
+ nodeUid: node.uid,
172
+ nodeName: node.name,
173
+ },
174
+ });
175
+ },
176
+ onNodeExit: (node) => {
177
+ send({
178
+ type: "node-exit",
179
+ data: {
180
+ nodeUid: node.uid,
181
+ nodeName: node.name,
182
+ },
183
+ });
184
+ },
185
+ onLog: (data, node) => {
186
+ if (data.type === "log") {
187
+ const sanitizedData = {
188
+ message: data.data.message,
189
+ timestamp: data.data.time,
190
+ };
191
+ send({
192
+ type: "node-log",
193
+ data: {
194
+ nodeUid: node?.uid || "unknown",
195
+ logData: sanitizedData,
196
+ },
197
+ });
198
+ }
199
+ },
200
+ abortSignal: abortControllerGraph!.signal,
201
+ cachePath: effectiveCachePath,
202
+ context,
203
+ });
204
+
205
+ send({
206
+ type: "end",
207
+ data: {
208
+ type: "success",
209
+ result: {
210
+ result,
211
+ buildId,
212
+ },
213
+ },
214
+ });
215
+ } catch (e) {
216
+ console.error("Graph execution failed:", e);
217
+ logger().error("Graph execution failed:", e);
218
+ const isCanceled = e instanceof Error && e.name === "AbortError";
219
+ send({
220
+ type: "end",
221
+ data: {
222
+ type: "error",
223
+ code: isCanceled ? "canceled" : "error",
224
+ ipcError: e instanceof Error ? e.message : "Unknown error",
225
+ },
226
+ });
227
+ }
228
+ });
229
+ };
@@ -0,0 +1,97 @@
1
+ import { useAPI } from "../ipc-core";
2
+ import { useLogger } from "@pipelab/shared";
3
+ import { writeFile, readFile, readdir, stat } from "node:fs/promises";
4
+ import { join } from "node:path";
5
+
6
+ import { PipelabContext } from "../context";
7
+
8
+ export const registerFsHandlers = (_context: PipelabContext) => {
9
+ const { handle } = useAPI();
10
+ const { logger } = useLogger();
11
+
12
+ handle("fs:read", async (event, { value, send }) => {
13
+ logger().info("fs:read", value.path);
14
+
15
+ try {
16
+ const data = await readFile(value.path, "utf-8");
17
+ logger().info("fs:read success, content length:", data.length);
18
+
19
+ send({
20
+ type: "end",
21
+ data: {
22
+ type: "success",
23
+ result: {
24
+ content: data,
25
+ },
26
+ },
27
+ });
28
+ } catch (e) {
29
+ logger().error("fs:read error for path:", value.path, e);
30
+ send({
31
+ type: "end",
32
+ data: {
33
+ type: "error",
34
+ ipcError: "Unable to read file",
35
+ },
36
+ });
37
+ }
38
+ });
39
+
40
+ handle("fs:write", async (event, { value, send }) => {
41
+ await writeFile(value.path, value.content, "utf-8");
42
+
43
+ send({
44
+ type: "end",
45
+ data: {
46
+ type: "success",
47
+ result: {
48
+ ok: true,
49
+ },
50
+ },
51
+ });
52
+ });
53
+
54
+ handle("fs:listDirectory", async (event, { value, send }) => {
55
+ try {
56
+ const entries = await readdir(value.path, { withFileTypes: true });
57
+ const files = await Promise.all(
58
+ entries.map(async (entry) => {
59
+ const fullPath = join(value.path, entry.name);
60
+ let stats: any = {};
61
+ try {
62
+ stats = await stat(fullPath);
63
+ } catch (e) {
64
+ // Might happen for broken symlinks etc
65
+ }
66
+
67
+ return {
68
+ name: entry.name,
69
+ isDirectory: entry.isDirectory(),
70
+ isSymbolicLink: entry.isSymbolicLink(),
71
+ size: stats.size || 0,
72
+ mtime: stats.mtime?.getTime() || 0,
73
+ };
74
+ }),
75
+ );
76
+
77
+ send({
78
+ type: "end",
79
+ data: {
80
+ type: "success",
81
+ result: {
82
+ files,
83
+ },
84
+ },
85
+ });
86
+ } catch (error) {
87
+ logger().error("Failed to list directory:", error);
88
+ send({
89
+ type: "end",
90
+ data: {
91
+ type: "error",
92
+ ipcError: error instanceof Error ? error.message : "Unable to list directory",
93
+ },
94
+ });
95
+ }
96
+ });
97
+ };
@@ -0,0 +1,329 @@
1
+ import { useAPI, WsEvent } from "../ipc-core";
2
+ import { useLogger, AppConfig } from "@pipelab/shared";
3
+ import { BuildHistoryStorage } from "./build-history";
4
+ import { SubscriptionRequiredError } from "@pipelab/shared";
5
+ import { PipelabContext } from "../context";
6
+ import { setupConfigFile } from "../config";
7
+
8
+ // Helper function to check build history authorization
9
+ const checkBuildHistoryAuthorization = async (event: WsEvent): Promise<boolean> => {
10
+ const { logger } = useLogger();
11
+ logger().info("AUTH BYPASS: Skipping auth verification for build history access");
12
+
13
+ // Always authorize for now - relying on frontend auth checks only
14
+ const isAuthorized = true;
15
+
16
+ if (!isAuthorized) {
17
+ throw new SubscriptionRequiredError("build-history");
18
+ }
19
+
20
+ return true;
21
+ };
22
+
23
+ export const registerHistoryHandlers = (context: PipelabContext) => {
24
+ const { handle } = useAPI();
25
+ const { logger } = useLogger();
26
+ const buildHistoryStorage = new BuildHistoryStorage(context);
27
+
28
+ // Build History Handlers
29
+ handle("build-history:save", async (event, { send, value }) => {
30
+ try {
31
+ // Check authorization before allowing save
32
+ logger().info("AUTH BYPASS: Processing build-history:save request");
33
+ await checkBuildHistoryAuthorization(event);
34
+
35
+ await buildHistoryStorage.save(value.entry);
36
+ send({
37
+ type: "end",
38
+ data: {
39
+ type: "success",
40
+ result: { result: "ok" },
41
+ },
42
+ });
43
+ } catch (error) {
44
+ logger().error("Failed to save build history entry:", error);
45
+
46
+ if (error instanceof SubscriptionRequiredError) {
47
+ send({
48
+ type: "end",
49
+ data: { type: "error", ipcError: error.userMessage, code: error.code },
50
+ });
51
+ return;
52
+ }
53
+
54
+ send({
55
+ type: "end",
56
+ data: {
57
+ type: "error",
58
+ ipcError: error instanceof Error ? error.message : "Failed to save build history entry",
59
+ },
60
+ });
61
+ }
62
+ });
63
+
64
+ handle("build-history:get", async (event, { send, value }) => {
65
+ try {
66
+ logger().info("AUTH BYPASS: Processing build-history:get request");
67
+ await checkBuildHistoryAuthorization(event);
68
+
69
+ const entry = await buildHistoryStorage.get(value.id, value.pipelineId);
70
+ send({
71
+ type: "end",
72
+ data: { type: "success", result: { entry } },
73
+ });
74
+ } catch (error) {
75
+ logger().error("Failed to get build history entry:", error);
76
+
77
+ if (error instanceof SubscriptionRequiredError) {
78
+ send({
79
+ type: "end",
80
+ data: { type: "error", ipcError: error.userMessage, code: error.code },
81
+ });
82
+ return;
83
+ }
84
+
85
+ send({
86
+ type: "end",
87
+ data: {
88
+ type: "error",
89
+ ipcError: error instanceof Error ? error.message : "Failed to get build history entry",
90
+ },
91
+ });
92
+ }
93
+ });
94
+
95
+ handle("build-history:get-all", async (event, { send, value }) => {
96
+ try {
97
+ logger().info("AUTH BYPASS: Processing build-history:get-all request");
98
+ await checkBuildHistoryAuthorization(event);
99
+
100
+ const allEntries = await buildHistoryStorage.getAll();
101
+ const filteredEntries = value?.query?.pipelineId
102
+ ? allEntries.filter((entry) => entry.pipelineId === value?.query?.pipelineId)
103
+ : allEntries;
104
+
105
+ send({
106
+ type: "end",
107
+ data: {
108
+ type: "success",
109
+ result: {
110
+ entries: filteredEntries,
111
+ total: filteredEntries.length,
112
+ },
113
+ },
114
+ });
115
+ } catch (error) {
116
+ logger().error("Failed to get build history entries:", error);
117
+
118
+ if (error instanceof SubscriptionRequiredError) {
119
+ send({
120
+ type: "end",
121
+ data: { type: "error", ipcError: error.userMessage, code: error.code },
122
+ });
123
+ return;
124
+ }
125
+
126
+ send({
127
+ type: "end",
128
+ data: {
129
+ type: "error",
130
+ ipcError:
131
+ error instanceof Error ? error.message : "Failed to get build history entries",
132
+ },
133
+ });
134
+ }
135
+ });
136
+
137
+ handle("build-history:update", async (event, { send, value }) => {
138
+ try {
139
+ await checkBuildHistoryAuthorization(event);
140
+
141
+ await buildHistoryStorage.update(value.id, value.updates, value.pipelineId);
142
+ send({
143
+ type: "end",
144
+ data: { type: "success", result: { result: "ok" } },
145
+ });
146
+ } catch (error) {
147
+ logger().error("Failed to update build history entry:", error);
148
+
149
+ if (error instanceof SubscriptionRequiredError) {
150
+ send({
151
+ type: "end",
152
+ data: { type: "error", ipcError: error.userMessage, code: error.code },
153
+ });
154
+ return;
155
+ }
156
+
157
+ send({
158
+ type: "end",
159
+ data: {
160
+ type: "error",
161
+ ipcError:
162
+ error instanceof Error ? error.message : "Failed to update build history entry",
163
+ },
164
+ });
165
+ }
166
+ });
167
+
168
+ handle("build-history:delete", async (event, { send, value }) => {
169
+ try {
170
+ await checkBuildHistoryAuthorization(event);
171
+
172
+ await buildHistoryStorage.delete(value.id, value.pipelineId);
173
+ send({
174
+ type: "end",
175
+ data: { type: "success", result: { result: "ok" } },
176
+ });
177
+ } catch (error) {
178
+ logger().error("Failed to delete build history entry:", error);
179
+
180
+ if (error instanceof SubscriptionRequiredError) {
181
+ send({
182
+ type: "end",
183
+ data: { type: "error", ipcError: error.userMessage, code: error.code },
184
+ });
185
+ return;
186
+ }
187
+
188
+ send({
189
+ type: "end",
190
+ data: {
191
+ type: "error",
192
+ ipcError:
193
+ error instanceof Error ? error.message : "Failed to delete build history entry",
194
+ },
195
+ });
196
+ }
197
+ });
198
+
199
+ handle("build-history:clear", async (event, { send }) => {
200
+ try {
201
+ await checkBuildHistoryAuthorization(event);
202
+
203
+ await buildHistoryStorage.clear();
204
+ send({
205
+ type: "end",
206
+ data: { type: "success", result: { result: "ok" } },
207
+ });
208
+ } catch (error) {
209
+ logger().error("Failed to clear build history:", error);
210
+
211
+ if (error instanceof SubscriptionRequiredError) {
212
+ send({
213
+ type: "end",
214
+ data: { type: "error", ipcError: error.userMessage, code: error.code },
215
+ });
216
+ return;
217
+ }
218
+
219
+ send({
220
+ type: "end",
221
+ data: {
222
+ type: "error",
223
+ ipcError: error instanceof Error ? error.message : "Failed to clear build history",
224
+ },
225
+ });
226
+ }
227
+ });
228
+
229
+ handle("build-history:clear-by-pipeline", async (event, { send, value }) => {
230
+ try {
231
+ await checkBuildHistoryAuthorization(event);
232
+
233
+ await buildHistoryStorage.clearByPipeline(value.pipelineId);
234
+ send({
235
+ type: "end",
236
+ data: { type: "success", result: { result: "ok" } },
237
+ });
238
+ } catch (error) {
239
+ logger().error(`Failed to clear build history for pipeline ${value.pipelineId}:`, error);
240
+
241
+ if (error instanceof SubscriptionRequiredError) {
242
+ send({
243
+ type: "end",
244
+ data: { type: "error", ipcError: error.userMessage, code: error.code },
245
+ });
246
+ return;
247
+ }
248
+
249
+ send({
250
+ type: "end",
251
+ data: {
252
+ type: "error",
253
+ ipcError: error instanceof Error ? error.message : "Failed to clear build history for pipeline",
254
+ },
255
+ });
256
+ }
257
+ });
258
+
259
+ handle("build-history:get-storage-info", async (event, { send }) => {
260
+ try {
261
+ await checkBuildHistoryAuthorization(event);
262
+
263
+ const info = await buildHistoryStorage.getStorageInfo();
264
+ send({
265
+ type: "end",
266
+ data: { type: "success", result: info },
267
+ });
268
+ } catch (error) {
269
+ logger().error("Failed to get build history storage info:", error);
270
+
271
+ if (error instanceof SubscriptionRequiredError) {
272
+ send({
273
+ type: "end",
274
+ data: { type: "error", ipcError: error.userMessage, code: error.code },
275
+ });
276
+ return;
277
+ }
278
+
279
+ send({
280
+ type: "end",
281
+ data: {
282
+ type: "error",
283
+ ipcError:
284
+ error instanceof Error ? error.message : "Failed to get build history storage info",
285
+ },
286
+ });
287
+ }
288
+ });
289
+
290
+ handle("build-history:configure", async (_, { send, value }) => {
291
+ try {
292
+ logger().info("Updating build history configuration:", value.config);
293
+
294
+ const settings = await setupConfigFile<AppConfig>("settings", { context });
295
+ const currentConfig = await settings.getConfig();
296
+
297
+ // Deep merge the new config with the existing one
298
+ const newConfig = {
299
+ ...currentConfig,
300
+ buildHistory: {
301
+ ...currentConfig?.buildHistory,
302
+ retentionPolicy: {
303
+ ...currentConfig?.buildHistory?.retentionPolicy,
304
+ ...value.config.retentionPolicy,
305
+ },
306
+ },
307
+ };
308
+
309
+ await settings.saveConfig(newConfig);
310
+
311
+ send({
312
+ type: "end",
313
+ data: {
314
+ type: "success",
315
+ result: { result: "ok" },
316
+ },
317
+ });
318
+ } catch (error) {
319
+ logger().error("Failed to configure build history:", error);
320
+ send({
321
+ type: "end",
322
+ data: {
323
+ type: "error",
324
+ ipcError: error instanceof Error ? error.message : "Failed to configure build history",
325
+ },
326
+ });
327
+ }
328
+ });
329
+ };
@@ -0,0 +1,41 @@
1
+ import { registerShellHandlers } from "./shell";
2
+ import { registerFsHandlers } from "./fs";
3
+ import { registerConfigHandlers } from "./config";
4
+ import { registerHistoryHandlers } from "./history";
5
+ import { registerEngineHandlers } from "./engine";
6
+ import { registerAgentsHandlers } from "./agents";
7
+ import { registerAuthHandlers } from "./auth";
8
+ import { registerSystemHandlers } from "./system";
9
+ import { builtInPlugins } from "../plugins-registry";
10
+ import { usePlugins } from "@pipelab/shared";
11
+ import { PipelabContext } from "../context";
12
+
13
+ export const registerAllHandlers = async (options: {
14
+ version: string;
15
+ context: PipelabContext;
16
+ }) => {
17
+ const context = options.context;
18
+
19
+ registerShellHandlers(context);
20
+ registerFsHandlers(context);
21
+ registerConfigHandlers(context);
22
+ registerHistoryHandlers(context);
23
+ registerEngineHandlers(context);
24
+ registerAgentsHandlers(context);
25
+ registerAuthHandlers(context);
26
+ registerSystemHandlers(options);
27
+
28
+ const { registerPlugins } = usePlugins();
29
+ const plugins = await builtInPlugins({
30
+ context,
31
+ });
32
+ registerPlugins(plugins as any);
33
+ };
34
+
35
+ export { registerShellHandlers } from "./shell";
36
+ export { registerFsHandlers } from "./fs";
37
+ export { registerConfigHandlers } from "./config";
38
+ export { registerHistoryHandlers } from "./history";
39
+ export { registerEngineHandlers } from "./engine";
40
+ export { registerAgentsHandlers } from "./agents";
41
+ export { BuildHistoryStorage } from "./build-history";