@stage5/lumine 0.2.3 → 0.2.5
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/bin/lumine.js +1 -2841
- package/lib/api.js +381 -0
- package/lib/assets.js +670 -0
- package/lib/auth.js +217 -0
- package/lib/commands.js +1491 -0
- package/lib/constants.js +256 -0
- package/lib/http.js +72 -0
- package/lib/sdk.js +489 -0
- package/lib/util.js +154 -0
- package/lib/workspace.js +577 -0
- package/package.json +5 -1
package/lib/api.js
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
|
|
3
|
+
import { PACKAGE_METADATA_URL, UPDATE_CHECK_TIMEOUT_MS } from "./constants.js";
|
|
4
|
+
import { requestJson } from "./http.js";
|
|
5
|
+
import { isNewerVersion } from "./util.js";
|
|
6
|
+
|
|
7
|
+
export async function listBuilds({ options, auth }) {
|
|
8
|
+
const url = new URL(`${options.apiUrl}/cli/builds`);
|
|
9
|
+
url.searchParams.set("limit", String(options.limit));
|
|
10
|
+
const result = await requestJson({
|
|
11
|
+
url: url.toString(),
|
|
12
|
+
authToken: auth.token,
|
|
13
|
+
timeoutMs: options.timeoutMs,
|
|
14
|
+
});
|
|
15
|
+
return Array.isArray(result.builds) ? result.builds : [];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function listOpenSourceBuilds({ options, auth }) {
|
|
19
|
+
const url = new URL(`${options.apiUrl}/cli/open-source-builds`);
|
|
20
|
+
url.searchParams.set("limit", String(options.limit));
|
|
21
|
+
url.searchParams.set("sort", options.sort);
|
|
22
|
+
if (options.searchQuery) {
|
|
23
|
+
url.searchParams.set("search", options.searchQuery);
|
|
24
|
+
}
|
|
25
|
+
const result = await requestJson({
|
|
26
|
+
url: url.toString(),
|
|
27
|
+
authToken: auth.token,
|
|
28
|
+
timeoutMs: options.timeoutMs,
|
|
29
|
+
});
|
|
30
|
+
return Array.isArray(result.builds) ? result.builds : [];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function loadBuildMetadata({ options, auth, buildId }) {
|
|
34
|
+
const result = await loadBuildFiles({
|
|
35
|
+
options,
|
|
36
|
+
auth,
|
|
37
|
+
buildId,
|
|
38
|
+
includeContent: false,
|
|
39
|
+
});
|
|
40
|
+
if (!result.build) {
|
|
41
|
+
throw new Error(`Build ${buildId} could not be loaded.`);
|
|
42
|
+
}
|
|
43
|
+
return result.build;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function loadOpenSourceBuildFiles({
|
|
47
|
+
options,
|
|
48
|
+
auth,
|
|
49
|
+
buildId,
|
|
50
|
+
includeContent,
|
|
51
|
+
}) {
|
|
52
|
+
const url = new URL(`${options.apiUrl}/cli/build/${buildId}/open-source-files`);
|
|
53
|
+
if (!includeContent) url.searchParams.set("includeContent", "0");
|
|
54
|
+
return await requestJson({
|
|
55
|
+
url: url.toString(),
|
|
56
|
+
authToken: auth.token,
|
|
57
|
+
timeoutMs: options.timeoutMs,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function loadBuildFiles({ options, auth, buildId, includeContent }) {
|
|
62
|
+
const url = new URL(`${options.apiUrl}/cli/build/${buildId}/files`);
|
|
63
|
+
if (!includeContent) url.searchParams.set("includeContent", "0");
|
|
64
|
+
return await requestJson({
|
|
65
|
+
url: url.toString(),
|
|
66
|
+
authToken: auth.token,
|
|
67
|
+
timeoutMs: options.timeoutMs,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function resolveBranchBuild({
|
|
72
|
+
options,
|
|
73
|
+
auth,
|
|
74
|
+
rootBuildId,
|
|
75
|
+
branchNumber,
|
|
76
|
+
}) {
|
|
77
|
+
const result = await requestJson({
|
|
78
|
+
url: `${options.apiUrl}/cli/build/${rootBuildId}/branches/${branchNumber}`,
|
|
79
|
+
authToken: auth.token,
|
|
80
|
+
timeoutMs: options.timeoutMs,
|
|
81
|
+
});
|
|
82
|
+
const branchBuildId = Number(result.build?.id || 0);
|
|
83
|
+
if (!branchBuildId) {
|
|
84
|
+
throw new Error(`Branch ${branchNumber} for Build ${rootBuildId} could not be loaded.`);
|
|
85
|
+
}
|
|
86
|
+
return result.build;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function forkBuild({ options, auth, buildId }) {
|
|
90
|
+
return await requestJson({
|
|
91
|
+
method: "POST",
|
|
92
|
+
url: `${options.apiUrl}/build/${buildId}/fork`,
|
|
93
|
+
authToken: auth.token,
|
|
94
|
+
body: {},
|
|
95
|
+
timeoutMs: options.timeoutMs,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function createBuild({ options, auth, title, description }) {
|
|
100
|
+
return await requestJson({
|
|
101
|
+
method: "POST",
|
|
102
|
+
url: `${options.apiUrl}/build/create`,
|
|
103
|
+
authToken: auth.token,
|
|
104
|
+
body: {
|
|
105
|
+
title,
|
|
106
|
+
...(description ? { description } : {}),
|
|
107
|
+
},
|
|
108
|
+
timeoutMs: options.timeoutMs,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export async function saveProjectFiles({ options, auth, buildId, files, summary }) {
|
|
113
|
+
return await requestJson({
|
|
114
|
+
method: "PUT",
|
|
115
|
+
url: `${options.apiUrl}/build/${buildId}/project-files`,
|
|
116
|
+
authToken: auth.token,
|
|
117
|
+
body: {
|
|
118
|
+
files,
|
|
119
|
+
createVersion: true,
|
|
120
|
+
summary,
|
|
121
|
+
},
|
|
122
|
+
timeoutMs: options.timeoutMs,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function loadContributionDiff({
|
|
127
|
+
options,
|
|
128
|
+
auth,
|
|
129
|
+
rootBuildId,
|
|
130
|
+
contributionBuildId,
|
|
131
|
+
}) {
|
|
132
|
+
return await requestJson({
|
|
133
|
+
url: `${options.apiUrl}/build/${rootBuildId}/contributions/${contributionBuildId}`,
|
|
134
|
+
authToken: auth.token,
|
|
135
|
+
timeoutMs: options.timeoutMs,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export async function mergeContributionIntoMain({
|
|
140
|
+
options,
|
|
141
|
+
auth,
|
|
142
|
+
rootBuildId,
|
|
143
|
+
contributionBuildId,
|
|
144
|
+
}) {
|
|
145
|
+
return await requestJson({
|
|
146
|
+
method: "POST",
|
|
147
|
+
url: `${options.apiUrl}/build/${rootBuildId}/contributions/${contributionBuildId}/merge`,
|
|
148
|
+
authToken: auth.token,
|
|
149
|
+
body: {},
|
|
150
|
+
timeoutMs: options.timeoutMs,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export async function replaceMainWithContribution({
|
|
155
|
+
options,
|
|
156
|
+
auth,
|
|
157
|
+
rootBuildId,
|
|
158
|
+
contributionBuildId,
|
|
159
|
+
}) {
|
|
160
|
+
return await requestJson({
|
|
161
|
+
method: "POST",
|
|
162
|
+
url: `${options.apiUrl}/build/${rootBuildId}/contributions/${contributionBuildId}/replace-main`,
|
|
163
|
+
authToken: auth.token,
|
|
164
|
+
body: {},
|
|
165
|
+
timeoutMs: options.timeoutMs,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export async function publishBuild({ options, buildId, auth }) {
|
|
170
|
+
if (auth.releaseStatus?.state === "up_to_date") {
|
|
171
|
+
return { skipped: true };
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
const result = await requestJson({
|
|
175
|
+
method: "POST",
|
|
176
|
+
url: `${options.apiUrl}/build/${buildId}/publish`,
|
|
177
|
+
authToken: auth.token,
|
|
178
|
+
body: {},
|
|
179
|
+
timeoutMs: options.timeoutMs,
|
|
180
|
+
});
|
|
181
|
+
return { skipped: false, build: result.build || null };
|
|
182
|
+
} catch (error) {
|
|
183
|
+
if (
|
|
184
|
+
error.status === 409 &&
|
|
185
|
+
error.data?.code === "build_release_up_to_date"
|
|
186
|
+
) {
|
|
187
|
+
return {
|
|
188
|
+
skipped: true,
|
|
189
|
+
build: { releaseStatus: error.data.releaseStatus || null },
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
throw error;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export async function mintBuildApiToken({ options, auth, buildId, scopes }) {
|
|
197
|
+
const startedAt = Date.now();
|
|
198
|
+
const result = await requestJson({
|
|
199
|
+
method: "POST",
|
|
200
|
+
url: `${options.apiUrl}/build/${buildId}/api/token`,
|
|
201
|
+
authToken: auth.token,
|
|
202
|
+
body: { scopes },
|
|
203
|
+
timeoutMs: options.timeoutMs,
|
|
204
|
+
});
|
|
205
|
+
if (!result?.token) {
|
|
206
|
+
throw new Error("Failed to mint a build API token.");
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
token: result.token,
|
|
210
|
+
scopes: Array.isArray(result.scopes) ? result.scopes : [],
|
|
211
|
+
ms: Date.now() - startedAt,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export async function buildApiJson({
|
|
216
|
+
options,
|
|
217
|
+
auth,
|
|
218
|
+
buildId,
|
|
219
|
+
buildApiToken,
|
|
220
|
+
endpointPath,
|
|
221
|
+
body,
|
|
222
|
+
}) {
|
|
223
|
+
return requestJson({
|
|
224
|
+
method: "POST",
|
|
225
|
+
url: `${options.apiUrl}/build/${buildId}/${endpointPath}`,
|
|
226
|
+
authToken: auth.token,
|
|
227
|
+
body: body || {},
|
|
228
|
+
timeoutMs: options.timeoutMs,
|
|
229
|
+
headers: { "x-build-api-token": buildApiToken },
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export async function fetchAllRuntimeAssets({
|
|
234
|
+
options,
|
|
235
|
+
auth,
|
|
236
|
+
buildId,
|
|
237
|
+
buildApiToken,
|
|
238
|
+
includeReferences = false,
|
|
239
|
+
}) {
|
|
240
|
+
const assets = [];
|
|
241
|
+
let usage = null;
|
|
242
|
+
let cursor = null;
|
|
243
|
+
let projectAssets = null;
|
|
244
|
+
do {
|
|
245
|
+
const data = await buildApiJson({
|
|
246
|
+
options,
|
|
247
|
+
auth,
|
|
248
|
+
buildId,
|
|
249
|
+
buildApiToken,
|
|
250
|
+
endpointPath: "api/files/list",
|
|
251
|
+
body: {
|
|
252
|
+
cursor,
|
|
253
|
+
limit: 50,
|
|
254
|
+
// Opt-in: the server annotates each asset with `referencedByProject`
|
|
255
|
+
// (referenced by draft files, any artifact version snapshot incl. the
|
|
256
|
+
// published one, or derived builds). Prune requires it.
|
|
257
|
+
...(includeReferences ? { includeReferences: true } : {}),
|
|
258
|
+
// Project-owner asset context is opt-in server-side (running apps
|
|
259
|
+
// list frequently and don't need it). First page only — the server
|
|
260
|
+
// pages the full owner set once per request that asks.
|
|
261
|
+
...(cursor === null ? { includeProjectAssets: true } : {}),
|
|
262
|
+
},
|
|
263
|
+
});
|
|
264
|
+
assets.push(...(Array.isArray(data?.assets) ? data.assets : []));
|
|
265
|
+
usage = data?.usage || usage;
|
|
266
|
+
// Project-owner assets (team projects): server-provided context so branch
|
|
267
|
+
// agents can reuse the project's existing media. Absent on older API
|
|
268
|
+
// deployments and for non-team builds.
|
|
269
|
+
if (projectAssets === null && Array.isArray(data?.projectAssets)) {
|
|
270
|
+
projectAssets = data.projectAssets;
|
|
271
|
+
}
|
|
272
|
+
cursor = data?.nextCursor || null;
|
|
273
|
+
} while (cursor);
|
|
274
|
+
return { assets, usage, projectAssets };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export async function loadLumineCliVersionInfo({ options }) {
|
|
278
|
+
const packageMetadata = await loadLocalPackageMetadata();
|
|
279
|
+
const packageName = packageMetadata.name || "@stage5/lumine";
|
|
280
|
+
const version = packageMetadata.version || null;
|
|
281
|
+
return {
|
|
282
|
+
packageName,
|
|
283
|
+
version,
|
|
284
|
+
latestVersion: null,
|
|
285
|
+
updateAvailable: false,
|
|
286
|
+
updateCommand: `npx ${packageName}@latest`,
|
|
287
|
+
checkedAt: null,
|
|
288
|
+
checkFailed: false,
|
|
289
|
+
checkSkipped: !options.updateCheck,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export async function loadLocalPackageMetadata() {
|
|
294
|
+
try {
|
|
295
|
+
const rawPackage = await fs.readFile(PACKAGE_METADATA_URL, "utf8");
|
|
296
|
+
const parsedPackage = JSON.parse(rawPackage);
|
|
297
|
+
return {
|
|
298
|
+
name: String(parsedPackage?.name || "").trim(),
|
|
299
|
+
version: String(parsedPackage?.version || "").trim(),
|
|
300
|
+
};
|
|
301
|
+
} catch {
|
|
302
|
+
return {
|
|
303
|
+
name: "@stage5/lumine",
|
|
304
|
+
version: null,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export async function maybeCheckForLumineCliUpdate({ options }) {
|
|
310
|
+
const info = options.lumineCli || (await loadLumineCliVersionInfo({ options }));
|
|
311
|
+
const checkedAt = new Date().toISOString();
|
|
312
|
+
if (!info.packageName || !info.version) {
|
|
313
|
+
options.lumineCli = {
|
|
314
|
+
...info,
|
|
315
|
+
checkedAt,
|
|
316
|
+
checkFailed: true,
|
|
317
|
+
checkSkipped: false,
|
|
318
|
+
};
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
try {
|
|
323
|
+
const latestVersion = await loadLatestPackageVersion({
|
|
324
|
+
packageName: info.packageName,
|
|
325
|
+
registryUrl: options.npmRegistryUrl,
|
|
326
|
+
});
|
|
327
|
+
const updateAvailable = isNewerVersion(latestVersion, info.version);
|
|
328
|
+
options.lumineCli = {
|
|
329
|
+
...info,
|
|
330
|
+
latestVersion,
|
|
331
|
+
updateAvailable,
|
|
332
|
+
checkedAt,
|
|
333
|
+
checkFailed: false,
|
|
334
|
+
checkSkipped: false,
|
|
335
|
+
};
|
|
336
|
+
if (updateAvailable) {
|
|
337
|
+
printLumineCliUpdateWarning(options.lumineCli);
|
|
338
|
+
}
|
|
339
|
+
} catch {
|
|
340
|
+
options.lumineCli = {
|
|
341
|
+
...info,
|
|
342
|
+
checkedAt,
|
|
343
|
+
checkFailed: true,
|
|
344
|
+
checkSkipped: false,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export async function loadLatestPackageVersion({ packageName, registryUrl }) {
|
|
350
|
+
const encodedPackageName = encodeURIComponent(packageName);
|
|
351
|
+
const result = await requestJson({
|
|
352
|
+
url: `${registryUrl}/${encodedPackageName}/latest`,
|
|
353
|
+
timeoutMs: UPDATE_CHECK_TIMEOUT_MS,
|
|
354
|
+
});
|
|
355
|
+
const latestVersion = String(result?.version || "").trim();
|
|
356
|
+
if (!latestVersion) {
|
|
357
|
+
throw new Error("No latest package version returned");
|
|
358
|
+
}
|
|
359
|
+
return latestVersion;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export function printLumineCliUpdateWarning(info) {
|
|
363
|
+
console.error(
|
|
364
|
+
`lumine: update available for ${info.packageName}: ${info.version} -> ${info.latestVersion}.`,
|
|
365
|
+
);
|
|
366
|
+
console.error(`lumine: run \`${info.updateCommand}\` to use the latest CLI.`);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export function serializeLumineCliMetadata(options) {
|
|
370
|
+
const info = options.lumineCli || {};
|
|
371
|
+
return {
|
|
372
|
+
packageName: info.packageName || "@stage5/lumine",
|
|
373
|
+
version: info.version || null,
|
|
374
|
+
latestVersion: info.latestVersion || null,
|
|
375
|
+
updateAvailable: Boolean(info.updateAvailable),
|
|
376
|
+
updateCommand: info.updateCommand || "npx @stage5/lumine@latest",
|
|
377
|
+
checkedAt: info.checkedAt || null,
|
|
378
|
+
checkFailed: Boolean(info.checkFailed),
|
|
379
|
+
checkSkipped: Boolean(info.checkSkipped),
|
|
380
|
+
};
|
|
381
|
+
}
|