@pipelab/core-node 1.0.1-beta.23
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/.turbo/turbo-build.log +75 -0
- package/CHANGELOG.md +291 -0
- package/LICENSE +110 -0
- package/LICENSE.md +110 -0
- package/README.md +10 -0
- package/dist/index.d.mts +344 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +51852 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +62 -0
- package/src/api.ts +115 -0
- package/src/config.ts +105 -0
- package/src/context.ts +53 -0
- package/src/handler-func.ts +234 -0
- package/src/handlers/agents.ts +32 -0
- package/src/handlers/auth.ts +95 -0
- package/src/handlers/build-history.ts +360 -0
- package/src/handlers/config.ts +109 -0
- package/src/handlers/engine.ts +229 -0
- package/src/handlers/fs.ts +97 -0
- package/src/handlers/history.ts +299 -0
- package/src/handlers/index.ts +41 -0
- package/src/handlers/shell.ts +57 -0
- package/src/handlers/system.ts +18 -0
- package/src/handlers.ts +2 -0
- package/src/heavy.ts +4 -0
- package/src/index.ts +16 -0
- package/src/ipc-core.ts +70 -0
- package/src/migrations.ts +72 -0
- package/src/paths.ts +1 -0
- package/src/plugins-registry.ts +62 -0
- package/src/presets/c3toSteam.ts +272 -0
- package/src/presets/demo.ts +123 -0
- package/src/presets/if.ts +69 -0
- package/src/presets/list.ts +30 -0
- package/src/presets/loop.ts +65 -0
- package/src/presets/moreToCome.ts +32 -0
- package/src/presets/newProject.ts +31 -0
- package/src/presets/preset.model.ts +0 -0
- package/src/presets/test-c3-offline.ts +78 -0
- package/src/presets/test-c3-unzip.ts +124 -0
- package/src/runner.ts +107 -0
- package/src/server.ts +80 -0
- package/src/types/runner.ts +101 -0
- package/src/utils/fs-extras.ts +182 -0
- package/src/utils/remote.ts +381 -0
- package/src/utils/storage.ts +99 -0
- package/src/utils.ts +258 -0
- package/src/websocket-server.ts +288 -0
- package/tsconfig.json +19 -0
|
@@ -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,299 @@
|
|
|
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:get-storage-info", async (event, { send }) => {
|
|
230
|
+
try {
|
|
231
|
+
await checkBuildHistoryAuthorization(event);
|
|
232
|
+
|
|
233
|
+
const info = await buildHistoryStorage.getStorageInfo();
|
|
234
|
+
send({
|
|
235
|
+
type: "end",
|
|
236
|
+
data: { type: "success", result: info },
|
|
237
|
+
});
|
|
238
|
+
} catch (error) {
|
|
239
|
+
logger().error("Failed to get build history storage info:", 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:
|
|
254
|
+
error instanceof Error ? error.message : "Failed to get build history storage info",
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
handle("build-history:configure", async (_, { send, value }) => {
|
|
261
|
+
try {
|
|
262
|
+
logger().info("Updating build history configuration:", value.config);
|
|
263
|
+
|
|
264
|
+
const settings = await setupConfigFile<AppConfig>("settings", { context });
|
|
265
|
+
const currentConfig = await settings.getConfig();
|
|
266
|
+
|
|
267
|
+
// Deep merge the new config with the existing one
|
|
268
|
+
const newConfig = {
|
|
269
|
+
...currentConfig,
|
|
270
|
+
buildHistory: {
|
|
271
|
+
...currentConfig?.buildHistory,
|
|
272
|
+
retentionPolicy: {
|
|
273
|
+
...currentConfig?.buildHistory?.retentionPolicy,
|
|
274
|
+
...value.config.retentionPolicy,
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
await settings.saveConfig(newConfig);
|
|
280
|
+
|
|
281
|
+
send({
|
|
282
|
+
type: "end",
|
|
283
|
+
data: {
|
|
284
|
+
type: "success",
|
|
285
|
+
result: { result: "ok" },
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
} catch (error) {
|
|
289
|
+
logger().error("Failed to configure build history:", error);
|
|
290
|
+
send({
|
|
291
|
+
type: "end",
|
|
292
|
+
data: {
|
|
293
|
+
type: "error",
|
|
294
|
+
ipcError: error instanceof Error ? error.message : "Failed to configure build history",
|
|
295
|
+
},
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
};
|
|
@@ -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";
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { useAPI } from "../ipc-core";
|
|
2
|
+
import { useLogger } from "@pipelab/shared";
|
|
3
|
+
import { PipelabContext } from "../context";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
|
|
6
|
+
export const registerShellHandlers = (_context: PipelabContext) => {
|
|
7
|
+
const { handle } = useAPI();
|
|
8
|
+
const { logger } = useLogger();
|
|
9
|
+
|
|
10
|
+
handle("dialog:showOpenDialog", async (event, { value, send }) => {
|
|
11
|
+
logger().info("value", value);
|
|
12
|
+
logger().info("dialog:showOpenDialog");
|
|
13
|
+
|
|
14
|
+
// Since we are in a standalone server, we cannot show Electron dialogs.
|
|
15
|
+
// In the future, this could be handled by the UI or a separate GUI process.
|
|
16
|
+
send({
|
|
17
|
+
type: "end",
|
|
18
|
+
data: {
|
|
19
|
+
type: "success",
|
|
20
|
+
result: {
|
|
21
|
+
filePaths: [],
|
|
22
|
+
canceled: true,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
handle("dialog:showSaveDialog", async (event, { value, send }) => {
|
|
29
|
+
const { logger } = useLogger();
|
|
30
|
+
|
|
31
|
+
logger().info("value", value);
|
|
32
|
+
logger().info("dialog:showSaveDialog");
|
|
33
|
+
|
|
34
|
+
send({
|
|
35
|
+
type: "end",
|
|
36
|
+
data: {
|
|
37
|
+
type: "success",
|
|
38
|
+
result: {
|
|
39
|
+
filePath: undefined,
|
|
40
|
+
canceled: true,
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
handle("fs:getHomeDirectory", async (event, { send }) => {
|
|
47
|
+
send({
|
|
48
|
+
type: "end",
|
|
49
|
+
data: {
|
|
50
|
+
type: "success",
|
|
51
|
+
result: {
|
|
52
|
+
path: homedir(),
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useAPI } from "../ipc-core";
|
|
2
|
+
import { PipelabContext } from "../context";
|
|
3
|
+
|
|
4
|
+
export const registerSystemHandlers = (options: { version: string; context: PipelabContext }) => {
|
|
5
|
+
const { handle } = useAPI();
|
|
6
|
+
|
|
7
|
+
handle("agent:version:get", async (_, { send }) => {
|
|
8
|
+
send({
|
|
9
|
+
type: "end",
|
|
10
|
+
data: {
|
|
11
|
+
type: "success",
|
|
12
|
+
result: {
|
|
13
|
+
version: options.version,
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
};
|
package/src/handlers.ts
ADDED
package/src/heavy.ts
ADDED
package/src/index.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export * from "./context";
|
|
2
|
+
export * from "./websocket-server";
|
|
3
|
+
export * from "./ipc-core";
|
|
4
|
+
export * from "./handlers/index";
|
|
5
|
+
export * from "./config";
|
|
6
|
+
export * from "./paths";
|
|
7
|
+
export * from "./api";
|
|
8
|
+
export * from "./heavy";
|
|
9
|
+
export * from "./plugins-registry";
|
|
10
|
+
export * from "./utils/remote";
|
|
11
|
+
export * from "./utils/fs-extras";
|
|
12
|
+
export * from "./types/runner";
|
|
13
|
+
export * from "./runner";
|
|
14
|
+
export * from "./migrations";
|
|
15
|
+
export * from "./server";
|
|
16
|
+
export * from "./utils";
|
package/src/ipc-core.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Channels, IpcMessage } from "@pipelab/shared";
|
|
2
|
+
import { WebSocket as WSWebSocket } from "ws";
|
|
3
|
+
import { useLogger } from "@pipelab/shared";
|
|
4
|
+
import { WebSocketEvent, WebSocketHandler, WebSocketSendFunction } from "@pipelab/shared";
|
|
5
|
+
|
|
6
|
+
export type HandleListenerSendFn<KEY extends Channels> = WebSocketSendFunction<KEY>;
|
|
7
|
+
|
|
8
|
+
export type WsEvent = WebSocketEvent;
|
|
9
|
+
|
|
10
|
+
export type HandleListener<KEY extends Channels> = WebSocketHandler<KEY>;
|
|
11
|
+
|
|
12
|
+
const handlers: Record<string, WebSocketHandler<any>> = {};
|
|
13
|
+
|
|
14
|
+
export const useAPI = () => {
|
|
15
|
+
const { logger } = useLogger();
|
|
16
|
+
|
|
17
|
+
const handle = <KEY extends Channels>(channel: KEY, listener: WebSocketHandler<KEY>) => {
|
|
18
|
+
handlers[channel] = listener;
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
channel,
|
|
22
|
+
listener,
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const processWebSocketMessage = (ws: WSWebSocket, channel: string, message: IpcMessage) => {
|
|
27
|
+
const { data, requestId } = message;
|
|
28
|
+
|
|
29
|
+
if (handlers[channel]) {
|
|
30
|
+
logger().debug("Executing handler for channel:", channel, "with data:", JSON.stringify(data));
|
|
31
|
+
const event: WsEvent = {
|
|
32
|
+
sender: ws.url || "websocket-client",
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const send: HandleListenerSendFn<any> = (events) => {
|
|
36
|
+
const serialized = JSON.stringify(events);
|
|
37
|
+
logger().debug(
|
|
38
|
+
"sending response to",
|
|
39
|
+
requestId,
|
|
40
|
+
":",
|
|
41
|
+
serialized.length > 500 ? serialized.substring(0, 500) + "..." : serialized,
|
|
42
|
+
);
|
|
43
|
+
const response = {
|
|
44
|
+
type: "response",
|
|
45
|
+
requestId,
|
|
46
|
+
events,
|
|
47
|
+
};
|
|
48
|
+
ws.send(JSON.stringify(response), (error) => {
|
|
49
|
+
if (error) {
|
|
50
|
+
logger().error("Failed to send WebSocket response:", error);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
return Promise.resolve();
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return handlers[channel](event, {
|
|
57
|
+
send,
|
|
58
|
+
value: data,
|
|
59
|
+
});
|
|
60
|
+
} else {
|
|
61
|
+
logger().warn("No handler found for channel:", channel);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
handle,
|
|
67
|
+
processWebSocketMessage,
|
|
68
|
+
handlers,
|
|
69
|
+
};
|
|
70
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { useAPI } from "./ipc-core";
|
|
2
|
+
import { PipelabContext } from "./context";
|
|
3
|
+
import { setupConfigFile } from "./config";
|
|
4
|
+
import {
|
|
5
|
+
savedFileMigrator,
|
|
6
|
+
fileRepoMigrations,
|
|
7
|
+
appSettingsMigrator,
|
|
8
|
+
useLogger,
|
|
9
|
+
} from "@pipelab/shared";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Registers migration handlers for the CLI/Standalone server.
|
|
13
|
+
* This overrides the default handlers from @pipelab/core-node to include
|
|
14
|
+
* pipeline and file repo migrations directly in the backend.
|
|
15
|
+
*/
|
|
16
|
+
export function registerMigrationHandlers(context: PipelabContext) {
|
|
17
|
+
const { handle } = useAPI();
|
|
18
|
+
const { logger } = useLogger();
|
|
19
|
+
|
|
20
|
+
handle("config:load", async (_, { send, value }) => {
|
|
21
|
+
const { config: name } = value;
|
|
22
|
+
logger().info("[CLI Migration] config:load", name);
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
// Determine which migrator to use
|
|
26
|
+
let migrator: any = null;
|
|
27
|
+
if (name === "projects") {
|
|
28
|
+
migrator = fileRepoMigrations;
|
|
29
|
+
} else if (name === "settings") {
|
|
30
|
+
migrator = appSettingsMigrator;
|
|
31
|
+
} else if (name.startsWith("pipeline-") || name.endsWith(".plb")) {
|
|
32
|
+
migrator = savedFileMigrator;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!migrator) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`No migrator found for configuration: ${name}. All files loaded via config:load MUST have a migration schema.`,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// We use a custom loading logic that applies the migrator
|
|
42
|
+
// Since setupConfigFile in core-node expects the migrator to be in the configRegistry,
|
|
43
|
+
// and we want to keep migrations in the CLI package, we'll manually apply them here.
|
|
44
|
+
|
|
45
|
+
const manager = await setupConfigFile(name, { context, migrator });
|
|
46
|
+
const json = await manager.getConfig();
|
|
47
|
+
|
|
48
|
+
send({
|
|
49
|
+
type: "end",
|
|
50
|
+
data: {
|
|
51
|
+
type: "success",
|
|
52
|
+
result: {
|
|
53
|
+
result: json,
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
} catch (e) {
|
|
58
|
+
logger().error(`[CLI Migration] config:load error for ${name}:`, e);
|
|
59
|
+
send({
|
|
60
|
+
type: "end",
|
|
61
|
+
data: {
|
|
62
|
+
type: "error",
|
|
63
|
+
ipcError: e instanceof Error ? e.message : `Unable to load config ${name}`,
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// We also need to override config:save if we want to ensure consistency,
|
|
70
|
+
// although mostly we just care about migrations on load.
|
|
71
|
+
// The default config:save from core-node will work fine as it uses the same setupConfigFile logic.
|
|
72
|
+
}
|
package/src/paths.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./context";
|