@bitmagic/cli 0.1.52-dev.0 → 0.1.52-dev.10

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 (139) hide show
  1. package/README.md +178 -6
  2. package/dist/assets/add.d.ts +40 -0
  3. package/dist/assets/add.js +229 -0
  4. package/dist/assets/add.js.map +1 -0
  5. package/dist/assets/entry.d.ts +44 -0
  6. package/dist/assets/entry.js +71 -0
  7. package/dist/assets/entry.js.map +1 -0
  8. package/dist/assets/file-kind.d.ts +61 -0
  9. package/dist/assets/file-kind.js +195 -0
  10. package/dist/assets/file-kind.js.map +1 -0
  11. package/dist/assets/list.d.ts +27 -0
  12. package/dist/assets/list.js +50 -0
  13. package/dist/assets/list.js.map +1 -0
  14. package/dist/assets/remove.d.ts +50 -0
  15. package/dist/assets/remove.js +91 -0
  16. package/dist/assets/remove.js.map +1 -0
  17. package/dist/assets/voxelize-glb.d.ts +37 -0
  18. package/dist/assets/voxelize-glb.js +65 -0
  19. package/dist/assets/voxelize-glb.js.map +1 -0
  20. package/dist/assets/vxl-summary.d.ts +32 -0
  21. package/dist/assets/vxl-summary.js +118 -0
  22. package/dist/assets/vxl-summary.js.map +1 -0
  23. package/dist/bin.js +25 -0
  24. package/dist/bin.js.map +1 -1
  25. package/dist/browser-gl.d.ts +46 -8
  26. package/dist/browser-gl.js +54 -26
  27. package/dist/browser-gl.js.map +1 -1
  28. package/dist/cli.d.ts +22 -0
  29. package/dist/cli.js +17 -1
  30. package/dist/cli.js.map +1 -1
  31. package/dist/commands/assets.d.ts +56 -0
  32. package/dist/commands/assets.js +328 -0
  33. package/dist/commands/assets.js.map +1 -0
  34. package/dist/commands/cover.js +2 -3
  35. package/dist/commands/cover.js.map +1 -1
  36. package/dist/commands/forge.d.ts +7 -9
  37. package/dist/commands/forge.js +12 -14
  38. package/dist/commands/forge.js.map +1 -1
  39. package/dist/commands/generate.d.ts +41 -9
  40. package/dist/commands/generate.js +234 -54
  41. package/dist/commands/generate.js.map +1 -1
  42. package/dist/commands/init.js +19 -12
  43. package/dist/commands/init.js.map +1 -1
  44. package/dist/commands/judge.d.ts +36 -0
  45. package/dist/commands/judge.js +216 -0
  46. package/dist/commands/judge.js.map +1 -0
  47. package/dist/commands/levels.d.ts +33 -0
  48. package/dist/commands/levels.js +241 -0
  49. package/dist/commands/levels.js.map +1 -0
  50. package/dist/commands/number-flag.d.ts +7 -0
  51. package/dist/commands/number-flag.js +38 -0
  52. package/dist/commands/number-flag.js.map +1 -0
  53. package/dist/commands/publish.js +13 -0
  54. package/dist/commands/publish.js.map +1 -1
  55. package/dist/commands/reset-account.js +2 -8
  56. package/dist/commands/reset-account.js.map +1 -1
  57. package/dist/commands/run-deps.d.ts +27 -0
  58. package/dist/commands/run-deps.js +43 -0
  59. package/dist/commands/run-deps.js.map +1 -0
  60. package/dist/commands/verify.js +3 -3
  61. package/dist/commands/verify.js.map +1 -1
  62. package/dist/editor/journal.d.ts +48 -1
  63. package/dist/editor/journal.js +44 -0
  64. package/dist/editor/journal.js.map +1 -1
  65. package/dist/editor/previews.d.ts +6 -0
  66. package/dist/editor/previews.js +16 -0
  67. package/dist/editor/previews.js.map +1 -1
  68. package/dist/editor/server.js +228 -53
  69. package/dist/editor/server.js.map +1 -1
  70. package/dist/editor/shell-page.js +221 -116
  71. package/dist/editor/shell-page.js.map +1 -1
  72. package/dist/editor/voxel-save.d.ts +47 -0
  73. package/dist/editor/voxel-save.js +49 -0
  74. package/dist/editor/voxel-save.js.map +1 -0
  75. package/dist/errors.d.ts +10 -0
  76. package/dist/errors.js +16 -0
  77. package/dist/errors.js.map +1 -1
  78. package/dist/forge/apply-modifications.d.ts +18 -1
  79. package/dist/forge/apply-modifications.js +15 -32
  80. package/dist/forge/apply-modifications.js.map +1 -1
  81. package/dist/forge/browser-host.js +9 -4
  82. package/dist/forge/browser-host.js.map +1 -1
  83. package/dist/forge/voxelize-session.d.ts +61 -0
  84. package/dist/forge/voxelize-session.js +137 -0
  85. package/dist/forge/voxelize-session.js.map +1 -0
  86. package/dist/generate/model.d.ts +36 -0
  87. package/dist/generate/model.js +90 -0
  88. package/dist/generate/model.js.map +1 -0
  89. package/dist/generate/prop.d.ts +52 -4
  90. package/dist/generate/prop.js +169 -102
  91. package/dist/generate/prop.js.map +1 -1
  92. package/dist/generate/stream.d.ts +7 -0
  93. package/dist/generate/stream.js +2 -1
  94. package/dist/generate/stream.js.map +1 -1
  95. package/dist/generate/vehicle.js +5 -85
  96. package/dist/generate/vehicle.js.map +1 -1
  97. package/dist/http/signed-upload.d.ts +55 -0
  98. package/dist/http/signed-upload.js +168 -0
  99. package/dist/http/signed-upload.js.map +1 -0
  100. package/dist/judge/facts.d.ts +18 -0
  101. package/dist/judge/facts.js +180 -0
  102. package/dist/judge/facts.js.map +1 -0
  103. package/dist/judge/record.d.ts +16 -0
  104. package/dist/judge/record.js +55 -0
  105. package/dist/judge/record.js.map +1 -0
  106. package/dist/judge/stream.d.ts +36 -0
  107. package/dist/judge/stream.js +122 -0
  108. package/dist/judge/stream.js.map +1 -0
  109. package/dist/levels/registry.d.ts +88 -0
  110. package/dist/levels/registry.js +309 -0
  111. package/dist/levels/registry.js.map +1 -0
  112. package/dist/messages/check.d.ts +30 -0
  113. package/dist/messages/check.js +119 -0
  114. package/dist/messages/check.js.map +1 -0
  115. package/dist/messages/notice.d.ts +58 -0
  116. package/dist/messages/notice.js +54 -0
  117. package/dist/messages/notice.js.map +1 -0
  118. package/dist/messages/refresh.d.ts +19 -0
  119. package/dist/messages/refresh.js +42 -0
  120. package/dist/messages/refresh.js.map +1 -0
  121. package/dist/project/jobs.d.ts +1 -1
  122. package/dist/project/jobs.js.map +1 -1
  123. package/dist/publish/client.d.ts +13 -0
  124. package/dist/publish/client.js +13 -34
  125. package/dist/publish/client.js.map +1 -1
  126. package/dist/publish/platform.d.ts +46 -0
  127. package/dist/publish/platform.js +92 -0
  128. package/dist/publish/platform.js.map +1 -0
  129. package/dist/scaffold/project-files.d.ts +8 -0
  130. package/dist/scaffold/project-files.js +192 -34
  131. package/dist/scaffold/project-files.js.map +1 -1
  132. package/dist/scaffold/project.js +2 -1
  133. package/dist/scaffold/project.js.map +1 -1
  134. package/dist/verify/browser.d.ts +11 -6
  135. package/dist/verify/browser.js +31 -15
  136. package/dist/verify/browser.js.map +1 -1
  137. package/dist/verify/iterate.js +3 -3
  138. package/dist/verify/iterate.js.map +1 -1
  139. package/package.json +4 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry.js","sourceRoot":"","sources":["../../src/assets/entry.ts"],"names":[],"mappings":"AA4BA,SAAS,SAAS,CAAC,KAAwB,EAAE,IAAY;IACvD,OAAO;QACL,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,IAAI;QACJ,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,UAAU,EAAE;YACV,MAAM,EAAE,UAAU;YAClB,EAAE,EAAE,KAAK,CAAC,EAAE;YACZ,SAAS,EAAE,KAAK,CAAC,GAAG;YACpB,UAAU,EAAE,KAAK,CAAC,UAAU;SAC7B;KACF,CAAC;AACJ,CAAC;AAED,gGAAgG;AAChG,MAAM,UAAU,eAAe,CAAC,KAAwB;IACtD,OAAO,EAAE,GAAG,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,aAAa,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC;AACpE,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAwB;IACtD,OAAO,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAwB;IACrD,OAAO,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;AAClC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,KAAwB,EAAE,OAAmB;IACzE,OAAO;QACL,GAAG,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC;QAC1B,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,yFAAyF;QACzF,+FAA+F;QAC/F,8EAA8E;QAC9E,4FAA4F;QAC5F,4FAA4F;QAC5F,gGAAgG;QAChG,mBAAmB,EAAE,IAAI;QACzB,SAAS,EAAE,OAAO,CAAC,YAAY;QAC/B,aAAa,EAAE,OAAO,CAAC,aAAa;QACpC,GAAG,CAAC,OAAO,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAChF,CAAC;AACJ,CAAC;AAED,wGAAwG;AACxG,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAG,CAAC;AAE7C,MAAM,UAAU,aAAa,CAAC,KAAwB,EAAE,YAAgC;IACtF,OAAO;QACL,GAAG,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC;QAC1B,YAAY,EAAE,KAAK,CAAC,GAAG;QACvB,YAAY,EAAE,YAAY,IAAI,yBAAyB;KACxD,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CACpC,WAAoC,EACpC,KAA2D;IAE3D,OAAO;QACL,GAAG,WAAW;QACd,UAAU,EAAE;YACV,MAAM,EAAE,UAAU;YAClB,EAAE,EAAE,KAAK,CAAC,EAAE;YACZ,SAAS,EAAE,KAAK,CAAC,GAAG;YACpB,UAAU,EAAE,KAAK,CAAC,UAAU;SAC7B;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,61 @@
