@pipelab/core-node 1.0.0-beta.2 → 1.0.0-beta.21
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 +164 -0
- package/dist/config-Bi0ORcTK.mjs +2 -0
- package/dist/config-CFgGRD9U.mjs +265 -0
- package/dist/config-CFgGRD9U.mjs.map +1 -0
- package/dist/index.d.mts +74 -53
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1705 -576
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -5
- package/scratch/simulate-updates.ts +120 -0
- package/src/config.test.ts +232 -0
- package/src/config.ts +111 -50
- package/src/context.ts +117 -5
- package/src/handler-func.ts +2 -77
- package/src/handlers/build-history.ts +60 -90
- package/src/handlers/config.ts +265 -55
- package/src/handlers/engine.ts +32 -35
- package/src/handlers/history.ts +0 -40
- package/src/handlers/index.ts +4 -0
- package/src/handlers/migration.ts +621 -0
- package/src/handlers/plugins.ts +305 -0
- package/src/handlers/system.ts +7 -2
- package/src/index.ts +9 -2
- package/src/plugins-registry.ts +280 -38
- package/src/presets/c3toSteam.ts +13 -7
- package/src/presets/demo.ts +9 -9
- package/src/presets/list.ts +0 -6
- package/src/presets/moreToCome.ts +3 -2
- package/src/presets/newProject.ts +3 -2
- package/src/presets/test-c3-offline.ts +15 -6
- package/src/presets/test-c3-unzip.ts +19 -8
- package/src/runner.ts +2 -8
- package/src/server.ts +45 -4
- package/src/types/runner.ts +2 -30
- package/src/utils/fs-extras.ts +6 -24
- package/src/utils/github.test.ts +211 -0
- package/src/utils/github.ts +90 -10
- package/src/utils/remote.test.ts +209 -0
- package/src/utils/remote.ts +325 -87
- package/src/utils/storage.ts +2 -1
- package/src/utils.ts +20 -24
- package/src/migrations.ts +0 -72
- package/src/presets/if.ts +0 -69
- package/src/presets/loop.ts +0 -65
package/src/context.ts
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
import { join, dirname, resolve } from "node:path";
|
|
2
2
|
import { homedir, platform } from "node:os";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { existsSync } from "node:fs";
|
|
4
|
+
import { existsSync, readFileSync } 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";
|
|
14
19
|
|
|
15
|
-
export
|
|
20
|
+
export type PipelabEnv = "dev" | "beta" | "prod";
|
|
21
|
+
|
|
22
|
+
export const getDefaultUserDataPath = (env?: PipelabEnv) => {
|
|
16
23
|
const base = (() => {
|
|
17
24
|
switch (platform()) {
|
|
18
25
|
case "win32":
|
|
@@ -24,7 +31,10 @@ export const getDefaultUserDataPath = () => {
|
|
|
24
31
|
}
|
|
25
32
|
})();
|
|
26
33
|
|
|
27
|
-
|
|
34
|
+
const mode = env ?? (isDev ? "dev" : "prod");
|
|
35
|
+
const folder = mode === "dev" ? "app-dev" : mode === "beta" ? "app-beta" : "app";
|
|
36
|
+
|
|
37
|
+
return join(base, "@pipelab", folder);
|
|
28
38
|
};
|
|
29
39
|
|
|
30
40
|
/**
|
|
@@ -43,15 +53,26 @@ function findProjectRoot(startDir: string): string | null {
|
|
|
43
53
|
|
|
44
54
|
export const projectRoot = findProjectRoot(_dirname);
|
|
45
55
|
|
|
56
|
+
export const CacheFolder = {
|
|
57
|
+
Actions: "actions",
|
|
58
|
+
Pipelines: "pipelines",
|
|
59
|
+
Pacote: "pacote",
|
|
60
|
+
} as const;
|
|
61
|
+
|
|
62
|
+
export type CacheFolderType = (typeof CacheFolder)[keyof typeof CacheFolder];
|
|
63
|
+
|
|
46
64
|
export interface PipelabContextOptions {
|
|
47
65
|
userDataPath: string;
|
|
66
|
+
releaseTag?: string;
|
|
48
67
|
}
|
|
49
68
|
|
|
50
69
|
export class PipelabContext {
|
|
51
70
|
public readonly userDataPath: string;
|
|
71
|
+
public readonly releaseTag: string;
|
|
52
72
|
|
|
53
73
|
constructor(options: PipelabContextOptions) {
|
|
54
74
|
this.userDataPath = options.userDataPath;
|
|
75
|
+
this.releaseTag = options.releaseTag || "latest";
|
|
55
76
|
}
|
|
56
77
|
|
|
57
78
|
getPackagesPath(...subpaths: string[]) {
|
|
@@ -65,4 +86,95 @@ export class PipelabContext {
|
|
|
65
86
|
getConfigPath(...subpaths: string[]) {
|
|
66
87
|
return join(this.userDataPath, "config", ...subpaths);
|
|
67
88
|
}
|
|
89
|
+
|
|
90
|
+
getSettingsPath() {
|
|
91
|
+
return this.getConfigPath("settings.json");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
getConnectionsPath() {
|
|
95
|
+
return this.getConfigPath("connections.json");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
getProjectsPath() {
|
|
99
|
+
return this.getConfigPath("projects.json");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private _cachedSettings: any = null;
|
|
103
|
+
private _cachedSettingsTime: number = 0;
|
|
104
|
+
|
|
105
|
+
private getSettings() {
|
|
106
|
+
const settingsPath = this.getSettingsPath();
|
|
107
|
+
if (!existsSync(settingsPath)) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
const now = Date.now();
|
|
111
|
+
if (this._cachedSettings && now - this._cachedSettingsTime < 2000) {
|
|
112
|
+
return this._cachedSettings;
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
const content = readFileSync(settingsPath, "utf8");
|
|
116
|
+
this._cachedSettings = JSON.parse(content);
|
|
117
|
+
this._cachedSettingsTime = now;
|
|
118
|
+
return this._cachedSettings;
|
|
119
|
+
} catch (e) {
|
|
120
|
+
return this._cachedSettings;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
getTempPath(...subpaths: string[]) {
|
|
125
|
+
const settings = this.getSettings();
|
|
126
|
+
const base = settings?.tempFolder || join(this.userDataPath, "temp");
|
|
127
|
+
return join(base, ...subpaths);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async createTempFolder(prefix = "pipelab-") {
|
|
131
|
+
const baseDir = this.getTempPath();
|
|
132
|
+
await mkdir(baseDir, { recursive: true });
|
|
133
|
+
const realBaseDir = await realpath(baseDir);
|
|
134
|
+
return await mkdtemp(join(realBaseDir, prefix));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
getCachePath(): string;
|
|
138
|
+
getCachePath(folder: CacheFolderType, ...subpaths: string[]): string;
|
|
139
|
+
getCachePath(folder?: CacheFolderType, ...subpaths: string[]) {
|
|
140
|
+
const settings = this.getSettings();
|
|
141
|
+
const base = settings?.cacheFolder || join(this.userDataPath, "cache");
|
|
142
|
+
if (!folder) {
|
|
143
|
+
return base;
|
|
144
|
+
}
|
|
145
|
+
return join(base, folder, ...subpaths);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
getPnpmPath(...subpaths: string[]) {
|
|
149
|
+
return join(this.userDataPath, "pnpm", ...subpaths);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
getBuildHistoryPath(...subpaths: string[]) {
|
|
153
|
+
return join(this.userDataPath, "build-history", ...subpaths);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
getNodePath(version = DEFAULT_NODE_VERSION) {
|
|
157
|
+
const isWindows = process.platform === "win32";
|
|
158
|
+
return this.getThirdPartyPath("node", version, isWindows ? "node.exe" : "bin/node");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
getPnpmBinPath(version = DEFAULT_PNPM_VERSION) {
|
|
162
|
+
return this.getPackagesPath("pnpm", version, "bin", "pnpm.cjs");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
getSandboxFolders(): Array<{ name: SandboxFolder; label: string; path: string }> {
|
|
166
|
+
const foldersRecord: Record<SandboxFolder, { label: string; path: string }> = {
|
|
167
|
+
[SandboxFolder.Packages]: { label: "Packages", path: this.getPackagesPath() },
|
|
168
|
+
[SandboxFolder.ThirdParty]: { label: "Third-Party Tools", path: this.getThirdPartyPath() },
|
|
169
|
+
[SandboxFolder.Config]: { label: "Configuration", path: this.getConfigPath() },
|
|
170
|
+
[SandboxFolder.Temp]: { label: "Temporary Files", path: this.getTempPath() },
|
|
171
|
+
[SandboxFolder.Cache]: { label: "Cache", path: this.getCachePath() },
|
|
172
|
+
[SandboxFolder.Pnpm]: { label: "PNPM Home", path: this.getPnpmPath() },
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
return (Object.keys(foldersRecord) as SandboxFolder[]).map((key) => ({
|
|
176
|
+
name: key,
|
|
177
|
+
...foldersRecord[key],
|
|
178
|
+
}));
|
|
179
|
+
}
|
|
68
180
|
}
|
package/src/handler-func.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { End } from "@pipelab/shared";
|
|
2
2
|
import { ensureNodeJS, ensurePNPM } from "./utils/remote";
|
|
3
|
-
import { Action, ActionRunner
|
|
3
|
+
import { Action, ActionRunner } from "./types/runner";
|
|
4
4
|
import { InputsDefinition } from "@pipelab/shared";
|
|
5
5
|
import { usePlugins } from "@pipelab/shared";
|
|
6
6
|
import { isRequired } from "@pipelab/shared";
|
|
7
7
|
import { mkdir, stat } from "node:fs/promises";
|
|
8
8
|
import { PipelabContext } from "./context";
|
|
9
9
|
import { useLogger } from "@pipelab/shared";
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
import { HandleListenerSendFn } from "./handlers";
|
|
12
12
|
import path from "node:path";
|
|
13
13
|
const { join } = path;
|
|
@@ -38,81 +38,6 @@ const checkParams = (definitionParams: InputsDefinition, elementParams: Record<s
|
|
|
38
38
|
}
|
|
39
39
|
};
|
|
40
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
41
|
export const handleActionExecute = async (
|
|
117
42
|
nodeId: string,
|
|
118
43
|
pluginId: string,
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { PipelabContext } from "../context";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { writeFile, readFile, unlink, mkdir, stat, readdir } from "node:fs/promises";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
3
|
+
import { writeFile, readFile, unlink, mkdir, stat, readdir, rm } from "node:fs/promises";
|
|
5
4
|
import { useLogger, BuildHistoryEntry, IBuildHistoryStorage, AppConfig } from "@pipelab/shared";
|
|
6
|
-
import { setupConfigFile } from "../config";
|
|
7
5
|
import checkDiskSpace from "check-disk-space";
|
|
8
6
|
import { getFolderSize } from "../utils/fs-extras";
|
|
7
|
+
import { SandboxFolder } from "@pipelab/constants";
|
|
9
8
|
|
|
10
9
|
// Simplified storage - one file per pipeline containing array of build entries
|
|
11
10
|
|
|
@@ -17,18 +16,11 @@ export class BuildHistoryStorage implements IBuildHistoryStorage {
|
|
|
17
16
|
}
|
|
18
17
|
|
|
19
18
|
private getStoragePath() {
|
|
20
|
-
return
|
|
19
|
+
return this.context.getConfigPath("pipelines");
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
private getPipelinePath(pipelineId: string): string {
|
|
24
|
-
|
|
25
|
-
// Replace invalid filename characters with underscores
|
|
26
|
-
const sanitizedId = pipelineId
|
|
27
|
-
.replace(/[/\:*?"<>|]/g, "_")
|
|
28
|
-
.replace(/__/g, "_") // Replace multiple underscores with single
|
|
29
|
-
.replace(/^_+|_+$/g, ""); // Remove leading/trailing underscores
|
|
30
|
-
|
|
31
|
-
return join(this.getStoragePath(), `pipeline-${sanitizedId}.json`);
|
|
23
|
+
return join(this.getStoragePath(), `${pipelineId}.history.json`);
|
|
32
24
|
}
|
|
33
25
|
|
|
34
26
|
private async ensureStoragePath(): Promise<void> {
|
|
@@ -65,51 +57,6 @@ export class BuildHistoryStorage implements IBuildHistoryStorage {
|
|
|
65
57
|
}
|
|
66
58
|
}
|
|
67
59
|
|
|
68
|
-
async applyRetentionPolicy(): Promise<void> {
|
|
69
|
-
try {
|
|
70
|
-
this.logger.logger().info("Applying build history retention policy...");
|
|
71
|
-
const settings = await setupConfigFile<AppConfig>("settings", { context: this.context });
|
|
72
|
-
const config = await settings.getConfig();
|
|
73
|
-
const policy = config?.buildHistory?.retentionPolicy;
|
|
74
|
-
|
|
75
|
-
if (!policy || !policy.enabled) {
|
|
76
|
-
this.logger.logger().info("Retention policy is disabled. Skipping.");
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const { maxAge, maxEntries } = policy;
|
|
81
|
-
const pipelineFiles = await this.getAllPipelineFiles();
|
|
82
|
-
|
|
83
|
-
for (const file of pipelineFiles) {
|
|
84
|
-
const pipelineId = file.replace("pipeline-", "").replace(".json", "");
|
|
85
|
-
let entries = await this.loadPipelineHistory(pipelineId);
|
|
86
|
-
const originalCount = entries.length;
|
|
87
|
-
|
|
88
|
-
// 1. Filter by maxAge
|
|
89
|
-
if (maxAge > 0) {
|
|
90
|
-
const minDate = Date.now() - maxAge * 24 * 60 * 60 * 1000;
|
|
91
|
-
entries = entries.filter((entry) => entry.createdAt >= minDate);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// 2. Filter by maxEntries (sort by date first to keep the newest)
|
|
95
|
-
if (maxEntries > 0 && entries.length > maxEntries) {
|
|
96
|
-
entries = entries.sort((a, b) => b.createdAt - a.createdAt).slice(0, maxEntries);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (entries.length < originalCount) {
|
|
100
|
-
this.logger
|
|
101
|
-
.logger()
|
|
102
|
-
.info(`[${pipelineId}] Pruned ${originalCount - entries.length} history entries.`);
|
|
103
|
-
await this.savePipelineHistory(pipelineId, entries);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
this.logger.logger().info("Retention policy applied successfully.");
|
|
107
|
-
} catch (error) {
|
|
108
|
-
this.logger.logger().error("Failed to apply retention policy:", error);
|
|
109
|
-
// We don't re-throw here as this is a background task and shouldn't crash the app
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
60
|
async save(entry: BuildHistoryEntry): Promise<void> {
|
|
114
61
|
try {
|
|
115
62
|
const entries = await this.loadPipelineHistory(entry.pipelineId);
|
|
@@ -125,11 +72,6 @@ export class BuildHistoryStorage implements IBuildHistoryStorage {
|
|
|
125
72
|
this.logger
|
|
126
73
|
.logger()
|
|
127
74
|
.info(`Saved build history entry: ${entry.id} for pipeline: ${entry.pipelineId}`);
|
|
128
|
-
|
|
129
|
-
// Apply retention policy after each save
|
|
130
|
-
this.applyRetentionPolicy().catch((err) => {
|
|
131
|
-
this.logger.logger().error("Failed to apply retention policy after save:", err);
|
|
132
|
-
});
|
|
133
75
|
} catch (error) {
|
|
134
76
|
this.logger.logger().error("Failed to save build history entry:", error);
|
|
135
77
|
throw new Error(`Failed to save build history entry: ${error}`);
|
|
@@ -145,7 +87,8 @@ export class BuildHistoryStorage implements IBuildHistoryStorage {
|
|
|
145
87
|
|
|
146
88
|
const files = await this.getAllPipelineFiles();
|
|
147
89
|
for (const file of files) {
|
|
148
|
-
const pId =
|
|
90
|
+
const pId = this.parsePipelineIdFromFilename(file);
|
|
91
|
+
if (!pId) continue;
|
|
149
92
|
const entries = await this.loadPipelineHistory(pId);
|
|
150
93
|
const entry = entries.find((e) => e.id === id);
|
|
151
94
|
if (entry) return entry;
|
|
@@ -162,7 +105,8 @@ export class BuildHistoryStorage implements IBuildHistoryStorage {
|
|
|
162
105
|
const files = await this.getAllPipelineFiles();
|
|
163
106
|
const allEntries: BuildHistoryEntry[] = [];
|
|
164
107
|
for (const file of files) {
|
|
165
|
-
const pipelineId =
|
|
108
|
+
const pipelineId = this.parsePipelineIdFromFilename(file);
|
|
109
|
+
if (!pipelineId) continue;
|
|
166
110
|
const entries = await this.loadPipelineHistory(pipelineId);
|
|
167
111
|
allEntries.push(...entries);
|
|
168
112
|
}
|
|
@@ -200,7 +144,8 @@ export class BuildHistoryStorage implements IBuildHistoryStorage {
|
|
|
200
144
|
} else {
|
|
201
145
|
const files = await this.getAllPipelineFiles();
|
|
202
146
|
for (const file of files) {
|
|
203
|
-
const pId =
|
|
147
|
+
const pId = this.parsePipelineIdFromFilename(file);
|
|
148
|
+
if (!pId) continue;
|
|
204
149
|
const entries = await this.loadPipelineHistory(pId);
|
|
205
150
|
const entryIndex = entries.findIndex((e) => e.id === id);
|
|
206
151
|
if (entryIndex >= 0) {
|
|
@@ -231,7 +176,8 @@ export class BuildHistoryStorage implements IBuildHistoryStorage {
|
|
|
231
176
|
} else {
|
|
232
177
|
const files = await this.getAllPipelineFiles();
|
|
233
178
|
for (const file of files) {
|
|
234
|
-
const pId =
|
|
179
|
+
const pId = this.parsePipelineIdFromFilename(file);
|
|
180
|
+
if (!pId) continue;
|
|
235
181
|
const entries = await this.loadPipelineHistory(pId);
|
|
236
182
|
const entryIndex = entries.findIndex((e) => e.id === id);
|
|
237
183
|
if (entryIndex >= 0) {
|
|
@@ -253,8 +199,26 @@ export class BuildHistoryStorage implements IBuildHistoryStorage {
|
|
|
253
199
|
try {
|
|
254
200
|
await this.ensureStoragePath();
|
|
255
201
|
const files = await this.getAllPipelineFiles();
|
|
256
|
-
|
|
202
|
+
const cachePathsToDelete = new Set<string>();
|
|
203
|
+
|
|
204
|
+
for (const file of files) {
|
|
205
|
+
const pipelineId = this.parsePipelineIdFromFilename(file);
|
|
206
|
+
if (pipelineId) {
|
|
207
|
+
const entries = await this.loadPipelineHistory(pipelineId);
|
|
208
|
+
for (const entry of entries) {
|
|
209
|
+
if (entry.cachePath) {
|
|
210
|
+
cachePathsToDelete.add(entry.cachePath);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
await unlink(join(this.getStoragePath(), file));
|
|
215
|
+
}
|
|
216
|
+
|
|
257
217
|
this.logger.logger().info("Cleared all build history");
|
|
218
|
+
|
|
219
|
+
for (const cachePath of cachePathsToDelete) {
|
|
220
|
+
await rm(cachePath, { recursive: true, force: true }).catch(() => {});
|
|
221
|
+
}
|
|
258
222
|
} catch (error) {
|
|
259
223
|
this.logger.logger().error("Failed to clear build history:", error);
|
|
260
224
|
throw new Error(`Failed to clear build history: ${error}`);
|
|
@@ -264,8 +228,19 @@ export class BuildHistoryStorage implements IBuildHistoryStorage {
|
|
|
264
228
|
async clearByPipeline(pipelineId: string): Promise<void> {
|
|
265
229
|
try {
|
|
266
230
|
const pipelinePath = this.getPipelinePath(pipelineId);
|
|
231
|
+
const entries = await this.loadPipelineHistory(pipelineId);
|
|
267
232
|
await unlink(pipelinePath);
|
|
268
233
|
this.logger.logger().info(`Cleared history for pipeline "${pipelineId}"`);
|
|
234
|
+
|
|
235
|
+
const cachePathsToDelete = new Set<string>();
|
|
236
|
+
for (const entry of entries) {
|
|
237
|
+
if (entry.cachePath) {
|
|
238
|
+
cachePathsToDelete.add(entry.cachePath);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
for (const cachePath of cachePathsToDelete) {
|
|
242
|
+
await rm(cachePath, { recursive: true, force: true }).catch(() => {});
|
|
243
|
+
}
|
|
269
244
|
} catch (error: any) {
|
|
270
245
|
if (error.code === "ENOENT") {
|
|
271
246
|
this.logger
|
|
@@ -285,15 +260,11 @@ export class BuildHistoryStorage implements IBuildHistoryStorage {
|
|
|
285
260
|
newestEntry?: number;
|
|
286
261
|
numberOfPipelines: number;
|
|
287
262
|
userDataPath: string;
|
|
288
|
-
retentionPolicy: {
|
|
289
|
-
enabled: boolean;
|
|
290
|
-
maxEntries: number;
|
|
291
|
-
maxAge: number;
|
|
292
|
-
};
|
|
293
263
|
disk: {
|
|
294
264
|
total: number;
|
|
295
265
|
free: number;
|
|
296
266
|
pipelab: number;
|
|
267
|
+
folders: Array<{ name: SandboxFolder; label: string; size: number }>;
|
|
297
268
|
};
|
|
298
269
|
}> {
|
|
299
270
|
try {
|
|
@@ -303,13 +274,15 @@ export class BuildHistoryStorage implements IBuildHistoryStorage {
|
|
|
303
274
|
const diskSpace = await checkDiskSpace(this.context.userDataPath);
|
|
304
275
|
const pipelabSize = await getFolderSize(this.context.userDataPath);
|
|
305
276
|
|
|
306
|
-
const
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
277
|
+
const folders = [];
|
|
278
|
+
for (const folder of this.context.getSandboxFolders()) {
|
|
279
|
+
const size = await getFolderSize(folder.path);
|
|
280
|
+
folders.push({
|
|
281
|
+
name: folder.name,
|
|
282
|
+
label: folder.label,
|
|
283
|
+
size,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
313
286
|
|
|
314
287
|
if (allEntries.length === 0) {
|
|
315
288
|
return {
|
|
@@ -317,15 +290,11 @@ export class BuildHistoryStorage implements IBuildHistoryStorage {
|
|
|
317
290
|
totalSize: 0,
|
|
318
291
|
numberOfPipelines: files.length,
|
|
319
292
|
userDataPath: this.context.userDataPath,
|
|
320
|
-
retentionPolicy: {
|
|
321
|
-
enabled: policy.enabled,
|
|
322
|
-
maxEntries: policy.maxEntries,
|
|
323
|
-
maxAge: policy.maxAge,
|
|
324
|
-
},
|
|
325
293
|
disk: {
|
|
326
294
|
total: diskSpace.size,
|
|
327
295
|
free: diskSpace.free,
|
|
328
296
|
pipelab: pipelabSize,
|
|
297
|
+
folders,
|
|
329
298
|
},
|
|
330
299
|
};
|
|
331
300
|
}
|
|
@@ -350,15 +319,11 @@ export class BuildHistoryStorage implements IBuildHistoryStorage {
|
|
|
350
319
|
newestEntry: sortedEntries[sortedEntries.length - 1]?.createdAt,
|
|
351
320
|
numberOfPipelines: files.length,
|
|
352
321
|
userDataPath: this.context.userDataPath,
|
|
353
|
-
retentionPolicy: {
|
|
354
|
-
enabled: policy.enabled,
|
|
355
|
-
maxEntries: policy.maxEntries,
|
|
356
|
-
maxAge: policy.maxAge,
|
|
357
|
-
},
|
|
358
322
|
disk: {
|
|
359
323
|
total: diskSpace.size,
|
|
360
324
|
free: diskSpace.free,
|
|
361
325
|
pipelab: pipelabSize,
|
|
326
|
+
folders,
|
|
362
327
|
},
|
|
363
328
|
};
|
|
364
329
|
} catch (error) {
|
|
@@ -371,9 +336,14 @@ export class BuildHistoryStorage implements IBuildHistoryStorage {
|
|
|
371
336
|
try {
|
|
372
337
|
await this.ensureStoragePath();
|
|
373
338
|
const files = await readdir(this.getStoragePath());
|
|
374
|
-
return files.filter((file) => file.
|
|
339
|
+
return files.filter((file) => file.endsWith(".history.json"));
|
|
375
340
|
} catch (error) {
|
|
376
341
|
return [];
|
|
377
342
|
}
|
|
378
343
|
}
|
|
344
|
+
|
|
345
|
+
private parsePipelineIdFromFilename(filename: string): string | null {
|
|
346
|
+
const match = filename.match(/^(.+)\.history\.json$/);
|
|
347
|
+
return match ? match[1] : null;
|
|
348
|
+
}
|
|
379
349
|
}
|