@pipelab/core-node 1.0.1-latest.35 → 1.0.1-latest.37
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 +18 -0
- package/dist/index.d.mts +2 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +49541 -49500
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/ipc-core.ts +12 -5
- package/src/plugins-registry.ts +9 -4
- package/src/server.ts +15 -1
- package/src/utils/remote.ts +103 -66
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pipelab/core-node",
|
|
3
|
-
"version": "1.0.1-latest.
|
|
3
|
+
"version": "1.0.1-latest.37",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The Pipelab automation engine for Node.js",
|
|
6
6
|
"license": "FSL-1.1-MIT",
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"type-fest": "4.39.0",
|
|
40
40
|
"ws": "8.18.3",
|
|
41
41
|
"yauzl": "2.10.0",
|
|
42
|
-
"@pipelab/constants": "1.0.1-latest.
|
|
43
|
-
"@pipelab/shared": "2.0.1-latest.
|
|
42
|
+
"@pipelab/constants": "1.0.1-latest.33",
|
|
43
|
+
"@pipelab/shared": "2.0.1-latest.34"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/adm-zip": "0.5.7",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"@types/yauzl": "2.10.3",
|
|
54
54
|
"tsdown": "0.21.2",
|
|
55
55
|
"typescript": "^5.0.0",
|
|
56
|
-
"@pipelab/tsconfig": "1.0.1-latest.
|
|
56
|
+
"@pipelab/tsconfig": "1.0.1-latest.33"
|
|
57
57
|
},
|
|
58
58
|
"scripts": {
|
|
59
59
|
"build": "tsdown",
|
package/src/ipc-core.ts
CHANGED
|
@@ -45,11 +45,18 @@ export const useAPI = () => {
|
|
|
45
45
|
requestId,
|
|
46
46
|
events,
|
|
47
47
|
};
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
|
|
49
|
+
if (ws.readyState === WSWebSocket.OPEN) {
|
|
50
|
+
ws.send(JSON.stringify(response), (error) => {
|
|
51
|
+
if (error) {
|
|
52
|
+
logger().error("Failed to send WebSocket response:", error);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
} else {
|
|
56
|
+
logger().debug(
|
|
57
|
+
`Cannot send response to ${requestId}: WebSocket is not open (state: ${ws.readyState})`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
53
60
|
return Promise.resolve();
|
|
54
61
|
};
|
|
55
62
|
|
package/src/plugins-registry.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { join } from "node:path";
|
|
|
4
4
|
import { readdir } from "node:fs/promises";
|
|
5
5
|
import { existsSync } from "node:fs";
|
|
6
6
|
import { isDev, projectRoot, PipelabContext } from "./context";
|
|
7
|
+
import { sendStartupProgress } from "./server";
|
|
7
8
|
|
|
8
9
|
const DEFAULT_PLUGIN_IDS = [
|
|
9
10
|
"construct",
|
|
@@ -53,11 +54,15 @@ export const loadPipelabPlugin = async (id: string, options: { context: PipelabC
|
|
|
53
54
|
|
|
54
55
|
export const builtInPlugins = async (options: { context: PipelabContext }) => {
|
|
55
56
|
console.log("[Plugins] Finalizing default plugins list...");
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
const plugins = [];
|
|
58
|
+
for (const id of DEFAULT_PLUGIN_IDS) {
|
|
59
|
+
sendStartupProgress(`Loading plugin: ${id}`);
|
|
60
|
+
const plugin = await loadPipelabPlugin(id, options);
|
|
61
|
+
if (plugin) {
|
|
62
|
+
plugins.push(plugin);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
59
65
|
|
|
60
|
-
const plugins = await Promise.all(promises);
|
|
61
66
|
const filtered = plugins.filter(Boolean).flat();
|
|
62
67
|
console.log(`[Plugins] Successfully loaded ${filtered.length} default plugins`);
|
|
63
68
|
return filtered;
|
package/src/server.ts
CHANGED
|
@@ -21,6 +21,14 @@ export interface ServeOptions {
|
|
|
21
21
|
pnpmPath?: string;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
export const sendStartupProgress = (message: string) => {
|
|
25
|
+
console.log(`[Startup Progress] ${message}`);
|
|
26
|
+
webSocketServer.broadcast("startup:progress", {
|
|
27
|
+
type: "progress",
|
|
28
|
+
data: { message },
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
|
|
24
32
|
export async function serveCommand(options: ServeOptions, version: string, _dirname: string) {
|
|
25
33
|
if (!options.userData) throw new Error("userDataPath is required for serveCommand");
|
|
26
34
|
const context = new PipelabContext({
|
|
@@ -69,12 +77,18 @@ export async function serveCommand(options: ServeOptions, version: string, _dirn
|
|
|
69
77
|
console.log(`UI available at http://localhost:${options.port}`);
|
|
70
78
|
}
|
|
71
79
|
|
|
80
|
+
// Start the server EARLY so the UI can connect and receive progress updates
|
|
81
|
+
await webSocketServer.start(Number(options.port), server);
|
|
82
|
+
|
|
72
83
|
await registerAllHandlers({
|
|
73
84
|
version,
|
|
74
85
|
context,
|
|
75
86
|
});
|
|
76
87
|
registerMigrationHandlers(context);
|
|
77
88
|
|
|
78
|
-
|
|
89
|
+
if (process.send) {
|
|
90
|
+
process.send({ type: "ready" });
|
|
91
|
+
}
|
|
92
|
+
|
|
79
93
|
return server;
|
|
80
94
|
}
|
package/src/utils/remote.ts
CHANGED
|
@@ -6,8 +6,33 @@ import pacote from "pacote";
|
|
|
6
6
|
import semver from "semver";
|
|
7
7
|
import { isDev, projectRoot, PipelabContext } from "../context";
|
|
8
8
|
import { execa } from "execa";
|
|
9
|
+
import { sendStartupProgress } from "../server";
|
|
9
10
|
import { downloadFile, extractZip, extractTarGz, generateTempFolder } from "./fs-extras";
|
|
10
11
|
|
|
12
|
+
/**
|
|
13
|
+
* In-memory lock to prevent concurrent operations on the same resource (e.g., downloading Node.js).
|
|
14
|
+
*/
|
|
15
|
+
const activeOperations = new Map<string, Promise<any>>();
|
|
16
|
+
|
|
17
|
+
async function withLock<T>(key: string, fn: () => Promise<T>): Promise<T> {
|
|
18
|
+
const existing = activeOperations.get(key);
|
|
19
|
+
if (existing) {
|
|
20
|
+
console.log(`[Lock] Waiting for concurrent operation on: ${key}`);
|
|
21
|
+
return existing;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const promise = (async () => {
|
|
25
|
+
try {
|
|
26
|
+
return await fn();
|
|
27
|
+
} finally {
|
|
28
|
+
activeOperations.delete(key);
|
|
29
|
+
}
|
|
30
|
+
})();
|
|
31
|
+
|
|
32
|
+
activeOperations.set(key, promise);
|
|
33
|
+
return promise;
|
|
34
|
+
}
|
|
35
|
+
|
|
11
36
|
export type FetchOptions = {
|
|
12
37
|
installDeps?: boolean;
|
|
13
38
|
signal?: AbortSignal;
|
|
@@ -72,36 +97,39 @@ export async function fetchPackage(
|
|
|
72
97
|
}
|
|
73
98
|
}
|
|
74
99
|
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
100
|
+
const lockKey = `package:${packageName}:${resolvedVersion}`;
|
|
101
|
+
return withLock(lockKey, async () => {
|
|
102
|
+
const packageDir = join(baseDir, resolvedVersion);
|
|
103
|
+
if (!existsSync(packageDir)) {
|
|
104
|
+
console.log(`[Fetcher] ${packageName}@${resolvedVersion}: Downloading to ${packageDir}...`);
|
|
105
|
+
await mkdir(packageDir, { recursive: true });
|
|
106
|
+
await pacote.extract(`${packageName}@${resolvedVersion}`, packageDir);
|
|
107
|
+
}
|
|
81
108
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
109
|
+
// 2. Resolve entry point from package.json for downloaded package
|
|
110
|
+
let entryPoint: string | undefined;
|
|
111
|
+
try {
|
|
112
|
+
const pkgPath = join(packageDir, "package.json");
|
|
113
|
+
if (existsSync(pkgPath)) {
|
|
114
|
+
const pkg = JSON.parse(await readFile(pkgPath, "utf-8"));
|
|
115
|
+
const main = pkg.module || pkg.main || pkg.publishConfig?.module || pkg.publishConfig?.main;
|
|
116
|
+
if (main) {
|
|
117
|
+
entryPoint = join(packageDir, main);
|
|
118
|
+
} else if (pkg.bin) {
|
|
119
|
+
const binFile = typeof pkg.bin === "string" ? pkg.bin : Object.values(pkg.bin)[0];
|
|
120
|
+
if (binFile) entryPoint = join(packageDir, binFile as string);
|
|
121
|
+
}
|
|
94
122
|
}
|
|
123
|
+
} catch (e) {
|
|
124
|
+
console.warn(`[Fetcher] ${packageName}: Failed to parse package.json for entry point resolution:`, e);
|
|
95
125
|
}
|
|
96
|
-
} catch (e) {
|
|
97
|
-
console.warn(`[Fetcher] ${packageName}: Failed to parse package.json for entry point resolution:`, e);
|
|
98
|
-
}
|
|
99
126
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
127
|
+
if (options?.installDeps) {
|
|
128
|
+
await installDependencies(packageDir, packageName, options);
|
|
129
|
+
}
|
|
103
130
|
|
|
104
|
-
|
|
131
|
+
return { packageDir, resolvedVersion, entryPoint };
|
|
132
|
+
});
|
|
105
133
|
}
|
|
106
134
|
|
|
107
135
|
/**
|
|
@@ -148,63 +176,72 @@ export async function runPnpm(
|
|
|
148
176
|
* Installs a specific version of Node.js if not already present.
|
|
149
177
|
*/
|
|
150
178
|
export async function ensureNodeJS(version: string, options: { context: PipelabContext }) {
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
179
|
+
const lockKey = `node:${version}`;
|
|
180
|
+
return withLock(lockKey, async () => {
|
|
181
|
+
const ctx = options.context;
|
|
182
|
+
const nodeDir = ctx.getThirdPartyPath("node", version);
|
|
183
|
+
const isWindows = process.platform === "win32";
|
|
184
|
+
const executableName = isWindows ? "node.exe" : "bin/node";
|
|
185
|
+
const finalNodePath = join(nodeDir, executableName);
|
|
156
186
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
187
|
+
try {
|
|
188
|
+
await access(finalNodePath, constants.X_OK);
|
|
189
|
+
return finalNodePath;
|
|
190
|
+
} catch (e) {}
|
|
161
191
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
192
|
+
const arch = process.arch === "x64" ? "x64" : process.arch === "arm64" ? "arm64" : "x86";
|
|
193
|
+
const platform = isWindows ? "win" : process.platform === "darwin" ? "osx" : "linux";
|
|
194
|
+
const extension = isWindows ? "zip" : "tar.gz";
|
|
195
|
+
const downloadPlatform = platform === "osx" ? "darwin" : platform;
|
|
166
196
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
197
|
+
const fileName = `node-v${version}-${downloadPlatform}-${arch}.${extension}`;
|
|
198
|
+
const downloadUrl = `https://nodejs.org/dist/v${version}/${fileName}`;
|
|
199
|
+
const tempDir = await generateTempFolder(tmpdir());
|
|
200
|
+
const archivePath = join(tempDir, fileName);
|
|
171
201
|
|
|
172
|
-
|
|
173
|
-
|
|
202
|
+
sendStartupProgress(`Downloading Node.js v${version}...`);
|
|
203
|
+
console.log(`Downloading Node.js from ${downloadUrl}...`);
|
|
204
|
+
await downloadFile(downloadUrl, archivePath);
|
|
174
205
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
206
|
+
sendStartupProgress(`Extracting Node.js v${version}...`);
|
|
207
|
+
console.log(`Extracting Node.js to ${tempDir}...`);
|
|
208
|
+
const extractTempDir = join(tempDir, "extracted");
|
|
209
|
+
await mkdir(extractTempDir, { recursive: true });
|
|
178
210
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
211
|
+
if (extension === "zip") {
|
|
212
|
+
await extractZip(archivePath, extractTempDir);
|
|
213
|
+
} else {
|
|
214
|
+
await extractTarGz(archivePath, extractTempDir);
|
|
215
|
+
}
|
|
184
216
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
217
|
+
const extractedEntries = await readdir(extractTempDir);
|
|
218
|
+
const nodeSubDir = extractedEntries.find((entry) => entry.startsWith(`node-v${version}`));
|
|
219
|
+
if (!nodeSubDir) throw new Error(`Could not find extracted Node.js directory`);
|
|
188
220
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
221
|
+
const sourceDir = join(extractTempDir, nodeSubDir);
|
|
222
|
+
await mkdir(dirname(nodeDir), { recursive: true });
|
|
223
|
+
await rm(nodeDir, { recursive: true, force: true });
|
|
224
|
+
await cp(sourceDir, nodeDir, { recursive: true });
|
|
225
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
194
226
|
|
|
195
|
-
|
|
196
|
-
|
|
227
|
+
if (!isWindows) await chmod(finalNodePath, 0o755).catch(() => {});
|
|
228
|
+
return finalNodePath;
|
|
229
|
+
});
|
|
197
230
|
}
|
|
198
231
|
|
|
199
232
|
/**
|
|
200
233
|
* Installs the PNPM package from npm if not already present.
|
|
201
234
|
*/
|
|
202
235
|
export async function ensurePNPM(version = "10.12.0", options: { context: PipelabContext }) {
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
236
|
+
const lockKey = `pnpm:${version}`;
|
|
237
|
+
return withLock(lockKey, async () => {
|
|
238
|
+
sendStartupProgress(`Checking PNPM v${version}...`);
|
|
239
|
+
const ctx = options.context;
|
|
240
|
+
const { packageDir } = await fetchPackage("pnpm", version, {
|
|
241
|
+
context: ctx,
|
|
242
|
+
});
|
|
243
|
+
return join(packageDir, "bin", "pnpm.cjs");
|
|
206
244
|
});
|
|
207
|
-
return join(packageDir, "bin", "pnpm.cjs");
|
|
208
245
|
}
|
|
209
246
|
|
|
210
247
|
async function installDependencies(packageDir: string, packageName: string, options: FetchOptions) {
|