@pipelab/core-node 1.0.0-beta.18 → 1.0.0-beta.19
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/CHANGELOG.md +9 -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 +29 -18
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +784 -303
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/config.test.ts +59 -11
- package/src/config.ts +71 -32
- package/src/context.ts +42 -4
- package/src/handlers/config.ts +252 -72
- package/src/handlers/engine.ts +2 -2
- package/src/handlers/index.ts +2 -0
- package/src/handlers/migration.ts +598 -0
- package/src/index.ts +9 -2
- package/src/plugins-registry.ts +2 -2
- package/src/runner.ts +0 -3
- package/src/server.ts +0 -3
- package/src/utils.ts +2 -2
- package/dist/config-CEkOf95p.mjs +0 -2
- package/src/migrations.ts +0 -60
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.19",
|
|
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.17",
|
|
44
|
+
"@pipelab/shared": "1.0.0-beta.15"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@types/adm-zip": "0.5.7",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"tsdown": "0.21.2",
|
|
57
57
|
"typescript": "^5.0.0",
|
|
58
58
|
"vitest": "3.1.4",
|
|
59
|
-
"@pipelab/tsconfig": "1.0.0-beta.
|
|
59
|
+
"@pipelab/tsconfig": "1.0.0-beta.15"
|
|
60
60
|
},
|
|
61
61
|
"scripts": {
|
|
62
62
|
"build": "tsdown",
|
package/src/config.test.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { describe, test, expect, beforeEach, vi } from "vitest";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
setupConfigFile,
|
|
4
|
+
setupProjectsConfigFile,
|
|
5
|
+
deletePipelineConfigFileByName,
|
|
6
|
+
deletePipelineConfigFileByPath,
|
|
7
|
+
} from "./config";
|
|
3
8
|
import { FileRepo } from "@pipelab/shared";
|
|
4
9
|
import { PipelabContext } from "./context";
|
|
5
10
|
import fs from "node:fs/promises";
|
|
@@ -39,7 +44,7 @@ describe("setupConfigFile & Backup Creation", () => {
|
|
|
39
44
|
const configDir = context.getConfigPath();
|
|
40
45
|
await fs.mkdir(configDir, { recursive: true });
|
|
41
46
|
|
|
42
|
-
const projectsFilePath =
|
|
47
|
+
const projectsFilePath = context.getProjectsPath();
|
|
43
48
|
|
|
44
49
|
const v1Config = {
|
|
45
50
|
version: "1.0.0",
|
|
@@ -56,7 +61,7 @@ describe("setupConfigFile & Backup Creation", () => {
|
|
|
56
61
|
await fs.writeFile(projectsFilePath, JSON.stringify(v1Config));
|
|
57
62
|
|
|
58
63
|
// 2. Initialize setupConfigFile
|
|
59
|
-
const configInstance = await
|
|
64
|
+
const configInstance = await setupProjectsConfigFile(context);
|
|
60
65
|
|
|
61
66
|
// Verify file exists
|
|
62
67
|
expect(existsSync(projectsFilePath)).toBe(true);
|
|
@@ -82,12 +87,12 @@ describe("setupConfigFile & Backup Creation", () => {
|
|
|
82
87
|
|
|
83
88
|
test("should create default config file if missing on setup", async () => {
|
|
84
89
|
const configDir = context.getConfigPath();
|
|
85
|
-
const projectsFilePath =
|
|
90
|
+
const projectsFilePath = context.getProjectsPath();
|
|
86
91
|
|
|
87
92
|
expect(existsSync(projectsFilePath)).toBe(false);
|
|
88
93
|
|
|
89
94
|
// Initialize setupConfigFile
|
|
90
|
-
await
|
|
95
|
+
await setupProjectsConfigFile(context);
|
|
91
96
|
|
|
92
97
|
// Should create file with default value
|
|
93
98
|
expect(existsSync(projectsFilePath)).toBe(true);
|
|
@@ -100,11 +105,11 @@ describe("setupConfigFile & Backup Creation", () => {
|
|
|
100
105
|
test("should fallback to default config and preserve corrupted file if parsing fails", async () => {
|
|
101
106
|
const configDir = context.getConfigPath();
|
|
102
107
|
await fs.mkdir(configDir, { recursive: true });
|
|
103
|
-
const projectsFilePath =
|
|
108
|
+
const projectsFilePath = context.getProjectsPath();
|
|
104
109
|
|
|
105
110
|
await fs.writeFile(projectsFilePath, "{ corrupted json... }");
|
|
106
111
|
|
|
107
|
-
const configInstance = await
|
|
112
|
+
const configInstance = await setupProjectsConfigFile(context);
|
|
108
113
|
const config = await configInstance.getConfig();
|
|
109
114
|
|
|
110
115
|
expect(config.version).toBe("3.0.0");
|
|
@@ -124,7 +129,7 @@ describe("setupConfigFile & Backup Creation", () => {
|
|
|
124
129
|
test("should fallback to default config and preserve file if migration throws", async () => {
|
|
125
130
|
const configDir = context.getConfigPath();
|
|
126
131
|
await fs.mkdir(configDir, { recursive: true });
|
|
127
|
-
const projectsFilePath =
|
|
132
|
+
const projectsFilePath = context.getProjectsPath();
|
|
128
133
|
|
|
129
134
|
const customMigrator = {
|
|
130
135
|
defaultValue: { version: "2.0.0", projects: [], pipelines: [] } as any,
|
|
@@ -135,7 +140,7 @@ describe("setupConfigFile & Backup Creation", () => {
|
|
|
135
140
|
|
|
136
141
|
await fs.writeFile(projectsFilePath, JSON.stringify({ version: "1.0.0", invalidData: true }));
|
|
137
142
|
|
|
138
|
-
const configInstance = await setupConfigFile<any>(
|
|
143
|
+
const configInstance = await setupConfigFile<any>(context.getProjectsPath(), {
|
|
139
144
|
context,
|
|
140
145
|
migrator: customMigrator,
|
|
141
146
|
});
|
|
@@ -156,7 +161,7 @@ describe("setupConfigFile & Backup Creation", () => {
|
|
|
156
161
|
});
|
|
157
162
|
|
|
158
163
|
test("should save config to disk via setConfig", async () => {
|
|
159
|
-
const configInstance = await
|
|
164
|
+
const configInstance = await setupProjectsConfigFile(context);
|
|
160
165
|
const initialConfig = await configInstance.getConfig();
|
|
161
166
|
|
|
162
167
|
const newConfig: FileRepo = {
|
|
@@ -176,9 +181,52 @@ describe("setupConfigFile & Backup Creation", () => {
|
|
|
176
181
|
expect(success).toBe(true);
|
|
177
182
|
|
|
178
183
|
const savedContent = JSON.parse(
|
|
179
|
-
await fs.readFile(
|
|
184
|
+
await fs.readFile(context.getProjectsPath(), "utf8"),
|
|
180
185
|
);
|
|
181
186
|
expect(savedContent.pipelines).toHaveLength(1);
|
|
182
187
|
expect(savedContent.pipelines[0].id).toBe("pipeline-new");
|
|
183
188
|
});
|
|
184
189
|
});
|
|
190
|
+
|
|
191
|
+
describe("deletePipelineConfigFile", () => {
|
|
192
|
+
let tempDir: string;
|
|
193
|
+
let context: PipelabContext;
|
|
194
|
+
|
|
195
|
+
beforeEach(() => {
|
|
196
|
+
vol.reset();
|
|
197
|
+
tempDir = "/tmp/pipelab-test-config";
|
|
198
|
+
context = new PipelabContext({ userDataPath: tempDir });
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("should delete pipeline config file using relative name", async () => {
|
|
202
|
+
const configDir = context.getConfigPath();
|
|
203
|
+
await fs.mkdir(configDir, { recursive: true });
|
|
204
|
+
|
|
205
|
+
const pipelineFilePath = path.join(configDir, "my-pipeline.json");
|
|
206
|
+
await fs.writeFile(pipelineFilePath, JSON.stringify({ name: "My Pipeline" }));
|
|
207
|
+
expect(existsSync(pipelineFilePath)).toBe(true);
|
|
208
|
+
|
|
209
|
+
await deletePipelineConfigFileByName("my-pipeline", context);
|
|
210
|
+
expect(existsSync(pipelineFilePath)).toBe(false);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test("should delete pipeline config file using absolute path", async () => {
|
|
214
|
+
const configDir = context.getConfigPath();
|
|
215
|
+
await fs.mkdir(configDir, { recursive: true });
|
|
216
|
+
|
|
217
|
+
const pipelineFilePath = path.join(configDir, "another-pipeline.json");
|
|
218
|
+
await fs.writeFile(pipelineFilePath, JSON.stringify({ name: "Another Pipeline" }));
|
|
219
|
+
expect(existsSync(pipelineFilePath)).toBe(true);
|
|
220
|
+
|
|
221
|
+
await deletePipelineConfigFileByPath(pipelineFilePath, context);
|
|
222
|
+
expect(existsSync(pipelineFilePath)).toBe(false);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test("should not throw error when attempting to delete non-existent pipeline file", async () => {
|
|
226
|
+
const nonExistentPath = context.getConfigPath("does-not-exist.json");
|
|
227
|
+
expect(existsSync(nonExistentPath)).toBe(false);
|
|
228
|
+
|
|
229
|
+
await expect(deletePipelineConfigFileByName("does-not-exist", context)).resolves.not.toThrow();
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
package/src/config.ts
CHANGED
|
@@ -2,28 +2,26 @@ import { PipelabContext } from "./context";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { ensure } from "./utils/fs-extras";
|
|
4
4
|
import fs from "node:fs/promises";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
import {
|
|
6
|
+
useLogger,
|
|
7
|
+
Migrator,
|
|
8
|
+
AppConfig,
|
|
9
|
+
ConnectionsConfig,
|
|
10
|
+
FileRepo,
|
|
11
|
+
SavedFile,
|
|
12
|
+
appSettingsMigrator,
|
|
13
|
+
connectionsMigrator,
|
|
14
|
+
fileRepoMigrations,
|
|
15
|
+
savedFileMigrator,
|
|
16
|
+
} from "@pipelab/shared";
|
|
11
17
|
|
|
12
18
|
export const setupConfigFile = async <T>(
|
|
13
|
-
|
|
14
|
-
options: { context: PipelabContext; migrator
|
|
19
|
+
filesPath: string,
|
|
20
|
+
options: { context: PipelabContext; migrator: Migrator<T> },
|
|
15
21
|
) => {
|
|
16
22
|
const ctx = options.context;
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
if (!migrator) {
|
|
20
|
-
throw new Error(
|
|
21
|
-
`No migrator found for configuration: ${name}. All managed files must have a migration schema.`,
|
|
22
|
-
);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const isAbsolutePath = path.isAbsolute(name);
|
|
26
|
-
const filesPath = isAbsolutePath ? name : ctx.getConfigPath(`${name}.json`);
|
|
23
|
+
const parsedPath = path.parse(filesPath);
|
|
24
|
+
const migrator = options.migrator;
|
|
27
25
|
|
|
28
26
|
await ensure(filesPath, JSON.stringify(migrator.defaultValue));
|
|
29
27
|
|
|
@@ -34,7 +32,7 @@ export const setupConfigFile = async <T>(
|
|
|
34
32
|
await fs.writeFile(filesPath, JSON.stringify(config));
|
|
35
33
|
return true;
|
|
36
34
|
} catch (e) {
|
|
37
|
-
logger().error(`Error saving config ${name}:`, e);
|
|
35
|
+
logger().error(`Error saving config ${parsedPath.name}:`, e);
|
|
38
36
|
return false;
|
|
39
37
|
}
|
|
40
38
|
},
|
|
@@ -50,7 +48,7 @@ export const setupConfigFile = async <T>(
|
|
|
50
48
|
originalJson = JSON.parse(content);
|
|
51
49
|
}
|
|
52
50
|
} catch (e) {
|
|
53
|
-
logger().error(`Error reading or parsing config ${name}:`, e);
|
|
51
|
+
logger().error(`Error reading or parsing config ${parsedPath.name}:`, e);
|
|
54
52
|
parseFailed = true;
|
|
55
53
|
}
|
|
56
54
|
|
|
@@ -61,14 +59,13 @@ export const setupConfigFile = async <T>(
|
|
|
61
59
|
json = await migrator.migrate(originalJson, {
|
|
62
60
|
debug: false,
|
|
63
61
|
onStep: async (state: any, version: string) => {
|
|
64
|
-
const
|
|
65
|
-
const versionedPath = ctx.getConfigPath(`${parsedPath.name}.v${version}.json`);
|
|
62
|
+
const versionedPath = path.join(parsedPath.dir, `${parsedPath.name}.v${version}.json`);
|
|
66
63
|
try {
|
|
67
64
|
await fs.writeFile(versionedPath, JSON.stringify(state));
|
|
68
|
-
logger().info(`Intermediate backup created for ${name} at ${versionedPath}`);
|
|
65
|
+
logger().info(`Intermediate backup created for ${parsedPath.name} at ${versionedPath}`);
|
|
69
66
|
} catch (e) {
|
|
70
67
|
logger().error(
|
|
71
|
-
`Failed to create intermediate backup for ${name} at v${version}:`,
|
|
68
|
+
`Failed to create intermediate backup for ${parsedPath.name} at v${version}:`,
|
|
72
69
|
e,
|
|
73
70
|
);
|
|
74
71
|
}
|
|
@@ -78,7 +75,7 @@ export const setupConfigFile = async <T>(
|
|
|
78
75
|
json = migrator.defaultValue;
|
|
79
76
|
}
|
|
80
77
|
} catch (e) {
|
|
81
|
-
logger().error(`Error migrating config ${name}:`, e);
|
|
78
|
+
logger().error(`Error migrating config ${parsedPath.name}:`, e);
|
|
82
79
|
migrationFailed = true;
|
|
83
80
|
json = migrator.defaultValue;
|
|
84
81
|
}
|
|
@@ -92,9 +89,9 @@ export const setupConfigFile = async <T>(
|
|
|
92
89
|
if (shouldSaveBack) {
|
|
93
90
|
if (parseFailed || migrationFailed) {
|
|
94
91
|
try {
|
|
95
|
-
const parsedPath = path.parse(filesPath);
|
|
96
92
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
97
|
-
const corruptedPath =
|
|
93
|
+
const corruptedPath = path.join(
|
|
94
|
+
parsedPath.dir,
|
|
98
95
|
`${parsedPath.name}.corrupted.${timestamp}.json`,
|
|
99
96
|
);
|
|
100
97
|
const backupContent = parseFailed
|
|
@@ -103,14 +100,14 @@ export const setupConfigFile = async <T>(
|
|
|
103
100
|
await fs.writeFile(corruptedPath, backupContent);
|
|
104
101
|
logger().info(`Corrupted config file preserved at ${corruptedPath}`);
|
|
105
102
|
} catch (e) {
|
|
106
|
-
logger().error(`Failed to backup corrupted config ${name}:`, e);
|
|
103
|
+
logger().error(`Failed to backup corrupted config ${parsedPath.name}:`, e);
|
|
107
104
|
}
|
|
108
105
|
}
|
|
109
106
|
|
|
110
107
|
try {
|
|
111
108
|
await fs.writeFile(filesPath, JSON.stringify(json));
|
|
112
109
|
} catch (e) {
|
|
113
|
-
logger().error(`Error saving migrated config ${name}:`, e);
|
|
110
|
+
logger().error(`Error saving migrated config ${parsedPath.name}:`, e);
|
|
114
111
|
}
|
|
115
112
|
}
|
|
116
113
|
|
|
@@ -119,9 +116,51 @@ export const setupConfigFile = async <T>(
|
|
|
119
116
|
};
|
|
120
117
|
};
|
|
121
118
|
|
|
122
|
-
export const
|
|
123
|
-
|
|
124
|
-
|
|
119
|
+
export const setupSettingsConfigFile = (context: PipelabContext) => {
|
|
120
|
+
return setupConfigFile<AppConfig>(context.getSettingsPath(), {
|
|
121
|
+
context,
|
|
122
|
+
migrator: appSettingsMigrator,
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export const setupConnectionsConfigFile = (context: PipelabContext) => {
|
|
127
|
+
return setupConfigFile<ConnectionsConfig>(context.getConnectionsPath(), {
|
|
128
|
+
context,
|
|
129
|
+
migrator: connectionsMigrator,
|
|
130
|
+
});
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export const setupProjectsConfigFile = (context: PipelabContext) => {
|
|
134
|
+
return setupConfigFile<FileRepo>(context.getProjectsPath(), {
|
|
135
|
+
context,
|
|
136
|
+
migrator: fileRepoMigrations,
|
|
137
|
+
});
|
|
138
|
+
};
|
|
125
139
|
|
|
140
|
+
export const setupPipelineConfigFileByName = (name: string, context: PipelabContext) => {
|
|
141
|
+
const filesPath = context.getConfigPath(`${name}.json`);
|
|
142
|
+
return setupConfigFile<SavedFile>(filesPath, {
|
|
143
|
+
context,
|
|
144
|
+
migrator: savedFileMigrator,
|
|
145
|
+
});
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
export const setupPipelineConfigFileByPath = (absolutePath: string, context: PipelabContext) => {
|
|
149
|
+
return setupConfigFile<SavedFile>(absolutePath, {
|
|
150
|
+
context,
|
|
151
|
+
migrator: savedFileMigrator,
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const deleteConfigFile = async (filesPath: string) => {
|
|
126
156
|
await fs.rm(filesPath, { force: true });
|
|
127
157
|
};
|
|
158
|
+
|
|
159
|
+
export const deletePipelineConfigFileByName = async (name: string, context: PipelabContext) => {
|
|
160
|
+
const filesPath = context.getConfigPath(`${name}.json`);
|
|
161
|
+
await deleteConfigFile(filesPath);
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
export const deletePipelineConfigFileByPath = async (absolutePath: string, context: PipelabContext) => {
|
|
165
|
+
await deleteConfigFile(absolutePath);
|
|
166
|
+
};
|
package/src/context.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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
5
|
import { mkdir, realpath, mkdtemp } from "node:fs/promises";
|
|
6
6
|
import { SandboxFolder, DEFAULT_NODE_VERSION, DEFAULT_PNPM_VERSION } from "@pipelab/constants";
|
|
7
7
|
|
|
@@ -85,8 +85,44 @@ export class PipelabContext {
|
|
|
85
85
|
return join(this.userDataPath, "config", ...subpaths);
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
getSettingsPath() {
|
|
89
|
+
return this.getConfigPath("settings.json");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
getConnectionsPath() {
|
|
93
|
+
return this.getConfigPath("connections.json");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
getProjectsPath() {
|
|
97
|
+
return this.getConfigPath("projects.json");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private _cachedSettings: any = null;
|
|
101
|
+
private _cachedSettingsTime: number = 0;
|
|
102
|
+
|
|
103
|
+
private getSettings() {
|
|
104
|
+
const settingsPath = this.getSettingsPath();
|
|
105
|
+
if (!existsSync(settingsPath)) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
const now = Date.now();
|
|
109
|
+
if (this._cachedSettings && now - this._cachedSettingsTime < 2000) {
|
|
110
|
+
return this._cachedSettings;
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
const content = readFileSync(settingsPath, "utf8");
|
|
114
|
+
this._cachedSettings = JSON.parse(content);
|
|
115
|
+
this._cachedSettingsTime = now;
|
|
116
|
+
return this._cachedSettings;
|
|
117
|
+
} catch (e) {
|
|
118
|
+
return this._cachedSettings;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
88
122
|
getTempPath(...subpaths: string[]) {
|
|
89
|
-
|
|
123
|
+
const settings = this.getSettings();
|
|
124
|
+
const base = settings?.tempFolder || join(this.userDataPath, "temp");
|
|
125
|
+
return join(base, ...subpaths);
|
|
90
126
|
}
|
|
91
127
|
|
|
92
128
|
async createTempFolder(prefix = "pipelab-") {
|
|
@@ -99,10 +135,12 @@ export class PipelabContext {
|
|
|
99
135
|
getCachePath(): string;
|
|
100
136
|
getCachePath(folder: CacheFolderType, ...subpaths: string[]): string;
|
|
101
137
|
getCachePath(folder?: CacheFolderType, ...subpaths: string[]) {
|
|
138
|
+
const settings = this.getSettings();
|
|
139
|
+
const base = settings?.cacheFolder || join(this.userDataPath, "cache");
|
|
102
140
|
if (!folder) {
|
|
103
|
-
return
|
|
141
|
+
return base;
|
|
104
142
|
}
|
|
105
|
-
return join(
|
|
143
|
+
return join(base, folder, ...subpaths);
|
|
106
144
|
}
|
|
107
145
|
|
|
108
146
|
getPnpmPath(...subpaths: string[]) {
|