@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,360 @@
|
|
|
1
|
+
import { PipelabContext } from "../context";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { writeFile, readFile, unlink, mkdir, stat, readdir } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { BuildHistoryEntry, IBuildHistoryStorage, AppConfig } from "@pipelab/shared";
|
|
6
|
+
import { useLogger } from "@pipelab/shared";
|
|
7
|
+
import { setupConfigFile } from "../config";
|
|
8
|
+
|
|
9
|
+
// Simplified storage - one file per pipeline containing array of build entries
|
|
10
|
+
|
|
11
|
+
export class BuildHistoryStorage implements IBuildHistoryStorage {
|
|
12
|
+
private logger = useLogger();
|
|
13
|
+
|
|
14
|
+
constructor(private context: PipelabContext) {
|
|
15
|
+
// Simple initialization - no complex setup needed
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
private getStoragePath() {
|
|
19
|
+
return join(this.context.userDataPath, "build-history");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private getPipelinePath(pipelineId: string): string {
|
|
23
|
+
// Sanitize the pipelineId to create a valid filename
|
|
24
|
+
// Replace invalid filename characters with underscores
|
|
25
|
+
const sanitizedId = pipelineId
|
|
26
|
+
.replace(/[/\:*?"<>|]/g, "_")
|
|
27
|
+
.replace(/__/g, "_") // Replace multiple underscores with single
|
|
28
|
+
.replace(/^_+|_+$/g, ""); // Remove leading/trailing underscores
|
|
29
|
+
|
|
30
|
+
return join(this.getStoragePath(), `pipeline-${sanitizedId}.json`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private async ensureStoragePath(): Promise<void> {
|
|
34
|
+
try {
|
|
35
|
+
await mkdir(this.getStoragePath(), { recursive: true });
|
|
36
|
+
} catch (error) {
|
|
37
|
+
this.logger.logger().error("Failed to create storage path:", error);
|
|
38
|
+
throw new Error(`Failed to create storage directory: ${error}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private async loadPipelineHistory(pipelineId: string): Promise<BuildHistoryEntry[]> {
|
|
43
|
+
try {
|
|
44
|
+
const pipelinePath = this.getPipelinePath(pipelineId);
|
|
45
|
+
const data = await readFile(pipelinePath, "utf-8");
|
|
46
|
+
return JSON.parse(data);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
// File doesn't exist or is corrupted, return empty array
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private async savePipelineHistory(
|
|
54
|
+
pipelineId: string,
|
|
55
|
+
entries: BuildHistoryEntry[],
|
|
56
|
+
): Promise<void> {
|
|
57
|
+
try {
|
|
58
|
+
await this.ensureStoragePath();
|
|
59
|
+
const pipelinePath = this.getPipelinePath(pipelineId);
|
|
60
|
+
await writeFile(pipelinePath, JSON.stringify(entries, null, 2), "utf-8");
|
|
61
|
+
} catch (error) {
|
|
62
|
+
this.logger.logger().error("Failed to save pipeline history:", error);
|
|
63
|
+
throw new Error(`Failed to save pipeline history: ${error}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async applyRetentionPolicy(): Promise<void> {
|
|
68
|
+
try {
|
|
69
|
+
this.logger.logger().info("Applying build history retention policy...");
|
|
70
|
+
const settings = await setupConfigFile<AppConfig>("settings", { context: this.context });
|
|
71
|
+
const config = await settings.getConfig();
|
|
72
|
+
const policy = config?.buildHistory?.retentionPolicy;
|
|
73
|
+
|
|
74
|
+
if (!policy || !policy.enabled) {
|
|
75
|
+
this.logger.logger().info("Retention policy is disabled. Skipping.");
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const { maxAge, maxEntries } = policy;
|
|
80
|
+
const pipelineFiles = await this.getAllPipelineFiles();
|
|
81
|
+
|
|
82
|
+
for (const file of pipelineFiles) {
|
|
83
|
+
const pipelineId = file.replace("pipeline-", "").replace(".json", "");
|
|
84
|
+
let entries = await this.loadPipelineHistory(pipelineId);
|
|
85
|
+
const originalCount = entries.length;
|
|
86
|
+
|
|
87
|
+
// 1. Filter by maxAge
|
|
88
|
+
if (maxAge > 0) {
|
|
89
|
+
const minDate = Date.now() - maxAge * 24 * 60 * 60 * 1000;
|
|
90
|
+
entries = entries.filter((entry) => entry.createdAt >= minDate);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 2. Filter by maxEntries (sort by date first to keep the newest)
|
|
94
|
+
if (maxEntries > 0 && entries.length > maxEntries) {
|
|
95
|
+
entries = entries
|
|
96
|
+
.sort((a, b) => b.createdAt - a.createdAt)
|
|
97
|
+
.slice(0, maxEntries);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (entries.length < originalCount) {
|
|
101
|
+
this.logger
|
|
102
|
+
.logger()
|
|
103
|
+
.info(
|
|
104
|
+
`[${pipelineId}] Pruned ${originalCount - entries.length} history entries.`,
|
|
105
|
+
);
|
|
106
|
+
await this.savePipelineHistory(pipelineId, entries);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
this.logger.logger().info("Retention policy applied successfully.");
|
|
110
|
+
} catch (error) {
|
|
111
|
+
this.logger.logger().error("Failed to apply retention policy:", error);
|
|
112
|
+
// We don't re-throw here as this is a background task and shouldn't crash the app
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async save(entry: BuildHistoryEntry): Promise<void> {
|
|
117
|
+
try {
|
|
118
|
+
const entries = await this.loadPipelineHistory(entry.pipelineId);
|
|
119
|
+
const existingIndex = entries.findIndex((e) => e.id === entry.id);
|
|
120
|
+
|
|
121
|
+
if (existingIndex >= 0) {
|
|
122
|
+
entries[existingIndex] = entry;
|
|
123
|
+
} else {
|
|
124
|
+
entries.push(entry);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
await this.savePipelineHistory(entry.pipelineId, entries);
|
|
128
|
+
this.logger
|
|
129
|
+
.logger()
|
|
130
|
+
.info(`Saved build history entry: ${entry.id} for pipeline: ${entry.pipelineId}`);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
this.logger.logger().error("Failed to save build history entry:", error);
|
|
133
|
+
throw new Error(`Failed to save build history entry: ${error}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async get(id: string, pipelineId?: string): Promise<BuildHistoryEntry | undefined> {
|
|
138
|
+
try {
|
|
139
|
+
if (pipelineId) {
|
|
140
|
+
const entries = await this.loadPipelineHistory(pipelineId);
|
|
141
|
+
return entries.find((e) => e.id === id);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const files = await this.getAllPipelineFiles();
|
|
145
|
+
for (const file of files) {
|
|
146
|
+
const pId = file.replace("pipeline-", "").replace(".json", "");
|
|
147
|
+
const entries = await this.loadPipelineHistory(pId);
|
|
148
|
+
const entry = entries.find((e) => e.id === id);
|
|
149
|
+
if (entry) return entry;
|
|
150
|
+
}
|
|
151
|
+
return undefined;
|
|
152
|
+
} catch (error) {
|
|
153
|
+
this.logger.logger().error(`Failed to get build history entry ${id}:`, error);
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async getAll(): Promise<BuildHistoryEntry[]> {
|
|
159
|
+
try {
|
|
160
|
+
const files = await this.getAllPipelineFiles();
|
|
161
|
+
const allEntries: BuildHistoryEntry[] = [];
|
|
162
|
+
for (const file of files) {
|
|
163
|
+
const pipelineId = file.replace("pipeline-", "").replace(".json", "");
|
|
164
|
+
const entries = await this.loadPipelineHistory(pipelineId);
|
|
165
|
+
allEntries.push(...entries);
|
|
166
|
+
}
|
|
167
|
+
return allEntries.sort((a, b) => b.createdAt - a.createdAt);
|
|
168
|
+
} catch (error) {
|
|
169
|
+
this.logger.logger().error("Failed to get all build history entries:", error);
|
|
170
|
+
throw new Error(`Failed to get all build history entries: ${error}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async getByPipeline(pipelineId: string): Promise<BuildHistoryEntry[]> {
|
|
175
|
+
try {
|
|
176
|
+
const entries = await this.loadPipelineHistory(pipelineId);
|
|
177
|
+
return entries.sort((a, b) => b.createdAt - a.createdAt);
|
|
178
|
+
} catch (error) {
|
|
179
|
+
this.logger.logger().error(`Failed to get build history for pipeline ${pipelineId}:`, error);
|
|
180
|
+
throw new Error(`Failed to get build history for pipeline: ${error}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async update(
|
|
185
|
+
id: string,
|
|
186
|
+
updates: Partial<BuildHistoryEntry>,
|
|
187
|
+
pipelineId?: string,
|
|
188
|
+
): Promise<void> {
|
|
189
|
+
try {
|
|
190
|
+
if (pipelineId) {
|
|
191
|
+
const entries = await this.loadPipelineHistory(pipelineId);
|
|
192
|
+
const entryIndex = entries.findIndex((e) => e.id === id);
|
|
193
|
+
if (entryIndex >= 0) {
|
|
194
|
+
entries[entryIndex] = { ...entries[entryIndex], ...updates, updatedAt: Date.now() };
|
|
195
|
+
await this.savePipelineHistory(pipelineId, entries);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
} else {
|
|
199
|
+
const files = await this.getAllPipelineFiles();
|
|
200
|
+
for (const file of files) {
|
|
201
|
+
const pId = file.replace("pipeline-", "").replace(".json", "");
|
|
202
|
+
const entries = await this.loadPipelineHistory(pId);
|
|
203
|
+
const entryIndex = entries.findIndex((e) => e.id === id);
|
|
204
|
+
if (entryIndex >= 0) {
|
|
205
|
+
entries[entryIndex] = { ...entries[entryIndex], ...updates, updatedAt: Date.now() };
|
|
206
|
+
await this.savePipelineHistory(pId, entries);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
throw new Error(`Build history entry ${id} not found`);
|
|
212
|
+
} catch (error) {
|
|
213
|
+
this.logger.logger().error(`Failed to update build history entry ${id}:`, error);
|
|
214
|
+
throw new Error(`Failed to update build history entry: ${error}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async delete(id: string, pipelineId?: string): Promise<void> {
|
|
219
|
+
try {
|
|
220
|
+
if (pipelineId) {
|
|
221
|
+
const entries = await this.loadPipelineHistory(pipelineId);
|
|
222
|
+
const entryIndex = entries.findIndex((e) => e.id === id);
|
|
223
|
+
if (entryIndex >= 0) {
|
|
224
|
+
entries.splice(entryIndex, 1);
|
|
225
|
+
await this.savePipelineHistory(pipelineId, entries);
|
|
226
|
+
this.logger.logger().info(`Deleted build history entry: ${id}`);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
} else {
|
|
230
|
+
const files = await this.getAllPipelineFiles();
|
|
231
|
+
for (const file of files) {
|
|
232
|
+
const pId = file.replace("pipeline-", "").replace(".json", "");
|
|
233
|
+
const entries = await this.loadPipelineHistory(pId);
|
|
234
|
+
const entryIndex = entries.findIndex((e) => e.id === id);
|
|
235
|
+
if (entryIndex >= 0) {
|
|
236
|
+
entries.splice(entryIndex, 1);
|
|
237
|
+
await this.savePipelineHistory(pId, entries);
|
|
238
|
+
this.logger.logger().info(`Deleted build history entry: ${id}`);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
this.logger.logger().info(`Build history entry ${id} not found for deletion`);
|
|
244
|
+
} catch (error) {
|
|
245
|
+
this.logger.logger().error(`Failed to delete build history entry ${id}:`, error);
|
|
246
|
+
throw new Error(`Failed to delete build history entry: ${error}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async clear(): Promise<void> {
|
|
251
|
+
try {
|
|
252
|
+
await this.ensureStoragePath();
|
|
253
|
+
const files = await this.getAllPipelineFiles();
|
|
254
|
+
await Promise.all(files.map((file) => unlink(join(this.getStoragePath(), file))));
|
|
255
|
+
this.logger.logger().info("Cleared all build history");
|
|
256
|
+
} catch (error) {
|
|
257
|
+
this.logger.logger().error("Failed to clear build history:", error);
|
|
258
|
+
throw new Error(`Failed to clear build history: ${error}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async clearByPipeline(pipelineId: string): Promise<void> {
|
|
263
|
+
try {
|
|
264
|
+
const pipelinePath = this.getPipelinePath(pipelineId);
|
|
265
|
+
await unlink(pipelinePath);
|
|
266
|
+
this.logger.logger().info(`Cleared history for pipeline "${pipelineId}"`);
|
|
267
|
+
} catch (error: any) {
|
|
268
|
+
if (error.code === 'ENOENT') {
|
|
269
|
+
this.logger.logger().warn(`No history file found for pipeline "${pipelineId}". Nothing to clear.`);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
this.logger.logger().error(`Failed to clear history for pipeline "${pipelineId}":`, error);
|
|
273
|
+
throw new Error(`Failed to clear history for pipeline: ${error}`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async getStorageInfo(): Promise<{
|
|
278
|
+
totalEntries: number;
|
|
279
|
+
totalSize: number;
|
|
280
|
+
oldestEntry?: number;
|
|
281
|
+
newestEntry?: number;
|
|
282
|
+
numberOfPipelines: number;
|
|
283
|
+
cachePath: string;
|
|
284
|
+
userDataPath: string;
|
|
285
|
+
retentionPolicy: {
|
|
286
|
+
enabled: boolean;
|
|
287
|
+
maxEntries: number;
|
|
288
|
+
maxAge: number;
|
|
289
|
+
};
|
|
290
|
+
}> {
|
|
291
|
+
try {
|
|
292
|
+
const allEntries = await this.getAll();
|
|
293
|
+
const files = await this.getAllPipelineFiles();
|
|
294
|
+
|
|
295
|
+
const settings = await setupConfigFile<AppConfig>("settings", { context: this.context });
|
|
296
|
+
const config = await settings.getConfig();
|
|
297
|
+
const policy = config?.buildHistory?.retentionPolicy || {
|
|
298
|
+
enabled: false,
|
|
299
|
+
maxEntries: 0,
|
|
300
|
+
maxAge: 0,
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
if (allEntries.length === 0) {
|
|
304
|
+
return {
|
|
305
|
+
totalEntries: 0,
|
|
306
|
+
totalSize: 0,
|
|
307
|
+
numberOfPipelines: files.length,
|
|
308
|
+
cachePath: config?.cacheFolder || tmpdir(),
|
|
309
|
+
userDataPath: this.context.userDataPath,
|
|
310
|
+
retentionPolicy: {
|
|
311
|
+
enabled: policy.enabled,
|
|
312
|
+
maxEntries: policy.maxEntries,
|
|
313
|
+
maxAge: policy.maxAge,
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
let totalSize = 0;
|
|
319
|
+
try {
|
|
320
|
+
for (const file of files) {
|
|
321
|
+
const filePath = join(this.getStoragePath(), file);
|
|
322
|
+
const stats = await stat(filePath);
|
|
323
|
+
totalSize += stats.size;
|
|
324
|
+
}
|
|
325
|
+
} catch (error) {
|
|
326
|
+
totalSize = allEntries.length * 1024; // Rough estimate
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const sortedEntries = allEntries.sort((a, b) => a.createdAt - b.createdAt);
|
|
330
|
+
|
|
331
|
+
return {
|
|
332
|
+
totalEntries: allEntries.length,
|
|
333
|
+
totalSize,
|
|
334
|
+
oldestEntry: sortedEntries[0]?.createdAt,
|
|
335
|
+
newestEntry: sortedEntries[sortedEntries.length - 1]?.createdAt,
|
|
336
|
+
numberOfPipelines: files.length,
|
|
337
|
+
cachePath: config?.cacheFolder || tmpdir(),
|
|
338
|
+
userDataPath: this.context.userDataPath,
|
|
339
|
+
retentionPolicy: {
|
|
340
|
+
enabled: policy.enabled,
|
|
341
|
+
maxEntries: policy.maxEntries,
|
|
342
|
+
maxAge: policy.maxAge,
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
} catch (error) {
|
|
346
|
+
this.logger.logger().error("Failed to get storage info:", error);
|
|
347
|
+
throw new Error(`Failed to get storage info: ${error}`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
private async getAllPipelineFiles(): Promise<string[]> {
|
|
352
|
+
try {
|
|
353
|
+
await this.ensureStoragePath();
|
|
354
|
+
const files = await readdir(this.getStoragePath());
|
|
355
|
+
return files.filter((file) => file.startsWith("pipeline-") && file.endsWith(".json"));
|
|
356
|
+
} catch (error) {
|
|
357
|
+
return [];
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { useAPI } from "../ipc-core";
|
|
2
|
+
import { useLogger, configRegistry } from "@pipelab/shared";
|
|
3
|
+
import { setupConfigFile, getMigrator } from "../config";
|
|
4
|
+
import { PipelabContext } from "../context";
|
|
5
|
+
|
|
6
|
+
export const registerConfigHandlers = (context: PipelabContext) => {
|
|
7
|
+
const { handle } = useAPI();
|
|
8
|
+
const { logger } = useLogger();
|
|
9
|
+
|
|
10
|
+
handle("config:load", async (_, { send, value }) => {
|
|
11
|
+
const { config: name } = value;
|
|
12
|
+
logger().info("config:load", name);
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const manager = await setupConfigFile(name, { context });
|
|
16
|
+
const json = await manager.getConfig();
|
|
17
|
+
|
|
18
|
+
send({
|
|
19
|
+
type: "end",
|
|
20
|
+
data: {
|
|
21
|
+
type: "success",
|
|
22
|
+
result: {
|
|
23
|
+
result: json,
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
} catch (e) {
|
|
28
|
+
logger().error(`config:load error for ${name}:`, e);
|
|
29
|
+
send({
|
|
30
|
+
type: "end",
|
|
31
|
+
data: {
|
|
32
|
+
type: "error",
|
|
33
|
+
ipcError: e instanceof Error ? e.message : `Unable to load config ${name}`,
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
handle("config:save", async (_, { send, value }) => {
|
|
40
|
+
const { data, config: name } = value;
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const manager = await setupConfigFile(name, { context });
|
|
44
|
+
const json = typeof data === "string" ? JSON.parse(data) : data;
|
|
45
|
+
await manager.setConfig(json);
|
|
46
|
+
|
|
47
|
+
send({
|
|
48
|
+
type: "end",
|
|
49
|
+
data: {
|
|
50
|
+
type: "success",
|
|
51
|
+
result: {
|
|
52
|
+
result: "ok",
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
} catch (e) {
|
|
57
|
+
logger().error(`config:save error for ${name}:`, e);
|
|
58
|
+
send({
|
|
59
|
+
type: "end",
|
|
60
|
+
data: {
|
|
61
|
+
type: "error",
|
|
62
|
+
ipcError: e instanceof Error ? e.message : `Unable to save config ${name}`,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
handle("config:reset", async (event, { value, send }) => {
|
|
69
|
+
const { config: name, key } = value;
|
|
70
|
+
logger().info("config:reset", name, key);
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const manager = await setupConfigFile(name, { context });
|
|
74
|
+
const currentConfig = await manager.getConfig();
|
|
75
|
+
|
|
76
|
+
const migrator = getMigrator(name);
|
|
77
|
+
|
|
78
|
+
if (!migrator) {
|
|
79
|
+
throw new Error(`No migrator found for configuration: ${name}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const defaultValue = (migrator.defaultValue as any)[key];
|
|
83
|
+
|
|
84
|
+
await manager.setConfig({
|
|
85
|
+
...(currentConfig ? (currentConfig as any) : {}),
|
|
86
|
+
[key]: defaultValue,
|
|
87
|
+
} as any);
|
|
88
|
+
|
|
89
|
+
send({
|
|
90
|
+
type: "end",
|
|
91
|
+
data: {
|
|
92
|
+
type: "success",
|
|
93
|
+
result: {
|
|
94
|
+
result: "ok",
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
} catch (e) {
|
|
99
|
+
logger().error(`config:reset error for ${name}:`, e);
|
|
100
|
+
send({
|
|
101
|
+
type: "end",
|
|
102
|
+
data: {
|
|
103
|
+
type: "error",
|
|
104
|
+
ipcError: e instanceof Error ? e.message : `Unable to reset config ${name}`,
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
};
|
|
@@ -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
|
+
};
|