@genex-ai/cli-demo 1.29.1-dev.634 → 1.30.0-dev.636
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 +415 -311
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -659,8 +659,8 @@ async function runAuth(opts) {
|
|
|
659
659
|
}
|
|
660
660
|
|
|
661
661
|
// src/commands/remix.ts
|
|
662
|
-
import
|
|
663
|
-
import
|
|
662
|
+
import fs18 from "fs/promises";
|
|
663
|
+
import path17 from "path";
|
|
664
664
|
import crypto4 from "crypto";
|
|
665
665
|
import { spawn as spawn4 } from "child_process";
|
|
666
666
|
|
|
@@ -2798,9 +2798,63 @@ function auditGenerationPlan(input) {
|
|
|
2798
2798
|
|
|
2799
2799
|
// src/lib/deploy.ts
|
|
2800
2800
|
import crypto2 from "crypto";
|
|
2801
|
-
import
|
|
2801
|
+
import fs17 from "fs/promises";
|
|
2802
2802
|
import os5 from "os";
|
|
2803
|
-
import
|
|
2803
|
+
import path16 from "path";
|
|
2804
|
+
|
|
2805
|
+
// src/lib/source-size.ts
|
|
2806
|
+
import fs14 from "fs/promises";
|
|
2807
|
+
import path14 from "path";
|
|
2808
|
+
var FALLBACK_SOURCE_LIMITS = {
|
|
2809
|
+
maxTotalBytes: 2 * 1024 * 1024 * 1024,
|
|
2810
|
+
maxFiles: 5e3
|
|
2811
|
+
};
|
|
2812
|
+
function isOversized(p, limits) {
|
|
2813
|
+
return p.bytes >= limits.maxTotalBytes || p.files >= limits.maxFiles;
|
|
2814
|
+
}
|
|
2815
|
+
function formatBytes2(n) {
|
|
2816
|
+
if (n >= 1024 ** 3) return `${(n / 1024 ** 3).toFixed(1)} GB`;
|
|
2817
|
+
if (n >= 1024 ** 2) return `${Math.round(n / 1024 ** 2)} MB`;
|
|
2818
|
+
if (n >= 1024) return `${Math.round(n / 1024)} KB`;
|
|
2819
|
+
return `${n} B`;
|
|
2820
|
+
}
|
|
2821
|
+
function groupByDir(entries, limit = 5) {
|
|
2822
|
+
const acc = /* @__PURE__ */ new Map();
|
|
2823
|
+
for (const [file, bytes] of entries) {
|
|
2824
|
+
const dirParts = file.split("/").slice(0, -1).slice(0, 2);
|
|
2825
|
+
const dir = dirParts.length > 0 ? dirParts.join("/") : "(root)";
|
|
2826
|
+
const row = acc.get(dir) ?? { dir, files: 0, bytes: 0 };
|
|
2827
|
+
row.files += 1;
|
|
2828
|
+
row.bytes += bytes;
|
|
2829
|
+
acc.set(dir, row);
|
|
2830
|
+
}
|
|
2831
|
+
return [...acc.values()].sort((a, b) => b.bytes - a.bytes).slice(0, limit);
|
|
2832
|
+
}
|
|
2833
|
+
function oversizedLines(p, limits) {
|
|
2834
|
+
return [
|
|
2835
|
+
`This is ${formatBytes2(p.bytes)} across ${p.files} files, past the ${formatBytes2(limits.maxTotalBytes)} / ${limits.maxFiles} file limit on this account.`,
|
|
2836
|
+
"Heaviest directories:",
|
|
2837
|
+
...p.top.map((d) => ` ${formatBytes2(d.bytes).padStart(8)} ${String(d.files).padStart(6)} files ${d.dir}`),
|
|
2838
|
+
"Screenshots, traces and reports made while building are not part of the game \u2014",
|
|
2839
|
+
"put them in .genex/scratch/ (already ignored), or add the folders above to .gitignore.",
|
|
2840
|
+
"If this really is the game, ask an admin to raise the source limit on your account."
|
|
2841
|
+
];
|
|
2842
|
+
}
|
|
2843
|
+
async function measurePayload(cwd, staged) {
|
|
2844
|
+
const entries = [];
|
|
2845
|
+
let bytes = 0;
|
|
2846
|
+
for (const file of staged) {
|
|
2847
|
+
let size = 0;
|
|
2848
|
+
try {
|
|
2849
|
+
size = (await fs14.stat(path14.join(cwd, file))).size;
|
|
2850
|
+
} catch {
|
|
2851
|
+
size = 0;
|
|
2852
|
+
}
|
|
2853
|
+
bytes += size;
|
|
2854
|
+
entries.push([file, size]);
|
|
2855
|
+
}
|
|
2856
|
+
return { files: staged.length, bytes, top: groupByDir(entries) };
|
|
2857
|
+
}
|
|
2804
2858
|
|
|
2805
2859
|
// ../../packages/mobile-scan/src/image-dims.ts
|
|
2806
2860
|
function u32be(b, o) {
|
|
@@ -3064,12 +3118,12 @@ function tierFor(estVramMb) {
|
|
|
3064
3118
|
}
|
|
3065
3119
|
|
|
3066
3120
|
// src/commands/ui.ts
|
|
3067
|
-
import
|
|
3068
|
-
import
|
|
3121
|
+
import fs16 from "fs/promises";
|
|
3122
|
+
import path15 from "path";
|
|
3069
3123
|
import { PNG as PNG2 } from "pngjs";
|
|
3070
3124
|
|
|
3071
3125
|
// src/lib/png-tools.ts
|
|
3072
|
-
import
|
|
3126
|
+
import fs15 from "fs/promises";
|
|
3073
3127
|
import { PNG } from "pngjs";
|
|
3074
3128
|
var ALPHA_TRANSPARENT_MAX = 16;
|
|
3075
3129
|
var isHttpUrl = (s) => /^https?:\/\//i.test(s);
|
|
@@ -3080,12 +3134,12 @@ async function loadPng(input) {
|
|
|
3080
3134
|
if (!res.ok) throw new Error(`Couldn't fetch ${input} (HTTP ${res.status}).`);
|
|
3081
3135
|
buf = Buffer.from(await res.arrayBuffer());
|
|
3082
3136
|
} else {
|
|
3083
|
-
buf = await
|
|
3137
|
+
buf = await fs15.readFile(input);
|
|
3084
3138
|
}
|
|
3085
3139
|
return PNG.sync.read(buf);
|
|
3086
3140
|
}
|
|
3087
3141
|
async function writePng(file, png) {
|
|
3088
|
-
await
|
|
3142
|
+
await fs15.writeFile(file, PNG.sync.write(png));
|
|
3089
3143
|
}
|
|
3090
3144
|
function cropPng(image, box) {
|
|
3091
3145
|
const out = new PNG({ width: box.w, height: box.h });
|
|
@@ -3385,7 +3439,7 @@ async function uiExtract(opts, log) {
|
|
|
3385
3439
|
const dilatePx = opts.dilate ?? 0;
|
|
3386
3440
|
const sheet = await loadPng(input);
|
|
3387
3441
|
const { width: W, height: H, data } = sheet;
|
|
3388
|
-
await
|
|
3442
|
+
await fs16.mkdir(outDir, { recursive: true });
|
|
3389
3443
|
log.plain(c.bold("genex ui extract"));
|
|
3390
3444
|
log.dim(` ${input} (${W}x${H}) \u2192 ${outDir}, ${names.length} names`);
|
|
3391
3445
|
let hasTransparency = false;
|
|
@@ -3614,7 +3668,7 @@ async function uiExtract(opts, log) {
|
|
|
3614
3668
|
rimPixels: speckle.sampled
|
|
3615
3669
|
});
|
|
3616
3670
|
}
|
|
3617
|
-
const outPath =
|
|
3671
|
+
const outPath = path15.join(outDir, `${name}.png`);
|
|
3618
3672
|
await writePng(outPath, out);
|
|
3619
3673
|
const sidecar = {
|
|
3620
3674
|
name,
|
|
@@ -3633,7 +3687,7 @@ async function uiExtract(opts, log) {
|
|
|
3633
3687
|
defringed
|
|
3634
3688
|
};
|
|
3635
3689
|
const { name: _n, out: _o, ...sidecarBody } = sidecar;
|
|
3636
|
-
await
|
|
3690
|
+
await fs16.writeFile(
|
|
3637
3691
|
outPath.replace(/\.png$/i, "") + ".bbox.json",
|
|
3638
3692
|
JSON.stringify(sidecarBody, null, 2)
|
|
3639
3693
|
);
|
|
@@ -3642,8 +3696,8 @@ async function uiExtract(opts, log) {
|
|
|
3642
3696
|
`${name}.png ${cropW}x${cropH} (ar ${sidecar.aspectRatio}) at sheet ${padX0},${padY0}`
|
|
3643
3697
|
);
|
|
3644
3698
|
}
|
|
3645
|
-
const debugPath =
|
|
3646
|
-
await
|
|
3699
|
+
const debugPath = path15.join(outDir, "extract-debug.json");
|
|
3700
|
+
await fs16.writeFile(
|
|
3647
3701
|
debugPath,
|
|
3648
3702
|
JSON.stringify(
|
|
3649
3703
|
{
|
|
@@ -4105,7 +4159,7 @@ async function uiMasks(opts, log) {
|
|
|
4105
4159
|
const registrationTolerance = opts.registrationTolerance ?? 0.04;
|
|
4106
4160
|
const edgeFlushMax = opts.edgeFlushMax ?? 0.04;
|
|
4107
4161
|
const loosened = registrationTolerance > 0.04 || edgeFlushMax > 0.04 || minCoverage < 0.01 || maxCoverage > 0.85;
|
|
4108
|
-
await
|
|
4162
|
+
await fs16.mkdir(outDir, { recursive: true });
|
|
4109
4163
|
log.plain(c.bold("genex ui masks"));
|
|
4110
4164
|
log.dim(` ${input} (${image.width}x${image.height}), ${pairs.length} pair(s) \u2192 ${outDir}`);
|
|
4111
4165
|
const sheetComponents = detectSheetComponents(image, opts.minPixels ?? 2e3);
|
|
@@ -4158,11 +4212,11 @@ async function uiMasks(opts, log) {
|
|
|
4158
4212
|
});
|
|
4159
4213
|
}
|
|
4160
4214
|
const overlay = makeOverlay(clean2, converted.png);
|
|
4161
|
-
const framePath =
|
|
4162
|
-
const maskPath =
|
|
4163
|
-
const annotatedPath =
|
|
4164
|
-
const overlayPath =
|
|
4165
|
-
const metaPath =
|
|
4215
|
+
const framePath = path15.join(outDir, `${pair.name}-frame.png`);
|
|
4216
|
+
const maskPath = path15.join(outDir, `${pair.name}-mask.png`);
|
|
4217
|
+
const annotatedPath = path15.join(outDir, `${pair.name}-annotated-source.png`);
|
|
4218
|
+
const overlayPath = path15.join(outDir, `${pair.name}-overlay.png`);
|
|
4219
|
+
const metaPath = path15.join(outDir, `${pair.name}.annotated-progress.json`);
|
|
4166
4220
|
await writePng(framePath, clean2);
|
|
4167
4221
|
await writePng(maskPath, converted.png);
|
|
4168
4222
|
await writePng(annotatedPath, annotated);
|
|
@@ -4209,7 +4263,7 @@ async function uiMasks(opts, log) {
|
|
|
4209
4263
|
},
|
|
4210
4264
|
overlay: overlayPath
|
|
4211
4265
|
};
|
|
4212
|
-
await
|
|
4266
|
+
await fs16.writeFile(metaPath, `${JSON.stringify(meta, null, 2)}
|
|
4213
4267
|
`);
|
|
4214
4268
|
results.push(meta);
|
|
4215
4269
|
const fb = converted.bbox;
|
|
@@ -4225,8 +4279,8 @@ async function uiMasks(opts, log) {
|
|
|
4225
4279
|
);
|
|
4226
4280
|
}
|
|
4227
4281
|
}
|
|
4228
|
-
const indexPath =
|
|
4229
|
-
await
|
|
4282
|
+
const indexPath = path15.join(outDir, "annotated-progress.json");
|
|
4283
|
+
await fs16.writeFile(indexPath, `${JSON.stringify({ input, loosened, pairs: results }, null, 2)}
|
|
4230
4284
|
`);
|
|
4231
4285
|
log.plain("");
|
|
4232
4286
|
log.success(`Wrote ${pairs.length} frame+mask pair(s) \u2192 ${outDir} (index: ${indexPath}).`);
|
|
@@ -4352,7 +4406,7 @@ async function uiTextColor(opts, log) {
|
|
|
4352
4406
|
};
|
|
4353
4407
|
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
4354
4408
|
`);
|
|
4355
|
-
if (opts.out) await
|
|
4409
|
+
if (opts.out) await fs16.writeFile(opts.out, `${JSON.stringify(result, null, 2)}
|
|
4356
4410
|
`);
|
|
4357
4411
|
if (opts.cropPath) log.dim(` crop \u2192 ${opts.cropPath}`);
|
|
4358
4412
|
}
|
|
@@ -4384,7 +4438,7 @@ async function uiTrim(opts, log) {
|
|
|
4384
4438
|
const sidecar = computeBBoxes(trimmed);
|
|
4385
4439
|
const speckleAllowed = !!(speckle && speckle.ratio > SPECKLE_MAX_RATIO);
|
|
4386
4440
|
const sidecarPath = outPath.replace(/\.png$/i, "") + ".bbox.json";
|
|
4387
|
-
await
|
|
4441
|
+
await fs16.writeFile(
|
|
4388
4442
|
sidecarPath,
|
|
4389
4443
|
JSON.stringify(speckleAllowed ? { ...sidecar, speckleAllowed: true } : sidecar, null, 2)
|
|
4390
4444
|
);
|
|
@@ -4491,7 +4545,7 @@ async function uiPlate(opts, log) {
|
|
|
4491
4545
|
fail("No interior found \u2014 the image is fully transparent (or erode ate everything). Check --in / lower --erode.");
|
|
4492
4546
|
}
|
|
4493
4547
|
await writePng(outPath, out);
|
|
4494
|
-
const name =
|
|
4548
|
+
const name = path15.basename(outPath);
|
|
4495
4549
|
log.plain(c.bold("genex ui plate"));
|
|
4496
4550
|
log.success(`${outPath} ${W}x${H}, interior ${(count / (W * H) * 100).toFixed(1)}% (erode ${erode}px)`);
|
|
4497
4551
|
log.dim(" Wire it as the plate's silhouette (same box as the frame <img>, plate UNDER the art):");
|
|
@@ -4656,13 +4710,13 @@ async function walkFiles(dir) {
|
|
|
4656
4710
|
const out = [];
|
|
4657
4711
|
let entries;
|
|
4658
4712
|
try {
|
|
4659
|
-
entries = await
|
|
4713
|
+
entries = await fs16.readdir(dir, { withFileTypes: true });
|
|
4660
4714
|
} catch {
|
|
4661
4715
|
return out;
|
|
4662
4716
|
}
|
|
4663
4717
|
for (const entry of entries) {
|
|
4664
4718
|
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
4665
|
-
const p =
|
|
4719
|
+
const p = path15.join(dir, entry.name);
|
|
4666
4720
|
if (entry.isDirectory()) out.push(...await walkFiles(p));
|
|
4667
4721
|
else out.push(p);
|
|
4668
4722
|
}
|
|
@@ -4671,7 +4725,7 @@ async function walkFiles(dir) {
|
|
|
4671
4725
|
async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
4672
4726
|
const viewportFindings = [];
|
|
4673
4727
|
try {
|
|
4674
|
-
const indexHtml = await
|
|
4728
|
+
const indexHtml = await fs16.readFile(path15.join(cwd, "index.html"), "utf8");
|
|
4675
4729
|
if (!/<meta[^>]+name=["']viewport["']/i.test(indexHtml)) {
|
|
4676
4730
|
viewportFindings.push({
|
|
4677
4731
|
kind: "viewport-meta",
|
|
@@ -4685,28 +4739,28 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4685
4739
|
}
|
|
4686
4740
|
} catch {
|
|
4687
4741
|
}
|
|
4688
|
-
const absAssets =
|
|
4742
|
+
const absAssets = path15.resolve(cwd, assetDir);
|
|
4689
4743
|
try {
|
|
4690
|
-
if (!(await
|
|
4744
|
+
if (!(await fs16.stat(absAssets)).isDirectory()) {
|
|
4691
4745
|
return viewportFindings.length > 0 ? viewportFindings : null;
|
|
4692
4746
|
}
|
|
4693
4747
|
} catch {
|
|
4694
4748
|
return viewportFindings.length > 0 ? viewportFindings : null;
|
|
4695
4749
|
}
|
|
4696
|
-
const srcFiles = (await walkFiles(
|
|
4697
|
-
(p) => AUDIT_SRC_EXTS.has(
|
|
4750
|
+
const srcFiles = (await walkFiles(path15.resolve(cwd, srcDir))).filter(
|
|
4751
|
+
(p) => AUDIT_SRC_EXTS.has(path15.extname(p).toLowerCase())
|
|
4698
4752
|
);
|
|
4699
4753
|
try {
|
|
4700
|
-
for (const name of await
|
|
4701
|
-
const ext =
|
|
4702
|
-
if (ext === ".html" || ext === ".css") srcFiles.push(
|
|
4754
|
+
for (const name of await fs16.readdir(cwd)) {
|
|
4755
|
+
const ext = path15.extname(name).toLowerCase();
|
|
4756
|
+
if (ext === ".html" || ext === ".css") srcFiles.push(path15.join(cwd, name));
|
|
4703
4757
|
}
|
|
4704
4758
|
} catch {
|
|
4705
4759
|
}
|
|
4706
4760
|
const sources = [];
|
|
4707
4761
|
for (const p of srcFiles) {
|
|
4708
4762
|
try {
|
|
4709
|
-
sources.push({ rel:
|
|
4763
|
+
sources.push({ rel: path15.relative(cwd, p), text: await fs16.readFile(p, "utf8") });
|
|
4710
4764
|
} catch {
|
|
4711
4765
|
}
|
|
4712
4766
|
}
|
|
@@ -4716,11 +4770,11 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4716
4770
|
const metaByName = /* @__PURE__ */ new Map();
|
|
4717
4771
|
const bboxByPng = /* @__PURE__ */ new Map();
|
|
4718
4772
|
for (const p of assetFiles) {
|
|
4719
|
-
const base =
|
|
4773
|
+
const base = path15.basename(p);
|
|
4720
4774
|
const metaMatch = /^(.+)\.annotated-progress\.json$/.exec(base);
|
|
4721
4775
|
if (metaMatch) {
|
|
4722
4776
|
try {
|
|
4723
|
-
const meta = JSON.parse(await
|
|
4777
|
+
const meta = JSON.parse(await fs16.readFile(p, "utf8"));
|
|
4724
4778
|
metaByName.set(metaMatch[1], {
|
|
4725
4779
|
cleanCrop: meta.clean?.crop ?? null,
|
|
4726
4780
|
loosened: meta.loosened === true
|
|
@@ -4731,7 +4785,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4731
4785
|
}
|
|
4732
4786
|
if (base.endsWith(".bbox.json")) {
|
|
4733
4787
|
try {
|
|
4734
|
-
const sidecar = JSON.parse(await
|
|
4788
|
+
const sidecar = JSON.parse(await fs16.readFile(p, "utf8"));
|
|
4735
4789
|
if (sidecar.sheetBBox) bboxByPng.set(base.replace(/\.bbox\.json$/, ".png"), sidecar.sheetBBox);
|
|
4736
4790
|
} catch {
|
|
4737
4791
|
}
|
|
@@ -4740,7 +4794,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4740
4794
|
for (const [name, meta] of metaByName) {
|
|
4741
4795
|
if (!meta.cleanCrop || !referenced(`${name}-mask.png`)) continue;
|
|
4742
4796
|
for (const p of assetFiles) {
|
|
4743
|
-
const base =
|
|
4797
|
+
const base = path15.basename(p);
|
|
4744
4798
|
if (!base.toLowerCase().endsWith(".png")) continue;
|
|
4745
4799
|
if (base !== `${name}.png` && !base.startsWith(`${name}-`)) continue;
|
|
4746
4800
|
if (/-mask\.png$|-frame\.png$|-overlay\.png$|-annotated-source\.png$/.test(base)) continue;
|
|
@@ -4764,7 +4818,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4764
4818
|
}
|
|
4765
4819
|
const pngByBase = /* @__PURE__ */ new Map();
|
|
4766
4820
|
for (const p of assetFiles) {
|
|
4767
|
-
const base =
|
|
4821
|
+
const base = path15.basename(p);
|
|
4768
4822
|
if (base.toLowerCase().endsWith(".png")) pngByBase.set(base, p);
|
|
4769
4823
|
}
|
|
4770
4824
|
for (const [maskBase, maskPath] of pngByBase) {
|
|
@@ -4777,8 +4831,8 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4777
4831
|
let frame;
|
|
4778
4832
|
let mask;
|
|
4779
4833
|
try {
|
|
4780
|
-
frame = PNG2.sync.read(await
|
|
4781
|
-
mask = PNG2.sync.read(await
|
|
4834
|
+
frame = PNG2.sync.read(await fs16.readFile(framePath));
|
|
4835
|
+
mask = PNG2.sync.read(await fs16.readFile(maskPath));
|
|
4782
4836
|
} catch {
|
|
4783
4837
|
continue;
|
|
4784
4838
|
}
|
|
@@ -4797,7 +4851,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4797
4851
|
if (!referenced(base)) continue;
|
|
4798
4852
|
let png;
|
|
4799
4853
|
try {
|
|
4800
|
-
png = PNG2.sync.read(await
|
|
4854
|
+
png = PNG2.sync.read(await fs16.readFile(p));
|
|
4801
4855
|
} catch {
|
|
4802
4856
|
continue;
|
|
4803
4857
|
}
|
|
@@ -4817,7 +4871,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4817
4871
|
}
|
|
4818
4872
|
const maskReported = /* @__PURE__ */ new Set();
|
|
4819
4873
|
for (const p of assetFiles) {
|
|
4820
|
-
const m = /^(.+)\.annotated-progress\.json$/.exec(
|
|
4874
|
+
const m = /^(.+)\.annotated-progress\.json$/.exec(path15.basename(p));
|
|
4821
4875
|
if (!m) continue;
|
|
4822
4876
|
const maskBase = `${m[1]}-mask.png`;
|
|
4823
4877
|
if (!referenced(maskBase)) {
|
|
@@ -4829,14 +4883,14 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4829
4883
|
}
|
|
4830
4884
|
}
|
|
4831
4885
|
for (const p of assetFiles) {
|
|
4832
|
-
const base =
|
|
4886
|
+
const base = path15.basename(p);
|
|
4833
4887
|
if (!base.toLowerCase().endsWith(".png")) continue;
|
|
4834
4888
|
if (/-annotated-source\.png$|-overlay\.png$/.test(base)) continue;
|
|
4835
4889
|
if (maskReported.has(base)) continue;
|
|
4836
4890
|
if (!referenced(base)) {
|
|
4837
4891
|
findings.push({
|
|
4838
4892
|
kind: "unwired-sprite",
|
|
4839
|
-
message: `${
|
|
4893
|
+
message: `${path15.relative(cwd, p)} is on disk but never referenced in ${srcDir}/index.html/CSS \u2014 wire it, or record the one-line reason it was cut. (Computed-string references are invisible here \u2014 check.)`
|
|
4840
4894
|
});
|
|
4841
4895
|
}
|
|
4842
4896
|
}
|
|
@@ -4951,7 +5005,7 @@ async function printUiAuditPreflight(log) {
|
|
|
4951
5005
|
}
|
|
4952
5006
|
async function printPipelineStatePreflight(log, cwd = process.cwd()) {
|
|
4953
5007
|
try {
|
|
4954
|
-
const design = await
|
|
5008
|
+
const design = await fs17.readFile(path16.join(cwd, "DESIGN.md"), "utf8").catch(() => "");
|
|
4955
5009
|
const warnings = [];
|
|
4956
5010
|
if (design.length > 0 && !/##\s*build plan & status/i.test(design)) {
|
|
4957
5011
|
warnings.push(
|
|
@@ -4959,7 +5013,7 @@ async function printPipelineStatePreflight(log, cwd = process.cwd()) {
|
|
|
4959
5013
|
);
|
|
4960
5014
|
}
|
|
4961
5015
|
if (!/player character:/i.test(design)) {
|
|
4962
|
-
const hasCharacter = await
|
|
5016
|
+
const hasCharacter = await fs17.access(path16.join(cwd, "public", "assets", "meshy-character.json")).then(() => true, () => false);
|
|
4963
5017
|
if (!hasCharacter && await loadsPlayerBody(cwd)) {
|
|
4964
5018
|
warnings.push(
|
|
4965
5019
|
`Player character is the stock avatar \u2014 no generated character is wired. The game's own generated character is the player's body wherever a human body appears on screen, first-person included (genex-ai-character). Generate it, or record "Player character: VRM \u2014 <reason>" in DESIGN.md (no human body in this game / out of credits / player declined).`
|
|
@@ -4976,12 +5030,12 @@ async function printPipelineStatePreflight(log, cwd = process.cwd()) {
|
|
|
4976
5030
|
async function loadsPlayerBody(cwd) {
|
|
4977
5031
|
const BODY_LOADERS = /\bloadPlayerCharacter\s*\(|\bloadVrm(?:Clone)?\s*\(/;
|
|
4978
5032
|
try {
|
|
4979
|
-
const entries = await
|
|
5033
|
+
const entries = await fs17.readdir(path16.join(cwd, "src"), { recursive: true });
|
|
4980
5034
|
for (const rel of entries) {
|
|
4981
5035
|
if (rel.includes("node_modules")) continue;
|
|
4982
|
-
if (rel.split(
|
|
5036
|
+
if (rel.split(path16.sep)[0] === "controllers" || rel.startsWith("controllers/")) continue;
|
|
4983
5037
|
if (!/\.(ts|tsx|js|jsx)$/.test(rel)) continue;
|
|
4984
|
-
const text = await
|
|
5038
|
+
const text = await fs17.readFile(path16.join(cwd, "src", rel), "utf8").catch(() => "");
|
|
4985
5039
|
if (BODY_LOADERS.test(text)) return true;
|
|
4986
5040
|
}
|
|
4987
5041
|
} catch {
|
|
@@ -5018,9 +5072,9 @@ async function deployGame(ctx, opts, log) {
|
|
|
5018
5072
|
}
|
|
5019
5073
|
log.success("Built.");
|
|
5020
5074
|
}
|
|
5021
|
-
const distDir =
|
|
5075
|
+
const distDir = path16.join(cwd, "dist");
|
|
5022
5076
|
const siteDir = await isDir(distDir) ? distDir : cwd;
|
|
5023
|
-
const rel =
|
|
5077
|
+
const rel = path16.relative(cwd, siteDir) || ".";
|
|
5024
5078
|
if (siteDir === cwd) await writeGitignore(cwd, log);
|
|
5025
5079
|
const files = await collectFiles(siteDir);
|
|
5026
5080
|
if (files.length === 0) {
|
|
@@ -5111,7 +5165,7 @@ async function deployGame(ctx, opts, log) {
|
|
|
5111
5165
|
}
|
|
5112
5166
|
async function hasBuildScript(cwd) {
|
|
5113
5167
|
try {
|
|
5114
|
-
const pkg = JSON.parse(await
|
|
5168
|
+
const pkg = JSON.parse(await fs17.readFile(path16.join(cwd, "package.json"), "utf8"));
|
|
5115
5169
|
return Boolean(pkg.scripts?.build);
|
|
5116
5170
|
} catch {
|
|
5117
5171
|
return false;
|
|
@@ -5120,12 +5174,12 @@ async function hasBuildScript(cwd) {
|
|
|
5120
5174
|
async function collectFiles(root) {
|
|
5121
5175
|
const out = [];
|
|
5122
5176
|
const walk2 = async (dir, prefix) => {
|
|
5123
|
-
for (const e of await
|
|
5177
|
+
for (const e of await fs17.readdir(dir, { withFileTypes: true })) {
|
|
5124
5178
|
const relPath = prefix ? `${prefix}/${e.name}` : e.name;
|
|
5125
5179
|
if (e.isDirectory()) {
|
|
5126
|
-
if (!EXCLUDE_DIRS.has(e.name)) await walk2(
|
|
5180
|
+
if (!EXCLUDE_DIRS.has(e.name)) await walk2(path16.join(dir, e.name), relPath);
|
|
5127
5181
|
} else if (e.isFile() && !isSecretEnvFile(e.name)) {
|
|
5128
|
-
out.push({ relPath, bytes: await
|
|
5182
|
+
out.push({ relPath, bytes: await fs17.readFile(path16.join(dir, e.name)) });
|
|
5129
5183
|
}
|
|
5130
5184
|
}
|
|
5131
5185
|
};
|
|
@@ -5362,23 +5416,43 @@ async function pushSource(cwd, ctx, log, branch = "main", expectStagingCommit, o
|
|
|
5362
5416
|
if (target === "stale") return "stale";
|
|
5363
5417
|
if (!target) return false;
|
|
5364
5418
|
const ref = target.managed ? branch : "main";
|
|
5365
|
-
|
|
5419
|
+
const first = await pushWorktree(
|
|
5420
|
+
cwd,
|
|
5421
|
+
target.pushUrl,
|
|
5422
|
+
target.managed,
|
|
5423
|
+
log,
|
|
5424
|
+
ref,
|
|
5425
|
+
onCommit,
|
|
5426
|
+
false,
|
|
5427
|
+
target.sourceLimits
|
|
5428
|
+
);
|
|
5429
|
+
if (first === true) return true;
|
|
5430
|
+
if (first === "oversize") return false;
|
|
5366
5431
|
if (!target.managed) return false;
|
|
5367
5432
|
log.info("Retrying the source push\u2026");
|
|
5368
5433
|
await new Promise((r) => setTimeout(r, 2e3));
|
|
5369
5434
|
const fresh = await fetchPushUrl(ctx, log, expectStagingCommit);
|
|
5370
5435
|
if (fresh === "stale") return "stale";
|
|
5371
5436
|
if (!fresh) return false;
|
|
5372
|
-
return pushWorktree(
|
|
5437
|
+
return await pushWorktree(
|
|
5438
|
+
cwd,
|
|
5439
|
+
fresh.pushUrl,
|
|
5440
|
+
fresh.managed,
|
|
5441
|
+
log,
|
|
5442
|
+
ref,
|
|
5443
|
+
onCommit,
|
|
5444
|
+
false,
|
|
5445
|
+
fresh.sourceLimits
|
|
5446
|
+
) === true;
|
|
5373
5447
|
}
|
|
5374
5448
|
var WORKSPACE_SOURCE_REF = "workspace";
|
|
5375
|
-
async function pushWorktree(cwd, pushUrl, managed, log, branch = "main", onCommit) {
|
|
5449
|
+
async function pushWorktree(cwd, pushUrl, managed, log, branch = "main", onCommit, skipSizeCheck = false, limits) {
|
|
5376
5450
|
const EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
5377
5451
|
const failed = () => {
|
|
5378
5452
|
log.error("Couldn't save your game's source \u2014 please try again.");
|
|
5379
|
-
return
|
|
5453
|
+
return "failed";
|
|
5380
5454
|
};
|
|
5381
|
-
const gitDir = await
|
|
5455
|
+
const gitDir = await fs17.mkdtemp(path16.join(os5.tmpdir(), "genex-source-"));
|
|
5382
5456
|
const base = { GIT_DIR: gitDir };
|
|
5383
5457
|
if (urlHasEmbeddedCredentials(pushUrl)) {
|
|
5384
5458
|
base.GIT_CONFIG_COUNT = "1";
|
|
@@ -5393,8 +5467,8 @@ async function pushWorktree(cwd, pushUrl, managed, log, branch = "main", onCommi
|
|
|
5393
5467
|
};
|
|
5394
5468
|
try {
|
|
5395
5469
|
if ((await run("git", ["init", "-q"], base)).code !== 0) return failed();
|
|
5396
|
-
await
|
|
5397
|
-
const env = { ...base, GIT_WORK_TREE: cwd, GIT_INDEX_FILE:
|
|
5470
|
+
await fs17.writeFile(path16.join(gitDir, "info", "exclude"), excludeFile());
|
|
5471
|
+
const env = { ...base, GIT_WORK_TREE: cwd, GIT_INDEX_FILE: path16.join(gitDir, "index-source") };
|
|
5398
5472
|
let lfs = (await run("git", ["lfs", "version"], base)).code !== 0 ? false : true;
|
|
5399
5473
|
if (!lfs) {
|
|
5400
5474
|
log.step("Installing git-lfs (keeps large binary assets out of the source push)\u2026");
|
|
@@ -5418,17 +5492,31 @@ async function pushWorktree(cwd, pushUrl, managed, log, branch = "main", onCommi
|
|
|
5418
5492
|
for (const [key, value] of filters) await run("git", ["config", key, value], base);
|
|
5419
5493
|
}
|
|
5420
5494
|
await run("git", ["add", "-A"], env);
|
|
5421
|
-
const
|
|
5495
|
+
const staged = (await run("git", ["ls-files", "-z"], env)).out.split("\0").filter(Boolean);
|
|
5496
|
+
if (staged.length === 0) {
|
|
5497
|
+
log.error("Nothing to publish \u2014 the project has no files.");
|
|
5498
|
+
return "failed";
|
|
5499
|
+
}
|
|
5500
|
+
const payload = await measurePayload(cwd, staged);
|
|
5501
|
+
log.dim(`Source: ${payload.files} files, ${formatBytes2(payload.bytes)}.`);
|
|
5502
|
+
const ceiling = limits ?? FALLBACK_SOURCE_LIMITS;
|
|
5503
|
+
if (!skipSizeCheck && isOversized(payload, ceiling)) {
|
|
5504
|
+
const [head, ...rest] = oversizedLines(payload, ceiling);
|
|
5505
|
+
log.error(head ?? "Source too large.");
|
|
5506
|
+
for (const line of rest) log.dim(` ${line}`);
|
|
5507
|
+
return "oversize";
|
|
5508
|
+
}
|
|
5509
|
+
const tree = (await run("git", ["write-tree"], env)).out.trim();
|
|
5422
5510
|
if (!tree || tree === EMPTY_TREE) {
|
|
5423
5511
|
log.error("Nothing to publish \u2014 the project has no files.");
|
|
5424
|
-
return
|
|
5512
|
+
return "failed";
|
|
5425
5513
|
}
|
|
5426
5514
|
const commit = (await run("git", ["commit-tree", tree, "-m", "source"], { ...base, ...ident })).out.trim();
|
|
5427
5515
|
if (commit) onCommit?.(commit);
|
|
5428
5516
|
await run("git", ["update-ref", "refs/heads/main", commit], base);
|
|
5429
5517
|
if (lfs && (await run("git", ["lfs", "push", "--all", pushUrl, "main"], base)).code !== 0) {
|
|
5430
5518
|
log.error("Couldn't upload your game's binary assets to the source store.");
|
|
5431
|
-
return
|
|
5519
|
+
return "failed";
|
|
5432
5520
|
}
|
|
5433
5521
|
const push = await run(
|
|
5434
5522
|
"git",
|
|
@@ -5440,13 +5528,13 @@ async function pushWorktree(cwd, pushUrl, managed, log, branch = "main", onCommi
|
|
|
5440
5528
|
log.error(
|
|
5441
5529
|
"Couldn't push to your repo. Check that you have push access (SSH key or git credentials) and that the repo exists."
|
|
5442
5530
|
);
|
|
5443
|
-
return
|
|
5531
|
+
return "failed";
|
|
5444
5532
|
}
|
|
5445
5533
|
return failed();
|
|
5446
5534
|
} catch {
|
|
5447
5535
|
return failed();
|
|
5448
5536
|
} finally {
|
|
5449
|
-
await
|
|
5537
|
+
await fs17.rm(gitDir, { recursive: true, force: true }).catch(() => {
|
|
5450
5538
|
});
|
|
5451
5539
|
}
|
|
5452
5540
|
}
|
|
@@ -5496,11 +5584,18 @@ async function fetchPushUrl(ctx, log, expectStagingCommit) {
|
|
|
5496
5584
|
log.error("The API didn't return a push URL.");
|
|
5497
5585
|
return null;
|
|
5498
5586
|
}
|
|
5499
|
-
|
|
5587
|
+
const l = data.sourceLimits;
|
|
5588
|
+
const sourceLimits = typeof l?.maxTotalBytes === "number" && typeof l?.maxFiles === "number" ? { maxTotalBytes: l.maxTotalBytes, maxFiles: l.maxFiles } : void 0;
|
|
5589
|
+
return {
|
|
5590
|
+
pushUrl: data.pushUrl,
|
|
5591
|
+
managed: data.managed !== false,
|
|
5592
|
+
sourceRef: data.sourceRef ?? null,
|
|
5593
|
+
sourceLimits
|
|
5594
|
+
};
|
|
5500
5595
|
}
|
|
5501
5596
|
async function isDir(p) {
|
|
5502
5597
|
try {
|
|
5503
|
-
return (await
|
|
5598
|
+
return (await fs17.stat(p)).isDirectory();
|
|
5504
5599
|
} catch {
|
|
5505
5600
|
return false;
|
|
5506
5601
|
}
|
|
@@ -5712,28 +5807,28 @@ function loggerFor(opts) {
|
|
|
5712
5807
|
}
|
|
5713
5808
|
async function readState(directory) {
|
|
5714
5809
|
try {
|
|
5715
|
-
const marker =
|
|
5716
|
-
if ((await
|
|
5717
|
-
const value = JSON.parse(await
|
|
5810
|
+
const marker = path17.join(directory, ".genex", "remix-operation.json");
|
|
5811
|
+
if ((await fs18.lstat(marker)).isSymbolicLink()) return null;
|
|
5812
|
+
const value = JSON.parse(await fs18.readFile(marker, "utf8"));
|
|
5718
5813
|
return value.schema === 1 && value.directory === directory && typeof value.operationId === "string" && /^[a-zA-Z0-9_-]{8,128}$/.test(value.operationId) && value.source && /^[a-f0-9]{40}$/.test(value.source.sourceCommitSha) ? value : null;
|
|
5719
5814
|
} catch {
|
|
5720
5815
|
return null;
|
|
5721
5816
|
}
|
|
5722
5817
|
}
|
|
5723
5818
|
async function saveState(state) {
|
|
5724
|
-
const dir =
|
|
5725
|
-
await
|
|
5726
|
-
const temp =
|
|
5727
|
-
await
|
|
5728
|
-
await
|
|
5819
|
+
const dir = path17.join(state.directory, ".genex");
|
|
5820
|
+
await fs18.mkdir(dir, { recursive: true, mode: 448 });
|
|
5821
|
+
const temp = path17.join(dir, "remix-operation.next.json");
|
|
5822
|
+
await fs18.writeFile(temp, JSON.stringify(state, null, 2) + "\n", { mode: 384 });
|
|
5823
|
+
await fs18.rename(temp, path17.join(dir, "remix-operation.json"));
|
|
5729
5824
|
}
|
|
5730
5825
|
async function checkDestination(directory) {
|
|
5731
5826
|
try {
|
|
5732
|
-
const stat = await
|
|
5827
|
+
const stat = await fs18.lstat(directory);
|
|
5733
5828
|
if (!stat.isDirectory() || stat.isSymbolicLink()) throw new RemixError("destination_occupied", "The destination is not a fresh directory. Choose a new folder.");
|
|
5734
|
-
const entries = await
|
|
5829
|
+
const entries = await fs18.readdir(directory);
|
|
5735
5830
|
if (!entries.length) return null;
|
|
5736
|
-
const genex = await
|
|
5831
|
+
const genex = await fs18.lstat(path17.join(directory, ".genex")).catch(() => null);
|
|
5737
5832
|
const state = genex?.isDirectory() && !genex.isSymbolicLink() ? await readState(directory) : null;
|
|
5738
5833
|
if (state) return state;
|
|
5739
5834
|
throw new RemixError("destination_occupied", "The destination already contains files. Choose a fresh folder; remix never clears an existing project.");
|
|
@@ -5744,7 +5839,7 @@ async function checkDestination(directory) {
|
|
|
5744
5839
|
}
|
|
5745
5840
|
async function assertReferenceParents(cwd) {
|
|
5746
5841
|
for (const rel of [".genex", ".genex/refs"]) {
|
|
5747
|
-
const stat = await
|
|
5842
|
+
const stat = await fs18.lstat(path17.join(cwd, rel)).catch((err) => {
|
|
5748
5843
|
if (err.code === "ENOENT") return null;
|
|
5749
5844
|
throw err;
|
|
5750
5845
|
});
|
|
@@ -5752,41 +5847,41 @@ async function assertReferenceParents(cwd) {
|
|
|
5752
5847
|
}
|
|
5753
5848
|
}
|
|
5754
5849
|
async function archiveSourceInstructions(directory) {
|
|
5755
|
-
const archive =
|
|
5850
|
+
const archive = path17.join(directory, ".genex", "source-instructions");
|
|
5756
5851
|
const rootArtifacts = /* @__PURE__ */ new Set([".claude", ".codex", ".cursor", ".agents", ".mcp.json", ".cursorrules", "DESIGN.md"]);
|
|
5757
5852
|
const instructionNames = /* @__PURE__ */ new Set(["AGENTS.md", "CLAUDE.md", "GEMINI.md"]);
|
|
5758
5853
|
const visit = async (dir) => {
|
|
5759
|
-
for (const entry of await
|
|
5854
|
+
for (const entry of await fs18.readdir(dir, { withFileTypes: true })) {
|
|
5760
5855
|
if (entry.name === ".genex" || entry.name === ".git" || entry.name === "node_modules") continue;
|
|
5761
|
-
const from =
|
|
5856
|
+
const from = path17.join(dir, entry.name), rel = path17.relative(directory, from);
|
|
5762
5857
|
if (instructionNames.has(entry.name) || dir === directory && rootArtifacts.has(entry.name)) {
|
|
5763
|
-
const to =
|
|
5764
|
-
await
|
|
5765
|
-
if (await
|
|
5766
|
-
await
|
|
5858
|
+
const to = path17.join(archive, rel);
|
|
5859
|
+
await fs18.mkdir(path17.dirname(to), { recursive: true });
|
|
5860
|
+
if (await fs18.lstat(to).catch(() => null)) throw new RemixError("destination_changed", `Source instructions at ${rel} changed during preparation; both copies were retained.`);
|
|
5861
|
+
await fs18.rename(from, to);
|
|
5767
5862
|
} else if (entry.isDirectory()) await visit(from);
|
|
5768
5863
|
}
|
|
5769
5864
|
};
|
|
5770
5865
|
await visit(directory);
|
|
5771
5866
|
}
|
|
5772
5867
|
async function acquireLock(directory) {
|
|
5773
|
-
const lock =
|
|
5868
|
+
const lock = path17.join(directory, ".genex", "remix.lock");
|
|
5774
5869
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
5775
5870
|
try {
|
|
5776
|
-
const handle = await
|
|
5871
|
+
const handle = await fs18.open(lock, "wx", 384);
|
|
5777
5872
|
await handle.writeFile(String(process.pid));
|
|
5778
5873
|
await handle.close();
|
|
5779
|
-
return () =>
|
|
5874
|
+
return () => fs18.unlink(lock).catch(() => {
|
|
5780
5875
|
});
|
|
5781
5876
|
} catch (err) {
|
|
5782
5877
|
if (err.code !== "EEXIST") throw err;
|
|
5783
|
-
const pid = Number(await
|
|
5878
|
+
const pid = Number(await fs18.readFile(lock, "utf8").catch(() => ""));
|
|
5784
5879
|
if (!Number.isInteger(pid) || pid <= 0) throw new RemixError("remix_in_progress", "Another remix owns this destination. Retry after it finishes.");
|
|
5785
5880
|
try {
|
|
5786
5881
|
process.kill(pid, 0);
|
|
5787
5882
|
} catch (probe) {
|
|
5788
5883
|
if (probe.code === "ESRCH") {
|
|
5789
|
-
await
|
|
5884
|
+
await fs18.unlink(lock);
|
|
5790
5885
|
continue;
|
|
5791
5886
|
}
|
|
5792
5887
|
}
|
|
@@ -5872,13 +5967,13 @@ function pinRemixDependencies(pkg, version) {
|
|
|
5872
5967
|
return warnings;
|
|
5873
5968
|
}
|
|
5874
5969
|
async function installRemixDependencies(cwd, pm, version, log, runner = childRun) {
|
|
5875
|
-
const pkgPath =
|
|
5876
|
-
const pkg = JSON.parse(await
|
|
5970
|
+
const pkgPath = path17.join(cwd, "package.json");
|
|
5971
|
+
const pkg = JSON.parse(await fs18.readFile(pkgPath, "utf8"));
|
|
5877
5972
|
const warnings = pinRemixDependencies(pkg, version);
|
|
5878
|
-
await
|
|
5973
|
+
await fs18.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
5879
5974
|
log.step(`Restoring dependencies with ${pm}; pinning ${CLI_PACKAGE}@${version} (${CLI_CHANNEL}).`);
|
|
5880
5975
|
const args = pm === "npm" ? ["install", "--workspaces=false", "--no-audit", "--no-fund"] : pm === "pnpm" ? ["install", "--ignore-workspace", "--no-frozen-lockfile"] : ["install"];
|
|
5881
|
-
if (pm === "yarn") await
|
|
5976
|
+
if (pm === "yarn") await fs18.writeFile(path17.join(cwd, "yarn.lock"), "", { flag: "wx" }).catch((err) => {
|
|
5882
5977
|
if (err.code !== "EEXIST") throw err;
|
|
5883
5978
|
});
|
|
5884
5979
|
await runner(cwd, pm, args);
|
|
@@ -5912,27 +6007,27 @@ function rebindRemixConfig(content, slug) {
|
|
|
5912
6007
|
async function configureIdentity(state, log) {
|
|
5913
6008
|
const cwd = state.directory, meta = state.project;
|
|
5914
6009
|
await writeProject(meta, cwd);
|
|
5915
|
-
const configFile =
|
|
5916
|
-
const config = await
|
|
6010
|
+
const configFile = path17.join(cwd, "src", "genex.config.ts");
|
|
6011
|
+
const config = await fs18.readFile(configFile, "utf8").catch((err) => {
|
|
5917
6012
|
if (err.code === "ENOENT") return null;
|
|
5918
6013
|
throw err;
|
|
5919
6014
|
});
|
|
5920
|
-
if (config !== null) await
|
|
6015
|
+
if (config !== null) await fs18.writeFile(configFile, rebindRemixConfig(config, meta.slug));
|
|
5921
6016
|
await writeGameConfigFiles(meta, log, cwd);
|
|
5922
6017
|
await writeGitignore(cwd, log);
|
|
5923
6018
|
await installRemixProfile(cwd, state.apiUrl, log);
|
|
5924
|
-
const manifestFile =
|
|
5925
|
-
const manifest = JSON.parse(await
|
|
6019
|
+
const manifestFile = path17.join(cwd, "package.json");
|
|
6020
|
+
const manifest = JSON.parse(await fs18.readFile(manifestFile, "utf8"));
|
|
5926
6021
|
manifest.genex.remixSource = { projectId: state.source.projectId, slug: state.source.slug, sourceCommitSha: state.source.sourceCommitSha, version: state.source.version, versionCertainty: state.source.versionCertainty };
|
|
5927
|
-
await
|
|
6022
|
+
await fs18.writeFile(manifestFile, JSON.stringify(manifest, null, 2) + "\n");
|
|
5928
6023
|
const possible = [];
|
|
5929
6024
|
const quoted = new RegExp(`(["'\`])(?:${state.source.slug.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}|${state.source.projectId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})\\1`);
|
|
5930
6025
|
const scan = async (dir) => {
|
|
5931
|
-
for (const entry of await
|
|
6026
|
+
for (const entry of await fs18.readdir(dir, { withFileTypes: true })) {
|
|
5932
6027
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
5933
|
-
const file =
|
|
6028
|
+
const file = path17.join(dir, entry.name);
|
|
5934
6029
|
if (entry.isDirectory()) await scan(file);
|
|
5935
|
-
else if (entry.isFile() && /\.(?:[cm]?[jt]sx?|html)$/.test(entry.name) && (await
|
|
6030
|
+
else if (entry.isFile() && /\.(?:[cm]?[jt]sx?|html)$/.test(entry.name) && (await fs18.stat(file)).size < 1024 * 1024 && quoted.test(await fs18.readFile(file, "utf8"))) possible.push(path17.relative(cwd, file));
|
|
5936
6031
|
}
|
|
5937
6032
|
};
|
|
5938
6033
|
await scan(cwd);
|
|
@@ -5957,7 +6052,7 @@ async function runRemix(opts, deps = {}) {
|
|
|
5957
6052
|
const apiUrl = getApiUrl(opts.apiUrl), dashboardUrl = new URL(getAuthUrl(opts.authUrl)).origin;
|
|
5958
6053
|
if (opts.sourceOnly && !opts.destination) await assertReferenceParents(originalCwd);
|
|
5959
6054
|
const selected = parseRemixSource(opts.remixSource, apiUrl, dashboardUrl);
|
|
5960
|
-
let directory = opts.destination ?
|
|
6055
|
+
let directory = opts.destination ? path17.resolve(originalCwd, opts.destination) : !opts.sourceOnly ? path17.resolve(originalCwd, `${selected.slug}-remix`) : void 0;
|
|
5961
6056
|
if (directory) state = await checkDestination(directory);
|
|
5962
6057
|
resumed = state !== null;
|
|
5963
6058
|
if (state && (state.apiUrl !== apiUrl || state.sourceOnly !== Boolean(opts.sourceOnly) || ![state.requestedSlug, state.source.slug].includes(selected.slug) || opts.operationId && state.operationId !== opts.operationId || opts.name && state.name !== opts.name || selected.version && selected.version !== state.source.version)) throw new RemixError("operation_mismatch", "This folder belongs to a different remix operation. Resume with its original source/options or choose a fresh folder.");
|
|
@@ -5975,7 +6070,7 @@ async function runRemix(opts, deps = {}) {
|
|
|
5975
6070
|
log.step(`Resolving source for ${selected.slug}\u2026`);
|
|
5976
6071
|
const body = await responseJson(await request(`${apiUrl}/api/projects/${encodeURIComponent(selected.slug)}/source?${query}`, { headers, redirect: "error" }));
|
|
5977
6072
|
validateSource(body, Boolean(opts.sourceOnly));
|
|
5978
|
-
directory ??=
|
|
6073
|
+
directory ??= path17.resolve(originalCwd, ".genex", "refs", `${body.source.slug}-${body.source.sourceCommitSha.slice(0, 12)}`);
|
|
5979
6074
|
const existing = await checkDestination(directory);
|
|
5980
6075
|
if (existing) {
|
|
5981
6076
|
if (!existing.sourceOnly || !opts.sourceOnly || existing.apiUrl !== apiUrl || existing.source.sourceCommitSha !== body.source.sourceCommitSha || existing.source.projectId !== body.source.projectId) throw new RemixError("destination_occupied", "This reference destination already belongs to another operation. Choose a fresh folder.");
|
|
@@ -5983,21 +6078,21 @@ async function runRemix(opts, deps = {}) {
|
|
|
5983
6078
|
resumed = true;
|
|
5984
6079
|
} else {
|
|
5985
6080
|
state = { schema: 1, operationId: opts.operationId ?? crypto4.randomUUID(), directory, apiUrl, dashboardUrl, requestedSlug: selected.slug, source: body.source, sourceToken: body.sourceToken, sourceOnly: Boolean(opts.sourceOnly), cliVersion: getCliVersion(), name: opts.name?.trim() || `${body.source.title.slice(0, 74)} remix`, stage: "download", warnings: [...body.source.warnings] };
|
|
5986
|
-
await
|
|
5987
|
-
if ((await
|
|
5988
|
-
await
|
|
6081
|
+
await fs18.mkdir(directory, { recursive: true });
|
|
6082
|
+
if ((await fs18.readdir(directory)).length) throw new RemixError("destination_occupied", "The destination changed during setup. Choose a fresh folder.");
|
|
6083
|
+
await fs18.mkdir(path17.join(directory, ".genex"), { mode: 448 });
|
|
5989
6084
|
await saveState(state);
|
|
5990
6085
|
}
|
|
5991
6086
|
}
|
|
5992
6087
|
unlock = await acquireLock(state.directory);
|
|
5993
6088
|
state = await readState(state.directory) ?? state;
|
|
5994
6089
|
if (resumed && state.stage === "download") await renewSource(state, request, headers);
|
|
5995
|
-
const staging =
|
|
6090
|
+
const staging = path17.join(state.directory, ".genex", "remix-stage");
|
|
5996
6091
|
if (state.stage === "download") {
|
|
5997
|
-
await
|
|
5998
|
-
await
|
|
5999
|
-
const archive =
|
|
6000
|
-
await
|
|
6092
|
+
await fs18.rm(staging, { recursive: true, force: true });
|
|
6093
|
+
await fs18.mkdir(staging, { recursive: true });
|
|
6094
|
+
const archive = path17.join(staging, "source.zip"), sourceDir = path17.join(staging, "source");
|
|
6095
|
+
await fs18.mkdir(sourceDir);
|
|
6001
6096
|
const archiveUrl = new URL(state.source.archiveUrl, state.apiUrl);
|
|
6002
6097
|
if (archiveUrl.origin !== new URL(state.apiUrl).origin || !archiveUrl.pathname.startsWith("/api/projects/")) throw new RemixError("invalid_source_response", "Source archive must be served by the selected Genex API.");
|
|
6003
6098
|
log.step(`Downloading ${state.source.slug} at ${state.source.sourceCommitSha.slice(0, 12)}\u2026`);
|
|
@@ -6012,26 +6107,26 @@ async function runRemix(opts, deps = {}) {
|
|
|
6012
6107
|
if (!state.sourceOnly) {
|
|
6013
6108
|
let pkg;
|
|
6014
6109
|
try {
|
|
6015
|
-
pkg = JSON.parse(await
|
|
6110
|
+
pkg = JSON.parse(await fs18.readFile(path17.join(sourceDir, "package.json"), "utf8"));
|
|
6016
6111
|
} catch (error) {
|
|
6017
|
-
const staticEntry = await
|
|
6112
|
+
const staticEntry = await fs18.stat(path17.join(sourceDir, "index.html")).catch(() => null);
|
|
6018
6113
|
if (error.code !== "ENOENT" || !staticEntry?.isFile()) throw new RemixError("source_unavailable", "Source has neither a readable package.json nor a static index.html. Use --source-only to inspect it.");
|
|
6019
6114
|
pkg = { name: manifestName(state.directory), private: true };
|
|
6020
|
-
await
|
|
6115
|
+
await fs18.writeFile(path17.join(sourceDir, "package.json"), JSON.stringify(pkg, null, 2) + "\n");
|
|
6021
6116
|
state.warnings.push("This static game had no package.json; added a minimal private manifest for the local CLI. Its source and index.html remain unchanged.");
|
|
6022
6117
|
}
|
|
6023
6118
|
if (!pkg || typeof pkg !== "object" || Array.isArray(pkg)) throw new RemixError("source_unavailable", "Source package.json is not a project manifest.");
|
|
6024
6119
|
}
|
|
6025
|
-
state.transferEntries = await
|
|
6120
|
+
state.transferEntries = await fs18.readdir(sourceDir);
|
|
6026
6121
|
state.stage = "transfer";
|
|
6027
6122
|
await saveState(state);
|
|
6028
6123
|
}
|
|
6029
6124
|
if (state.stage === "transfer") {
|
|
6030
6125
|
for (const entry of state.transferEntries ?? []) {
|
|
6031
|
-
const from =
|
|
6032
|
-
if (await
|
|
6033
|
-
if (await
|
|
6034
|
-
await
|
|
6126
|
+
const from = path17.join(staging, "source", entry), to = path17.join(state.directory, entry);
|
|
6127
|
+
if (await fs18.lstat(from).catch(() => null)) {
|
|
6128
|
+
if (await fs18.lstat(to).catch(() => null)) throw new RemixError("destination_changed", `A file appeared at ${entry} during source preparation. It was left untouched.`);
|
|
6129
|
+
await fs18.rename(from, to);
|
|
6035
6130
|
}
|
|
6036
6131
|
}
|
|
6037
6132
|
if (!state.sourceOnly) {
|
|
@@ -6040,7 +6135,7 @@ async function runRemix(opts, deps = {}) {
|
|
|
6040
6135
|
}
|
|
6041
6136
|
state.stage = state.sourceOnly ? "prepared" : "configure";
|
|
6042
6137
|
await saveState(state);
|
|
6043
|
-
await
|
|
6138
|
+
await fs18.rm(staging, { recursive: true, force: true });
|
|
6044
6139
|
}
|
|
6045
6140
|
process.chdir(state.directory);
|
|
6046
6141
|
if (state.stage === "configure") {
|
|
@@ -6111,8 +6206,8 @@ async function runRemix(opts, deps = {}) {
|
|
|
6111
6206
|
}
|
|
6112
6207
|
|
|
6113
6208
|
// src/commands/init.ts
|
|
6114
|
-
import
|
|
6115
|
-
import
|
|
6209
|
+
import fs19 from "fs/promises";
|
|
6210
|
+
import path18 from "path";
|
|
6116
6211
|
|
|
6117
6212
|
// src/lib/printed.ts
|
|
6118
6213
|
var printed = /* @__PURE__ */ new WeakSet();
|
|
@@ -6153,7 +6248,7 @@ var TOOLING_ENTRIES = /* @__PURE__ */ new Set([
|
|
|
6153
6248
|
]);
|
|
6154
6249
|
async function listPreexistingEntries(dir) {
|
|
6155
6250
|
try {
|
|
6156
|
-
return (await
|
|
6251
|
+
return (await fs19.readdir(dir)).filter((name) => !TOOLING_ENTRIES.has(name)).sort();
|
|
6157
6252
|
} catch {
|
|
6158
6253
|
return [];
|
|
6159
6254
|
}
|
|
@@ -6220,8 +6315,8 @@ async function runInit(opts) {
|
|
|
6220
6315
|
let totalNew = 0;
|
|
6221
6316
|
let totalUpdated = 0;
|
|
6222
6317
|
for (const t of targets) {
|
|
6223
|
-
const src = t.full ? templatesDir :
|
|
6224
|
-
const dest = t.full ? t.baseDir :
|
|
6318
|
+
const src = t.full ? templatesDir : path18.join(templatesDir, "skills");
|
|
6319
|
+
const dest = t.full ? t.baseDir : path18.join(t.baseDir, "skills");
|
|
6225
6320
|
const family = remixing ? skillFamilyFilter("remix") : converting ? skillFamilyFilter("tools", { hosted: true }) : skillFamilyFilter("game");
|
|
6226
6321
|
if (remixing) await pruneRemixWorkflow(t.baseDir);
|
|
6227
6322
|
const { copied, updated } = await copyTemplates(src, dest, {
|
|
@@ -6229,8 +6324,8 @@ async function runInit(opts) {
|
|
|
6229
6324
|
exclude: ["controllers", "motion", "asset-viewer", "blender-service"],
|
|
6230
6325
|
filter: t.full ? family : (rel) => family(`skills/${rel}`)
|
|
6231
6326
|
});
|
|
6232
|
-
await pruneRemovedSkills(
|
|
6233
|
-
await writeSkillsMarker(
|
|
6327
|
+
await pruneRemovedSkills(path18.join(t.baseDir, "skills"), log);
|
|
6328
|
+
await writeSkillsMarker(path18.join(t.baseDir, "skills"));
|
|
6234
6329
|
const added = copied.length - updated.length;
|
|
6235
6330
|
totalNew += added;
|
|
6236
6331
|
totalUpdated += updated.length;
|
|
@@ -6287,7 +6382,7 @@ async function runInit(opts) {
|
|
|
6287
6382
|
if (email) log.plain(` signed in as ${c.cyan(email)}`);
|
|
6288
6383
|
};
|
|
6289
6384
|
await echoIdentity();
|
|
6290
|
-
const projectName = opts.name?.trim() ||
|
|
6385
|
+
const projectName = opts.name?.trim() || path18.basename(process.cwd());
|
|
6291
6386
|
const create = (bearer) => createDraftProject({
|
|
6292
6387
|
apiUrl,
|
|
6293
6388
|
token: bearer,
|
|
@@ -6338,7 +6433,7 @@ async function runInit(opts) {
|
|
|
6338
6433
|
if (await writeToolsContract(process.cwd(), { hosted: true })) {
|
|
6339
6434
|
log.dim(" wrote the Genex Tools publishing rules into AGENTS.md (managed block)");
|
|
6340
6435
|
}
|
|
6341
|
-
await ensureEnvVar(
|
|
6436
|
+
await ensureEnvVar(path18.join(process.cwd(), ".env"), "VITE_GENEX_SLUG", meta.slug, log);
|
|
6342
6437
|
await reportCliEvent(apiUrl, token, "tools_converted");
|
|
6343
6438
|
}
|
|
6344
6439
|
if (!remixing) warnPreexisting(log, preexisting);
|
|
@@ -6365,9 +6460,9 @@ async function runInit(opts) {
|
|
|
6365
6460
|
}
|
|
6366
6461
|
|
|
6367
6462
|
// src/commands/link.ts
|
|
6368
|
-
import
|
|
6463
|
+
import fs20 from "fs/promises";
|
|
6369
6464
|
import os6 from "os";
|
|
6370
|
-
import
|
|
6465
|
+
import path19 from "path";
|
|
6371
6466
|
async function runLink(opts) {
|
|
6372
6467
|
const log = createLogger({ quiet: opts.quiet });
|
|
6373
6468
|
log.plain(c.bold("genex link"));
|
|
@@ -6448,7 +6543,7 @@ async function runLink(opts) {
|
|
|
6448
6543
|
}
|
|
6449
6544
|
async function isEmptyDir(cwd) {
|
|
6450
6545
|
try {
|
|
6451
|
-
const entries = await
|
|
6546
|
+
const entries = await fs20.readdir(cwd);
|
|
6452
6547
|
return entries.every((e) => e === ".git" || e === ".genex" || e === ".DS_Store");
|
|
6453
6548
|
} catch {
|
|
6454
6549
|
return false;
|
|
@@ -6462,13 +6557,13 @@ async function downloadSource(apiUrl, token, project, log) {
|
|
|
6462
6557
|
return false;
|
|
6463
6558
|
}
|
|
6464
6559
|
log.step(`Downloading the ${grant.sourceRef === "preview" ? "draft" : "published"} source\u2026`);
|
|
6465
|
-
const staging = await
|
|
6466
|
-
const fresh =
|
|
6560
|
+
const staging = await fs20.mkdtemp(path19.join(os6.tmpdir(), "genex-link-"));
|
|
6561
|
+
const fresh = path19.join(staging, "source");
|
|
6467
6562
|
try {
|
|
6468
6563
|
if (!await cloneSource(grant, fresh, log)) return false;
|
|
6469
|
-
await
|
|
6470
|
-
for (const entry of await
|
|
6471
|
-
await
|
|
6564
|
+
await fs20.rm(path19.join(fresh, ".git"), { recursive: true, force: true });
|
|
6565
|
+
for (const entry of await fs20.readdir(fresh)) {
|
|
6566
|
+
await fs20.cp(path19.join(fresh, entry), path19.join(process.cwd(), entry), {
|
|
6472
6567
|
recursive: true,
|
|
6473
6568
|
force: true
|
|
6474
6569
|
});
|
|
@@ -6476,28 +6571,28 @@ async function downloadSource(apiUrl, token, project, log) {
|
|
|
6476
6571
|
log.success("Downloaded.");
|
|
6477
6572
|
return true;
|
|
6478
6573
|
} finally {
|
|
6479
|
-
await
|
|
6574
|
+
await fs20.rm(staging, { recursive: true, force: true }).catch(() => {
|
|
6480
6575
|
});
|
|
6481
6576
|
}
|
|
6482
6577
|
}
|
|
6483
6578
|
async function ensureSlugEnv(slug, log, cwd = process.cwd()) {
|
|
6484
|
-
const file =
|
|
6579
|
+
const file = path19.join(cwd, ".env");
|
|
6485
6580
|
let content;
|
|
6486
6581
|
try {
|
|
6487
|
-
content = await
|
|
6582
|
+
content = await fs20.readFile(file, "utf8");
|
|
6488
6583
|
} catch {
|
|
6489
6584
|
return;
|
|
6490
6585
|
}
|
|
6491
6586
|
const re = /^(\s*VITE_GENEX_SLUG=)(.*)$/m;
|
|
6492
6587
|
const m = content.match(re);
|
|
6493
6588
|
if (!m) {
|
|
6494
|
-
await
|
|
6589
|
+
await fs20.appendFile(file, `${content.endsWith("\n") ? "" : "\n"}VITE_GENEX_SLUG=${slug}
|
|
6495
6590
|
`);
|
|
6496
6591
|
log.dim(` added VITE_GENEX_SLUG=${slug} to .env`);
|
|
6497
6592
|
return;
|
|
6498
6593
|
}
|
|
6499
6594
|
if (m[2].trim() === slug) return;
|
|
6500
|
-
await
|
|
6595
|
+
await fs20.writeFile(file, content.replace(re, `$1${slug}`));
|
|
6501
6596
|
log.dim(` updated .env: VITE_GENEX_SLUG=${slug} (was ${m[2].trim()})`);
|
|
6502
6597
|
}
|
|
6503
6598
|
async function fetchOwnProject(apiUrl, token, slug, log) {
|
|
@@ -6551,8 +6646,8 @@ async function listOwnSlugs(apiUrl, token, log) {
|
|
|
6551
6646
|
}
|
|
6552
6647
|
|
|
6553
6648
|
// src/commands/pull.ts
|
|
6554
|
-
import
|
|
6555
|
-
import
|
|
6649
|
+
import fs21 from "fs/promises";
|
|
6650
|
+
import path20 from "path";
|
|
6556
6651
|
import os7 from "os";
|
|
6557
6652
|
function isMachineLocal(entry) {
|
|
6558
6653
|
if (entry === ".genex" || entry === "node_modules" || entry === ".git") return true;
|
|
@@ -6612,23 +6707,23 @@ async function runPull(opts) {
|
|
|
6612
6707
|
process.exitCode = 1;
|
|
6613
6708
|
return;
|
|
6614
6709
|
}
|
|
6615
|
-
const staging = await
|
|
6616
|
-
const fresh =
|
|
6710
|
+
const staging = await fs21.mkdtemp(path20.join(os7.tmpdir(), "genex-pull-"));
|
|
6711
|
+
const fresh = path20.join(staging, "source");
|
|
6617
6712
|
try {
|
|
6618
6713
|
if (!await cloneSource(grant, fresh, log)) {
|
|
6619
6714
|
process.exitCode = 1;
|
|
6620
6715
|
return;
|
|
6621
6716
|
}
|
|
6622
|
-
await
|
|
6717
|
+
await fs21.rm(path20.join(fresh, ".git"), { recursive: true, force: true });
|
|
6623
6718
|
const kept = await keepReplaced(cwd, log);
|
|
6624
6719
|
await replaceTree(cwd, fresh);
|
|
6625
6720
|
if (kept) {
|
|
6626
6721
|
log.plain("");
|
|
6627
|
-
log.info(`What was here is kept at ${c.cyan(
|
|
6722
|
+
log.info(`What was here is kept at ${c.cyan(path20.relative(cwd, kept) || kept)}`);
|
|
6628
6723
|
log.dim(" Nothing was thrown away \u2014 re-apply from there, or delete it when you are done.");
|
|
6629
6724
|
}
|
|
6630
6725
|
} finally {
|
|
6631
|
-
await
|
|
6726
|
+
await fs21.rm(staging, { recursive: true, force: true }).catch(() => {
|
|
6632
6727
|
});
|
|
6633
6728
|
}
|
|
6634
6729
|
if (await hasRemixProfile(cwd)) await installRemixProfile(cwd, apiUrl, log);
|
|
@@ -6648,13 +6743,13 @@ async function runPull(opts) {
|
|
|
6648
6743
|
}
|
|
6649
6744
|
async function keepReplaced(cwd, log) {
|
|
6650
6745
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
6651
|
-
const dest =
|
|
6746
|
+
const dest = path20.join(cwd, ".genex", `replaced-${stamp}`);
|
|
6652
6747
|
try {
|
|
6653
|
-
const entries = (await
|
|
6748
|
+
const entries = (await fs21.readdir(cwd)).filter((e) => !isMachineLocal(e));
|
|
6654
6749
|
if (entries.length === 0) return null;
|
|
6655
|
-
await
|
|
6750
|
+
await fs21.mkdir(dest, { recursive: true });
|
|
6656
6751
|
for (const entry of entries) {
|
|
6657
|
-
await
|
|
6752
|
+
await fs21.cp(path20.join(cwd, entry), path20.join(dest, entry), { recursive: true });
|
|
6658
6753
|
}
|
|
6659
6754
|
return dest;
|
|
6660
6755
|
} catch (err) {
|
|
@@ -6664,19 +6759,19 @@ async function keepReplaced(cwd, log) {
|
|
|
6664
6759
|
}
|
|
6665
6760
|
}
|
|
6666
6761
|
async function replaceTree(dest, src) {
|
|
6667
|
-
for (const entry of await
|
|
6762
|
+
for (const entry of await fs21.readdir(dest)) {
|
|
6668
6763
|
if (isMachineLocal(entry)) continue;
|
|
6669
|
-
await
|
|
6764
|
+
await fs21.rm(path20.join(dest, entry), { recursive: true, force: true });
|
|
6670
6765
|
}
|
|
6671
|
-
for (const entry of await
|
|
6766
|
+
for (const entry of await fs21.readdir(src)) {
|
|
6672
6767
|
if (isMachineLocal(entry)) continue;
|
|
6673
|
-
await
|
|
6768
|
+
await fs21.cp(path20.join(src, entry), path20.join(dest, entry), { recursive: true });
|
|
6674
6769
|
}
|
|
6675
6770
|
}
|
|
6676
6771
|
|
|
6677
6772
|
// src/commands/rename.ts
|
|
6678
|
-
import
|
|
6679
|
-
import
|
|
6773
|
+
import fs22 from "fs/promises";
|
|
6774
|
+
import path21 from "path";
|
|
6680
6775
|
async function runRename(opts) {
|
|
6681
6776
|
const log = createLogger({ quiet: opts.quiet });
|
|
6682
6777
|
log.plain(c.bold("genex rename"));
|
|
@@ -6764,23 +6859,23 @@ async function runRename(opts) {
|
|
|
6764
6859
|
log.info("Run `genex preview` (or `publish`) to rebuild \u2014 the new slug is baked into the bundle.");
|
|
6765
6860
|
}
|
|
6766
6861
|
async function rewriteSlugEnv(from, to, log, cwd = process.cwd()) {
|
|
6767
|
-
const file =
|
|
6862
|
+
const file = path21.join(cwd, ".env");
|
|
6768
6863
|
let content;
|
|
6769
6864
|
try {
|
|
6770
|
-
content = await
|
|
6865
|
+
content = await fs22.readFile(file, "utf8");
|
|
6771
6866
|
} catch {
|
|
6772
6867
|
return;
|
|
6773
6868
|
}
|
|
6774
6869
|
const re = /^(\s*VITE_GENEX_SLUG=)(.*)$/m;
|
|
6775
6870
|
if (!re.test(content)) return;
|
|
6776
|
-
await
|
|
6871
|
+
await fs22.writeFile(file, content.replace(re, `$1${to}`));
|
|
6777
6872
|
log.dim(` .env: VITE_GENEX_SLUG=${to} (was ${from})`);
|
|
6778
6873
|
}
|
|
6779
6874
|
async function rewriteBakedSlug(from, to, log, cwd = process.cwd()) {
|
|
6780
|
-
const file =
|
|
6875
|
+
const file = path21.join(cwd, "src", "genex.config.ts");
|
|
6781
6876
|
let content;
|
|
6782
6877
|
try {
|
|
6783
|
-
content = await
|
|
6878
|
+
content = await fs22.readFile(file, "utf8");
|
|
6784
6879
|
} catch {
|
|
6785
6880
|
return;
|
|
6786
6881
|
}
|
|
@@ -6790,7 +6885,7 @@ async function rewriteBakedSlug(from, to, log, cwd = process.cwd()) {
|
|
|
6790
6885
|
log.warn(` src/genex.config.ts has no "${from}" literal \u2014 check its slug by hand.`);
|
|
6791
6886
|
return;
|
|
6792
6887
|
}
|
|
6793
|
-
await
|
|
6888
|
+
await fs22.writeFile(file, content.replace(quoted, `"${to}"`));
|
|
6794
6889
|
log.dim(` src/genex.config.ts: baked slug -> ${to}`);
|
|
6795
6890
|
}
|
|
6796
6891
|
|
|
@@ -6969,7 +7064,7 @@ async function runMakeRemixable(opts) {
|
|
|
6969
7064
|
return;
|
|
6970
7065
|
}
|
|
6971
7066
|
log.step("Copying your game's source to the public repo\u2026");
|
|
6972
|
-
if (
|
|
7067
|
+
if (await pushWorktree(process.cwd(), data.pushUrl, true, log) !== true) {
|
|
6973
7068
|
process.exitCode = 1;
|
|
6974
7069
|
return;
|
|
6975
7070
|
}
|
|
@@ -7614,7 +7709,16 @@ async function runSave(opts) {
|
|
|
7614
7709
|
process.exitCode = 1;
|
|
7615
7710
|
return;
|
|
7616
7711
|
}
|
|
7617
|
-
|
|
7712
|
+
const saved = await pushWorktree(
|
|
7713
|
+
process.cwd(),
|
|
7714
|
+
data.pushUrl,
|
|
7715
|
+
true,
|
|
7716
|
+
log,
|
|
7717
|
+
WORKSPACE_SOURCE_REF,
|
|
7718
|
+
void 0,
|
|
7719
|
+
true
|
|
7720
|
+
);
|
|
7721
|
+
if (saved !== true) {
|
|
7618
7722
|
process.exitCode = 1;
|
|
7619
7723
|
return;
|
|
7620
7724
|
}
|
|
@@ -7775,8 +7879,8 @@ async function runRollback(opts) {
|
|
|
7775
7879
|
}
|
|
7776
7880
|
|
|
7777
7881
|
// src/commands/generate.ts
|
|
7778
|
-
import
|
|
7779
|
-
import
|
|
7882
|
+
import fs23 from "fs/promises";
|
|
7883
|
+
import path22 from "path";
|
|
7780
7884
|
import { PNG as PNG4 } from "pngjs";
|
|
7781
7885
|
|
|
7782
7886
|
// src/lib/glass.ts
|
|
@@ -8080,7 +8184,7 @@ var isRemoteRef = (value) => /^(https?:\/\/|data:)/i.test(value);
|
|
|
8080
8184
|
async function inlineLocalImage(filePath, flag) {
|
|
8081
8185
|
let bytes;
|
|
8082
8186
|
try {
|
|
8083
|
-
bytes = await
|
|
8187
|
+
bytes = await fs23.readFile(filePath);
|
|
8084
8188
|
} catch {
|
|
8085
8189
|
return { ok: false, error: `Couldn't read the ${flag} file at ${filePath}.` };
|
|
8086
8190
|
}
|
|
@@ -8090,7 +8194,7 @@ async function inlineLocalImage(filePath, flag) {
|
|
|
8090
8194
|
error: `${flag} file is ${(bytes.length / 1048576).toFixed(1)} MB \u2014 over the ~4 MB inline limit. Downscale/compress it first, or pass an asset URL instead.`
|
|
8091
8195
|
};
|
|
8092
8196
|
}
|
|
8093
|
-
const mime = IMAGE_MIME_BY_EXT[
|
|
8197
|
+
const mime = IMAGE_MIME_BY_EXT[path22.extname(filePath).toLowerCase()] ?? "image/png";
|
|
8094
8198
|
return { ok: true, dataUri: `data:${mime};base64,${bytes.toString("base64")}` };
|
|
8095
8199
|
}
|
|
8096
8200
|
var SKYBOX_ENVIRONMENT_SUFFIX = ". The image contains ONLY sky: cloud, atmosphere, light, weather and distant haze at the horizon. Every structure, object, plant and ground surface is outside the frame.";
|
|
@@ -8276,7 +8380,7 @@ async function runGenerate(kind, opts) {
|
|
|
8276
8380
|
let typedPrompt = opts.prompt?.trim();
|
|
8277
8381
|
if (!typedPrompt && kind === "model" && opts.imageUrl) {
|
|
8278
8382
|
const ref = opts.imageUrl.startsWith("data:") ? "local image" : opts.imageUrl;
|
|
8279
|
-
typedPrompt = `from image: ${
|
|
8383
|
+
typedPrompt = `from image: ${path22.basename(ref).slice(0, 120)}`;
|
|
8280
8384
|
}
|
|
8281
8385
|
if (kind === "model" && opts.texture !== void 0 && !MODEL_TEXTURE_TIERS.includes(opts.texture)) {
|
|
8282
8386
|
log.error(`--texture ${opts.texture} is a character texture size. \`genex model\` takes a texture TIER: ${MODEL_TEXTURE_TIERS.join("|")} (default detailed).`);
|
|
@@ -8325,7 +8429,7 @@ async function runGenerate(kind, opts) {
|
|
|
8325
8429
|
return;
|
|
8326
8430
|
}
|
|
8327
8431
|
try {
|
|
8328
|
-
const bytes = await
|
|
8432
|
+
const bytes = await fs23.readFile(opts.inpaintUrl);
|
|
8329
8433
|
opts = { ...opts, inpaintUrl: `data:image/png;base64,${bytes.toString("base64")}` };
|
|
8330
8434
|
} catch {
|
|
8331
8435
|
log.error(`Couldn't read the --inpaint mask at ${opts.inpaintUrl}.`);
|
|
@@ -8519,7 +8623,7 @@ async function reportGlassTerminal(view, outDir, log, json) {
|
|
|
8519
8623
|
return;
|
|
8520
8624
|
}
|
|
8521
8625
|
await recordTerminal(view.id, "completed", files.map((f) => f.url));
|
|
8522
|
-
await
|
|
8626
|
+
await fs23.mkdir(outDir, { recursive: true });
|
|
8523
8627
|
const solved = [];
|
|
8524
8628
|
for (let i = 0; i < files.length; i++) {
|
|
8525
8629
|
const f = files[i];
|
|
@@ -8551,8 +8655,8 @@ async function reportGlassTerminal(view, outDir, log, json) {
|
|
|
8551
8655
|
});
|
|
8552
8656
|
continue;
|
|
8553
8657
|
}
|
|
8554
|
-
const outPath =
|
|
8555
|
-
await
|
|
8658
|
+
const outPath = path22.join(outDir, `glass-${i + 1}.png`);
|
|
8659
|
+
await fs23.writeFile(outPath, PNG4.sync.write(r.png));
|
|
8556
8660
|
solved.push({
|
|
8557
8661
|
path: outPath,
|
|
8558
8662
|
url: f.url,
|
|
@@ -9070,8 +9174,8 @@ function writeJson(value) {
|
|
|
9070
9174
|
}
|
|
9071
9175
|
|
|
9072
9176
|
// src/commands/model-sub.ts
|
|
9073
|
-
import
|
|
9074
|
-
import
|
|
9177
|
+
import fs24 from "fs/promises";
|
|
9178
|
+
import path23 from "path";
|
|
9075
9179
|
var MODEL_SUBCOMMANDS = ["segment", "rig", "animate", "import"];
|
|
9076
9180
|
function apiErrorMessage(data, fallback) {
|
|
9077
9181
|
if (typeof data !== "object" || data === null) return fallback;
|
|
@@ -9086,29 +9190,29 @@ async function importModelFile(args) {
|
|
|
9086
9190
|
const { log } = args;
|
|
9087
9191
|
const filePath = args.filePath.trim();
|
|
9088
9192
|
if (!/\.glb$/i.test(filePath)) {
|
|
9089
|
-
log.error(`\`import\` takes a .glb file (binary glTF 2.0). Export ${
|
|
9193
|
+
log.error(`\`import\` takes a .glb file (binary glTF 2.0). Export ${path23.basename(filePath) || "the model"} as GLB first \u2014 Blender: File \u2192 Export \u2192 glTF 2.0, format "glTF Binary".`);
|
|
9090
9194
|
return null;
|
|
9091
9195
|
}
|
|
9092
9196
|
let bytes;
|
|
9093
9197
|
try {
|
|
9094
|
-
bytes = await
|
|
9198
|
+
bytes = await fs24.readFile(filePath);
|
|
9095
9199
|
} catch {
|
|
9096
9200
|
log.error(`Couldn't read ${filePath}.`);
|
|
9097
9201
|
return null;
|
|
9098
9202
|
}
|
|
9099
9203
|
if (bytes.byteLength > MODEL_IMPORT_MAX_BYTES) {
|
|
9100
|
-
log.error(`${
|
|
9204
|
+
log.error(`${path23.basename(filePath)} is ${(bytes.byteLength / 1e6).toFixed(1)} MB; imports are capped at ${MODEL_IMPORT_MAX_BYTES / 1024 / 1024} MB. Shrink its textures (they are usually the bulk) and try again.`);
|
|
9101
9205
|
return null;
|
|
9102
9206
|
}
|
|
9103
9207
|
if (bytes.byteLength < 12 || bytes.readUInt32LE(0) !== GLB_MAGIC) {
|
|
9104
|
-
log.error(`${
|
|
9208
|
+
log.error(`${path23.basename(filePath)} is not a GLB (no glTF magic). A .gltf + .bin pair must be exported as one binary .glb.`);
|
|
9105
9209
|
return null;
|
|
9106
9210
|
}
|
|
9107
9211
|
const headers = { "Content-Type": "application/json", Authorization: `Bearer ${args.token}` };
|
|
9108
9212
|
const minted = await apiFetch(`${args.apiUrl}/api/generations/import`, {
|
|
9109
9213
|
method: "POST",
|
|
9110
9214
|
headers,
|
|
9111
|
-
body: JSON.stringify({ filename:
|
|
9215
|
+
body: JSON.stringify({ filename: path23.basename(filePath), bytes: bytes.byteLength, contentType: "model/gltf-binary" })
|
|
9112
9216
|
});
|
|
9113
9217
|
if (printedStructuredError(minted)) return null;
|
|
9114
9218
|
if (!minted.ok) {
|
|
@@ -9117,7 +9221,7 @@ async function importModelFile(args) {
|
|
|
9117
9221
|
return null;
|
|
9118
9222
|
}
|
|
9119
9223
|
const { id, uploadUrl, url } = await minted.json();
|
|
9120
|
-
log.dim(` uploading ${
|
|
9224
|
+
log.dim(` uploading ${path23.basename(filePath)} (${(bytes.byteLength / 1e6).toFixed(1)} MB)\u2026`);
|
|
9121
9225
|
const put = await fetch(uploadUrl, {
|
|
9122
9226
|
method: "PUT",
|
|
9123
9227
|
headers: { "Content-Type": "model/gltf-binary", "Content-Length": String(bytes.byteLength) },
|
|
@@ -9265,7 +9369,7 @@ async function runModelAnimate(opts) {
|
|
|
9265
9369
|
}
|
|
9266
9370
|
|
|
9267
9371
|
// src/commands/wait.ts
|
|
9268
|
-
import
|
|
9372
|
+
import path24 from "path";
|
|
9269
9373
|
var TERMINAL2 = /* @__PURE__ */ new Set(["completed", "failed"]);
|
|
9270
9374
|
async function runWait(opts) {
|
|
9271
9375
|
if (opts.all) return runWaitAll(opts);
|
|
@@ -9396,7 +9500,7 @@ async function runWaitAll(opts) {
|
|
|
9396
9500
|
if (v?.status !== "completed" || !v.files?.length) continue;
|
|
9397
9501
|
const local = localDeliveryFor("tools", opts, e.prompt || e.kind);
|
|
9398
9502
|
if (!local) continue;
|
|
9399
|
-
const outDir =
|
|
9503
|
+
const outDir = path24.join(path24.relative(process.cwd(), cwd) || ".", local.outDir);
|
|
9400
9504
|
const target = { kind: e.kind, prompt: local.prompt, id: e.id, outDir };
|
|
9401
9505
|
const missing = await undeliveredFiles(v.files, target);
|
|
9402
9506
|
if (missing.length === 0) continue;
|
|
@@ -9491,8 +9595,8 @@ async function toRow(e, v, cwd) {
|
|
|
9491
9595
|
}
|
|
9492
9596
|
|
|
9493
9597
|
// src/commands/controller.ts
|
|
9494
|
-
import
|
|
9495
|
-
import
|
|
9598
|
+
import fs26 from "fs/promises";
|
|
9599
|
+
import path26 from "path";
|
|
9496
9600
|
|
|
9497
9601
|
// ../../packages/meshy-animation-catalog/src/index.ts
|
|
9498
9602
|
import { createHash } from "crypto";
|
|
@@ -18622,9 +18726,9 @@ function searchMeshyAnimations(query, options = {}) {
|
|
|
18622
18726
|
}
|
|
18623
18727
|
|
|
18624
18728
|
// src/lib/anims.ts
|
|
18625
|
-
import
|
|
18626
|
-
import
|
|
18627
|
-
var ANIMS_DEST =
|
|
18729
|
+
import fs25 from "fs/promises";
|
|
18730
|
+
import path25 from "path";
|
|
18731
|
+
var ANIMS_DEST = path25.join("public", "assets", "anims");
|
|
18628
18732
|
var HIDDEN_TAG = "reference";
|
|
18629
18733
|
async function runAnims(opts) {
|
|
18630
18734
|
const log = createLogger({ quiet: opts.quiet });
|
|
@@ -18640,7 +18744,7 @@ async function runAnims(opts) {
|
|
|
18640
18744
|
printCatalog(log, manifest, selectors);
|
|
18641
18745
|
return;
|
|
18642
18746
|
}
|
|
18643
|
-
const controllerMarker =
|
|
18747
|
+
const controllerMarker = path25.join(root, "src", "controllers", "character");
|
|
18644
18748
|
if (!await exists3(controllerMarker)) {
|
|
18645
18749
|
log.error(
|
|
18646
18750
|
`No character controller in this game (missing ${c.cyan("src/controllers/character/")}).`
|
|
@@ -18649,11 +18753,11 @@ async function runAnims(opts) {
|
|
|
18649
18753
|
process.exitCode = 1;
|
|
18650
18754
|
return;
|
|
18651
18755
|
}
|
|
18652
|
-
const destDir =
|
|
18653
|
-
const gameManifestPath =
|
|
18756
|
+
const destDir = path25.join(root, ANIMS_DEST);
|
|
18757
|
+
const gameManifestPath = path25.join(destDir, "manifest.json");
|
|
18654
18758
|
if (opts.reset) {
|
|
18655
|
-
await
|
|
18656
|
-
log.step(`Cleared ${c.cyan(ANIMS_DEST +
|
|
18759
|
+
await fs25.rm(destDir, { recursive: true, force: true });
|
|
18760
|
+
log.step(`Cleared ${c.cyan(ANIMS_DEST + path25.sep)} (--reset)`);
|
|
18657
18761
|
}
|
|
18658
18762
|
if (selectors.length === 0) {
|
|
18659
18763
|
const installed = await readGameManifest(gameManifestPath);
|
|
@@ -18691,35 +18795,35 @@ async function runAnims(opts) {
|
|
|
18691
18795
|
}
|
|
18692
18796
|
}
|
|
18693
18797
|
const wanted = [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
18694
|
-
const cacheDir =
|
|
18798
|
+
const cacheDir = path25.join(
|
|
18695
18799
|
opts.cacheDir ?? getAnimsCacheDir(),
|
|
18696
18800
|
`${manifest.library}-v${manifest.version}`
|
|
18697
18801
|
);
|
|
18698
|
-
await
|
|
18699
|
-
await
|
|
18802
|
+
await fs25.mkdir(cacheDir, { recursive: true });
|
|
18803
|
+
await fs25.mkdir(destDir, { recursive: true });
|
|
18700
18804
|
const base = getAnimsBase(opts.animsBase);
|
|
18701
18805
|
let installedCount = 0;
|
|
18702
18806
|
let presentCount = 0;
|
|
18703
18807
|
let addedBytes = 0;
|
|
18704
18808
|
const failures = [];
|
|
18705
18809
|
for (const entry of wanted) {
|
|
18706
|
-
const dest =
|
|
18810
|
+
const dest = path25.join(destDir, entry.file);
|
|
18707
18811
|
if (await hasSize(dest, entry.bytes)) {
|
|
18708
18812
|
presentCount++;
|
|
18709
18813
|
continue;
|
|
18710
18814
|
}
|
|
18711
18815
|
try {
|
|
18712
|
-
const cached =
|
|
18816
|
+
const cached = path25.join(cacheDir, entry.file);
|
|
18713
18817
|
if (!await hasSize(cached, entry.bytes)) {
|
|
18714
18818
|
const res = await fetch(base + entry.file);
|
|
18715
18819
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
18716
18820
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
18717
|
-
await
|
|
18821
|
+
await fs25.writeFile(cached, buf);
|
|
18718
18822
|
}
|
|
18719
|
-
await
|
|
18823
|
+
await fs25.copyFile(cached, dest);
|
|
18720
18824
|
installedCount++;
|
|
18721
18825
|
addedBytes += entry.bytes;
|
|
18722
|
-
log.dim(` ${
|
|
18826
|
+
log.dim(` ${path25.join(ANIMS_DEST, entry.file)} (${formatMb(entry.bytes)})`);
|
|
18723
18827
|
} catch (err) {
|
|
18724
18828
|
failures.push(`${entry.name} (${err instanceof Error ? err.message : String(err)})`);
|
|
18725
18829
|
}
|
|
@@ -18735,13 +18839,13 @@ async function runAnims(opts) {
|
|
|
18735
18839
|
version: manifest.version,
|
|
18736
18840
|
clips: [...union].sort((a, b) => a.localeCompare(b))
|
|
18737
18841
|
};
|
|
18738
|
-
await
|
|
18842
|
+
await fs25.writeFile(gameManifestPath, JSON.stringify(gameManifest, null, 2) + "\n");
|
|
18739
18843
|
log.plain("");
|
|
18740
18844
|
const parts = [`${installedCount} clip${installedCount === 1 ? "" : "s"} installed`];
|
|
18741
18845
|
if (presentCount > 0) parts.push(`${presentCount} already present`);
|
|
18742
18846
|
if (bundledSkips > 0) parts.push(`${bundledSkips} already bundled in animation-library.glb`);
|
|
18743
18847
|
log.success(
|
|
18744
|
-
`${parts.join(", ")} \u2192 ${c.cyan(ANIMS_DEST +
|
|
18848
|
+
`${parts.join(", ")} \u2192 ${c.cyan(ANIMS_DEST + path25.sep)}${addedBytes > 0 ? ` (+${formatMb(addedBytes)})` : ""}`
|
|
18745
18849
|
);
|
|
18746
18850
|
for (const [selector, entries] of resolved) {
|
|
18747
18851
|
const names = entries.filter((e) => !coreNames.has(e.name)).map((e) => e.name);
|
|
@@ -18773,8 +18877,8 @@ async function loadManifest(baseOverride) {
|
|
|
18773
18877
|
}
|
|
18774
18878
|
} catch {
|
|
18775
18879
|
}
|
|
18776
|
-
const snapshotPath =
|
|
18777
|
-
const manifest = JSON.parse(await
|
|
18880
|
+
const snapshotPath = path25.join(getTemplatesDir(), "controllers", "assets", "anims-manifest.json");
|
|
18881
|
+
const manifest = JSON.parse(await fs25.readFile(snapshotPath, "utf8"));
|
|
18778
18882
|
return { manifest, source: "snapshot" };
|
|
18779
18883
|
}
|
|
18780
18884
|
function resolveSelectors(manifest, selectors) {
|
|
@@ -18892,21 +18996,21 @@ function printCatalog(log, manifest, selectors) {
|
|
|
18892
18996
|
}
|
|
18893
18997
|
async function readGameManifest(file) {
|
|
18894
18998
|
try {
|
|
18895
|
-
return JSON.parse(await
|
|
18999
|
+
return JSON.parse(await fs25.readFile(file, "utf8"));
|
|
18896
19000
|
} catch {
|
|
18897
19001
|
return null;
|
|
18898
19002
|
}
|
|
18899
19003
|
}
|
|
18900
19004
|
async function hasSize(file, bytes) {
|
|
18901
19005
|
try {
|
|
18902
|
-
return (await
|
|
19006
|
+
return (await fs25.stat(file)).size === bytes;
|
|
18903
19007
|
} catch {
|
|
18904
19008
|
return false;
|
|
18905
19009
|
}
|
|
18906
19010
|
}
|
|
18907
19011
|
async function exists3(p) {
|
|
18908
19012
|
try {
|
|
18909
|
-
await
|
|
19013
|
+
await fs25.access(p);
|
|
18910
19014
|
return true;
|
|
18911
19015
|
} catch {
|
|
18912
19016
|
return false;
|
|
@@ -19107,8 +19211,8 @@ var CONTROLLER_FILE_SETS = {
|
|
|
19107
19211
|
]
|
|
19108
19212
|
}
|
|
19109
19213
|
};
|
|
19110
|
-
var CODE_DEST =
|
|
19111
|
-
var ASSETS_DEST =
|
|
19214
|
+
var CODE_DEST = path26.join("src", "controllers");
|
|
19215
|
+
var ASSETS_DEST = path26.join("public", "assets");
|
|
19112
19216
|
async function runController(opts) {
|
|
19113
19217
|
const log = createLogger({ quiet: opts.quiet });
|
|
19114
19218
|
if (opts.kind?.trim() === "anims") {
|
|
@@ -19125,31 +19229,31 @@ async function runController(opts) {
|
|
|
19125
19229
|
process.exitCode = 1;
|
|
19126
19230
|
return;
|
|
19127
19231
|
}
|
|
19128
|
-
const srcDir =
|
|
19232
|
+
const srcDir = path26.join(getTemplatesDir(), "controllers");
|
|
19129
19233
|
const root = opts.cwd ?? process.cwd();
|
|
19130
19234
|
const set = CONTROLLER_FILE_SETS[kind];
|
|
19131
19235
|
log.plain(c.bold(`genex controller ${kind}`));
|
|
19132
19236
|
log.plain("");
|
|
19133
|
-
log.step(`Installing the ${kind} controller into ${c.cyan(CODE_DEST +
|
|
19237
|
+
log.step(`Installing the ${kind} controller into ${c.cyan(CODE_DEST + path26.sep)}`);
|
|
19134
19238
|
const plan = [
|
|
19135
|
-
...set.code.map((rel) => ({ from: rel, rel:
|
|
19239
|
+
...set.code.map((rel) => ({ from: rel, rel: path26.join(CODE_DEST, rel) })),
|
|
19136
19240
|
...set.assets.map((rel) => ({
|
|
19137
19241
|
from: rel,
|
|
19138
|
-
rel:
|
|
19242
|
+
rel: path26.join(ASSETS_DEST, path26.basename(rel))
|
|
19139
19243
|
}))
|
|
19140
19244
|
];
|
|
19141
19245
|
let copied = 0;
|
|
19142
19246
|
let skipped = 0;
|
|
19143
19247
|
try {
|
|
19144
19248
|
for (const file of plan) {
|
|
19145
|
-
const dest =
|
|
19249
|
+
const dest = path26.join(root, file.rel);
|
|
19146
19250
|
if (!opts.force && await exists4(dest)) {
|
|
19147
19251
|
skipped++;
|
|
19148
19252
|
log.dim(` skipped ${file.rel} (exists \u2014 use --force to overwrite)`);
|
|
19149
19253
|
continue;
|
|
19150
19254
|
}
|
|
19151
|
-
await
|
|
19152
|
-
await
|
|
19255
|
+
await fs26.mkdir(path26.dirname(dest), { recursive: true });
|
|
19256
|
+
await fs26.copyFile(path26.join(srcDir, file.from), dest);
|
|
19153
19257
|
copied++;
|
|
19154
19258
|
log.dim(` ${file.rel}`);
|
|
19155
19259
|
}
|
|
@@ -19202,7 +19306,7 @@ async function runController(opts) {
|
|
|
19202
19306
|
for (const line of set.sketch) {
|
|
19203
19307
|
log.dim(` ${line}`);
|
|
19204
19308
|
}
|
|
19205
|
-
if (kind === "character" && !await exists4(
|
|
19309
|
+
if (kind === "character" && !await exists4(path26.join(root, ASSETS_DEST, "meshy-character.json"))) {
|
|
19206
19310
|
log.plain("");
|
|
19207
19311
|
log.plain(
|
|
19208
19312
|
` ${stepOffset + 3}. This game has no generated character yet \u2014 ${c.cyan(
|
|
@@ -19234,9 +19338,9 @@ async function installMeshyCharacterManifest(args) {
|
|
|
19234
19338
|
throw new Error("The API returned an invalid Meshy character manifest.");
|
|
19235
19339
|
}
|
|
19236
19340
|
assertCompleteMeshyControllerPack(manifest);
|
|
19237
|
-
const destination =
|
|
19238
|
-
await
|
|
19239
|
-
await
|
|
19341
|
+
const destination = path26.join(args.root, ASSETS_DEST, "meshy-character.json");
|
|
19342
|
+
await fs26.mkdir(path26.dirname(destination), { recursive: true });
|
|
19343
|
+
await fs26.writeFile(
|
|
19240
19344
|
destination,
|
|
19241
19345
|
`${JSON.stringify(manifest, null, 2)}
|
|
19242
19346
|
`
|
|
@@ -19381,14 +19485,14 @@ function assertCompleteMeshyControllerPack(manifest) {
|
|
|
19381
19485
|
}
|
|
19382
19486
|
async function installFallbackAvatar(args) {
|
|
19383
19487
|
const { root, srcDir, log } = args;
|
|
19384
|
-
const dest =
|
|
19385
|
-
await
|
|
19386
|
-
await
|
|
19488
|
+
const dest = path26.join(root, ASSETS_DEST, "avatar.vrm");
|
|
19489
|
+
await fs26.mkdir(path26.dirname(dest), { recursive: true });
|
|
19490
|
+
await fs26.copyFile(path26.join(srcDir, "assets", "default-avatar.vrm"), dest);
|
|
19387
19491
|
log.dim(" public/assets/avatar.vrm (fallback avatar \u2014 bundled CC0 default)");
|
|
19388
19492
|
}
|
|
19389
19493
|
async function exists4(p) {
|
|
19390
19494
|
try {
|
|
19391
|
-
await
|
|
19495
|
+
await fs26.access(p);
|
|
19392
19496
|
return true;
|
|
19393
19497
|
} catch {
|
|
19394
19498
|
return false;
|
|
@@ -19396,8 +19500,8 @@ async function exists4(p) {
|
|
|
19396
19500
|
}
|
|
19397
19501
|
|
|
19398
19502
|
// src/commands/character.ts
|
|
19399
|
-
import
|
|
19400
|
-
import
|
|
19503
|
+
import fs27 from "fs/promises";
|
|
19504
|
+
import path27 from "path";
|
|
19401
19505
|
function exactAnimation(selector) {
|
|
19402
19506
|
const trimmed = selector.trim();
|
|
19403
19507
|
if (/^-?\d+$/.test(trimmed)) return animationById(Number(trimmed));
|
|
@@ -19477,7 +19581,7 @@ function showAmbiguity(selector, candidates, log, json = false) {
|
|
|
19477
19581
|
}
|
|
19478
19582
|
process.exitCode = 1;
|
|
19479
19583
|
}
|
|
19480
|
-
var INSTALLED_MANIFEST =
|
|
19584
|
+
var INSTALLED_MANIFEST = path27.join("public", "assets", "meshy-character.json");
|
|
19481
19585
|
async function resolveAdoptTarget(selector) {
|
|
19482
19586
|
const trimmed = selector?.trim();
|
|
19483
19587
|
if (trimmed && !trimmed.endsWith(".json")) {
|
|
@@ -19486,7 +19590,7 @@ async function resolveAdoptTarget(selector) {
|
|
|
19486
19590
|
const file = trimmed ?? INSTALLED_MANIFEST;
|
|
19487
19591
|
let raw;
|
|
19488
19592
|
try {
|
|
19489
|
-
raw = await
|
|
19593
|
+
raw = await fs27.readFile(file, "utf8");
|
|
19490
19594
|
} catch {
|
|
19491
19595
|
return {
|
|
19492
19596
|
ok: false,
|
|
@@ -19607,7 +19711,7 @@ async function runCharacterImport(opts) {
|
|
|
19607
19711
|
opts,
|
|
19608
19712
|
ctx,
|
|
19609
19713
|
kind: "character",
|
|
19610
|
-
prompt: `Import ${
|
|
19714
|
+
prompt: `Import ${path27.basename(filePath)} as a rigged character`,
|
|
19611
19715
|
createPath: "/api/characters/import",
|
|
19612
19716
|
body,
|
|
19613
19717
|
quote: price,
|
|
@@ -20087,22 +20191,22 @@ async function context2(opts) {
|
|
|
20087
20191
|
const project = await readProject();
|
|
20088
20192
|
return { token, apiUrl: getApiUrl(opts.apiUrl ?? project?.apiUrl) };
|
|
20089
20193
|
}
|
|
20090
|
-
async function readVideo(
|
|
20194
|
+
async function readVideo(path33, log) {
|
|
20091
20195
|
let bytes;
|
|
20092
20196
|
try {
|
|
20093
|
-
bytes = await readFile(
|
|
20197
|
+
bytes = await readFile(path33);
|
|
20094
20198
|
} catch {
|
|
20095
|
-
log.error(`Can't read ${
|
|
20199
|
+
log.error(`Can't read ${path33}.`);
|
|
20096
20200
|
return null;
|
|
20097
20201
|
}
|
|
20098
20202
|
if (bytes.byteLength > MAX_VIDEO_BYTES) {
|
|
20099
|
-
log.error(`${basename(
|
|
20203
|
+
log.error(`${basename(path33)} is ${(bytes.byteLength / 1e6).toFixed(0)} MB \u2014 the limit is ${MAX_VIDEO_BYTES / 1e6} MB.`);
|
|
20100
20204
|
return null;
|
|
20101
20205
|
}
|
|
20102
20206
|
return bytes;
|
|
20103
20207
|
}
|
|
20104
|
-
async function uploadVideo(apiUrl, token, characterId,
|
|
20105
|
-
const contentType = /\.mov$/i.test(
|
|
20208
|
+
async function uploadVideo(apiUrl, token, characterId, path33, bytes, log) {
|
|
20209
|
+
const contentType = /\.mov$/i.test(path33) ? "video/quicktime" : "video/mp4";
|
|
20106
20210
|
const minted = await apiFetch(
|
|
20107
20211
|
`${apiUrl}/api/characters/${encodeURIComponent(characterId)}/motions/video-url`,
|
|
20108
20212
|
{
|
|
@@ -20117,7 +20221,7 @@ async function uploadVideo(apiUrl, token, characterId, path32, bytes, log) {
|
|
|
20117
20221
|
return null;
|
|
20118
20222
|
}
|
|
20119
20223
|
const { uploadUrl, videoUrl } = await minted.json();
|
|
20120
|
-
log.dim(` uploading ${basename(
|
|
20224
|
+
log.dim(` uploading ${basename(path33)} (${(bytes.byteLength / 1e6).toFixed(1)} MB)\u2026`);
|
|
20121
20225
|
const put = await fetch(uploadUrl, {
|
|
20122
20226
|
method: "PUT",
|
|
20123
20227
|
headers: { "Content-Type": contentType, "Content-Length": String(bytes.byteLength) },
|
|
@@ -20433,8 +20537,8 @@ function rank(items, query) {
|
|
|
20433
20537
|
}
|
|
20434
20538
|
|
|
20435
20539
|
// src/commands/motion.ts
|
|
20436
|
-
import
|
|
20437
|
-
import
|
|
20540
|
+
import fs28 from "fs/promises";
|
|
20541
|
+
import path28 from "path";
|
|
20438
20542
|
|
|
20439
20543
|
// src/lib/motion/npz.ts
|
|
20440
20544
|
import zlib from "zlib";
|
|
@@ -21685,7 +21789,7 @@ async function motionGen(opts, log) {
|
|
|
21685
21789
|
}
|
|
21686
21790
|
if (opts.constraintsPath !== void 0) {
|
|
21687
21791
|
try {
|
|
21688
|
-
const raw = await
|
|
21792
|
+
const raw = await fs28.readFile(opts.constraintsPath, "utf8");
|
|
21689
21793
|
generationOptions.constraints = JSON.parse(raw);
|
|
21690
21794
|
} catch {
|
|
21691
21795
|
log.error(`Couldn't read the --constraints JSON at ${opts.constraintsPath}.`);
|
|
@@ -21709,10 +21813,10 @@ async function motionGen(opts, log) {
|
|
|
21709
21813
|
async function expandTakes(selectors) {
|
|
21710
21814
|
const out = [];
|
|
21711
21815
|
for (const sel of selectors) {
|
|
21712
|
-
const st = await
|
|
21816
|
+
const st = await fs28.stat(sel).catch(() => null);
|
|
21713
21817
|
if (st?.isDirectory()) {
|
|
21714
|
-
const names = await
|
|
21715
|
-
for (const n of names.sort()) if (n.endsWith(".npz")) out.push(
|
|
21818
|
+
const names = await fs28.readdir(sel);
|
|
21819
|
+
for (const n of names.sort()) if (n.endsWith(".npz")) out.push(path28.join(sel, n));
|
|
21716
21820
|
} else if (st?.isFile()) {
|
|
21717
21821
|
out.push(sel);
|
|
21718
21822
|
} else {
|
|
@@ -21747,7 +21851,7 @@ async function motionVerify(opts, log) {
|
|
|
21747
21851
|
let gates = DEFAULT_GATES;
|
|
21748
21852
|
if (opts.gatesPath) {
|
|
21749
21853
|
try {
|
|
21750
|
-
gates = mergeGates(DEFAULT_GATES, JSON.parse(await
|
|
21854
|
+
gates = mergeGates(DEFAULT_GATES, JSON.parse(await fs28.readFile(opts.gatesPath, "utf8")));
|
|
21751
21855
|
} catch {
|
|
21752
21856
|
log.error(`Couldn't read the --gates JSON at ${opts.gatesPath}.`);
|
|
21753
21857
|
process.exitCode = 1;
|
|
@@ -21769,9 +21873,9 @@ async function motionVerify(opts, log) {
|
|
|
21769
21873
|
}
|
|
21770
21874
|
const reports = [];
|
|
21771
21875
|
for (const file of files) {
|
|
21772
|
-
const stem =
|
|
21876
|
+
const stem = path28.basename(file).replace(/\.npz$/, "");
|
|
21773
21877
|
try {
|
|
21774
|
-
reports.push(analyzeTake(stem, await
|
|
21878
|
+
reports.push(analyzeTake(stem, await fs28.readFile(file), gates));
|
|
21775
21879
|
} catch (err) {
|
|
21776
21880
|
reports.push({
|
|
21777
21881
|
take: stem,
|
|
@@ -21809,7 +21913,7 @@ async function motionCompile(opts, log) {
|
|
|
21809
21913
|
let cfg = DEFAULT_MOTION_CONFIG;
|
|
21810
21914
|
if (opts.configPath) {
|
|
21811
21915
|
try {
|
|
21812
|
-
const patch = JSON.parse(await
|
|
21916
|
+
const patch = JSON.parse(await fs28.readFile(opts.configPath, "utf8"));
|
|
21813
21917
|
cfg = { ...DEFAULT_MOTION_CONFIG, ...patch, idleLoop: { ...DEFAULT_MOTION_CONFIG.idleLoop, ...patch.idleLoop } };
|
|
21814
21918
|
} catch {
|
|
21815
21919
|
log.error(`Couldn't read the --config JSON at ${opts.configPath}.`);
|
|
@@ -21827,16 +21931,16 @@ async function motionCompile(opts, log) {
|
|
|
21827
21931
|
}
|
|
21828
21932
|
const inputs = [];
|
|
21829
21933
|
for (const file of files) {
|
|
21830
|
-
const stem =
|
|
21934
|
+
const stem = path28.basename(file).replace(/\.npz$/, "");
|
|
21831
21935
|
try {
|
|
21832
|
-
inputs.push({ stem, take: loadTake(await
|
|
21936
|
+
inputs.push({ stem, take: loadTake(await fs28.readFile(file)) });
|
|
21833
21937
|
} catch (err) {
|
|
21834
21938
|
log.error(`${stem}: ${err instanceof Error ? err.message : String(err)}`);
|
|
21835
21939
|
process.exitCode = 1;
|
|
21836
21940
|
return;
|
|
21837
21941
|
}
|
|
21838
21942
|
}
|
|
21839
|
-
const setName = opts.set ??
|
|
21943
|
+
const setName = opts.set ?? path28.basename(opts.out).replace(/\.json$/, "");
|
|
21840
21944
|
let result;
|
|
21841
21945
|
try {
|
|
21842
21946
|
result = compileSet(inputs, setName, cfg);
|
|
@@ -21851,9 +21955,9 @@ async function motionCompile(opts, log) {
|
|
|
21851
21955
|
process.exitCode = 1;
|
|
21852
21956
|
return;
|
|
21853
21957
|
}
|
|
21854
|
-
await
|
|
21958
|
+
await fs28.mkdir(path28.dirname(path28.resolve(opts.out)), { recursive: true });
|
|
21855
21959
|
const json = JSON.stringify(result.data);
|
|
21856
|
-
await
|
|
21960
|
+
await fs28.writeFile(opts.out, json);
|
|
21857
21961
|
if (opts.json) {
|
|
21858
21962
|
writeJson({ out: opts.out, set: setName, tracks: Object.keys(result.data.gaits), bytes: json.length });
|
|
21859
21963
|
return;
|
|
@@ -21871,9 +21975,9 @@ var MOTION_RUNTIME_FILES = [
|
|
|
21871
21975
|
var MOTION_PRESETS = {
|
|
21872
21976
|
rifle: ["sets/rifle.json", "sets/jumps.json"]
|
|
21873
21977
|
};
|
|
21874
|
-
var MOTION_DEST =
|
|
21978
|
+
var MOTION_DEST = path28.join("src", "motion");
|
|
21875
21979
|
async function motionInstall(opts, log) {
|
|
21876
|
-
const srcDir =
|
|
21980
|
+
const srcDir = path28.join(getTemplatesDir(), "motion");
|
|
21877
21981
|
const root = opts.cwd ?? process.cwd();
|
|
21878
21982
|
const preset = opts.set;
|
|
21879
21983
|
if (preset !== void 0 && !MOTION_PRESETS[preset]) {
|
|
@@ -21884,21 +21988,21 @@ async function motionInstall(opts, log) {
|
|
|
21884
21988
|
const files = [...MOTION_RUNTIME_FILES, ...preset ? MOTION_PRESETS[preset] : []];
|
|
21885
21989
|
log.plain(c.bold(`genex motion install${preset ? ` --set ${preset}` : ""}`));
|
|
21886
21990
|
log.plain("");
|
|
21887
|
-
log.step(`Vendoring the motion runtime into ${c.cyan(MOTION_DEST +
|
|
21991
|
+
log.step(`Vendoring the motion runtime into ${c.cyan(MOTION_DEST + path28.sep)}`);
|
|
21888
21992
|
let copied = 0, skipped = 0;
|
|
21889
21993
|
try {
|
|
21890
21994
|
for (const rel of files) {
|
|
21891
|
-
const dest =
|
|
21892
|
-
const exists5 = await
|
|
21995
|
+
const dest = path28.join(root, MOTION_DEST, rel);
|
|
21996
|
+
const exists5 = await fs28.access(dest).then(() => true, () => false);
|
|
21893
21997
|
if (!opts.force && exists5) {
|
|
21894
21998
|
skipped++;
|
|
21895
|
-
log.dim(` skipped ${
|
|
21999
|
+
log.dim(` skipped ${path28.join(MOTION_DEST, rel)} (exists \u2014 use --force to overwrite)`);
|
|
21896
22000
|
continue;
|
|
21897
22001
|
}
|
|
21898
|
-
await
|
|
21899
|
-
await
|
|
22002
|
+
await fs28.mkdir(path28.dirname(dest), { recursive: true });
|
|
22003
|
+
await fs28.copyFile(path28.join(srcDir, rel), dest);
|
|
21900
22004
|
copied++;
|
|
21901
|
-
log.dim(` ${
|
|
22005
|
+
log.dim(` ${path28.join(MOTION_DEST, rel)}`);
|
|
21902
22006
|
}
|
|
21903
22007
|
} catch (err) {
|
|
21904
22008
|
log.error(`Copy failed: ${String(err)}`);
|
|
@@ -21939,7 +22043,7 @@ async function motionConstraints(opts, log) {
|
|
|
21939
22043
|
}
|
|
21940
22044
|
const doc = directionConstraint(dir, speed, duration);
|
|
21941
22045
|
const out = opts.out ?? "constraints.json";
|
|
21942
|
-
await
|
|
22046
|
+
await fs28.writeFile(out, JSON.stringify(doc));
|
|
21943
22047
|
if (opts.json) {
|
|
21944
22048
|
writeJson({ out, dir: dirName, speed, duration, waypoints: doc[0].frame_indices.length });
|
|
21945
22049
|
return;
|
|
@@ -21976,14 +22080,14 @@ async function runMotion(opts) {
|
|
|
21976
22080
|
}
|
|
21977
22081
|
|
|
21978
22082
|
// src/commands/blender.ts
|
|
21979
|
-
import
|
|
21980
|
-
import
|
|
22083
|
+
import fs29 from "fs/promises";
|
|
22084
|
+
import path29 from "path";
|
|
21981
22085
|
var SUBS2 = ["demo", "exec", "snap", "scene", "import", "export", "reset", "mcp", "serve", "seat", "release"];
|
|
21982
22086
|
var DEFAULT_OUT_DIR = "assets/blender";
|
|
21983
22087
|
async function writeB64(dir, name, b64) {
|
|
21984
|
-
await
|
|
21985
|
-
const p =
|
|
21986
|
-
await
|
|
22088
|
+
await fs29.mkdir(dir, { recursive: true });
|
|
22089
|
+
const p = path29.join(dir, name);
|
|
22090
|
+
await fs29.writeFile(p, Buffer.from(b64, "base64"));
|
|
21987
22091
|
return p;
|
|
21988
22092
|
}
|
|
21989
22093
|
function reportScene(log, s) {
|
|
@@ -22067,7 +22171,7 @@ async function runBlender(opts) {
|
|
|
22067
22171
|
log.plain(rest.join("\n"));
|
|
22068
22172
|
return 1;
|
|
22069
22173
|
}
|
|
22070
|
-
const outDir =
|
|
22174
|
+
const outDir = path29.resolve(opts.outDir ?? DEFAULT_OUT_DIR);
|
|
22071
22175
|
const mode = opts.mode;
|
|
22072
22176
|
if (mode !== void 0 && !isRenderMode(mode)) {
|
|
22073
22177
|
log.error(`Unknown --mode ${mode}. Use one of: ${RENDER_MODES.join(", ")}.`);
|
|
@@ -22107,14 +22211,14 @@ async function runBlender(opts) {
|
|
|
22107
22211
|
return 0;
|
|
22108
22212
|
}
|
|
22109
22213
|
case "export": {
|
|
22110
|
-
const target = opts.out ??
|
|
22214
|
+
const target = opts.out ?? path29.join(outDir, "scene.glb");
|
|
22111
22215
|
const r = await blenderCall(base, "/export", { path: "/tmp/genex-export.glb" });
|
|
22112
22216
|
if (!r.glbBase64) {
|
|
22113
22217
|
log.error(`/export answered with no GLB bytes${r.uploaded ? " (it was uploaded, not inlined)" : ""}`);
|
|
22114
22218
|
return 1;
|
|
22115
22219
|
}
|
|
22116
|
-
await
|
|
22117
|
-
await
|
|
22220
|
+
await fs29.mkdir(path29.dirname(target), { recursive: true });
|
|
22221
|
+
await fs29.writeFile(target, Buffer.from(r.glbBase64, "base64"));
|
|
22118
22222
|
log.success(`Exported ${r.bytes ?? 0} bytes`);
|
|
22119
22223
|
log.plain(` ${c.cyan(target)}`);
|
|
22120
22224
|
return 0;
|
|
@@ -22147,12 +22251,12 @@ async function runBlender(opts) {
|
|
|
22147
22251
|
return 1;
|
|
22148
22252
|
}
|
|
22149
22253
|
try {
|
|
22150
|
-
script = await
|
|
22254
|
+
script = await fs29.readFile(opts.input, "utf8");
|
|
22151
22255
|
} catch {
|
|
22152
22256
|
log.error(`Can't read ${opts.input}`);
|
|
22153
22257
|
return 1;
|
|
22154
22258
|
}
|
|
22155
|
-
label =
|
|
22259
|
+
label = path29.basename(opts.input);
|
|
22156
22260
|
}
|
|
22157
22261
|
const r = await blenderCall(base, "/exec", { script, ...mode ? { mode } : {}, sheet: { formats: SHEET_FORMATS } });
|
|
22158
22262
|
if (r.stdout?.trim()) log.plain(r.stdout.trimEnd());
|
|
@@ -22271,9 +22375,9 @@ print(f"castle: {n} objects")
|
|
|
22271
22375
|
`;
|
|
22272
22376
|
|
|
22273
22377
|
// src/commands/asset-new.ts
|
|
22274
|
-
import
|
|
22378
|
+
import fs30 from "fs";
|
|
22275
22379
|
import fsp from "fs/promises";
|
|
22276
|
-
import
|
|
22380
|
+
import path30 from "path";
|
|
22277
22381
|
import { pathToFileURL } from "url";
|
|
22278
22382
|
var EXTRA_FILES = [
|
|
22279
22383
|
"genex-asset.example.json",
|
|
@@ -22367,7 +22471,7 @@ Every milestone ends with \`npm run verify\`, then \`npx genex preview --no-buil
|
|
|
22367
22471
|
}
|
|
22368
22472
|
async function runAssetNew(options) {
|
|
22369
22473
|
const log = createLogger();
|
|
22370
|
-
const cwd = options.dir ?
|
|
22474
|
+
const cwd = options.dir ? path30.resolve(options.dir) : process.cwd();
|
|
22371
22475
|
const slug = options.assetSlug;
|
|
22372
22476
|
if (!slug) {
|
|
22373
22477
|
log.error('Usage: genex asset new <slug> [--title "<Name>"] [--dims WxDxH] [--tri-band LOW-HIGH] [--holder "<your name>"] [--out <dir>]');
|
|
@@ -22377,14 +22481,14 @@ async function runAssetNew(options) {
|
|
|
22377
22481
|
log.error(`The slug must be kebab-case (letters, digits, dashes), got: ${slug}`);
|
|
22378
22482
|
return 1;
|
|
22379
22483
|
}
|
|
22380
|
-
const templateDir =
|
|
22381
|
-
if (!
|
|
22484
|
+
const templateDir = path30.join(getTemplatesDir(), "asset-viewer");
|
|
22485
|
+
if (!fs30.existsSync(templateDir)) {
|
|
22382
22486
|
log.error(`Vendored asset-viewer template not found at ${templateDir} \u2014 reinstall the CLI.`);
|
|
22383
22487
|
return 1;
|
|
22384
22488
|
}
|
|
22385
|
-
const manifestTools = await import(pathToFileURL(
|
|
22489
|
+
const manifestTools = await import(pathToFileURL(path30.join(templateDir, "tools", "emit-manifest.mjs")).href);
|
|
22386
22490
|
const { SHARED_FILES, PARITY_FILENAME, hashSharedFiles, pascalCase } = manifestTools;
|
|
22387
|
-
const lockPath =
|
|
22491
|
+
const lockPath = path30.join(templateDir, "shared-files.sha256.json");
|
|
22388
22492
|
const lock = JSON.parse(await fsp.readFile(lockPath, "utf8"));
|
|
22389
22493
|
const actual = hashSharedFiles(templateDir);
|
|
22390
22494
|
const drifted = SHARED_FILES.filter((rel) => lock.files[rel] !== actual[rel]);
|
|
@@ -22397,8 +22501,8 @@ async function runAssetNew(options) {
|
|
|
22397
22501
|
const triBand = parseBand(options.triBand ?? "500-8000");
|
|
22398
22502
|
const holder = options.holder?.trim() || HOLDER_PLACEHOLDER;
|
|
22399
22503
|
const year = (/* @__PURE__ */ new Date()).getUTCFullYear();
|
|
22400
|
-
const outDir =
|
|
22401
|
-
if (
|
|
22504
|
+
const outDir = path30.resolve(cwd, options.out ?? slug);
|
|
22505
|
+
if (fs30.existsSync(outDir) && fs30.readdirSync(outDir).length > 0 && !options.force) {
|
|
22402
22506
|
log.error(`${outDir} already holds files. Nothing was touched \u2014 pass --force to write into it anyway.`);
|
|
22403
22507
|
return 1;
|
|
22404
22508
|
}
|
|
@@ -22426,24 +22530,24 @@ async function runAssetNew(options) {
|
|
|
22426
22530
|
};
|
|
22427
22531
|
await fsp.mkdir(outDir, { recursive: true });
|
|
22428
22532
|
for (const rel of [...SHARED_FILES, ...EXTRA_FILES]) {
|
|
22429
|
-
const to =
|
|
22430
|
-
await fsp.mkdir(
|
|
22431
|
-
await fsp.copyFile(
|
|
22533
|
+
const to = path30.join(outDir, rel);
|
|
22534
|
+
await fsp.mkdir(path30.dirname(to), { recursive: true });
|
|
22535
|
+
await fsp.copyFile(path30.join(templateDir, rel), to);
|
|
22432
22536
|
}
|
|
22433
|
-
const pkg = fillTemplate(await fsp.readFile(
|
|
22537
|
+
const pkg = fillTemplate(await fsp.readFile(path30.join(templateDir, "package.json"), "utf8"), {
|
|
22434
22538
|
slug,
|
|
22435
22539
|
name,
|
|
22436
22540
|
version
|
|
22437
22541
|
});
|
|
22438
|
-
await fsp.writeFile(
|
|
22439
|
-
await fsp.writeFile(
|
|
22440
|
-
await fsp.writeFile(
|
|
22542
|
+
await fsp.writeFile(path30.join(outDir, "package.json"), pkg, "utf8");
|
|
22543
|
+
await fsp.writeFile(path30.join(outDir, "asset.config.json"), JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
22544
|
+
await fsp.writeFile(path30.join(outDir, ".gitignore"), GITIGNORE, "utf8");
|
|
22441
22545
|
await fsp.writeFile(
|
|
22442
|
-
|
|
22546
|
+
path30.join(outDir, "DESIGN.md"),
|
|
22443
22547
|
designDoc({ name, slug, sizeMeters, triBand, holder }),
|
|
22444
22548
|
"utf8"
|
|
22445
22549
|
);
|
|
22446
|
-
const placeholder = await fsp.readFile(
|
|
22550
|
+
const placeholder = await fsp.readFile(path30.join(templateDir, "src", "asset", "PLACEHOLDER.ts"), "utf8");
|
|
22447
22551
|
const seeded = seedAssetSource(placeholder, {
|
|
22448
22552
|
slug,
|
|
22449
22553
|
name,
|
|
@@ -22454,8 +22558,8 @@ async function runAssetNew(options) {
|
|
|
22454
22558
|
pascalCase
|
|
22455
22559
|
});
|
|
22456
22560
|
const assetFilename = `${slug.replace(/-([a-z0-9])/g, (_, ch) => ch.toUpperCase())}.ts`;
|
|
22457
|
-
await fsp.mkdir(
|
|
22458
|
-
await fsp.writeFile(
|
|
22561
|
+
await fsp.mkdir(path30.join(outDir, "src", "asset"), { recursive: true });
|
|
22562
|
+
await fsp.writeFile(path30.join(outDir, "src", "asset", assetFilename), seeded, "utf8");
|
|
22459
22563
|
const copied = hashSharedFiles(outDir);
|
|
22460
22564
|
const mismatched = SHARED_FILES.filter((rel) => copied[rel] !== lock.files[rel]);
|
|
22461
22565
|
if (mismatched.length) {
|
|
@@ -22463,7 +22567,7 @@ async function runAssetNew(options) {
|
|
|
22463
22567
|
return 1;
|
|
22464
22568
|
}
|
|
22465
22569
|
await fsp.writeFile(
|
|
22466
|
-
|
|
22570
|
+
path30.join(outDir, PARITY_FILENAME),
|
|
22467
22571
|
JSON.stringify(
|
|
22468
22572
|
{
|
|
22469
22573
|
note: "Recorded by `genex asset new`. Gate G13 recomputes these; do not hand-edit the viewer.",
|
|
@@ -22507,10 +22611,10 @@ async function runAssetNew(options) {
|
|
|
22507
22611
|
}
|
|
22508
22612
|
|
|
22509
22613
|
// src/commands/tools.ts
|
|
22510
|
-
import
|
|
22614
|
+
import path32 from "path";
|
|
22511
22615
|
|
|
22512
22616
|
// src/commands/doctor.ts
|
|
22513
|
-
import
|
|
22617
|
+
import path31 from "path";
|
|
22514
22618
|
var LANE_ORDER = [
|
|
22515
22619
|
"model",
|
|
22516
22620
|
"image",
|
|
@@ -22791,7 +22895,7 @@ async function fetchLegalStatus(apiUrl, token) {
|
|
|
22791
22895
|
}
|
|
22792
22896
|
async function firstSkillsMarker() {
|
|
22793
22897
|
for (const target of resolveAgentTargets()) {
|
|
22794
|
-
const marker = await readSkillsMarker(
|
|
22898
|
+
const marker = await readSkillsMarker(path31.join(target.baseDir, "skills"));
|
|
22795
22899
|
if (marker) return marker;
|
|
22796
22900
|
}
|
|
22797
22901
|
return null;
|
|
@@ -22842,8 +22946,8 @@ async function runTools(opts) {
|
|
|
22842
22946
|
let totalNew = 0;
|
|
22843
22947
|
let totalUpdated = 0;
|
|
22844
22948
|
for (const t of targets) {
|
|
22845
|
-
const dest =
|
|
22846
|
-
const { copied, updated } = await copyTemplates(
|
|
22949
|
+
const dest = path32.join(t.baseDir, "skills");
|
|
22950
|
+
const { copied, updated } = await copyTemplates(path32.join(templatesDir, "skills"), dest, {
|
|
22847
22951
|
filter: (rel) => skillFamilyFilter("tools")(`skills/${rel}`)
|
|
22848
22952
|
});
|
|
22849
22953
|
await pruneRemovedSkills(dest, log);
|