makefx 0.2.5 → 0.2.6

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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/makefx.mjs +288 -6
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -176,4 +176,4 @@ Run `makefx --help` or `makefx help <command>` for command-specific options.
176
176
  - Media generation guide: https://makefx.app/docs/media-playbooks
177
177
  - Source: https://github.com/krasnoperov/inventory
178
178
 
179
- Version: 0.2.5
179
+ Version: 0.2.6
package/makefx.mjs CHANGED
@@ -4,9 +4,10 @@ import process$1 from "node:process";
4
4
  import { createHash, randomBytes } from "node:crypto";
5
5
  import http from "node:http";
6
6
  import { spawn } from "node:child_process";
7
- import { mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
7
+ import { mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
8
8
  import os from "node:os";
9
9
  import path from "node:path";
10
+ import { createReadStream } from "node:fs";
10
11
  import https from "node:https";
11
12
  //#region \0rolldown/runtime.js
12
13
  var __create = Object.create;
@@ -5160,6 +5161,167 @@ function resolveMediaType(ext, requestedMediaKind) {
5160
5161
  return mediaType;
5161
5162
  }
5162
5163
  //#endregion
5164
+ //#region src/shared/websocket-types.ts
5165
+ var DEFAULT_MEDIA_KIND = "image";
5166
+ //#endregion
5167
+ //#region src/cli/lib/mirror-store.ts
5168
+ var MIRROR_REGISTRY_VERSION = 1;
5169
+ var MIRROR_REGISTRY_FILE = "mirrors.json";
5170
+ function getMirrorRegistryPath(projectRoot = process.cwd()) {
5171
+ return path.join(projectRoot, ".inventory", MIRROR_REGISTRY_FILE);
5172
+ }
5173
+ async function readMirrorRegistry(projectRoot = process.cwd()) {
5174
+ const registryPath = getMirrorRegistryPath(projectRoot);
5175
+ let raw;
5176
+ try {
5177
+ raw = await readFile(registryPath, "utf8");
5178
+ } catch (error) {
5179
+ if (error.code === "ENOENT") return emptyRegistry();
5180
+ throw error;
5181
+ }
5182
+ let parsed;
5183
+ try {
5184
+ parsed = JSON.parse(raw);
5185
+ } catch {
5186
+ throw new Error(`Mirror registry is not valid JSON: ${registryPath}. Move it aside or delete it so the CLI can recreate it.`);
5187
+ }
5188
+ if (!isMirrorRegistry(parsed)) throw new Error(`Mirror registry has an unsupported format: ${registryPath}`);
5189
+ return parsed;
5190
+ }
5191
+ async function writeMirrorRegistry(registry, projectRoot = process.cwd()) {
5192
+ const registryPath = getMirrorRegistryPath(projectRoot);
5193
+ await mkdir(path.dirname(registryPath), { recursive: true });
5194
+ const tempPath = `${registryPath}.${process.pid}.${Date.now()}.tmp`;
5195
+ await writeFile(tempPath, `${JSON.stringify(registry, null, 2)}\n`, "utf8");
5196
+ await rename(tempPath, registryPath);
5197
+ return registryPath;
5198
+ }
5199
+ async function fingerprintFile(filePath) {
5200
+ const fileStat = await stat(filePath);
5201
+ if (!fileStat.isFile()) throw new Error(`Mirror source is not a file: ${filePath}`);
5202
+ const hash = createHash("sha256");
5203
+ await new Promise((resolve, reject) => {
5204
+ const stream = createReadStream(filePath);
5205
+ stream.on("data", (chunk) => hash.update(chunk));
5206
+ stream.on("error", reject);
5207
+ stream.on("end", resolve);
5208
+ });
5209
+ return {
5210
+ sha256: hash.digest("hex"),
5211
+ sizeBytes: fileStat.size
5212
+ };
5213
+ }
5214
+ async function resolveMirrorForFile(input) {
5215
+ const projectRoot = input.projectRoot ?? process.cwd();
5216
+ const registry = await readMirrorRegistry(projectRoot);
5217
+ const fingerprint = await fingerprintFile(input.filePath);
5218
+ const pathAlias = toMirrorPathAlias(input.filePath, projectRoot);
5219
+ const mediaKind = input.mediaKind ?? "image";
5220
+ return {
5221
+ fingerprint,
5222
+ digestEntry: findMirrorByDigest(registry, {
5223
+ ...input,
5224
+ mediaKind,
5225
+ ...fingerprint
5226
+ }),
5227
+ pathEntry: findMirrorByPath(registry, {
5228
+ ...input,
5229
+ mediaKind,
5230
+ pathAlias
5231
+ }),
5232
+ pathAlias
5233
+ };
5234
+ }
5235
+ async function recordMirrorForFile(input) {
5236
+ const projectRoot = input.projectRoot ?? process.cwd();
5237
+ const registry = await readMirrorRegistry(projectRoot);
5238
+ const fingerprint = await fingerprintFile(input.filePath);
5239
+ const pathAlias = toMirrorPathAlias(input.filePath, projectRoot);
5240
+ const mediaKind = input.mediaKind ?? "image";
5241
+ const now = (/* @__PURE__ */ new Date()).toISOString();
5242
+ movePathAliasToCurrentMirror(registry, {
5243
+ ...input,
5244
+ mediaKind,
5245
+ pathAlias
5246
+ });
5247
+ const existing = findMirrorByDigest(registry, {
5248
+ ...input,
5249
+ mediaKind,
5250
+ ...fingerprint
5251
+ });
5252
+ const entry = {
5253
+ version: MIRROR_REGISTRY_VERSION,
5254
+ baseUrl: input.baseUrl,
5255
+ ...input.environment ? { environment: input.environment } : {},
5256
+ spaceId: input.spaceId,
5257
+ sha256: fingerprint.sha256,
5258
+ sizeBytes: fingerprint.sizeBytes,
5259
+ paths: mergePathAliases(existing?.paths ?? [], pathAlias),
5260
+ assetId: input.assetId,
5261
+ variantId: input.variantId,
5262
+ mediaKind,
5263
+ mediaKey: input.mediaKey ?? null,
5264
+ updatedAt: now
5265
+ };
5266
+ if (existing) {
5267
+ const index = registry.entries.indexOf(existing);
5268
+ registry.entries[index] = entry;
5269
+ } else registry.entries.push(entry);
5270
+ await writeMirrorRegistry(registry, projectRoot);
5271
+ return entry;
5272
+ }
5273
+ function describeMirrorMismatch(filePath, entry) {
5274
+ return [
5275
+ `Local reference changed since it was mirrored: ${filePath}`,
5276
+ `Previously mirrored to variant ${entry.variantId} in space ${entry.spaceId}.`,
5277
+ "Use the existing variant ID explicitly, restore the original file, or upload/register a new reference intentionally."
5278
+ ].join(" ");
5279
+ }
5280
+ function findMirrorByDigest(registry, input) {
5281
+ return registry.entries.find((entry) => entry.baseUrl === input.baseUrl && entry.spaceId === input.spaceId && entry.sha256 === input.sha256 && entry.sizeBytes === input.sizeBytes && entry.mediaKind === input.mediaKind && environmentMatches(entry.environment, input.environment));
5282
+ }
5283
+ function findMirrorByPath(registry, input) {
5284
+ return registry.entries.find((entry) => entry.baseUrl === input.baseUrl && entry.spaceId === input.spaceId && entry.paths.includes(input.pathAlias) && entry.mediaKind === input.mediaKind && environmentMatches(entry.environment, input.environment));
5285
+ }
5286
+ function environmentMatches(entryEnvironment, inputEnvironment) {
5287
+ return (entryEnvironment ?? "") === (inputEnvironment ?? "");
5288
+ }
5289
+ function movePathAliasToCurrentMirror(registry, input) {
5290
+ for (const entry of registry.entries) {
5291
+ if (entry.baseUrl !== input.baseUrl || entry.spaceId !== input.spaceId || entry.mediaKind !== input.mediaKind || !environmentMatches(entry.environment, input.environment)) continue;
5292
+ entry.paths = entry.paths.filter((pathAlias) => pathAlias !== input.pathAlias);
5293
+ }
5294
+ }
5295
+ function toMirrorPathAlias(filePath, projectRoot) {
5296
+ const absolutePath = path.resolve(filePath);
5297
+ const relativePath = path.relative(projectRoot, absolutePath);
5298
+ if (relativePath && !relativePath.startsWith("..") && !path.isAbsolute(relativePath)) return normalizePath(relativePath);
5299
+ return normalizePath(absolutePath);
5300
+ }
5301
+ function normalizePath(value) {
5302
+ return value.split(path.sep).join("/");
5303
+ }
5304
+ function mergePathAliases(paths, pathAlias) {
5305
+ return Array.from(new Set([...paths, pathAlias])).sort();
5306
+ }
5307
+ function emptyRegistry() {
5308
+ return {
5309
+ version: MIRROR_REGISTRY_VERSION,
5310
+ entries: []
5311
+ };
5312
+ }
5313
+ function isMirrorRegistry(value) {
5314
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
5315
+ const registry = value;
5316
+ if (registry.version !== MIRROR_REGISTRY_VERSION || !Array.isArray(registry.entries)) return false;
5317
+ return registry.entries.every(isMirrorEntry);
5318
+ }
5319
+ function isMirrorEntry(value) {
5320
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
5321
+ const entry = value;
5322
+ return entry.version === MIRROR_REGISTRY_VERSION && typeof entry.baseUrl === "string" && (entry.environment === void 0 || typeof entry.environment === "string") && typeof entry.spaceId === "string" && typeof entry.sha256 === "string" && typeof entry.sizeBytes === "number" && Array.isArray(entry.paths) && entry.paths.every((item) => typeof item === "string") && typeof entry.assetId === "string" && typeof entry.variantId === "string" && typeof entry.mediaKind === "string" && (entry.mediaKey === void 0 || entry.mediaKey === null || typeof entry.mediaKey === "string") && typeof entry.updatedAt === "string";
5323
+ }
5324
+ //#endregion
5163
5325
  //#region src/cli/commands/upload.ts
5164
5326
  /**
5165
5327
  * Upload Command - Upload one media file to create variants
@@ -5176,6 +5338,7 @@ var defaultDeps$12 = {
5176
5338
  fetch,
5177
5339
  readFile,
5178
5340
  stat,
5341
+ recordMirrorForFile,
5179
5342
  print: console.log
5180
5343
  };
5181
5344
  var UploadUsageError = class extends Error {};
@@ -5318,6 +5481,18 @@ async function executeUpload(parsed, deps = defaultDeps$12) {
5318
5481
  lineage: upload.lineage,
5319
5482
  organization
5320
5483
  };
5484
+ await recordUploadMirror({
5485
+ deps,
5486
+ projectRoot: projectConfig?.projectRoot,
5487
+ env,
5488
+ baseUrl,
5489
+ spaceId,
5490
+ filePath,
5491
+ assetId: upload.asset?.id ?? upload.variant.asset_id,
5492
+ variantId: upload.variant.id,
5493
+ mediaKind: upload.variant.media_kind || mediaType.mediaKind,
5494
+ mediaKey: upload.variant.media_key || upload.variant.image_key
5495
+ });
5321
5496
  if (jsonOutput) {
5322
5497
  deps.print(JSON.stringify(result, null, 2));
5323
5498
  return result;
@@ -5359,6 +5534,24 @@ async function executeUpload(parsed, deps = defaultDeps$12) {
5359
5534
  throw error;
5360
5535
  }
5361
5536
  }
5537
+ async function recordUploadMirror(input) {
5538
+ if (!input.deps.recordMirrorForFile) return;
5539
+ try {
5540
+ await input.deps.recordMirrorForFile({
5541
+ projectRoot: input.projectRoot,
5542
+ baseUrl: input.baseUrl,
5543
+ environment: input.env,
5544
+ spaceId: input.spaceId,
5545
+ filePath: input.filePath,
5546
+ assetId: input.assetId,
5547
+ variantId: input.variantId,
5548
+ mediaKind: input.mediaKind,
5549
+ mediaKey: input.mediaKey
5550
+ });
5551
+ } catch (error) {
5552
+ console.warn(`Warning: upload succeeded but mirror registry was not updated: ${error instanceof Error ? error.message : String(error)}`);
5553
+ }
5554
+ }
5362
5555
  function parseJsonObjectOption(value, optionName) {
5363
5556
  if (value === void 0) return void 0;
5364
5557
  let parsed;
@@ -5631,9 +5824,6 @@ Examples:
5631
5824
  `);
5632
5825
  }
5633
5826
  //#endregion
5634
- //#region src/shared/websocket-types.ts
5635
- var DEFAULT_MEDIA_KIND = "image";
5636
- //#endregion
5637
5827
  //#region src/cli/lib/image-transfer.ts
5638
5828
  var MAX_FILE_SIZE_MB = 10;
5639
5829
  var MAX_FILE_SIZE_BYTES = MAX_FILE_SIZE_MB * 1024 * 1024;
@@ -22898,6 +23088,8 @@ var defaultDeps$10 = {
22898
23088
  resolveBaseUrl,
22899
23089
  createClient: WebSocketClient.create,
22900
23090
  uploadLocalReference: uploadLocalImageAsReference,
23091
+ resolveMirrorForFile,
23092
+ recordMirrorForFile,
22901
23093
  downloadImage,
22902
23094
  downloadFile,
22903
23095
  saveRunManifest,
@@ -23615,12 +23807,41 @@ async function resolveReferenceVariantIds(refs, ctx, deps, variants = [], mediaK
23615
23807
  const variantIds = [];
23616
23808
  for (const ref of refs) {
23617
23809
  if (await deps.fileExists(ref)) {
23810
+ const localReferenceMediaKind = mediaKind === "video" ? CLI_GENERATION_MEDIA_KIND : mediaKind;
23811
+ const mirror = deps.resolveMirrorForFile ? await deps.resolveMirrorForFile({
23812
+ projectRoot: ctx.projectRoot,
23813
+ baseUrl: ctx.baseUrl,
23814
+ environment: ctx.env,
23815
+ spaceId: ctx.spaceId,
23816
+ filePath: ref,
23817
+ mediaKind: localReferenceMediaKind
23818
+ }) : void 0;
23819
+ if (mirror?.pathEntry && (mirror.pathEntry.sha256 !== mirror.fingerprint.sha256 || mirror.pathEntry.sizeBytes !== mirror.fingerprint.sizeBytes)) throw new Error(describeMirrorMismatch(ref, mirror.pathEntry));
23820
+ if (mirror?.digestEntry) {
23821
+ validateReferenceVariant(mirror.digestEntry.variantId, variants, mediaKind);
23822
+ await recordReusedReferenceMirror({
23823
+ ref,
23824
+ ctx,
23825
+ deps,
23826
+ entry: mirror.digestEntry
23827
+ });
23828
+ variantIds.push(mirror.digestEntry.variantId);
23829
+ continue;
23830
+ }
23831
+ if (mirror?.pathEntry) throw new Error(describeMirrorMismatch(ref, mirror.pathEntry));
23618
23832
  const uploaded = await deps.uploadLocalReference({
23619
23833
  baseUrl: ctx.baseUrl,
23620
23834
  accessToken: ctx.accessToken,
23621
23835
  spaceId: ctx.spaceId,
23622
23836
  filePath: ref
23623
23837
  });
23838
+ if (deps.recordMirrorForFile) await recordUploadedReferenceMirror({
23839
+ ref,
23840
+ ctx,
23841
+ deps,
23842
+ uploaded,
23843
+ mediaKind: localReferenceMediaKind
23844
+ });
23624
23845
  variantIds.push(uploaded.variant.id);
23625
23846
  continue;
23626
23847
  }
@@ -23630,6 +23851,40 @@ async function resolveReferenceVariantIds(refs, ctx, deps, variants = [], mediaK
23630
23851
  }
23631
23852
  return variantIds;
23632
23853
  }
23854
+ async function recordReusedReferenceMirror(input) {
23855
+ try {
23856
+ await input.deps.recordMirrorForFile?.({
23857
+ projectRoot: input.ctx.projectRoot,
23858
+ baseUrl: input.ctx.baseUrl,
23859
+ environment: input.ctx.env,
23860
+ spaceId: input.ctx.spaceId,
23861
+ filePath: input.ref,
23862
+ assetId: input.entry.assetId,
23863
+ variantId: input.entry.variantId,
23864
+ mediaKind: input.entry.mediaKind,
23865
+ mediaKey: input.entry.mediaKey
23866
+ });
23867
+ } catch (error) {
23868
+ console.warn(`Warning: reused mirrored reference but mirror registry alias was not updated: ${error instanceof Error ? error.message : String(error)}`);
23869
+ }
23870
+ }
23871
+ async function recordUploadedReferenceMirror(input) {
23872
+ try {
23873
+ await input.deps.recordMirrorForFile?.({
23874
+ projectRoot: input.ctx.projectRoot,
23875
+ baseUrl: input.ctx.baseUrl,
23876
+ environment: input.ctx.env,
23877
+ spaceId: input.ctx.spaceId,
23878
+ filePath: input.ref,
23879
+ assetId: input.uploaded.asset?.id ?? input.uploaded.variant.asset_id,
23880
+ variantId: input.uploaded.variant.id,
23881
+ mediaKind: input.uploaded.variant.media_kind ?? input.mediaKind,
23882
+ mediaKey: input.uploaded.variant.media_key ?? input.uploaded.variant.image_key
23883
+ });
23884
+ } catch (error) {
23885
+ console.warn(`Warning: reference upload succeeded but mirror registry was not updated: ${error instanceof Error ? error.message : String(error)}`);
23886
+ }
23887
+ }
23633
23888
  function validateReferenceVariant(ref, variants, mediaKind) {
23634
23889
  const variant = variants.find((candidate) => candidate.id === ref);
23635
23890
  if (!variant) throw new Error(`Reference variant not found in space sync state: ${ref}`);
@@ -24400,6 +24655,7 @@ var defaultDeps$6 = {
24400
24655
  resolveBaseUrl,
24401
24656
  fetch,
24402
24657
  downloadFile,
24658
+ recordMirrorForFile,
24403
24659
  createMutationClient: (env, spaceId) => WebSocketClient.create(env, spaceId),
24404
24660
  print: console.log
24405
24661
  };
@@ -24456,6 +24712,12 @@ async function executeAssets(parsed, deps = defaultDeps$6) {
24456
24712
  outputPath,
24457
24713
  force: ctx.force
24458
24714
  });
24715
+ await recordDownloadedMirror({
24716
+ ctx,
24717
+ deps,
24718
+ outputPath,
24719
+ variant: resolved.variant
24720
+ });
24459
24721
  deps.print(`Downloaded ${resolved.mediaKey} to ${outputPath}`);
24460
24722
  return {
24461
24723
  type: "download",
@@ -24515,9 +24777,29 @@ async function buildContext$6(parsed, deps) {
24515
24777
  spaceId,
24516
24778
  baseUrl: deps.resolveBaseUrl(env),
24517
24779
  accessToken: config.token.accessToken,
24518
- force: parsed.options.force === "true"
24780
+ force: parsed.options.force === "true",
24781
+ projectRoot: projectConfig?.projectRoot
24519
24782
  };
24520
24783
  }
24784
+ async function recordDownloadedMirror(input) {
24785
+ const variant = input.variant;
24786
+ if (!variant || !input.deps.recordMirrorForFile) return;
24787
+ try {
24788
+ await input.deps.recordMirrorForFile({
24789
+ projectRoot: input.ctx.projectRoot,
24790
+ baseUrl: input.ctx.baseUrl,
24791
+ environment: input.ctx.env,
24792
+ spaceId: input.ctx.spaceId,
24793
+ filePath: input.outputPath,
24794
+ assetId: variant.asset_id,
24795
+ variantId: variant.id,
24796
+ mediaKind: variant.media_kind || "image",
24797
+ mediaKey: variant.media_key || variant.image_key
24798
+ });
24799
+ } catch (error) {
24800
+ console.warn(`Warning: download succeeded but mirror registry was not updated: ${error instanceof Error ? error.message : String(error)}`);
24801
+ }
24802
+ }
24521
24803
  async function listAssets$1(ctx, deps) {
24522
24804
  return (await fetchJson(ctx, deps, `/api/spaces/${ctx.spaceId}/assets`)).assets || [];
24523
24805
  }
@@ -26270,7 +26552,7 @@ Options:
26270
26552
  }
26271
26553
  //#endregion
26272
26554
  //#region src/cli/index.ts
26273
- var CLI_VERSION = "0.2.5+fc579b35cabc";
26555
+ var CLI_VERSION = "0.2.6+be63b9dd565a";
26274
26556
  async function main() {
26275
26557
  const argv = process$1.argv.slice(2);
26276
26558
  if (argv[0] === "--") argv.shift();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "makefx",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "Command-line interface for AI-assisted game asset production with Make Effects.",
5
5
  "license": "MIT",
6
6
  "type": "module",