@playdrop/playdrop-cli 0.3.8-build.3 → 0.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -1
- package/config/client-meta.json +5 -6
- package/dist/apps/build.js +43 -38
- package/dist/catalogue-utils.js +30 -18
- package/dist/catalogue.d.ts +0 -3
- package/dist/catalogue.js +80 -49
- package/dist/clientInfo.js +16 -2
- package/dist/commands/browse.js +10 -1
- package/dist/commands/capture.js +3 -2
- package/dist/commands/create.js +153 -54
- package/dist/commands/createRemixContent.js +61 -46
- package/dist/commands/creations.js +10 -1
- package/dist/commands/devServer.d.ts +1 -1
- package/dist/commands/devShared.js +1 -1
- package/dist/commands/generation.js +91 -74
- package/dist/commands/gettingStarted.js +1 -1
- package/dist/commands/init.js +30 -2
- package/dist/commands/search.js +10 -1
- package/dist/commands/upload-content.d.ts +70 -0
- package/dist/commands/upload-content.js +627 -0
- package/dist/commands/upload-graph.d.ts +23 -0
- package/dist/commands/upload-graph.js +108 -0
- package/dist/commands/upload.js +264 -543
- package/dist/http.d.ts +1 -1
- package/dist/playwright.d.ts +12 -4
- package/dist/proxyFetch.js +3 -2
- package/dist/taskSelection.js +2 -2
- package/node_modules/@playdrop/ai-client/dist/index.d.ts.map +1 -1
- package/node_modules/@playdrop/ai-client/dist/index.js +74 -54
- package/node_modules/@playdrop/api-client/dist/client.d.ts +20 -12
- package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/client.js +6 -8
- package/node_modules/@playdrop/api-client/dist/core/errors.js +11 -11
- package/node_modules/@playdrop/api-client/dist/core/request.d.ts +2 -0
- package/node_modules/@playdrop/api-client/dist/core/request.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/core/request.js +10 -3
- package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts +12 -10
- package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/admin.js +33 -30
- package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts +1 -0
- package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/apps.js +127 -128
- package/node_modules/@playdrop/api-client/dist/domains/asset-packs.d.ts +9 -5
- package/node_modules/@playdrop/api-client/dist/domains/asset-packs.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/asset-packs.js +151 -88
- package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts +1 -0
- package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/assets.js +150 -115
- package/node_modules/@playdrop/api-client/dist/domains/auth.d.ts +3 -1
- package/node_modules/@playdrop/api-client/dist/domains/auth.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/auth.js +21 -0
- package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts +1 -0
- package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/payments.js +10 -0
- package/node_modules/@playdrop/api-client/dist/index.d.ts +34 -31
- package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/index.js +19 -9
- package/node_modules/@playdrop/boxel-core/dist/src/entity-cleaner.js +2 -0
- package/node_modules/@playdrop/boxel-core/dist/src/entity-cleaner.js.map +1 -1
- package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/textured-builder.js +1 -1
- package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/textured-builder.js.map +1 -1
- package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/voxel-builder.js +1 -1
- package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/voxel-builder.js.map +1 -1
- package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/builder.js +95 -75
- package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/builder.js.map +1 -1
- package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/scanner.d.ts +2 -3
- package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/scanner.js.map +1 -1
- package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/textures/face-map.js +4 -4
- package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/textures/face-map.js.map +1 -1
- package/node_modules/@playdrop/boxel-core/dist/src/palette_tools.d.ts +2 -2
- package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/slice.d.ts +5 -5
- package/node_modules/@playdrop/boxel-core/dist/src/transforms/voxels/textured-to-voxel.d.ts +3 -3
- package/node_modules/@playdrop/boxel-core/dist/src/types.d.ts +25 -25
- package/node_modules/@playdrop/boxel-core/dist/src/validation.js +2 -1
- package/node_modules/@playdrop/boxel-core/dist/src/validation.js.map +1 -1
- package/node_modules/@playdrop/boxel-three/dist/src/exporters/glb.js +5 -0
- package/node_modules/@playdrop/config/client-meta.json +5 -6
- package/node_modules/@playdrop/config/dist/src/index.js +6 -6
- package/node_modules/@playdrop/config/dist/test/validateClientEnvironment.test.js +15 -2
- package/node_modules/@playdrop/config/dist/tsconfig.tsbuildinfo +1 -1
- package/node_modules/@playdrop/types/dist/api.d.ts +54 -9
- package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/api.js +15 -8
- package/node_modules/@playdrop/types/dist/asset-pack.d.ts +105 -11
- package/node_modules/@playdrop/types/dist/asset-pack.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/asset-pack.js +2 -0
- package/node_modules/@playdrop/types/dist/asset.d.ts +18 -50
- package/node_modules/@playdrop/types/dist/asset.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/ecs.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/ecs.js +10 -6
- package/node_modules/@playdrop/types/dist/entity.d.ts +5 -10
- package/node_modules/@playdrop/types/dist/entity.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/entity.js +40 -23
- package/node_modules/@playdrop/types/dist/graph.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/graph.js +13 -5
- package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/version.js +7 -3
- package/node_modules/@playdrop/vox-three/dist/src/vox.d.ts +1 -0
- package/node_modules/@playdrop/vox-three/dist/src/vox.js +15 -6
- package/node_modules/@playdrop/vox-three/dist/src/vox.js.map +1 -1
- package/node_modules/@playdrop/vox-three/dist/test/vox.test.js +16 -0
- package/node_modules/@playdrop/vox-three/dist/test/vox.test.js.map +1 -1
- package/package.json +23 -2
package/README.md
CHANGED
|
@@ -1,6 +1,48 @@
|
|
|
1
1
|
# @playdrop/playdrop-cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Official Playdrop CLI for the live [Playdrop](https://www.playdrop.ai/) platform.
|
|
4
|
+
|
|
5
|
+
Use it to browse live examples, create or remix projects, publish browser games and creator apps, and work with AI-generated game assets on Playdrop.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @playdrop/playdrop-cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
playdrop auth login
|
|
17
|
+
playdrop auth whoami
|
|
18
|
+
playdrop project init .
|
|
19
|
+
playdrop project create app my-first-game --template playdrop/template/typescript_template
|
|
20
|
+
playdrop project dev my-first-game
|
|
21
|
+
playdrop project publish .
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## What You Can Do
|
|
25
|
+
|
|
26
|
+
- Browse the live Playdrop catalogue of apps, assets, and packs
|
|
27
|
+
- Inspect real references before building or remixing
|
|
28
|
+
- Create, remix, validate, and publish Playdrop projects
|
|
29
|
+
- Manage comments, versions, credits, notifications, and creations
|
|
30
|
+
- Read the public Playdrop docs directly from the CLI
|
|
31
|
+
|
|
32
|
+
## Links
|
|
33
|
+
|
|
34
|
+
- Website: [playdrop.ai](https://www.playdrop.ai/)
|
|
35
|
+
- Getting started: [playdrop.ai/docs/getting-started](https://www.playdrop.ai/docs/getting-started)
|
|
36
|
+
- CLI docs: [playdrop.ai/docs/cli](https://www.playdrop.ai/docs/cli)
|
|
37
|
+
- SDK docs: [playdrop.ai/docs/sdk](https://www.playdrop.ai/docs/sdk)
|
|
38
|
+
- Templates and demos: [playdrop.ai/docs/samples/templates-and-demos](https://www.playdrop.ai/docs/samples/templates-and-demos)
|
|
39
|
+
- Public Playdrop skill: [skills.sh/playdrop-ai/playdrop-skills/playdrop](https://skills.sh/playdrop-ai/playdrop-skills/playdrop)
|
|
40
|
+
|
|
41
|
+
## Live Examples
|
|
42
|
+
|
|
43
|
+
- [Starter Kit Racing](https://www.playdrop.ai/creators/playdrop/apps/game/starter-kit-racing)
|
|
44
|
+
- [Hangingout](https://www.playdrop.ai/creators/playdrop/apps/game/hangingout)
|
|
45
|
+
- [Top Creators](https://www.playdrop.ai/top-creators)
|
|
4
46
|
|
|
5
47
|
## Main Commands
|
|
6
48
|
|
package/config/client-meta.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.3.
|
|
2
|
+
"version": "0.3.10",
|
|
3
3
|
"build": 1,
|
|
4
4
|
"platforms": {
|
|
5
5
|
"ios": {
|
|
@@ -26,20 +26,19 @@
|
|
|
26
26
|
},
|
|
27
27
|
"clients": {
|
|
28
28
|
"web": {
|
|
29
|
-
"minimumVersion": "0.3.
|
|
29
|
+
"minimumVersion": "0.3.10",
|
|
30
30
|
"minimumBuild": 1
|
|
31
31
|
},
|
|
32
32
|
"admin": {
|
|
33
|
-
"minimumVersion": "0.3.
|
|
33
|
+
"minimumVersion": "0.3.10",
|
|
34
34
|
"minimumBuild": 1
|
|
35
35
|
},
|
|
36
36
|
"apple": {
|
|
37
|
-
"minimumVersion": "0.3.
|
|
37
|
+
"minimumVersion": "0.3.10",
|
|
38
38
|
"minimumBuild": 1
|
|
39
39
|
},
|
|
40
40
|
"cli": {
|
|
41
|
-
"minimumVersion": "0.3.
|
|
42
|
-
"minimumBuild": 1
|
|
41
|
+
"minimumVersion": "0.3.10"
|
|
43
42
|
}
|
|
44
43
|
}
|
|
45
44
|
}
|
package/dist/apps/build.js
CHANGED
|
@@ -182,6 +182,47 @@ function isPathWithinRoot(rootDir, targetPath) {
|
|
|
182
182
|
const rootPrefix = normalizedRoot.endsWith(node_path_1.sep) ? normalizedRoot : `${normalizedRoot}${node_path_1.sep}`;
|
|
183
183
|
return normalizedTarget.startsWith(rootPrefix);
|
|
184
184
|
}
|
|
185
|
+
function readProjectDirEntries(current) {
|
|
186
|
+
try {
|
|
187
|
+
return (0, node_fs_1.readdirSync)(current, { withFileTypes: true });
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
return [];
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function appendIncludedProjectFile(rootDir, includePath, fileByRelativePath, results, enforceReservedBundleNames) {
|
|
194
|
+
const normalizedIncludePath = (0, node_path_1.normalize)(includePath).split(node_path_1.sep).join('/');
|
|
195
|
+
if (!normalizedIncludePath || normalizedIncludePath === '.') {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (normalizedIncludePath.startsWith('..')) {
|
|
199
|
+
throw new Error(`[apps][build] Invalid include file path "${includePath}".`);
|
|
200
|
+
}
|
|
201
|
+
if (fileByRelativePath.has(normalizedIncludePath)) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const absolutePath = (0, node_path_1.resolve)(rootDir, normalizedIncludePath);
|
|
205
|
+
if (!isPathWithinRoot(rootDir, absolutePath)) {
|
|
206
|
+
throw new Error(`[apps][build] Included file "${includePath}" escapes the project root.`);
|
|
207
|
+
}
|
|
208
|
+
if (!(0, node_fs_1.existsSync)(absolutePath)) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const stats = (0, node_fs_1.statSync)(absolutePath);
|
|
212
|
+
if (!stats.isFile()) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (enforceReservedBundleNames && isReservedBundlePath(normalizedIncludePath)) {
|
|
216
|
+
throw new Error(`[apps][build] Reserved bundle filename "${normalizedIncludePath}" is not allowed. Use dedicated source/ecs/server upload fields.`);
|
|
217
|
+
}
|
|
218
|
+
const record = {
|
|
219
|
+
absolutePath,
|
|
220
|
+
relativePath: normalizedIncludePath,
|
|
221
|
+
mtime: stats.mtime,
|
|
222
|
+
};
|
|
223
|
+
results.push(record);
|
|
224
|
+
fileByRelativePath.set(record.relativePath, record);
|
|
225
|
+
}
|
|
185
226
|
function collectProjectFiles(rootDir, rules, options) {
|
|
186
227
|
const includeRelativeFiles = options?.includeRelativeFiles ?? [];
|
|
187
228
|
const enforceReservedBundleNames = options?.enforceReservedBundleNames ?? false;
|
|
@@ -194,13 +235,7 @@ function collectProjectFiles(rootDir, rules, options) {
|
|
|
194
235
|
if (visited.has(current))
|
|
195
236
|
continue;
|
|
196
237
|
visited.add(current);
|
|
197
|
-
|
|
198
|
-
try {
|
|
199
|
-
dirEntries = (0, node_fs_1.readdirSync)(current, { withFileTypes: true });
|
|
200
|
-
}
|
|
201
|
-
catch {
|
|
202
|
-
continue;
|
|
203
|
-
}
|
|
238
|
+
const dirEntries = readProjectDirEntries(current);
|
|
204
239
|
for (const entry of dirEntries) {
|
|
205
240
|
const absolutePath = (0, node_path_1.join)(current, entry.name);
|
|
206
241
|
const relativePath = normalizeRelativePath(rootDir, absolutePath);
|
|
@@ -238,37 +273,7 @@ function collectProjectFiles(rootDir, rules, options) {
|
|
|
238
273
|
}
|
|
239
274
|
}
|
|
240
275
|
for (const includePath of includeRelativeFiles) {
|
|
241
|
-
|
|
242
|
-
if (!normalizedIncludePath || normalizedIncludePath === '.') {
|
|
243
|
-
continue;
|
|
244
|
-
}
|
|
245
|
-
if (normalizedIncludePath.startsWith('..')) {
|
|
246
|
-
throw new Error(`[apps][build] Invalid include file path "${includePath}".`);
|
|
247
|
-
}
|
|
248
|
-
if (fileByRelativePath.has(normalizedIncludePath)) {
|
|
249
|
-
continue;
|
|
250
|
-
}
|
|
251
|
-
const absolutePath = (0, node_path_1.resolve)(rootDir, normalizedIncludePath);
|
|
252
|
-
if (!isPathWithinRoot(rootDir, absolutePath)) {
|
|
253
|
-
throw new Error(`[apps][build] Included file "${includePath}" escapes the project root.`);
|
|
254
|
-
}
|
|
255
|
-
if (!(0, node_fs_1.existsSync)(absolutePath)) {
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
258
|
-
const stats = (0, node_fs_1.statSync)(absolutePath);
|
|
259
|
-
if (!stats.isFile()) {
|
|
260
|
-
continue;
|
|
261
|
-
}
|
|
262
|
-
if (enforceReservedBundleNames && isReservedBundlePath(normalizedIncludePath)) {
|
|
263
|
-
throw new Error(`[apps][build] Reserved bundle filename "${normalizedIncludePath}" is not allowed. Use dedicated source/ecs/server upload fields.`);
|
|
264
|
-
}
|
|
265
|
-
const record = {
|
|
266
|
-
absolutePath,
|
|
267
|
-
relativePath: normalizedIncludePath,
|
|
268
|
-
mtime: stats.mtime,
|
|
269
|
-
};
|
|
270
|
-
results.push(record);
|
|
271
|
-
fileByRelativePath.set(record.relativePath, record);
|
|
276
|
+
appendIncludedProjectFile(rootDir, includePath, fileByRelativePath, results, enforceReservedBundleNames);
|
|
272
277
|
}
|
|
273
278
|
results.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
274
279
|
return results;
|
package/dist/catalogue-utils.js
CHANGED
|
@@ -42,6 +42,34 @@ function parseSurfaceTargets(input) {
|
|
|
42
42
|
return { map, list };
|
|
43
43
|
}
|
|
44
44
|
const DEFAULT_SURFACE_TARGETS_OBJECT = { desktop: true, mobileLandscape: false, mobilePortrait: false };
|
|
45
|
+
const LEGACY_CATALOGUE_VERSION_KEY = ['schema', 'Version'].join('');
|
|
46
|
+
function parseCatalogueEntries(cataloguePath) {
|
|
47
|
+
try {
|
|
48
|
+
const raw = (0, node_fs_1.readFileSync)(cataloguePath, 'utf8');
|
|
49
|
+
const data = JSON.parse(raw);
|
|
50
|
+
if (!data || typeof data !== 'object' || Array.isArray(data)) {
|
|
51
|
+
throw new Error('catalogue.json must be an object');
|
|
52
|
+
}
|
|
53
|
+
if (Object.prototype.hasOwnProperty.call(data, LEGACY_CATALOGUE_VERSION_KEY)) {
|
|
54
|
+
throw new Error('legacy top-level version field is not supported');
|
|
55
|
+
}
|
|
56
|
+
const entries = data?.apps;
|
|
57
|
+
return Array.isArray(entries)
|
|
58
|
+
? entries.filter((entry) => Boolean(entry && typeof entry === 'object'))
|
|
59
|
+
: [];
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
throw new Error(`Failed to parse ${cataloguePath}. Fix the JSON and retry.`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function findMatchingCatalogueEntries(apps, relativePath, fileName) {
|
|
66
|
+
return apps.filter((entry) => {
|
|
67
|
+
const entryName = typeof entry.name === 'string' ? entry.name.trim() : '';
|
|
68
|
+
const entryFile = typeof entry.file === 'string' ? entry.file.trim() : `${entryName}.html`;
|
|
69
|
+
const normalized = entryFile.replace(/\\/g, '/');
|
|
70
|
+
return normalized === relativePath || normalized === fileName;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
45
73
|
function cloneSurfaceTargets(targets) {
|
|
46
74
|
return {
|
|
47
75
|
list: [...targets.list],
|
|
@@ -54,25 +82,9 @@ function findAppCatalogueInfo(htmlPath) {
|
|
|
54
82
|
for (let dir = (0, node_path_1.dirname)(absolute);; dir = (0, node_path_1.dirname)(dir)) {
|
|
55
83
|
const cataloguePath = (0, node_path_1.join)(dir, 'catalogue.json');
|
|
56
84
|
if ((0, node_fs_1.existsSync)(cataloguePath) && (0, node_fs_1.statSync)(cataloguePath).isFile()) {
|
|
57
|
-
let entries;
|
|
58
|
-
try {
|
|
59
|
-
const raw = (0, node_fs_1.readFileSync)(cataloguePath, 'utf8');
|
|
60
|
-
const data = raw.trim() ? JSON.parse(raw) : {};
|
|
61
|
-
entries = Array.isArray(data) ? data : data?.apps;
|
|
62
|
-
}
|
|
63
|
-
catch {
|
|
64
|
-
throw new Error(`Failed to parse ${cataloguePath}. Fix the JSON and retry.`);
|
|
65
|
-
}
|
|
66
|
-
const apps = Array.isArray(entries) ? entries : [];
|
|
67
85
|
const rel = (0, node_path_1.relative)(dir, absolute).replace(/\\/g, '/');
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
return false;
|
|
71
|
-
const entryName = typeof entry.name === 'string' ? entry.name.trim() : '';
|
|
72
|
-
const entryFile = typeof entry.file === 'string' ? entry.file.trim() : `${entryName}.html`;
|
|
73
|
-
const normalized = entryFile.replace(/\\/g, '/');
|
|
74
|
-
return normalized === rel || normalized === fileName;
|
|
75
|
-
});
|
|
86
|
+
const apps = parseCatalogueEntries(cataloguePath);
|
|
87
|
+
const matches = findMatchingCatalogueEntries(apps, rel, fileName);
|
|
76
88
|
if (matches.length > 1) {
|
|
77
89
|
throw new Error(`Multiple entries in ${cataloguePath} reference ${absolute}.`);
|
|
78
90
|
}
|
package/dist/catalogue.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { type AppSurface, type AppType, type AppHostingMode, type AppVersionVisibility } from '@playdrop/types';
|
|
2
2
|
export type CatalogueJson = {
|
|
3
|
-
schemaVersion?: number;
|
|
4
3
|
apps?: Array<Record<string, unknown>>;
|
|
5
4
|
assets?: Array<Record<string, unknown>>;
|
|
6
5
|
assetPacks?: Array<Record<string, unknown>>;
|
|
@@ -85,7 +84,6 @@ export type AssetPackCatalogueEntry = {
|
|
|
85
84
|
hostingMode?: string;
|
|
86
85
|
externalUrl?: string;
|
|
87
86
|
downloadUrl?: string;
|
|
88
|
-
previewApp?: string;
|
|
89
87
|
visibility?: string;
|
|
90
88
|
releaseNotes?: string;
|
|
91
89
|
listing?: AppListingConfig;
|
|
@@ -200,7 +198,6 @@ export type AssetPackTask = {
|
|
|
200
198
|
hostingMode?: AppHostingMode;
|
|
201
199
|
externalUrl?: string;
|
|
202
200
|
downloadUrl?: string;
|
|
203
|
-
previewApp?: string;
|
|
204
201
|
visibility?: string;
|
|
205
202
|
releaseNotes?: string;
|
|
206
203
|
listing?: ResolvedListingAssets;
|
package/dist/catalogue.js
CHANGED
|
@@ -12,6 +12,7 @@ const types_1 = require("@playdrop/types");
|
|
|
12
12
|
const catalogue_utils_1 = require("./catalogue-utils");
|
|
13
13
|
const HEX_COLOR_REGEX = /^#?(?:[0-9a-fA-F]{6})$/;
|
|
14
14
|
const SEMVER_REGEX = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
|
|
15
|
+
const LEGACY_CATALOGUE_VERSION_KEY = ['schema', 'Version'].join('');
|
|
15
16
|
const ASSET_CATEGORY_SET = new Set(types_1.ASSET_CATEGORY_VALUES);
|
|
16
17
|
const LISTING_IMAGE_EXTENSIONS = new Set(['.png']);
|
|
17
18
|
const LISTING_VIDEO_EXTENSIONS = new Set(['.mp4']);
|
|
@@ -192,24 +193,24 @@ function shouldSkipDirectory(name) {
|
|
|
192
193
|
function readCatalogueFile(rootDir, filePath) {
|
|
193
194
|
try {
|
|
194
195
|
const raw = (0, node_fs_1.readFileSync)(filePath, 'utf8');
|
|
195
|
-
const parsed =
|
|
196
|
+
const parsed = JSON.parse(raw);
|
|
196
197
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
197
198
|
return { error: 'catalogue.json must contain a JSON object' };
|
|
198
199
|
}
|
|
199
|
-
if (parsed
|
|
200
|
-
return { error: 'catalogue.json must
|
|
200
|
+
if (Object.prototype.hasOwnProperty.call(parsed, LEGACY_CATALOGUE_VERSION_KEY)) {
|
|
201
|
+
return { error: 'catalogue.json must not define the legacy top-level version field. Remove it and retry.' };
|
|
201
202
|
}
|
|
202
203
|
const forbiddenKeys = ['avatars', 'humanoids', 'creatures', 'blocks', 'games'];
|
|
203
204
|
for (const key of forbiddenKeys) {
|
|
204
205
|
if (Object.prototype.hasOwnProperty.call(parsed, key)) {
|
|
205
|
-
return { error: `catalogue.json
|
|
206
|
+
return { error: `catalogue.json forbids top-level "${key}"` };
|
|
206
207
|
}
|
|
207
208
|
}
|
|
208
|
-
const allowedKeys = new Set(['
|
|
209
|
+
const allowedKeys = new Set(['apps', 'assets', 'assetPacks']);
|
|
209
210
|
const unknownKeys = Object.keys(parsed).filter((key) => !allowedKeys.has(key));
|
|
210
211
|
if (unknownKeys.length > 0) {
|
|
211
212
|
return {
|
|
212
|
-
error: `catalogue.json
|
|
213
|
+
error: `catalogue.json contains unsupported top-level key "${unknownKeys[0]}"`,
|
|
213
214
|
};
|
|
214
215
|
}
|
|
215
216
|
const directory = (0, node_path_1.dirname)(filePath);
|
|
@@ -283,6 +284,9 @@ function walkCatalogueTree(rootDir) {
|
|
|
283
284
|
files.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
284
285
|
return { files, errors };
|
|
285
286
|
}
|
|
287
|
+
function isCatalogueMarker(file) {
|
|
288
|
+
return Object.keys(file.data).length === 0;
|
|
289
|
+
}
|
|
286
290
|
const packageJsonCache = new Map();
|
|
287
291
|
function resolvePackageJson(projectDir) {
|
|
288
292
|
const candidate = (0, node_path_1.join)(projectDir, 'package.json');
|
|
@@ -396,6 +400,48 @@ function normalizeAssetSubcategoryValue(raw, category, errors, label) {
|
|
|
396
400
|
}
|
|
397
401
|
return normalized;
|
|
398
402
|
}
|
|
403
|
+
function validateOptionalEmoji(value, hasEmojiField, errors) {
|
|
404
|
+
const emoji = value;
|
|
405
|
+
if (!hasEmojiField) {
|
|
406
|
+
return emoji;
|
|
407
|
+
}
|
|
408
|
+
if (emoji !== null && typeof emoji === 'string') {
|
|
409
|
+
const trimmed = emoji.trim();
|
|
410
|
+
if (!trimmed) {
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
if (Array.from(trimmed).length > 4) {
|
|
414
|
+
errors.push('emoji must be a single character');
|
|
415
|
+
return emoji;
|
|
416
|
+
}
|
|
417
|
+
return trimmed;
|
|
418
|
+
}
|
|
419
|
+
if (emoji !== null) {
|
|
420
|
+
errors.push('emoji must be a string or null');
|
|
421
|
+
}
|
|
422
|
+
return emoji;
|
|
423
|
+
}
|
|
424
|
+
function validateOptionalColor(value, hasColorField, errors) {
|
|
425
|
+
const color = value;
|
|
426
|
+
if (!hasColorField) {
|
|
427
|
+
return color;
|
|
428
|
+
}
|
|
429
|
+
if (color !== null && typeof color === 'string') {
|
|
430
|
+
const trimmed = color.trim();
|
|
431
|
+
if (!trimmed) {
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
if (!HEX_COLOR_REGEX.test(trimmed)) {
|
|
435
|
+
errors.push('color must be a hex RGB value like #FFAA00');
|
|
436
|
+
return color;
|
|
437
|
+
}
|
|
438
|
+
return trimmed.startsWith('#') ? trimmed.toUpperCase() : `#${trimmed.toUpperCase()}`;
|
|
439
|
+
}
|
|
440
|
+
if (color !== null) {
|
|
441
|
+
errors.push('color must be a string or null');
|
|
442
|
+
}
|
|
443
|
+
return color;
|
|
444
|
+
}
|
|
399
445
|
function validateAppMetadata(entry) {
|
|
400
446
|
const warnings = [];
|
|
401
447
|
const errors = [];
|
|
@@ -403,42 +449,8 @@ function validateAppMetadata(entry) {
|
|
|
403
449
|
if (missingFields.length > 0) {
|
|
404
450
|
warnings.push(formatMissingMetadata(missingFields));
|
|
405
451
|
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
if (emoji !== null && typeof emoji === 'string') {
|
|
409
|
-
const trimmed = emoji.trim();
|
|
410
|
-
if (!trimmed) {
|
|
411
|
-
emoji = null;
|
|
412
|
-
}
|
|
413
|
-
else if (Array.from(trimmed).length > 4) {
|
|
414
|
-
errors.push('emoji must be a single character');
|
|
415
|
-
}
|
|
416
|
-
else {
|
|
417
|
-
emoji = trimmed;
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
else if (emoji !== null) {
|
|
421
|
-
errors.push('emoji must be a string or null');
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
let color = entry.color;
|
|
425
|
-
if (Object.prototype.hasOwnProperty.call(entry, 'color')) {
|
|
426
|
-
if (color !== null && typeof color === 'string') {
|
|
427
|
-
const trimmed = color.trim();
|
|
428
|
-
if (!trimmed) {
|
|
429
|
-
color = null;
|
|
430
|
-
}
|
|
431
|
-
else if (!HEX_COLOR_REGEX.test(trimmed)) {
|
|
432
|
-
errors.push('color must be a hex RGB value like #FFAA00');
|
|
433
|
-
}
|
|
434
|
-
else {
|
|
435
|
-
color = trimmed.startsWith('#') ? trimmed.toUpperCase() : `#${trimmed.toUpperCase()}`;
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
else if (color !== null) {
|
|
439
|
-
errors.push('color must be a string or null');
|
|
440
|
-
}
|
|
441
|
-
}
|
|
452
|
+
const emoji = validateOptionalEmoji(entry.emoji, Object.prototype.hasOwnProperty.call(entry, 'emoji'), errors);
|
|
453
|
+
const color = validateOptionalColor(entry.color, Object.prototype.hasOwnProperty.call(entry, 'color'), errors);
|
|
442
454
|
let type;
|
|
443
455
|
if (typeof entry.type === 'string' && entry.type.trim()) {
|
|
444
456
|
const parsedType = (0, types_1.parseAppType)(entry.type.trim());
|
|
@@ -625,18 +637,17 @@ function buildAppTasks(rootDir, catalogues, options) {
|
|
|
625
637
|
}
|
|
626
638
|
serverPath = absoluteServerPath;
|
|
627
639
|
}
|
|
628
|
-
// Parse versioning fields
|
|
629
|
-
let version;
|
|
640
|
+
// Parse versioning fields.
|
|
630
641
|
const rawVersion = typeof entry.version === 'string' ? entry.version.trim() : '';
|
|
631
642
|
if (!rawVersion) {
|
|
632
|
-
errors.push(`[${label}] Skipping ${rawName}: app version is required
|
|
643
|
+
errors.push(`[${label}] Skipping ${rawName}: app version is required (e.g., "1.0.0").`);
|
|
633
644
|
continue;
|
|
634
645
|
}
|
|
635
646
|
if (!SEMVER_REGEX.test(rawVersion)) {
|
|
636
647
|
errors.push(`[${label}] Skipping ${rawName}: version must be in Major.Minor.Patch format (e.g., "1.0.0").`);
|
|
637
648
|
continue;
|
|
638
649
|
}
|
|
639
|
-
version = rawVersion;
|
|
650
|
+
const version = rawVersion;
|
|
640
651
|
const releaseNotes = typeof entry.releaseNotes === 'string' ? entry.releaseNotes : undefined;
|
|
641
652
|
let versionVisibility;
|
|
642
653
|
const rawVisibility = typeof entry.visibility === 'string' ? entry.visibility.trim().toUpperCase() : '';
|
|
@@ -936,7 +947,7 @@ function buildAppTasks(rootDir, catalogues, options) {
|
|
|
936
947
|
category,
|
|
937
948
|
subcategory,
|
|
938
949
|
format: embeddedFormat,
|
|
939
|
-
visibility: normalizeAssetVisibilityValue(embedded?.visibility, errors, `${label} embedded asset
|
|
950
|
+
visibility: normalizeAssetVisibilityValue(embedded?.visibility, errors, `${label} embedded asset "${embeddedName}"`),
|
|
940
951
|
shopListed: typeof embedded?.shopListed === 'boolean' ? embedded.shopListed : undefined,
|
|
941
952
|
shopPriceCredits: Number.isInteger(embedded?.shopPriceCredits) ? Number(embedded.shopPriceCredits) : undefined,
|
|
942
953
|
files,
|
|
@@ -1107,7 +1118,7 @@ function buildAssetTasks(catalogues) {
|
|
|
1107
1118
|
username,
|
|
1108
1119
|
remix,
|
|
1109
1120
|
format,
|
|
1110
|
-
visibility: normalizeAssetVisibilityValue(entry.visibility, errors, `${label} asset
|
|
1121
|
+
visibility: normalizeAssetVisibilityValue(entry.visibility, errors, `${label} asset "${name}"`),
|
|
1111
1122
|
shopListed: typeof entry.shopListed === 'boolean' ? entry.shopListed : undefined,
|
|
1112
1123
|
shopPriceCredits: Number.isInteger(entry.shopPriceCredits) ? Number(entry.shopPriceCredits) : undefined,
|
|
1113
1124
|
files,
|
|
@@ -1351,6 +1362,13 @@ function buildAssetPackTasks(catalogues) {
|
|
|
1351
1362
|
}
|
|
1352
1363
|
listing = resolvedListing;
|
|
1353
1364
|
}
|
|
1365
|
+
const previewApp = typeof entry.previewApp === 'string'
|
|
1366
|
+
? String(entry.previewApp).trim()
|
|
1367
|
+
: '';
|
|
1368
|
+
if (previewApp.length > 0) {
|
|
1369
|
+
errors.push(`[${label}] Skipping asset pack "${name}": previewApp is no longer supported during publish. Upload the pack first, then set previewAppVersionId explicitly with creations asset-packs update.`);
|
|
1370
|
+
continue;
|
|
1371
|
+
}
|
|
1354
1372
|
tasks.push({
|
|
1355
1373
|
kind: 'asset-pack',
|
|
1356
1374
|
name,
|
|
@@ -1363,7 +1381,6 @@ function buildAssetPackTasks(catalogues) {
|
|
|
1363
1381
|
hostingMode,
|
|
1364
1382
|
externalUrl,
|
|
1365
1383
|
downloadUrl,
|
|
1366
|
-
previewApp: typeof entry.previewApp === 'string' && entry.previewApp.trim().length > 0 ? entry.previewApp.trim() : undefined,
|
|
1367
1384
|
visibility,
|
|
1368
1385
|
releaseNotes: typeof entry.releaseNotes === 'string' ? entry.releaseNotes : undefined,
|
|
1369
1386
|
listing,
|
|
@@ -1385,6 +1402,20 @@ function resolveCatalogueEntries(rootDir, options = {}) {
|
|
|
1385
1402
|
errors: discoveryErrors,
|
|
1386
1403
|
};
|
|
1387
1404
|
}
|
|
1405
|
+
const rootCatalogue = files.find((file) => file.relativePath === 'catalogue.json');
|
|
1406
|
+
const hasNestedCatalogues = files.some((file) => file.relativePath !== 'catalogue.json');
|
|
1407
|
+
if (rootCatalogue && hasNestedCatalogues && !isCatalogueMarker(rootCatalogue)) {
|
|
1408
|
+
return {
|
|
1409
|
+
apps: [],
|
|
1410
|
+
assets: [],
|
|
1411
|
+
embeddedAssets: [],
|
|
1412
|
+
assetPacks: [],
|
|
1413
|
+
warnings: [],
|
|
1414
|
+
errors: [
|
|
1415
|
+
'[catalogue.json] Workspace root catalogue.json must be {} when nested catalogue.json files exist below it.',
|
|
1416
|
+
],
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1388
1419
|
const { tasks: appTasks, warnings: appWarnings, errors: appErrors } = buildAppTasks(rootDir, files, options);
|
|
1389
1420
|
const { tasks: assetTasks, warnings: assetWarnings, errors: assetErrors } = buildAssetTasks(files);
|
|
1390
1421
|
const { tasks: assetPackTasks, warnings: assetPackWarnings, errors: assetPackErrors } = buildAssetPackTasks(files);
|
package/dist/clientInfo.js
CHANGED
|
@@ -12,14 +12,28 @@ const os_1 = __importDefault(require("os"));
|
|
|
12
12
|
const config_1 = require("@playdrop/config");
|
|
13
13
|
function mapPlatform(platform) {
|
|
14
14
|
switch (platform) {
|
|
15
|
+
case 'aix':
|
|
16
|
+
return 'aix';
|
|
17
|
+
case 'android':
|
|
18
|
+
return 'android';
|
|
19
|
+
case 'cygwin':
|
|
20
|
+
return 'cygwin';
|
|
15
21
|
case 'darwin':
|
|
16
22
|
return 'macos';
|
|
23
|
+
case 'freebsd':
|
|
24
|
+
return 'freebsd';
|
|
25
|
+
case 'haiku':
|
|
26
|
+
return 'haiku';
|
|
17
27
|
case 'win32':
|
|
18
28
|
return 'windows';
|
|
19
29
|
case 'linux':
|
|
20
30
|
return 'linux';
|
|
21
|
-
|
|
22
|
-
return
|
|
31
|
+
case 'netbsd':
|
|
32
|
+
return 'netbsd';
|
|
33
|
+
case 'openbsd':
|
|
34
|
+
return 'openbsd';
|
|
35
|
+
case 'sunos':
|
|
36
|
+
return 'sunos';
|
|
23
37
|
}
|
|
24
38
|
}
|
|
25
39
|
function detectPlatformVersion() {
|
package/dist/commands/browse.js
CHANGED
|
@@ -91,7 +91,16 @@ function formatCountValue(value) {
|
|
|
91
91
|
}
|
|
92
92
|
function buildCountsSuffix(item) {
|
|
93
93
|
const parts = [];
|
|
94
|
-
if (
|
|
94
|
+
if (item.kind === 'app' && typeof item.item.playCount === 'number') {
|
|
95
|
+
if (typeof item.item.viewCount === 'number') {
|
|
96
|
+
parts.push(`view ${formatCountValue(item.item.viewCount)}`);
|
|
97
|
+
}
|
|
98
|
+
parts.push(`launch ${formatCountValue(item.item.playCount)}`);
|
|
99
|
+
}
|
|
100
|
+
if (item.kind === 'asset' && typeof item.item.playCount === 'number') {
|
|
101
|
+
if (typeof item.item.viewCount === 'number') {
|
|
102
|
+
parts.push(`view ${formatCountValue(item.item.viewCount)}`);
|
|
103
|
+
}
|
|
95
104
|
parts.push(`play ${formatCountValue(item.item.playCount)}`);
|
|
96
105
|
}
|
|
97
106
|
if (typeof item.item.likeCount === 'number') {
|
package/dist/commands/capture.js
CHANGED
|
@@ -35,6 +35,8 @@ function formatAppTypeSlug(type) {
|
|
|
35
35
|
return 'template';
|
|
36
36
|
case 'DEMO':
|
|
37
37
|
return 'demo';
|
|
38
|
+
case undefined:
|
|
39
|
+
return 'game';
|
|
38
40
|
default:
|
|
39
41
|
return 'game';
|
|
40
42
|
}
|
|
@@ -414,8 +416,7 @@ async function capture(targetArg, options = {}) {
|
|
|
414
416
|
projectInfo,
|
|
415
417
|
});
|
|
416
418
|
signalHandler = () => {
|
|
417
|
-
cleanup()
|
|
418
|
-
.finally(() => process.exit(130));
|
|
419
|
+
void cleanup().finally(() => process.exit(130));
|
|
419
420
|
};
|
|
420
421
|
process.on('SIGINT', signalHandler);
|
|
421
422
|
process.on('SIGTERM', signalHandler);
|