@pipelab/core-node 1.0.0-beta.13 → 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.
@@ -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
+ });
@@ -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, generateTempFolder } from "./fs-extras";
19
+ import { downloadFile, extractZip, extractTarGz } from "./fs-extras";
21
20
 
22
- export const DEFAULT_NODE_VERSION = "24.14.1";
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 generateTempFolder(tmpdir());
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, generateTempFolder, extractTarGz, extractZip, zipFolder } from "./utils/fs-extras";
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 generateTempFolder(cachePath);
123
+ const sandboxPath = await ctx.createTempFolder("pipeline-sandbox-");
124
124
  const { logger } = useLogger();
125
125
  let completedSteps = 0;
126
126
  let failedSteps = 0;