@pipelab/core-node 1.0.0-beta.10 → 1.0.0-beta.12
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/config-CEkOf95p.mjs +2 -0
- package/dist/index.d.mts +12 -21
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +614 -131
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/scratch/simulate-updates.ts +6 -4
- package/src/config.ts +16 -6
- package/src/handler-func.ts +2 -77
- package/src/handlers/config.ts +49 -0
- package/src/handlers/engine.ts +1 -7
- package/src/handlers/index.ts +2 -0
- package/src/handlers/plugins.ts +310 -0
- package/src/plugins-registry.ts +272 -32
- package/src/presets/c3toSteam.ts +13 -7
- package/src/presets/demo.ts +9 -9
- package/src/presets/list.ts +0 -6
- package/src/presets/moreToCome.ts +3 -2
- package/src/presets/newProject.ts +3 -2
- package/src/presets/test-c3-offline.ts +15 -6
- package/src/presets/test-c3-unzip.ts +19 -8
- package/src/server.ts +40 -0
- package/src/types/runner.ts +1 -30
- package/src/utils/fs-extras.ts +4 -1
- package/src/utils/github.ts +87 -10
- package/src/utils/remote.ts +122 -31
- package/src/utils.ts +10 -14
- package/src/presets/if.ts +0 -69
- package/src/presets/loop.ts +0 -65
package/src/server.ts
CHANGED
|
@@ -50,6 +50,46 @@ export async function serveCommand(options: ServeOptions, version: string, _dirn
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
const server = http.createServer(async (request, response) => {
|
|
53
|
+
// Serve local media files securely via HTTP
|
|
54
|
+
if (request.url?.startsWith("/media-file/")) {
|
|
55
|
+
const prefix = "/media-file/";
|
|
56
|
+
const encodedPath = request.url.substring(prefix.length);
|
|
57
|
+
const filePath = decodeURIComponent(encodedPath);
|
|
58
|
+
// Strip leading slash on Windows if followed by a drive letter (e.g. /C:/...)
|
|
59
|
+
const normalizedPath =
|
|
60
|
+
filePath.startsWith("/") && filePath.match(/^\/[a-zA-Z]:/)
|
|
61
|
+
? filePath.substring(1)
|
|
62
|
+
: filePath;
|
|
63
|
+
|
|
64
|
+
if (existsSync(normalizedPath)) {
|
|
65
|
+
try {
|
|
66
|
+
const content = await readFile(normalizedPath);
|
|
67
|
+
let contentType = "application/octet-stream";
|
|
68
|
+
if (normalizedPath.endsWith(".png")) contentType = "image/png";
|
|
69
|
+
else if (normalizedPath.endsWith(".jpg") || normalizedPath.endsWith(".jpeg"))
|
|
70
|
+
contentType = "image/jpeg";
|
|
71
|
+
else if (normalizedPath.endsWith(".svg")) contentType = "image/svg+xml";
|
|
72
|
+
else if (normalizedPath.endsWith(".gif")) contentType = "image/gif";
|
|
73
|
+
else if (normalizedPath.endsWith(".webp")) contentType = "image/webp";
|
|
74
|
+
|
|
75
|
+
response.writeHead(200, {
|
|
76
|
+
"Content-Type": contentType,
|
|
77
|
+
"Access-Control-Allow-Origin": "*",
|
|
78
|
+
});
|
|
79
|
+
response.end(content);
|
|
80
|
+
return;
|
|
81
|
+
} catch (e) {
|
|
82
|
+
response.writeHead(500, { "Content-Type": "text/plain" });
|
|
83
|
+
response.end(`Error reading file: ${e}`);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
response.writeHead(404, { "Content-Type": "text/plain" });
|
|
88
|
+
response.end(`File not found: ${normalizedPath}`);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
53
93
|
if (isDev) {
|
|
54
94
|
response.writeHead(200, { "Content-Type": "text/html" });
|
|
55
95
|
response.end(`
|
package/src/types/runner.ts
CHANGED
|
@@ -2,32 +2,22 @@ import type { BrowserWindow } from "electron";
|
|
|
2
2
|
import type { PipelabContext } from "../context";
|
|
3
3
|
import type {
|
|
4
4
|
Action,
|
|
5
|
-
Condition,
|
|
6
|
-
Loop,
|
|
7
5
|
Expression,
|
|
8
6
|
Event,
|
|
9
7
|
SetOutputActionFn,
|
|
10
|
-
SetOutputLoopFn,
|
|
11
8
|
SetOutputExpressionFn,
|
|
12
9
|
ExtractInputsFromAction,
|
|
13
|
-
ExtractInputsFromCondition,
|
|
14
|
-
ExtractInputsFromLoop,
|
|
15
10
|
ExtractInputsFromEvent,
|
|
16
11
|
ExtractInputsFromExpression,
|
|
17
12
|
} from "@pipelab/shared";
|
|
18
13
|
|
|
19
14
|
export type {
|
|
20
15
|
Action,
|
|
21
|
-
Condition,
|
|
22
|
-
Loop,
|
|
23
16
|
Expression,
|
|
24
17
|
Event,
|
|
25
18
|
SetOutputActionFn,
|
|
26
|
-
SetOutputLoopFn,
|
|
27
19
|
SetOutputExpressionFn,
|
|
28
20
|
ExtractInputsFromAction,
|
|
29
|
-
ExtractInputsFromCondition,
|
|
30
|
-
ExtractInputsFromLoop,
|
|
31
21
|
ExtractInputsFromEvent,
|
|
32
22
|
ExtractInputsFromExpression,
|
|
33
23
|
};
|
|
@@ -60,25 +50,6 @@ export type ActionRunnerData<ACTION extends Action> = {
|
|
|
60
50
|
|
|
61
51
|
export type ActionRunner<ACTION extends Action> = (data: ActionRunnerData<ACTION>) => Promise<void>;
|
|
62
52
|
|
|
63
|
-
export type ConditionRunner<CONDITION extends Condition> = (data: {
|
|
64
|
-
log: typeof console.log;
|
|
65
|
-
inputs: ExtractInputsFromCondition<CONDITION>;
|
|
66
|
-
setMeta: (callback: (data: CONDITION["meta"]) => CONDITION["meta"]) => void;
|
|
67
|
-
meta: CONDITION["meta"];
|
|
68
|
-
cwd: string;
|
|
69
|
-
context: PipelabContext;
|
|
70
|
-
}) => Promise<boolean>;
|
|
71
|
-
|
|
72
|
-
export type LoopRunner<LOOP extends Loop> = (data: {
|
|
73
|
-
log: typeof console.log;
|
|
74
|
-
setOutput: SetOutputLoopFn<LOOP>;
|
|
75
|
-
inputs: ExtractInputsFromLoop<LOOP>;
|
|
76
|
-
setMeta: (callback: (data: LOOP["meta"]) => LOOP["meta"]) => void;
|
|
77
|
-
meta: LOOP["meta"];
|
|
78
|
-
cwd: string;
|
|
79
|
-
context: PipelabContext;
|
|
80
|
-
}) => Promise<"step" | "exit">;
|
|
81
|
-
|
|
82
53
|
export type ExpressionRunner<EXPRESSION extends Expression> = (data: {
|
|
83
54
|
log: typeof console.log;
|
|
84
55
|
setOutput: SetOutputExpressionFn<EXPRESSION>;
|
|
@@ -98,4 +69,4 @@ export type EventRunner<EVENT extends Event> = (data: {
|
|
|
98
69
|
context: PipelabContext;
|
|
99
70
|
}) => Promise<void>;
|
|
100
71
|
|
|
101
|
-
export type Runner = ActionRunner<any> |
|
|
72
|
+
export type Runner = ActionRunner<any> | EventRunner<any>;
|
package/src/utils/fs-extras.ts
CHANGED
|
@@ -23,7 +23,10 @@ import { pipeline } from "node:stream/promises";
|
|
|
23
23
|
export const ensure = async (filesPath: string, defaultContent = "{}") => {
|
|
24
24
|
await mkdirP(dirname(filesPath), { recursive: true });
|
|
25
25
|
try {
|
|
26
|
-
await
|
|
26
|
+
const s = await stat(filesPath);
|
|
27
|
+
if (s.size === 0) {
|
|
28
|
+
await writeFile(filesPath, defaultContent);
|
|
29
|
+
}
|
|
27
30
|
} catch {
|
|
28
31
|
await writeFile(filesPath, defaultContent);
|
|
29
32
|
}
|
package/src/utils/github.ts
CHANGED
|
@@ -25,12 +25,32 @@ export async function fetchPackageReleases(
|
|
|
25
25
|
options: FetchReleaseOptions = {},
|
|
26
26
|
): Promise<GitHubRelease[]> {
|
|
27
27
|
const { repo = "CynToolkit/pipelab", allowPrerelease = false } = options;
|
|
28
|
-
const url = `https://api.github.com/repos/${repo}/releases`;
|
|
29
|
-
|
|
30
|
-
console.log(`[GitHub] Fetching releases for ${packageName} from ${url}...`);
|
|
31
28
|
|
|
32
29
|
try {
|
|
33
|
-
const
|
|
30
|
+
const override = process.env.PIPELAB_OVERRIDE_RELEASE;
|
|
31
|
+
if (override) {
|
|
32
|
+
const targetTag = override.includes("@") ? override : `${packageName}@${override}`;
|
|
33
|
+
console.log(`[GitHub] Fetching specific override release: ${targetTag}`);
|
|
34
|
+
const response = await fetch(
|
|
35
|
+
`https://api.github.com/repos/${repo}/releases/tags/${encodeURIComponent(targetTag)}`,
|
|
36
|
+
{
|
|
37
|
+
headers: {
|
|
38
|
+
"User-Agent": "Pipelab-Desktop-Updater",
|
|
39
|
+
Accept: "application/vnd.github.v3+json",
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
);
|
|
43
|
+
if (response.ok) {
|
|
44
|
+
const release: GitHubRelease = await response.json();
|
|
45
|
+
return [release];
|
|
46
|
+
}
|
|
47
|
+
console.warn(`[GitHub] Override release tag ${targetTag} not found or error occurred`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const matchingRefsUrl = `https://api.github.com/repos/${repo}/git/matching-refs/tags/${encodeURIComponent(packageName)}`;
|
|
51
|
+
console.log(`[GitHub] Querying matching tags from ${matchingRefsUrl}...`);
|
|
52
|
+
|
|
53
|
+
const response = await fetch(matchingRefsUrl, {
|
|
34
54
|
headers: {
|
|
35
55
|
"User-Agent": "Pipelab-Desktop-Updater",
|
|
36
56
|
Accept: "application/vnd.github.v3+json",
|
|
@@ -41,14 +61,71 @@ export async function fetchPackageReleases(
|
|
|
41
61
|
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
|
|
42
62
|
}
|
|
43
63
|
|
|
44
|
-
const
|
|
64
|
+
const refs = await response.json();
|
|
65
|
+
if (!Array.isArray(refs)) {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
45
68
|
|
|
46
|
-
//
|
|
47
|
-
const
|
|
48
|
-
.
|
|
49
|
-
.filter((
|
|
69
|
+
// Extract tags matching the packageName@ pattern
|
|
70
|
+
const matchingTags = refs
|
|
71
|
+
.map((r: any) => r.ref.replace("refs/tags/", ""))
|
|
72
|
+
.filter((tag: string) => tag.startsWith(`${packageName}@`));
|
|
73
|
+
|
|
74
|
+
// Filter by semver and prerelease options
|
|
75
|
+
const filteredTags = matchingTags.filter((tag: string) => {
|
|
76
|
+
const version = tag.split("@").pop();
|
|
77
|
+
if (!version || !semver.valid(version)) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
const isPrerelease = semver.prerelease(version) !== null;
|
|
81
|
+
return allowPrerelease || !isPrerelease;
|
|
82
|
+
});
|
|
50
83
|
|
|
51
|
-
|
|
84
|
+
if (filteredTags.length === 0) {
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Sort by version (newest/highest first)
|
|
89
|
+
filteredTags.sort((a, b) => {
|
|
90
|
+
const vA = a.split("@").pop() || "0.0.0";
|
|
91
|
+
const vB = b.split("@").pop() || "0.0.0";
|
|
92
|
+
return semver.rcompare(vA, vB);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Walk tags from newest to oldest, skipping any that have no GitHub Release attached.
|
|
96
|
+
// A bare git tag (no release) returns 404 from the releases/tags endpoint.
|
|
97
|
+
for (const tag of filteredTags) {
|
|
98
|
+
console.log(`[GitHub] Fetching release details for tag: ${tag}`);
|
|
99
|
+
|
|
100
|
+
const releaseResponse = await fetch(
|
|
101
|
+
`https://api.github.com/repos/${repo}/releases/tags/${encodeURIComponent(tag)}`,
|
|
102
|
+
{
|
|
103
|
+
headers: {
|
|
104
|
+
"User-Agent": "Pipelab-Desktop-Updater",
|
|
105
|
+
Accept: "application/vnd.github.v3+json",
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
if (releaseResponse.status === 404) {
|
|
111
|
+
// Tag exists but no Release was published for it — skip and try older tag
|
|
112
|
+
console.warn(`[GitHub] Tag "${tag}" has no associated Release, skipping.`);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!releaseResponse.ok) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`GitHub API error: ${releaseResponse.status} ${releaseResponse.statusText}`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const release: GitHubRelease = await releaseResponse.json();
|
|
123
|
+
return [release];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// All tags were bare (no Release found)
|
|
127
|
+
console.warn(`[GitHub] No published Release found for any matching tag of "${packageName}".`);
|
|
128
|
+
return [];
|
|
52
129
|
} catch (error) {
|
|
53
130
|
console.error(`[GitHub] Failed to fetch releases for ${packageName}:`, error);
|
|
54
131
|
return [];
|
package/src/utils/remote.ts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { dirname, delimiter, join } from "node:path";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
mkdir,
|
|
5
|
+
readdir,
|
|
6
|
+
readFile,
|
|
7
|
+
writeFile,
|
|
8
|
+
access,
|
|
9
|
+
chmod,
|
|
10
|
+
rm,
|
|
11
|
+
cp,
|
|
12
|
+
rename,
|
|
13
|
+
} from "node:fs/promises";
|
|
4
14
|
import { existsSync, constants, statSync, readdirSync } from "node:fs";
|
|
5
15
|
import pacote from "pacote";
|
|
6
16
|
import semver from "semver";
|
|
@@ -80,12 +90,16 @@ export async function fetchPackage(
|
|
|
80
90
|
isLocal?: boolean;
|
|
81
91
|
entryPoint?: string;
|
|
82
92
|
}> {
|
|
93
|
+
const start = Date.now();
|
|
83
94
|
// 0. Check for local monorepo package in development
|
|
84
95
|
if (isDev && projectRoot && process.env.PIPELAB_FORCE_NPM !== "true") {
|
|
85
96
|
if (packageName.startsWith("@pipelab/")) {
|
|
97
|
+
const localStart = Date.now();
|
|
86
98
|
const local = await tryResolveMonorepoPackage(packageName);
|
|
87
99
|
if (local) {
|
|
88
|
-
console.log(
|
|
100
|
+
console.log(
|
|
101
|
+
`[Fetcher] ${packageName}: Resolved to local source at ${local.packageDir} (${Date.now() - localStart}ms)`,
|
|
102
|
+
);
|
|
89
103
|
return {
|
|
90
104
|
...local,
|
|
91
105
|
resolvedVersion: "workspace",
|
|
@@ -98,7 +112,13 @@ export async function fetchPackage(
|
|
|
98
112
|
const baseDir = ctx.getPackagesPath(packageName);
|
|
99
113
|
let resolvedVersion: string;
|
|
100
114
|
|
|
101
|
-
|
|
115
|
+
let resolvedVersionOrRange = versionOrRange;
|
|
116
|
+
if (resolvedVersionOrRange === "local") {
|
|
117
|
+
resolvedVersionOrRange = "latest";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
console.log(`[Fetcher] Resolving ${packageName}@${resolvedVersionOrRange || "latest"}...`);
|
|
121
|
+
const resolveStart = Date.now();
|
|
102
122
|
|
|
103
123
|
try {
|
|
104
124
|
// 1. Resolve version/range using npm with session-wide memoization and disk cache
|
|
@@ -111,7 +131,7 @@ export async function fetchPackage(
|
|
|
111
131
|
|
|
112
132
|
const packument = await packumentPromise;
|
|
113
133
|
const versions = Object.keys(packument.versions);
|
|
114
|
-
const range =
|
|
134
|
+
const range = resolvedVersionOrRange || "latest";
|
|
115
135
|
|
|
116
136
|
// Prioritize tags (like 'latest', 'beta', etc.) over semver ranges
|
|
117
137
|
const foundVersion = packument["dist-tags"]?.[range] || semver.maxSatisfying(versions, range);
|
|
@@ -122,12 +142,25 @@ export async function fetchPackage(
|
|
|
122
142
|
);
|
|
123
143
|
}
|
|
124
144
|
resolvedVersion = foundVersion;
|
|
125
|
-
console.log(
|
|
145
|
+
console.log(
|
|
146
|
+
`[Fetcher] ${packageName}: Resolved to v${resolvedVersion} via npm (${Date.now() - resolveStart}ms)`,
|
|
147
|
+
);
|
|
126
148
|
} catch (error) {
|
|
127
|
-
console.warn(
|
|
128
|
-
|
|
149
|
+
console.warn(
|
|
150
|
+
`[Fetcher] ${packageName}: remote resolution failed (${Date.now() - resolveStart}ms), trying local fallback...`,
|
|
151
|
+
);
|
|
152
|
+
const fallbackStart = Date.now();
|
|
153
|
+
const fallbackVersion = await tryLocalFallback(
|
|
154
|
+
resolvedVersionOrRange,
|
|
155
|
+
error,
|
|
156
|
+
baseDir,
|
|
157
|
+
packageName,
|
|
158
|
+
);
|
|
129
159
|
if (fallbackVersion) {
|
|
130
160
|
resolvedVersion = fallbackVersion;
|
|
161
|
+
console.log(
|
|
162
|
+
`[Fetcher] ${packageName}: Resolved to local fallback ${resolvedVersion} (${Date.now() - fallbackStart}ms)`,
|
|
163
|
+
);
|
|
131
164
|
} else {
|
|
132
165
|
throw error;
|
|
133
166
|
}
|
|
@@ -136,12 +169,17 @@ export async function fetchPackage(
|
|
|
136
169
|
const cachePath = join(ctx.userDataPath, "cache", "pacote");
|
|
137
170
|
const packageDir = join(baseDir, resolvedVersion);
|
|
138
171
|
|
|
172
|
+
const checkStart = Date.now();
|
|
139
173
|
// If the package already exists and we don't need to install dependencies (or they are already installed), return immediately
|
|
140
174
|
const isInstalled = options?.installDeps
|
|
141
|
-
?
|
|
175
|
+
? isPackageComplete(packageDir) && isDependenciesInstalledSync(packageDir)
|
|
142
176
|
: isPackageComplete(packageDir);
|
|
177
|
+
const checkDuration = Date.now() - checkStart;
|
|
143
178
|
|
|
144
179
|
if (isInstalled) {
|
|
180
|
+
console.log(
|
|
181
|
+
`[Fetcher] ${packageName}@${resolvedVersion}: Already installed (check took ${checkDuration}ms, fetchPackage took ${Date.now() - start}ms)`,
|
|
182
|
+
);
|
|
145
183
|
return { packageDir, resolvedVersion };
|
|
146
184
|
}
|
|
147
185
|
|
|
@@ -149,8 +187,12 @@ export async function fetchPackage(
|
|
|
149
187
|
return withLock(lockKey, async () => {
|
|
150
188
|
if (!isPackageComplete(packageDir)) {
|
|
151
189
|
console.log(`[Fetcher] ${packageName}@${resolvedVersion}: Downloading to ${packageDir}...`);
|
|
152
|
-
|
|
153
|
-
|
|
190
|
+
const downloadStart = Date.now();
|
|
191
|
+
|
|
192
|
+
const tempDir = join(
|
|
193
|
+
baseDir,
|
|
194
|
+
`.tmp-${resolvedVersion}-${Math.random().toString(36).slice(2)}`,
|
|
195
|
+
);
|
|
154
196
|
await mkdir(tempDir, { recursive: true });
|
|
155
197
|
try {
|
|
156
198
|
await pacote.extract(`${packageName}@${resolvedVersion}`, tempDir, {
|
|
@@ -175,6 +217,9 @@ export async function fetchPackage(
|
|
|
175
217
|
throw err;
|
|
176
218
|
}
|
|
177
219
|
}
|
|
220
|
+
console.log(
|
|
221
|
+
`[Fetcher] ${packageName}@${resolvedVersion}: Downloaded and extracted in ${Date.now() - downloadStart}ms`,
|
|
222
|
+
);
|
|
178
223
|
} catch (err) {
|
|
179
224
|
await rm(tempDir, { recursive: true, force: true }).catch(() => {});
|
|
180
225
|
throw err;
|
|
@@ -182,12 +227,23 @@ export async function fetchPackage(
|
|
|
182
227
|
}
|
|
183
228
|
|
|
184
229
|
// 2. Resolve entry point from package.json for downloaded package
|
|
230
|
+
const entryStart = Date.now();
|
|
185
231
|
const entryPoint = await resolveEntryPoint(packageDir, packageName);
|
|
232
|
+
console.log(
|
|
233
|
+
`[Fetcher] ${packageName}@${resolvedVersion}: Resolved entry point in ${Date.now() - entryStart}ms`,
|
|
234
|
+
);
|
|
186
235
|
|
|
187
236
|
if (options?.installDeps) {
|
|
237
|
+
const depsStart = Date.now();
|
|
188
238
|
await installDependencies(packageDir, packageName, options);
|
|
239
|
+
console.log(
|
|
240
|
+
`[Fetcher] ${packageName}@${resolvedVersion}: Installed dependencies in ${Date.now() - depsStart}ms`,
|
|
241
|
+
);
|
|
189
242
|
}
|
|
190
243
|
|
|
244
|
+
console.log(
|
|
245
|
+
`[Fetcher] ${packageName}@${resolvedVersion}: FetchPackage complete in ${Date.now() - start}ms`,
|
|
246
|
+
);
|
|
191
247
|
return { packageDir, resolvedVersion, entryPoint };
|
|
192
248
|
});
|
|
193
249
|
}
|
|
@@ -245,11 +301,15 @@ export async function runPnpm(
|
|
|
245
301
|
* Installs a specific version of Node.js if not already present.
|
|
246
302
|
*/
|
|
247
303
|
export async function ensureNodeJS(context: PipelabContext, version = DEFAULT_NODE_VERSION) {
|
|
304
|
+
const checkStart = Date.now();
|
|
248
305
|
const isWindows = process.platform === "win32";
|
|
249
306
|
const nodeDir = context.getThirdPartyPath("node", version);
|
|
250
307
|
const finalNodePath = join(nodeDir, isWindows ? "node.exe" : "bin/node");
|
|
251
308
|
|
|
252
309
|
if (isNodeJSComplete(finalNodePath)) {
|
|
310
|
+
console.log(
|
|
311
|
+
`[Environment] Node.js check took ${Date.now() - checkStart}ms (found at ${finalNodePath})`,
|
|
312
|
+
);
|
|
253
313
|
return finalNodePath;
|
|
254
314
|
}
|
|
255
315
|
|
|
@@ -269,10 +329,13 @@ export async function ensureNodeJS(context: PipelabContext, version = DEFAULT_NO
|
|
|
269
329
|
|
|
270
330
|
sendStartupProgress(`Downloading Node.js v${version}...`);
|
|
271
331
|
console.log(`Downloading Node.js from ${downloadUrl}...`);
|
|
332
|
+
const dlStart = Date.now();
|
|
272
333
|
await downloadFile(downloadUrl, archivePath);
|
|
334
|
+
console.log(`[Environment] Node.js download took ${Date.now() - dlStart}ms`);
|
|
273
335
|
|
|
274
336
|
sendStartupProgress(`Extracting Node.js v${version}...`);
|
|
275
337
|
console.log(`Extracting Node.js to ${tempDir}...`);
|
|
338
|
+
const extStart = Date.now();
|
|
276
339
|
const extractTempDir = join(tempDir, "extracted");
|
|
277
340
|
await mkdir(extractTempDir, { recursive: true });
|
|
278
341
|
|
|
@@ -290,7 +353,10 @@ export async function ensureNodeJS(context: PipelabContext, version = DEFAULT_NO
|
|
|
290
353
|
const parentDir = dirname(nodeDir);
|
|
291
354
|
await mkdir(parentDir, { recursive: true });
|
|
292
355
|
|
|
293
|
-
const tempNodeDir = join(
|
|
356
|
+
const tempNodeDir = join(
|
|
357
|
+
parentDir,
|
|
358
|
+
`.tmp-node-${version}-${Math.random().toString(36).slice(2)}`,
|
|
359
|
+
);
|
|
294
360
|
await mkdir(tempNodeDir, { recursive: true });
|
|
295
361
|
|
|
296
362
|
try {
|
|
@@ -313,11 +379,13 @@ export async function ensureNodeJS(context: PipelabContext, version = DEFAULT_NO
|
|
|
313
379
|
throw err;
|
|
314
380
|
}
|
|
315
381
|
}
|
|
382
|
+
console.log(`[Environment] Node.js extraction took ${Date.now() - extStart}ms`);
|
|
316
383
|
} finally {
|
|
317
384
|
await rm(tempNodeDir, { recursive: true, force: true }).catch(() => {});
|
|
318
385
|
await rm(tempDir, { recursive: true, force: true }).catch(() => {});
|
|
319
386
|
}
|
|
320
387
|
|
|
388
|
+
console.log(`[Environment] Node.js set up complete in ${Date.now() - checkStart}ms`);
|
|
321
389
|
return finalNodePath;
|
|
322
390
|
});
|
|
323
391
|
}
|
|
@@ -326,10 +394,14 @@ export async function ensureNodeJS(context: PipelabContext, version = DEFAULT_NO
|
|
|
326
394
|
* Installs the PNPM package from npm if not already present.
|
|
327
395
|
*/
|
|
328
396
|
export async function ensurePNPM(context: PipelabContext, version = DEFAULT_PNPM_VERSION) {
|
|
397
|
+
const checkStart = Date.now();
|
|
329
398
|
const pnpmDir = context.getPackagesPath("pnpm", version);
|
|
330
399
|
const pnpmPath = join(pnpmDir, "bin", "pnpm.cjs");
|
|
331
400
|
|
|
332
401
|
if (existsSync(pnpmPath)) {
|
|
402
|
+
console.log(
|
|
403
|
+
`[Environment] PNPM check took ${Date.now() - checkStart}ms (found at ${pnpmPath})`,
|
|
404
|
+
);
|
|
333
405
|
return pnpmPath;
|
|
334
406
|
}
|
|
335
407
|
|
|
@@ -340,11 +412,13 @@ export async function ensurePNPM(context: PipelabContext, version = DEFAULT_PNPM
|
|
|
340
412
|
const { packageDir } = await fetchPackage("pnpm", version, {
|
|
341
413
|
context,
|
|
342
414
|
});
|
|
415
|
+
console.log(`[Environment] PNPM set up complete in ${Date.now() - checkStart}ms`);
|
|
343
416
|
return join(packageDir, "bin", "pnpm.cjs");
|
|
344
417
|
});
|
|
345
418
|
}
|
|
346
419
|
|
|
347
420
|
async function installDependencies(packageDir: string, packageName: string, options: FetchOptions) {
|
|
421
|
+
const start = Date.now();
|
|
348
422
|
const nodeModulesPath = join(packageDir, "node_modules");
|
|
349
423
|
|
|
350
424
|
if (isDependenciesInstalledSync(packageDir)) {
|
|
@@ -359,12 +433,14 @@ async function installDependencies(packageDir: string, packageName: string, opti
|
|
|
359
433
|
try {
|
|
360
434
|
// Copy package.json to tempDir so pnpm can install dependencies
|
|
361
435
|
await cp(join(packageDir, "package.json"), join(tempDir, "package.json"));
|
|
362
|
-
|
|
436
|
+
|
|
363
437
|
console.log(`[Fetcher] ${packageName}: Ensuring dependencies are installed...`);
|
|
438
|
+
const pnpmStart = Date.now();
|
|
364
439
|
const { all } = await runPnpm(tempDir, {
|
|
365
440
|
signal: options.signal,
|
|
366
441
|
context: options.context,
|
|
367
442
|
});
|
|
443
|
+
console.log(`[Fetcher] ${packageName}: pnpm install command took ${Date.now() - pnpmStart}ms`);
|
|
368
444
|
|
|
369
445
|
if (all) console.log(`[Fetcher] ${packageName}: Installation trace:\n${all}`);
|
|
370
446
|
|
|
@@ -373,8 +449,11 @@ async function installDependencies(packageDir: string, packageName: string, opti
|
|
|
373
449
|
if (existsSync(nodeModulesPath)) {
|
|
374
450
|
await rm(nodeModulesPath, { recursive: true, force: true }).catch(() => {});
|
|
375
451
|
}
|
|
452
|
+
const renameStart = Date.now();
|
|
376
453
|
await rename(tempNodeModules, nodeModulesPath);
|
|
377
|
-
console.log(
|
|
454
|
+
console.log(
|
|
455
|
+
`[Fetcher] ${packageName}: Dependencies installed successfully (rename took ${Date.now() - renameStart}ms, total installDependencies took ${Date.now() - start}ms).`,
|
|
456
|
+
);
|
|
378
457
|
} catch (err: any) {
|
|
379
458
|
console.error(
|
|
380
459
|
`[Fetcher] ${packageName}: CRITICAL ERROR during dependency installation: ${err.message}`,
|
|
@@ -527,30 +606,42 @@ async function crawlMonorepoPackages(): Promise<Record<string, string>> {
|
|
|
527
606
|
return cache;
|
|
528
607
|
}
|
|
529
608
|
|
|
530
|
-
async function
|
|
609
|
+
async function tryLocalFallback(
|
|
610
|
+
versionOrRange: string | undefined,
|
|
611
|
+
_error: unknown,
|
|
612
|
+
baseDir: string,
|
|
613
|
+
logPrefix: string,
|
|
614
|
+
): Promise<string | null> {
|
|
531
615
|
if (!existsSync(baseDir)) return null;
|
|
532
616
|
try {
|
|
533
617
|
const entries = await readdir(baseDir, { withFileTypes: true });
|
|
534
|
-
const
|
|
618
|
+
const localVersions = entries
|
|
535
619
|
.filter((e) => e.isDirectory() || e.isSymbolicLink())
|
|
536
620
|
.map((e) => e.name)
|
|
537
|
-
.
|
|
538
|
-
return versions[0] || null;
|
|
539
|
-
} catch {
|
|
540
|
-
return null;
|
|
541
|
-
}
|
|
542
|
-
}
|
|
621
|
+
.filter((name) => !!semver.valid(name));
|
|
543
622
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
)
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
623
|
+
if (localVersions.length === 0) return null;
|
|
624
|
+
|
|
625
|
+
const range = versionOrRange || "latest";
|
|
626
|
+
|
|
627
|
+
if (range === "latest") {
|
|
628
|
+
const sorted = localVersions.sort((a, b) => semver.rcompare(a, b));
|
|
629
|
+
const latestLocal = sorted[0] || null;
|
|
630
|
+
if (latestLocal) {
|
|
631
|
+
console.info(`[Fetcher] ${logPrefix}: Using locally cached latest version: ${latestLocal}`);
|
|
632
|
+
return latestLocal;
|
|
633
|
+
}
|
|
634
|
+
} else {
|
|
635
|
+
const matched = semver.maxSatisfying(localVersions, range);
|
|
636
|
+
if (matched) {
|
|
637
|
+
console.info(
|
|
638
|
+
`[Fetcher] ${logPrefix}: Using locally cached matching version: ${matched} for range ${range}`,
|
|
639
|
+
);
|
|
640
|
+
return matched;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
} catch (e) {
|
|
644
|
+
console.warn(`[Fetcher] ${logPrefix}: Error during local fallback resolution:`, e);
|
|
554
645
|
}
|
|
555
646
|
return null;
|
|
556
647
|
}
|
package/src/utils.ts
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
import { nanoid } from "nanoid";
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
usePlugins,
|
|
4
|
+
RendererPluginDefinition,
|
|
5
|
+
processGraph,
|
|
6
|
+
useLogger,
|
|
7
|
+
BuildHistoryEntry,
|
|
8
|
+
Variable,
|
|
9
|
+
AppConfig,
|
|
10
|
+
transformUrl,
|
|
11
|
+
} from "@pipelab/shared";
|
|
4
12
|
import { downloadFile, DownloadHooks } from "./utils/fs-extras";
|
|
5
13
|
import { access, chmod, mkdir, rm, writeFile, readdir, cp } from "node:fs/promises";
|
|
6
14
|
import { dirname, join } from "node:path";
|
|
7
15
|
import { pathToFileURL } from "node:url";
|
|
8
16
|
import { isDev, projectRoot, PipelabContext } from "./context";
|
|
9
17
|
import { constants, existsSync } from "node:fs";
|
|
10
|
-
import { processGraph } from "@pipelab/shared";
|
|
11
18
|
import { handleActionExecute } from "./handler-func";
|
|
12
|
-
import { useLogger } from "@pipelab/shared";
|
|
13
19
|
import { BuildHistoryStorage } from "./handlers/build-history";
|
|
14
|
-
import type { BuildHistoryEntry } from "@pipelab/shared";
|
|
15
|
-
import type { Variable } from "@pipelab/shared";
|
|
16
20
|
|
|
17
21
|
import { ensure, generateTempFolder, extractTarGz, extractZip, zipFolder } from "./utils/fs-extras";
|
|
18
22
|
import { fetchPipelabAsset } from "./utils/remote";
|
|
19
23
|
import { loadPipelabPlugin } from "./plugins-registry";
|
|
20
24
|
import { setupConfigFile } from "./config";
|
|
21
|
-
import { AppConfig } from "@pipelab/shared";
|
|
22
25
|
|
|
23
26
|
export const getFinalPlugins = () => {
|
|
24
27
|
const { plugins } = usePlugins();
|
|
@@ -29,13 +32,6 @@ export const getFinalPlugins = () => {
|
|
|
29
32
|
for (const plugin of plugins.value) {
|
|
30
33
|
const finalNodes = [];
|
|
31
34
|
|
|
32
|
-
const transformUrl = (url: string) => {
|
|
33
|
-
if (url.startsWith("file://")) {
|
|
34
|
-
return url.replace("file://", "media://");
|
|
35
|
-
}
|
|
36
|
-
return url;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
35
|
const finalIcon =
|
|
40
36
|
plugin.icon?.type === "image"
|
|
41
37
|
? {
|
package/src/presets/if.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
import { PresetFn, SavedFile } from "@pipelab/shared";
|
|
3
|
-
|
|
4
|
-
export const ifPreset: PresetFn = async () => {
|
|
5
|
-
const branchId = "branchId";
|
|
6
|
-
const logOkId = "logOkId";
|
|
7
|
-
const logKoId = "logKoId";
|
|
8
|
-
const booleanId = "booleanId";
|
|
9
|
-
|
|
10
|
-
const data: SavedFile = {
|
|
11
|
-
version: "1.0.0",
|
|
12
|
-
name: "Condition demo",
|
|
13
|
-
description: "Condition demo",
|
|
14
|
-
variables: [
|
|
15
|
-
{
|
|
16
|
-
type: "boolean",
|
|
17
|
-
id: booleanId,
|
|
18
|
-
description: "The value of the conditon",
|
|
19
|
-
name: "Value",
|
|
20
|
-
value: true,
|
|
21
|
-
},
|
|
22
|
-
],
|
|
23
|
-
canvas: {
|
|
24
|
-
blocks: [
|
|
25
|
-
{
|
|
26
|
-
type: "condition",
|
|
27
|
-
origin: {
|
|
28
|
-
pluginId: "system",
|
|
29
|
-
nodeId: "branch",
|
|
30
|
-
},
|
|
31
|
-
uid: branchId,
|
|
32
|
-
params: {
|
|
33
|
-
condition: "",
|
|
34
|
-
},
|
|
35
|
-
branchTrue: [
|
|
36
|
-
{
|
|
37
|
-
type: "action",
|
|
38
|
-
origin: {
|
|
39
|
-
pluginId: "system",
|
|
40
|
-
nodeId: "log",
|
|
41
|
-
},
|
|
42
|
-
uid: logOkId,
|
|
43
|
-
params: {
|
|
44
|
-
text: "OK",
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
],
|
|
48
|
-
branchFalse: [
|
|
49
|
-
{
|
|
50
|
-
type: "action",
|
|
51
|
-
origin: {
|
|
52
|
-
pluginId: "system",
|
|
53
|
-
nodeId: "log",
|
|
54
|
-
},
|
|
55
|
-
uid: logKoId,
|
|
56
|
-
params: {
|
|
57
|
-
text: "KO",
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
],
|
|
61
|
-
},
|
|
62
|
-
],
|
|
63
|
-
},
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
return {
|
|
67
|
-
data,
|
|
68
|
-
};
|
|
69
|
-
};
|