@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
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pipelab/core-node",
|
|
3
|
+
"version": "1.0.1-beta.23",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "The Pipelab automation engine for Node.js",
|
|
6
|
+
"license": "FSL-1.1-MIT",
|
|
7
|
+
"author": "CynToolkit",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/CynToolkit/pipelab.git",
|
|
11
|
+
"directory": "packages/core-node"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "./dist/index.cjs",
|
|
15
|
+
"module": "./dist/index.mjs",
|
|
16
|
+
"types": "./dist/index.d.mts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.mts",
|
|
20
|
+
"import": "./dist/index.mjs",
|
|
21
|
+
"require": "./dist/index.cjs",
|
|
22
|
+
"default": "./dist/index.mjs"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"adm-zip": "0.5.16",
|
|
30
|
+
"archiver": "7.0.1",
|
|
31
|
+
"es-toolkit": "1.25.2",
|
|
32
|
+
"execa": "9.5.2",
|
|
33
|
+
"nanoid": "5.0.7",
|
|
34
|
+
"pacote": "21.5.0",
|
|
35
|
+
"semver": "^7.7.4",
|
|
36
|
+
"slash": "3.0.0",
|
|
37
|
+
"tar": "6.2.1",
|
|
38
|
+
"type-fest": "4.39.0",
|
|
39
|
+
"ws": "8.18.3",
|
|
40
|
+
"yauzl": "2.10.0",
|
|
41
|
+
"@pipelab/constants": "1.0.1-beta.19",
|
|
42
|
+
"@pipelab/shared": "2.0.1-beta.20"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/adm-zip": "0.5.7",
|
|
46
|
+
"@types/archiver": "7.0.0",
|
|
47
|
+
"@types/node": "24.12.2",
|
|
48
|
+
"@types/pacote": "11.1.8",
|
|
49
|
+
"@types/semver": "^7.7.1",
|
|
50
|
+
"@types/tar": "7.0.87",
|
|
51
|
+
"@types/ws": "8.18.1",
|
|
52
|
+
"@types/yauzl": "2.10.3",
|
|
53
|
+
"tsdown": "0.21.2",
|
|
54
|
+
"typescript": "^5.0.0",
|
|
55
|
+
"@pipelab/tsconfig": "1.0.1-beta.19"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "tsdown",
|
|
59
|
+
"format": "oxfmt .",
|
|
60
|
+
"lint": "oxlint ."
|
|
61
|
+
}
|
|
62
|
+
}
|
package/src/api.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { nanoid } from "nanoid";
|
|
2
|
+
import { klona } from "klona";
|
|
3
|
+
import { toRaw } from "vue";
|
|
4
|
+
|
|
5
|
+
import type { Tagged } from "type-fest";
|
|
6
|
+
import { ILogObjMeta } from "tslog";
|
|
7
|
+
import { useLogger } from "@pipelab/shared";
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
UpdateStatus,
|
|
11
|
+
RendererChannels,
|
|
12
|
+
RendererData,
|
|
13
|
+
RendererEvents,
|
|
14
|
+
RendererEnd,
|
|
15
|
+
RendererMessage,
|
|
16
|
+
RequestId,
|
|
17
|
+
HandleListenerRendererSendFn,
|
|
18
|
+
HandleListenerRenderer as BaseHandleListenerRenderer,
|
|
19
|
+
} from "@pipelab/shared";
|
|
20
|
+
|
|
21
|
+
export type {
|
|
22
|
+
UpdateStatus,
|
|
23
|
+
RendererChannels,
|
|
24
|
+
RendererData,
|
|
25
|
+
RendererEvents,
|
|
26
|
+
RendererEnd,
|
|
27
|
+
RendererMessage,
|
|
28
|
+
RequestId,
|
|
29
|
+
HandleListenerRendererSendFn,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type HandleListenerRenderer<KEY extends RendererChannels> = (
|
|
33
|
+
event: Electron.IpcMainInvokeEvent,
|
|
34
|
+
data: { value: RendererData<KEY>; send: HandleListenerRendererSendFn<KEY> },
|
|
35
|
+
) => Promise<void>;
|
|
36
|
+
|
|
37
|
+
export type ListenerMain<KEY extends RendererChannels> = (
|
|
38
|
+
event: Electron.IpcMainEvent,
|
|
39
|
+
data: RendererEvents<KEY>,
|
|
40
|
+
) => Promise<void>;
|
|
41
|
+
|
|
42
|
+
export const usePluginAPI = (browserWindow: any) => {
|
|
43
|
+
const { logger } = useLogger();
|
|
44
|
+
/**
|
|
45
|
+
* Send an order
|
|
46
|
+
*/
|
|
47
|
+
const send = <KEY extends RendererChannels>(channel: KEY, args?: RendererData<KEY>) => {
|
|
48
|
+
if (!browserWindow || browserWindow.isDestroyed()) return;
|
|
49
|
+
browserWindow.webContents.send(channel, args);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const on = <KEY extends RendererChannels>(
|
|
53
|
+
channel: KEY | string,
|
|
54
|
+
listener: (event: any, data: RendererEvents<KEY>) => void,
|
|
55
|
+
) => {
|
|
56
|
+
if (!browserWindow || browserWindow.isDestroyed()) return () => {};
|
|
57
|
+
const ipcMain = browserWindow.webContents.ipc.on(channel, listener);
|
|
58
|
+
|
|
59
|
+
const cancel = () => {
|
|
60
|
+
if (browserWindow.isDestroyed()) return;
|
|
61
|
+
ipcMain.removeListener(channel, listener);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return cancel;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Send an order and wait for it's execution
|
|
69
|
+
*/
|
|
70
|
+
const execute = async <KEY extends RendererChannels>(
|
|
71
|
+
channel: KEY,
|
|
72
|
+
data?: RendererData<KEY>,
|
|
73
|
+
listener?: ListenerMain<KEY>,
|
|
74
|
+
) => {
|
|
75
|
+
const newId = nanoid() as RequestId;
|
|
76
|
+
// eslint-disable-next-line no-async-promise-executor
|
|
77
|
+
return new Promise<RendererEnd<KEY>>(async (resolve, reject) => {
|
|
78
|
+
const message: RendererMessage = {
|
|
79
|
+
requestId: newId,
|
|
80
|
+
data: toRaw(klona(data)),
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
if (!browserWindow || browserWindow.isDestroyed()) {
|
|
84
|
+
return reject(new Error("Browser window is destroyed"));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const cancel = on(newId, async (event, data) => {
|
|
88
|
+
// console.log('receiving event', event, data)
|
|
89
|
+
if (data.type === "end") {
|
|
90
|
+
cancel();
|
|
91
|
+
return resolve(data.data);
|
|
92
|
+
} else {
|
|
93
|
+
await listener?.(event, data);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// send the message
|
|
98
|
+
try {
|
|
99
|
+
browserWindow.webContents.send(channel, message);
|
|
100
|
+
} catch (e) {
|
|
101
|
+
logger().error(e);
|
|
102
|
+
logger().error(channel, message);
|
|
103
|
+
reject(e);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
send,
|
|
110
|
+
on,
|
|
111
|
+
execute,
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export type UseMainAPI = ReturnType<typeof usePluginAPI>;
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { PipelabContext } from "./context";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { ensure } from "./utils/fs-extras";
|
|
4
|
+
import fs from "node:fs/promises";
|
|
5
|
+
import { useLogger } from "@pipelab/shared";
|
|
6
|
+
import { configRegistry, Migrator } from "@pipelab/shared";
|
|
7
|
+
|
|
8
|
+
export const getMigrator = <T>(name: string) => {
|
|
9
|
+
if (configRegistry[name]) {
|
|
10
|
+
return configRegistry[name] as Migrator<T>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (name.startsWith("pipeline-")) {
|
|
14
|
+
return configRegistry["pipeline"] as Migrator<T>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return undefined;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const setupConfigFile = async <T>(
|
|
21
|
+
name: string,
|
|
22
|
+
options: { context: PipelabContext; migrator?: Migrator<T> },
|
|
23
|
+
) => {
|
|
24
|
+
const ctx = options.context;
|
|
25
|
+
const migrator = options.migrator || getMigrator<T>(name);
|
|
26
|
+
|
|
27
|
+
if (!migrator) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
`No migrator found for configuration: ${name}. All managed files must have a migration schema.`,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const isAbsolutePath = path.isAbsolute(name);
|
|
34
|
+
const filesPath = isAbsolutePath ? name : ctx.getConfigPath(`${name}.json`);
|
|
35
|
+
|
|
36
|
+
await ensure(filesPath, JSON.stringify(migrator.defaultValue));
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
setConfig: async (config: T) => {
|
|
40
|
+
const { logger } = useLogger();
|
|
41
|
+
try {
|
|
42
|
+
await fs.writeFile(filesPath, JSON.stringify(config));
|
|
43
|
+
return true;
|
|
44
|
+
} catch (e) {
|
|
45
|
+
logger().error(`Error saving config ${name}:`, e);
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
getConfig: async () => {
|
|
50
|
+
const { logger } = useLogger();
|
|
51
|
+
let content = undefined;
|
|
52
|
+
let originalJson: any = undefined;
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
content = await fs.readFile(filesPath, "utf8");
|
|
56
|
+
if (content !== undefined) {
|
|
57
|
+
originalJson = JSON.parse(content);
|
|
58
|
+
}
|
|
59
|
+
} catch (e) {
|
|
60
|
+
logger().error(`Error reading or parsing config ${name}:`, e);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// console.log("content", content);
|
|
64
|
+
|
|
65
|
+
let json: any = undefined;
|
|
66
|
+
try {
|
|
67
|
+
json = await migrator.migrate(originalJson, {
|
|
68
|
+
debug: false,
|
|
69
|
+
});
|
|
70
|
+
} catch (e) {
|
|
71
|
+
logger().error(`Error migrating config ${name}:`, e);
|
|
72
|
+
json = migrator.defaultValue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const originalVersion = originalJson?.version;
|
|
76
|
+
const newVersion = json?.version;
|
|
77
|
+
|
|
78
|
+
// Check if migration actually changed the version
|
|
79
|
+
if (originalVersion !== newVersion && content !== undefined) {
|
|
80
|
+
// Backup previous file before overwriting
|
|
81
|
+
const parsedPath = path.parse(filesPath);
|
|
82
|
+
const versionSuffix = originalVersion || "unknown";
|
|
83
|
+
const backupPath = path.join(parsedPath.dir, `${parsedPath.name}.${versionSuffix}.bak`);
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
await fs.copyFile(filesPath, backupPath);
|
|
87
|
+
logger().info(`Backup created for ${name} at ${backupPath} (version ${versionSuffix})`);
|
|
88
|
+
} catch (e) {
|
|
89
|
+
logger().error(`Failed to create backup for ${name}:`, e);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Save back migrated config if changed or if it's a new file
|
|
94
|
+
if (originalVersion !== newVersion || content === undefined) {
|
|
95
|
+
try {
|
|
96
|
+
await fs.writeFile(filesPath, JSON.stringify(json));
|
|
97
|
+
} catch (e) {
|
|
98
|
+
logger().error(`Error saving migrated config ${name}:`, e);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return json as T;
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
};
|
package/src/context.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { join, dirname, resolve } from "node:path";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
|
|
6
|
+
const _dirname =
|
|
7
|
+
typeof __dirname !== "undefined"
|
|
8
|
+
? __dirname
|
|
9
|
+
: typeof import.meta !== "undefined" && import.meta.url
|
|
10
|
+
? dirname(fileURLToPath(import.meta.url))
|
|
11
|
+
: process.cwd();
|
|
12
|
+
|
|
13
|
+
export const isDev = process.env.NODE_ENV === "development";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Finds the monorepo root by looking for pnpm-workspace.yaml.
|
|
17
|
+
*/
|
|
18
|
+
function findProjectRoot(startDir: string): string | null {
|
|
19
|
+
let curr = startDir;
|
|
20
|
+
while (curr !== dirname(curr)) {
|
|
21
|
+
if (existsSync(join(curr, "pnpm-workspace.yaml"))) {
|
|
22
|
+
return curr;
|
|
23
|
+
}
|
|
24
|
+
curr = dirname(curr);
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const projectRoot = findProjectRoot(_dirname);
|
|
30
|
+
|
|
31
|
+
export interface PipelabContextOptions {
|
|
32
|
+
userDataPath: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class PipelabContext {
|
|
36
|
+
public readonly userDataPath: string;
|
|
37
|
+
|
|
38
|
+
constructor(options: PipelabContextOptions) {
|
|
39
|
+
this.userDataPath = options.userDataPath;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
getPackagesPath(...subpaths: string[]) {
|
|
43
|
+
return join(this.userDataPath, "packages", ...subpaths);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
getThirdPartyPath(...subpaths: string[]) {
|
|
47
|
+
return join(this.userDataPath, "thirdparty", ...subpaths);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
getConfigPath(...subpaths: string[]) {
|
|
51
|
+
return join(this.userDataPath, "config", ...subpaths);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { End } from "@pipelab/shared";
|
|
2
|
+
import { ensureNodeJS, ensurePNPM } from "./utils/remote";
|
|
3
|
+
import { Action, ActionRunner, Condition, ConditionRunner } from "./types/runner";
|
|
4
|
+
import { InputsDefinition } from "@pipelab/shared";
|
|
5
|
+
import { usePlugins } from "@pipelab/shared";
|
|
6
|
+
import { isRequired } from "@pipelab/shared";
|
|
7
|
+
import { mkdir, stat } from "node:fs/promises";
|
|
8
|
+
import { PipelabContext } from "./context";
|
|
9
|
+
import { useLogger } from "@pipelab/shared";
|
|
10
|
+
import { BlockCondition } from "@pipelab/shared";
|
|
11
|
+
import { HandleListenerSendFn } from "./handlers";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
const { join } = path;
|
|
14
|
+
|
|
15
|
+
const checkParams = (definitionParams: InputsDefinition, elementParams: Record<string, string>) => {
|
|
16
|
+
// get a list of all required params
|
|
17
|
+
let expected = Object.keys(definitionParams);
|
|
18
|
+
|
|
19
|
+
const found: string[] = [];
|
|
20
|
+
// for each param in elementParams
|
|
21
|
+
for (const param of Object.keys(elementParams)) {
|
|
22
|
+
// if the param is in the expected list
|
|
23
|
+
if (expected.includes(param)) {
|
|
24
|
+
// add to found
|
|
25
|
+
found.push(param);
|
|
26
|
+
// remove from expected
|
|
27
|
+
expected = expected.filter((x) => x !== param);
|
|
28
|
+
} else {
|
|
29
|
+
// throw new Error('Unexpected param "' + param + '"')
|
|
30
|
+
console.warn('Unexpected param "' + param + '"');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
for (const param of expected) {
|
|
35
|
+
if (isRequired(definitionParams[param])) {
|
|
36
|
+
throw new Error('Missing param "' + param + '"');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const handleConditionExecute = async (
|
|
42
|
+
nodeId: string,
|
|
43
|
+
pluginId: string,
|
|
44
|
+
params: BlockCondition["params"],
|
|
45
|
+
cwd: string,
|
|
46
|
+
context: PipelabContext,
|
|
47
|
+
): Promise<End<"condition:execute">> => {
|
|
48
|
+
const ctx = context;
|
|
49
|
+
const { plugins } = usePlugins();
|
|
50
|
+
const { logger } = useLogger();
|
|
51
|
+
|
|
52
|
+
const node = plugins.value
|
|
53
|
+
.find((plugin) => plugin.id === pluginId)
|
|
54
|
+
?.nodes.find((node: any) => node.node.id === nodeId) as
|
|
55
|
+
| {
|
|
56
|
+
node: Condition;
|
|
57
|
+
runner: ConditionRunner<any>;
|
|
58
|
+
}
|
|
59
|
+
| undefined;
|
|
60
|
+
|
|
61
|
+
if (!node) {
|
|
62
|
+
return {
|
|
63
|
+
type: "error",
|
|
64
|
+
ipcError: "Node not found",
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const s = await stat(cwd);
|
|
70
|
+
if (!s.isDirectory()) {
|
|
71
|
+
throw new Error(`Execution directory "${cwd}" exists but is not a directory.`);
|
|
72
|
+
}
|
|
73
|
+
} catch (e: any) {
|
|
74
|
+
if (e.code === "ENOENT") {
|
|
75
|
+
await mkdir(cwd, { recursive: true });
|
|
76
|
+
} else {
|
|
77
|
+
throw e;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
checkParams(node.node.params, params);
|
|
83
|
+
const resolvedInputs = params; // await resolveConditionInputs(params, node.node, steps)
|
|
84
|
+
|
|
85
|
+
const result = await node.runner({
|
|
86
|
+
inputs: resolvedInputs,
|
|
87
|
+
log: (...args) => {
|
|
88
|
+
logger().info(`[${node.node.name}]`, ...args);
|
|
89
|
+
},
|
|
90
|
+
meta: {
|
|
91
|
+
definition: "",
|
|
92
|
+
},
|
|
93
|
+
setMeta: () => {
|
|
94
|
+
logger().info("set meta defined here");
|
|
95
|
+
},
|
|
96
|
+
cwd,
|
|
97
|
+
context: ctx,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
type: "success",
|
|
102
|
+
result: {
|
|
103
|
+
outputs: {},
|
|
104
|
+
value: result,
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
} catch (e) {
|
|
108
|
+
logger().error("Error in condition execution:", e);
|
|
109
|
+
return {
|
|
110
|
+
type: "error",
|
|
111
|
+
ipcError: String(e),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export const handleActionExecute = async (
|
|
117
|
+
nodeId: string,
|
|
118
|
+
pluginId: string,
|
|
119
|
+
params: Record<string, string>,
|
|
120
|
+
mainWindow: any | undefined,
|
|
121
|
+
send: HandleListenerSendFn<"action:execute">,
|
|
122
|
+
abortSignal: AbortSignal,
|
|
123
|
+
cwd: string,
|
|
124
|
+
cachePath: string,
|
|
125
|
+
context: PipelabContext,
|
|
126
|
+
): Promise<End<"action:execute">> => {
|
|
127
|
+
const ctx = context;
|
|
128
|
+
const { plugins } = usePlugins();
|
|
129
|
+
const { logger } = useLogger();
|
|
130
|
+
|
|
131
|
+
mainWindow?.setProgressBar(1, {
|
|
132
|
+
mode: "indeterminate",
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const node = plugins.value
|
|
136
|
+
.find((plugin) => plugin.id === pluginId)
|
|
137
|
+
?.nodes.find((node: any) => node.node.id === nodeId) as
|
|
138
|
+
| {
|
|
139
|
+
node: Action;
|
|
140
|
+
runner: ActionRunner<any>;
|
|
141
|
+
}
|
|
142
|
+
| undefined;
|
|
143
|
+
|
|
144
|
+
if (!node) {
|
|
145
|
+
mainWindow?.setProgressBar(1, { mode: "normal" });
|
|
146
|
+
return {
|
|
147
|
+
type: "error",
|
|
148
|
+
ipcError: "Node not found",
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const nodePath = await ensureNodeJS("24.14.1", { context: ctx });
|
|
153
|
+
const pnpm = await ensurePNPM("10.12.0", { context: ctx });
|
|
154
|
+
|
|
155
|
+
const outputs: Record<string, unknown> = {};
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
try {
|
|
159
|
+
const s = await stat(cwd);
|
|
160
|
+
if (!s.isDirectory()) {
|
|
161
|
+
throw new Error(`Execution directory "${cwd}" exists but is not a directory.`);
|
|
162
|
+
}
|
|
163
|
+
} catch (e: any) {
|
|
164
|
+
if (e.code === "ENOENT") {
|
|
165
|
+
await mkdir(cwd, { recursive: true });
|
|
166
|
+
} else {
|
|
167
|
+
throw e;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
checkParams(node.node.params, params);
|
|
171
|
+
const resolvedInputs = params; // await resolveActionInputs(params, node.node, steps)
|
|
172
|
+
logger().info("resolvedInputs", resolvedInputs);
|
|
173
|
+
|
|
174
|
+
await node.runner({
|
|
175
|
+
inputs: resolvedInputs,
|
|
176
|
+
log: (...args) => {
|
|
177
|
+
const decorator = `[${node.node.name}]`;
|
|
178
|
+
const logArgs = [decorator, ...args];
|
|
179
|
+
logger().info(...logArgs);
|
|
180
|
+
send({
|
|
181
|
+
type: "log",
|
|
182
|
+
data: {
|
|
183
|
+
decorator,
|
|
184
|
+
time: Date.now(),
|
|
185
|
+
message: args,
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
},
|
|
189
|
+
setOutput: (key: any, value: unknown) => {
|
|
190
|
+
outputs[key] = value;
|
|
191
|
+
},
|
|
192
|
+
meta: {
|
|
193
|
+
definition: "",
|
|
194
|
+
},
|
|
195
|
+
setMeta: () => {
|
|
196
|
+
logger().info("set meta defined here");
|
|
197
|
+
},
|
|
198
|
+
cwd: cwd,
|
|
199
|
+
paths: {
|
|
200
|
+
cache: cachePath,
|
|
201
|
+
node: nodePath,
|
|
202
|
+
pnpm,
|
|
203
|
+
modules: "",
|
|
204
|
+
userData: ctx.userDataPath,
|
|
205
|
+
thirdparty: ctx.getThirdPartyPath(),
|
|
206
|
+
},
|
|
207
|
+
browserWindow: mainWindow,
|
|
208
|
+
abortSignal,
|
|
209
|
+
context: ctx,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
mainWindow?.setProgressBar(1, { mode: "normal" });
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
type: "success",
|
|
216
|
+
result: { outputs, tmp: cwd },
|
|
217
|
+
};
|
|
218
|
+
} catch (e) {
|
|
219
|
+
logger().error("Error in action execution:", e);
|
|
220
|
+
mainWindow?.setProgressBar(1, { mode: "normal" });
|
|
221
|
+
|
|
222
|
+
// If aborted, throw AbortError to distinguish from actual errors
|
|
223
|
+
if (abortSignal.aborted) {
|
|
224
|
+
const abortError = new Error("Aborted");
|
|
225
|
+
abortError.name = "AbortError";
|
|
226
|
+
throw abortError;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
type: "error",
|
|
231
|
+
ipcError: String(e),
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { useAPI } from "../ipc-core";
|
|
2
|
+
import { useLogger } from "@pipelab/shared";
|
|
3
|
+
import { webSocketServer } from "../websocket-server";
|
|
4
|
+
import { PipelabContext } from "../context";
|
|
5
|
+
|
|
6
|
+
export const registerAgentsHandlers = (_context: PipelabContext) => {
|
|
7
|
+
const { handle } = useAPI();
|
|
8
|
+
const { logger } = useLogger();
|
|
9
|
+
|
|
10
|
+
handle("agents:get", async (event, { send }) => {
|
|
11
|
+
try {
|
|
12
|
+
const agents = webSocketServer.getAgents();
|
|
13
|
+
|
|
14
|
+
send({
|
|
15
|
+
type: "end",
|
|
16
|
+
data: {
|
|
17
|
+
type: "success",
|
|
18
|
+
result: { agents },
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
} catch (error) {
|
|
22
|
+
logger().error("Failed to get agents:", error);
|
|
23
|
+
send({
|
|
24
|
+
type: "end",
|
|
25
|
+
data: {
|
|
26
|
+
type: "error",
|
|
27
|
+
ipcError: error instanceof Error ? error.message : "Failed to get agents",
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { supabase, useLogger, isSupabaseAvailable } from "@pipelab/shared";
|
|
2
|
+
import { useAPI } from "../ipc-core";
|
|
3
|
+
import { JsonFileStorage } from "../utils/storage";
|
|
4
|
+
import { PipelabContext } from "../context";
|
|
5
|
+
import { webSocketServer } from "../websocket-server";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Registers authentication handlers for the CLI/system backend.
|
|
9
|
+
*/
|
|
10
|
+
export const registerAuthHandlers = (context: PipelabContext) => {
|
|
11
|
+
const { handle } = useAPI();
|
|
12
|
+
const { logger } = useLogger();
|
|
13
|
+
|
|
14
|
+
const supabaseAvailable = isSupabaseAvailable();
|
|
15
|
+
if (!supabaseAvailable) {
|
|
16
|
+
logger().warn("[Auth] Supabase is not available. Auth handlers will not be functional.");
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Initialize the singleton Supabase client for the backend with the custom FileStorage
|
|
21
|
+
const client = supabase({
|
|
22
|
+
auth: {
|
|
23
|
+
storage: new JsonFileStorage("auth-session.json", context),
|
|
24
|
+
persistSession: true,
|
|
25
|
+
autoRefreshToken: true,
|
|
26
|
+
detectSessionInUrl: false,
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
handle("auth:getUser", async (_, { send }) => {
|
|
31
|
+
try {
|
|
32
|
+
const { data, error } = await client.auth.getUser();
|
|
33
|
+
if (error) {
|
|
34
|
+
return send({ type: "end", data: { type: "success", result: { user: null } } });
|
|
35
|
+
}
|
|
36
|
+
return send({ type: "end", data: { type: "success", result: { user: data.user } } });
|
|
37
|
+
} catch (e) {
|
|
38
|
+
return send({ type: "end", data: { type: "success", result: { user: null } } });
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
handle("auth:signInWithPassword", async (_, { value, send }) => {
|
|
43
|
+
const { email, password } = value;
|
|
44
|
+
logger().info("[Auth] signInWithPassword:", email);
|
|
45
|
+
const result = await client.auth.signInWithPassword({ email, password });
|
|
46
|
+
return send({ type: "end", data: { type: "success", result: result as any } });
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
handle("auth:signUp", async (_, { value, send }) => {
|
|
50
|
+
const { email, password } = value;
|
|
51
|
+
logger().info("[Auth] signUp:", email);
|
|
52
|
+
const result = await client.auth.signUp({ email, password });
|
|
53
|
+
return send({ type: "end", data: { type: "success", result: result as any } });
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
handle("auth:signOut", async (_, { send }) => {
|
|
57
|
+
logger().info("[Auth] signOut");
|
|
58
|
+
await client.auth.signOut();
|
|
59
|
+
return send({ type: "end", data: { type: "success", result: undefined } });
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
handle("auth:resetPasswordForEmail", async (_, { value, send }) => {
|
|
63
|
+
const { email } = value;
|
|
64
|
+
logger().info("[Auth] resetPasswordForEmail:", email);
|
|
65
|
+
const { error } = await client.auth.resetPasswordForEmail(email);
|
|
66
|
+
return send({ type: "end", data: { type: "success", result: { error } } });
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
handle("auth:invoke", async (_, { value, send }) => {
|
|
70
|
+
const { name, options } = value;
|
|
71
|
+
logger().info("[Auth] invoke function:", name);
|
|
72
|
+
try {
|
|
73
|
+
const result = await client.functions.invoke(name, options);
|
|
74
|
+
return send({ type: "end", data: { type: "success", result } });
|
|
75
|
+
} catch (e) {
|
|
76
|
+
return send({
|
|
77
|
+
type: "end",
|
|
78
|
+
data: {
|
|
79
|
+
type: "error",
|
|
80
|
+
ipcError: e instanceof Error ? e.message : "Edge Function invocation failed",
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Maintain logs for the backend state change and broadcast to all clients
|
|
87
|
+
client.auth.onAuthStateChange(async (event, session) => {
|
|
88
|
+
logger().info(
|
|
89
|
+
"[Auth] State changed, broadcasting:",
|
|
90
|
+
event,
|
|
91
|
+
session?.user?.email || "anonymous",
|
|
92
|
+
);
|
|
93
|
+
webSocketServer.broadcast("auth:getUser" as any, { user: session?.user || null } as any);
|
|
94
|
+
});
|
|
95
|
+
};
|