@pipelab/core-node 1.0.0-beta.6 → 1.0.0-beta.9
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 +22 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +94 -28
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/utils/remote.ts +115 -32
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.9",
|
|
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.7",
|
|
44
|
+
"@pipelab/shared": "1.0.0-beta.5"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@types/adm-zip": "0.5.7",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"@types/yauzl": "2.10.3",
|
|
55
55
|
"tsdown": "0.21.2",
|
|
56
56
|
"typescript": "^5.0.0",
|
|
57
|
-
"@pipelab/tsconfig": "1.0.0-beta.
|
|
57
|
+
"@pipelab/tsconfig": "1.0.0-beta.5"
|
|
58
58
|
},
|
|
59
59
|
"scripts": {
|
|
60
60
|
"build": "tsdown",
|
package/src/utils/remote.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { dirname, delimiter, join } from "node:path";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
|
-
import { mkdir, readdir, readFile, writeFile, access, chmod, rm, cp } from "node:fs/promises";
|
|
4
|
-
import { existsSync, constants } from "node:fs";
|
|
3
|
+
import { mkdir, readdir, readFile, writeFile, access, chmod, rm, cp, rename } from "node:fs/promises";
|
|
4
|
+
import { existsSync, constants, statSync, readdirSync } from "node:fs";
|
|
5
5
|
import pacote from "pacote";
|
|
6
6
|
import semver from "semver";
|
|
7
7
|
import { isDev, projectRoot, PipelabContext } from "../context";
|
|
@@ -12,6 +12,29 @@ import { downloadFile, extractZip, extractTarGz, generateTempFolder } from "./fs
|
|
|
12
12
|
export const DEFAULT_NODE_VERSION = "24.14.1";
|
|
13
13
|
export const DEFAULT_PNPM_VERSION = "10.12.0";
|
|
14
14
|
|
|
15
|
+
function isPackageComplete(packageDir: string): boolean {
|
|
16
|
+
return existsSync(join(packageDir, "package.json"));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isNodeJSComplete(nodePath: string): boolean {
|
|
20
|
+
try {
|
|
21
|
+
return existsSync(nodePath) && statSync(nodePath).size > 0;
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isDependenciesInstalledSync(packageDir: string): boolean {
|
|
28
|
+
const nodeModulesPath = join(packageDir, "node_modules");
|
|
29
|
+
if (!existsSync(nodeModulesPath)) return false;
|
|
30
|
+
try {
|
|
31
|
+
const files = readdirSync(nodeModulesPath);
|
|
32
|
+
return files.length > 0;
|
|
33
|
+
} catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
15
38
|
/**
|
|
16
39
|
* In-memory lock to prevent concurrent operations on the same resource (e.g., downloading Node.js).
|
|
17
40
|
*/
|
|
@@ -113,19 +136,49 @@ export async function fetchPackage(
|
|
|
113
136
|
const cachePath = join(ctx.userDataPath, "cache", "pacote");
|
|
114
137
|
const packageDir = join(baseDir, resolvedVersion);
|
|
115
138
|
|
|
116
|
-
// If the package already exists and we don't need to install dependencies, return immediately
|
|
117
|
-
|
|
139
|
+
// If the package already exists and we don't need to install dependencies (or they are already installed), return immediately
|
|
140
|
+
const isInstalled = options?.installDeps
|
|
141
|
+
? (isPackageComplete(packageDir) && isDependenciesInstalledSync(packageDir))
|
|
142
|
+
: isPackageComplete(packageDir);
|
|
143
|
+
|
|
144
|
+
if (isInstalled) {
|
|
118
145
|
return { packageDir, resolvedVersion };
|
|
119
146
|
}
|
|
120
147
|
|
|
121
148
|
const lockKey = `package:${packageName}:${resolvedVersion}`;
|
|
122
149
|
return withLock(lockKey, async () => {
|
|
123
|
-
if (!
|
|
150
|
+
if (!isPackageComplete(packageDir)) {
|
|
124
151
|
console.log(`[Fetcher] ${packageName}@${resolvedVersion}: Downloading to ${packageDir}...`);
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
152
|
+
|
|
153
|
+
const tempDir = join(baseDir, `.tmp-${resolvedVersion}-${Math.random().toString(36).slice(2)}`);
|
|
154
|
+
await mkdir(tempDir, { recursive: true });
|
|
155
|
+
try {
|
|
156
|
+
await pacote.extract(`${packageName}@${resolvedVersion}`, tempDir, {
|
|
157
|
+
cache: cachePath,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Resolve entryPoint from package.json inside the temp directory first to ensure everything works
|
|
161
|
+
await resolveEntryPoint(tempDir, packageName);
|
|
162
|
+
|
|
163
|
+
// Remove existing incomplete packageDir if any
|
|
164
|
+
if (existsSync(packageDir)) {
|
|
165
|
+
await rm(packageDir, { recursive: true, force: true }).catch(() => {});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Atomically rename the temp directory to the target packageDir
|
|
169
|
+
try {
|
|
170
|
+
await rename(tempDir, packageDir);
|
|
171
|
+
} catch (err: any) {
|
|
172
|
+
if (isPackageComplete(packageDir)) {
|
|
173
|
+
console.log(`[Fetcher] Destination ${packageDir} already exists and is valid.`);
|
|
174
|
+
} else {
|
|
175
|
+
throw err;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
} catch (err) {
|
|
179
|
+
await rm(tempDir, { recursive: true, force: true }).catch(() => {});
|
|
180
|
+
throw err;
|
|
181
|
+
}
|
|
129
182
|
}
|
|
130
183
|
|
|
131
184
|
// 2. Resolve entry point from package.json for downloaded package
|
|
@@ -196,13 +249,13 @@ export async function ensureNodeJS(context: PipelabContext, version = DEFAULT_NO
|
|
|
196
249
|
const nodeDir = context.getThirdPartyPath("node", version);
|
|
197
250
|
const finalNodePath = join(nodeDir, isWindows ? "node.exe" : "bin/node");
|
|
198
251
|
|
|
199
|
-
if (
|
|
252
|
+
if (isNodeJSComplete(finalNodePath)) {
|
|
200
253
|
return finalNodePath;
|
|
201
254
|
}
|
|
202
255
|
|
|
203
256
|
const lockKey = `node:${version}`;
|
|
204
257
|
return withLock(lockKey, async () => {
|
|
205
|
-
if (
|
|
258
|
+
if (isNodeJSComplete(finalNodePath)) return finalNodePath;
|
|
206
259
|
|
|
207
260
|
const arch = process.arch === "x64" ? "x64" : process.arch === "arm64" ? "arm64" : "x86";
|
|
208
261
|
const platform = isWindows ? "win" : process.platform === "darwin" ? "osx" : "linux";
|
|
@@ -234,12 +287,37 @@ export async function ensureNodeJS(context: PipelabContext, version = DEFAULT_NO
|
|
|
234
287
|
if (!nodeSubDir) throw new Error(`Could not find extracted Node.js directory`);
|
|
235
288
|
|
|
236
289
|
const sourceDir = join(extractTempDir, nodeSubDir);
|
|
237
|
-
|
|
238
|
-
await
|
|
239
|
-
|
|
240
|
-
|
|
290
|
+
const parentDir = dirname(nodeDir);
|
|
291
|
+
await mkdir(parentDir, { recursive: true });
|
|
292
|
+
|
|
293
|
+
const tempNodeDir = join(parentDir, `.tmp-node-${version}-${Math.random().toString(36).slice(2)}`);
|
|
294
|
+
await mkdir(tempNodeDir, { recursive: true });
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
await cp(sourceDir, tempNodeDir, { recursive: true });
|
|
298
|
+
if (!isWindows) {
|
|
299
|
+
const tempNodePath = join(tempNodeDir, "bin/node");
|
|
300
|
+
await chmod(tempNodePath, 0o755).catch(() => {});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (existsSync(nodeDir)) {
|
|
304
|
+
await rm(nodeDir, { recursive: true, force: true }).catch(() => {});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
try {
|
|
308
|
+
await rename(tempNodeDir, nodeDir);
|
|
309
|
+
} catch (err: any) {
|
|
310
|
+
if (isNodeJSComplete(finalNodePath)) {
|
|
311
|
+
console.log(`[Fetcher] Node.js directory already exists and is valid.`);
|
|
312
|
+
} else {
|
|
313
|
+
throw err;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
} finally {
|
|
317
|
+
await rm(tempNodeDir, { recursive: true, force: true }).catch(() => {});
|
|
318
|
+
await rm(tempDir, { recursive: true, force: true }).catch(() => {});
|
|
319
|
+
}
|
|
241
320
|
|
|
242
|
-
if (!isWindows) await chmod(finalNodePath, 0o755).catch(() => {});
|
|
243
321
|
return finalNodePath;
|
|
244
322
|
});
|
|
245
323
|
}
|
|
@@ -269,30 +347,33 @@ export async function ensurePNPM(context: PipelabContext, version = DEFAULT_PNPM
|
|
|
269
347
|
async function installDependencies(packageDir: string, packageName: string, options: FetchOptions) {
|
|
270
348
|
const nodeModulesPath = join(packageDir, "node_modules");
|
|
271
349
|
|
|
272
|
-
if (
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
if (files.length === 0) {
|
|
276
|
-
console.warn(
|
|
277
|
-
`[Fetcher] ${packageName}: node_modules exists but is empty. Re-installing...`,
|
|
278
|
-
);
|
|
279
|
-
} else {
|
|
280
|
-
console.log(`[Fetcher] ${packageName}: Dependencies already installed, skipping.`);
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
} catch (e) {
|
|
284
|
-
// Continue to install if readdir fails
|
|
285
|
-
}
|
|
350
|
+
if (isDependenciesInstalledSync(packageDir)) {
|
|
351
|
+
console.log(`[Fetcher] ${packageName}: Dependencies already installed, skipping.`);
|
|
352
|
+
return;
|
|
286
353
|
}
|
|
287
354
|
|
|
288
|
-
|
|
355
|
+
// To prevent other processes from seeing a partially populated node_modules,
|
|
356
|
+
// we can create a temp directory next to packageDir, run pnpm there, and rename node_modules
|
|
357
|
+
const tempDir = `${packageDir}.tmp-deps-${Math.random().toString(36).slice(2)}`;
|
|
358
|
+
await mkdir(tempDir, { recursive: true });
|
|
289
359
|
try {
|
|
290
|
-
|
|
360
|
+
// Copy package.json to tempDir so pnpm can install dependencies
|
|
361
|
+
await cp(join(packageDir, "package.json"), join(tempDir, "package.json"));
|
|
362
|
+
|
|
363
|
+
console.log(`[Fetcher] ${packageName}: Ensuring dependencies are installed...`);
|
|
364
|
+
const { all } = await runPnpm(tempDir, {
|
|
291
365
|
signal: options.signal,
|
|
292
366
|
context: options.context,
|
|
293
367
|
});
|
|
294
368
|
|
|
295
369
|
if (all) console.log(`[Fetcher] ${packageName}: Installation trace:\n${all}`);
|
|
370
|
+
|
|
371
|
+
// Atomically rename node_modules from tempDir to packageDir/node_modules
|
|
372
|
+
const tempNodeModules = join(tempDir, "node_modules");
|
|
373
|
+
if (existsSync(nodeModulesPath)) {
|
|
374
|
+
await rm(nodeModulesPath, { recursive: true, force: true }).catch(() => {});
|
|
375
|
+
}
|
|
376
|
+
await rename(tempNodeModules, nodeModulesPath);
|
|
296
377
|
console.log(`[Fetcher] ${packageName}: Dependencies installed successfully.`);
|
|
297
378
|
} catch (err: any) {
|
|
298
379
|
console.error(
|
|
@@ -300,6 +381,8 @@ async function installDependencies(packageDir: string, packageName: string, opti
|
|
|
300
381
|
);
|
|
301
382
|
if (err.all) console.error(`[Fetcher] ${packageName}: Error details:\n${err.all}`);
|
|
302
383
|
throw new Error(`Failed to install dependencies for ${packageName}. See logs for details.`);
|
|
384
|
+
} finally {
|
|
385
|
+
await rm(tempDir, { recursive: true, force: true }).catch(() => {});
|
|
303
386
|
}
|
|
304
387
|
}
|
|
305
388
|
|