@pipelab/core-node 1.0.0-beta.2 → 1.0.0-beta.22

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 (45) hide show
  1. package/.oxfmtrc.json +1 -1
  2. package/CHANGELOG.md +173 -0
  3. package/dist/config-Bi0ORcTK.mjs +2 -0
  4. package/dist/config-CFgGRD9U.mjs +265 -0
  5. package/dist/config-CFgGRD9U.mjs.map +1 -0
  6. package/dist/index.d.mts +74 -53
  7. package/dist/index.d.mts.map +1 -1
  8. package/dist/index.mjs +1705 -576
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +9 -5
  11. package/scratch/simulate-updates.ts +120 -0
  12. package/src/config.test.ts +232 -0
  13. package/src/config.ts +111 -50
  14. package/src/context.ts +117 -5
  15. package/src/handler-func.ts +2 -77
  16. package/src/handlers/build-history.ts +60 -90
  17. package/src/handlers/config.ts +265 -55
  18. package/src/handlers/engine.ts +32 -35
  19. package/src/handlers/history.ts +0 -40
  20. package/src/handlers/index.ts +4 -0
  21. package/src/handlers/migration.ts +621 -0
  22. package/src/handlers/plugins.ts +305 -0
  23. package/src/handlers/system.ts +7 -2
  24. package/src/index.ts +9 -2
  25. package/src/plugins-registry.ts +280 -38
  26. package/src/presets/c3toSteam.ts +13 -7
  27. package/src/presets/demo.ts +9 -9
  28. package/src/presets/list.ts +0 -6
  29. package/src/presets/moreToCome.ts +3 -2
  30. package/src/presets/newProject.ts +3 -2
  31. package/src/presets/test-c3-offline.ts +15 -6
  32. package/src/presets/test-c3-unzip.ts +19 -8
  33. package/src/runner.ts +2 -8
  34. package/src/server.ts +45 -4
  35. package/src/types/runner.ts +2 -30
  36. package/src/utils/fs-extras.ts +6 -24
  37. package/src/utils/github.test.ts +211 -0
  38. package/src/utils/github.ts +90 -10
  39. package/src/utils/remote.test.ts +209 -0
  40. package/src/utils/remote.ts +325 -87
  41. package/src/utils/storage.ts +2 -1
  42. package/src/utils.ts +20 -24
  43. package/src/migrations.ts +0 -72
  44. package/src/presets/if.ts +0 -69
  45. package/src/presets/loop.ts +0 -65
@@ -1,108 +1,318 @@
1
1
  import { useAPI } from "../ipc-core";
2
- import { useLogger, configRegistry } from "@pipelab/shared";
3
- import { setupConfigFile, getMigrator } from "../config";
2
+ import {
3
+ useLogger,
4
+ appSettingsMigrator,
5
+ connectionsMigrator,
6
+ fileRepoMigrations,
7
+ } from "@pipelab/shared";
8
+ import {
9
+ setupSettingsConfigFile,
10
+ setupConnectionsConfigFile,
11
+ setupProjectsConfigFile,
12
+ setupPipelineConfigFileByName,
13
+ setupPipelineConfigFileByPath,
14
+ deletePipelineConfigFileByName,
15
+ deletePipelineConfigFileByPath,
16
+ } from "../config";
4
17
  import { PipelabContext } from "../context";
5
18
 
