@series-inc/stowkit-cli 0.6.23 → 0.6.35
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/assets-package.d.ts +0 -14
- package/dist/assets-package.js +1 -4
- package/dist/firestore.d.ts +42 -0
- package/dist/firestore.js +240 -0
- package/dist/gcp-auth.d.ts +9 -0
- package/dist/gcp-auth.js +69 -0
- package/dist/gcs.d.ts +0 -5
- package/dist/gcs.js +2 -112
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/init.js +121 -10
- package/dist/publish.js +17 -40
- package/dist/server.js +58 -38
- package/dist/store.d.ts +6 -7
- package/dist/store.js +36 -39
- package/package.json +3 -2
- package/templates/engine/index.html +12 -0
- package/templates/engine/package.json +32 -0
- package/templates/engine/prefabs/build.json +1 -0
- package/templates/engine/scripts/copy-decoders.mjs +40 -0
- package/templates/engine/src/Game.ts +84 -0
- package/templates/engine/src/Prefabs.ts +28 -0
- package/templates/engine/src/main.ts +16 -0
- package/templates/engine/src/styles/main.css +22 -0
- package/templates/engine/stubs/capacitor.ts +6 -0
- package/templates/engine/tsconfig.json +27 -0
- package/templates/engine/vite-env.d.ts +1 -0
- package/templates/engine/vite.config.ts +39 -0
package/dist/init.js
CHANGED
|
@@ -95,6 +95,7 @@ export async function initProject(projectDir, opts) {
|
|
|
95
95
|
const stowkitIgnores = [
|
|
96
96
|
'# StowKit',
|
|
97
97
|
'*.stowcache',
|
|
98
|
+
'.stow-thumbnails/',
|
|
98
99
|
'public/cdn-assets/',
|
|
99
100
|
'Open Packer.bat',
|
|
100
101
|
'open-packer.sh',
|
|
@@ -104,6 +105,16 @@ export async function initProject(projectDir, opts) {
|
|
|
104
105
|
if (!existing.includes('*.stowcache')) {
|
|
105
106
|
await fs.writeFile(gitignorePath, existing.trimEnd() + '\n\n' + stowkitIgnores + '\n');
|
|
106
107
|
}
|
|
108
|
+
else {
|
|
109
|
+
// Existing project — ensure newer ignore entries are present
|
|
110
|
+
let updated = existing;
|
|
111
|
+
if (!existing.includes('.stow-thumbnails/')) {
|
|
112
|
+
updated = updated.trimEnd() + '\n.stow-thumbnails/\n';
|
|
113
|
+
}
|
|
114
|
+
if (updated !== existing) {
|
|
115
|
+
await fs.writeFile(gitignorePath, updated);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
107
118
|
}
|
|
108
119
|
catch {
|
|
109
120
|
await fs.writeFile(gitignorePath, stowkitIgnores + '\n');
|
|
@@ -118,35 +129,135 @@ export async function initProject(projectDir, opts) {
|
|
|
118
129
|
console.log(` Config: .felicityproject`);
|
|
119
130
|
console.log(` AI skills: .claude/skills/stowkit/SKILL.md, .cursor/rules/stowkit.mdc`);
|
|
120
131
|
console.log(` Launcher: Open Packer.bat (Windows) / open-packer.sh (macOS/Linux)`);
|
|
132
|
+
// Add engine-specific gitignore entries
|
|
133
|
+
if (withEngine) {
|
|
134
|
+
const engineIgnores = [
|
|
135
|
+
'# Engine (generated by postinstall)',
|
|
136
|
+
'node_modules/',
|
|
137
|
+
'dist/',
|
|
138
|
+
'public/basis/',
|
|
139
|
+
'public/stowkit/',
|
|
140
|
+
'public/stowkit_reader.wasm',
|
|
141
|
+
].join('\n');
|
|
142
|
+
try {
|
|
143
|
+
const existing = await fs.readFile(gitignorePath, 'utf-8');
|
|
144
|
+
if (!existing.includes('public/stowkit_reader.wasm')) {
|
|
145
|
+
await fs.writeFile(gitignorePath, existing.trimEnd() + '\n\n' + engineIgnores + '\n');
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// gitignore should exist from earlier step, but handle gracefully
|
|
150
|
+
}
|
|
151
|
+
}
|
|
121
152
|
// Install engine if selected
|
|
122
153
|
if (withEngine) {
|
|
123
154
|
await installEngine(absDir);
|
|
124
155
|
}
|
|
125
156
|
console.log('');
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
157
|
+
if (withEngine) {
|
|
158
|
+
console.log('Ready to go! Run:');
|
|
159
|
+
console.log(' npm run dev');
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
console.log('Drop your assets (PNG, JPG, FBX, WAV, etc.) into assets/');
|
|
163
|
+
console.log('Then run: stowkit build');
|
|
164
|
+
console.log('Or double-click "Open Packer.bat" to launch the packer GUI.');
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
async function copyTemplateFiles(templateDir, targetDir) {
|
|
168
|
+
const copied = [];
|
|
169
|
+
const entries = await fs.readdir(templateDir, { withFileTypes: true });
|
|
170
|
+
for (const entry of entries) {
|
|
171
|
+
const src = path.join(templateDir, entry.name);
|
|
172
|
+
const dest = path.join(targetDir, entry.name);
|
|
173
|
+
if (entry.isDirectory()) {
|
|
174
|
+
const sub = await copyTemplateFiles(src, dest);
|
|
175
|
+
copied.push(...sub);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
// Skip package.json — handled separately via merge logic
|
|
179
|
+
if (entry.name === 'package.json')
|
|
180
|
+
continue;
|
|
181
|
+
try {
|
|
182
|
+
await fs.access(dest);
|
|
183
|
+
// File exists — don't clobber
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
await fs.mkdir(path.dirname(dest), { recursive: true });
|
|
187
|
+
await fs.copyFile(src, dest);
|
|
188
|
+
copied.push(path.relative(targetDir, dest));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return copied;
|
|
193
|
+
}
|
|
194
|
+
async function mergePackageJson(templatePkgPath, targetDir) {
|
|
195
|
+
const templatePkg = JSON.parse(await fs.readFile(templatePkgPath, 'utf-8'));
|
|
196
|
+
const targetPkgPath = path.join(targetDir, 'package.json');
|
|
197
|
+
let targetPkg;
|
|
198
|
+
try {
|
|
199
|
+
targetPkg = JSON.parse(await fs.readFile(targetPkgPath, 'utf-8'));
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
// No existing package.json — use template as-is
|
|
203
|
+
templatePkg.name = path.basename(targetDir);
|
|
204
|
+
await fs.writeFile(targetPkgPath, JSON.stringify(templatePkg, null, 2) + '\n');
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
// Merge scripts (don't overwrite existing scripts)
|
|
208
|
+
targetPkg.scripts = { ...templatePkg.scripts, ...targetPkg.scripts };
|
|
209
|
+
// Ensure postinstall includes copy-decoders
|
|
210
|
+
if (!targetPkg.scripts.postinstall?.includes('copy-decoders')) {
|
|
211
|
+
targetPkg.scripts.postinstall = templatePkg.scripts.postinstall;
|
|
212
|
+
}
|
|
213
|
+
// Merge dependencies
|
|
214
|
+
for (const depType of ['dependencies', 'devDependencies', 'peerDependencies']) {
|
|
215
|
+
if (templatePkg[depType]) {
|
|
216
|
+
targetPkg[depType] = { ...templatePkg[depType], ...(targetPkg[depType] ?? {}) };
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// Ensure type: module
|
|
220
|
+
if (!targetPkg.type)
|
|
221
|
+
targetPkg.type = 'module';
|
|
222
|
+
await fs.writeFile(targetPkgPath, JSON.stringify(targetPkg, null, 2) + '\n');
|
|
129
223
|
}
|
|
130
224
|
async function installEngine(absDir) {
|
|
225
|
+
const thisDir = path.dirname(fileURLToPath(import.meta.url));
|
|
226
|
+
const templateDir = path.resolve(thisDir, '../templates/engine');
|
|
227
|
+
// Verify template directory exists
|
|
228
|
+
try {
|
|
229
|
+
await fs.access(templateDir);
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
console.error('Engine template not found. Please update @series-inc/stowkit-cli.');
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
131
235
|
console.log('');
|
|
132
|
-
console.log('
|
|
236
|
+
console.log('Setting up 3D engine project...');
|
|
237
|
+
// Copy template files (skip existing, skip package.json)
|
|
238
|
+
const copied = await copyTemplateFiles(templateDir, absDir);
|
|
239
|
+
if (copied.length > 0) {
|
|
240
|
+
console.log(` Created: ${copied.join(', ')}`);
|
|
241
|
+
}
|
|
242
|
+
// Merge or create package.json
|
|
243
|
+
await mergePackageJson(path.join(templateDir, 'package.json'), absDir);
|
|
244
|
+
// Install dependencies
|
|
245
|
+
console.log(' Installing dependencies...');
|
|
133
246
|
const { execSync } = await import('node:child_process');
|
|
134
247
|
try {
|
|
135
|
-
execSync('npm install
|
|
248
|
+
execSync('npm install', {
|
|
136
249
|
cwd: absDir,
|
|
137
250
|
stdio: 'inherit',
|
|
138
251
|
});
|
|
139
252
|
}
|
|
140
253
|
catch {
|
|
141
|
-
console.error('Failed to install
|
|
142
|
-
console.error(' npm install @series-inc/rundot-3d-engine three');
|
|
254
|
+
console.error('Failed to install dependencies. Run `npm install` manually.');
|
|
143
255
|
return;
|
|
144
256
|
}
|
|
257
|
+
// Copy engine skill files
|
|
145
258
|
await copyEngineSkillFiles(absDir);
|
|
146
259
|
console.log('');
|
|
147
|
-
console.log(' 3D Engine
|
|
148
|
-
console.log(' @series-inc/rundot-3d-engine');
|
|
149
|
-
console.log(' three');
|
|
260
|
+
console.log(' 3D Engine project ready!');
|
|
150
261
|
console.log(' AI skills: .claude/skills/stowkit-engine/SKILL.md, .cursor/rules/stowkit-engine.mdc');
|
|
151
262
|
}
|
|
152
263
|
async function copySkillFiles(absDir) {
|
package/dist/publish.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as fs from 'node:fs/promises';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import { readProjectConfig, scanDirectory } from './node-fs.js';
|
|
4
|
-
import { readAssetsPackage, initAssetsPackage,
|
|
4
|
+
import { readAssetsPackage, initAssetsPackage, } from './assets-package.js';
|
|
5
|
+
import { createFirestoreClient } from './firestore.js';
|
|
5
6
|
import { createGCSClient } from './gcs.js';
|
|
6
7
|
import { readStowmeta } from './app/stowmeta-io.js';
|
|
7
8
|
// ─── Build full dependency graph from srcArtDir ──────────────────────────────
|
|
@@ -145,7 +146,7 @@ export async function publishPackage(projectDir, opts = {}) {
|
|
|
145
146
|
const bucketUri = opts.bucket
|
|
146
147
|
?? pkg.bucket
|
|
147
148
|
?? process.env.STOWKIT_BUCKET
|
|
148
|
-
?? 'gs://
|
|
149
|
+
?? 'gs://rungame-shared-assets-test';
|
|
149
150
|
log(`Bucket: ${bucketUri}`);
|
|
150
151
|
// Step 4: Scan srcArtDir
|
|
151
152
|
const scan = await scanDirectory(project.srcArtDir);
|
|
@@ -252,29 +253,15 @@ export async function publishPackage(projectDir, opts = {}) {
|
|
|
252
253
|
}
|
|
253
254
|
// Step 7: Create GCS client and auth
|
|
254
255
|
const emitProgress = opts.onProgress ?? (() => { });
|
|
255
|
-
const totalUploads = filesToUpload.length + Object.keys(thumbMap).length + 2; // +2 for assets-package.json and
|
|
256
|
+
const totalUploads = filesToUpload.length + Object.keys(thumbMap).length + 2; // +2 for assets-package.json and Firestore writes
|
|
256
257
|
let totalDone = 0;
|
|
257
258
|
log('Authenticating with GCS...');
|
|
258
259
|
emitProgress({ phase: 'auth', done: 0, total: totalUploads, message: 'Authenticating with GCS...' });
|
|
259
260
|
const gcs = await createGCSClient(projectDir, bucketUri);
|
|
260
|
-
// Step 8:
|
|
261
|
-
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
let generation;
|
|
265
|
-
if (registryResult) {
|
|
266
|
-
registry = JSON.parse(registryResult.data);
|
|
267
|
-
generation = registryResult.generation;
|
|
268
|
-
vlog(`Registry loaded (generation: ${generation})`);
|
|
269
|
-
}
|
|
270
|
-
else {
|
|
271
|
-
registry = createEmptyRegistry();
|
|
272
|
-
generation = null;
|
|
273
|
-
vlog('No existing registry — will create new one');
|
|
274
|
-
}
|
|
275
|
-
// Step 9: Check version not already published
|
|
276
|
-
const existingPkg = registry.packages[pkg.name];
|
|
277
|
-
if (existingPkg?.versions[pkg.version] && !opts.force) {
|
|
261
|
+
// Step 8: Create Firestore client and check version
|
|
262
|
+
const firestore = await createFirestoreClient(projectDir);
|
|
263
|
+
const existingVer = await firestore.getVersion(pkg.name, pkg.version);
|
|
264
|
+
if (existingVer && !opts.force) {
|
|
278
265
|
throw new Error(`Version ${pkg.version} of "${pkg.name}" is already published. ` +
|
|
279
266
|
`Bump the version in assets-package.json or use --force to overwrite.`);
|
|
280
267
|
}
|
|
@@ -338,27 +325,17 @@ export async function publishPackage(projectDir, opts = {}) {
|
|
|
338
325
|
}
|
|
339
326
|
catch { /* file doesn't exist, try next */ }
|
|
340
327
|
}
|
|
341
|
-
// Step 13:
|
|
328
|
+
// Step 13: Write to Firestore
|
|
342
329
|
log('Updating registry...');
|
|
343
|
-
if (!registry.packages[pkg.name]) {
|
|
344
|
-
registry.packages[pkg.name] = {
|
|
345
|
-
description: pkg.description,
|
|
346
|
-
author: pkg.author,
|
|
347
|
-
tags: pkg.tags ?? [],
|
|
348
|
-
latest: pkg.version,
|
|
349
|
-
versions: {},
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
const regPkg = registry.packages[pkg.name];
|
|
353
|
-
regPkg.versions[pkg.version] = versionEntry;
|
|
354
|
-
regPkg.latest = pkg.version;
|
|
355
|
-
regPkg.description = pkg.description;
|
|
356
|
-
regPkg.author = pkg.author;
|
|
357
|
-
regPkg.tags = pkg.tags ?? [];
|
|
358
|
-
if (packThumbnail)
|
|
359
|
-
regPkg.thumbnail = packThumbnail;
|
|
360
330
|
emitProgress({ phase: 'registry', done: totalDone, total: totalUploads, message: 'Updating registry...' });
|
|
361
|
-
await
|
|
331
|
+
await firestore.setVersion(pkg.name, pkg.version, versionEntry);
|
|
332
|
+
await firestore.setPackage(pkg.name, {
|
|
333
|
+
description: pkg.description,
|
|
334
|
+
author: pkg.author,
|
|
335
|
+
tags: pkg.tags ?? [],
|
|
336
|
+
latest: pkg.version,
|
|
337
|
+
thumbnail: packThumbnail ?? null,
|
|
338
|
+
});
|
|
362
339
|
totalDone++;
|
|
363
340
|
emitProgress({ phase: 'registry', done: totalDone, total: totalUploads, message: 'Done' });
|
|
364
341
|
log(`\nPublished ${pkg.name}@${pkg.version} successfully!`);
|
package/dist/server.js
CHANGED
|
@@ -55,6 +55,8 @@ function broadcast(msg) {
|
|
|
55
55
|
ws.send(data);
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
+
/** Tracks the most recent fire-and-forget processing promise so wait=true can join it. */
|
|
59
|
+
let activeProcessingTask = null;
|
|
58
60
|
/**
|
|
59
61
|
* Single entry point for all processing orchestration.
|
|
60
62
|
* - Skips materials (they don't need processing)
|
|
@@ -136,10 +138,15 @@ function queueProcessing(opts = {}) {
|
|
|
136
138
|
}
|
|
137
139
|
broadcast({ type: 'processing-complete' });
|
|
138
140
|
})();
|
|
141
|
+
// Track the active task so wait=true callers can join in-flight processing
|
|
142
|
+
activeProcessingTask = task.finally(() => {
|
|
143
|
+
if (activeProcessingTask === task)
|
|
144
|
+
activeProcessingTask = null;
|
|
145
|
+
});
|
|
139
146
|
if (opts.await)
|
|
140
|
-
return
|
|
147
|
+
return activeProcessingTask;
|
|
141
148
|
// Fire-and-forget: log errors but don't block caller
|
|
142
|
-
|
|
149
|
+
activeProcessingTask.catch(err => console.error('[server] queueProcessing error:', err));
|
|
143
150
|
return Promise.resolve();
|
|
144
151
|
}
|
|
145
152
|
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
@@ -1028,10 +1035,16 @@ async function handleRequest(req, res, staticApps) {
|
|
|
1028
1035
|
json(res, { error: 'No project open' }, 400);
|
|
1029
1036
|
return;
|
|
1030
1037
|
}
|
|
1031
|
-
// If wait=true,
|
|
1038
|
+
// If wait=true, ensure all asset processing completes before building.
|
|
1039
|
+
// First join any in-flight processing, then start a new run for any remaining pending assets.
|
|
1032
1040
|
const body = await readBody(req).then(b => b ? JSON.parse(b) : {}).catch(() => ({}));
|
|
1033
1041
|
const wait = body.wait === true || url.searchParams.get('wait') === 'true';
|
|
1034
1042
|
if (wait) {
|
|
1043
|
+
// Await any already-running background processing
|
|
1044
|
+
if (activeProcessingTask) {
|
|
1045
|
+
await activeProcessingTask;
|
|
1046
|
+
}
|
|
1047
|
+
// Pick up any assets that became pending after the previous run
|
|
1035
1048
|
const pending = assets.filter(a => a.status === 'pending' || a.status === 'processing');
|
|
1036
1049
|
if (pending.length > 0) {
|
|
1037
1050
|
await queueProcessing({ await: true });
|
|
@@ -1767,21 +1780,28 @@ async function handleRequest(req, res, staticApps) {
|
|
|
1767
1780
|
// GET /api/asset-store/registry — fetch the public registry
|
|
1768
1781
|
if (pathname === '/api/asset-store/registry' && req.method === 'GET') {
|
|
1769
1782
|
try {
|
|
1770
|
-
const
|
|
1771
|
-
const
|
|
1772
|
-
const
|
|
1773
|
-
const
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1783
|
+
const { createFirestoreReader } = await import('./firestore.js');
|
|
1784
|
+
const reader = createFirestoreReader();
|
|
1785
|
+
const allPackages = await reader.listPackages();
|
|
1786
|
+
const packages = {};
|
|
1787
|
+
for (const { name, data: pkg } of allPackages) {
|
|
1788
|
+
const versionKeys = await reader.listVersionKeys(name);
|
|
1789
|
+
const versions = {};
|
|
1790
|
+
for (const ver of versionKeys) {
|
|
1791
|
+
const versionDoc = await reader.getVersion(name, ver);
|
|
1792
|
+
if (versionDoc)
|
|
1793
|
+
versions[ver] = versionDoc;
|
|
1777
1794
|
}
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1795
|
+
packages[name] = {
|
|
1796
|
+
description: pkg.description,
|
|
1797
|
+
author: pkg.author,
|
|
1798
|
+
tags: pkg.tags ?? [],
|
|
1799
|
+
latest: pkg.latest,
|
|
1800
|
+
thumbnail: pkg.thumbnail,
|
|
1801
|
+
versions,
|
|
1802
|
+
};
|
|
1782
1803
|
}
|
|
1783
|
-
|
|
1784
|
-
json(res, registry);
|
|
1804
|
+
json(res, { schemaVersion: 1, packages });
|
|
1785
1805
|
}
|
|
1786
1806
|
catch (err) {
|
|
1787
1807
|
json(res, { error: err.message }, 500);
|
|
@@ -1797,9 +1817,11 @@ async function handleRequest(req, res, staticApps) {
|
|
|
1797
1817
|
const bucketParam = url.searchParams.get('bucket') ?? undefined;
|
|
1798
1818
|
const limitParam = url.searchParams.get('limit');
|
|
1799
1819
|
const limit = limitParam ? parseInt(limitParam, 10) : undefined;
|
|
1800
|
-
const {
|
|
1801
|
-
const
|
|
1802
|
-
|
|
1820
|
+
const { createFirestoreReader } = await import('./firestore.js');
|
|
1821
|
+
const { searchAssets } = await import('./store.js');
|
|
1822
|
+
const reader = createFirestoreReader();
|
|
1823
|
+
const bucket = (bucketParam ?? 'rungame-shared-assets-test').replace(/^gs:\/\//, '').replace(/\/$/, '');
|
|
1824
|
+
let results = await searchAssets(reader, bucket, query, { type, package: pkg });
|
|
1803
1825
|
if (limit && limit > 0)
|
|
1804
1826
|
results = results.slice(0, limit);
|
|
1805
1827
|
json(res, results);
|
|
@@ -1813,9 +1835,11 @@ async function handleRequest(req, res, staticApps) {
|
|
|
1813
1835
|
if (pathname === '/api/asset-store/packages' && req.method === 'GET') {
|
|
1814
1836
|
try {
|
|
1815
1837
|
const bucketParam = url.searchParams.get('bucket') ?? undefined;
|
|
1816
|
-
const {
|
|
1817
|
-
const
|
|
1818
|
-
const
|
|
1838
|
+
const { createFirestoreReader } = await import('./firestore.js');
|
|
1839
|
+
const { listStorePackages } = await import('./store.js');
|
|
1840
|
+
const reader = createFirestoreReader();
|
|
1841
|
+
const bucket = (bucketParam ?? 'rungame-shared-assets-test').replace(/^gs:\/\//, '').replace(/\/$/, '');
|
|
1842
|
+
const packages = await listStorePackages(reader, bucket);
|
|
1819
1843
|
json(res, packages);
|
|
1820
1844
|
}
|
|
1821
1845
|
catch (err) {
|
|
@@ -1827,22 +1851,22 @@ async function handleRequest(req, res, staticApps) {
|
|
|
1827
1851
|
if (pathname.startsWith('/api/asset-store/package/') && req.method === 'GET') {
|
|
1828
1852
|
try {
|
|
1829
1853
|
const packageName = decodeURIComponent(pathname.slice('/api/asset-store/package/'.length));
|
|
1830
|
-
const
|
|
1831
|
-
const
|
|
1832
|
-
const
|
|
1833
|
-
const pkg = registry.packages[packageName];
|
|
1854
|
+
const { createFirestoreReader } = await import('./firestore.js');
|
|
1855
|
+
const reader = createFirestoreReader();
|
|
1856
|
+
const pkg = await reader.getPackage(packageName);
|
|
1834
1857
|
if (!pkg) {
|
|
1835
1858
|
json(res, { error: `Package "${packageName}" not found` }, 404);
|
|
1836
1859
|
return;
|
|
1837
1860
|
}
|
|
1838
|
-
const ver =
|
|
1861
|
+
const ver = await reader.getVersion(packageName, pkg.latest);
|
|
1862
|
+
const versionKeys = await reader.listVersionKeys(packageName);
|
|
1839
1863
|
json(res, {
|
|
1840
1864
|
name: packageName,
|
|
1841
1865
|
description: pkg.description,
|
|
1842
1866
|
author: pkg.author,
|
|
1843
1867
|
tags: pkg.tags ?? [],
|
|
1844
1868
|
latest: pkg.latest,
|
|
1845
|
-
versions:
|
|
1869
|
+
versions: versionKeys,
|
|
1846
1870
|
assets: ver?.assets ?? [],
|
|
1847
1871
|
});
|
|
1848
1872
|
}
|
|
@@ -1864,21 +1888,17 @@ async function handleRequest(req, res, staticApps) {
|
|
|
1864
1888
|
json(res, { error: 'Missing packageName, version, or stringIds' }, 400);
|
|
1865
1889
|
return;
|
|
1866
1890
|
}
|
|
1867
|
-
const bucketName = (bucketParam ?? '
|
|
1891
|
+
const bucketName = (bucketParam ?? 'rungame-shared-assets-test').replace(/^gs:\/\//, '').replace(/\/$/, '');
|
|
1868
1892
|
const baseUrl = `https://storage.googleapis.com/${bucketName}`;
|
|
1869
|
-
//
|
|
1870
|
-
const
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
return;
|
|
1874
|
-
}
|
|
1875
|
-
const registry = await regRes.json();
|
|
1876
|
-
const pkg = registry.packages[packageName];
|
|
1893
|
+
// Resolve deps via Firestore
|
|
1894
|
+
const { createFirestoreReader } = await import('./firestore.js');
|
|
1895
|
+
const reader = createFirestoreReader();
|
|
1896
|
+
const pkg = await reader.getPackage(packageName);
|
|
1877
1897
|
if (!pkg) {
|
|
1878
1898
|
json(res, { error: `Package "${packageName}" not found` }, 404);
|
|
1879
1899
|
return;
|
|
1880
1900
|
}
|
|
1881
|
-
const ver =
|
|
1901
|
+
const ver = await reader.getVersion(packageName, version);
|
|
1882
1902
|
if (!ver) {
|
|
1883
1903
|
json(res, { error: `Version "${version}" not found` }, 404);
|
|
1884
1904
|
return;
|
package/dist/store.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare function fetchRegistry(bucket?: string): Promise<Registry>;
|
|
1
|
+
import type { FirestoreReader } from './firestore.js';
|
|
3
2
|
export interface SearchResult {
|
|
4
3
|
stringId: string;
|
|
5
4
|
type: string;
|
|
@@ -27,15 +26,15 @@ export interface PackageInfo {
|
|
|
27
26
|
/** Full URL to pack-level thumbnail, if uploaded */
|
|
28
27
|
thumbnailUrl?: string;
|
|
29
28
|
}
|
|
30
|
-
export declare function searchAssets(
|
|
29
|
+
export declare function searchAssets(firestore: FirestoreReader, bucket: string, query: string, opts?: {
|
|
31
30
|
type?: string;
|
|
32
31
|
package?: string;
|
|
33
|
-
}): SearchResult[]
|
|
34
|
-
export declare function
|
|
35
|
-
export declare function resolveAssetDeps(
|
|
32
|
+
}): Promise<SearchResult[]>;
|
|
33
|
+
export declare function listStorePackages(firestore: FirestoreReader, bucket: string): Promise<PackageInfo[]>;
|
|
34
|
+
export declare function resolveAssetDeps(firestore: FirestoreReader, packageName: string, stringIds: string[], version?: string): Promise<{
|
|
36
35
|
resolvedIds: string[];
|
|
37
36
|
files: string[];
|
|
38
|
-
}
|
|
37
|
+
}>;
|
|
39
38
|
export declare function storeSearch(query: string, opts?: {
|
|
40
39
|
type?: string;
|
|
41
40
|
json?: boolean;
|
package/dist/store.js
CHANGED
|
@@ -1,31 +1,21 @@
|
|
|
1
1
|
import { resolveTransitiveDeps, resolveFiles } from './assets-package.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export async function fetchRegistry(bucket) {
|
|
5
|
-
const bucketName = (bucket ?? DEFAULT_BUCKET).replace(/^gs:\/\//, '').replace(/\/$/, '');
|
|
6
|
-
const url = `https://storage.googleapis.com/${bucketName}/registry.json?t=${Date.now()}`;
|
|
7
|
-
const res = await fetch(url);
|
|
8
|
-
if (!res.ok) {
|
|
9
|
-
if (res.status === 404)
|
|
10
|
-
return { schemaVersion: 1, packages: {} };
|
|
11
|
-
throw new Error(`Failed to fetch registry: ${res.status}`);
|
|
12
|
-
}
|
|
13
|
-
return res.json();
|
|
14
|
-
}
|
|
2
|
+
import { createFirestoreReader } from './firestore.js';
|
|
3
|
+
const DEFAULT_BUCKET = 'rungame-shared-assets-test';
|
|
15
4
|
// ─── Search ──────────────────────────────────────────────────────────────────
|
|
16
|
-
export function searchAssets(
|
|
5
|
+
export async function searchAssets(firestore, bucket, query, opts) {
|
|
17
6
|
// Support comma-separated terms: "coral, sea, ocean" matches any term
|
|
18
7
|
const terms = query.split(',').map(t => t.toLowerCase().trim()).filter(Boolean);
|
|
19
8
|
const scored = [];
|
|
20
|
-
const bucket = DEFAULT_BUCKET;
|
|
21
9
|
// When searching within a specific package, skip package-level metadata
|
|
22
10
|
// (name, description, tags) — it matches every asset and adds noise.
|
|
23
11
|
const skipPkgMeta = !!opts?.package;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
12
|
+
let packages = await firestore.listPackages();
|
|
13
|
+
if (opts?.package) {
|
|
14
|
+
packages = packages.filter(p => p.name === opts.package);
|
|
15
|
+
}
|
|
16
|
+
for (const { name: pkgName, data: pkg } of packages) {
|
|
27
17
|
const verStr = pkg.latest;
|
|
28
|
-
const ver =
|
|
18
|
+
const ver = await firestore.getVersion(pkgName, verStr);
|
|
29
19
|
if (!ver)
|
|
30
20
|
continue;
|
|
31
21
|
for (const asset of ver.assets) {
|
|
@@ -186,33 +176,36 @@ function scoreAsset(terms, asset, pkg, pkgName, skipPkgMeta = false) {
|
|
|
186
176
|
return total;
|
|
187
177
|
}
|
|
188
178
|
// ─── List Packages ───────────────────────────────────────────────────────────
|
|
189
|
-
export function
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
179
|
+
export async function listStorePackages(firestore, bucket) {
|
|
180
|
+
const packages = await firestore.listPackages();
|
|
181
|
+
const results = [];
|
|
182
|
+
for (const { name, data: pkg } of packages) {
|
|
183
|
+
const ver = await firestore.getVersion(name, pkg.latest);
|
|
184
|
+
const versionKeys = await firestore.listVersionKeys(name);
|
|
193
185
|
const thumbnailUrl = pkg.thumbnail
|
|
194
186
|
? `https://storage.googleapis.com/${bucket}/packages/${name}/${pkg.latest}/${pkg.thumbnail}`
|
|
195
187
|
: undefined;
|
|
196
|
-
|
|
188
|
+
results.push({
|
|
197
189
|
name,
|
|
198
190
|
description: pkg.description,
|
|
199
191
|
author: pkg.author,
|
|
200
192
|
tags: pkg.tags ?? [],
|
|
201
193
|
latest: pkg.latest,
|
|
202
|
-
versions:
|
|
194
|
+
versions: versionKeys,
|
|
203
195
|
assetCount: ver?.assets.length ?? 0,
|
|
204
196
|
totalSize: ver?.totalSize ?? 0,
|
|
205
197
|
thumbnailUrl,
|
|
206
|
-
};
|
|
207
|
-
}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
return results;
|
|
208
201
|
}
|
|
209
202
|
// ─── Resolve Dependencies ────────────────────────────────────────────────────
|
|
210
|
-
export function resolveAssetDeps(
|
|
211
|
-
const pkg =
|
|
203
|
+
export async function resolveAssetDeps(firestore, packageName, stringIds, version) {
|
|
204
|
+
const pkg = await firestore.getPackage(packageName);
|
|
212
205
|
if (!pkg)
|
|
213
206
|
throw new Error(`Package "${packageName}" not found`);
|
|
214
207
|
const verStr = version ?? pkg.latest;
|
|
215
|
-
const ver =
|
|
208
|
+
const ver = await firestore.getVersion(packageName, verStr);
|
|
216
209
|
if (!ver)
|
|
217
210
|
throw new Error(`Version "${verStr}" not found`);
|
|
218
211
|
const resolvedIds = resolveTransitiveDeps(stringIds, ver.assets);
|
|
@@ -221,8 +214,9 @@ export function resolveAssetDeps(registry, packageName, stringIds, version) {
|
|
|
221
214
|
}
|
|
222
215
|
// ─── CLI Commands ────────────────────────────────────────────────────────────
|
|
223
216
|
export async function storeSearch(query, opts) {
|
|
224
|
-
const
|
|
225
|
-
|
|
217
|
+
const bucket = (opts?.bucket ?? DEFAULT_BUCKET).replace(/^gs:\/\//, '').replace(/\/$/, '');
|
|
218
|
+
const firestore = createFirestoreReader();
|
|
219
|
+
let results = await searchAssets(firestore, bucket, query, { type: opts?.type });
|
|
226
220
|
if (opts?.limit && opts.limit > 0)
|
|
227
221
|
results = results.slice(0, opts.limit);
|
|
228
222
|
if (opts?.json) {
|
|
@@ -244,8 +238,9 @@ export async function storeSearch(query, opts) {
|
|
|
244
238
|
console.log('');
|
|
245
239
|
}
|
|
246
240
|
export async function storeList(opts) {
|
|
247
|
-
const
|
|
248
|
-
const
|
|
241
|
+
const bucket = (opts?.bucket ?? DEFAULT_BUCKET).replace(/^gs:\/\//, '').replace(/\/$/, '');
|
|
242
|
+
const firestore = createFirestoreReader();
|
|
243
|
+
const packages = await listStorePackages(firestore, bucket);
|
|
249
244
|
if (opts?.json) {
|
|
250
245
|
console.log(JSON.stringify(packages, null, 2));
|
|
251
246
|
return;
|
|
@@ -264,13 +259,15 @@ export async function storeList(opts) {
|
|
|
264
259
|
console.log('');
|
|
265
260
|
}
|
|
266
261
|
export async function storeInfo(packageName, opts) {
|
|
267
|
-
const
|
|
268
|
-
const
|
|
262
|
+
const bucket = (opts?.bucket ?? DEFAULT_BUCKET).replace(/^gs:\/\//, '').replace(/\/$/, '');
|
|
263
|
+
const firestore = createFirestoreReader();
|
|
264
|
+
const pkg = await firestore.getPackage(packageName);
|
|
269
265
|
if (!pkg) {
|
|
270
266
|
console.error(`Package "${packageName}" not found.`);
|
|
271
267
|
process.exit(1);
|
|
272
268
|
}
|
|
273
|
-
const ver =
|
|
269
|
+
const ver = await firestore.getVersion(packageName, pkg.latest);
|
|
270
|
+
const versionKeys = await firestore.listVersionKeys(packageName);
|
|
274
271
|
if (opts?.json) {
|
|
275
272
|
console.log(JSON.stringify({
|
|
276
273
|
name: packageName,
|
|
@@ -278,7 +275,7 @@ export async function storeInfo(packageName, opts) {
|
|
|
278
275
|
author: pkg.author,
|
|
279
276
|
tags: pkg.tags ?? [],
|
|
280
277
|
latest: pkg.latest,
|
|
281
|
-
versions:
|
|
278
|
+
versions: versionKeys,
|
|
282
279
|
assets: ver?.assets ?? [],
|
|
283
280
|
}, null, 2));
|
|
284
281
|
return;
|
|
@@ -290,7 +287,7 @@ export async function storeInfo(packageName, opts) {
|
|
|
290
287
|
console.log(` Author: ${pkg.author}`);
|
|
291
288
|
if (pkg.tags?.length)
|
|
292
289
|
console.log(` Tags: ${pkg.tags.join(', ')}`);
|
|
293
|
-
console.log(` Versions: ${
|
|
290
|
+
console.log(` Versions: ${versionKeys.join(', ')}`);
|
|
294
291
|
if (ver) {
|
|
295
292
|
console.log(`\nAssets (${ver.assets.length}):\n`);
|
|
296
293
|
for (const a of ver.assets) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@series-inc/stowkit-cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.35",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"stowkit": "./dist/cli.js"
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
12
|
"skill.md",
|
|
13
|
-
"wasm"
|
|
13
|
+
"wasm",
|
|
14
|
+
"templates"
|
|
14
15
|
],
|
|
15
16
|
"scripts": {
|
|
16
17
|
"build": "tsc",
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
|
|
6
|
+
<title>My Game</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<canvas id="renderCanvas"></canvas>
|
|
10
|
+
<script type="module" src="/src/main.ts"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|