1
+ /** How the file is handled, which decides everything downstream. */
2
+ export type AssetFileKind = 'glb-voxelize' | 'glb-keep' | 'vxl' | 'image' | 'audio' | 'json';
3
+ export interface AssetFileClass {
4
+ kind: AssetFileKind;
5
+ /** The `type` recorded on the world.json asset entry. */
6
+ assetType: 'vxl' | 'glb' | 'image' | 'audio' | 'json';
7
+ /** What the upload is signed for; the server decides gzip from this. */
8
+ contentType: string;
9
+ /** Lower-case extension without the dot. */
10
+ extension: string;
11
+ }
12
+ /** Every extension `classifyAssetFile` accepts, for help text and the README. */
13
+ export declare const ACCEPTED_EXTENSIONS: string[];
14
+ /**
15
+ * The extension, treating a dotfile as having one.
16
+ *
17
+ * `path.extname('.vxl')` is `''` — Node reads a leading dot as the start of the basename, not an
18
+ * extension — so a file literally named `.vxl` (what an export tool that names by extension alone
19
+ * produces) fell through to the catch-all and was refused with a message listing `.vxl` among the
20
+ * types it takes.
21
+ */
22
+ export declare function fileExtension(filename: string): string;
23
+ export declare function classifyAssetFile(filename: string, options: {
24
+ keepGlb: boolean;
25
+ }): AssetFileClass;
26
+ /**
27
+ * The asset's name when `--name` is not given: the file's basename without its extension, which
28
+ * is what the Creator pre-fills its name prompt with.
29
+ */
30
+ export declare function defaultAssetName(filename: string): string;
31
+ /**
32
+ * A name the engine can build a filename from. `CREATE_ASSET_FROM_GLB_URL` uploads its bake as
33
+ * `<gameId>-<name>-<timestamp>.vxl`, and api-server refuses a filename carrying a separator or
34
+ * `..` — so a name that would trip that fails HERE, before the file has been uploaded and the
35
+ * browser launched, instead of as an opaque "Failed to upload VXL file" minutes later.
36
+ *
37
+ * The length cap and the URL-unsafe set are part of the same job: both produce a failure that only
38
+ * appears after the upload (or after a ten-minute bake), which is precisely what this is for.
39
+ */
40
+ export declare function validateAssetName(name: string): string;
41
+ /**
42
+ * The upload filename: a bare basename, which is all api-server accepts.
43
+ *
44
+ * Mirrors `sanitizeUploadFilename` in full — separators AND `..`, as three independent checks.
45
+ * `path.basename` strips `/` on POSIX but leaves `\`, so a legal `my\model.glb` used to pass here
46
+ * and then eat a network round-trip to be refused by the server with a message naming nothing the
47
+ * creator could act on. The URL-unsafe and length checks are here for the same reason as on the
48
+ * name above.
49
+ */
50
+ export declare function uploadFilename(filePath: string): string;
51
+ /**
52
+ * `asset_<timestamp>_<random>` — byte-compatible with the engine's `ObjectIdService.generateId`
53
+ * and with what api-server mints for the generators (`cli-assets.ts`), so an id minted here is
54
+ * indistinguishable from one minted anywhere else in the system.
55
+ */
56
+ export declare function mintAssetId(now?: () => number, random?: () => number): string;
57
+ /**
58
+ * Said once per audio upload that the Creator would have transcoded. The engine plays the file
59
+ * as-is; this is about download size, and it names the exact command rather than a vague "consider".
60
+ */
61
+ export declare function audioTranscodeNote(extension: string): string | null;
@@ -0,0 +1,195 @@
1
+ /**
2
+ * What `bitmagic assets add <file>` makes of a filename, decided BEFORE anything is read, uploaded
3
+ * or written — so a file this lane cannot take costs nothing and leaves world.json untouched.
4
+ *
5
+ * The table mirrors the Creator's Assets-tab upload (`creator/src/vue/composables/useAssetsEditor.ts`
6
+ * `handleFileUpload` + `assetsUploadTypes.ts` `detectAssetType`) where the CLI can do the same
7
+ * thing, and refuses — by name, with where to go instead — where it cannot:
8
+ *
9
+ * - `.gltf` is refused rather than accepted-and-broken: the engine voxelizes ONE fetched URL, and
10
+ * a `.gltf` is a JSON index whose buffers and textures live in files beside it.
11
+ * - `.vox`/`.qb` (MagicaVoxel/Qubicle import), `.fbx` (animation import), `.spz`/`.ply`/`.sog`
12
+ * (splat transcode) and `.mp4` (video faststart) each need a conversion the Creator runs through
13
+ * game-play-agent, which a CLI project cannot reach.
14
+ *
15
+ * Audio and raster images are uploaded AS THEY ARE. The Creator transcodes them to `.opus`/`.webp`
16
+ * for size; the engine decodes mp3/ogg/m4a through Web Audio and draws png/jpg like any browser,
17
+ * so the difference is bytes, not whether it works. `audioTranscodeNote` says so once.
18
+ */
19
+ import * as path from 'path';
20
+ import { CliError } from '../errors.js';
21
+ const CREATOR_ONLY = {
22
+ vox: 'MagicaVoxel import',
23
+ qb: 'Qubicle import',
24
+ fbx: 'FBX animation import',
25
+ spz: 'Gaussian-splat transcode',
26
+ ply: 'Gaussian-splat transcode',
27
+ sog: 'Gaussian-splat transcode',
28
+ mp4: 'video',
29
+ };
30
+ const IMAGE_TYPES = {
31
+ png: 'image/png',
32
+ jpg: 'image/jpeg',
33
+ jpeg: 'image/jpeg',
34
+ webp: 'image/webp',
35
+ };
36
+ const AUDIO_TYPES = {
37
+ opus: 'audio/ogg',
38
+ ogg: 'audio/ogg',
39
+ m4a: 'audio/mp4',
40
+ mp3: 'audio/mpeg',
41
+ };
42
+ /** Every extension `classifyAssetFile` accepts, for help text and the README. */
43
+ export const ACCEPTED_EXTENSIONS = ['glb', 'vxl', 'png', 'jpg', 'jpeg', 'webp', 'opus', 'ogg', 'm4a', 'mp3', 'json'];
44
+ /**
45
+ * The extension, treating a dotfile as having one.
46
+ *
47
+ * `path.extname('.vxl')` is `''` — Node reads a leading dot as the start of the basename, not an
48
+ * extension — so a file literally named `.vxl` (what an export tool that names by extension alone
49
+ * produces) fell through to the catch-all and was refused with a message listing `.vxl` among the
50
+ * types it takes.
51
+ */
52
+ export function fileExtension(filename) {
53
+ const base = path.basename(filename);
54
+ const fromExtname = path.extname(base).slice(1);
55
+ const extension = fromExtname !== '' ? fromExtname : (base.startsWith('.') ? base.slice(1) : '');
56
+ return extension.toLowerCase();
57
+ }
58
+ export function classifyAssetFile(filename, options) {
59
+ const extension = fileExtension(filename);
60
+ const base = path.basename(filename);
61
+ if (options.keepGlb && extension !== 'glb') {
62
+ throw new CliError(`--keep-glb only applies to a .glb file, and ${base} is not one. world.json is unchanged.`);
63
+ }
64
+ if (extension === 'glb') {
65
+ return options.keepGlb
66
+ ? { kind: 'glb-keep', assetType: 'glb', contentType: 'model/gltf-binary', extension }
67
+ : { kind: 'glb-voxelize', assetType: 'vxl', contentType: 'model/gltf-binary', extension };
68
+ }
69
+ if (extension === 'vxl') {
70
+ // The same content type the engine uploads its own bakes under (VoxelMessageHandlers.ts).
71
+ // api-server marks it compressible, so the PUT gzips.
72
+ return { kind: 'vxl', assetType: 'vxl', contentType: 'application/octet-stream', extension };
73
+ }
74
+ const imageType = IMAGE_TYPES[extension];
75
+ if (imageType !== undefined) {
76
+ return { kind: 'image', assetType: 'image', contentType: imageType, extension };
77
+ }
78
+ const audioType = AUDIO_TYPES[extension];
79
+ if (audioType !== undefined) {
80
+ return { kind: 'audio', assetType: 'audio', contentType: audioType, extension };
81
+ }
82
+ if (extension === 'json') {
83
+ return { kind: 'json', assetType: 'json', contentType: 'application/json', extension };
84
+ }
85
+ if (extension === 'gltf') {
86
+ throw new CliError(`${base} is a .gltf, which keeps its buffers and textures in files beside it — the game loads a `
87
+ + 'single URL. Export it as a self-contained .glb and add that instead. world.json is unchanged.');
88
+ }
89
+ const creatorOnly = CREATOR_ONLY[extension];
90
+ if (creatorOnly !== undefined) {
91
+ throw new CliError(`.${extension} (${creatorOnly}) needs a conversion this CLI does not run. `
92
+ + 'Upload it through the Creator\'s Assets tab instead. world.json is unchanged.');
93
+ }
94
+ throw new CliError(`Cannot add ${base}: this CLI takes ${ACCEPTED_EXTENSIONS.map((e) => `.${e}`).join(', ')}. `
95
+ + 'world.json is unchanged.');
96
+ }
97
+ /**
98
+ * The asset's name when `--name` is not given: the file's basename without its extension, which
99
+ * is what the Creator pre-fills its name prompt with.
100
+ */
101
+ export function defaultAssetName(filename) {
102
+ const base = path.basename(filename);
103
+ const stripped = base.replace(/\.[^/.]+$/, '');
104
+ // A file named for its extension alone (`.png`) strips to nothing, and an empty name is refused
105
+ // downstream — a dead end for a file this lane otherwise accepts. Drop the leading dot instead.
106
+ return stripped !== '' ? stripped : base.replace(/^\.+/, '');
107
+ }
108
+ /**
109
+ * Characters that survive a path check but break the URL the object is served from, or the storage
110
+ * key itself. api-server builds `publicUrl` by concatenating the key onto the portal origin with no
111
+ * percent-encoding, so a `#` truncates the URL at a fragment and a `?` at a query — the asset then
112
+ * 404s at game load, long after a successful-looking upload. GCS rejects CR/LF in object names
113
+ * outright. Refused up front rather than escaped, so the name in world.json is the name on disk.
114
+ */
115
+ // eslint-disable-next-line no-control-regex -- control characters are exactly what this rejects
116
+ const URL_UNSAFE = /[#?%\u0000-\u001f\u007f]/;
117
+ /** GCS caps an object key at 1024 bytes; the engine prefixes and suffixes the name inside one. */
118
+ const MAX_NAME_LENGTH = 128;
119
+ function describeUnsafe(value) {
120
+ const bad = URL_UNSAFE.exec(value)?.[0] ?? '';
121
+ const code = bad.charCodeAt(0);
122
+ return code < 0x20 || code === 0x7f ? `a control character (0x${code.toString(16)})` : `"${bad}"`;
123
+ }
124
+ /**
125
+ * A name the engine can build a filename from. `CREATE_ASSET_FROM_GLB_URL` uploads its bake as
126
+ * `<gameId>-<name>-<timestamp>.vxl`, and api-server refuses a filename carrying a separator or
127
+ * `..` — so a name that would trip that fails HERE, before the file has been uploaded and the
128
+ * browser launched, instead of as an opaque "Failed to upload VXL file" minutes later.
129
+ *
130
+ * The length cap and the URL-unsafe set are part of the same job: both produce a failure that only
131
+ * appears after the upload (or after a ten-minute bake), which is precisely what this is for.
132
+ */
133
+ export function validateAssetName(name) {
134
+ const trimmed = name.trim();
135
+ if (trimmed === '')
136
+ throw new CliError('The asset name cannot be empty. world.json is unchanged.');
137
+ if (/[/\\]/.test(trimmed) || trimmed.includes('..')) {
138
+ throw new CliError(`The asset name ${JSON.stringify(name)} cannot contain a path separator or "..". world.json is unchanged.`);
139
+ }
140
+ if (URL_UNSAFE.test(trimmed)) {
141
+ throw new CliError(`The asset name ${JSON.stringify(name)} cannot contain ${describeUnsafe(trimmed)} — it ends up in `
142
+ + 'the uploaded file\'s URL. world.json is unchanged.');
143
+ }
144
+ if (trimmed.length > MAX_NAME_LENGTH) {
145
+ throw new CliError(`The asset name is ${trimmed.length} characters; keep it under ${MAX_NAME_LENGTH}. world.json is unchanged.`);
146
+ }
147
+ return trimmed;
148
+ }
149
+ /**
150
+ * The upload filename: a bare basename, which is all api-server accepts.
151
+ *
152
+ * Mirrors `sanitizeUploadFilename` in full — separators AND `..`, as three independent checks.
153
+ * `path.basename` strips `/` on POSIX but leaves `\`, so a legal `my\model.glb` used to pass here
154
+ * and then eat a network round-trip to be refused by the server with a message naming nothing the
155
+ * creator could act on. The URL-unsafe and length checks are here for the same reason as on the
156
+ * name above.
157
+ */
158
+ export function uploadFilename(filePath) {
159
+ const base = path.basename(filePath);
160
+ if (base.includes('/') || base.includes('\\')) {
161
+ throw new CliError(`Rename ${JSON.stringify(base)} first: an upload filename cannot contain a path separator. `
162
+ + 'world.json is unchanged.');
163
+ }
164
+ if (base.includes('..')) {
165
+ throw new CliError(`Rename ${base} first: an upload filename cannot contain "..". world.json is unchanged.`);
166
+ }
167
+ if (URL_UNSAFE.test(base)) {
168
+ throw new CliError(`Rename ${JSON.stringify(base)} first: a filename cannot contain ${describeUnsafe(base)} — it ends up `
169
+ + 'in the uploaded file\'s URL. world.json is unchanged.');
170
+ }
171
+ if (base.length > MAX_NAME_LENGTH) {
172
+ throw new CliError(`Rename ${JSON.stringify(base)} first: keep the filename under ${MAX_NAME_LENGTH} characters. `
173
+ + 'world.json is unchanged.');
174
+ }
175
+ return base;
176
+ }
177
+ /**
178
+ * `asset_<timestamp>_<random>` — byte-compatible with the engine's `ObjectIdService.generateId`
179
+ * and with what api-server mints for the generators (`cli-assets.ts`), so an id minted here is
180
+ * indistinguishable from one minted anywhere else in the system.
181
+ */
182
+ export function mintAssetId(now = Date.now, random = Math.random) {
183
+ return `asset_${now()}_${random().toString(36).substring(2, 8)}`;
184
+ }
185
+ /**
186
+ * Said once per audio upload that the Creator would have transcoded. The engine plays the file
187
+ * as-is; this is about download size, and it names the exact command rather than a vague "consider".
188
+ */
189
+ export function audioTranscodeNote(extension) {
190
+ if (extension === 'opus')
191
+ return null;
192
+ return `Uploaded as .${extension}. The game plays it as-is; for a smaller download convert it first: `
193
+ + `ffmpeg -i in.${extension} -c:a libopus -b:a 96k out.opus`;
194
+ }
195
+ //# sourceMappingURL=file-kind.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-kind.js","sourceRoot":"","sources":["../../src/assets/file-kind.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * `bitmagic assets list` — the project's `assets[]`, as the dev view's assets panel sees it
3
+ * (`editor/server.ts` `GET /api/assets` reads the same array and nothing else).
4
+ */
5
+ import { type JsonObject } from '../project/world-json.js';
6
+ export interface AssetRow {
7
+ id: string;
8
+ name: string;
9
+ type: string;
10
+ size?: number;
11
+ /** Placed instances in `environmentObjects[]`. */
12
+ placed: number;
13
+ /**
14
+ * Every reference anywhere in world.json — placed instances plus levels, maps, doors, key items,
15
+ * village rosters.
16
+ *
17
+ * Reported alongside {@link placed} rather than folded into it because the two answer different
18
+ * questions, and counting only placed instances was actively misleading: a level asset showed
19
+ * `0`, an agent read that as "safe to delete", and `assets remove` then refused — or, with
20
+ * `--force`, removed the asset while leaving the level pointing at nothing.
21
+ */
22
+ references: number;
23
+ }
24
+ export declare function summarizeAssets(world: JsonObject): AssetRow[];
25
+ export declare function formatSize(bytes: number | undefined): string;
26
+ /** Fixed-width columns, no dependency, readable in a terminal and greppable by an agent. */
27
+ export declare function formatAssetTable(rows: AssetRow[]): string;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * `bitmagic assets list` — the project's `assets[]`, as the dev view's assets panel sees it
3
+ * (`editor/server.ts` `GET /api/assets` reads the same array and nothing else).
4
+ */
5
+ import { isJsonObject } from '../project/world-json.js';
6
+ import { findAssetReferences } from './remove.js';
7
+ export function summarizeAssets(world) {
8
+ const assets = Array.isArray(world.assets) ? world.assets : [];
9
+ return assets.filter(isJsonObject).map((asset) => {
10
+ const id = typeof asset.id === 'string' ? asset.id : '';
11
+ // The SAME scan `assets remove` refuses on, so the two commands cannot disagree about what
12
+ // counts as a reference.
13
+ const found = id === '' ? [] : findAssetReferences(world, id);
14
+ return {
15
+ id,
16
+ name: typeof asset.name === 'string' ? asset.name : '',
17
+ type: typeof asset.type === 'string' ? asset.type : '',
18
+ ...(typeof asset.size === 'number' ? { size: asset.size } : {}),
19
+ placed: found.filter((ref) => ref.placed).length,
20
+ references: found.length,
21
+ };
22
+ });
23
+ }
24
+ export function formatSize(bytes) {
25
+ if (bytes === undefined || !Number.isFinite(bytes) || bytes < 0)
26
+ return '';
27
+ if (bytes < 1024)
28
+ return `${bytes} B`;
29
+ if (bytes < 1024 * 1024)
30
+ return `${Math.round(bytes / 1024)} KB`;
31
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
32
+ }
33
+ /** Fixed-width columns, no dependency, readable in a terminal and greppable by an agent. */
34
+ export function formatAssetTable(rows) {
35
+ if (rows.length === 0)
36
+ return 'No assets in world.json yet. `bitmagic assets add <file>` adds one.';
37
+ const header = { id: 'ID', name: 'NAME', type: 'TYPE', size: 'SIZE', placed: 'PLACED', references: 'REFS' };
38
+ const cells = rows.map((row) => ({
39
+ id: row.id, name: row.name, type: row.type, size: formatSize(row.size),
40
+ placed: String(row.placed), references: String(row.references),
41
+ }));
42
+ const width = (key) => Math.max(header[key].length, ...cells.map((cell) => cell[key].length));
43
+ const w = {
44
+ id: width('id'), name: width('name'), type: width('type'), size: width('size'), placed: width('placed'),
45
+ };
46
+ const line = (cell) => `${cell.id.padEnd(w.id)} ${cell.name.padEnd(w.name)} ${cell.type.padEnd(w.type)} `
47
+ + `${cell.size.padStart(w.size)} ${cell.placed.padStart(w.placed)} ${cell.references}`;
48
+ return [line(header), ...cells.map(line)].join('\n');
49
+ }
50
+ //# sourceMappingURL=list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/assets/list.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,YAAY,EAAmB,MAAM,0BAA0B,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAqBlD,MAAM,UAAU,eAAe,CAAC,KAAiB;IAC/C,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/D,OAAO,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QAC/C,MAAM,EAAE,GAAG,OAAO,KAAK,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACxD,2FAA2F;QAC3F,yBAAyB;QACzB,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,mBAAmB,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC9D,OAAO;YACL,EAAE;YACF,IAAI,EAAE,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE;YACtD,IAAI,EAAE,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE;YACtD,GAAG,CAAC,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/D,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,MAAM;YAChD,UAAU,EAAE,KAAK,CAAC,MAAM;SACzB,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAyB;IAClD,IAAI,KAAK,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IAC3E,IAAI,KAAK,GAAG,IAAI;QAAE,OAAO,GAAG,KAAK,IAAI,CAAC;IACtC,IAAI,KAAK,GAAG,IAAI,GAAG,IAAI;QAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;IACjE,OAAO,GAAG,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;AACpD,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,gBAAgB,CAAC,IAAgB;IAC/C,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,qEAAqE,CAAC;IACpG,MAAM,MAAM,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC;IAC5G,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAC/B,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC;QACtE,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC;KAC/D,CAAC,CAAC,CAAC;IACJ,MAAM,KAAK,GAAG,CAAC,GAAwB,EAAU,EAAE,CACjD,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IACzE,MAAM,CAAC,GAAG;QACR,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC;KACxG,CAAC;IACF,MAAM,IAAI,GAAG,CAAC,IAAmB,EAAU,EAAE,CAC3C,GAAG,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI;UACnF,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,UAAU,EAAE,CAAC;IAC3F,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACvD,CAAC"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * What `bitmagic assets remove <id>` does to world.json, decided before anything is written.
3
+ *
4
+ * Removal is world.json only. The uploaded bytes stay in storage — that is what the Creator's
5
+ * Remove and game-play-agent's `/api/remove-asset` do too (there is no delete endpoint on either
6
+ * server, and every upload is immutable-cached), so a published game that still points at the file
7
+ * keeps working.
8
+ *
9
+ * The one thing this does that the Creator's button does not: it looks for what still POINTS at
10
+ * the asset first. world.json references an asset by id under a handful of keys — placed objects'
11
+ * `assetId`, a level's `vwldAssetId`, a map's `mapAssetId`, doors, key items, towers — and a
12
+ * removed asset that any of them still names is a load error the creator finds later, in the
13
+ * browser, with nothing connecting it back to this command. So the scan is a generic walk for
14
+ * those keys rather than a list of known containers: a container added to the schema tomorrow is
15
+ * still caught. What it cannot see is game CODE (`engine.playSound(assetId)`), and the refusal
16
+ * says so.
17
+ *
18
+ * `--force` removes the asset and every placed instance in `environmentObjects[]` in the same
19
+ * atomic write, and reports the rest as warnings.
20
+ */
21
+ import type { WorldJsonModification } from '@bitmagic/world-forger/pipeline/index.js';
22
+ import { type JsonObject } from '../project/world-json.js';
23
+ export interface AssetReference {
24
+ /** JSON-pointer-ish path to the referencing value, e.g. `environmentObjects[3].assetId`. */
25
+ path: string;
26
+ /**
27
+ * True when this is a placed instance in root `environmentObjects[]` — the one kind of reference
28
+ * `--force` can clean up by removing it alongside the asset.
29
+ *
30
+ * Recorded during the walk rather than recovered by re-parsing {@link path}: the path is a
31
+ * human-readable rendering, and deriving structure back out of it silently returns "no placed
32
+ * instances" the moment that rendering changes.
33
+ */
34
+ placed: boolean;
35
+ }
36
+ export interface RemovalPlan {
37
+ asset: JsonObject;
38
+ modifications: WorldJsonModification[];
39
+ /** Placed instances in `environmentObjects[]` — removed with the asset under `--force`. */
40
+ placedInstances: number;
41
+ /** References elsewhere in the document, which `--force` leaves in place and reports. */
42
+ otherReferences: AssetReference[];
43
+ }
44
+ /**
45
+ * Every place in the document where one of the reference keys holds `assetId`. The asset's own
46
+ * `assets[]` entry is skipped — its `id` is not a reference key, but a vehicle asset's own
47
+ * `vehicleFitment` might one day carry one, so the whole `assets[]` subtree is excluded outright.
48
+ */
49
+ export declare function findAssetReferences(world: JsonObject, assetId: string): AssetReference[];
50
+ export declare function planAssetRemoval(world: JsonObject, assetId: string, force: boolean): RemovalPlan;
@@ -0,0 +1,91 @@
1
+ import { CliError } from '../errors.js';
2
+ import { isJsonObject } from '../project/world-json.js';
3
+ /**
4
+ * Whether a key holds an asset id. Matched by SHAPE rather than by an enumerated list, because an
5
+ * enumerated list is exactly what went wrong: `buildingAssetIds` (required on every village
6
+ * hotspot) was missing, so removing a village's building asset reported zero references and left
7
+ * the world throwing `references unknown assetId(s)` at load.
8
+ *
9
+ * The shape covers `assetId` and every `<thing>AssetId` / `<thing>AssetIds` in the GameData types
10
+ * (`vwldAssetId`, `mapAssetId`, `characterAssetId`, `buildingAssetIds`, `audioAssetIds`, …), so a
11
+ * container OR a key added to the schema tomorrow is caught without editing this file.
12
+ */
13
+ function isReferenceKey(key) {
14
+ return key === 'assetId' || /^[A-Za-z]\w*AssetIds?$/.test(key);
15
+ }
16
+ /**
17
+ * Does this value name the asset? Plural keys hold an ARRAY of ids, which a bare `===` could never
18
+ * match — the second half of the same bug.
19
+ */
20
+ function namesAsset(value, assetId) {
21
+ if (typeof value === 'string')
22
+ return value === assetId;
23
+ return Array.isArray(value) && value.some((item) => item === assetId);
24
+ }
25
+ /** Root array of placed instances — the only references `--force` removes rather than reports. */
26
+ const PLACED_OBJECTS_KEY = 'environmentObjects';
27
+ /**
28
+ * Every place in the document where one of the reference keys holds `assetId`. The asset's own
29
+ * `assets[]` entry is skipped — its `id` is not a reference key, but a vehicle asset's own
30
+ * `vehicleFitment` might one day carry one, so the whole `assets[]` subtree is excluded outright.
31
+ */
32
+ export function findAssetReferences(world, assetId) {
33
+ const found = [];
34
+ const walk = (value, path, inPlacedObjects) => {
35
+ if (Array.isArray(value)) {
36
+ value.forEach((item, index) => walk(item, `${path}[${index}]`, inPlacedObjects));
37
+ return;
38
+ }
39
+ if (!isJsonObject(value))
40
+ return;
41
+ for (const [key, child] of Object.entries(value)) {
42
+ const childPath = path === '' ? key : `${path}.${key}`;
43
+ if (path === '' && key === 'assets')
44
+ continue;
45
+ if (isReferenceKey(key) && namesAsset(child, assetId)) {
46
+ found.push({ path: childPath, placed: inPlacedObjects && key === 'assetId' });
47
+ continue;
48
+ }
49
+ walk(child, childPath, inPlacedObjects || (path === '' && key === PLACED_OBJECTS_KEY));
50
+ }
51
+ };
52
+ walk(world, '', false);
53
+ return found;
54
+ }
55
+ export function planAssetRemoval(world, assetId, force) {
56
+ const assets = Array.isArray(world.assets) ? world.assets : [];
57
+ const asset = assets.find((item) => isJsonObject(item) && item.id === assetId);
58
+ if (asset === undefined) {
59
+ throw new CliError(`No asset with id "${assetId}" in this project's world.json. `
60
+ + '`bitmagic assets list` shows the ids. world.json is unchanged.');
61
+ }
62
+ const references = findAssetReferences(world, assetId);
63
+ const placed = references.filter((ref) => ref.placed);
64
+ const others = references.filter((ref) => !ref.placed);
65
+ if (references.length > 0 && !force) {
66
+ const name = typeof asset.name === 'string' ? asset.name : assetId;
67
+ const lines = references.slice(0, 8).map((ref) => ` - ${ref.path}`);
68
+ if (references.length > 8)
69
+ lines.push(` - …and ${references.length - 8} more`);
70
+ throw new CliError(`"${name}" is still referenced ${references.length === 1 ? 'once' : `${references.length} times`} in world.json:\n`
71
+ + `${lines.join('\n')}\n`
72
+ + 'Removing it would leave those pointing at nothing. Re-run with --force to remove the asset '
73
+ + `and its ${placed.length} placed instance${placed.length === 1 ? '' : 's'} together`
74
+ + (others.length > 0 ? ' (the other references stay and are reported)' : '')
75
+ + '. Game code that names the id (playSound, spawnFromAsset, …) is not checked. world.json is unchanged.');
76
+ }
77
+ const modifications = [{
78
+ type: 'removeRoot',
79
+ path: ['assets'],
80
+ predicate: (item) => isJsonObject(item) && item.id === assetId,
81
+ }];
82
+ if (placed.length > 0) {
83
+ modifications.push({
84
+ type: 'removeRoot',
85
+ path: ['environmentObjects'],
86
+ predicate: (item) => isJsonObject(item) && item.assetId === assetId,
87
+ });
88
+ }
89
+ return { asset, modifications, placedInstances: placed.length, otherReferences: others };
90
+ }
91
+ //# sourceMappingURL=remove.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remove.js","sourceRoot":"","sources":["../../src/assets/remove.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import type { Environment } from '../config/environments.js';
2
+ import type { ProjectContext } from '../project/context.js';
3
+ /** Bake resolution and scale. Defaults live with the commands, which document them to creators. */
4
+ export interface VoxelizeFlags {
5
+ minVoxelSize: number;
6
+ maxVoxelSize: number;
7
+ /** Metres. Absent = the GLB's own size, as the Creator's voxelize dialog leaves it by default. */
8
+ targetHeight?: number;
9
+ fillInterior: boolean;
10
+ }
11
+ export interface VoxelizeGlbRequest {
12
+ context: ProjectContext;
13
+ environment: Environment;
14
+ token: string;
15
+ /** Asset name; also what the engine builds its uploaded `.vxl` filename from. */
16
+ name: string;
17
+ glbUrl: string;
18
+ /** The id to register under. The engine echoes it back, and callers must check that it did. */
19
+ assetId: string;
20
+ voxel: VoxelizeFlags;
21
+ /** What the creator should do if this fails — what is kept, and the command that resumes it. */
22
+ retryHint: string;
23
+ /** Distinguishes concurrent requests in the page; also names the lane in the transport log. */
24
+ requestPrefix: string;
25
+ log: (message: string) => void;
26
+ }
27
+ /** Runs the bake and returns the engine's asset record. Injected in tests, real everywhere else. */
28
+ export type VoxelizeGlb = (request: VoxelizeGlbRequest) => Promise<Record<string, unknown>>;
29
+ export declare const voxelizeGlbIntoAsset: VoxelizeGlb;
30
+ /**
31
+ * The engine ECHOES the id it was given — but only since it learned to honour a supplied id for an
32
+ * asset that does not exist yet. An older vendored engine mints its own, and since the caller's
33
+ * upsert matches on the id it ASKED for, a mismatch would silently append under a different id
34
+ * than the one reported: the command prints an id absent from world.json, `assets remove` cannot
35
+ * find it, and a retry adds a second copy instead of replacing.
36
+ */
37
+ export declare function assertEngineHonouredId(engineAsset: Record<string, unknown>, assetId: string, name: string, keptHint: string): void;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Bake one GLB into a NEW voxel asset, in the project's own headless engine.
3
+ *
4
+ * Two lanes need exactly this and differ only in where the GLB came from:
5
+ *
6
+ * - `bitmagic assets add model.glb` uploads a file the creator already has;
7
+ * - `bitmagic generate model --prompt …` pays the Asset Forger for one.
8
+ *
9
+ * From the moment there is a URL the work is identical — fetch it in the engine, voxelize to the
10
+ * requested resolution, upload the `.vxl`, hand back the asset record — so it lives here once
11
+ * rather than in both. `generate prop` deliberately does NOT use this: it upgrades an EXISTING
12
+ * asset in place, constrained to that asset's recorded `fitBox`, which is a different operation
13
+ * with a different precondition (see generate/prop.ts).
14
+ */
15
+ import { CliError } from '../errors.js';
16
+ import { VOXELIZE_TIMEOUT_MS, withVoxelizeSession } from '../forge/voxelize-session.js';
17
+ import { isJsonObject } from '../project/world-json.js';
18
+ export const voxelizeGlbIntoAsset = async (request) => {
19
+ const { context, environment, token, name, glbUrl, assetId, voxel, retryHint, requestPrefix, log } = request;
20
+ return withVoxelizeSession({
21
+ context, environment, token, log, subject: `"${name}"`, retryHint,
22
+ }, async ({ host, port }) => {
23
+ log(`Voxelizing "${name}" at ${voxel.minVoxelSize}m…`);
24
+ const result = await host.transport.sendAndWait({
25
+ type: 'CREATE_ASSET_FROM_GLB_URL',
26
+ requestId: `${requestPrefix}-${assetId}-${port}`,
27
+ name,
28
+ glbUrl,
29
+ assetId,
30
+ options: {
31
+ minVoxelSize: voxel.minVoxelSize,
32
+ maxVoxelSize: voxel.maxVoxelSize,
33
+ ...(voxel.targetHeight !== undefined ? { targetHeight: voxel.targetHeight } : {}),
34
+ fillInterior: voxel.fillInterior,
35
+ // No `fitBox`: there is no placed instance to fit. `placeholder: false` because this is
36
+ // a finished asset, never a World-Forger stand-in awaiting regeneration.
37
+ placeholder: false,
38
+ },
39
+ }, 'ASSET_FROM_GLB_URL_RESULT', VOXELIZE_TIMEOUT_MS, { maxRetries: 1 });
40
+ if (!result) {
41
+ throw new CliError(`Voxelizing "${name}" timed out. ${retryHint}`);
42
+ }
43
+ if (result.success === false || !isJsonObject(result.asset)) {
44
+ const reason = typeof result.error === 'string' ? result.error : 'unknown error';
45
+ throw new CliError(`Voxelizing "${name}" failed: ${reason}. ${retryHint}`);
46
+ }
47
+ return result.asset;
48
+ });
49
+ };
50
+ /**
51
+ * The engine ECHOES the id it was given — but only since it learned to honour a supplied id for an
52
+ * asset that does not exist yet. An older vendored engine mints its own, and since the caller's
53
+ * upsert matches on the id it ASKED for, a mismatch would silently append under a different id
54
+ * than the one reported: the command prints an id absent from world.json, `assets remove` cannot
55
+ * find it, and a retry adds a second copy instead of replacing.
56
+ */
57
+ export function assertEngineHonouredId(engineAsset, assetId, name, keptHint) {
58
+ const engineId = typeof engineAsset.id === 'string' ? engineAsset.id : '';
59
+ if (engineId === assetId)
60
+ return;
61
+ throw new CliError(`The engine registered "${name}" under id "${engineId || '(none)'}" instead of "${assetId}", so `
62
+ + 'this CLI cannot write it reliably. The project\'s vendored engine is too old — run '
63
+ + `\`bitmagic upgrade\`, then retry. world.json is unchanged (${keptHint}).`);
64
+ }
65
+ //# sourceMappingURL=voxelize-glb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voxelize-glb.js","sourceRoot":"","sources":["../../src/assets/voxelize-glb.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAExC,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAExF,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AA+BxD,MAAM,CAAC,MAAM,oBAAoB,GAAgB,KAAK,EAAE,OAAO,EAAE,EAAE;IACjE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC;IAC7G,OAAO,mBAAmB,CAAC;QACzB,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,IAAI,GAAG,EAAE,SAAS;KAClE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE;QAC1B,GAAG,CAAC,eAAe,IAAI,QAAQ,KAAK,CAAC,YAAY,IAAI,CAAC,CAAC;QACvD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,WAAW,CAC7C;YACE,IAAI,EAAE,2BAA2B;YACjC,SAAS,EAAE,GAAG,aAAa,IAAI,OAAO,IAAI,IAAI,EAAE;YAChD,IAAI;YACJ,MAAM;YACN,OAAO;YACP,OAAO,EAAE;gBACP,YAAY,EAAE,KAAK,CAAC,YAAY;gBAChC,YAAY,EAAE,KAAK,CAAC,YAAY;gBAChC,GAAG,CAAC,KAAK,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACjF,YAAY,EAAE,KAAK,CAAC,YAAY;gBAChC,wFAAwF;gBACxF,yEAAyE;gBACzE,WAAW,EAAE,KAAK;aACnB;SACF,EACD,2BAA2B,EAC3B,mBAAmB,EACnB,EAAE,UAAU,EAAE,CAAC,EAAE,CAClB,CAAC;QAEF,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,QAAQ,CAAC,eAAe,IAAI,gBAAgB,SAAS,EAAE,CAAC,CAAC;QACrE,CAAC;QACD,IAAI,MAAM,CAAC,OAAO,KAAK,KAAK,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;YAC5D,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,eAAe,CAAC;YACjF,MAAM,IAAI,QAAQ,CAAC,eAAe,IAAI,aAAa,MAAM,KAAK,SAAS,EAAE,CAAC,CAAC;QAC7E,CAAC;QACD,OAAO,MAAM,CAAC,KAAK,CAAC;IACtB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CACpC,WAAoC,EACpC,OAAe,EACf,IAAY,EACZ,QAAgB;IAEhB,MAAM,QAAQ,GAAG,OAAO,WAAW,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1E,IAAI,QAAQ,KAAK,OAAO;QAAE,OAAO;IACjC,MAAM,IAAI,QAAQ,CAChB,0BAA0B,IAAI,eAAe,QAAQ,IAAI,QAAQ,iBAAiB,OAAO,QAAQ;UAC/F,qFAAqF;UACrF,8DAA8D,QAAQ,IAAI,CAC7E,CAAC;AACJ,CAAC"}