@pipelab/core-node 1.0.0-beta.26 → 1.0.0-beta.30
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 +33 -0
- package/dist/{config-CFgGRD9U.mjs → config-6j2vtyRe.mjs} +29 -5
- package/dist/config-6j2vtyRe.mjs.map +1 -0
- package/dist/config-CAz_XbPS.cjs +448 -0
- package/dist/config-DRifo1Uc.mjs +2 -0
- package/dist/index.cjs +54030 -0
- package/dist/index.d.cts +774 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +316 -17
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +69 -22
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/context.ts +17 -8
- package/src/handlers/system.ts +26 -0
- package/src/utils/fs-extras.ts +42 -3
- package/src/utils/github.test.ts +6 -0
- package/src/utils/remote.ts +65 -20
- package/tsconfig.json +0 -2
- package/tsdown.config.ts +12 -0
- package/dist/config-Bi0ORcTK.mjs +0 -2
- package/dist/config-CFgGRD9U.mjs.map +0 -1
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.30",
|
|
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.26",
|
|
44
|
+
"@pipelab/shared": "1.0.0-beta.25"
|
|
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.24"
|
|
60
60
|
},
|
|
61
61
|
"scripts": {
|
|
62
62
|
"build": "tsdown",
|
package/src/context.ts
CHANGED
|
@@ -66,11 +66,13 @@ export interface PipelabContextOptions {
|
|
|
66
66
|
releaseTag?: string;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
type Join<T extends string[], D extends string> =
|
|
70
|
-
|
|
71
|
-
T extends [infer F extends string]
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
type Join<T extends string[], D extends string> = T extends []
|
|
70
|
+
? ""
|
|
71
|
+
: T extends [infer F extends string]
|
|
72
|
+
? F
|
|
73
|
+
: T extends [infer F extends string, ...infer R extends string[]]
|
|
74
|
+
? `${F}${D}${Join<R, D>}`
|
|
75
|
+
: string;
|
|
74
76
|
|
|
75
77
|
export class PipelabContext {
|
|
76
78
|
public readonly userDataPath: string;
|
|
@@ -146,7 +148,10 @@ export class PipelabContext {
|
|
|
146
148
|
}
|
|
147
149
|
|
|
148
150
|
getCachePath(): `CACHE/`;
|
|
149
|
-
getCachePath<F extends CacheFolderType, S extends string[]>(
|
|
151
|
+
getCachePath<F extends CacheFolderType, S extends string[]>(
|
|
152
|
+
folder: F,
|
|
153
|
+
...subpaths: S
|
|
154
|
+
): `CACHE/${F}/${Join<S, "/">}`;
|
|
150
155
|
getCachePath(folder?: CacheFolderType, ...subpaths: string[]): string {
|
|
151
156
|
const settings = this.getSettings();
|
|
152
157
|
const base = settings?.cacheFolder || join(this.userDataPath, "cache");
|
|
@@ -166,13 +171,17 @@ export class PipelabContext {
|
|
|
166
171
|
return join(this.userDataPath, "build-history", ...subpaths);
|
|
167
172
|
}
|
|
168
173
|
|
|
169
|
-
getNodePath<V extends string = typeof DEFAULT_NODE_VERSION>(
|
|
174
|
+
getNodePath<V extends string = typeof DEFAULT_NODE_VERSION>(
|
|
175
|
+
version?: V,
|
|
176
|
+
): `THIRDPARTY/node/${V}/${string}`;
|
|
170
177
|
getNodePath(version = DEFAULT_NODE_VERSION): string {
|
|
171
178
|
const isWindows = process.platform === "win32";
|
|
172
179
|
return this.getThirdPartyPath("node", version, isWindows ? "node.exe" : "bin/node");
|
|
173
180
|
}
|
|
174
181
|
|
|
175
|
-
getPnpmBinPath<V extends string = typeof DEFAULT_PNPM_VERSION>(
|
|
182
|
+
getPnpmBinPath<V extends string = typeof DEFAULT_PNPM_VERSION>(
|
|
183
|
+
version?: V,
|
|
184
|
+
): `PACKAGES/pnpm/${V}/bin/pnpm.cjs`;
|
|
176
185
|
getPnpmBinPath(version = DEFAULT_PNPM_VERSION): string {
|
|
177
186
|
return this.getPackagesPath("pnpm", version, "bin", "pnpm.cjs");
|
|
178
187
|
}
|
package/src/handlers/system.ts
CHANGED
|
@@ -20,4 +20,30 @@ export const registerSystemHandlers = (options: { version: string; context: Pipe
|
|
|
20
20
|
},
|
|
21
21
|
});
|
|
22
22
|
});
|
|
23
|
+
|
|
24
|
+
handle("system:packages:cleanup", async (_, { send }) => {
|
|
25
|
+
try {
|
|
26
|
+
const packagesDir = options.context.getPackagesPath();
|
|
27
|
+
const { existsSync } = await import("fs");
|
|
28
|
+
const { rm } = await import("fs/promises");
|
|
29
|
+
if (existsSync(packagesDir)) {
|
|
30
|
+
await rm(packagesDir, { recursive: true, force: true });
|
|
31
|
+
}
|
|
32
|
+
send({
|
|
33
|
+
type: "end",
|
|
34
|
+
data: {
|
|
35
|
+
type: "success",
|
|
36
|
+
result: true,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
} catch (e: any) {
|
|
40
|
+
send({
|
|
41
|
+
type: "end",
|
|
42
|
+
data: {
|
|
43
|
+
type: "error",
|
|
44
|
+
ipcError: e.message,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
});
|
|
23
49
|
};
|
package/src/utils/fs-extras.ts
CHANGED
|
@@ -77,18 +77,57 @@ export const zipFolder = async (
|
|
|
77
77
|
from: string,
|
|
78
78
|
to: string,
|
|
79
79
|
log: typeof console.log = console.log,
|
|
80
|
+
abortSignal?: AbortSignal,
|
|
80
81
|
) => {
|
|
82
|
+
if (abortSignal?.aborted) {
|
|
83
|
+
const abortError = new Error("Aborted");
|
|
84
|
+
abortError.name = "AbortError";
|
|
85
|
+
throw abortError;
|
|
86
|
+
}
|
|
87
|
+
|
|
81
88
|
const output = createWriteStream(to);
|
|
82
89
|
const archive = archiver("zip", { zlib: { level: 9 } });
|
|
90
|
+
|
|
83
91
|
return new Promise<string>((resolve, reject) => {
|
|
92
|
+
const onAbort = () => {
|
|
93
|
+
try {
|
|
94
|
+
archive.abort();
|
|
95
|
+
} catch {}
|
|
96
|
+
try {
|
|
97
|
+
output.destroy();
|
|
98
|
+
} catch {}
|
|
99
|
+
const abortError = new Error("Aborted");
|
|
100
|
+
abortError.name = "AbortError";
|
|
101
|
+
reject(abortError);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
if (abortSignal) {
|
|
105
|
+
abortSignal.addEventListener("abort", onAbort);
|
|
106
|
+
}
|
|
107
|
+
|
|
84
108
|
output.on("close", () => {
|
|
109
|
+
if (abortSignal) {
|
|
110
|
+
abortSignal.removeEventListener("abort", onAbort);
|
|
111
|
+
}
|
|
85
112
|
log(archive.pointer() + " total bytes");
|
|
86
113
|
resolve(to);
|
|
87
114
|
});
|
|
88
|
-
|
|
115
|
+
|
|
116
|
+
archive.on("error", (err) => {
|
|
117
|
+
if (abortSignal) {
|
|
118
|
+
abortSignal.removeEventListener("abort", onAbort);
|
|
119
|
+
}
|
|
120
|
+
reject(err);
|
|
121
|
+
});
|
|
122
|
+
|
|
89
123
|
archive.pipe(output);
|
|
90
124
|
archive.directory(from, false);
|
|
91
|
-
archive.finalize()
|
|
125
|
+
archive.finalize().catch((err) => {
|
|
126
|
+
if (abortSignal) {
|
|
127
|
+
abortSignal.removeEventListener("abort", onAbort);
|
|
128
|
+
}
|
|
129
|
+
reject(err);
|
|
130
|
+
});
|
|
92
131
|
});
|
|
93
132
|
};
|
|
94
133
|
|
|
@@ -149,7 +188,7 @@ export const runWithLiveLogs = async (
|
|
|
149
188
|
TERM: "xterm-256color",
|
|
150
189
|
FORCE_STDERR_LOGGING: "1",
|
|
151
190
|
},
|
|
152
|
-
cancelSignal: abortSignal,
|
|
191
|
+
cancelSignal: abortSignal ?? execaOptions.cancelSignal,
|
|
153
192
|
});
|
|
154
193
|
|
|
155
194
|
hooks?.onCreated?.(subprocess);
|
package/src/utils/github.test.ts
CHANGED
|
@@ -7,9 +7,15 @@ import {
|
|
|
7
7
|
|
|
8
8
|
describe("GitHub Release Updates API", () => {
|
|
9
9
|
let fetchSpy: any;
|
|
10
|
+
let consoleLogSpy: any;
|
|
11
|
+
let consoleWarnSpy: any;
|
|
12
|
+
let consoleErrorSpy: any;
|
|
10
13
|
|
|
11
14
|
beforeEach(() => {
|
|
12
15
|
fetchSpy = vi.spyOn(global, "fetch");
|
|
16
|
+
consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
17
|
+
consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
18
|
+
consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
13
19
|
// Clean up environment variables
|
|
14
20
|
delete process.env.PIPELAB_OVERRIDE_RELEASE;
|
|
15
21
|
});
|
package/src/utils/remote.ts
CHANGED
|
@@ -96,6 +96,27 @@ export type FetchOptions = {
|
|
|
96
96
|
context: PipelabContext;
|
|
97
97
|
};
|
|
98
98
|
|
|
99
|
+
function allowPrereleaseInRange(rangeStr: string): string {
|
|
100
|
+
try {
|
|
101
|
+
const r = new semver.Range(rangeStr);
|
|
102
|
+
return r.set
|
|
103
|
+
.map((conj) =>
|
|
104
|
+
conj
|
|
105
|
+
.map((c) => {
|
|
106
|
+
const v = c.semver;
|
|
107
|
+
if (v.prerelease && v.prerelease.length) {
|
|
108
|
+
return c.operator + v.format();
|
|
109
|
+
}
|
|
110
|
+
return c.operator + v.major + "." + v.minor + "." + v.patch + "-0";
|
|
111
|
+
})
|
|
112
|
+
.join(" "),
|
|
113
|
+
)
|
|
114
|
+
.join(" || ");
|
|
115
|
+
} catch {
|
|
116
|
+
return rangeStr;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
99
120
|
/**
|
|
100
121
|
* Robust utility to fetch, cache, and resolve an NPM package.
|
|
101
122
|
* Centralized in core-node to avoid circular dependencies.
|
|
@@ -131,6 +152,7 @@ export async function fetchPackage(
|
|
|
131
152
|
const ctx = options.context;
|
|
132
153
|
const baseDir = ctx.getPackagesPath(packageName);
|
|
133
154
|
let resolvedVersion: string;
|
|
155
|
+
const includePrerelease = !!(ctx.releaseTag && ctx.releaseTag !== "latest");
|
|
134
156
|
|
|
135
157
|
let resolvedVersionOrRange = versionOrRange;
|
|
136
158
|
if (resolvedVersionOrRange === "local") {
|
|
@@ -151,6 +173,7 @@ export async function fetchPackage(
|
|
|
151
173
|
new Error("Offline"),
|
|
152
174
|
baseDir,
|
|
153
175
|
packageName,
|
|
176
|
+
includePrerelease,
|
|
154
177
|
);
|
|
155
178
|
if (fallbackVersion) {
|
|
156
179
|
resolvedVersion = fallbackVersion;
|
|
@@ -175,7 +198,41 @@ export async function fetchPackage(
|
|
|
175
198
|
const range = resolvedVersionOrRange || "latest";
|
|
176
199
|
|
|
177
200
|
// Prioritize tags (like 'latest', 'beta', etc.) over semver ranges
|
|
178
|
-
let foundVersion =
|
|
201
|
+
let foundVersion =
|
|
202
|
+
packument["dist-tags"]?.[range] ||
|
|
203
|
+
semver.maxSatisfying(versions, range, { includePrerelease });
|
|
204
|
+
|
|
205
|
+
// If we are in a non-latest releaseTag channel (like "beta" or "dev"),
|
|
206
|
+
// and we are resolving a range, we can prefer the releaseTag version if it exists
|
|
207
|
+
// and satisfies the range (ignoring strict prerelease constraints if includePrerelease is true).
|
|
208
|
+
if (!foundVersion && ctx.releaseTag && ctx.releaseTag !== "latest") {
|
|
209
|
+
const releaseTagVersion = packument["dist-tags"]?.[ctx.releaseTag];
|
|
210
|
+
if (releaseTagVersion && semver.valid(releaseTagVersion)) {
|
|
211
|
+
const rewrittenRangeForCheck = allowPrereleaseInRange(range);
|
|
212
|
+
if (
|
|
213
|
+
semver.satisfies(releaseTagVersion, rewrittenRangeForCheck, { includePrerelease: true })
|
|
214
|
+
) {
|
|
215
|
+
console.log(
|
|
216
|
+
`[Fetcher] Using release tag "${ctx.releaseTag}" (${releaseTagVersion}) for ${packageName}@${range} because it satisfies the range`,
|
|
217
|
+
);
|
|
218
|
+
foundVersion = releaseTagVersion;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// If we still don't have a version, and includePrerelease is true, try rewriting the range to allow prerelease matching
|
|
224
|
+
if (!foundVersion && includePrerelease) {
|
|
225
|
+
const rewrittenRange = allowPrereleaseInRange(range);
|
|
226
|
+
if (rewrittenRange !== range) {
|
|
227
|
+
const matched = semver.maxSatisfying(versions, rewrittenRange, { includePrerelease });
|
|
228
|
+
if (matched) {
|
|
229
|
+
console.log(
|
|
230
|
+
`[Fetcher] Resolved ${packageName}@${range} to ${matched} via rewritten range ${rewrittenRange}`,
|
|
231
|
+
);
|
|
232
|
+
foundVersion = matched;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
179
236
|
|
|
180
237
|
// If we are in a non-latest releaseTag channel (like "beta" or "dev"),
|
|
181
238
|
// and we are resolving "latest", we prefer the releaseTag version if it exists
|
|
@@ -218,6 +275,7 @@ export async function fetchPackage(
|
|
|
218
275
|
error,
|
|
219
276
|
baseDir,
|
|
220
277
|
packageName,
|
|
278
|
+
includePrerelease,
|
|
221
279
|
);
|
|
222
280
|
if (fallbackVersion) {
|
|
223
281
|
resolvedVersion = fallbackVersion;
|
|
@@ -490,17 +548,10 @@ async function installDependencies(packageDir: string, packageName: string, opti
|
|
|
490
548
|
return;
|
|
491
549
|
}
|
|
492
550
|
|
|
493
|
-
// To prevent other processes from seeing a partially populated node_modules,
|
|
494
|
-
// we can create a temp directory next to packageDir, run pnpm there, and rename node_modules
|
|
495
|
-
const tempDir = `${packageDir}.tmp-deps-${Math.random().toString(36).slice(2)}`;
|
|
496
|
-
await mkdir(tempDir, { recursive: true });
|
|
497
551
|
try {
|
|
498
|
-
// Copy package.json to tempDir so pnpm can install dependencies
|
|
499
|
-
await cp(join(packageDir, "package.json"), join(tempDir, "package.json"));
|
|
500
|
-
|
|
501
552
|
console.log(`[Fetcher] ${packageName}: Ensuring dependencies are installed...`);
|
|
502
553
|
const pnpmStart = Date.now();
|
|
503
|
-
const { all } = await runPnpm(
|
|
554
|
+
const { all } = await runPnpm(packageDir, {
|
|
504
555
|
signal: options.signal,
|
|
505
556
|
context: options.context,
|
|
506
557
|
});
|
|
@@ -508,24 +559,17 @@ async function installDependencies(packageDir: string, packageName: string, opti
|
|
|
508
559
|
|
|
509
560
|
if (all) console.log(`[Fetcher] ${packageName}: Installation trace:\n${all}`);
|
|
510
561
|
|
|
511
|
-
// Atomically rename node_modules from tempDir to packageDir/node_modules
|
|
512
|
-
const tempNodeModules = join(tempDir, "node_modules");
|
|
513
|
-
if (existsSync(nodeModulesPath)) {
|
|
514
|
-
await rm(nodeModulesPath, { recursive: true, force: true }).catch(() => {});
|
|
515
|
-
}
|
|
516
|
-
const renameStart = Date.now();
|
|
517
|
-
await rename(tempNodeModules, nodeModulesPath);
|
|
518
562
|
console.log(
|
|
519
|
-
`[Fetcher] ${packageName}: Dependencies installed successfully (
|
|
563
|
+
`[Fetcher] ${packageName}: Dependencies installed successfully (total installDependencies took ${Date.now() - start}ms).`,
|
|
520
564
|
);
|
|
521
565
|
} catch (err: any) {
|
|
522
566
|
console.error(
|
|
523
567
|
`[Fetcher] ${packageName}: CRITICAL ERROR during dependency installation: ${err.message}`,
|
|
524
568
|
);
|
|
525
569
|
if (err.all) console.error(`[Fetcher] ${packageName}: Error details:\n${err.all}`);
|
|
570
|
+
// Clean up node_modules on error to allow retries
|
|
571
|
+
await rm(nodeModulesPath, { recursive: true, force: true }).catch(() => {});
|
|
526
572
|
throw new Error(`Failed to install dependencies for ${packageName}. See logs for details.`);
|
|
527
|
-
} finally {
|
|
528
|
-
await rm(tempDir, { recursive: true, force: true }).catch(() => {});
|
|
529
573
|
}
|
|
530
574
|
}
|
|
531
575
|
|
|
@@ -675,6 +719,7 @@ async function tryLocalFallback(
|
|
|
675
719
|
_error: unknown,
|
|
676
720
|
baseDir: string,
|
|
677
721
|
logPrefix: string,
|
|
722
|
+
includePrerelease = false,
|
|
678
723
|
): Promise<string | null> {
|
|
679
724
|
if (!existsSync(baseDir)) return null;
|
|
680
725
|
try {
|
|
@@ -696,7 +741,7 @@ async function tryLocalFallback(
|
|
|
696
741
|
return latestLocal;
|
|
697
742
|
}
|
|
698
743
|
} else {
|
|
699
|
-
const matched = semver.maxSatisfying(localVersions, range);
|
|
744
|
+
const matched = semver.maxSatisfying(localVersions, range, { includePrerelease });
|
|
700
745
|
if (matched) {
|
|
701
746
|
console.info(
|
|
702
747
|
`[Fetcher] ${logPrefix}: Using locally cached matching version: ${matched} for range ${range}`,
|
package/tsconfig.json
CHANGED
package/tsdown.config.ts
ADDED
package/dist/config-Bi0ORcTK.mjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"config-CFgGRD9U.mjs","names":["mkdirP","fs"],"sources":["../src/utils/fs-extras.ts","../src/config.ts"],"sourcesContent":["import { createWriteStream } from \"node:fs\";\nimport { execa, Options, Subprocess } from \"execa\";\nimport { mkdir as mkdirP, writeFile, stat, readdir } from \"node:fs/promises\";\nimport { join, dirname } from \"node:path\";\nimport tar from \"tar\";\nimport yauzl from \"yauzl\";\nimport archiver from \"archiver\";\nimport { pipeline } from \"node:stream/promises\";\n\n/**\n * Ensures a directory exists and a file is created with default content if missing.\n */\nexport const ensure = async (filesPath: string, defaultContent = \"{}\") => {\n await mkdirP(dirname(filesPath), { recursive: true });\n try {\n const s = await stat(filesPath);\n if (s.size === 0) {\n await writeFile(filesPath, defaultContent);\n }\n } catch {\n await writeFile(filesPath, defaultContent);\n }\n};\n\n/**\n * Extracts a .tar.gz archive.\n */\nexport async function extractTarGz(archivePath: string, destinationDir: string): Promise<void> {\n await mkdirP(destinationDir, { recursive: true });\n await tar.x({\n file: archivePath,\n cwd: destinationDir,\n });\n}\n\n/**\n * Extracts a .zip archive.\n */\nexport async function extractZip(archivePath: string, destinationDir: string): Promise<void> {\n await mkdirP(destinationDir, { recursive: true });\n return new Promise((resolve, reject) => {\n yauzl.open(archivePath, { lazyEntries: true }, (err, zipfile) => {\n if (err || !zipfile) return reject(err || new Error(\"Could not open zip file\"));\n zipfile.on(\"error\", reject);\n zipfile.readEntry();\n zipfile.on(\"entry\", (entry) => {\n const entryPath = join(destinationDir, entry.fileName);\n if (/\\/$/.test(entry.fileName)) {\n mkdirP(entryPath, { recursive: true })\n .then(() => zipfile.readEntry())\n .catch(reject);\n } else {\n mkdirP(dirname(entryPath), { recursive: true })\n .then(() => {\n zipfile.openReadStream(entry, (err, readStream) => {\n if (err || !readStream)\n return reject(err || new Error(\"Could not open read stream\"));\n readStream.on(\"error\", reject);\n const writeStream = createWriteStream(entryPath);\n writeStream.on(\"error\", reject);\n writeStream.on(\"close\", () => zipfile.readEntry());\n readStream.pipe(writeStream);\n });\n })\n .catch(reject);\n }\n });\n zipfile.on(\"end\", () => resolve());\n });\n });\n}\n\n/**\n * Zips a folder.\n */\nexport const zipFolder = async (\n from: string,\n to: string,\n log: typeof console.log = console.log,\n) => {\n const output = createWriteStream(to);\n const archive = archiver(\"zip\", { zlib: { level: 9 } });\n return new Promise<string>((resolve, reject) => {\n output.on(\"close\", () => {\n log(archive.pointer() + \" total bytes\");\n resolve(to);\n });\n archive.on(\"error\", reject);\n archive.pipe(output);\n archive.directory(from, false);\n archive.finalize();\n });\n};\n\n/**\n * Downloads a file with progress tracking.\n */\nexport interface DownloadHooks {\n onProgress?: (data: { progress: number; downloadedSize: number }) => void;\n}\n\nexport const downloadFile = async (\n url: string,\n localPath: string,\n hooks?: DownloadHooks,\n abortSignal?: AbortSignal,\n): Promise<void> => {\n const response = await fetch(url, { signal: abortSignal });\n if (!response.ok) throw new Error(`Failed to fetch file: ${response.statusText}`);\n const contentLength = response.headers.get(\"content-length\");\n if (!contentLength) throw new Error(\"Content-Length header is missing\");\n const totalSize = parseInt(contentLength, 10);\n let downloadedSize = 0;\n const fileStream = createWriteStream(localPath);\n const progressStream = new TransformStream({\n transform(chunk, controller) {\n downloadedSize += chunk.length;\n const progress = (downloadedSize / totalSize) * 100;\n hooks?.onProgress?.({ progress, downloadedSize });\n controller.enqueue(chunk);\n },\n });\n const readable = response.body?.pipeThrough(progressStream);\n if (!readable) throw new Error(\"Failed to create a readable stream\");\n await pipeline(readable, fileStream);\n};\n\nexport const runWithLiveLogs = async (\n command: string,\n args: string[],\n execaOptions: Options,\n log: typeof console.log,\n hooks?: {\n onStdout?: (data: string, subprocess: Subprocess) => void;\n onStderr?: (data: string, subprocess: Subprocess) => void;\n onExit?: (code: number) => void;\n onCreated?: (subprocess: Subprocess) => void;\n },\n abortSignal?: AbortSignal,\n): Promise<void> => {\n const subprocess = execa(command, args, {\n ...execaOptions,\n stdout: \"pipe\",\n stderr: \"pipe\",\n stdin: \"pipe\",\n env: {\n ...process.env,\n ...execaOptions.env,\n TERM: \"xterm-256color\",\n FORCE_STDERR_LOGGING: \"1\",\n },\n cancelSignal: abortSignal,\n });\n\n hooks?.onCreated?.(subprocess);\n\n subprocess.stdout?.on(\"data\", (data: Buffer) => {\n hooks?.onStdout?.(data.toString(), subprocess);\n });\n\n subprocess.stderr?.on(\"data\", (data: Buffer) => {\n hooks?.onStderr?.(data.toString(), subprocess);\n });\n\n try {\n const { exitCode } = await subprocess;\n hooks?.onExit?.(exitCode ?? 0);\n } catch (error: any) {\n const code = error.exitCode ?? 1;\n hooks?.onExit?.(code);\n throw new Error(`Command failed with exit code ${code}: ${error.message}`);\n }\n};\n\n/**\n * Calculates the total size of a directory recursively.\n */\nexport async function getFolderSize(dirPath: string): Promise<number> {\n try {\n const files = await readdir(dirPath, { withFileTypes: true });\n const ArrayOfPromises = files.map(async (file) => {\n const path = join(dirPath, file.name);\n if (file.isDirectory()) {\n try {\n return await getFolderSize(path);\n } catch {\n return 0;\n }\n }\n try {\n const { size } = await stat(path);\n return size;\n } catch {\n return 0;\n }\n });\n const results = await Promise.all(ArrayOfPromises);\n return results.reduce((acc, size) => acc + size, 0);\n } catch {\n return 0;\n }\n}\n","import { PipelabContext } from \"./context\";\nimport path from \"node:path\";\nimport { ensure } from \"./utils/fs-extras\";\nimport fs from \"node:fs/promises\";\nimport {\n useLogger,\n Migrator,\n AppConfig,\n ConnectionsConfig,\n FileRepo,\n SavedFile,\n appSettingsMigrator,\n connectionsMigrator,\n fileRepoMigrations,\n savedFileMigrator,\n} from \"@pipelab/shared\";\n\nexport const setupConfigFile = async <T>(\n filesPath: string,\n options: { context: PipelabContext; migrator: Migrator<T> },\n) => {\n const ctx = options.context;\n const parsedPath = path.parse(filesPath);\n const migrator = options.migrator;\n\n await ensure(filesPath, JSON.stringify(migrator.defaultValue));\n\n return {\n setConfig: async (config: T) => {\n const { logger } = useLogger();\n try {\n await fs.writeFile(filesPath, JSON.stringify(config));\n return true;\n } catch (e) {\n logger().error(`Error saving config ${parsedPath.name}:`, e);\n return false;\n }\n },\n getConfig: async () => {\n const { logger } = useLogger();\n let content = undefined;\n let originalJson: any = undefined;\n let parseFailed = false;\n\n try {\n content = await fs.readFile(filesPath, \"utf8\");\n if (content !== undefined) {\n originalJson = JSON.parse(content);\n }\n } catch (e) {\n logger().error(`Error reading or parsing config ${parsedPath.name}:`, e);\n parseFailed = true;\n }\n\n let json: any = undefined;\n let migrationFailed = false;\n try {\n if (!parseFailed) {\n json = await migrator.migrate(originalJson, {\n debug: false,\n onStep: async (state: any, version: string) => {\n const versionedPath = path.join(\n parsedPath.dir,\n `${parsedPath.name}.v${version}.json`,\n );\n try {\n await fs.writeFile(versionedPath, JSON.stringify(state));\n logger().info(\n `Intermediate backup created for ${parsedPath.name} at ${versionedPath}`,\n );\n } catch (e) {\n logger().error(\n `Failed to create intermediate backup for ${parsedPath.name} at v${version}:`,\n e,\n );\n }\n },\n });\n } else {\n json = migrator.defaultValue;\n }\n } catch (e) {\n logger().error(`Error migrating config ${parsedPath.name}:`, e);\n migrationFailed = true;\n json = migrator.defaultValue;\n }\n\n const originalVersion = originalJson?.version;\n const newVersion = json?.version;\n\n const shouldSaveBack =\n originalVersion !== newVersion || content === undefined || parseFailed || migrationFailed;\n\n if (shouldSaveBack) {\n if (parseFailed || migrationFailed) {\n try {\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const corruptedPath = path.join(\n parsedPath.dir,\n `${parsedPath.name}.corrupted.${timestamp}.json`,\n );\n const backupContent = parseFailed\n ? content || \"\"\n : JSON.stringify(originalJson, null, 2);\n await fs.writeFile(corruptedPath, backupContent);\n logger().info(`Corrupted config file preserved at ${corruptedPath}`);\n } catch (e) {\n logger().error(`Failed to backup corrupted config ${parsedPath.name}:`, e);\n }\n }\n\n try {\n await fs.writeFile(filesPath, JSON.stringify(json));\n } catch (e) {\n logger().error(`Error saving migrated config ${parsedPath.name}:`, e);\n }\n }\n\n return json as T;\n },\n };\n};\n\nexport const setupSettingsConfigFile = (context: PipelabContext) => {\n return setupConfigFile<AppConfig>(context.getSettingsPath(), {\n context,\n migrator: appSettingsMigrator,\n });\n};\n\nexport const setupConnectionsConfigFile = (context: PipelabContext) => {\n return setupConfigFile<ConnectionsConfig>(context.getConnectionsPath(), {\n context,\n migrator: connectionsMigrator,\n });\n};\n\nexport const setupProjectsConfigFile = (context: PipelabContext) => {\n return setupConfigFile<FileRepo>(context.getProjectsPath(), {\n context,\n migrator: fileRepoMigrations,\n });\n};\n\nexport const setupPipelineConfigFileByName = (name: string, context: PipelabContext) => {\n const filesPath = context.getConfigPath(`${name}.json`);\n return setupConfigFile<SavedFile>(filesPath, {\n context,\n migrator: savedFileMigrator,\n });\n};\n\nexport const setupPipelineConfigFileByPath = (absolutePath: string, context: PipelabContext) => {\n return setupConfigFile<SavedFile>(absolutePath, {\n context,\n migrator: savedFileMigrator,\n });\n};\n\nconst deleteConfigFile = async (filesPath: string) => {\n await fs.rm(filesPath, { force: true });\n};\n\nexport const deletePipelineConfigFileByName = async (name: string, context: PipelabContext) => {\n const filesPath = context.getConfigPath(`${name}.json`);\n await deleteConfigFile(filesPath);\n};\n\nexport const deletePipelineConfigFileByPath = async (\n absolutePath: string,\n context: PipelabContext,\n) => {\n await deleteConfigFile(absolutePath);\n};\n"],"mappings":"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"}
|