@pipelab/core-node 1.0.1-latest.35 → 1.0.1-latest.36

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