@pipelab/core-node 1.0.0-beta.12 → 1.0.0-beta.14
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/.oxfmtrc.json +1 -1
- package/CHANGELOG.md +18 -0
- package/dist/index.d.mts +23 -12
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +104 -41
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -5
- package/src/config.test.ts +176 -0
- package/src/config.ts +44 -24
- package/src/context.ts +51 -2
- package/src/handlers/build-history.ts +14 -1
- package/src/handlers/engine.ts +6 -7
- package/src/handlers/history.ts +1 -1
- package/src/handlers/plugins.ts +1 -6
- package/src/migrations.ts +3 -15
- package/src/plugins-registry.ts +1 -3
- package/src/runner.ts +0 -1
- package/src/server.ts +1 -0
- package/src/types/runner.ts +1 -0
- package/src/utils/fs-extras.ts +2 -23
- package/src/utils/github.test.ts +213 -0
- package/src/utils/remote.ts +3 -5
- package/src/utils.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pipelab/core-node",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.14",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The Pipelab automation engine for Node.js",
|
|
6
6
|
"license": "FSL-1.1-MIT",
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"type-fest": "4.39.0",
|
|
41
41
|
"ws": "8.18.3",
|
|
42
42
|
"yauzl": "2.10.0",
|
|
43
|
-
"@pipelab/constants": "1.0.0-beta.
|
|
44
|
-
"@pipelab/shared": "1.0.0-beta.
|
|
43
|
+
"@pipelab/constants": "1.0.0-beta.12",
|
|
44
|
+
"@pipelab/shared": "1.0.0-beta.10"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@types/adm-zip": "0.5.7",
|
|
@@ -52,13 +52,17 @@
|
|
|
52
52
|
"@types/tar": "7.0.87",
|
|
53
53
|
"@types/ws": "8.18.1",
|
|
54
54
|
"@types/yauzl": "2.10.3",
|
|
55
|
+
"memfs": "4.57.6",
|
|
55
56
|
"tsdown": "0.21.2",
|
|
56
57
|
"typescript": "^5.0.0",
|
|
57
|
-
"
|
|
58
|
+
"vitest": "3.1.4",
|
|
59
|
+
"@pipelab/tsconfig": "1.0.0-beta.10"
|
|
58
60
|
},
|
|
59
61
|
"scripts": {
|
|
60
62
|
"build": "tsdown",
|
|
61
63
|
"format": "oxfmt .",
|
|
62
|
-
"lint": "oxlint ."
|
|
64
|
+
"lint": "oxlint .",
|
|
65
|
+
"typecheck": "tsc -b",
|
|
66
|
+
"test": "vitest run"
|
|
63
67
|
}
|
|
64
68
|
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, vi } from "vitest";
|
|
2
|
+
import { setupConfigFile } from "./config";
|
|
3
|
+
import { FileRepo } from "@pipelab/shared";
|
|
4
|
+
import { PipelabContext } from "./context";
|
|
5
|
+
import fs from "node:fs/promises";
|
|
6
|
+
import { existsSync } from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { vol } from "memfs";
|
|
9
|
+
|
|
10
|
+
// Mock the node:fs and node:fs/promises modules to use memfs
|
|
11
|
+
vi.mock("node:fs", async () => {
|
|
12
|
+
const memfs = await import("memfs");
|
|
13
|
+
return {
|
|
14
|
+
...memfs.fs,
|
|
15
|
+
default: memfs.fs,
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
vi.mock("node:fs/promises", async () => {
|
|
20
|
+
const memfs = await import("memfs");
|
|
21
|
+
return {
|
|
22
|
+
...memfs.fs.promises,
|
|
23
|
+
default: memfs.fs.promises,
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe("setupConfigFile & Backup Creation", () => {
|
|
28
|
+
let tempDir: string;
|
|
29
|
+
let context: PipelabContext;
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
vol.reset();
|
|
33
|
+
tempDir = "/tmp/pipelab-test-config";
|
|
34
|
+
context = new PipelabContext({ userDataPath: tempDir });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("should migrate config and create backup files on disk", async () => {
|
|
38
|
+
// 1. Setup V1 configuration file in the context's config path
|
|
39
|
+
const configDir = context.getConfigPath();
|
|
40
|
+
await fs.mkdir(configDir, { recursive: true });
|
|
41
|
+
|
|
42
|
+
const projectsFilePath = path.join(configDir, "projects.json");
|
|
43
|
+
|
|
44
|
+
const v1Config = {
|
|
45
|
+
version: "1.0.0",
|
|
46
|
+
data: {
|
|
47
|
+
"pipeline-1": {
|
|
48
|
+
project: "main",
|
|
49
|
+
type: "internal",
|
|
50
|
+
configName: "pipeline-1",
|
|
51
|
+
lastModified: "2026-06-04",
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
await fs.writeFile(projectsFilePath, JSON.stringify(v1Config));
|
|
57
|
+
|
|
58
|
+
// 2. Initialize setupConfigFile
|
|
59
|
+
const configInstance = await setupConfigFile<FileRepo>("projects", { context });
|
|
60
|
+
|
|
61
|
+
// Verify file exists
|
|
62
|
+
expect(existsSync(projectsFilePath)).toBe(true);
|
|
63
|
+
|
|
64
|
+
// 3. Retrieve config (this triggers the migration process and onStep callback)
|
|
65
|
+
const migratedConfig = await configInstance.getConfig();
|
|
66
|
+
|
|
67
|
+
// 4. Assert the main config is updated on disk to version 2.0.0
|
|
68
|
+
expect(migratedConfig.version).toBe("2.0.0");
|
|
69
|
+
|
|
70
|
+
const updatedContent = JSON.parse(await fs.readFile(projectsFilePath, "utf8"));
|
|
71
|
+
expect(updatedContent.version).toBe("2.0.0");
|
|
72
|
+
|
|
73
|
+
// 5. Assert that the intermediate backup file was created on disk
|
|
74
|
+
const backupFilePath = path.join(configDir, "projects.v2.0.0.json");
|
|
75
|
+
expect(existsSync(backupFilePath)).toBe(true);
|
|
76
|
+
|
|
77
|
+
const backupContent = JSON.parse(await fs.readFile(backupFilePath, "utf8"));
|
|
78
|
+
expect(backupContent.version).toBe("2.0.0");
|
|
79
|
+
expect(backupContent.projects).toHaveLength(1);
|
|
80
|
+
expect(backupContent.pipelines).toHaveLength(1);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("should create default config file if missing on setup", async () => {
|
|
84
|
+
const configDir = context.getConfigPath();
|
|
85
|
+
const projectsFilePath = path.join(configDir, "projects.json");
|
|
86
|
+
|
|
87
|
+
expect(existsSync(projectsFilePath)).toBe(false);
|
|
88
|
+
|
|
89
|
+
// Initialize setupConfigFile
|
|
90
|
+
await setupConfigFile<FileRepo>("projects", { context });
|
|
91
|
+
|
|
92
|
+
// Should create file with default value
|
|
93
|
+
expect(existsSync(projectsFilePath)).toBe(true);
|
|
94
|
+
const content = JSON.parse(await fs.readFile(projectsFilePath, "utf8"));
|
|
95
|
+
expect(content.version).toBe("2.0.0");
|
|
96
|
+
expect(content.projects).toHaveLength(1);
|
|
97
|
+
expect(content.pipelines).toEqual([]);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("should fallback to default config and preserve corrupted file if parsing fails", async () => {
|
|
101
|
+
const configDir = context.getConfigPath();
|
|
102
|
+
await fs.mkdir(configDir, { recursive: true });
|
|
103
|
+
const projectsFilePath = path.join(configDir, "projects.json");
|
|
104
|
+
|
|
105
|
+
await fs.writeFile(projectsFilePath, "{ corrupted json... }");
|
|
106
|
+
|
|
107
|
+
const configInstance = await setupConfigFile<FileRepo>("projects", { context });
|
|
108
|
+
const config = await configInstance.getConfig();
|
|
109
|
+
|
|
110
|
+
expect(config.version).toBe("2.0.0");
|
|
111
|
+
expect(config.projects).toHaveLength(1);
|
|
112
|
+
expect(config.pipelines).toEqual([]);
|
|
113
|
+
|
|
114
|
+
// Verify a timestamped corrupted backup file exists
|
|
115
|
+
const files = await fs.readdir(configDir);
|
|
116
|
+
const corruptedFile = files.find(f => f.startsWith("projects.corrupted.") && f.endsWith(".json"));
|
|
117
|
+
expect(corruptedFile).toBeDefined();
|
|
118
|
+
const corruptedContent = await fs.readFile(path.join(configDir, corruptedFile!), "utf8");
|
|
119
|
+
expect(corruptedContent).toBe("{ corrupted json... }");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("should fallback to default config and preserve file if migration throws", async () => {
|
|
123
|
+
const configDir = context.getConfigPath();
|
|
124
|
+
await fs.mkdir(configDir, { recursive: true });
|
|
125
|
+
const projectsFilePath = path.join(configDir, "projects.json");
|
|
126
|
+
|
|
127
|
+
const customMigrator = {
|
|
128
|
+
defaultValue: { version: "2.0.0", projects: [], pipelines: [] } as any,
|
|
129
|
+
migrate: async () => {
|
|
130
|
+
throw new Error("Migration failed!");
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
await fs.writeFile(projectsFilePath, JSON.stringify({ version: "1.0.0", invalidData: true }));
|
|
135
|
+
|
|
136
|
+
const configInstance = await setupConfigFile<any>("projects", {
|
|
137
|
+
context,
|
|
138
|
+
migrator: customMigrator,
|
|
139
|
+
});
|
|
140
|
+
const config = await configInstance.getConfig();
|
|
141
|
+
|
|
142
|
+
expect(config.version).toBe("2.0.0");
|
|
143
|
+
|
|
144
|
+
// Verify a timestamped corrupted backup file exists
|
|
145
|
+
const files = await fs.readdir(configDir);
|
|
146
|
+
const corruptedFile = files.find(f => f.startsWith("projects.corrupted.") && f.endsWith(".json"));
|
|
147
|
+
expect(corruptedFile).toBeDefined();
|
|
148
|
+
const corruptedContent = JSON.parse(await fs.readFile(path.join(configDir, corruptedFile!), "utf8"));
|
|
149
|
+
expect(corruptedContent.invalidData).toBe(true);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("should save config to disk via setConfig", async () => {
|
|
153
|
+
const configInstance = await setupConfigFile<FileRepo>("projects", { context });
|
|
154
|
+
const initialConfig = await configInstance.getConfig();
|
|
155
|
+
|
|
156
|
+
const newConfig: FileRepo = {
|
|
157
|
+
...initialConfig,
|
|
158
|
+
pipelines: [
|
|
159
|
+
{
|
|
160
|
+
id: "pipeline-new",
|
|
161
|
+
project: "main",
|
|
162
|
+
type: "internal",
|
|
163
|
+
configName: "pipeline-new",
|
|
164
|
+
lastModified: "2026-06-05",
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const success = await configInstance.setConfig(newConfig);
|
|
170
|
+
expect(success).toBe(true);
|
|
171
|
+
|
|
172
|
+
const savedContent = JSON.parse(await fs.readFile(path.join(context.getConfigPath(), "projects.json"), "utf8"));
|
|
173
|
+
expect(savedContent.pipelines).toHaveLength(1);
|
|
174
|
+
expect(savedContent.pipelines[0].id).toBe("pipeline-new");
|
|
175
|
+
});
|
|
176
|
+
});
|
package/src/config.ts
CHANGED
|
@@ -6,15 +6,7 @@ import { useLogger } from "@pipelab/shared";
|
|
|
6
6
|
import { configRegistry, Migrator, normalizePipelineConfig } from "@pipelab/shared";
|
|
7
7
|
|
|
8
8
|
export const getMigrator = <T>(name: string) => {
|
|
9
|
-
|
|
10
|
-
return configRegistry[name] as Migrator<T>;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
if (name.startsWith("pipeline-") || path.isAbsolute(name) || name.endsWith(".json")) {
|
|
14
|
-
return configRegistry["pipeline"] as Migrator<T>;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
return undefined;
|
|
9
|
+
return (configRegistry[name] || configRegistry["pipeline"]) as Migrator<T>;
|
|
18
10
|
};
|
|
19
11
|
|
|
20
12
|
export const setupConfigFile = async <T>(
|
|
@@ -63,22 +55,28 @@ export const setupConfigFile = async <T>(
|
|
|
63
55
|
}
|
|
64
56
|
|
|
65
57
|
let json: any = undefined;
|
|
58
|
+
let migrationFailed = false;
|
|
66
59
|
try {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
60
|
+
if (!parseFailed) {
|
|
61
|
+
json = await migrator.migrate(originalJson, {
|
|
62
|
+
debug: false,
|
|
63
|
+
onStep: async (state: any, version: string) => {
|
|
64
|
+
const parsedPath = path.parse(filesPath);
|
|
65
|
+
const versionedPath = path.join(parsedPath.dir, `${parsedPath.name}.v${version}.json`);
|
|
66
|
+
try {
|
|
67
|
+
await fs.writeFile(versionedPath, JSON.stringify(state));
|
|
68
|
+
logger().info(`Intermediate backup created for ${name} at ${versionedPath}`);
|
|
69
|
+
} catch (e) {
|
|
70
|
+
logger().error(`Failed to create intermediate backup for ${name} at v${version}:`, e);
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
} else {
|
|
75
|
+
json = migrator.defaultValue;
|
|
76
|
+
}
|
|
80
77
|
} catch (e) {
|
|
81
78
|
logger().error(`Error migrating config ${name}:`, e);
|
|
79
|
+
migrationFailed = true;
|
|
82
80
|
json = migrator.defaultValue;
|
|
83
81
|
}
|
|
84
82
|
|
|
@@ -95,8 +93,30 @@ export const setupConfigFile = async <T>(
|
|
|
95
93
|
const originalVersion = originalJson?.version;
|
|
96
94
|
const newVersion = json?.version;
|
|
97
95
|
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
const shouldSaveBack =
|
|
97
|
+
originalVersion !== newVersion ||
|
|
98
|
+
normalized ||
|
|
99
|
+
content === undefined ||
|
|
100
|
+
parseFailed ||
|
|
101
|
+
migrationFailed;
|
|
102
|
+
|
|
103
|
+
if (shouldSaveBack) {
|
|
104
|
+
if (parseFailed || migrationFailed) {
|
|
105
|
+
try {
|
|
106
|
+
const parsedPath = path.parse(filesPath);
|
|
107
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
108
|
+
const corruptedPath = path.join(
|
|
109
|
+
parsedPath.dir,
|
|
110
|
+
`${parsedPath.name}.corrupted.${timestamp}.json`,
|
|
111
|
+
);
|
|
112
|
+
const backupContent = parseFailed ? (content || "") : JSON.stringify(originalJson, null, 2);
|
|
113
|
+
await fs.writeFile(corruptedPath, backupContent);
|
|
114
|
+
logger().info(`Corrupted config file preserved at ${corruptedPath}`);
|
|
115
|
+
} catch (e) {
|
|
116
|
+
logger().error(`Failed to backup corrupted config ${name}:`, e);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
100
120
|
try {
|
|
101
121
|
await fs.writeFile(filesPath, JSON.stringify(json));
|
|
102
122
|
} catch (e) {
|
package/src/context.ts
CHANGED
|
@@ -2,12 +2,17 @@ import { join, dirname, resolve } from "node:path";
|
|
|
2
2
|
import { homedir, platform } from "node:os";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { existsSync } from "node:fs";
|
|
5
|
+
import { mkdir, realpath, mkdtemp } from "node:fs/promises";
|
|
6
|
+
import { SandboxFolder, DEFAULT_NODE_VERSION, DEFAULT_PNPM_VERSION } from "@pipelab/constants";
|
|
7
|
+
|
|
8
|
+
// @ts-ignore import.meta is only allowed in ES Modules
|
|
9
|
+
const metaUrl = typeof import.meta !== "undefined" ? import.meta.url : undefined;
|
|
5
10
|
|
|
6
11
|
const _dirname =
|
|
7
12
|
typeof __dirname !== "undefined"
|
|
8
13
|
? __dirname
|
|
9
|
-
:
|
|
10
|
-
? dirname(fileURLToPath(
|
|
14
|
+
: metaUrl
|
|
15
|
+
? dirname(fileURLToPath(metaUrl))
|
|
11
16
|
: process.cwd();
|
|
12
17
|
|
|
13
18
|
export const isDev = process.env.NODE_ENV === "development";
|
|
@@ -71,4 +76,48 @@ export class PipelabContext {
|
|
|
71
76
|
getConfigPath(...subpaths: string[]) {
|
|
72
77
|
return join(this.userDataPath, "config", ...subpaths);
|
|
73
78
|
}
|
|
79
|
+
|
|
80
|
+
getTempPath(...subpaths: string[]) {
|
|
81
|
+
return join(this.userDataPath, "temp", ...subpaths);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async createTempFolder(prefix = "pipelab-") {
|
|
85
|
+
const baseDir = this.getTempPath();
|
|
86
|
+
await mkdir(baseDir, { recursive: true });
|
|
87
|
+
const realBaseDir = await realpath(baseDir);
|
|
88
|
+
return await mkdtemp(join(realBaseDir, prefix));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
getCachePath(...subpaths: string[]) {
|
|
92
|
+
return join(this.userDataPath, "cache", ...subpaths);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
getPnpmPath(...subpaths: string[]) {
|
|
96
|
+
return join(this.userDataPath, "pnpm", ...subpaths);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
getNodePath(version = DEFAULT_NODE_VERSION) {
|
|
100
|
+
const isWindows = process.platform === "win32";
|
|
101
|
+
return this.getThirdPartyPath("node", version, isWindows ? "node.exe" : "bin/node");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
getPnpmBinPath(version = DEFAULT_PNPM_VERSION) {
|
|
105
|
+
return this.getPackagesPath("pnpm", version, "bin", "pnpm.cjs");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
getSandboxFolders(): Array<{ name: SandboxFolder; label: string; path: string }> {
|
|
109
|
+
const foldersRecord: Record<SandboxFolder, { label: string; path: string }> = {
|
|
110
|
+
[SandboxFolder.Packages]: { label: "Packages", path: this.getPackagesPath() },
|
|
111
|
+
[SandboxFolder.ThirdParty]: { label: "Third-Party Tools", path: this.getThirdPartyPath() },
|
|
112
|
+
[SandboxFolder.Config]: { label: "Configuration", path: this.getConfigPath() },
|
|
113
|
+
[SandboxFolder.Temp]: { label: "Temporary Files", path: this.getTempPath() },
|
|
114
|
+
[SandboxFolder.Cache]: { label: "Cache", path: this.getCachePath() },
|
|
115
|
+
[SandboxFolder.Pnpm]: { label: "PNPM Home", path: this.getPnpmPath() },
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
return (Object.keys(foldersRecord) as SandboxFolder[]).map((key) => ({
|
|
119
|
+
name: key,
|
|
120
|
+
...foldersRecord[key],
|
|
121
|
+
}));
|
|
122
|
+
}
|
|
74
123
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { PipelabContext } from "../context";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { writeFile, readFile, unlink, mkdir, stat, readdir } from "node:fs/promises";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
5
4
|
import { useLogger, BuildHistoryEntry, IBuildHistoryStorage, AppConfig } from "@pipelab/shared";
|
|
6
5
|
import { setupConfigFile } from "../config";
|
|
7
6
|
import checkDiskSpace from "check-disk-space";
|
|
8
7
|
import { getFolderSize } from "../utils/fs-extras";
|
|
8
|
+
import { SandboxFolder } from "@pipelab/constants";
|
|
9
9
|
|
|
10
10
|
// Simplified storage - one file per pipeline containing array of build entries
|
|
11
11
|
|
|
@@ -294,6 +294,7 @@ export class BuildHistoryStorage implements IBuildHistoryStorage {
|
|
|
294
294
|
total: number;
|
|
295
295
|
free: number;
|
|
296
296
|
pipelab: number;
|
|
297
|
+
folders: Array<{ name: SandboxFolder; label: string; size: number }>;
|
|
297
298
|
};
|
|
298
299
|
}> {
|
|
299
300
|
try {
|
|
@@ -303,6 +304,16 @@ export class BuildHistoryStorage implements IBuildHistoryStorage {
|
|
|
303
304
|
const diskSpace = await checkDiskSpace(this.context.userDataPath);
|
|
304
305
|
const pipelabSize = await getFolderSize(this.context.userDataPath);
|
|
305
306
|
|
|
307
|
+
const folders = [];
|
|
308
|
+
for (const folder of this.context.getSandboxFolders()) {
|
|
309
|
+
const size = await getFolderSize(folder.path);
|
|
310
|
+
folders.push({
|
|
311
|
+
name: folder.name,
|
|
312
|
+
label: folder.label,
|
|
313
|
+
size,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
306
317
|
const settings = await setupConfigFile<AppConfig>("settings", { context: this.context });
|
|
307
318
|
const config = await settings.getConfig();
|
|
308
319
|
const policy = config?.buildHistory?.retentionPolicy || {
|
|
@@ -326,6 +337,7 @@ export class BuildHistoryStorage implements IBuildHistoryStorage {
|
|
|
326
337
|
total: diskSpace.size,
|
|
327
338
|
free: diskSpace.free,
|
|
328
339
|
pipelab: pipelabSize,
|
|
340
|
+
folders,
|
|
329
341
|
},
|
|
330
342
|
};
|
|
331
343
|
}
|
|
@@ -359,6 +371,7 @@ export class BuildHistoryStorage implements IBuildHistoryStorage {
|
|
|
359
371
|
total: diskSpace.size,
|
|
360
372
|
free: diskSpace.free,
|
|
361
373
|
pipelab: pipelabSize,
|
|
374
|
+
folders,
|
|
362
375
|
},
|
|
363
376
|
};
|
|
364
377
|
} catch (error) {
|
package/src/handlers/engine.ts
CHANGED
|
@@ -4,8 +4,7 @@ import { useLogger } from "@pipelab/shared";
|
|
|
4
4
|
import { getFinalPlugins, executeGraphWithHistory } from "../utils";
|
|
5
5
|
import { presets } from "../presets/list";
|
|
6
6
|
import { handleActionExecute } from "../handler-func";
|
|
7
|
-
import {
|
|
8
|
-
import { tmpdir } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
9
8
|
import { setupConfigFile } from "../config";
|
|
10
9
|
import { AppConfig } from "@pipelab/shared";
|
|
11
10
|
|
|
@@ -82,10 +81,10 @@ export const registerEngineHandlers = (context: PipelabContext) => {
|
|
|
82
81
|
const settings = await setupConfigFile<AppConfig>("settings", { context });
|
|
83
82
|
const config = await settings.getConfig();
|
|
84
83
|
|
|
85
|
-
const cachePath =
|
|
86
|
-
const cwd = await
|
|
84
|
+
const cachePath = join(context.userDataPath, "cache", "actions", pluginId, nodeId);
|
|
85
|
+
const cwd = await context.createTempFolder("action-execute-");
|
|
87
86
|
|
|
88
|
-
const mainWindow = undefined;
|
|
87
|
+
const mainWindow: undefined = undefined;
|
|
89
88
|
abortControllerGraph = new AbortController();
|
|
90
89
|
|
|
91
90
|
const signalPromise = new Promise((resolve, reject) => {
|
|
@@ -145,9 +144,9 @@ export const registerEngineHandlers = (context: PipelabContext) => {
|
|
|
145
144
|
const effectiveProjectName = projectName || "Unnamed Project";
|
|
146
145
|
const effectiveProjectPath = projectPath || "";
|
|
147
146
|
const effectivePipelineId = pipelineId || "unknown";
|
|
148
|
-
const effectiveCachePath =
|
|
147
|
+
const effectiveCachePath = join(context.userDataPath, "cache", effectivePipelineId);
|
|
149
148
|
|
|
150
|
-
const mainWindow = undefined;
|
|
149
|
+
const mainWindow: undefined = undefined;
|
|
151
150
|
abortControllerGraph = new AbortController();
|
|
152
151
|
|
|
153
152
|
try {
|
package/src/handlers/history.ts
CHANGED
package/src/handlers/plugins.ts
CHANGED
|
@@ -249,17 +249,12 @@ export const registerPluginsHandlers = (context: PipelabContext) => {
|
|
|
249
249
|
// Ensures all required plugin IDs are loaded, JIT-installing any that are missing.
|
|
250
250
|
// Called before opening a pipeline in the editor.
|
|
251
251
|
handle("plugin:ensure-loaded", async (_, { send, value }) => {
|
|
252
|
-
const {
|
|
252
|
+
const { plugins } = value;
|
|
253
253
|
const { plugins: registeredPlugins, registerPlugins } = usePlugins();
|
|
254
254
|
const loaded: string[] = [];
|
|
255
255
|
const failed: string[] = [];
|
|
256
256
|
|
|
257
257
|
const pluginsToEnsure = new Set<string>();
|
|
258
|
-
if (Array.isArray(pluginIds)) {
|
|
259
|
-
for (const id of pluginIds) {
|
|
260
|
-
if (id) pluginsToEnsure.add(id);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
258
|
if (plugins && typeof plugins === "object") {
|
|
264
259
|
for (const id of Object.keys(plugins)) {
|
|
265
260
|
if (id) pluginsToEnsure.add(id);
|
package/src/migrations.ts
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import { useAPI } from "./ipc-core";
|
|
2
2
|
import { PipelabContext } from "./context";
|
|
3
|
-
import { setupConfigFile } from "./config";
|
|
4
|
-
import {
|
|
5
|
-
savedFileMigrator,
|
|
6
|
-
fileRepoMigrations,
|
|
7
|
-
appSettingsMigrator,
|
|
8
|
-
useLogger,
|
|
9
|
-
} from "@pipelab/shared";
|
|
3
|
+
import { setupConfigFile, getMigrator } from "./config";
|
|
4
|
+
import { useLogger } from "@pipelab/shared";
|
|
10
5
|
|
|
11
6
|
/**
|
|
12
7
|
* Registers migration handlers for the CLI/Standalone server.
|
|
@@ -23,14 +18,7 @@ export function registerMigrationHandlers(context: PipelabContext) {
|
|
|
23
18
|
|
|
24
19
|
try {
|
|
25
20
|
// Determine which migrator to use
|
|
26
|
-
|
|
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
|
-
}
|
|
21
|
+
const migrator = getMigrator(name);
|
|
34
22
|
|
|
35
23
|
if (!migrator) {
|
|
36
24
|
throw new Error(
|
package/src/plugins-registry.ts
CHANGED
|
@@ -203,7 +203,7 @@ export async function findInstalledPlugins(
|
|
|
203
203
|
return installed;
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
-
export const builtInPlugins = async (options: { context: PipelabContext }) => {
|
|
206
|
+
export const builtInPlugins = async (options: { context: PipelabContext }): Promise<void> => {
|
|
207
207
|
console.log("[Plugins] Starting background plugin loading...");
|
|
208
208
|
|
|
209
209
|
// Pre-ensure Node.js and PNPM once in parallel so plugins don't have to wait for them
|
|
@@ -318,6 +318,4 @@ export const builtInPlugins = async (options: { context: PipelabContext }) => {
|
|
|
318
318
|
webSocketServer.broadcast("startup:progress", { type: "ready" });
|
|
319
319
|
}, 2000);
|
|
320
320
|
})();
|
|
321
|
-
|
|
322
|
-
return [];
|
|
323
321
|
};
|
package/src/runner.ts
CHANGED
|
@@ -3,7 +3,6 @@ import { setupConfigFile } from "./config";
|
|
|
3
3
|
import { isDev, PipelabContext } from "./context";
|
|
4
4
|
import { readFile, access, writeFile, mkdir } from "node:fs/promises";
|
|
5
5
|
import { resolve, isAbsolute, join, dirname } from "node:path";
|
|
6
|
-
import { tmpdir } from "node:os";
|
|
7
6
|
import { savedFileMigrator } from "@pipelab/shared";
|
|
8
7
|
import type { AppConfig } from "@pipelab/shared";
|
|
9
8
|
import { registerMigrationHandlers } from "./migrations";
|
package/src/server.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { existsSync } from "node:fs";
|
|
|
11
11
|
import { readFile } from "node:fs/promises";
|
|
12
12
|
import { join } from "node:path";
|
|
13
13
|
import http from "http";
|
|
14
|
+
// @ts-expect-error serve-handler has no type definitions
|
|
14
15
|
import handler from "serve-handler";
|
|
15
16
|
import { registerMigrationHandlers } from "./migrations";
|
|
16
17
|
|
package/src/types/runner.ts
CHANGED
|
@@ -35,6 +35,7 @@ export type ActionRunnerData<ACTION extends Action> = {
|
|
|
35
35
|
setMeta: (callback: (data: ACTION["meta"]) => ACTION["meta"]) => void;
|
|
36
36
|
meta: ACTION["meta"];
|
|
37
37
|
cwd: string;
|
|
38
|
+
/** @deprecated Use `context` instead to resolve sandboxed folders and binary paths. */
|
|
38
39
|
paths: {
|
|
39
40
|
cache: string;
|
|
40
41
|
pnpm: string;
|
package/src/utils/fs-extras.ts
CHANGED
|
@@ -1,17 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createWriteStream } from "node:fs";
|
|
2
2
|
import { execa, Options, Subprocess } from "execa";
|
|
3
|
-
import {
|
|
4
|
-
mkdir as mkdirP,
|
|
5
|
-
access,
|
|
6
|
-
writeFile,
|
|
7
|
-
realpath,
|
|
8
|
-
mkdtemp,
|
|
9
|
-
chmod,
|
|
10
|
-
stat,
|
|
11
|
-
readdir,
|
|
12
|
-
} from "node:fs/promises";
|
|
3
|
+
import { mkdir as mkdirP, writeFile, stat, readdir } from "node:fs/promises";
|
|
13
4
|
import { join, dirname } from "node:path";
|
|
14
|
-
import { tmpdir } from "node:os";
|
|
15
5
|
import tar from "tar";
|
|
16
6
|
import yauzl from "yauzl";
|
|
17
7
|
import archiver from "archiver";
|
|
@@ -32,17 +22,6 @@ export const ensure = async (filesPath: string, defaultContent = "{}") => {
|
|
|
32
22
|
}
|
|
33
23
|
};
|
|
34
24
|
|
|
35
|
-
/**
|
|
36
|
-
* Generates a unique temporary folder.
|
|
37
|
-
*/
|
|
38
|
-
export const generateTempFolder = async (base?: string) => {
|
|
39
|
-
const targetBase = base || tmpdir();
|
|
40
|
-
await mkdirP(targetBase, { recursive: true });
|
|
41
|
-
const realPath = await realpath(targetBase);
|
|
42
|
-
const tempFolder = await mkdtemp(join(realPath, "pipelab-"));
|
|
43
|
-
return tempFolder;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
25
|
/**
|
|
47
26
|
* Extracts a .tar.gz archive.
|
|
48
27
|
*/
|