@playdrop/playdrop-cli 0.3.9 → 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/catalogue-utils.js +9 -2
- package/dist/catalogue.d.ts +0 -1
- package/dist/catalogue.js +26 -8
- package/dist/commands/create.js +105 -9
- package/dist/commands/createRemixContent.js +32 -2
- package/dist/commands/init.js +30 -2
- package/dist/taskSelection.js +2 -2
- package/node_modules/@playdrop/config/client-meta.json +5 -6
- package/node_modules/@playdrop/config/dist/test/validateClientEnvironment.test.js +13 -0
- package/node_modules/@playdrop/config/dist/tsconfig.tsbuildinfo +1 -1
- package/node_modules/@playdrop/types/dist/api.d.ts +27 -2
- package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/asset.d.ts +18 -50
- package/node_modules/@playdrop/types/dist/asset.d.ts.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/catalogue-utils.js
CHANGED
|
@@ -42,11 +42,18 @@ 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('');
|
|
45
46
|
function parseCatalogueEntries(cataloguePath) {
|
|
46
47
|
try {
|
|
47
48
|
const raw = (0, node_fs_1.readFileSync)(cataloguePath, 'utf8');
|
|
48
|
-
const data =
|
|
49
|
-
|
|
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;
|
|
50
57
|
return Array.isArray(entries)
|
|
51
58
|
? entries.filter((entry) => Boolean(entry && typeof entry === 'object'))
|
|
52
59
|
: [];
|
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>>;
|
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');
|
|
@@ -633,10 +637,10 @@ function buildAppTasks(rootDir, catalogues, options) {
|
|
|
633
637
|
}
|
|
634
638
|
serverPath = absoluteServerPath;
|
|
635
639
|
}
|
|
636
|
-
// Parse versioning fields
|
|
640
|
+
// Parse versioning fields.
|
|
637
641
|
const rawVersion = typeof entry.version === 'string' ? entry.version.trim() : '';
|
|
638
642
|
if (!rawVersion) {
|
|
639
|
-
errors.push(`[${label}] Skipping ${rawName}: app version is required
|
|
643
|
+
errors.push(`[${label}] Skipping ${rawName}: app version is required (e.g., "1.0.0").`);
|
|
640
644
|
continue;
|
|
641
645
|
}
|
|
642
646
|
if (!SEMVER_REGEX.test(rawVersion)) {
|
|
@@ -1398,6 +1402,20 @@ function resolveCatalogueEntries(rootDir, options = {}) {
|
|
|
1398
1402
|
errors: discoveryErrors,
|
|
1399
1403
|
};
|
|
1400
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
|
+
}
|
|
1401
1419
|
const { tasks: appTasks, warnings: appWarnings, errors: appErrors } = buildAppTasks(rootDir, files, options);
|
|
1402
1420
|
const { tasks: assetTasks, warnings: assetWarnings, errors: assetErrors } = buildAssetTasks(files);
|
|
1403
1421
|
const { tasks: assetPackTasks, warnings: assetPackWarnings, errors: assetPackErrors } = buildAssetPackTasks(files);
|
package/dist/commands/create.js
CHANGED
|
@@ -21,6 +21,8 @@ const app_1 = require("@playdrop/types/app");
|
|
|
21
21
|
const catalogue_utils_1 = require("../catalogue-utils");
|
|
22
22
|
const catalogue_1 = require("../catalogue");
|
|
23
23
|
const CATALOGUE_FILENAME = 'catalogue.json';
|
|
24
|
+
const LEGACY_CATALOGUE_VERSION_KEY = ['schema', 'Version'].join('');
|
|
25
|
+
const ALLOWED_CATALOGUE_TOP_LEVEL_KEYS = new Set(['apps', 'assets', 'assetPacks']);
|
|
24
26
|
async function downloadArchive(url) {
|
|
25
27
|
const response = await fetch(url);
|
|
26
28
|
if (!response.ok) {
|
|
@@ -147,6 +149,14 @@ function runNpmInstall(projectDir) {
|
|
|
147
149
|
});
|
|
148
150
|
return result.status === 0;
|
|
149
151
|
}
|
|
152
|
+
function findUnsupportedCatalogueTopLevelKey(parsed) {
|
|
153
|
+
for (const key of Object.keys(parsed)) {
|
|
154
|
+
if (!ALLOWED_CATALOGUE_TOP_LEVEL_KEYS.has(key) && key !== 'games') {
|
|
155
|
+
return key;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
150
160
|
function extractEntryPointFromMetadata(metadata) {
|
|
151
161
|
if (!metadata) {
|
|
152
162
|
return null;
|
|
@@ -265,9 +275,6 @@ function buildCatalogueEntry(name, metadata, sourceInfo, relativeFilePath) {
|
|
|
265
275
|
function readCatalogue(path) {
|
|
266
276
|
try {
|
|
267
277
|
const raw = (0, node_fs_1.readFileSync)(path, 'utf8');
|
|
268
|
-
if (!raw.trim()) {
|
|
269
|
-
return { schemaVersion: 2, apps: [] };
|
|
270
|
-
}
|
|
271
278
|
const parsed = JSON.parse(raw);
|
|
272
279
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
273
280
|
return null;
|
|
@@ -278,14 +285,39 @@ function readCatalogue(path) {
|
|
|
278
285
|
return null;
|
|
279
286
|
}
|
|
280
287
|
}
|
|
288
|
+
function hasNestedCatalogueFiles(rootDir) {
|
|
289
|
+
const rootCataloguePath = (0, node_path_1.join)(rootDir, CATALOGUE_FILENAME);
|
|
290
|
+
const stack = [rootDir];
|
|
291
|
+
while (stack.length > 0) {
|
|
292
|
+
const current = stack.pop();
|
|
293
|
+
const entries = (0, node_fs_1.readdirSync)(current, { withFileTypes: true });
|
|
294
|
+
for (const entry of entries) {
|
|
295
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules') {
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
const absolutePath = (0, node_path_1.join)(current, entry.name);
|
|
299
|
+
if (entry.isDirectory()) {
|
|
300
|
+
stack.push(absolutePath);
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
if (entry.isFile() && entry.name === CATALOGUE_FILENAME && absolutePath !== rootCataloguePath) {
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
281
310
|
function ensureCatalogueEntry(name, metadata, cataloguePath, sourceInfo, htmlFilePath) {
|
|
282
311
|
const path = (0, node_path_1.resolve)(cataloguePath);
|
|
283
312
|
const catalogueDir = (0, node_path_1.dirname)(path);
|
|
313
|
+
if (hasNestedCatalogueFiles(catalogueDir)) {
|
|
314
|
+
return { path, createdFile: false, addedEntry: false, error: 'workspace_root_marker_required' };
|
|
315
|
+
}
|
|
284
316
|
const relativeFilePath = (0, node_path_1.relative)(catalogueDir, (0, node_path_1.resolve)(htmlFilePath)) || `${name}.html`;
|
|
285
317
|
const normalizedFilePath = relativeFilePath.replace(/\\/g, '/');
|
|
286
318
|
const entry = buildCatalogueEntry(name, metadata, sourceInfo, normalizedFilePath);
|
|
287
319
|
if (!(0, node_fs_1.existsSync)(path)) {
|
|
288
|
-
const catalogue = {
|
|
320
|
+
const catalogue = { apps: [entry] };
|
|
289
321
|
(0, node_fs_1.writeFileSync)(path, `${JSON.stringify(catalogue, null, 2)}\n`);
|
|
290
322
|
return { path, createdFile: true, addedEntry: true };
|
|
291
323
|
}
|
|
@@ -293,9 +325,13 @@ function ensureCatalogueEntry(name, metadata, cataloguePath, sourceInfo, htmlFil
|
|
|
293
325
|
if (!parsed) {
|
|
294
326
|
return { path, createdFile: false, addedEntry: false, error: 'invalid_json' };
|
|
295
327
|
}
|
|
296
|
-
if (parsed
|
|
328
|
+
if (Object.prototype.hasOwnProperty.call(parsed, LEGACY_CATALOGUE_VERSION_KEY)) {
|
|
297
329
|
return { path, createdFile: false, addedEntry: false, error: 'invalid_schema_version' };
|
|
298
330
|
}
|
|
331
|
+
const unsupportedKey = findUnsupportedCatalogueTopLevelKey(parsed);
|
|
332
|
+
if (unsupportedKey) {
|
|
333
|
+
return { path, createdFile: false, addedEntry: false, error: 'unsupported_top_level_key', unsupportedKey };
|
|
334
|
+
}
|
|
299
335
|
if (!Array.isArray(parsed.apps) && Array.isArray(parsed.games)) {
|
|
300
336
|
return { path, createdFile: false, addedEntry: false, error: 'legacy_games_property' };
|
|
301
337
|
}
|
|
@@ -325,7 +361,17 @@ function logCatalogueOutcome(result, name) {
|
|
|
325
361
|
return false;
|
|
326
362
|
}
|
|
327
363
|
if (result.error === 'invalid_schema_version') {
|
|
328
|
-
(0, messages_1.printErrorWithHelp)(`${label} must
|
|
364
|
+
(0, messages_1.printErrorWithHelp)(`${label} must not define the legacy top-level version field.`, ['Remove the legacy top-level version field from the file and rerun `playdrop project create app`.'], { command: 'project create app' });
|
|
365
|
+
process.exitCode = process.exitCode || 1;
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
if (result.error === 'unsupported_top_level_key') {
|
|
369
|
+
(0, messages_1.printErrorWithHelp)(`${label} contains unsupported top-level key "${result.unsupportedKey || 'unknown'}".`, ['Keep catalogue.json limited to apps, assets, and assetPacks, then rerun `playdrop project create app`.'], { command: 'project create app' });
|
|
370
|
+
process.exitCode = process.exitCode || 1;
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
if (result.error === 'workspace_root_marker_required') {
|
|
374
|
+
(0, messages_1.printErrorWithHelp)(`${label} must stay {} because nested catalogue.json files already exist below this directory.`, ['Create the app inside its own project folder instead of writing entries into the workspace root catalogue.json.'], { command: 'project create app' });
|
|
329
375
|
process.exitCode = process.exitCode || 1;
|
|
330
376
|
return false;
|
|
331
377
|
}
|
|
@@ -344,13 +390,20 @@ function logCatalogueOutcome(result, name) {
|
|
|
344
390
|
}
|
|
345
391
|
function updateExtractedProjectCatalogue(name, metadata, projectCataloguePath, sourceInfo, htmlFilePath) {
|
|
346
392
|
const path = (0, node_path_1.resolve)(projectCataloguePath);
|
|
393
|
+
if (hasNestedCatalogueFiles((0, node_path_1.dirname)(path))) {
|
|
394
|
+
return { path, updated: false, error: 'workspace_root_marker_required' };
|
|
395
|
+
}
|
|
347
396
|
const parsed = readCatalogue(path);
|
|
348
397
|
if (!parsed) {
|
|
349
398
|
return { path, updated: false, error: 'invalid_json' };
|
|
350
399
|
}
|
|
351
|
-
if (parsed
|
|
400
|
+
if (Object.prototype.hasOwnProperty.call(parsed, LEGACY_CATALOGUE_VERSION_KEY)) {
|
|
352
401
|
return { path, updated: false, error: 'invalid_schema_version' };
|
|
353
402
|
}
|
|
403
|
+
const unsupportedKey = findUnsupportedCatalogueTopLevelKey(parsed);
|
|
404
|
+
if (unsupportedKey) {
|
|
405
|
+
return { path, updated: false, error: 'unsupported_top_level_key', unsupportedKey };
|
|
406
|
+
}
|
|
354
407
|
if (!Array.isArray(parsed.apps) && Array.isArray(parsed.games)) {
|
|
355
408
|
return { path, updated: false, error: 'legacy_games_property' };
|
|
356
409
|
}
|
|
@@ -436,7 +489,17 @@ function logProjectCatalogueOutcome(result, name) {
|
|
|
436
489
|
return false;
|
|
437
490
|
}
|
|
438
491
|
if (result.error === 'invalid_schema_version') {
|
|
439
|
-
(0, messages_1.printErrorWithHelp)(`${label} must
|
|
492
|
+
(0, messages_1.printErrorWithHelp)(`${label} must not define the legacy top-level version field.`, ['Remove the legacy top-level version field from the file and rerun `playdrop project create app`.'], { command: 'project create app' });
|
|
493
|
+
process.exitCode = process.exitCode || 1;
|
|
494
|
+
return false;
|
|
495
|
+
}
|
|
496
|
+
if (result.error === 'unsupported_top_level_key') {
|
|
497
|
+
(0, messages_1.printErrorWithHelp)(`${label} contains unsupported top-level key "${result.unsupportedKey || 'unknown'}".`, ['Keep catalogue.json limited to apps, assets, and assetPacks, then rerun `playdrop project create app`.'], { command: 'project create app' });
|
|
498
|
+
process.exitCode = process.exitCode || 1;
|
|
499
|
+
return false;
|
|
500
|
+
}
|
|
501
|
+
if (result.error === 'workspace_root_marker_required') {
|
|
502
|
+
(0, messages_1.printErrorWithHelp)(`${label} must stay {} because nested catalogue.json files already exist below this directory.`, ['Create the app inside its own project folder instead of writing entries into the workspace root catalogue.json.'], { command: 'project create app' });
|
|
440
503
|
process.exitCode = process.exitCode || 1;
|
|
441
504
|
return false;
|
|
442
505
|
}
|
|
@@ -640,9 +703,10 @@ function parseRemixScaffoldResponse(response, version) {
|
|
|
640
703
|
if (!externalListingUrl) {
|
|
641
704
|
throw new Error('Remix response missing externalListingUrl');
|
|
642
705
|
}
|
|
706
|
+
const externalMetadata = parseExternalRemixMetadata(response.metadata);
|
|
643
707
|
return {
|
|
644
708
|
html: null,
|
|
645
|
-
metadata,
|
|
709
|
+
metadata: externalMetadata,
|
|
646
710
|
externalListingUrl,
|
|
647
711
|
entryPoint,
|
|
648
712
|
sourceVersion: null,
|
|
@@ -650,6 +714,38 @@ function parseRemixScaffoldResponse(response, version) {
|
|
|
650
714
|
}
|
|
651
715
|
throw new Error(`Unsupported remix mode: ${remixMode || 'missing'}`);
|
|
652
716
|
}
|
|
717
|
+
function parseExternalRemixMetadata(value) {
|
|
718
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
719
|
+
throw new Error('Remix response missing external metadata');
|
|
720
|
+
}
|
|
721
|
+
const record = value;
|
|
722
|
+
const name = typeof record.name === 'string' ? record.name.trim() : '';
|
|
723
|
+
const displayName = typeof record.displayName === 'string' ? record.displayName.trim() : '';
|
|
724
|
+
const description = typeof record.description === 'string' ? record.description.trim() : '';
|
|
725
|
+
const creatorUsername = typeof record.creatorUsername === 'string' ? record.creatorUsername.trim() : '';
|
|
726
|
+
const type = (0, app_1.parseAppType)(record.type) ?? '';
|
|
727
|
+
const emoji = record.emoji;
|
|
728
|
+
const color = record.color;
|
|
729
|
+
if (!name || !displayName || !description || !creatorUsername || !type) {
|
|
730
|
+
throw new Error('Remix response missing required external metadata fields');
|
|
731
|
+
}
|
|
732
|
+
if (emoji !== null && typeof emoji !== 'string') {
|
|
733
|
+
throw new Error('Remix response has invalid external metadata emoji');
|
|
734
|
+
}
|
|
735
|
+
if (color !== null && typeof color !== 'string') {
|
|
736
|
+
throw new Error('Remix response has invalid external metadata color');
|
|
737
|
+
}
|
|
738
|
+
return {
|
|
739
|
+
name,
|
|
740
|
+
displayName,
|
|
741
|
+
description,
|
|
742
|
+
creatorUsername,
|
|
743
|
+
type,
|
|
744
|
+
emoji,
|
|
745
|
+
color,
|
|
746
|
+
surfaceTargets: (0, catalogue_utils_1.parseSurfaceTargets)(record.surfaceTargets).list,
|
|
747
|
+
};
|
|
748
|
+
}
|
|
653
749
|
async function fetchRemixScaffold(client, creator, app, version) {
|
|
654
750
|
try {
|
|
655
751
|
const response = await client.fetchRemixScaffold(creator, app, version);
|
|
@@ -13,6 +13,8 @@ const http_1 = require("../http");
|
|
|
13
13
|
const messages_1 = require("../messages");
|
|
14
14
|
const init_1 = require("./init");
|
|
15
15
|
const CATALOGUE_FILENAME = 'catalogue.json';
|
|
16
|
+
const LEGACY_CATALOGUE_VERSION_KEY = ['schema', 'Version'].join('');
|
|
17
|
+
const ALLOWED_CATALOGUE_TOP_LEVEL_KEYS = new Set(['apps', 'assets', 'assetPacks']);
|
|
16
18
|
const MIME_TYPE_TO_EXTENSION = {
|
|
17
19
|
'image/png': '.png',
|
|
18
20
|
'image/jpeg': '.jpg',
|
|
@@ -67,14 +69,40 @@ function readWorkspaceCatalogue(path) {
|
|
|
67
69
|
throw new Error(`Invalid catalogue.json at ${path}.`);
|
|
68
70
|
}
|
|
69
71
|
const catalogue = parsed;
|
|
70
|
-
if (catalogue
|
|
71
|
-
throw new Error(`catalogue.json at ${path} must
|
|
72
|
+
if (Object.prototype.hasOwnProperty.call(catalogue, LEGACY_CATALOGUE_VERSION_KEY)) {
|
|
73
|
+
throw new Error(`catalogue.json at ${path} must not define the legacy top-level version field.`);
|
|
74
|
+
}
|
|
75
|
+
const unsupportedKey = Object.keys(catalogue).find((key) => !ALLOWED_CATALOGUE_TOP_LEVEL_KEYS.has(key));
|
|
76
|
+
if (unsupportedKey) {
|
|
77
|
+
throw new Error(`catalogue.json at ${path} contains unsupported top-level key "${unsupportedKey}".`);
|
|
72
78
|
}
|
|
73
79
|
return catalogue;
|
|
74
80
|
}
|
|
75
81
|
function writeWorkspaceCatalogue(path, catalogue) {
|
|
76
82
|
(0, node_fs_1.writeFileSync)(path, `${JSON.stringify(catalogue, null, 2)}\n`);
|
|
77
83
|
}
|
|
84
|
+
function assertWorkspaceRootCanOwnEntries(cataloguePath) {
|
|
85
|
+
const rootDir = (0, node_path_1.resolve)(cataloguePath, '..');
|
|
86
|
+
const rootCataloguePath = (0, node_path_1.join)(rootDir, CATALOGUE_FILENAME);
|
|
87
|
+
const stack = [rootDir];
|
|
88
|
+
while (stack.length > 0) {
|
|
89
|
+
const current = stack.pop();
|
|
90
|
+
const entries = (0, node_fs_1.readdirSync)(current, { withFileTypes: true });
|
|
91
|
+
for (const entry of entries) {
|
|
92
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules') {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const absolutePath = (0, node_path_1.join)(current, entry.name);
|
|
96
|
+
if (entry.isDirectory()) {
|
|
97
|
+
stack.push(absolutePath);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (entry.isFile() && entry.name === CATALOGUE_FILENAME && absolutePath !== rootCataloguePath) {
|
|
101
|
+
throw new Error(`catalogue.json at ${cataloguePath} must stay {} because nested catalogue.json files already exist below this directory.`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
78
106
|
async function createAuthenticatedClient(commandLabel) {
|
|
79
107
|
const cfg = (0, config_1.loadConfig)();
|
|
80
108
|
const envName = cfg.env;
|
|
@@ -178,6 +206,7 @@ async function createAssetRemix(name, remixRef) {
|
|
|
178
206
|
const client = await createAuthenticatedClient('project create asset');
|
|
179
207
|
const cataloguePath = await ensureWorkspaceCataloguePath();
|
|
180
208
|
const catalogue = readWorkspaceCatalogue(cataloguePath);
|
|
209
|
+
assertWorkspaceRootCanOwnEntries(cataloguePath);
|
|
181
210
|
ensureUniqueEntryName(catalogue, 'asset', name);
|
|
182
211
|
const detail = await client.fetchAssetBySlug(parsedRef.creatorUsername, parsedRef.name);
|
|
183
212
|
const versions = await client.listAssetVersions(parsedRef.creatorUsername, parsedRef.name, { limit: 200, offset: 0 });
|
|
@@ -223,6 +252,7 @@ async function createPackRemix(name, remixRef) {
|
|
|
223
252
|
const client = await createAuthenticatedClient('project create pack');
|
|
224
253
|
const cataloguePath = await ensureWorkspaceCataloguePath();
|
|
225
254
|
const catalogue = readWorkspaceCatalogue(cataloguePath);
|
|
255
|
+
assertWorkspaceRootCanOwnEntries(cataloguePath);
|
|
226
256
|
ensureUniqueEntryName(catalogue, 'pack', name);
|
|
227
257
|
const detail = await client.fetchAssetPackBySlug(parsedRef.creatorUsername, parsedRef.name);
|
|
228
258
|
const versions = await client.listAssetPackVersions(parsedRef.creatorUsername, parsedRef.name, { limit: 200, offset: 0 });
|
package/dist/commands/init.js
CHANGED
|
@@ -16,7 +16,9 @@ const http_1 = require("../http");
|
|
|
16
16
|
const environment_1 = require("../environment");
|
|
17
17
|
const messages_1 = require("../messages");
|
|
18
18
|
const clientInfo_1 = require("../clientInfo");
|
|
19
|
-
const DEFAULT_CATALOGUE =
|
|
19
|
+
const DEFAULT_CATALOGUE = '{}\n';
|
|
20
|
+
const LEGACY_CATALOGUE_VERSION_KEY = ['schema', 'Version'].join('');
|
|
21
|
+
const ALLOWED_CATALOGUE_TOP_LEVEL_KEYS = new Set(['apps', 'assets', 'assetPacks']);
|
|
20
22
|
function ensureTrailingNewline(content) {
|
|
21
23
|
return content.endsWith('\n') ? content : `${content}\n`;
|
|
22
24
|
}
|
|
@@ -100,7 +102,33 @@ function normalizeBootstrapPayload(payload) {
|
|
|
100
102
|
const catalogueRaw = typeof payload.catalogue === 'string' && payload.catalogue.trim().length > 0
|
|
101
103
|
? payload.catalogue
|
|
102
104
|
: DEFAULT_CATALOGUE;
|
|
103
|
-
const
|
|
105
|
+
const catalogueCandidate = ensureTrailingNewline(catalogueRaw.trim() ? catalogueRaw : DEFAULT_CATALOGUE);
|
|
106
|
+
let parsedCatalogue;
|
|
107
|
+
try {
|
|
108
|
+
parsedCatalogue = JSON.parse(catalogueCandidate);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
(0, messages_1.printErrorWithHelp)('Bootstrap payload returned an invalid catalogue.json.', ['Contact the platform team or try again shortly.'], { command: 'project init' });
|
|
112
|
+
process.exitCode = 1;
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
if (!parsedCatalogue || typeof parsedCatalogue !== 'object' || Array.isArray(parsedCatalogue)) {
|
|
116
|
+
(0, messages_1.printErrorWithHelp)('Bootstrap payload returned an invalid catalogue.json.', ['Contact the platform team or try again shortly.'], { command: 'project init' });
|
|
117
|
+
process.exitCode = 1;
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
if (Object.prototype.hasOwnProperty.call(parsedCatalogue, LEGACY_CATALOGUE_VERSION_KEY)) {
|
|
121
|
+
(0, messages_1.printErrorWithHelp)('Bootstrap payload returned a legacy catalogue.json with a top-level version field.', ['Contact the platform team or try again shortly.'], { command: 'project init' });
|
|
122
|
+
process.exitCode = 1;
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
const unsupportedKey = Object.keys(parsedCatalogue).find((key) => !ALLOWED_CATALOGUE_TOP_LEVEL_KEYS.has(key));
|
|
126
|
+
if (unsupportedKey) {
|
|
127
|
+
(0, messages_1.printErrorWithHelp)(`Bootstrap payload returned catalogue.json with unsupported top-level key "${unsupportedKey}".`, ['Contact the platform team or try again shortly.'], { command: 'project init' });
|
|
128
|
+
process.exitCode = 1;
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
const catalogue = catalogueCandidate;
|
|
104
132
|
return { readmeUri, agentsUri, catalogue };
|
|
105
133
|
}
|
|
106
134
|
async function downloadBootstrapAsset(webBase, uri, label) {
|
package/dist/taskSelection.js
CHANGED
|
@@ -66,8 +66,8 @@ function selectTasks(pathOrName) {
|
|
|
66
66
|
if (taskList.length === 0) {
|
|
67
67
|
errors.push({
|
|
68
68
|
type: 'no-tasks',
|
|
69
|
-
message: `No
|
|
70
|
-
help: ['Add catalogue.json files
|
|
69
|
+
message: `No publishable entries were found under ${absolute}.`,
|
|
70
|
+
help: ['Add child catalogue.json files or add apps, assets, or assetPacks to the root catalogue.json, then rerun the command.'],
|
|
71
71
|
directory: absolute,
|
|
72
72
|
});
|
|
73
73
|
return { tasks: [], warnings, errors, resolution: 'directory' };
|
|
@@ -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
|
}
|
|
@@ -53,6 +53,19 @@ function runtime(overrides = {}) {
|
|
|
53
53
|
strict_1.default.equal(result.code, 'unsupported_platform_version');
|
|
54
54
|
}
|
|
55
55
|
});
|
|
56
|
+
(0, node_test_1.default)('does not require a build floor for newer cli versions once the version is supported', () => {
|
|
57
|
+
const meta = (0, index_1.loadClientMeta)();
|
|
58
|
+
const requiredVersion = meta.clients['cli']?.minimumVersion ?? meta.version;
|
|
59
|
+
const info = runtime({
|
|
60
|
+
client: 'cli',
|
|
61
|
+
clientVersion: requiredVersion,
|
|
62
|
+
clientBuild: 0,
|
|
63
|
+
platform: 'macos',
|
|
64
|
+
platformVersion: '14.0',
|
|
65
|
+
});
|
|
66
|
+
const result = (0, index_1.validateClientEnvironment)(info);
|
|
67
|
+
strict_1.default.equal(result.ok, true);
|
|
68
|
+
});
|
|
56
69
|
(0, node_test_1.default)('returns failure when metadata is incomplete', () => {
|
|
57
70
|
const info = runtime({ clientVersion: '', platformVersion: '' });
|
|
58
71
|
const result = (0, index_1.validateClientEnvironment)(info);
|