6
19
  export const registerConfigHandlers = (context: PipelabContext) => {
7
20
  const { handle } = useAPI();
8
21
  const { logger } = useLogger();
9
22
 
10
- handle("config:load", async (_, { send, value }) => {
11
- const { config: name } = value;
12
- logger().info("config:load", name);
13
-
23
+ // Settings
24
+ handle("settings:load", async (_, { send }) => {
25
+ logger().info("settings:load");
14
26
  try {
15
- const manager = await setupConfigFile(name, { context });
27
+ const manager = await setupSettingsConfigFile(context);
16
28
  const json = await manager.getConfig();
17
-
18
29
  send({
19
30
  type: "end",
20
- data: {
21
- type: "success",
22
- result: {
23
- result: json,
24
- },
25
- },
31
+ data: { type: "success", result: json },
26
32
  });
27
33
  } catch (e) {
28
- logger().error(`config:load error for ${name}:`, e);
34
+ logger().error("settings:load error:", e);
29
35
  send({
30
36
  type: "end",
31
- data: {
32
- type: "error",
33
- ipcError: e instanceof Error ? e.message : `Unable to load config ${name}`,
34
- },
37
+ data: { type: "error", ipcError: e instanceof Error ? e.message : "Unable to load settings" },
35
38
  });
36
39
  }
37
40
  });
38
41
 
39
- handle("config:save", async (_, { send, value }) => {
40
- const { data, config: name } = value;
41
-
42
+ handle("settings:save", async (_, { send, value }) => {
43
+ const { data } = value;
42
44
  try {
43
- const manager = await setupConfigFile(name, { context });
45
+ const manager = await setupSettingsConfigFile(context);
44
46
  const json = typeof data === "string" ? JSON.parse(data) : data;
45
47
  await manager.setConfig(json);
48
+ send({
49
+ type: "end",
50
+ data: { type: "success", result: "ok" },
51
+ });
52
+ } catch (e) {
53
+ logger().error("settings:save error:", e);
54
+ send({
55
+ type: "end",
56
+ data: { type: "error", ipcError: e instanceof Error ? e.message : "Unable to save settings" },
57
+ });
58
+ }
59
+ });
46
60
 
61
+ handle("settings:reset", async (_, { send, value }) => {
62
+ const { key } = value;
63
+ try {
64
+ const manager = await setupSettingsConfigFile(context);
65
+ const currentConfig = await manager.getConfig();
66
+ const defaultValue = (appSettingsMigrator.defaultValue as any)[key];
67
+ await manager.setConfig({
68
+ ...(currentConfig ? (currentConfig as any) : {}),
69
+ [key]: defaultValue,
70
+ } as any);
47
71
  send({
48
72
  type: "end",
49
- data: {
50
- type: "success",
51
- result: {
52
- result: "ok",
53
- },
54
- },
73
+ data: { type: "success", result: "ok" },
55
74
  });
56
75
  } catch (e) {
57
- logger().error(`config:save error for ${name}:`, e);
76
+ logger().error("settings:reset error:", e);
58
77
  send({
59
78
  type: "end",
60
- data: {
61
- type: "error",
62
- ipcError: e instanceof Error ? e.message : `Unable to save config ${name}`,
63
- },
79
+ data: { type: "error", ipcError: e instanceof Error ? e.message : "Unable to reset settings" },
64
80
  });
65
81
  }
66
82
  });
67
83
 
68
- handle("config:reset", async (event, { value, send }) => {
69
- const { config: name, key } = value;
70
- logger().info("config:reset", name, key);
84
+ // Connections
85
+ handle("connections:load", async (_, { send }) => {
86
+ logger().info("connections:load");
87
+ try {
88
+ const manager = await setupConnectionsConfigFile(context);
89
+ const json = await manager.getConfig();
90
+ send({
91
+ type: "end",
92
+ data: { type: "success", result: json },
93
+ });
94
+ } catch (e) {
95
+ logger().error("connections:load error:", e);
96
+ send({
97
+ type: "end",
98
+ data: { type: "error", ipcError: e instanceof Error ? e.message : "Unable to load connections" },
99
+ });
100
+ }
101
+ });
71
102
 
103
+ handle("connections:save", async (_, { send, value }) => {
104
+ const { data } = value;
72
105
  try {
73
- const manager = await setupConfigFile(name, { context });
74
- const currentConfig = await manager.getConfig();
106
+ const manager = await setupConnectionsConfigFile(context);
107
+ const json = typeof data === "string" ? JSON.parse(data) : data;
108
+ await manager.setConfig(json);
109
+ send({
110
+ type: "end",
111
+ data: { type: "success", result: "ok" },
112
+ });
113
+ } catch (e) {
114
+ logger().error("connections:save error:", e);
115
+ send({
116
+ type: "end",
117
+ data: { type: "error", ipcError: e instanceof Error ? e.message : "Unable to save connections" },
118
+ });
119
+ }
120
+ });
75
121
 
76
- const migrator = getMigrator(name);
122
+ handle("connections:reset", async (_, { send, value }) => {
123
+ const { key } = value;
124
+ try {
125
+ const manager = await setupConnectionsConfigFile(context);
126
+ const currentConfig = await manager.getConfig();
127
+ const defaultValue = (connectionsMigrator.defaultValue as any)[key];
128
+ await manager.setConfig({
129
+ ...(currentConfig ? (currentConfig as any) : {}),
130
+ [key]: defaultValue,
131
+ } as any);
132
+ send({
133
+ type: "end",
134
+ data: { type: "success", result: "ok" },
135
+ });
136
+ } catch (e) {
137
+ logger().error("connections:reset error:", e);
138
+ send({
139
+ type: "end",
140
+ data: { type: "error", ipcError: e instanceof Error ? e.message : "Unable to reset connections" },
141
+ });
142
+ }
143
+ });
77
144
 
78
- if (!migrator) {
79
- throw new Error(`No migrator found for configuration: ${name}`);
80
- }
145
+ // Projects
146
+ handle("projects:load", async (_, { send }) => {
147
+ logger().info("projects:load");
148
+ try {
149
+ const manager = await setupProjectsConfigFile(context);
150
+ const json = await manager.getConfig();
151
+ send({
152
+ type: "end",
153
+ data: { type: "success", result: json },
154
+ });
155
+ } catch (e) {
156
+ logger().error("projects:load error:", e);
157
+ send({
158
+ type: "end",
159
+ data: { type: "error", ipcError: e instanceof Error ? e.message : "Unable to load projects" },
160
+ });
161
+ }
162
+ });
81
163
 
82
- const defaultValue = (migrator.defaultValue as any)[key];
164
+ handle("projects:save", async (_, { send, value }) => {
165
+ const { data } = value;
166
+ try {
167
+ const manager = await setupProjectsConfigFile(context);
168
+ const json = typeof data === "string" ? JSON.parse(data) : data;
169
+ await manager.setConfig(json);
170
+ send({
171
+ type: "end",
172
+ data: { type: "success", result: "ok" },
173
+ });
174
+ } catch (e) {
175
+ logger().error("projects:save error:", e);
176
+ send({
177
+ type: "end",
178
+ data: { type: "error", ipcError: e instanceof Error ? e.message : "Unable to save projects" },
179
+ });
180
+ }
181
+ });
83
182
 
183
+ handle("projects:reset", async (_, { send, value }) => {
184
+ const { key } = value;
185
+ try {
186
+ const manager = await setupProjectsConfigFile(context);
187
+ const currentConfig = await manager.getConfig();
188
+ const defaultValue = (fileRepoMigrations.defaultValue as any)[key];
84
189
  await manager.setConfig({
85
190
  ...(currentConfig ? (currentConfig as any) : {}),
86
191
  [key]: defaultValue,
87
192
  } as any);
193
+ send({
194
+ type: "end",
195
+ data: { type: "success", result: "ok" },
196
+ });
197
+ } catch (e) {
198
+ logger().error("projects:reset error:", e);
199
+ send({
200
+ type: "end",
201
+ data: { type: "error", ipcError: e instanceof Error ? e.message : "Unable to reset projects" },
202
+ });
203
+ }
204
+ });
205
+
206
+ // Pipeline (ByName)
207
+ handle("pipeline:load-by-name", async (_, { send, value }) => {
208
+ const { name } = value;
209
+ logger().info("pipeline:load-by-name", name);
210
+ try {
211
+ const manager = await setupPipelineConfigFileByName(name, context);
212
+ const json = await manager.getConfig();
213
+ send({
214
+ type: "end",
215
+ data: { type: "success", result: json },
216
+ });
217
+ } catch (e) {
218
+ logger().error(`pipeline:load-by-name error for ${name}:`, e);
219
+ send({
220
+ type: "end",
221
+ data: { type: "error", ipcError: e instanceof Error ? e.message : `Unable to load pipeline ${name}` },
222
+ });
223
+ }
224
+ });
225
+
226
+ // Pipeline (ByPath)
227
+ handle("pipeline:load-by-path", async (_, { send, value }) => {
228
+ const { path: absolutePath } = value;
229
+ logger().info("pipeline:load-by-path", absolutePath);
230
+ try {
231
+ const manager = await setupPipelineConfigFileByPath(absolutePath, context);
232
+ const json = await manager.getConfig();
233
+ send({
234
+ type: "end",
235
+ data: { type: "success", result: json },
236
+ });
237
+ } catch (e) {
238
+ logger().error(`pipeline:load-by-path error for ${absolutePath}:`, e);
239
+ send({
240
+ type: "end",
241
+ data: { type: "error", ipcError: e instanceof Error ? e.message : `Unable to load pipeline ${absolutePath}` },
242
+ });
243
+ }
244
+ });
88
245
 
246
+ handle("pipeline:save-by-name", async (_, { send, value }) => {
247
+ const { data, name } = value;
248
+ try {
249
+ const manager = await setupPipelineConfigFileByName(name, context);
250
+ const json = typeof data === "string" ? JSON.parse(data) : data;
251
+ await manager.setConfig(json);
252
+ send({
253
+ type: "end",
254
+ data: { type: "success", result: "ok" },
255
+ });
256
+ } catch (e) {
257
+ logger().error(`pipeline:save-by-name error for ${name}:`, e);
258
+ send({
259
+ type: "end",
260
+ data: { type: "error", ipcError: e instanceof Error ? e.message : `Unable to save pipeline ${name}` },
261
+ });
262
+ }
263
+ });
264
+
265
+ handle("pipeline:save-by-path", async (_, { send, value }) => {
266
+ const { data, path: absolutePath } = value;
267
+ try {
268
+ const manager = await setupPipelineConfigFileByPath(absolutePath, context);
269
+ const json = typeof data === "string" ? JSON.parse(data) : data;
270
+ await manager.setConfig(json);
271
+ send({
272
+ type: "end",
273
+ data: { type: "success", result: "ok" },
274
+ });
275
+ } catch (e) {
276
+ logger().error(`pipeline:save-by-path error for ${absolutePath}:`, e);
277
+ send({
278
+ type: "end",
279
+ data: { type: "error", ipcError: e instanceof Error ? e.message : `Unable to save pipeline ${absolutePath}` },
280
+ });
281
+ }
282
+ });
283
+
284
+ handle("pipeline:delete-by-name", async (_, { send, value }) => {
285
+ const { name } = value;
286
+ logger().info("pipeline:delete-by-name", name);
287
+ try {
288
+ await deletePipelineConfigFileByName(name, context);
289
+ send({
290
+ type: "end",
291
+ data: { type: "success", result: "ok" },
292
+ });
293
+ } catch (e) {
294
+ logger().error(`pipeline:delete-by-name error for ${name}:`, e);
295
+ send({
296
+ type: "end",
297
+ data: { type: "error", ipcError: e instanceof Error ? e.message : `Unable to delete pipeline ${name}` },
298
+ });
299
+ }
300
+ });
301
+
302
+ handle("pipeline:delete-by-path", async (_, { send, value }) => {
303
+ const { path: absolutePath } = value;
304
+ logger().info("pipeline:delete-by-path", absolutePath);
305
+ try {
306
+ await deletePipelineConfigFileByPath(absolutePath, context);
89
307
  send({
90
308
  type: "end",
91
- data: {
92
- type: "success",
93
- result: {
94
- result: "ok",
95
- },
96
- },
309
+ data: { type: "success", result: "ok" },
97
310
  });
98
311
  } catch (e) {
99
- logger().error(`config:reset error for ${name}:`, e);
312
+ logger().error(`pipeline:delete-by-path error for ${absolutePath}:`, e);
100
313
  send({
101
314
  type: "end",
102
- data: {
103
- type: "error",
104
- ipcError: e instanceof Error ? e.message : `Unable to reset config ${name}`,
105
- },
315
+ data: { type: "error", ipcError: e instanceof Error ? e.message : `Unable to delete pipeline ${absolutePath}` },
106
316
  });
107
317
  }
108
318
  });
@@ -1,12 +1,12 @@
1
1
  import { useAPI, HandleListenerSendFn } from "../ipc-core";
2
- import { PipelabContext } from "../context";
2
+ import { PipelabContext, CacheFolder } from "../context";
3
3
  import { useLogger } from "@pipelab/shared";
4
4
  import { getFinalPlugins, executeGraphWithHistory } from "../utils";
5
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";
6
+ import { handleActionExecute } from "../handler-func";
7
+ import { join } from "node:path";
8
+ import { rm } from "node:fs/promises";
9
+ import { setupSettingsConfigFile } from "../config";
10
10
  import { AppConfig } from "@pipelab/shared";
11
11
 
12
12
  export const registerEngineHandlers = (context: PipelabContext) => {
@@ -37,12 +37,6 @@ export const registerEngineHandlers = (context: PipelabContext) => {
37
37
  });
38
38
  });
39
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
40
  let abortControllerGraph: undefined | AbortController = undefined;
47
41
 
48
42
  const effectiveActionExecute = async (
@@ -85,32 +79,35 @@ export const registerEngineHandlers = (context: PipelabContext) => {
85
79
 
86
80
  handle("action:execute", async (event, { send, value }) => {
87
81
  const { nodeId, params, pluginId } = value;
88
- const settings = await setupConfigFile<AppConfig>("settings", { context });
89
- const config = await settings.getConfig();
82
+ const cachePath = context.getCachePath(CacheFolder.Actions, pluginId, nodeId);
83
+ const cwd = await context.createTempFolder("action-execute-");
90
84
 
91
- const cachePath = config?.cacheFolder || tmpdir();
92
- const cwd = await generateTempFolder(cachePath);
93
-
94
- const mainWindow = undefined;
95
- abortControllerGraph = new AbortController();
85
+ try {
86
+ const mainWindow: undefined = undefined;
87
+ abortControllerGraph = new AbortController();
96
88
 
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
- },
89
+ const signalPromise = new Promise((resolve, reject) => {
90
+ abortControllerGraph!.signal.addEventListener("abort", async () => {
91
+ await send({
92
+ type: "end",
93
+ data: {
94
+ ipcError: "Action aborted",
95
+ type: "error",
96
+ },
97
+ });
98
+ return reject(new Error("Action interrupted"));
105
99
  });
106
- return reject(new Error("Action interrupted"));
107
100
  });
108
- });
109
101
 
110
- await Promise.race([
111
- signalPromise,
112
- effectiveActionExecute(nodeId, pluginId, params, mainWindow, send, cwd, cachePath),
113
- ]);
102
+ await Promise.race([
103
+ signalPromise,
104
+ effectiveActionExecute(nodeId, pluginId, params, mainWindow, send, cwd, cachePath),
105
+ ]);
106
+ } finally {
107
+ await rm(cwd, { recursive: true, force: true }).catch((err) => {
108
+ console.warn(`Failed to cleanup temp folder at ${cwd}:`, err);
109
+ });
110
+ }
114
111
  });
115
112
 
116
113
  handle("constants:get", async (_, { send }) => {
@@ -145,15 +142,15 @@ export const registerEngineHandlers = (context: PipelabContext) => {
145
142
 
146
143
  handle("graph:execute", async (event, { send, value }) => {
147
144
  const { graph, variables, projectName, projectPath, pipelineId } = value;
148
- const settings = await setupConfigFile<AppConfig>("settings", { context });
145
+ const settings = await setupSettingsConfigFile(context);
149
146
  const config = await settings.getConfig();
150
147
 
151
148
  const effectiveProjectName = projectName || "Unnamed Project";
152
149
  const effectiveProjectPath = projectPath || "";
153
150
  const effectivePipelineId = pipelineId || "unknown";
154
- const effectiveCachePath = config?.cacheFolder || tmpdir();
151
+ const effectiveCachePath = context.getCachePath(CacheFolder.Pipelines, effectivePipelineId);
155
152
 
156
- const mainWindow = undefined;
153
+ const mainWindow: undefined = undefined;
157
154
  abortControllerGraph = new AbortController();
158
155
 
159
156
  try {
@@ -284,44 +284,4 @@ export const registerHistoryHandlers = (context: PipelabContext) => {
284
284
  });
285
285
  }
286
286
  });
287
-
288
- handle("build-history:configure", async (_, { send, value }) => {
289
- try {
290
- logger().info("Updating build history configuration:", value.config);
291
-
292
- const settings = await setupConfigFile<AppConfig>("settings", { context });
293
- const currentConfig = await settings.getConfig();
294
-
295
- // Deep merge the new config with the existing one
296
- const newConfig = {
297
- ...currentConfig,
298
- buildHistory: {
299
- ...currentConfig?.buildHistory,
300
- retentionPolicy: {
301
- ...currentConfig?.buildHistory?.retentionPolicy,
302
- ...value.config.retentionPolicy,
303
- },
304
- },
305
- };
306
-
307
- await settings.saveConfig(newConfig);
308
-
309
- send({
310
- type: "end",
311
- data: {
312
- type: "success",
313
- result: { result: "ok" },
314
- },
315
- });
316
- } catch (error) {
317
- logger().error("Failed to configure build history:", error);
318
- send({
319
- type: "end",
320
- data: {
321
- type: "error",
322
- ipcError: error instanceof Error ? error.message : "Failed to configure build history",
323
- },
324
- });
325
- }
326
- });
327
287
  };
@@ -6,6 +6,8 @@ import { registerEngineHandlers } from "./engine";
6
6
  import { registerAgentsHandlers } from "./agents";
7
7
  import { registerAuthHandlers } from "./auth";
8
8
  import { registerSystemHandlers } from "./system";
9
+ import { registerPluginsHandlers } from "./plugins";
10
+ import { registerMigrationHandlers } from "./migration";
9
11
  import { builtInPlugins } from "../plugins-registry";
10
12
  import { usePlugins } from "@pipelab/shared";
11
13
  import { PipelabContext } from "../context";
@@ -24,6 +26,8 @@ export const registerAllHandlers = async (options: {
24
26
  registerAgentsHandlers(context);
25
27
  registerAuthHandlers(context);
26
28
  registerSystemHandlers(options);
29
+ registerPluginsHandlers(context);
30
+ registerMigrationHandlers(context);
27
31
 
28
32
  const { registerPlugins } = usePlugins();
29
33
  // Execute in the background! The plugins will be dynamically registered and broadcasted to the UI.