@series-inc/stowkit-cli 0.6.34 → 0.6.36
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/ai-tagger.d.ts +13 -0
- package/dist/ai-tagger.js +95 -0
- package/dist/app/disk-project.d.ts +3 -0
- package/dist/assets-package.d.ts +0 -14
- package/dist/assets-package.js +1 -4
- package/dist/cli.js +19 -33
- package/dist/firestore.d.ts +42 -0
- package/dist/firestore.js +273 -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 +227 -35
- package/dist/store.d.ts +6 -21
- package/dist/store.js +24 -116
- package/package.json +6 -4
- 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/gcs.js
CHANGED
|
@@ -1,69 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import * as path from 'node:path';
|
|
3
|
-
import * as crypto from 'node:crypto';
|
|
4
|
-
// ─── JWT Auth ────────────────────────────────────────────────────────────────
|
|
5
|
-
function base64url(data) {
|
|
6
|
-
const buf = typeof data === 'string' ? Buffer.from(data) : data;
|
|
7
|
-
return buf.toString('base64url');
|
|
8
|
-
}
|
|
9
|
-
function createJWT(sa) {
|
|
10
|
-
const now = Math.floor(Date.now() / 1000);
|
|
11
|
-
const header = { alg: 'RS256', typ: 'JWT' };
|
|
12
|
-
const payload = {
|
|
13
|
-
iss: sa.client_email,
|
|
14
|
-
scope: 'https://www.googleapis.com/auth/devstorage.read_write',
|
|
15
|
-
aud: 'https://oauth2.googleapis.com/token',
|
|
16
|
-
iat: now,
|
|
17
|
-
exp: now + 3600,
|
|
18
|
-
};
|
|
19
|
-
const segments = `${base64url(JSON.stringify(header))}.${base64url(JSON.stringify(payload))}`;
|
|
20
|
-
const sign = crypto.createSign('RSA-SHA256');
|
|
21
|
-
sign.update(segments);
|
|
22
|
-
const signature = sign.sign(sa.private_key);
|
|
23
|
-
return `${segments}.${base64url(signature)}`;
|
|
24
|
-
}
|
|
25
|
-
async function getAccessToken(sa) {
|
|
26
|
-
const jwt = createJWT(sa);
|
|
27
|
-
const res = await fetch('https://oauth2.googleapis.com/token', {
|
|
28
|
-
method: 'POST',
|
|
29
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
30
|
-
body: `grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=${jwt}`,
|
|
31
|
-
});
|
|
32
|
-
if (!res.ok) {
|
|
33
|
-
const text = await res.text();
|
|
34
|
-
throw new Error(`GCS auth failed (${res.status}): ${text}`);
|
|
35
|
-
}
|
|
36
|
-
const data = await res.json();
|
|
37
|
-
return data.access_token;
|
|
38
|
-
}
|
|
39
|
-
// ─── Credential Resolution ───────────────────────────────────────────────────
|
|
40
|
-
async function loadServiceAccount(projectDir) {
|
|
41
|
-
// Search order: project dir, cwd, GOOGLE_APPLICATION_CREDENTIALS env
|
|
42
|
-
const candidates = [
|
|
43
|
-
path.join(projectDir, 'service_account.json'),
|
|
44
|
-
path.join(process.cwd(), 'service_account.json'),
|
|
45
|
-
];
|
|
46
|
-
for (const candidate of candidates) {
|
|
47
|
-
try {
|
|
48
|
-
const text = await fs.readFile(candidate, 'utf-8');
|
|
49
|
-
return JSON.parse(text);
|
|
50
|
-
}
|
|
51
|
-
catch { /* not found */ }
|
|
52
|
-
}
|
|
53
|
-
// Fall back to GOOGLE_APPLICATION_CREDENTIALS
|
|
54
|
-
const envPath = process.env.GOOGLE_APPLICATION_CREDENTIALS;
|
|
55
|
-
if (envPath) {
|
|
56
|
-
try {
|
|
57
|
-
const text = await fs.readFile(envPath, 'utf-8');
|
|
58
|
-
return JSON.parse(text);
|
|
59
|
-
}
|
|
60
|
-
catch {
|
|
61
|
-
throw new Error(`Could not read service account from GOOGLE_APPLICATION_CREDENTIALS: ${envPath}`);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
throw new Error('No GCS credentials found. Place service_account.json in project root, ' +
|
|
65
|
-
'current directory, or set GOOGLE_APPLICATION_CREDENTIALS environment variable.');
|
|
66
|
-
}
|
|
1
|
+
import { loadServiceAccount, getAccessToken, GCS_SCOPE } from './gcp-auth.js';
|
|
67
2
|
// ─── Bucket Name Parsing ─────────────────────────────────────────────────────
|
|
68
3
|
function parseBucket(bucketUri) {
|
|
69
4
|
// Accept "gs://bucket-name" or just "bucket-name"
|
|
@@ -74,7 +9,7 @@ function parseBucket(bucketUri) {
|
|
|
74
9
|
// ─── GCS Client Factory ─────────────────────────────────────────────────────
|
|
75
10
|
export async function createGCSClient(projectDir, bucketUri) {
|
|
76
11
|
const sa = await loadServiceAccount(projectDir);
|
|
77
|
-
const token = await getAccessToken(sa);
|
|
12
|
+
const token = await getAccessToken(sa, [GCS_SCOPE]);
|
|
78
13
|
const bucket = parseBucket(bucketUri);
|
|
79
14
|
const apiBase = `https://storage.googleapis.com`;
|
|
80
15
|
return {
|
|
@@ -109,50 +44,5 @@ export async function createGCSClient(projectDir, bucketUri) {
|
|
|
109
44
|
}
|
|
110
45
|
return res.text();
|
|
111
46
|
},
|
|
112
|
-
async downloadWithGeneration(objectPath) {
|
|
113
|
-
const encoded = encodeURIComponent(objectPath);
|
|
114
|
-
const url = `${apiBase}/storage/v1/b/${bucket}/o/${encoded}?alt=media`;
|
|
115
|
-
const res = await fetch(url, {
|
|
116
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
117
|
-
});
|
|
118
|
-
if (res.status === 404)
|
|
119
|
-
return null;
|
|
120
|
-
if (!res.ok) {
|
|
121
|
-
const text = await res.text();
|
|
122
|
-
throw new Error(`GCS download failed for ${objectPath} (${res.status}): ${text}`);
|
|
123
|
-
}
|
|
124
|
-
const data = await res.text();
|
|
125
|
-
const generation = res.headers.get('x-goog-generation') ?? '0';
|
|
126
|
-
return { data, generation };
|
|
127
|
-
},
|
|
128
|
-
async uploadWithGeneration(objectPath, data, generation, contentType = 'application/json') {
|
|
129
|
-
const encoded = encodeURIComponent(objectPath);
|
|
130
|
-
let url = `${apiBase}/upload/storage/v1/b/${bucket}/o?uploadType=media&name=${encoded}`;
|
|
131
|
-
const headers = {
|
|
132
|
-
Authorization: `Bearer ${token}`,
|
|
133
|
-
'Content-Type': contentType,
|
|
134
|
-
};
|
|
135
|
-
// Optimistic concurrency: if we have a generation, require it to match
|
|
136
|
-
if (generation) {
|
|
137
|
-
headers['x-goog-if-generation-match'] = generation;
|
|
138
|
-
}
|
|
139
|
-
else {
|
|
140
|
-
// Object should not exist yet
|
|
141
|
-
headers['x-goog-if-generation-match'] = '0';
|
|
142
|
-
}
|
|
143
|
-
const res = await fetch(url, {
|
|
144
|
-
method: 'POST',
|
|
145
|
-
headers,
|
|
146
|
-
body: data,
|
|
147
|
-
});
|
|
148
|
-
if (res.status === 412) {
|
|
149
|
-
throw new Error(`Registry was modified by another publish while uploading. ` +
|
|
150
|
-
`Please retry the publish command.`);
|
|
151
|
-
}
|
|
152
|
-
if (!res.ok) {
|
|
153
|
-
const text = await res.text();
|
|
154
|
-
throw new Error(`GCS upload failed for ${objectPath} (${res.status}): ${text}`);
|
|
155
|
-
}
|
|
156
|
-
},
|
|
157
47
|
};
|
|
158
48
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -30,5 +30,7 @@ export { syncRuntimeAssets } from './sync-runtime-assets.js';
|
|
|
30
30
|
export { publishPackage } from './publish.js';
|
|
31
31
|
export type { PublishOptions, PublishResult } from './publish.js';
|
|
32
32
|
export * from './assets-package.js';
|
|
33
|
-
export {
|
|
33
|
+
export { searchAssets, listStorePackages, resolveAssetDeps } from './store.js';
|
|
34
34
|
export type { SearchResult, PackageInfo } from './store.js';
|
|
35
|
+
export { createFirestoreReader, createFirestoreClient } from './firestore.js';
|
|
36
|
+
export type { FirestoreReader, FirestoreClient, FirestorePackageDoc, FirestoreVersionDoc } from './firestore.js';
|
package/dist/index.js
CHANGED
|
@@ -39,4 +39,6 @@ export { syncRuntimeAssets } from './sync-runtime-assets.js';
|
|
|
39
39
|
export { publishPackage } from './publish.js';
|
|
40
40
|
export * from './assets-package.js';
|
|
41
41
|
// Store
|
|
42
|
-
export {
|
|
42
|
+
export { searchAssets, listStorePackages, resolveAssetDeps } from './store.js';
|
|
43
|
+
// Firestore
|
|
44
|
+
export { createFirestoreReader, createFirestoreClient } from './firestore.js';
|
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!`);
|