@series-inc/stowkit-cli 0.1.14 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/orchestrator.js +59 -65
- package/package.json +2 -2
- package/skill.md +20 -6
package/dist/orchestrator.js
CHANGED
|
@@ -318,10 +318,9 @@ export async function fullBuild(projectDir, opts) {
|
|
|
318
318
|
console.error(` [glb] ${container.id} FAILED: ${container.error}`);
|
|
319
319
|
}
|
|
320
320
|
}
|
|
321
|
-
// 2c. Process
|
|
322
|
-
const
|
|
323
|
-
const
|
|
324
|
-
const totalWork = glbMeshChildren.length + pending.length;
|
|
321
|
+
// 2c. Process all pending assets via worker pool (one queue, no split)
|
|
322
|
+
const pending = assets.filter(a => a.status === 'pending');
|
|
323
|
+
const totalWork = pending.length;
|
|
325
324
|
if (totalWork === 0) {
|
|
326
325
|
if (verbose)
|
|
327
326
|
console.log('All assets cached, nothing to process.');
|
|
@@ -330,39 +329,34 @@ export async function fullBuild(projectDir, opts) {
|
|
|
330
329
|
console.log(`Processing ${totalWork} asset(s)...`);
|
|
331
330
|
const pool = new WorkerPool({ wasmDir: opts?.wasmDir });
|
|
332
331
|
let processed = 0;
|
|
333
|
-
// Process GLB mesh children
|
|
334
|
-
for (const child of glbMeshChildren) {
|
|
335
|
-
const extract = glbExtracts.get(child.parentId);
|
|
336
|
-
if (!extract)
|
|
337
|
-
continue;
|
|
338
|
-
const mesh = extract.meshes.find(m => `${child.parentId}/${m.name}` === child.id);
|
|
339
|
-
if (!mesh)
|
|
340
|
-
continue;
|
|
341
|
-
try {
|
|
342
|
-
const { result, blobs } = await pool.processExtractedMesh({ childId: child.id, imported: mesh.imported, hasSkeleton: mesh.hasSkeleton, stringId: child.stringId, settings: child.settings });
|
|
343
|
-
for (const [key, data] of blobs)
|
|
344
|
-
BlobStore.setProcessed(key, data);
|
|
345
|
-
child.status = 'ready';
|
|
346
|
-
child.metadata = result.metadata;
|
|
347
|
-
child.processedSize = result.processedSize;
|
|
348
|
-
processed++;
|
|
349
|
-
if (verbose)
|
|
350
|
-
console.log(` [${processed}/${totalWork}] ${child.id} (glb-mesh)`);
|
|
351
|
-
}
|
|
352
|
-
catch (err) {
|
|
353
|
-
child.status = 'error';
|
|
354
|
-
child.error = err instanceof Error ? err.message : String(err);
|
|
355
|
-
processed++;
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
// Process remaining pending assets with concurrency limit
|
|
359
332
|
const queue = [...pending];
|
|
360
333
|
async function processNext() {
|
|
361
334
|
while (queue.length > 0) {
|
|
362
335
|
const asset = queue.shift();
|
|
363
336
|
const id = asset.id;
|
|
364
337
|
try {
|
|
365
|
-
//
|
|
338
|
+
// GLB mesh children use processExtractedMesh (needs parsed mesh data)
|
|
339
|
+
const isMeshChild = asset.parentId &&
|
|
340
|
+
(asset.type === AssetType.StaticMesh || asset.type === AssetType.SkinnedMesh);
|
|
341
|
+
if (isMeshChild) {
|
|
342
|
+
const extract = glbExtracts.get(asset.parentId);
|
|
343
|
+
if (!extract)
|
|
344
|
+
throw new Error(`No extract for parent ${asset.parentId}`);
|
|
345
|
+
const mesh = extract.meshes.find(m => `${asset.parentId}/${m.name}` === id);
|
|
346
|
+
if (!mesh)
|
|
347
|
+
throw new Error(`Mesh not found in extract: ${id}`);
|
|
348
|
+
const { result, blobs } = await pool.processExtractedMesh({ childId: id, imported: mesh.imported, hasSkeleton: mesh.hasSkeleton, stringId: asset.stringId, settings: asset.settings });
|
|
349
|
+
for (const [key, data] of blobs)
|
|
350
|
+
BlobStore.setProcessed(key, data);
|
|
351
|
+
asset.status = 'ready';
|
|
352
|
+
asset.metadata = result.metadata;
|
|
353
|
+
asset.processedSize = result.processedSize;
|
|
354
|
+
processed++;
|
|
355
|
+
if (verbose)
|
|
356
|
+
console.log(` [${processed}/${totalWork}] ${id} (glb-mesh)`);
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
// Everything else uses processAsset (source bytes)
|
|
366
360
|
let sourceData = BlobStore.getSource(id);
|
|
367
361
|
if (!sourceData) {
|
|
368
362
|
const data = await readFile(config.srcArtDir, id);
|
|
@@ -381,39 +375,39 @@ export async function fullBuild(projectDir, opts) {
|
|
|
381
375
|
asset.processedSize = result.processedSize;
|
|
382
376
|
processed++;
|
|
383
377
|
console.log(` [${processed}/${totalWork}] ${id} (${elapsed}ms)`);
|
|
384
|
-
// Write cache
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
378
|
+
// Write cache (only for top-level assets that have their own file on disk)
|
|
379
|
+
if (!asset.parentId) {
|
|
380
|
+
const cacheEntries = new Map();
|
|
381
|
+
const processedBlob = BlobStore.getProcessed(id);
|
|
382
|
+
if (processedBlob)
|
|
383
|
+
cacheEntries.set(id, processedBlob);
|
|
384
|
+
if (result.metadata) {
|
|
385
|
+
cacheEntries.set(`${id}:__metadata__`, new TextEncoder().encode(JSON.stringify(result.metadata)));
|
|
386
|
+
}
|
|
387
|
+
for (const suffix of [':skinnedMeta', ':animMeta', ':animCount']) {
|
|
388
|
+
const blob = BlobStore.getProcessed(`${id}${suffix}`);
|
|
389
|
+
if (blob)
|
|
390
|
+
cacheEntries.set(`${id}${suffix}`, blob);
|
|
391
|
+
}
|
|
392
|
+
const animCountBlob = BlobStore.getProcessed(`${id}:animCount`);
|
|
393
|
+
const clipCount = animCountBlob ? animCountBlob[0] : 0;
|
|
394
|
+
for (let ci = 0; ci < clipCount; ci++) {
|
|
395
|
+
const animData = BlobStore.getProcessed(`${id}:anim:${ci}`);
|
|
396
|
+
if (animData)
|
|
397
|
+
cacheEntries.set(`${id}:anim:${ci}`, animData);
|
|
398
|
+
const animMeta = BlobStore.getProcessed(`${id}:animMeta:${ci}`);
|
|
399
|
+
if (animMeta)
|
|
400
|
+
cacheEntries.set(`${id}:animMeta:${ci}`, animMeta);
|
|
401
|
+
}
|
|
402
|
+
if (cacheEntries.size > 0) {
|
|
403
|
+
await writeCacheBlobs(config.srcArtDir, id, cacheEntries);
|
|
404
|
+
const snapshot = await getFileSnapshot(config.srcArtDir, id);
|
|
405
|
+
if (snapshot) {
|
|
406
|
+
const meta = await readStowmeta(config.srcArtDir, id);
|
|
407
|
+
if (meta) {
|
|
408
|
+
meta.cache = buildCacheStamp(snapshot, asset.type, asset.settings);
|
|
409
|
+
await writeStowmeta(config.srcArtDir, id, meta);
|
|
410
|
+
}
|
|
417
411
|
}
|
|
418
412
|
}
|
|
419
413
|
}
|
|
@@ -427,7 +421,7 @@ export async function fullBuild(projectDir, opts) {
|
|
|
427
421
|
}
|
|
428
422
|
}
|
|
429
423
|
const workers = [];
|
|
430
|
-
for (let i = 0; i < Math.min(maxConcurrent,
|
|
424
|
+
for (let i = 0; i < Math.min(maxConcurrent, queue.length); i++) {
|
|
431
425
|
workers.push(processNext());
|
|
432
426
|
}
|
|
433
427
|
await Promise.all(workers);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@series-inc/stowkit-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"stowkit": "./dist/cli.js"
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"dev": "tsc --watch"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@series-inc/stowkit-packer-gui": "^0.1.
|
|
20
|
+
"@series-inc/stowkit-packer-gui": "^0.1.8",
|
|
21
21
|
"@series-inc/stowkit-editor": "^0.1.2",
|
|
22
22
|
"draco3d": "^1.5.7",
|
|
23
23
|
"fbx-parser": "^2.1.3",
|
package/skill.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
StowKit is a game asset pipeline that compresses and packs assets into `.stow` binary files for runtime loading.
|
|
4
4
|
|
|
5
|
+
## CRITICAL: Never manually create .stowmeta files
|
|
6
|
+
|
|
7
|
+
`.stowmeta` files are **auto-generated by the CLI**. To add any asset to the project:
|
|
8
|
+
|
|
9
|
+
1. Place the source file (GLB, PNG, FBX, WAV, etc.) into the `srcArtDir`
|
|
10
|
+
2. Run `npx stowkit build` (or `npx stowkit scan`)
|
|
11
|
+
3. The CLI detects the new file, generates the correct `.stowmeta` with proper defaults, and processes it
|
|
12
|
+
|
|
13
|
+
**Do not write `.stowmeta` files by hand.** Only edit an existing `.stowmeta` after it has been generated by the CLI (e.g. to change quality settings, pack assignment, or stringId). The same applies to GLB children — the `children` array is populated automatically on the first build.
|
|
14
|
+
|
|
15
|
+
The only file you should manually create is `.stowmat` (material schema) files — these are user-authored material definitions, not generated metadata.
|
|
16
|
+
|
|
5
17
|
## Project Structure
|
|
6
18
|
|
|
7
19
|
A StowKit project has a `.felicityproject` JSON file at its root:
|
|
@@ -56,9 +68,9 @@ All commands default to the current directory.
|
|
|
56
68
|
|
|
57
69
|
**GLB/GLTF is the recommended format for 3D models.** Dropping a `.glb` file into the project is the easiest way to get meshes, textures, materials, and animations into the pipeline — everything is extracted and processed automatically. FBX and OBJ are still supported as standalone mesh formats but lack the automatic material/texture extraction that GLB provides.
|
|
58
70
|
|
|
59
|
-
## .stowmeta Files
|
|
71
|
+
## .stowmeta Files (auto-generated — do not create manually)
|
|
60
72
|
|
|
61
|
-
Every source asset gets a `.stowmeta` sidecar file
|
|
73
|
+
Every source asset gets a `.stowmeta` sidecar file generated by `npx stowkit build` or `npx stowkit scan`. These files control processing settings and should only be **edited** (never created) by hand. The examples below are reference for understanding and editing existing files.
|
|
62
74
|
|
|
63
75
|
**Texture example:**
|
|
64
76
|
```json
|
|
@@ -355,10 +367,12 @@ Add `*.stowcache` to `.gitignore`.
|
|
|
355
367
|
|
|
356
368
|
### Other common tasks
|
|
357
369
|
|
|
358
|
-
- **Add a texture:**
|
|
359
|
-
- **
|
|
360
|
-
- **
|
|
361
|
-
- **
|
|
370
|
+
- **Add a texture:** Place PNG/JPG into `assets/`, run `npx stowkit build`. The CLI auto-generates the `.stowmeta`. Do NOT create it yourself.
|
|
371
|
+
- **Add audio:** Place WAV/MP3/OGG into `assets/`, run `npx stowkit build`. Same rule — never manually create `.stowmeta`.
|
|
372
|
+
- **Add an FBX mesh:** Place FBX into `assets/`, run `npx stowkit build`.
|
|
373
|
+
- **Change compression quality:** Edit the **existing** `.stowmeta` file's quality/resize fields (after it was generated by a build/scan), then `npx stowkit build`
|
|
374
|
+
- **Create a material:** Create a `.stowmat` JSON file in `assets/` (this is the one file type you DO create manually), then run `npx stowkit build`
|
|
375
|
+
- **Assign material to mesh:** Edit the mesh's **existing** `.stowmeta` to add `materialOverrides`
|
|
362
376
|
- **Check project health:** Run `npx stowkit status`
|
|
363
377
|
- **Full rebuild:** `npx stowkit build --force`
|
|
364
378
|
- **Clean orphaned files:** `npx stowkit clean`
|