@genex-ai/cli-demo 1.36.20-dev.1078 → 1.38.0-dev.1079
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/dist/index.js +635 -68
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3123,15 +3123,15 @@ async function detectSurfaceScan(cwd = process.cwd()) {
|
|
|
3123
3123
|
detail: `repeat.set(${m[1]}, ${m[2]})`
|
|
3124
3124
|
});
|
|
3125
3125
|
}
|
|
3126
|
-
for (const
|
|
3127
|
-
if (/\b(map|alphaMap)\s*:/.test(
|
|
3126
|
+
for (const call3 of ctorArgs(content, "PointsMaterial")) {
|
|
3127
|
+
if (/\b(map|alphaMap)\s*:/.test(call3.text)) continue;
|
|
3128
3128
|
found.squarePoints.push({
|
|
3129
|
-
where: `${rel}:${lineOf(content,
|
|
3129
|
+
where: `${rel}:${lineOf(content, call3.index)}`,
|
|
3130
3130
|
detail: "PointsMaterial with no map/alphaMap"
|
|
3131
3131
|
});
|
|
3132
3132
|
}
|
|
3133
|
-
for (const
|
|
3134
|
-
const args = splitArgs(
|
|
3133
|
+
for (const call3 of ctorArgs(content, "PerspectiveCamera")) {
|
|
3134
|
+
const args = splitArgs(call3.text);
|
|
3135
3135
|
if (args.length < 4) continue;
|
|
3136
3136
|
const near = Number(args[2]);
|
|
3137
3137
|
const far = Number(args[3]);
|
|
@@ -3139,7 +3139,7 @@ async function detectSurfaceScan(cwd = process.cwd()) {
|
|
|
3139
3139
|
const ratio = far / near;
|
|
3140
3140
|
if (ratio <= DEPTH_RATIO_LIMIT) continue;
|
|
3141
3141
|
found.depthRange.push({
|
|
3142
|
-
where: `${rel}:${lineOf(content,
|
|
3142
|
+
where: `${rel}:${lineOf(content, call3.index)}`,
|
|
3143
3143
|
detail: `near ${near} / far ${far}`,
|
|
3144
3144
|
ratio
|
|
3145
3145
|
});
|
|
@@ -3434,7 +3434,7 @@ async function detectShellApis(cwd = process.cwd()) {
|
|
|
3434
3434
|
for (const m of content.matchAll(DOWNLOAD_ASSIGN)) add(found.download, m.index, "a.download");
|
|
3435
3435
|
}
|
|
3436
3436
|
for (const m of content.matchAll(DOWNLOAD_ATTR)) add(found.download, m.index, "a download link");
|
|
3437
|
-
for (const
|
|
3437
|
+
for (const call3 of dialogCalls(content)) add(found.dialogs, call3.index, `${call3.name}()`);
|
|
3438
3438
|
for (const m of content.matchAll(SERVICE_WORKER)) {
|
|
3439
3439
|
add(found.serviceWorker, m.index, "serviceWorker.register");
|
|
3440
3440
|
}
|
|
@@ -8658,10 +8658,10 @@ var PACKAGE_PART = "package";
|
|
|
8658
8658
|
function packagePart(zip) {
|
|
8659
8659
|
return { kind: PACKAGE_PART, bytes: zip, contentType: MediaContentType.Zip };
|
|
8660
8660
|
}
|
|
8661
|
-
async function uploadItemPart(
|
|
8661
|
+
async function uploadItemPart(call3, skuUrl2, part) {
|
|
8662
8662
|
const isPackage = part.kind === PACKAGE_PART;
|
|
8663
8663
|
const base = isPackage ? `${skuUrl2}/package` : `${skuUrl2}/media`;
|
|
8664
|
-
const post = (url, body) =>
|
|
8664
|
+
const post = (url, body) => call3(url, { method: "POST", body: JSON.stringify(body) });
|
|
8665
8665
|
const urlRes = await post(`${base}/upload-url`, isPackage ? { bytes: part.bytes.length } : { kind: part.kind, bytes: part.bytes.length, contentType: part.contentType });
|
|
8666
8666
|
if (!urlRes.ok) return { ok: false, refused: urlRes, what: `upload the ${part.kind}` };
|
|
8667
8667
|
const { uploadUrl, key } = await urlRes.json();
|
|
@@ -8680,19 +8680,19 @@ function bearerCaller(token) {
|
|
|
8680
8680
|
const auth = { Authorization: `Bearer ${token}`, "Content-Type": "application/json" };
|
|
8681
8681
|
return (url, init) => apiFetch(url, { ...init, headers: { ...auth, ...init?.headers ?? {} } });
|
|
8682
8682
|
}
|
|
8683
|
-
async function readPriceGrid(
|
|
8684
|
-
const res = await
|
|
8683
|
+
async function readPriceGrid(call3, apiUrl, projectId) {
|
|
8684
|
+
const res = await call3(`${apiUrl}/api/coin/projects/${encodeURIComponent(projectId)}/skus`).catch(() => null);
|
|
8685
8685
|
if (!res?.ok) return null;
|
|
8686
8686
|
const body = await res.json().catch(() => null);
|
|
8687
8687
|
const grid = body?.priceGrid;
|
|
8688
8688
|
return Array.isArray(grid) && grid.every((n) => typeof n === "number") ? grid : null;
|
|
8689
8689
|
}
|
|
8690
|
-
async function storeTakesFree(
|
|
8691
|
-
const res = await
|
|
8690
|
+
async function storeTakesFree(call3, apiUrl) {
|
|
8691
|
+
const res = await call3(`${apiUrl}/api/store/catalog?limit=1`).catch(() => null);
|
|
8692
8692
|
return res?.ok === true;
|
|
8693
8693
|
}
|
|
8694
|
-
async function priceGridHint(
|
|
8695
|
-
const [grid, free] = await Promise.all([readPriceGrid(
|
|
8694
|
+
async function priceGridHint(call3, apiUrl, projectId) {
|
|
8695
|
+
const [grid, free] = await Promise.all([readPriceGrid(call3, apiUrl, projectId), storeTakesFree(call3, apiUrl)]);
|
|
8696
8696
|
return grid ? priceGridLine(grid, free) : null;
|
|
8697
8697
|
}
|
|
8698
8698
|
async function openLinkedGame(opts, log, rerun) {
|
|
@@ -9892,14 +9892,14 @@ async function runDomain(opts) {
|
|
|
9892
9892
|
}
|
|
9893
9893
|
const base = `${apiUrl}/api/projects/${encodeURIComponent(meta.id)}/domains`;
|
|
9894
9894
|
const auth = { Authorization: `Bearer ${token}` };
|
|
9895
|
-
const
|
|
9895
|
+
const call3 = (url, init) => apiFetch(url, { ...init, headers: { ...auth, "Content-Type": "application/json", ...init?.headers ?? {} } });
|
|
9896
9896
|
let res;
|
|
9897
9897
|
try {
|
|
9898
|
-
if (sub === "list") res = await
|
|
9899
|
-
else if (sub === "add") res = await
|
|
9898
|
+
if (sub === "list") res = await call3(base);
|
|
9899
|
+
else if (sub === "add") res = await call3(base, { method: "POST", body: JSON.stringify({ hostname }) });
|
|
9900
9900
|
else if (sub === "verify")
|
|
9901
|
-
res = await
|
|
9902
|
-
else res = await
|
|
9901
|
+
res = await call3(`${base}/${encodeURIComponent(hostname)}/verify`, { method: "POST" });
|
|
9902
|
+
else res = await call3(`${base}/${encodeURIComponent(hostname)}`, { method: "DELETE" });
|
|
9903
9903
|
} catch (err) {
|
|
9904
9904
|
log.error(`Couldn't reach the API at ${apiUrl}.`);
|
|
9905
9905
|
log.dim(` ${String(err)}`);
|
|
@@ -23134,7 +23134,7 @@ async function runController(opts) {
|
|
|
23134
23134
|
log.plain(c.bold(`genex controller ${kind}`));
|
|
23135
23135
|
log.plain("");
|
|
23136
23136
|
log.step(`Installing the ${kind} controller into ${c.cyan(CODE_DEST + path34.sep)}`);
|
|
23137
|
-
const
|
|
23137
|
+
const plan2 = [
|
|
23138
23138
|
...set.code.map((rel) => ({ from: rel, rel: path34.join(CODE_DEST, rel) })),
|
|
23139
23139
|
...set.assets.map((rel) => ({
|
|
23140
23140
|
from: rel,
|
|
@@ -23144,7 +23144,7 @@ async function runController(opts) {
|
|
|
23144
23144
|
let copied = 0;
|
|
23145
23145
|
let skipped = 0;
|
|
23146
23146
|
try {
|
|
23147
|
-
for (const file of
|
|
23147
|
+
for (const file of plan2) {
|
|
23148
23148
|
const dest = path34.join(root, file.rel);
|
|
23149
23149
|
if (!opts.force && await exists4(dest)) {
|
|
23150
23150
|
skipped++;
|
|
@@ -24164,22 +24164,22 @@ async function context2(opts) {
|
|
|
24164
24164
|
if (!token) return null;
|
|
24165
24165
|
return { token, apiUrl };
|
|
24166
24166
|
}
|
|
24167
|
-
async function readVideo(
|
|
24167
|
+
async function readVideo(path52, log) {
|
|
24168
24168
|
let bytes;
|
|
24169
24169
|
try {
|
|
24170
|
-
bytes = await readFile(
|
|
24170
|
+
bytes = await readFile(path52);
|
|
24171
24171
|
} catch {
|
|
24172
|
-
log.error(`Can't read ${
|
|
24172
|
+
log.error(`Can't read ${path52}.`);
|
|
24173
24173
|
return null;
|
|
24174
24174
|
}
|
|
24175
24175
|
if (bytes.byteLength > MAX_VIDEO_BYTES) {
|
|
24176
|
-
log.error(`${basename(
|
|
24176
|
+
log.error(`${basename(path52)} is ${(bytes.byteLength / 1e6).toFixed(0)} MB \u2014 the limit is ${MAX_VIDEO_BYTES / 1e6} MB.`);
|
|
24177
24177
|
return null;
|
|
24178
24178
|
}
|
|
24179
24179
|
return bytes;
|
|
24180
24180
|
}
|
|
24181
|
-
async function uploadVideo(apiUrl, token, characterId,
|
|
24182
|
-
const contentType = /\.mov$/i.test(
|
|
24181
|
+
async function uploadVideo(apiUrl, token, characterId, path52, bytes, log) {
|
|
24182
|
+
const contentType = /\.mov$/i.test(path52) ? "video/quicktime" : "video/mp4";
|
|
24183
24183
|
const minted = await apiFetch(
|
|
24184
24184
|
`${apiUrl}/api/characters/${encodeURIComponent(characterId)}/motions/video-url`,
|
|
24185
24185
|
{
|
|
@@ -24194,7 +24194,7 @@ async function uploadVideo(apiUrl, token, characterId, path50, bytes, log) {
|
|
|
24194
24194
|
return null;
|
|
24195
24195
|
}
|
|
24196
24196
|
const { uploadUrl, videoUrl } = await minted.json();
|
|
24197
|
-
log.dim(` uploading ${basename(
|
|
24197
|
+
log.dim(` uploading ${basename(path52)} (${(bytes.byteLength / 1e6).toFixed(1)} MB)\u2026`);
|
|
24198
24198
|
const put = await fetch(uploadUrl, {
|
|
24199
24199
|
method: "PUT",
|
|
24200
24200
|
headers: { "Content-Type": contentType, "Content-Length": String(bytes.byteLength) },
|
|
@@ -24206,9 +24206,9 @@ async function uploadVideo(apiUrl, token, characterId, path50, bytes, log) {
|
|
|
24206
24206
|
}
|
|
24207
24207
|
return videoUrl;
|
|
24208
24208
|
}
|
|
24209
|
-
function printPlan(
|
|
24209
|
+
function printPlan(plan2, log, creature) {
|
|
24210
24210
|
log.plain(c.bold(creature ? "Creature animation plan" : "Animation plan"));
|
|
24211
|
-
for (const item of
|
|
24211
|
+
for (const item of plan2.items) {
|
|
24212
24212
|
const count = item.clips > 1 ? ` (${item.clips} clips)` : "";
|
|
24213
24213
|
log.plain(` \xB7 ${c.bold(item.request)}${count} \u2014 ${item.explanation}`);
|
|
24214
24214
|
if (item.supersededSlots.length > 0) {
|
|
@@ -24217,14 +24217,14 @@ function printPlan(plan, log, creature) {
|
|
|
24217
24217
|
);
|
|
24218
24218
|
}
|
|
24219
24219
|
}
|
|
24220
|
-
if (
|
|
24220
|
+
if (plan2.secondsApplied === false) {
|
|
24221
24221
|
log.plain(
|
|
24222
24222
|
` ${c.dim("--duration sets the reference-video length; none of these verbs took the video route, so it did nothing.")}`
|
|
24223
24223
|
);
|
|
24224
24224
|
}
|
|
24225
24225
|
log.plain("");
|
|
24226
|
-
if (
|
|
24227
|
-
else log.dim(` ${
|
|
24226
|
+
if (plan2.credits > 0) log.dim(` ${plan2.totalClips} clips \xB7 ${plan2.credits} credits`);
|
|
24227
|
+
else log.dim(` ${plan2.totalClips} clips`);
|
|
24228
24228
|
log.plain("");
|
|
24229
24229
|
}
|
|
24230
24230
|
async function runCharacterAnimateMotion(opts) {
|
|
@@ -26959,10 +26959,10 @@ async function reportModels(apiUrl, token, opts, log, toolsOnly = false) {
|
|
|
26959
26959
|
shown === lane.models ? ` ${lane.models.length} model${lane.models.length === 1 ? "" : "s"} live:` : ` ${shown.length} featured model${shown.length === 1 ? "" : "s"} of ${lane.models.length} live:`
|
|
26960
26960
|
);
|
|
26961
26961
|
for (const m of shown) {
|
|
26962
|
-
const
|
|
26962
|
+
const plan2 = m.personalPlan ? ` \xB7 personal plan: ${m.personalPlan}` : "";
|
|
26963
26963
|
const kind = m.kind === "typesafe" ? " \xB7 judge (classifier): judge() under a budget, not generate() or bench" : "";
|
|
26964
26964
|
log.plain(
|
|
26965
|
-
` ${c.cyan(m.id)} ${m.label} \u2014 ${usdPerMillion(m.inputUsdPerMillion)} in / ${usdPerMillion(m.outputUsdPerMillion)} out per million tokens${
|
|
26965
|
+
` ${c.cyan(m.id)} ${m.label} \u2014 ${usdPerMillion(m.inputUsdPerMillion)} in / ${usdPerMillion(m.outputUsdPerMillion)} out per million tokens${plan2}${kind}`
|
|
26966
26966
|
);
|
|
26967
26967
|
}
|
|
26968
26968
|
if (hiddenCount > 0) {
|
|
@@ -27125,7 +27125,7 @@ async function runBench(args) {
|
|
|
27125
27125
|
const samples = opts.samples ?? DEFAULT_BENCH_SAMPLES;
|
|
27126
27126
|
const prompt = opts.benchPrompt.trim();
|
|
27127
27127
|
const auth = { Authorization: `Bearer ${token}`, "Content-Type": "application/json" };
|
|
27128
|
-
const
|
|
27128
|
+
const call3 = (url, init) => apiFetch(url, { ...init, headers: { ...auth, ...init?.headers ?? {} } });
|
|
27129
27129
|
let schema;
|
|
27130
27130
|
if (opts.schemaPath) {
|
|
27131
27131
|
try {
|
|
@@ -27204,7 +27204,7 @@ async function runBench(args) {
|
|
|
27204
27204
|
if (!opts.json) log.warn("Stopping \u2014 cancelling the attempt in flight.");
|
|
27205
27205
|
void (async () => {
|
|
27206
27206
|
if (target) {
|
|
27207
|
-
await
|
|
27207
|
+
await call3(`${base}/${encodeURIComponent(target)}/cancel`, { method: "POST" }).catch(() => {
|
|
27208
27208
|
});
|
|
27209
27209
|
}
|
|
27210
27210
|
process.exit(130);
|
|
@@ -27213,7 +27213,7 @@ async function runBench(args) {
|
|
|
27213
27213
|
process.once("SIGINT", onSigint);
|
|
27214
27214
|
try {
|
|
27215
27215
|
for (let i = 0; i < samples; i++) {
|
|
27216
|
-
const res = await
|
|
27216
|
+
const res = await call3(base, {
|
|
27217
27217
|
method: "POST",
|
|
27218
27218
|
body: JSON.stringify({
|
|
27219
27219
|
maxCredits,
|
|
@@ -27227,7 +27227,7 @@ async function runBench(args) {
|
|
|
27227
27227
|
})
|
|
27228
27228
|
});
|
|
27229
27229
|
if (!res.ok) {
|
|
27230
|
-
const verdict = await explainBenchRefusal(res,
|
|
27230
|
+
const verdict = await explainBenchRefusal(res, call3, base, log, opts, i, results.length);
|
|
27231
27231
|
if (verdict.refusal) refusal2 = verdict.refusal;
|
|
27232
27232
|
if (verdict.stop) break;
|
|
27233
27233
|
continue;
|
|
@@ -27238,7 +27238,7 @@ async function runBench(args) {
|
|
|
27238
27238
|
continue;
|
|
27239
27239
|
}
|
|
27240
27240
|
inflight = started.id;
|
|
27241
|
-
const settled = await pollSettled(
|
|
27241
|
+
const settled = await pollSettled(call3, `${base}/${encodeURIComponent(started.id)}`, opts.timeoutSec ?? DEFAULT_SAMPLE_TIMEOUT_SEC);
|
|
27242
27242
|
inflight = null;
|
|
27243
27243
|
const row = sampleFrom(started.id, settled);
|
|
27244
27244
|
results.push(row);
|
|
@@ -27441,7 +27441,7 @@ function printAverage(log, record, attempts) {
|
|
|
27441
27441
|
log.dim(` = that average rounded up to a whole credit. disclosure.estimatedCreditsPerPeriod = your calls per period \xD7 ${formatCredits(record.averageCreditsPerCall)}, rounded up.`);
|
|
27442
27442
|
}
|
|
27443
27443
|
}
|
|
27444
|
-
async function explainBenchRefusal(res,
|
|
27444
|
+
async function explainBenchRefusal(res, call3, base, log, opts, index, settledSoFar) {
|
|
27445
27445
|
if (printedStructuredError(res)) {
|
|
27446
27446
|
const printed = await res.json().catch(() => ({}));
|
|
27447
27447
|
return { stop: true, refusal: { status: res.status, error: typeof printed.error === "string" ? printed.error : null, slotsHeld: null, slotLimit: null } };
|
|
@@ -27449,7 +27449,7 @@ async function explainBenchRefusal(res, call2, base, log, opts, index, settledSo
|
|
|
27449
27449
|
const body = await res.json().catch(() => ({}));
|
|
27450
27450
|
const refusal2 = { status: res.status, error: body.error ?? null, slotsHeld: null, slotLimit: null };
|
|
27451
27451
|
if (res.status === 429 && body.error === "generation_limit") {
|
|
27452
|
-
const counts = serverSlotCounts(body) ?? await readSlotCounts(
|
|
27452
|
+
const counts = serverSlotCounts(body) ?? await readSlotCounts(call3, base);
|
|
27453
27453
|
refusal2.slotsHeld = counts?.held ?? null;
|
|
27454
27454
|
refusal2.slotLimit = counts?.limit ?? null;
|
|
27455
27455
|
if (!opts.json) log.error(` Sample ${index + 1} ${benchSlotsHeldSentence(counts)}`);
|
|
@@ -27490,17 +27490,17 @@ function printProviderRefusal(log, status2, message, awaitingBill = false) {
|
|
|
27490
27490
|
log.dim(` A ${status2} here is this stand's provider configuration refusing the model \u2014 a message for the operator, not something to fix in the game.`);
|
|
27491
27491
|
}
|
|
27492
27492
|
}
|
|
27493
|
-
async function readSlotCounts(
|
|
27494
|
-
const res = await
|
|
27493
|
+
async function readSlotCounts(call3, base) {
|
|
27494
|
+
const res = await call3(`${base}?scope=open`).catch(() => null);
|
|
27495
27495
|
if (!res?.ok) return null;
|
|
27496
27496
|
const body = await res.json().catch(() => null);
|
|
27497
27497
|
return serverSlotCounts(body);
|
|
27498
27498
|
}
|
|
27499
|
-
async function pollSettled(
|
|
27499
|
+
async function pollSettled(call3, url, timeoutSec) {
|
|
27500
27500
|
const deadline = Date.now() + timeoutSec * 1e3;
|
|
27501
27501
|
let last = null;
|
|
27502
27502
|
while (Date.now() < deadline) {
|
|
27503
|
-
const res = await
|
|
27503
|
+
const res = await call3(url).catch(() => null);
|
|
27504
27504
|
if (res?.ok) {
|
|
27505
27505
|
last = await res.json().catch(() => null);
|
|
27506
27506
|
if (last?.billingStatus === "final") return last;
|
|
@@ -27563,9 +27563,9 @@ function emptyBenchJson(apiUrl, projectId, prompt, outputFormat, samples, maxCre
|
|
|
27563
27563
|
}
|
|
27564
27564
|
async function reportStatus(args) {
|
|
27565
27565
|
const { apiUrl, token, projectId, opts, log } = args;
|
|
27566
|
-
const
|
|
27566
|
+
const call3 = (url, init) => apiFetch(url, { ...init, headers: { Authorization: `Bearer ${token}`, ...init?.headers ?? {} } });
|
|
27567
27567
|
const base = `${apiUrl}/api/runtime/development/projects/${encodeURIComponent(projectId)}/generations`;
|
|
27568
|
-
const res = await
|
|
27568
|
+
const res = await call3(`${base}?scope=open`).catch(() => null);
|
|
27569
27569
|
if (!res || !res.ok) {
|
|
27570
27570
|
if (res && printedStructuredError(res)) {
|
|
27571
27571
|
if (opts.json) writeJsonLine(statusJson(apiUrl, projectId, "failed", `http_${res.status}`, [], null, null));
|
|
@@ -27656,14 +27656,14 @@ function statusJson(apiUrl, projectId, status2, error, rows, slotsHeld, slotLimi
|
|
|
27656
27656
|
}
|
|
27657
27657
|
async function cancelCall(args) {
|
|
27658
27658
|
const { apiUrl, token, projectId, id, opts, log } = args;
|
|
27659
|
-
const
|
|
27659
|
+
const call3 = (url, init) => apiFetch(url, { ...init, headers: { Authorization: `Bearer ${token}`, ...init?.headers ?? {} } });
|
|
27660
27660
|
const base = `${apiUrl}/api/runtime/development/projects/${encodeURIComponent(projectId)}/generations`;
|
|
27661
27661
|
const rowUrl = `${base}/${encodeURIComponent(id)}`;
|
|
27662
27662
|
const done = (status2, error, reason, row2) => {
|
|
27663
27663
|
if (opts.json) writeJsonLine({ command: "llm cancel", status: status2, error, id, cancelled: reason === "cancelled", reason, generation: row2 });
|
|
27664
27664
|
if (status2 === "failed") process.exitCode = 1;
|
|
27665
27665
|
};
|
|
27666
|
-
const read = await
|
|
27666
|
+
const read = await call3(rowUrl).catch(() => null);
|
|
27667
27667
|
if (!read || !read.ok) {
|
|
27668
27668
|
if (read && printedStructuredError(read)) return done("failed", `http_${read.status}`, null, null);
|
|
27669
27669
|
const body = read ? await read.json().catch(() => ({})) : {};
|
|
@@ -27702,7 +27702,7 @@ async function cancelCall(args) {
|
|
|
27702
27702
|
}
|
|
27703
27703
|
return done("ok", null, state === "awaiting bill" ? "awaiting_bill" : "already_final", row);
|
|
27704
27704
|
}
|
|
27705
|
-
const res = await
|
|
27705
|
+
const res = await call3(`${rowUrl}/cancel`, { method: "POST" }).catch(() => null);
|
|
27706
27706
|
if (!res || !res.ok) {
|
|
27707
27707
|
if (res && printedStructuredError(res)) return done("failed", `http_${res.status}`, null, row);
|
|
27708
27708
|
const body = res ? await res.json().catch(() => ({})) : {};
|
|
@@ -28658,14 +28658,14 @@ var PlayerClient = class {
|
|
|
28658
28658
|
this.version = options.version ?? getCliVersion();
|
|
28659
28659
|
this.timeoutMs = options.timeoutMs ?? 3e4;
|
|
28660
28660
|
}
|
|
28661
|
-
async call(
|
|
28661
|
+
async call(path52, init = {}, provider) {
|
|
28662
28662
|
const headers = {
|
|
28663
28663
|
Authorization: `Bearer ${this.token}`,
|
|
28664
28664
|
[PLAYER_VERSION_HEADER]: this.version,
|
|
28665
28665
|
...provider ? { [EXTERNAL_PROVIDER_HEADER]: provider } : {},
|
|
28666
28666
|
...init.body === void 0 ? {} : { "Content-Type": "application/json" }
|
|
28667
28667
|
};
|
|
28668
|
-
const res = await apiFetch(`${this.apiUrl}${
|
|
28668
|
+
const res = await apiFetch(`${this.apiUrl}${path52}`, {
|
|
28669
28669
|
...init,
|
|
28670
28670
|
headers,
|
|
28671
28671
|
signal: AbortSignal.timeout(this.timeoutMs)
|
|
@@ -29261,20 +29261,20 @@ async function markRelaunch(version, home) {
|
|
|
29261
29261
|
}
|
|
29262
29262
|
}
|
|
29263
29263
|
async function relaunchWatcher(input) {
|
|
29264
|
-
const
|
|
29264
|
+
const plan2 = relaunchPlan(input);
|
|
29265
29265
|
const log = input.log ?? (() => {
|
|
29266
29266
|
});
|
|
29267
|
-
if (input.relaunched) return { launched: false, plan, reason: "relaunched_child" };
|
|
29268
|
-
if (!relaunchPossible()) return { launched: false, plan, reason: "no_npx" };
|
|
29269
|
-
if (await relaunchOnCooldown(input.home)) return { launched: false, plan, reason: "cooldown" };
|
|
29267
|
+
if (input.relaunched) return { launched: false, plan: plan2, reason: "relaunched_child" };
|
|
29268
|
+
if (!relaunchPossible()) return { launched: false, plan: plan2, reason: "no_npx" };
|
|
29269
|
+
if (await relaunchOnCooldown(input.home)) return { launched: false, plan: plan2, reason: "cooldown" };
|
|
29270
29270
|
await markRelaunch(input.version, input.home);
|
|
29271
29271
|
if (supervisedWatcher()) {
|
|
29272
29272
|
log(`player: stopping so the login item restarts as ${input.version} (it resolves ${PLAYER_AUTOSTART_SPEC} each start)`);
|
|
29273
|
-
return { launched: false, plan, reason: "supervisor_restarts" };
|
|
29273
|
+
return { launched: false, plan: plan2, reason: "supervisor_restarts" };
|
|
29274
29274
|
}
|
|
29275
29275
|
if (playerDryRun()) {
|
|
29276
|
-
log(`player: would relaunch: ${JSON.stringify([
|
|
29277
|
-
return { launched: true, plan, reason: "dry_run" };
|
|
29276
|
+
log(`player: would relaunch: ${JSON.stringify([plan2.launcher, ...plan2.args])}`);
|
|
29277
|
+
return { launched: true, plan: plan2, reason: "dry_run" };
|
|
29278
29278
|
}
|
|
29279
29279
|
let out;
|
|
29280
29280
|
let err;
|
|
@@ -29286,7 +29286,7 @@ async function relaunchWatcher(input) {
|
|
|
29286
29286
|
} catch {
|
|
29287
29287
|
}
|
|
29288
29288
|
try {
|
|
29289
|
-
const child = spawn7(
|
|
29289
|
+
const child = spawn7(plan2.launcher, plan2.args, {
|
|
29290
29290
|
detached: true,
|
|
29291
29291
|
stdio: ["ignore", out ?? "ignore", err ?? "ignore"]
|
|
29292
29292
|
});
|
|
@@ -29299,7 +29299,7 @@ async function relaunchWatcher(input) {
|
|
|
29299
29299
|
}
|
|
29300
29300
|
}
|
|
29301
29301
|
log(`player: relaunching as ${input.version}`);
|
|
29302
|
-
return { launched: true, plan };
|
|
29302
|
+
return { launched: true, plan: plan2 };
|
|
29303
29303
|
}
|
|
29304
29304
|
|
|
29305
29305
|
// src/lib/player/loop.ts
|
|
@@ -29989,6 +29989,11 @@ function writeJsonLine2(value) {
|
|
|
29989
29989
|
`);
|
|
29990
29990
|
}
|
|
29991
29991
|
|
|
29992
|
+
// src/commands/native.ts
|
|
29993
|
+
import fs51 from "fs/promises";
|
|
29994
|
+
import os15 from "os";
|
|
29995
|
+
import path51 from "path";
|
|
29996
|
+
|
|
29992
29997
|
// src/lib/native-build.ts
|
|
29993
29998
|
import { createHash as createHash5 } from "crypto";
|
|
29994
29999
|
import { createReadStream } from "fs";
|
|
@@ -30595,6 +30600,380 @@ async function listInGallery(ctx, body, log) {
|
|
|
30595
30600
|
return null;
|
|
30596
30601
|
}
|
|
30597
30602
|
|
|
30603
|
+
// src/lib/cloud-build.ts
|
|
30604
|
+
import fs49 from "fs/promises";
|
|
30605
|
+
var CLOUD_BUILD_PLATFORMS = ["windows", "linux", "mac"];
|
|
30606
|
+
var isCloudBuildPlatform = (value) => CLOUD_BUILD_PLATFORMS.includes(value);
|
|
30607
|
+
var CLOUD_BUILD_JOB_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
|
|
30608
|
+
var UPLOAD_INCOMPLETE = "cloud_build_upload_incomplete";
|
|
30609
|
+
var PART_ATTEMPTS = 3;
|
|
30610
|
+
var PART_BACKOFF_MS = [500, 2e3];
|
|
30611
|
+
var PART_CONCURRENCY2 = 2;
|
|
30612
|
+
async function call2(ctx, route, init, log, silent) {
|
|
30613
|
+
let res;
|
|
30614
|
+
try {
|
|
30615
|
+
res = await apiFetch(`${ctx.apiUrl}/api/games/${ctx.projectId}${route}`, {
|
|
30616
|
+
...init,
|
|
30617
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${ctx.token}`, ...init.headers ?? {} }
|
|
30618
|
+
});
|
|
30619
|
+
} catch (err) {
|
|
30620
|
+
log.error(`Couldn't reach the API at ${ctx.apiUrl}: ${String(err)}`);
|
|
30621
|
+
return { ok: false, refusal: { status: 0, body: null } };
|
|
30622
|
+
}
|
|
30623
|
+
const body = await res.clone().json().catch(() => null);
|
|
30624
|
+
if (res.ok && body) return { ok: true, value: body, status: res.status };
|
|
30625
|
+
const error = typeof body?.error === "string" ? body.error : void 0;
|
|
30626
|
+
const refusal2 = { status: res.status, body: body && typeof body === "object" ? body : null, ...error ? { error } : {} };
|
|
30627
|
+
if (res.ok) {
|
|
30628
|
+
log.error(`The API answered HTTP ${res.status} with no build.`);
|
|
30629
|
+
return { ok: false, refusal: refusal2 };
|
|
30630
|
+
}
|
|
30631
|
+
if (silent !== void 0 && error === silent) return { ok: false, refusal: refusal2 };
|
|
30632
|
+
if (res.status === 401) {
|
|
30633
|
+
log.error("Not authorized (HTTP 401) \u2014 your sign-in expired or was revoked.");
|
|
30634
|
+
log.plain(" Fix: run `genex link <your-game>` and sign in as the game's owner, then re-run this command.");
|
|
30635
|
+
} else if (res.status === 404) {
|
|
30636
|
+
log.error(
|
|
30637
|
+
route.includes("/cloud-builds/") ? "No such build for this game (HTTP 404)." : "This account doesn't own this game, or Build on Genex is not on for this stand (HTTP 404)."
|
|
30638
|
+
);
|
|
30639
|
+
} else if (res.status === 429 && error === "rate_limited") {
|
|
30640
|
+
log.error(`Rate limited starting a build (HTTP 429).${retryAfterHint(res)}`);
|
|
30641
|
+
} else if (error === "cloud_build_unavailable") {
|
|
30642
|
+
log.error("Build on Genex isn't available on this stand right now (HTTP 503). Try again later.");
|
|
30643
|
+
} else {
|
|
30644
|
+
const reason = await serverReason(res);
|
|
30645
|
+
log.error(reason ?? `The API refused the build (HTTP ${res.status}).`);
|
|
30646
|
+
}
|
|
30647
|
+
return { ok: false, refusal: refusal2 };
|
|
30648
|
+
}
|
|
30649
|
+
async function startCloudBuild(ctx, body, log) {
|
|
30650
|
+
const answer = await call2(ctx, "/cloud-builds", { method: "POST", body: JSON.stringify(body) }, log);
|
|
30651
|
+
if (!answer.ok) return answer;
|
|
30652
|
+
const v = answer.value;
|
|
30653
|
+
if (!v.build) {
|
|
30654
|
+
log.error(`The API answered HTTP ${answer.status} with no build.`);
|
|
30655
|
+
return { ok: false, refusal: { status: answer.status, body: answer.value } };
|
|
30656
|
+
}
|
|
30657
|
+
return {
|
|
30658
|
+
ok: true,
|
|
30659
|
+
status: answer.status,
|
|
30660
|
+
value: {
|
|
30661
|
+
status: answer.status,
|
|
30662
|
+
build: v.build,
|
|
30663
|
+
...v.upload ? { upload: v.upload } : {},
|
|
30664
|
+
...v.deduplicated ? { deduplicated: true } : {}
|
|
30665
|
+
}
|
|
30666
|
+
};
|
|
30667
|
+
}
|
|
30668
|
+
async function completeCloudBuildSource(ctx, jobId, parts, log) {
|
|
30669
|
+
const route = `/cloud-builds/${encodeURIComponent(jobId)}/source`;
|
|
30670
|
+
const answer = await call2(ctx, route, { method: "POST", body: JSON.stringify({ parts }) }, log, UPLOAD_INCOMPLETE);
|
|
30671
|
+
if (!answer.ok) return answer;
|
|
30672
|
+
const build = answer.value.build;
|
|
30673
|
+
if (!build) {
|
|
30674
|
+
log.error(`The API answered HTTP ${answer.status} with no build.`);
|
|
30675
|
+
return { ok: false, refusal: { status: answer.status, body: answer.value } };
|
|
30676
|
+
}
|
|
30677
|
+
return { ok: true, status: answer.status, value: build };
|
|
30678
|
+
}
|
|
30679
|
+
async function readCloudBuild(ctx, jobId, log) {
|
|
30680
|
+
const answer = await call2(ctx, `/cloud-builds/${encodeURIComponent(jobId)}`, { method: "GET" }, log);
|
|
30681
|
+
if (!answer.ok) return answer;
|
|
30682
|
+
const build = answer.value.build;
|
|
30683
|
+
if (!build) {
|
|
30684
|
+
log.error(`The API answered HTTP ${answer.status} with no build.`);
|
|
30685
|
+
return { ok: false, refusal: { status: answer.status, body: answer.value } };
|
|
30686
|
+
}
|
|
30687
|
+
return { ok: true, status: answer.status, value: build };
|
|
30688
|
+
}
|
|
30689
|
+
async function readPart2(file, position, length) {
|
|
30690
|
+
const buffer = Buffer.alloc(length);
|
|
30691
|
+
const { bytesRead } = await file.read(buffer, 0, length, position);
|
|
30692
|
+
if (bytesRead !== length) throw new Error("the project zip changed while it was being uploaded");
|
|
30693
|
+
return buffer;
|
|
30694
|
+
}
|
|
30695
|
+
async function putPart(url, bytes, sleep7) {
|
|
30696
|
+
for (let attempt = 0; attempt < PART_ATTEMPTS; attempt++) {
|
|
30697
|
+
let res = null;
|
|
30698
|
+
try {
|
|
30699
|
+
res = await fetch(url, { method: "PUT", body: bytes });
|
|
30700
|
+
} catch {
|
|
30701
|
+
res = null;
|
|
30702
|
+
}
|
|
30703
|
+
if (res?.ok) {
|
|
30704
|
+
const etag = res.headers.get("etag");
|
|
30705
|
+
await res.arrayBuffer().catch(() => void 0);
|
|
30706
|
+
if (etag) return etag;
|
|
30707
|
+
} else if (res && res.status < 500 && res.status !== 408 && res.status !== 429) {
|
|
30708
|
+
return null;
|
|
30709
|
+
}
|
|
30710
|
+
if (attempt < PART_ATTEMPTS - 1) await sleep7(PART_BACKOFF_MS[attempt] ?? 2e3);
|
|
30711
|
+
}
|
|
30712
|
+
return null;
|
|
30713
|
+
}
|
|
30714
|
+
async function uploadCloudBuildSource(upload, zipPath, sizeBytes, log, onProgress, sleep7 = (ms) => new Promise((resolve) => setTimeout(resolve, ms))) {
|
|
30715
|
+
const parts = [...upload.parts].sort((a, b) => a.partNumber - b.partNumber);
|
|
30716
|
+
const expected = Math.ceil(sizeBytes / upload.partBytes);
|
|
30717
|
+
if (parts.length !== expected || parts.some((p, i) => p.partNumber !== i + 1)) {
|
|
30718
|
+
log.error(`The API sent ${parts.length} upload URLs for a ${expected}-part project. Run this again.`);
|
|
30719
|
+
return null;
|
|
30720
|
+
}
|
|
30721
|
+
const file = await fs49.open(zipPath, "r");
|
|
30722
|
+
const done = [];
|
|
30723
|
+
let sent = 0;
|
|
30724
|
+
let next = 0;
|
|
30725
|
+
let failed = false;
|
|
30726
|
+
try {
|
|
30727
|
+
const worker = async () => {
|
|
30728
|
+
while (!failed) {
|
|
30729
|
+
const index = next++;
|
|
30730
|
+
const part = parts[index];
|
|
30731
|
+
if (!part) return;
|
|
30732
|
+
const start = index * upload.partBytes;
|
|
30733
|
+
const length = Math.min(upload.partBytes, sizeBytes - start);
|
|
30734
|
+
const bytes = await readPart2(file, start, length);
|
|
30735
|
+
const etag = await putPart(part.url, bytes, sleep7);
|
|
30736
|
+
if (!etag) {
|
|
30737
|
+
failed = true;
|
|
30738
|
+
log.error(`Upload failed (part ${part.partNumber}/${parts.length}).`);
|
|
30739
|
+
return;
|
|
30740
|
+
}
|
|
30741
|
+
done.push({ partNumber: part.partNumber, etag });
|
|
30742
|
+
sent += length;
|
|
30743
|
+
onProgress?.(sent);
|
|
30744
|
+
}
|
|
30745
|
+
};
|
|
30746
|
+
await Promise.all(Array.from({ length: Math.min(PART_CONCURRENCY2, parts.length) }, () => worker()));
|
|
30747
|
+
} finally {
|
|
30748
|
+
await file.close();
|
|
30749
|
+
}
|
|
30750
|
+
if (failed) return null;
|
|
30751
|
+
return done.sort((a, b) => a.partNumber - b.partNumber);
|
|
30752
|
+
}
|
|
30753
|
+
|
|
30754
|
+
// src/lib/source-zip.ts
|
|
30755
|
+
import fs50 from "fs/promises";
|
|
30756
|
+
import path50 from "path";
|
|
30757
|
+
import zlib4 from "zlib";
|
|
30758
|
+
var SOURCE_ZIP_ROOT_EXCLUDES = [".genex", "Intermediate", "Saved", "DerivedDataCache", "Binaries"];
|
|
30759
|
+
var SOURCE_ZIP_EXCLUDES_ANYWHERE = [".git", ".DS_Store", ".env"];
|
|
30760
|
+
var ENV_VARIANT = /^\.env\./;
|
|
30761
|
+
var ENV_TEMPLATE = ".env.example";
|
|
30762
|
+
var SourceZipError = class extends Error {
|
|
30763
|
+
};
|
|
30764
|
+
var DOS_TIME = 0;
|
|
30765
|
+
var DOS_DATE2 = 0 << 9 | 1 << 5 | 1;
|
|
30766
|
+
var UTF8_FLAG = 2048;
|
|
30767
|
+
var VERSION_STORED = 20;
|
|
30768
|
+
var VERSION_ZIP64 = 45;
|
|
30769
|
+
var U16_MAX2 = 65535;
|
|
30770
|
+
var U32_MAX2 = 4294967295;
|
|
30771
|
+
var CHUNK = 1024 * 1024;
|
|
30772
|
+
var isExcludedAnywhere = (name) => SOURCE_ZIP_EXCLUDES_ANYWHERE.includes(name) || ENV_VARIANT.test(name) && name !== ENV_TEMPLATE;
|
|
30773
|
+
function inside(root, target) {
|
|
30774
|
+
const rel = path50.relative(root, target);
|
|
30775
|
+
return rel === "" || rel !== ".." && !rel.startsWith(`..${path50.sep}`) && !path50.isAbsolute(rel);
|
|
30776
|
+
}
|
|
30777
|
+
async function plan(root) {
|
|
30778
|
+
const entries = [];
|
|
30779
|
+
const skippedLinks = [];
|
|
30780
|
+
const walk2 = async (dir, rel) => {
|
|
30781
|
+
const children = await fs50.readdir(dir, { withFileTypes: true });
|
|
30782
|
+
let kept = 0;
|
|
30783
|
+
for (const child of children) {
|
|
30784
|
+
const name = child.name;
|
|
30785
|
+
const childRel = rel ? `${rel}/${name}` : name;
|
|
30786
|
+
const abs = path50.join(dir, name);
|
|
30787
|
+
if (isExcludedAnywhere(name)) continue;
|
|
30788
|
+
if (!rel && SOURCE_ZIP_ROOT_EXCLUDES.includes(name)) continue;
|
|
30789
|
+
if (child.isSymbolicLink()) {
|
|
30790
|
+
const target = await fs50.realpath(abs).catch(() => null);
|
|
30791
|
+
if (target !== null && !inside(root, target)) {
|
|
30792
|
+
throw new SourceZipError(
|
|
30793
|
+
`${childRel} is a link to ${target}, outside the folder being built. Copy what it points to into the project, or remove the link.`
|
|
30794
|
+
);
|
|
30795
|
+
}
|
|
30796
|
+
skippedLinks.push(childRel);
|
|
30797
|
+
continue;
|
|
30798
|
+
}
|
|
30799
|
+
if (child.isDirectory()) {
|
|
30800
|
+
kept++;
|
|
30801
|
+
await walk2(abs, childRel);
|
|
30802
|
+
} else if (child.isFile()) {
|
|
30803
|
+
kept++;
|
|
30804
|
+
const { size } = await fs50.stat(abs);
|
|
30805
|
+
entries.push({ name: childRel, abs, size });
|
|
30806
|
+
}
|
|
30807
|
+
}
|
|
30808
|
+
if (rel && kept === 0) entries.push({ name: `${rel}/`, size: 0 });
|
|
30809
|
+
};
|
|
30810
|
+
await walk2(root, "");
|
|
30811
|
+
entries.sort((a, b) => Buffer.compare(Buffer.from(a.name, "utf8"), Buffer.from(b.name, "utf8")));
|
|
30812
|
+
skippedLinks.sort();
|
|
30813
|
+
return { entries, skippedLinks };
|
|
30814
|
+
}
|
|
30815
|
+
var crcTable = (() => {
|
|
30816
|
+
const table = new Uint32Array(256);
|
|
30817
|
+
for (let n = 0; n < 256; n++) {
|
|
30818
|
+
let c2 = n;
|
|
30819
|
+
for (let k = 0; k < 8; k++) c2 = c2 & 1 ? 3988292384 ^ c2 >>> 1 : c2 >>> 1;
|
|
30820
|
+
table[n] = c2 >>> 0;
|
|
30821
|
+
}
|
|
30822
|
+
return table;
|
|
30823
|
+
})();
|
|
30824
|
+
var zlibCrc = zlib4.crc32;
|
|
30825
|
+
function crc32(data, value) {
|
|
30826
|
+
if (zlibCrc) return zlibCrc(data, value);
|
|
30827
|
+
let c2 = (value ^ U32_MAX2) >>> 0;
|
|
30828
|
+
for (let i = 0; i < data.length; i++) c2 = crcTable[(c2 ^ data[i]) & 255] ^ c2 >>> 8;
|
|
30829
|
+
return (c2 ^ U32_MAX2) >>> 0;
|
|
30830
|
+
}
|
|
30831
|
+
var Out = class {
|
|
30832
|
+
position = 0;
|
|
30833
|
+
file;
|
|
30834
|
+
constructor(file) {
|
|
30835
|
+
this.file = file;
|
|
30836
|
+
}
|
|
30837
|
+
async write(buffer) {
|
|
30838
|
+
let done = 0;
|
|
30839
|
+
while (done < buffer.length) {
|
|
30840
|
+
const { bytesWritten } = await this.file.write(buffer, done, buffer.length - done, this.position + done);
|
|
30841
|
+
done += bytesWritten;
|
|
30842
|
+
}
|
|
30843
|
+
this.position += buffer.length;
|
|
30844
|
+
}
|
|
30845
|
+
async patch(at, buffer) {
|
|
30846
|
+
await this.file.write(buffer, 0, buffer.length, at);
|
|
30847
|
+
}
|
|
30848
|
+
};
|
|
30849
|
+
async function writeSourceZip(root, zipPath, opts = {}) {
|
|
30850
|
+
const { entries, skippedLinks } = await plan(await fs50.realpath(root));
|
|
30851
|
+
const file = await fs50.open(zipPath, "w");
|
|
30852
|
+
const out = new Out(file);
|
|
30853
|
+
const central = [];
|
|
30854
|
+
let contentBytes = 0;
|
|
30855
|
+
const chunk = Buffer.alloc(CHUNK);
|
|
30856
|
+
try {
|
|
30857
|
+
for (const entry of entries) {
|
|
30858
|
+
const name = Buffer.from(entry.name, "utf8");
|
|
30859
|
+
const offset = out.position;
|
|
30860
|
+
const bigSize = opts.forceZip64 || entry.size >= U32_MAX2;
|
|
30861
|
+
const bigOffset = opts.forceZip64 || offset >= U32_MAX2;
|
|
30862
|
+
const localExtra = bigSize ? zip64Extra([entry.size, entry.size]) : Buffer.alloc(0);
|
|
30863
|
+
const local = Buffer.alloc(30);
|
|
30864
|
+
local.writeUInt32LE(67324752, 0);
|
|
30865
|
+
local.writeUInt16LE(bigSize ? VERSION_ZIP64 : VERSION_STORED, 4);
|
|
30866
|
+
local.writeUInt16LE(UTF8_FLAG, 6);
|
|
30867
|
+
local.writeUInt16LE(0, 8);
|
|
30868
|
+
local.writeUInt16LE(DOS_TIME, 10);
|
|
30869
|
+
local.writeUInt16LE(DOS_DATE2, 12);
|
|
30870
|
+
local.writeUInt32LE(0, 14);
|
|
30871
|
+
local.writeUInt32LE(bigSize ? U32_MAX2 : entry.size, 18);
|
|
30872
|
+
local.writeUInt32LE(bigSize ? U32_MAX2 : entry.size, 22);
|
|
30873
|
+
local.writeUInt16LE(name.length, 26);
|
|
30874
|
+
local.writeUInt16LE(localExtra.length, 28);
|
|
30875
|
+
await out.write(Buffer.concat([local, name, localExtra]));
|
|
30876
|
+
let crc = 0;
|
|
30877
|
+
if (entry.abs) {
|
|
30878
|
+
const source = await fs50.open(entry.abs, "r");
|
|
30879
|
+
try {
|
|
30880
|
+
let read = 0;
|
|
30881
|
+
for (; ; ) {
|
|
30882
|
+
const { bytesRead } = await source.read(chunk, 0, CHUNK, read);
|
|
30883
|
+
if (bytesRead === 0) break;
|
|
30884
|
+
read += bytesRead;
|
|
30885
|
+
if (read > entry.size) break;
|
|
30886
|
+
const piece = chunk.subarray(0, bytesRead);
|
|
30887
|
+
crc = crc32(piece, crc);
|
|
30888
|
+
await out.write(piece);
|
|
30889
|
+
}
|
|
30890
|
+
if (read !== entry.size) throw new SourceZipError(`${entry.name} changed while it was being zipped. Close the editor and run this again.`);
|
|
30891
|
+
} finally {
|
|
30892
|
+
await source.close();
|
|
30893
|
+
}
|
|
30894
|
+
contentBytes += entry.size;
|
|
30895
|
+
const crcBytes = Buffer.alloc(4);
|
|
30896
|
+
crcBytes.writeUInt32LE(crc >>> 0, 0);
|
|
30897
|
+
await out.patch(offset + 14, crcBytes);
|
|
30898
|
+
}
|
|
30899
|
+
const centralFields = [];
|
|
30900
|
+
if (bigSize) centralFields.push(entry.size, entry.size);
|
|
30901
|
+
if (bigOffset) centralFields.push(offset);
|
|
30902
|
+
const centralExtra = centralFields.length ? zip64Extra(centralFields) : Buffer.alloc(0);
|
|
30903
|
+
const needs64 = bigSize || bigOffset;
|
|
30904
|
+
const head = Buffer.alloc(46);
|
|
30905
|
+
head.writeUInt32LE(33639248, 0);
|
|
30906
|
+
head.writeUInt16LE(needs64 ? VERSION_ZIP64 : VERSION_STORED, 4);
|
|
30907
|
+
head.writeUInt16LE(needs64 ? VERSION_ZIP64 : VERSION_STORED, 6);
|
|
30908
|
+
head.writeUInt16LE(UTF8_FLAG, 8);
|
|
30909
|
+
head.writeUInt16LE(0, 10);
|
|
30910
|
+
head.writeUInt16LE(DOS_TIME, 12);
|
|
30911
|
+
head.writeUInt16LE(DOS_DATE2, 14);
|
|
30912
|
+
head.writeUInt32LE(crc >>> 0, 16);
|
|
30913
|
+
head.writeUInt32LE(bigSize ? U32_MAX2 : entry.size, 20);
|
|
30914
|
+
head.writeUInt32LE(bigSize ? U32_MAX2 : entry.size, 24);
|
|
30915
|
+
head.writeUInt16LE(name.length, 28);
|
|
30916
|
+
head.writeUInt16LE(centralExtra.length, 30);
|
|
30917
|
+
head.writeUInt16LE(0, 32);
|
|
30918
|
+
head.writeUInt16LE(0, 34);
|
|
30919
|
+
head.writeUInt16LE(0, 36);
|
|
30920
|
+
head.writeUInt32LE(entry.abs ? 0 : 16, 38);
|
|
30921
|
+
head.writeUInt32LE(bigOffset ? U32_MAX2 : offset, 42);
|
|
30922
|
+
central.push(head, name, centralExtra);
|
|
30923
|
+
}
|
|
30924
|
+
const directoryOffset = out.position;
|
|
30925
|
+
const directory = Buffer.concat(central);
|
|
30926
|
+
await out.write(directory);
|
|
30927
|
+
const count = entries.length;
|
|
30928
|
+
const end64 = opts.forceZip64 || count >= U16_MAX2 || directory.length >= U32_MAX2 || directoryOffset >= U32_MAX2;
|
|
30929
|
+
if (end64) {
|
|
30930
|
+
const recordOffset = out.position;
|
|
30931
|
+
const record = Buffer.alloc(56);
|
|
30932
|
+
record.writeUInt32LE(101075792, 0);
|
|
30933
|
+
record.writeBigUInt64LE(44n, 4);
|
|
30934
|
+
record.writeUInt16LE(VERSION_ZIP64, 12);
|
|
30935
|
+
record.writeUInt16LE(VERSION_ZIP64, 14);
|
|
30936
|
+
record.writeUInt32LE(0, 16);
|
|
30937
|
+
record.writeUInt32LE(0, 20);
|
|
30938
|
+
record.writeBigUInt64LE(BigInt(count), 24);
|
|
30939
|
+
record.writeBigUInt64LE(BigInt(count), 32);
|
|
30940
|
+
record.writeBigUInt64LE(BigInt(directory.length), 40);
|
|
30941
|
+
record.writeBigUInt64LE(BigInt(directoryOffset), 48);
|
|
30942
|
+
const locator = Buffer.alloc(20);
|
|
30943
|
+
locator.writeUInt32LE(117853008, 0);
|
|
30944
|
+
locator.writeUInt32LE(0, 4);
|
|
30945
|
+
locator.writeBigUInt64LE(BigInt(recordOffset), 8);
|
|
30946
|
+
locator.writeUInt32LE(1, 16);
|
|
30947
|
+
await out.write(Buffer.concat([record, locator]));
|
|
30948
|
+
}
|
|
30949
|
+
const end = Buffer.alloc(22);
|
|
30950
|
+
end.writeUInt32LE(101010256, 0);
|
|
30951
|
+
end.writeUInt16LE(end64 ? U16_MAX2 : count, 8);
|
|
30952
|
+
end.writeUInt16LE(end64 ? U16_MAX2 : count, 10);
|
|
30953
|
+
end.writeUInt32LE(end64 ? U32_MAX2 : directory.length, 12);
|
|
30954
|
+
end.writeUInt32LE(end64 ? U32_MAX2 : directoryOffset, 16);
|
|
30955
|
+
await out.write(end);
|
|
30956
|
+
} finally {
|
|
30957
|
+
await file.close();
|
|
30958
|
+
}
|
|
30959
|
+
return { entries: entries.length, contentBytes, skippedLinks };
|
|
30960
|
+
}
|
|
30961
|
+
function zip64Extra(values) {
|
|
30962
|
+
const extra = Buffer.alloc(4 + values.length * 8);
|
|
30963
|
+
extra.writeUInt16LE(1, 0);
|
|
30964
|
+
extra.writeUInt16LE(values.length * 8, 2);
|
|
30965
|
+
values.forEach((v, i) => extra.writeBigUInt64LE(BigInt(v), 4 + i * 8));
|
|
30966
|
+
return extra;
|
|
30967
|
+
}
|
|
30968
|
+
async function resolveZipSource(dir, cwd = process.cwd()) {
|
|
30969
|
+
const abs = path50.resolve(cwd, dir);
|
|
30970
|
+
const stat = await fs50.lstat(abs).catch(() => null);
|
|
30971
|
+
if (!stat) return { refused: `--source ${abs} does not exist.` };
|
|
30972
|
+
if (stat.isSymbolicLink()) return { refused: `--source ${abs} is a symbolic link. Pass the folder itself.` };
|
|
30973
|
+
if (!stat.isDirectory()) return { refused: `--source ${abs} is not a folder.` };
|
|
30974
|
+
return { dir: await fs50.realpath(abs) };
|
|
30975
|
+
}
|
|
30976
|
+
|
|
30598
30977
|
// src/commands/native.ts
|
|
30599
30978
|
var PROGRESS_STEPS = 10;
|
|
30600
30979
|
var MB2 = 1024 * 1024;
|
|
@@ -30638,9 +31017,9 @@ async function context3(opts, log) {
|
|
|
30638
31017
|
}
|
|
30639
31018
|
return { apiUrl, projectId: meta.id, token };
|
|
30640
31019
|
}
|
|
30641
|
-
function refusedInHostedSession(log) {
|
|
31020
|
+
function refusedInHostedSession(log, line = "Native builds are published from the creator's own computer, not from a hosted session.") {
|
|
30642
31021
|
if (!inHostedSession()) return false;
|
|
30643
|
-
log.error(
|
|
31022
|
+
log.error(line);
|
|
30644
31023
|
return true;
|
|
30645
31024
|
}
|
|
30646
31025
|
function sizeLine(bytes) {
|
|
@@ -30779,16 +31158,184 @@ async function runRemove(opts, log) {
|
|
|
30779
31158
|
log.success(`Removed the ${opts.platform} ${opts.arch} build. Copies already installed keep working.`);
|
|
30780
31159
|
return true;
|
|
30781
31160
|
}
|
|
31161
|
+
var CLOUD_HOSTED_REFUSAL = "Build on Genex is started from the creator's own computer, not from a hosted session.";
|
|
31162
|
+
var FAILURE_WORDS = {
|
|
31163
|
+
"engine-unsupported": "the project's Unreal version isn't one the build machine has",
|
|
31164
|
+
compile: "the game's code didn't compile",
|
|
31165
|
+
cook: "cooking the game's content failed",
|
|
31166
|
+
timeout: "the build ran past its time limit",
|
|
31167
|
+
quota: "this account has used its builds for the month",
|
|
31168
|
+
"source-too-big": "the project is over the size limit",
|
|
31169
|
+
"not-on-genex": "the game has no page on Genex yet",
|
|
31170
|
+
failed: "the build machine stopped or its result failed the checks"
|
|
31171
|
+
};
|
|
31172
|
+
var STATE_WORDS = {
|
|
31173
|
+
queued: "Queued",
|
|
31174
|
+
building: "Building",
|
|
31175
|
+
publishing: "Publishing",
|
|
31176
|
+
done: "Built",
|
|
31177
|
+
failed: "Failed"
|
|
31178
|
+
};
|
|
31179
|
+
function printRefusal(opts, refusal2) {
|
|
31180
|
+
if (opts.json && refusal2.body) console.log(JSON.stringify(refusal2.body));
|
|
31181
|
+
}
|
|
31182
|
+
function stateLine(build) {
|
|
31183
|
+
const state = STATE_WORDS[build.state] ?? build.state;
|
|
31184
|
+
if (build.state !== "failed") return `${state} \u2014 ${build.platform} build ${build.jobId}`;
|
|
31185
|
+
const why = build.failure ? `: ${FAILURE_WORDS[build.failure] ?? build.failure} (${build.failure})` : "";
|
|
31186
|
+
return `${state}${why} \u2014 ${build.platform} build ${build.jobId}`;
|
|
31187
|
+
}
|
|
31188
|
+
function reportCloudBuild(build, how, log) {
|
|
31189
|
+
if (how === "deduplicated") {
|
|
31190
|
+
log.success(`Already built (no change since): ${stateLine(build)}`);
|
|
31191
|
+
return;
|
|
31192
|
+
}
|
|
31193
|
+
const line = how === "running" ? `Already building: ${stateLine(build)}` : stateLine(build);
|
|
31194
|
+
if (build.state === "failed") log.warn(line);
|
|
31195
|
+
else log.success(line);
|
|
31196
|
+
if (build.state !== "done" && build.state !== "failed") {
|
|
31197
|
+
log.dim(` Follow it: genex native build-status ${build.jobId}`);
|
|
31198
|
+
log.dim(" When it is done the build is attached to the game's page (held for review unless you are a Genex admin).");
|
|
31199
|
+
}
|
|
31200
|
+
}
|
|
31201
|
+
async function runBuild(opts, log) {
|
|
31202
|
+
if (refusedInHostedSession(log, CLOUD_HOSTED_REFUSAL)) return false;
|
|
31203
|
+
if (!opts.platform || !isCloudBuildPlatform(opts.platform)) {
|
|
31204
|
+
log.error("Which platform? `genex native build --platform windows --source <folder>` (windows, linux or mac).");
|
|
31205
|
+
return false;
|
|
31206
|
+
}
|
|
31207
|
+
if (!opts.sourceDir) {
|
|
31208
|
+
log.error("Which project? `genex native build --platform windows --source <folder>` \u2014 the folder holding the .uproject.");
|
|
31209
|
+
return false;
|
|
31210
|
+
}
|
|
31211
|
+
if (opts.buildVersion !== void 0) {
|
|
31212
|
+
try {
|
|
31213
|
+
buildVersion(opts.buildVersion, void 0, "");
|
|
31214
|
+
} catch (err) {
|
|
31215
|
+
if (!(err instanceof NativeBuildError)) throw err;
|
|
31216
|
+
log.error(err.message);
|
|
31217
|
+
return false;
|
|
31218
|
+
}
|
|
31219
|
+
}
|
|
31220
|
+
const source = await resolveZipSource(opts.sourceDir);
|
|
31221
|
+
if ("refused" in source) {
|
|
31222
|
+
log.error(source.refused);
|
|
31223
|
+
return false;
|
|
31224
|
+
}
|
|
31225
|
+
const ctx = await context3(opts, log);
|
|
31226
|
+
if (!ctx) return false;
|
|
31227
|
+
const tmp = await fs51.mkdtemp(path51.join(os15.tmpdir(), "genex-cloud-build-"));
|
|
31228
|
+
const zipPath = path51.join(tmp, "source.zip");
|
|
31229
|
+
try {
|
|
31230
|
+
log.step(`Zipping ${source.dir}\u2026`);
|
|
31231
|
+
let zipped;
|
|
31232
|
+
try {
|
|
31233
|
+
zipped = await writeSourceZip(source.dir, zipPath);
|
|
31234
|
+
} catch (err) {
|
|
31235
|
+
if (!(err instanceof SourceZipError)) throw err;
|
|
31236
|
+
log.error(err.message);
|
|
31237
|
+
return false;
|
|
31238
|
+
}
|
|
31239
|
+
if (zipped.entries === 0) {
|
|
31240
|
+
log.error(`${source.dir} has nothing to build in it.`);
|
|
31241
|
+
return false;
|
|
31242
|
+
}
|
|
31243
|
+
if (zipped.skippedLinks.length > 0) {
|
|
31244
|
+
const shown = zipped.skippedLinks.slice(0, 5).join(", ");
|
|
31245
|
+
const more = zipped.skippedLinks.length > 5 ? ` and ${zipped.skippedLinks.length - 5} more` : "";
|
|
31246
|
+
log.warn(`Left out ${zipped.skippedLinks.length} link(s) inside the folder: ${shown}${more}.`);
|
|
31247
|
+
}
|
|
31248
|
+
const { sha256: sha2564, sizeBytes } = await hashFile(zipPath);
|
|
31249
|
+
log.step(`${zipped.entries} files, ${sizeLine(sizeBytes)} zipped.`);
|
|
31250
|
+
const body = {
|
|
31251
|
+
platform: opts.platform,
|
|
31252
|
+
sourceSha256: sha2564,
|
|
31253
|
+
sourceBytes: sizeBytes,
|
|
31254
|
+
...opts.buildVersion !== void 0 ? { version: opts.buildVersion } : {}
|
|
31255
|
+
};
|
|
31256
|
+
const started = await startCloudBuild(ctx, body, log);
|
|
31257
|
+
if (!started.ok) {
|
|
31258
|
+
printRefusal(opts, started.refusal);
|
|
31259
|
+
return false;
|
|
31260
|
+
}
|
|
31261
|
+
let build = started.value.build;
|
|
31262
|
+
let upload = started.value.upload;
|
|
31263
|
+
const how = started.value.deduplicated ? "deduplicated" : started.value.status === 201 ? "new" : "running";
|
|
31264
|
+
let uploaded = false;
|
|
31265
|
+
for (let round2 = 0; upload; round2++) {
|
|
31266
|
+
log.step(`Uploading ${sizeLine(sizeBytes)}\u2026`);
|
|
31267
|
+
const parts = await uploadCloudBuildSource(upload, zipPath, sizeBytes, log, progress(log, "uploaded", sizeBytes));
|
|
31268
|
+
if (!parts) return false;
|
|
31269
|
+
const done = await completeCloudBuildSource(ctx, build.jobId, parts, log);
|
|
31270
|
+
if (done.ok) {
|
|
31271
|
+
build = done.value;
|
|
31272
|
+
uploaded = true;
|
|
31273
|
+
break;
|
|
31274
|
+
}
|
|
31275
|
+
if (done.refusal.error !== UPLOAD_INCOMPLETE) {
|
|
31276
|
+
printRefusal(opts, done.refusal);
|
|
31277
|
+
return false;
|
|
31278
|
+
}
|
|
31279
|
+
if (round2 > 0) {
|
|
31280
|
+
log.error("The upload still isn't complete after sending it twice. Run this again.");
|
|
31281
|
+
printRefusal(opts, done.refusal);
|
|
31282
|
+
return false;
|
|
31283
|
+
}
|
|
31284
|
+
log.warn("Some parts did not arrive. Sending the project again\u2026");
|
|
31285
|
+
const again = await startCloudBuild(ctx, body, log);
|
|
31286
|
+
if (!again.ok) {
|
|
31287
|
+
printRefusal(opts, again.refusal);
|
|
31288
|
+
return false;
|
|
31289
|
+
}
|
|
31290
|
+
build = again.value.build;
|
|
31291
|
+
upload = again.value.upload;
|
|
31292
|
+
}
|
|
31293
|
+
reportCloudBuild(build, how === "running" && uploaded ? "new" : how, log);
|
|
31294
|
+
if (opts.json) console.log(JSON.stringify({ build }));
|
|
31295
|
+
return true;
|
|
31296
|
+
} finally {
|
|
31297
|
+
await fs51.rm(tmp, { recursive: true, force: true });
|
|
31298
|
+
}
|
|
31299
|
+
}
|
|
31300
|
+
async function runBuildStatus(opts, log) {
|
|
31301
|
+
if (refusedInHostedSession(log, CLOUD_HOSTED_REFUSAL)) return false;
|
|
31302
|
+
if (!opts.jobId || !CLOUD_BUILD_JOB_ID.test(opts.jobId)) {
|
|
31303
|
+
log.error(
|
|
31304
|
+
opts.jobId ? `"${opts.jobId}" is not a build id. \`genex native build\` prints one (cb-\u2026).` : "Which build? `genex native build-status <jobId>` \u2014 the id `genex native build` printed."
|
|
31305
|
+
);
|
|
31306
|
+
return false;
|
|
31307
|
+
}
|
|
31308
|
+
const ctx = await context3(opts, log);
|
|
31309
|
+
if (!ctx) return false;
|
|
31310
|
+
const read = await readCloudBuild(ctx, opts.jobId, log);
|
|
31311
|
+
if (!read.ok) {
|
|
31312
|
+
printRefusal(opts, read.refusal);
|
|
31313
|
+
return false;
|
|
31314
|
+
}
|
|
31315
|
+
const build = read.value;
|
|
31316
|
+
if (opts.json) {
|
|
31317
|
+
console.log(JSON.stringify({ build }));
|
|
31318
|
+
return true;
|
|
31319
|
+
}
|
|
31320
|
+
if (build.state === "failed") log.warn(stateLine(build));
|
|
31321
|
+
else log.success(stateLine(build));
|
|
31322
|
+
log.dim(` Started ${build.startedAt}${build.finishedAt ? `, finished ${build.finishedAt}` : ""}.`);
|
|
31323
|
+
return true;
|
|
31324
|
+
}
|
|
30782
31325
|
var SUBCOMMANDS2 = {
|
|
30783
31326
|
publish: runPublish2,
|
|
30784
31327
|
list: runList2,
|
|
30785
|
-
remove: runRemove
|
|
31328
|
+
remove: runRemove,
|
|
31329
|
+
build: runBuild,
|
|
31330
|
+
"build-status": runBuildStatus
|
|
30786
31331
|
};
|
|
30787
31332
|
async function runNative(opts) {
|
|
30788
31333
|
const log = loggerFor2(opts);
|
|
30789
31334
|
const run5 = opts.name ? SUBCOMMANDS2[opts.name] : void 0;
|
|
30790
31335
|
if (!run5) {
|
|
30791
|
-
log.error(
|
|
31336
|
+
log.error(
|
|
31337
|
+
"Unknown native command. Use `genex native publish <path>`, `genex native list`, `genex native remove`, `genex native build` or `genex native build-status <jobId>`."
|
|
31338
|
+
);
|
|
30792
31339
|
process.exitCode = 1;
|
|
30793
31340
|
return;
|
|
30794
31341
|
}
|
|
@@ -30987,10 +31534,13 @@ ${c.bold("Usage")}
|
|
|
30987
31534
|
version back, instantly \u2014 your code is not
|
|
30988
31535
|
changed, only what the play URL serves.
|
|
30989
31536
|
genex native <sub> [options] This game's Mac/Windows/Linux builds for the Genex
|
|
30990
|
-
desktop app: publish <path> | list | remove
|
|
31537
|
+
desktop app: publish <path> | list | remove |
|
|
31538
|
+
build | build-status <jobId>.
|
|
30991
31539
|
"publish" takes a .zip, a .app or the build's
|
|
30992
31540
|
folder and makes it what desktop players install
|
|
30993
31541
|
for its platform and processor, live at once.
|
|
31542
|
+
"build --platform windows --source <folder>" has
|
|
31543
|
+
Genex build the Windows app of an Unreal project.
|
|
30994
31544
|
genex web remove [--yes] [--json] Remove this game's web version and keep its
|
|
30995
31545
|
Mac/Windows/Linux apps: the page shows the apps
|
|
30996
31546
|
again. Refused for a game with no app.
|
|
@@ -31866,6 +32416,21 @@ ${c.bold("Usage")}
|
|
|
31866
32416
|
genex native remove --platform <p> --arch <a> --yes
|
|
31867
32417
|
Stop offering one build. Installed copies keep working.
|
|
31868
32418
|
|
|
32419
|
+
${c.bold("Build on Genex")} (an Unreal project, built for a system this computer can't build for)
|
|
32420
|
+
genex native build --platform windows --source <folder> [--build-version <v>]
|
|
32421
|
+
Zip the project folder (the same files always give
|
|
32422
|
+
the same zip, so an unchanged project is not built
|
|
32423
|
+
twice), upload it privately, and build it on a Genex
|
|
32424
|
+
machine. The result is attached to the game's page
|
|
32425
|
+
like a published build. Left out: .git, .genex,
|
|
32426
|
+
.DS_Store, .env files, and the top-level Intermediate,
|
|
32427
|
+
Saved, DerivedDataCache and Binaries folders. A link
|
|
32428
|
+
out of the folder is refused; one inside it is skipped.
|
|
32429
|
+
genex native build-status <jobId> Where that build stands: queued, building,
|
|
32430
|
+
publishing, done or failed (with why).
|
|
32431
|
+
--json One JSON object on stdout ({"build": \u2026}); a refusal
|
|
32432
|
+
prints the server's error body and exits 1.
|
|
32433
|
+
|
|
31869
32434
|
${c.bold("Read from the build, or set")}
|
|
31870
32435
|
--platform mac|windows|linux A .app at the archive's root is mac, an .exe windows.
|
|
31871
32436
|
--arch arm64|x64|universal From the app's executable header.
|
|
@@ -32357,6 +32922,8 @@ function parseArgs(argv) {
|
|
|
32357
32922
|
} else if (parsed.command === "native") {
|
|
32358
32923
|
if (parsed.options.name === "publish" && parsed.options.nativePath === void 0) {
|
|
32359
32924
|
parsed.options.nativePath = arg;
|
|
32925
|
+
} else if (parsed.options.name === "build-status" && parsed.options.jobId === void 0) {
|
|
32926
|
+
parsed.options.jobId = arg;
|
|
32360
32927
|
} else {
|
|
32361
32928
|
parsed.error = `Unexpected argument: ${arg}`;
|
|
32362
32929
|
return parsed;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genex-ai/cli-demo",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.38.0-dev.1079",
|
|
4
4
|
"description": "Set up your project's agent workspace (.claude/.codex/.cursor in the game folder), authorize, create a game project, generate AI assets, and publish (genex CLI).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|