@bitmagic/cli 0.1.52-dev.1 → 0.1.52-dev.11
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/README.md +217 -11
- package/dist/assets/add.d.ts +40 -0
- package/dist/assets/add.js +229 -0
- package/dist/assets/add.js.map +1 -0
- package/dist/assets/entry.d.ts +44 -0
- package/dist/assets/entry.js +71 -0
- package/dist/assets/entry.js.map +1 -0
- package/dist/assets/file-kind.d.ts +61 -0
- package/dist/assets/file-kind.js +195 -0
- package/dist/assets/file-kind.js.map +1 -0
- package/dist/assets/list.d.ts +27 -0
- package/dist/assets/list.js +50 -0
- package/dist/assets/list.js.map +1 -0
- package/dist/assets/remove.d.ts +50 -0
- package/dist/assets/remove.js +91 -0
- package/dist/assets/remove.js.map +1 -0
- package/dist/assets/voxelize-glb.d.ts +37 -0
- package/dist/assets/voxelize-glb.js +65 -0
- package/dist/assets/voxelize-glb.js.map +1 -0
- package/dist/assets/vxl-summary.d.ts +32 -0
- package/dist/assets/vxl-summary.js +118 -0
- package/dist/assets/vxl-summary.js.map +1 -0
- package/dist/bin.js +25 -0
- package/dist/bin.js.map +1 -1
- package/dist/cli.d.ts +12 -0
- package/dist/cli.js +15 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/assets.d.ts +56 -0
- package/dist/commands/assets.js +328 -0
- package/dist/commands/assets.js.map +1 -0
- package/dist/commands/cover.js +2 -3
- package/dist/commands/cover.js.map +1 -1
- package/dist/commands/forge.d.ts +2 -9
- package/dist/commands/forge.js +2 -13
- package/dist/commands/forge.js.map +1 -1
- package/dist/commands/generate.d.ts +41 -9
- package/dist/commands/generate.js +234 -54
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/judge.d.ts +7 -7
- package/dist/commands/judge.js +53 -25
- package/dist/commands/judge.js.map +1 -1
- package/dist/commands/levels.d.ts +33 -0
- package/dist/commands/levels.js +241 -0
- package/dist/commands/levels.js.map +1 -0
- package/dist/commands/number-flag.d.ts +7 -0
- package/dist/commands/number-flag.js +38 -0
- package/dist/commands/number-flag.js.map +1 -0
- package/dist/commands/publish.d.ts +6 -1
- package/dist/commands/publish.js +42 -15
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/run-deps.d.ts +27 -0
- package/dist/commands/run-deps.js +43 -0
- package/dist/commands/run-deps.js.map +1 -0
- package/dist/commands/verify.d.ts +4 -0
- package/dist/commands/verify.js +94 -51
- package/dist/commands/verify.js.map +1 -1
- package/dist/editor/journal.d.ts +48 -1
- package/dist/editor/journal.js +44 -0
- package/dist/editor/journal.js.map +1 -1
- package/dist/editor/previews.d.ts +6 -0
- package/dist/editor/previews.js +16 -0
- package/dist/editor/previews.js.map +1 -1
- package/dist/editor/server.js +228 -53
- package/dist/editor/server.js.map +1 -1
- package/dist/editor/shell-page.js +221 -116
- package/dist/editor/shell-page.js.map +1 -1
- package/dist/editor/voxel-save.d.ts +47 -0
- package/dist/editor/voxel-save.js +49 -0
- package/dist/editor/voxel-save.js.map +1 -0
- package/dist/errors.d.ts +10 -0
- package/dist/errors.js +16 -0
- package/dist/errors.js.map +1 -1
- package/dist/forge/apply-modifications.d.ts +18 -1
- package/dist/forge/apply-modifications.js +15 -32
- package/dist/forge/apply-modifications.js.map +1 -1
- package/dist/forge/browser-host.js +9 -4
- package/dist/forge/browser-host.js.map +1 -1
- package/dist/forge/voxelize-session.d.ts +61 -0
- package/dist/forge/voxelize-session.js +137 -0
- package/dist/forge/voxelize-session.js.map +1 -0
- package/dist/generate/model.d.ts +36 -0
- package/dist/generate/model.js +90 -0
- package/dist/generate/model.js.map +1 -0
- package/dist/generate/prop.d.ts +52 -4
- package/dist/generate/prop.js +169 -102
- package/dist/generate/prop.js.map +1 -1
- package/dist/generate/stream.d.ts +7 -0
- package/dist/generate/stream.js +2 -1
- package/dist/generate/stream.js.map +1 -1
- package/dist/generate/vehicle.js +5 -85
- package/dist/generate/vehicle.js.map +1 -1
- package/dist/http/signed-upload.d.ts +55 -0
- package/dist/http/signed-upload.js +168 -0
- package/dist/http/signed-upload.js.map +1 -0
- package/dist/judge/facts.js +72 -57
- package/dist/judge/facts.js.map +1 -1
- package/dist/judge/record.d.ts +3 -0
- package/dist/judge/record.js +3 -0
- package/dist/judge/record.js.map +1 -1
- package/dist/levels/registry.d.ts +88 -0
- package/dist/levels/registry.js +309 -0
- package/dist/levels/registry.js.map +1 -0
- package/dist/messages/check.d.ts +30 -0
- package/dist/messages/check.js +119 -0
- package/dist/messages/check.js.map +1 -0
- package/dist/messages/notice.d.ts +58 -0
- package/dist/messages/notice.js +54 -0
- package/dist/messages/notice.js.map +1 -0
- package/dist/messages/refresh.d.ts +19 -0
- package/dist/messages/refresh.js +42 -0
- package/dist/messages/refresh.js.map +1 -0
- package/dist/project/jobs.d.ts +1 -1
- package/dist/project/jobs.js.map +1 -1
- package/dist/project/platform-declaration.d.ts +51 -0
- package/dist/project/platform-declaration.js +81 -0
- package/dist/project/platform-declaration.js.map +1 -0
- package/dist/publish/client.d.ts +13 -0
- package/dist/publish/client.js +13 -34
- package/dist/publish/client.js.map +1 -1
- package/dist/publish/platform.d.ts +42 -0
- package/dist/publish/platform.js +58 -0
- package/dist/publish/platform.js.map +1 -0
- package/dist/scaffold/project-files.js +124 -22
- package/dist/scaffold/project-files.js.map +1 -1
- package/dist/verify/artifacts.d.ts +19 -0
- package/dist/verify/artifacts.js +34 -0
- package/dist/verify/artifacts.js.map +1 -0
- package/dist/verify/browser.d.ts +10 -0
- package/dist/verify/browser.js +79 -9
- package/dist/verify/browser.js.map +1 -1
- package/dist/verify/classify.d.ts +37 -1
- package/dist/verify/classify.js +66 -2
- package/dist/verify/classify.js.map +1 -1
- package/dist/verify/device.d.ts +43 -0
- package/dist/verify/device.js +59 -0
- package/dist/verify/device.js.map +1 -0
- package/dist/verify/iterate.d.ts +21 -2
- package/dist/verify/iterate.js +43 -36
- package/dist/verify/iterate.js.map +1 -1
- package/dist/verify/mobile-parity.d.ts +42 -0
- package/dist/verify/mobile-parity.js +84 -0
- package/dist/verify/mobile-parity.js.map +1 -0
- package/dist/verify/platform.d.ts +47 -0
- package/dist/verify/platform.js +89 -0
- package/dist/verify/platform.js.map +1 -0
- package/dist/verify/result.d.ts +90 -6
- package/dist/verify/result.js +89 -10
- package/dist/verify/result.js.map +1 -1
- package/dist/verify/url.d.ts +9 -1
- package/dist/verify/url.js +10 -2
- package/dist/verify/url.js.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
function baseEntry(input, type) {
|
|
2
|
+
return {
|
|
3
|
+
id: input.id,
|
|
4
|
+
name: input.name,
|
|
5
|
+
url: input.url,
|
|
6
|
+
type,
|
|
7
|
+
size: input.size,
|
|
8
|
+
production: {
|
|
9
|
+
method: 'uploaded',
|
|
10
|
+
at: input.at,
|
|
11
|
+
sourceUrl: input.url,
|
|
12
|
+
sourceName: input.sourceName,
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/** An image: the thumbnail IS the image, as the Creator and the image generator both record. */
|
|
17
|
+
export function imageAssetEntry(input) {
|
|
18
|
+
return { ...baseEntry(input, 'image'), screenshotUrl: input.url };
|
|
19
|
+
}
|
|
20
|
+
export function audioAssetEntry(input) {
|
|
21
|
+
return baseEntry(input, 'audio');
|
|
22
|
+
}
|
|
23
|
+
export function jsonAssetEntry(input) {
|
|
24
|
+
return baseEntry(input, 'json');
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A `.vxl` uploaded as-is, with the metadata its own header states — the fields the Creator gets
|
|
28
|
+
* back from `/api/analyze-asset` for the same file.
|
|
29
|
+
*/
|
|
30
|
+
export function vxlAssetEntry(input, summary) {
|
|
31
|
+
return {
|
|
32
|
+
...baseEntry(input, 'vxl'),
|
|
33
|
+
boundingBox: summary.boundingBox,
|
|
34
|
+
// NOT optional beside `voxelSize`. VXL3 stores world-space bounds, but the engine treats
|
|
35
|
+
// `boundingBox && !boundingBoxInMeters && voxelSize && !sourceGlbUrl` as a pre-1.1 asset whose
|
|
36
|
+
// box is in VOXEL GRID units and multiplies all six components by `voxelSize`
|
|
37
|
+
// (EnvironmentObjectSystem's legacy migration) — a silent 10-20x shrink that then mis-sizes
|
|
38
|
+
// terrain flattening, placement clearance and destruction. Every in-engine writer sets this
|
|
39
|
+
// flag for the same reason; the Creator's upload dodges it only by never recording a voxelSize.
|
|
40
|
+
boundingBoxInMeters: true,
|
|
41
|
+
voxelSize: summary.minVoxelSize,
|
|
42
|
+
fragmentCount: summary.fragmentCount,
|
|
43
|
+
...(summary.voxelCount !== undefined ? { voxelCount: summary.voxelCount } : {}),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/** The Creator's keep-as-GLB import: `type: 'glb'`, the mesh as its own source, 2 m tall by default. */
|
|
47
|
+
export const DEFAULT_GLB_TARGET_HEIGHT = 2.0;
|
|
48
|
+
export function glbAssetEntry(input, targetHeight) {
|
|
49
|
+
return {
|
|
50
|
+
...baseEntry(input, 'glb'),
|
|
51
|
+
sourceGlbUrl: input.url,
|
|
52
|
+
targetHeight: targetHeight ?? DEFAULT_GLB_TARGET_HEIGHT,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The record the engine returned from `CREATE_ASSET_FROM_GLB_URL`, re-stamped as an upload. The
|
|
57
|
+
* engine's own `production` says `generated` with the GLB as `sourceUrl`; the method is corrected,
|
|
58
|
+
* the source URL kept (it is the uploaded GLB), and the creator's filename added.
|
|
59
|
+
*/
|
|
60
|
+
export function voxelizedGlbAssetEntry(engineAsset, input) {
|
|
61
|
+
return {
|
|
62
|
+
...engineAsset,
|
|
63
|
+
production: {
|
|
64
|
+
method: 'uploaded',
|
|
65
|
+
at: input.at,
|
|
66
|
+
sourceUrl: input.url,
|
|
67
|
+
sourceName: input.sourceName,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=entry.js.map
|
|
@@ -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":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAexC,MAAM,YAAY,GAA2B;IAC3C,GAAG,EAAE,oBAAoB;IACzB,EAAE,EAAE,gBAAgB;IACpB,GAAG,EAAE,sBAAsB;IAC3B,GAAG,EAAE,0BAA0B;IAC/B,GAAG,EAAE,0BAA0B;IAC/B,GAAG,EAAE,0BAA0B;IAC/B,GAAG,EAAE,OAAO;CACb,CAAC;AAEF,MAAM,WAAW,GAA2B;IAC1C,GAAG,EAAE,WAAW;IAChB,GAAG,EAAE,YAAY;IACjB,IAAI,EAAE,YAAY;IAClB,IAAI,EAAE,YAAY;CACnB,CAAC;AAEF,MAAM,WAAW,GAA2B;IAC1C,IAAI,EAAE,WAAW;IACjB,GAAG,EAAE,WAAW;IAChB,GAAG,EAAE,WAAW;IAChB,GAAG,EAAE,YAAY;CAClB,CAAC;AAEF,iFAAiF;AACjF,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;AAErH;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACrC,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAChD,MAAM,SAAS,GAAG,WAAW,KAAK,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACjG,OAAO,SAAS,CAAC,WAAW,EAAE,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,QAAgB,EAAE,OAA6B;IAC/E,MAAM,SAAS,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC1C,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAErC,IAAI,OAAO,CAAC,OAAO,IAAI,SAAS,KAAK,KAAK,EAAE,CAAC;QAC3C,MAAM,IAAI,QAAQ,CAAC,+CAA+C,IAAI,uCAAuC,CAAC,CAAC;IACjH,CAAC;IACD,IAAI,SAAS,KAAK,KAAK,EAAE,CAAC;QACxB,OAAO,OAAO,CAAC,OAAO;YACpB,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,KAAK,EAAE,WAAW,EAAE,mBAAmB,EAAE,SAAS,EAAE;YACrF,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,SAAS,EAAE,KAAK,EAAE,WAAW,EAAE,mBAAmB,EAAE,SAAS,EAAE,CAAC;IAC9F,CAAC;IACD,IAAI,SAAS,KAAK,KAAK,EAAE,CAAC;QACxB,0FAA0F;QAC1F,sDAAsD;QACtD,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,WAAW,EAAE,0BAA0B,EAAE,SAAS,EAAE,CAAC;IAC/F,CAAC;IACD,MAAM,SAAS,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;IACzC,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC;IAClF,CAAC;IACD,MAAM,SAAS,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;IACzC,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC;IAClF,CAAC;IACD,IAAI,SAAS,KAAK,MAAM,EAAE,CAAC;QACzB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,kBAAkB,EAAE,SAAS,EAAE,CAAC;IACzF,CAAC;IAED,IAAI,SAAS,KAAK,MAAM,EAAE,CAAC;QACzB,MAAM,IAAI,QAAQ,CAChB,GAAG,IAAI,0FAA0F;cAC/F,+FAA+F,CAClG,CAAC;IACJ,CAAC;IACD,MAAM,WAAW,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC;IAC5C,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;QAC9B,MAAM,IAAI,QAAQ,CAChB,IAAI,SAAS,KAAK,WAAW,8CAA8C;cACzE,+EAA+E,CAClF,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,QAAQ,CAChB,cAAc,IAAI,oBAAoB,mBAAmB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;UAC1F,0BAA0B,CAC7B,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACrC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IAC/C,gGAAgG;IAChG,gGAAgG;IAChG,OAAO,QAAQ,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAC/D,CAAC;AAED;;;;;;GAMG;AACH,gGAAgG;AAChG,MAAM,UAAU,GAAG,0BAA0B,CAAC;AAE9C,kGAAkG;AAClG,MAAM,eAAe,GAAG,GAAG,CAAC;AAE5B,SAAS,cAAc,CAAC,KAAa;IACnC,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC9C,MAAM,IAAI,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAC/B,OAAO,IAAI,GAAG,IAAI,IAAI,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,0BAA0B,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,GAAG,GAAG,CAAC;AACpG,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5B,IAAI,OAAO,KAAK,EAAE;QAAE,MAAM,IAAI,QAAQ,CAAC,0DAA0D,CAAC,CAAC;IACnG,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,QAAQ,CAChB,kBAAkB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,oEAAoE,CAC3G,CAAC;IACJ,CAAC;IACD,IAAI,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,QAAQ,CAChB,kBAAkB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,mBAAmB,cAAc,CAAC,OAAO,CAAC,mBAAmB;cACjG,oDAAoD,CACvD,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,GAAG,eAAe,EAAE,CAAC;QACrC,MAAM,IAAI,QAAQ,CAChB,qBAAqB,OAAO,CAAC,MAAM,8BAA8B,eAAe,4BAA4B,CAC7G,CAAC;IACJ,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,QAAgB;IAC7C,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACrC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9C,MAAM,IAAI,QAAQ,CAChB,UAAU,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,8DAA8D;cAC1F,0BAA0B,CAC7B,CAAC;IACJ,CAAC;IACD,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,QAAQ,CAAC,UAAU,IAAI,0EAA0E,CAAC,CAAC;IAC/G,CAAC;IACD,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,QAAQ,CAChB,UAAU,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,qCAAqC,cAAc,CAAC,IAAI,CAAC,gBAAgB;cACrG,uDAAuD,CAC1D,CAAC;IACJ,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,GAAG,eAAe,EAAE,CAAC;QAClC,MAAM,IAAI,QAAQ,CAChB,UAAU,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,mCAAmC,eAAe,eAAe;cAC7F,0BAA0B,CAC7B,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,MAAoB,IAAI,CAAC,GAAG,EAAE,SAAuB,IAAI,CAAC,MAAM;IAC1F,OAAO,SAAS,GAAG,EAAE,IAAI,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;AACnE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,SAAiB;IAClD,IAAI,SAAS,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IACtC,OAAO,gBAAgB,SAAS,sEAAsE;UAClG,gBAAgB,SAAS,iCAAiC,CAAC;AACjE,CAAC"}
|
|
@@ -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":"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"}
|
|
@@ -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;
|