@pipelab/core-node 1.0.0-beta.12 → 1.0.0-beta.14
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/.oxfmtrc.json +1 -1
- package/CHANGELOG.md +18 -0
- package/dist/index.d.mts +23 -12
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +104 -41
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -5
- package/src/config.test.ts +176 -0
- package/src/config.ts +44 -24
- package/src/context.ts +51 -2
- package/src/handlers/build-history.ts +14 -1
- package/src/handlers/engine.ts +6 -7
- package/src/handlers/history.ts +1 -1
- package/src/handlers/plugins.ts +1 -6
- package/src/migrations.ts +3 -15
- package/src/plugins-registry.ts +1 -3
- package/src/runner.ts +0 -1
- package/src/server.ts +1 -0
- package/src/types/runner.ts +1 -0
- package/src/utils/fs-extras.ts +2 -23
- package/src/utils/github.test.ts +213 -0
- package/src/utils/remote.ts +3 -5
- package/src/utils.ts +2 -2
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
fetchPackageReleases,
|
|
4
|
+
fetchLatestPackageRelease,
|
|
5
|
+
fetchLatestDesktopRelease,
|
|
6
|
+
} from "./github";
|
|
7
|
+
|
|
8
|
+
describe("GitHub Release Updates API", () => {
|
|
9
|
+
let fetchSpy: any;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
fetchSpy = vi.spyOn(global, "fetch");
|
|
13
|
+
// Clean up environment variables
|
|
14
|
+
delete process.env.PIPELAB_OVERRIDE_RELEASE;
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
vi.restoreAllMocks();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("fetchPackageReleases - returns empty array when git matching-refs fails", async () => {
|
|
22
|
+
fetchSpy.mockResolvedValueOnce({
|
|
23
|
+
ok: false,
|
|
24
|
+
status: 404,
|
|
25
|
+
statusText: "Not Found",
|
|
26
|
+
} as any);
|
|
27
|
+
|
|
28
|
+
const releases = await fetchPackageReleases("@pipelab/app");
|
|
29
|
+
expect(releases).toEqual([]);
|
|
30
|
+
expect(fetchSpy).toHaveBeenCalledWith(
|
|
31
|
+
expect.stringContaining("matching-refs/tags/%40pipelab%2Fapp"),
|
|
32
|
+
expect.any(Object)
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("fetchPackageReleases - returns empty array when no tags match package", async () => {
|
|
37
|
+
fetchSpy.mockResolvedValueOnce({
|
|
38
|
+
ok: true,
|
|
39
|
+
json: async () => [],
|
|
40
|
+
} as any);
|
|
41
|
+
|
|
42
|
+
const releases = await fetchPackageReleases("@pipelab/app");
|
|
43
|
+
expect(releases).toEqual([]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("fetchPackageReleases - correctly filters prereleases when allowPrerelease is false", async () => {
|
|
47
|
+
// 1. mock matching refs
|
|
48
|
+
fetchSpy.mockResolvedValueOnce({
|
|
49
|
+
ok: true,
|
|
50
|
+
json: async () => [
|
|
51
|
+
{ ref: "refs/tags/@pipelab/app@1.0.0-beta.1" },
|
|
52
|
+
{ ref: "refs/tags/@pipelab/app@1.0.0" },
|
|
53
|
+
],
|
|
54
|
+
} as any);
|
|
55
|
+
|
|
56
|
+
// 2. mock tag lookup for 1.0.0 (since 1.0.0-beta.1 is filtered out)
|
|
57
|
+
fetchSpy.mockResolvedValueOnce({
|
|
58
|
+
ok: true,
|
|
59
|
+
status: 200,
|
|
60
|
+
json: async () => ({
|
|
61
|
+
tag_name: "@pipelab/app@1.0.0",
|
|
62
|
+
prerelease: false,
|
|
63
|
+
published_at: "2026-06-04T00:00:00Z",
|
|
64
|
+
html_url: "https://github.com/CynToolkit/pipelab/releases/tag/%40pipelab%2Fapp%401.0.0",
|
|
65
|
+
assets: [],
|
|
66
|
+
}),
|
|
67
|
+
} as any);
|
|
68
|
+
|
|
69
|
+
const releases = await fetchPackageReleases("@pipelab/app", { allowPrerelease: false });
|
|
70
|
+
expect(releases).toHaveLength(1);
|
|
71
|
+
expect(releases[0].tag_name).toBe("@pipelab/app@1.0.0");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("fetchPackageReleases - includes prereleases when allowPrerelease is true", async () => {
|
|
75
|
+
// 1. mock matching refs
|
|
76
|
+
fetchSpy.mockResolvedValueOnce({
|
|
77
|
+
ok: true,
|
|
78
|
+
json: async () => [
|
|
79
|
+
{ ref: "refs/tags/@pipelab/app@1.0.0-beta.1" },
|
|
80
|
+
{ ref: "refs/tags/@pipelab/app@1.0.0" },
|
|
81
|
+
],
|
|
82
|
+
} as any);
|
|
83
|
+
|
|
84
|
+
// 2. mock tag lookup for 1.0.0 (since sorting puts 1.0.0 first)
|
|
85
|
+
fetchSpy.mockResolvedValueOnce({
|
|
86
|
+
ok: true,
|
|
87
|
+
status: 200,
|
|
88
|
+
json: async () => ({
|
|
89
|
+
tag_name: "@pipelab/app@1.0.0",
|
|
90
|
+
prerelease: false,
|
|
91
|
+
published_at: "2026-06-04T00:00:00Z",
|
|
92
|
+
html_url: "https://github.com/CynToolkit/pipelab/releases/tag/%40pipelab%2Fapp%401.0.0",
|
|
93
|
+
assets: [],
|
|
94
|
+
}),
|
|
95
|
+
} as any);
|
|
96
|
+
|
|
97
|
+
const releases = await fetchPackageReleases("@pipelab/app", { allowPrerelease: true });
|
|
98
|
+
expect(releases).toHaveLength(1);
|
|
99
|
+
expect(releases[0].tag_name).toBe("@pipelab/app@1.0.0");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("fetchPackageReleases - handles tag that has no release (skips and falls back to older tag)", async () => {
|
|
103
|
+
// 1. mock matching refs
|
|
104
|
+
fetchSpy.mockResolvedValueOnce({
|
|
105
|
+
ok: true,
|
|
106
|
+
json: async () => [
|
|
107
|
+
{ ref: "refs/tags/@pipelab/app@1.1.0" },
|
|
108
|
+
{ ref: "refs/tags/@pipelab/app@1.0.0" },
|
|
109
|
+
],
|
|
110
|
+
} as any);
|
|
111
|
+
|
|
112
|
+
// 2. mock tag lookup for 1.1.0 (returns 404 - no release yet, tag exists)
|
|
113
|
+
fetchSpy.mockResolvedValueOnce({
|
|
114
|
+
status: 404,
|
|
115
|
+
ok: false,
|
|
116
|
+
statusText: "Not Found",
|
|
117
|
+
} as any);
|
|
118
|
+
|
|
119
|
+
// 3. mock tag lookup for 1.0.0 (returns 200 - release exists)
|
|
120
|
+
fetchSpy.mockResolvedValueOnce({
|
|
121
|
+
ok: true,
|
|
122
|
+
status: 200,
|
|
123
|
+
json: async () => ({
|
|
124
|
+
tag_name: "@pipelab/app@1.0.0",
|
|
125
|
+
prerelease: false,
|
|
126
|
+
published_at: "2026-06-04T00:00:00Z",
|
|
127
|
+
assets: [],
|
|
128
|
+
}),
|
|
129
|
+
} as any);
|
|
130
|
+
|
|
131
|
+
const releases = await fetchPackageReleases("@pipelab/app");
|
|
132
|
+
expect(releases).toHaveLength(1);
|
|
133
|
+
expect(releases[0].tag_name).toBe("@pipelab/app@1.0.0");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("fetchPackageReleases - handles environment variable PIPELAB_OVERRIDE_RELEASE override", async () => {
|
|
137
|
+
process.env.PIPELAB_OVERRIDE_RELEASE = "1.2.3";
|
|
138
|
+
|
|
139
|
+
// Mock direct tag retrieval
|
|
140
|
+
fetchSpy.mockResolvedValueOnce({
|
|
141
|
+
ok: true,
|
|
142
|
+
status: 200,
|
|
143
|
+
json: async () => ({
|
|
144
|
+
tag_name: "@pipelab/app@1.2.3",
|
|
145
|
+
prerelease: false,
|
|
146
|
+
published_at: "2026-06-04T00:00:00Z",
|
|
147
|
+
assets: [],
|
|
148
|
+
}),
|
|
149
|
+
} as any);
|
|
150
|
+
|
|
151
|
+
const releases = await fetchPackageReleases("@pipelab/app");
|
|
152
|
+
expect(releases).toHaveLength(1);
|
|
153
|
+
expect(releases[0].tag_name).toBe("@pipelab/app@1.2.3");
|
|
154
|
+
expect(fetchSpy).toHaveBeenCalledWith(
|
|
155
|
+
expect.stringContaining("releases/tags/%40pipelab%2Fapp%401.2.3"),
|
|
156
|
+
expect.any(Object)
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("fetchLatestPackageRelease - selects newest matching release", async () => {
|
|
161
|
+
// Mock matching refs
|
|
162
|
+
fetchSpy.mockResolvedValueOnce({
|
|
163
|
+
ok: true,
|
|
164
|
+
json: async () => [
|
|
165
|
+
{ ref: "refs/tags/@pipelab/app@1.0.0" },
|
|
166
|
+
{ ref: "refs/tags/@pipelab/app@2.0.0" },
|
|
167
|
+
{ ref: "refs/tags/@pipelab/app@1.5.0" },
|
|
168
|
+
],
|
|
169
|
+
} as any);
|
|
170
|
+
|
|
171
|
+
// Mock release lookup for 2.0.0 (since 2.0.0 is highest version)
|
|
172
|
+
fetchSpy.mockResolvedValueOnce({
|
|
173
|
+
ok: true,
|
|
174
|
+
status: 200,
|
|
175
|
+
json: async () => ({
|
|
176
|
+
tag_name: "@pipelab/app@2.0.0",
|
|
177
|
+
prerelease: false,
|
|
178
|
+
published_at: "2026-06-04T00:00:00Z",
|
|
179
|
+
assets: [],
|
|
180
|
+
}),
|
|
181
|
+
} as any);
|
|
182
|
+
|
|
183
|
+
const latest = await fetchLatestPackageRelease("@pipelab/app");
|
|
184
|
+
expect(latest).not.toBeNull();
|
|
185
|
+
expect(latest?.tag_name).toBe("@pipelab/app@2.0.0");
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("fetchLatestDesktopRelease - queries the @pipelab/app package specifically", async () => {
|
|
189
|
+
// Mock matching refs
|
|
190
|
+
fetchSpy.mockResolvedValueOnce({
|
|
191
|
+
ok: true,
|
|
192
|
+
json: async () => [
|
|
193
|
+
{ ref: "refs/tags/@pipelab/app@3.0.0" },
|
|
194
|
+
],
|
|
195
|
+
} as any);
|
|
196
|
+
|
|
197
|
+
// Mock release lookup
|
|
198
|
+
fetchSpy.mockResolvedValueOnce({
|
|
199
|
+
ok: true,
|
|
200
|
+
status: 200,
|
|
201
|
+
json: async () => ({
|
|
202
|
+
tag_name: "@pipelab/app@3.0.0",
|
|
203
|
+
prerelease: false,
|
|
204
|
+
published_at: "2026-06-04T00:00:00Z",
|
|
205
|
+
assets: [],
|
|
206
|
+
}),
|
|
207
|
+
} as any);
|
|
208
|
+
|
|
209
|
+
const latest = await fetchLatestDesktopRelease();
|
|
210
|
+
expect(latest).not.toBeNull();
|
|
211
|
+
expect(latest?.tag_name).toBe("@pipelab/app@3.0.0");
|
|
212
|
+
});
|
|
213
|
+
});
|
package/src/utils/remote.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { dirname, delimiter, join } from "node:path";
|
|
2
|
-
import { tmpdir } from "node:os";
|
|
3
2
|
import {
|
|
4
3
|
mkdir,
|
|
5
4
|
readdir,
|
|
@@ -17,10 +16,9 @@ import semver from "semver";
|
|
|
17
16
|
import { isDev, projectRoot, PipelabContext } from "../context";
|
|
18
17
|
import { execa } from "execa";
|
|
19
18
|
import { sendStartupProgress } from "../server";
|
|
20
|
-
import { downloadFile, extractZip, extractTarGz
|
|
19
|
+
import { downloadFile, extractZip, extractTarGz } from "./fs-extras";
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
export const DEFAULT_PNPM_VERSION = "10.12.0";
|
|
21
|
+
import { DEFAULT_NODE_VERSION, DEFAULT_PNPM_VERSION } from "@pipelab/constants";
|
|
24
22
|
|
|
25
23
|
function isPackageComplete(packageDir: string): boolean {
|
|
26
24
|
return existsSync(join(packageDir, "package.json"));
|
|
@@ -324,7 +322,7 @@ export async function ensureNodeJS(context: PipelabContext, version = DEFAULT_NO
|
|
|
324
322
|
|
|
325
323
|
const fileName = `node-v${version}-${downloadPlatform}-${arch}.${extension}`;
|
|
326
324
|
const downloadUrl = `https://nodejs.org/dist/v${version}/${fileName}`;
|
|
327
|
-
const tempDir = await
|
|
325
|
+
const tempDir = await context.createTempFolder("node-download-");
|
|
328
326
|
const archivePath = join(tempDir, fileName);
|
|
329
327
|
|
|
330
328
|
sendStartupProgress(`Downloading Node.js v${version}...`);
|
package/src/utils.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { constants, existsSync } from "node:fs";
|
|
|
18
18
|
import { handleActionExecute } from "./handler-func";
|
|
19
19
|
import { BuildHistoryStorage } from "./handlers/build-history";
|
|
20
20
|
|
|
21
|
-
import { ensure,
|
|
21
|
+
import { ensure, extractTarGz, extractZip, zipFolder } from "./utils/fs-extras";
|
|
22
22
|
import { fetchPipelabAsset } from "./utils/remote";
|
|
23
23
|
import { loadPipelabPlugin } from "./plugins-registry";
|
|
24
24
|
import { setupConfigFile } from "./config";
|
|
@@ -120,7 +120,7 @@ export const executeGraphWithHistory = async ({
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
const logs: any[] = [];
|
|
123
|
-
const sandboxPath = await
|
|
123
|
+
const sandboxPath = await ctx.createTempFolder("pipeline-sandbox-");
|
|
124
124
|
const { logger } = useLogger();
|
|
125
125
|
let completedSteps = 0;
|
|
126
126
|
let failedSteps = 0;
|