@playdrop/playdrop-cli 0.6.1 → 0.6.4
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 +4 -1
- package/config/client-meta.json +12 -4
- package/dist/apps/index.d.ts +3 -2
- package/dist/apps/index.js +4 -2
- package/dist/apps/upload.js +3 -0
- package/dist/apps/validate.d.ts +2 -0
- package/dist/apps/validate.js +379 -0
- package/dist/catalogue.d.ts +10 -1
- package/dist/catalogue.js +44 -0
- package/dist/commands/create.js +5 -1
- package/dist/commands/detail.js +10 -3
- package/dist/commands/upload-content.js +4 -0
- package/dist/commands/upload.js +11 -10
- package/dist/commands/validate.js +1 -0
- package/dist/commands/versionsBrowse.js +8 -1
- package/dist/taskSelection.js +2 -0
- package/node_modules/@playdrop/api-client/dist/client.d.ts +4 -5
- package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/client.js +12 -0
- package/node_modules/@playdrop/api-client/dist/core/errors.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/core/errors.js +4 -1
- package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts +2 -1
- package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/assets.js +2 -0
- package/node_modules/@playdrop/api-client/dist/index.d.ts +4 -5
- package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/index.js +3 -0
- package/node_modules/@playdrop/config/client-meta.json +12 -4
- package/node_modules/@playdrop/config/dist/test/validateClientEnvironment.test.js +29 -1
- package/node_modules/@playdrop/config/dist/tsconfig.tsbuildinfo +1 -1
- package/node_modules/@playdrop/types/dist/api.d.ts +31 -1
- package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/api.js +3 -1
- package/node_modules/@playdrop/types/dist/asset-pack.d.ts +6 -0
- package/node_modules/@playdrop/types/dist/asset-pack.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/asset.d.ts +6 -0
- package/node_modules/@playdrop/types/dist/asset.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/content-license.d.ts +13 -0
- package/node_modules/@playdrop/types/dist/content-license.d.ts.map +1 -0
- package/node_modules/@playdrop/types/dist/content-license.js +18 -0
- package/node_modules/@playdrop/types/dist/index.d.ts +1 -0
- package/node_modules/@playdrop/types/dist/index.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/index.js +1 -0
- package/node_modules/@playdrop/types/dist/owned-assets.d.ts +2 -0
- package/node_modules/@playdrop/types/dist/owned-assets.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/owned-assets.js +1 -0
- package/node_modules/@playdrop/types/dist/version.d.ts +8 -0
- package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@ Official Playdrop CLI for the live [Playdrop](https://www.playdrop.ai/) platform
|
|
|
4
4
|
|
|
5
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
6
|
|
|
7
|
+
For AI coding workflows, pair the CLI with the public Playdrop plugin. The plugin is the preferred public setup and contains many specialist Playdrop skills for Codex, Claude Code, and Cursor.
|
|
8
|
+
|
|
7
9
|
## Install
|
|
8
10
|
|
|
9
11
|
```bash
|
|
@@ -33,11 +35,12 @@ playdrop project publish .
|
|
|
33
35
|
|
|
34
36
|
- Website: [playdrop.ai](https://www.playdrop.ai/)
|
|
35
37
|
- Getting started: [playdrop.ai/getting-started](https://www.playdrop.ai/getting-started)
|
|
38
|
+
- Plugin docs: [playdrop.ai/docs/plugin](https://www.playdrop.ai/docs/plugin)
|
|
36
39
|
- CLI docs: [playdrop.ai/docs/cli](https://www.playdrop.ai/docs/cli)
|
|
37
40
|
- Runtime docs: [playdrop.ai/docs/runtime](https://www.playdrop.ai/docs/runtime)
|
|
38
41
|
- Templates and demos: [playdrop.ai/docs/examples#templates-and-demos](https://www.playdrop.ai/docs/examples#templates-and-demos)
|
|
39
42
|
- Canonical public plugin repo: [github.com/playdrop-ai/playdrop-plugin](https://github.com/playdrop-ai/playdrop-plugin)
|
|
40
|
-
- Legacy public skill
|
|
43
|
+
- Legacy public skill compatibility: [skills.sh/playdrop-ai/playdrop-skills/playdrop](https://skills.sh/playdrop-ai/playdrop-skills/playdrop)
|
|
41
44
|
|
|
42
45
|
## Live Examples
|
|
43
46
|
|
package/config/client-meta.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.6.
|
|
2
|
+
"version": "0.6.4",
|
|
3
3
|
"build": 1,
|
|
4
4
|
"platforms": {
|
|
5
5
|
"ios": {
|
|
@@ -26,19 +26,27 @@
|
|
|
26
26
|
},
|
|
27
27
|
"clients": {
|
|
28
28
|
"web": {
|
|
29
|
-
"minimumVersion": "0.6.
|
|
29
|
+
"minimumVersion": "0.6.4",
|
|
30
30
|
"minimumBuild": 1
|
|
31
31
|
},
|
|
32
32
|
"admin": {
|
|
33
|
-
"minimumVersion": "0.6.
|
|
33
|
+
"minimumVersion": "0.6.4",
|
|
34
34
|
"minimumBuild": 1
|
|
35
35
|
},
|
|
36
36
|
"apple": {
|
|
37
37
|
"minimumVersion": "0.3.10",
|
|
38
38
|
"minimumBuild": 1
|
|
39
39
|
},
|
|
40
|
+
"apple-playdrop": {
|
|
41
|
+
"minimumVersion": "0.3.10",
|
|
42
|
+
"minimumBuild": 1
|
|
43
|
+
},
|
|
44
|
+
"apple-games": {
|
|
45
|
+
"minimumVersion": "0.3.10",
|
|
46
|
+
"minimumBuild": 1
|
|
47
|
+
},
|
|
40
48
|
"cli": {
|
|
41
|
-
"minimumVersion": "0.6.
|
|
49
|
+
"minimumVersion": "0.6.4"
|
|
42
50
|
}
|
|
43
51
|
}
|
|
44
52
|
}
|
package/dist/apps/index.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { ApiClient } from '@playdrop/api-client';
|
|
2
2
|
import type { AppTask } from '../catalogue';
|
|
3
|
-
import { validateAppTask, runFormatScript } from './validate';
|
|
3
|
+
import { collectAppValidationWarnings, validateAppTask, runFormatScript } from './validate';
|
|
4
4
|
import { buildApp, type AppBuildArtifacts } from './build';
|
|
5
5
|
import { uploadApp, type AppUploadResult } from './upload';
|
|
6
6
|
export type AppPipelineResult = {
|
|
7
7
|
artifacts: AppBuildArtifacts | null;
|
|
8
8
|
upload: AppUploadResult;
|
|
9
|
+
warnings: string[];
|
|
9
10
|
};
|
|
10
11
|
export type AppPipelineOptions = {
|
|
11
12
|
skipEcs?: boolean;
|
|
@@ -14,4 +15,4 @@ export type AppPipelineOptions = {
|
|
|
14
15
|
creatorUsername?: string;
|
|
15
16
|
};
|
|
16
17
|
export declare function runAppPipeline(client: ApiClient, task: AppTask, options?: AppPipelineOptions): Promise<AppPipelineResult>;
|
|
17
|
-
export { validateAppTask, buildApp, uploadApp, runFormatScript };
|
|
18
|
+
export { collectAppValidationWarnings, validateAppTask, buildApp, uploadApp, runFormatScript };
|
package/dist/apps/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.runFormatScript = exports.uploadApp = exports.buildApp = exports.validateAppTask = void 0;
|
|
3
|
+
exports.runFormatScript = exports.uploadApp = exports.buildApp = exports.validateAppTask = exports.collectAppValidationWarnings = void 0;
|
|
4
4
|
exports.runAppPipeline = runAppPipeline;
|
|
5
5
|
const validate_1 = require("./validate");
|
|
6
|
+
Object.defineProperty(exports, "collectAppValidationWarnings", { enumerable: true, get: function () { return validate_1.collectAppValidationWarnings; } });
|
|
6
7
|
Object.defineProperty(exports, "validateAppTask", { enumerable: true, get: function () { return validate_1.validateAppTask; } });
|
|
7
8
|
Object.defineProperty(exports, "runFormatScript", { enumerable: true, get: function () { return validate_1.runFormatScript; } });
|
|
8
9
|
const build_1 = require("./build");
|
|
@@ -14,6 +15,7 @@ async function runAppPipeline(client, task, options) {
|
|
|
14
15
|
// External apps don't need to be built - they're hosted elsewhere
|
|
15
16
|
const isExternal = task.hostingMode === 'EXTERNAL' || !!task.externalUrl;
|
|
16
17
|
const artifacts = isExternal ? null : await (0, build_1.buildApp)(task);
|
|
18
|
+
const warnings = (0, validate_1.collectAppValidationWarnings)(task, isExternal ? 'source' : 'bundle');
|
|
17
19
|
const uploadOptions = {
|
|
18
20
|
skipEcs: options?.skipEcs,
|
|
19
21
|
skipReview: options?.skipReview,
|
|
@@ -21,5 +23,5 @@ async function runAppPipeline(client, task, options) {
|
|
|
21
23
|
creatorUsername: options?.creatorUsername,
|
|
22
24
|
};
|
|
23
25
|
const upload = await (0, upload_1.uploadApp)(client, task, artifacts, uploadOptions);
|
|
24
|
-
return { artifacts, upload };
|
|
26
|
+
return { artifacts, upload, warnings };
|
|
25
27
|
}
|
package/dist/apps/upload.js
CHANGED
|
@@ -114,6 +114,7 @@ async function prepareOwnedAssetUpload(task) {
|
|
|
114
114
|
subcategory: task.subcategory,
|
|
115
115
|
format: task.format || (0, node_path_1.extname)(files[0]?.filename || '').replace(/^\./, '').toUpperCase() || 'GLB',
|
|
116
116
|
visibility: task.visibility,
|
|
117
|
+
license: task.license,
|
|
117
118
|
assetSpec: task.assetSpec,
|
|
118
119
|
shopListed: task.shopListed,
|
|
119
120
|
shopPriceCredits: task.shopPriceCredits,
|
|
@@ -253,6 +254,7 @@ async function uploadAppVersion(client, task, artifacts, options) {
|
|
|
253
254
|
version: task.version,
|
|
254
255
|
releaseNotes: task.releaseNotes,
|
|
255
256
|
visibility: task.versionVisibility,
|
|
257
|
+
license: task.license,
|
|
256
258
|
remixRef: task.remix,
|
|
257
259
|
usesAssets: task.uses.assets.map((entry) => ({
|
|
258
260
|
ref: entry.ref,
|
|
@@ -313,6 +315,7 @@ async function uploadAppVersion(client, task, artifacts, options) {
|
|
|
313
315
|
format: ownedAsset.format,
|
|
314
316
|
assetSpec: ownedAsset.assetSpec,
|
|
315
317
|
visibility: ownedAsset.visibility,
|
|
318
|
+
license: ownedAsset.license,
|
|
316
319
|
shopListed: ownedAsset.shopListed,
|
|
317
320
|
shopPriceCredits: ownedAsset.shopPriceCredits,
|
|
318
321
|
files: ownedAsset.files.map((file) => ({
|
package/dist/apps/validate.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import type { AppTask } from '../catalogue';
|
|
2
|
+
export type AppValidationWarningMode = 'source' | 'bundle';
|
|
3
|
+
export declare function collectAppValidationWarnings(task: AppTask, mode?: AppValidationWarningMode): string[];
|
|
2
4
|
export declare function runFormatScript(task: AppTask): Promise<boolean>;
|
|
3
5
|
export declare function validateAppTask(task: AppTask): Promise<void>;
|
package/dist/apps/validate.js
CHANGED
|
@@ -1,14 +1,39 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.collectAppValidationWarnings = collectAppValidationWarnings;
|
|
3
4
|
exports.runFormatScript = runFormatScript;
|
|
4
5
|
exports.validateAppTask = validateAppTask;
|
|
5
6
|
const node_fs_1 = require("node:fs");
|
|
6
7
|
const node_path_1 = require("node:path");
|
|
8
|
+
const semver_1 = require("semver");
|
|
9
|
+
const clientInfo_1 = require("../clientInfo");
|
|
7
10
|
const build_1 = require("./build");
|
|
8
11
|
const projectValidateCache = new Map();
|
|
9
12
|
const projectFormatCache = new Map();
|
|
10
13
|
const LEGACY_SCAN_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.html']);
|
|
11
14
|
const LEGACY_SCAN_IGNORED_DIRS = new Set(['node_modules', '.git', 'dist-test', 'coverage']);
|
|
15
|
+
const SDK_SCAN_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.mts', '.cts', '.vue', '.svelte', '.astro', '.html']);
|
|
16
|
+
const SDK_SCAN_IGNORED_DIRS = new Set([
|
|
17
|
+
'node_modules',
|
|
18
|
+
'.git',
|
|
19
|
+
'dist',
|
|
20
|
+
'build',
|
|
21
|
+
'out',
|
|
22
|
+
'.next',
|
|
23
|
+
'.turbo',
|
|
24
|
+
'.cache',
|
|
25
|
+
'.svelte-kit',
|
|
26
|
+
'coverage',
|
|
27
|
+
'tmp',
|
|
28
|
+
'logs',
|
|
29
|
+
'test',
|
|
30
|
+
'tests',
|
|
31
|
+
'__tests__',
|
|
32
|
+
]);
|
|
33
|
+
const SDK_ENTRY_HTML_FILENAMES = ['index.html', 'main.html', 'template.html'];
|
|
34
|
+
const SDK_ENTRY_SOURCE_BASENAMES = ['main', 'index', 'app', 'bootstrap', 'entry', 'client'];
|
|
35
|
+
const SDK_ENTRY_ROOT_DIRS = ['src', 'app', 'pages'];
|
|
36
|
+
const SDK_SPECIAL_ENTRY_PATHS = ['app/page', 'app/layout', 'pages/index', 'pages/_app'];
|
|
12
37
|
const LEGACY_SDK_SYMBOL_PATTERNS = [
|
|
13
38
|
{ symbol: 'selectedAvatarKey', pattern: /\bselectedAvatarKey\b/g },
|
|
14
39
|
{ symbol: 'sdk.entities', pattern: /\bsdk\s*\.\s*entities\b/g },
|
|
@@ -16,6 +41,21 @@ const LEGACY_SDK_SYMBOL_PATTERNS = [
|
|
|
16
41
|
{ symbol: 'sdk.assets.block', pattern: /\bsdk\s*\.\s*assets\s*\.\s*block\b/g },
|
|
17
42
|
{ symbol: 'loadEntity(', pattern: /\.\s*loadEntity\s*\(/g },
|
|
18
43
|
];
|
|
44
|
+
const SDK_REFERENCE_PATTERNS = [
|
|
45
|
+
/https:\/\/assets\.playdrop\.ai\/sdk\/playdrop\.js/g,
|
|
46
|
+
/@playdrop\/sdk(?:\/browser)?/g,
|
|
47
|
+
];
|
|
48
|
+
const SDK_INIT_PATTERNS = [
|
|
49
|
+
/\bwindow\s*\.\s*playdrop\s*\.\s*init\s*\(/g,
|
|
50
|
+
/\bplaydrop\s*\.\s*init\s*\(/g,
|
|
51
|
+
/\bsdk\s*\.\s*initialize\s*\(/g,
|
|
52
|
+
];
|
|
53
|
+
const LOCAL_HTML_SCRIPT_PATTERN = /<script\b[^>]*\bsrc\s*=\s*["']([^"']+)["'][^>]*>/gi;
|
|
54
|
+
const LOCAL_IMPORT_PATTERNS = [
|
|
55
|
+
/(?:import|export)\s+(?:[^'"`]*?\s+from\s+)?["']([^"']+)["']/g,
|
|
56
|
+
/import\s*\(\s*["']([^"']+)["']\s*\)/g,
|
|
57
|
+
];
|
|
58
|
+
const LOCAL_SCRIPT_COMMAND_PATTERN = /(?:^|(?:&&|\|\||;)\s*)(?:node(?:\s+--[^\s"'`;|&]+)*|tsx|ts-node(?:-esm)?|bun|deno\s+run)\s+(["']?)([^"'`\s;&|]+)\1/g;
|
|
19
59
|
function ensureWithinProject(task) {
|
|
20
60
|
const relativePath = (0, node_path_1.relative)(task.projectDir, task.filePath);
|
|
21
61
|
const segments = relativePath.split(/[/\\]+/);
|
|
@@ -81,6 +121,345 @@ function scanForLegacySdkSymbols(task) {
|
|
|
81
121
|
throw new Error(`[apps][validate] legacy SDK symbols detected for ${task.name}: ${details}${suffix}. Remove entity-era APIs before publishing.`);
|
|
82
122
|
}
|
|
83
123
|
}
|
|
124
|
+
function stripSpecifierDecorators(specifier) {
|
|
125
|
+
return specifier.split(/[?#]/, 1)[0]?.trim() ?? '';
|
|
126
|
+
}
|
|
127
|
+
function safeReadFile(filePath) {
|
|
128
|
+
try {
|
|
129
|
+
return (0, node_fs_1.readFileSync)(filePath, 'utf8');
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function isIgnoredSdkScanDirectory(name) {
|
|
136
|
+
return SDK_SCAN_IGNORED_DIRS.has(name);
|
|
137
|
+
}
|
|
138
|
+
function resolveSdkModulePath(basePath) {
|
|
139
|
+
const tryFile = (candidate) => {
|
|
140
|
+
if (!(0, node_fs_1.existsSync)(candidate)) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
return (0, node_fs_1.statSync)(candidate).isFile() ? candidate : null;
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
const direct = tryFile(basePath);
|
|
151
|
+
if (direct) {
|
|
152
|
+
return direct;
|
|
153
|
+
}
|
|
154
|
+
if (!(0, node_path_1.extname)(basePath)) {
|
|
155
|
+
for (const extension of SDK_SCAN_EXTENSIONS) {
|
|
156
|
+
const withExtension = tryFile(`${basePath}${extension}`);
|
|
157
|
+
if (withExtension) {
|
|
158
|
+
return withExtension;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if ((0, node_fs_1.existsSync)(basePath)) {
|
|
163
|
+
try {
|
|
164
|
+
if ((0, node_fs_1.statSync)(basePath).isDirectory()) {
|
|
165
|
+
for (const extension of SDK_SCAN_EXTENSIONS) {
|
|
166
|
+
const indexCandidate = tryFile((0, node_path_1.join)(basePath, `index${extension}`));
|
|
167
|
+
if (indexCandidate) {
|
|
168
|
+
return indexCandidate;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
function resolveLocalSpecifier(task, sourceFilePath, specifier) {
|
|
180
|
+
const normalized = stripSpecifierDecorators(specifier);
|
|
181
|
+
if (!normalized) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
if (normalized.startsWith('/')) {
|
|
185
|
+
return resolveSdkModulePath((0, node_path_1.resolve)(task.projectDir, `.${normalized}`));
|
|
186
|
+
}
|
|
187
|
+
if (!normalized.startsWith('.')) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
return resolveSdkModulePath((0, node_path_1.resolve)((0, node_path_1.dirname)(sourceFilePath), normalized));
|
|
191
|
+
}
|
|
192
|
+
function extractLocalHtmlScriptFiles(task, filePath, source) {
|
|
193
|
+
const matches = [];
|
|
194
|
+
LOCAL_HTML_SCRIPT_PATTERN.lastIndex = 0;
|
|
195
|
+
let match = null;
|
|
196
|
+
while ((match = LOCAL_HTML_SCRIPT_PATTERN.exec(source)) !== null) {
|
|
197
|
+
const specifier = match[1];
|
|
198
|
+
if (!specifier) {
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const resolved = resolveLocalSpecifier(task, filePath, specifier);
|
|
202
|
+
if (resolved) {
|
|
203
|
+
matches.push(resolved);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return matches;
|
|
207
|
+
}
|
|
208
|
+
function extractLocalImportedFiles(task, filePath, source) {
|
|
209
|
+
const matches = [];
|
|
210
|
+
for (const pattern of LOCAL_IMPORT_PATTERNS) {
|
|
211
|
+
pattern.lastIndex = 0;
|
|
212
|
+
let match = null;
|
|
213
|
+
while ((match = pattern.exec(source)) !== null) {
|
|
214
|
+
const specifier = match[1];
|
|
215
|
+
if (!specifier) {
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
const resolved = resolveLocalSpecifier(task, filePath, specifier);
|
|
219
|
+
if (resolved) {
|
|
220
|
+
matches.push(resolved);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return matches;
|
|
225
|
+
}
|
|
226
|
+
function addFileIfPresent(target, filePath) {
|
|
227
|
+
if (!(0, node_fs_1.existsSync)(filePath)) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
if ((0, node_fs_1.statSync)(filePath).isFile()) {
|
|
232
|
+
target.add(filePath);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
// Ignore unreadable candidates and continue scanning the rest.
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
function extractLocalScriptCommandFiles(task) {
|
|
240
|
+
if (!task.packageJsonPath) {
|
|
241
|
+
return [];
|
|
242
|
+
}
|
|
243
|
+
const rawPackageJson = safeReadFile(task.packageJsonPath);
|
|
244
|
+
if (!rawPackageJson) {
|
|
245
|
+
return [];
|
|
246
|
+
}
|
|
247
|
+
let packageJson;
|
|
248
|
+
try {
|
|
249
|
+
packageJson = JSON.parse(rawPackageJson);
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
return [];
|
|
253
|
+
}
|
|
254
|
+
const scripts = packageJson?.scripts;
|
|
255
|
+
if (!scripts || typeof scripts !== 'object') {
|
|
256
|
+
return [];
|
|
257
|
+
}
|
|
258
|
+
const matches = new Set();
|
|
259
|
+
Object.values(scripts).forEach((scriptValue) => {
|
|
260
|
+
if (typeof scriptValue !== 'string') {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
LOCAL_SCRIPT_COMMAND_PATTERN.lastIndex = 0;
|
|
264
|
+
let match = null;
|
|
265
|
+
while ((match = LOCAL_SCRIPT_COMMAND_PATTERN.exec(scriptValue)) !== null) {
|
|
266
|
+
const specifier = stripSpecifierDecorators(match[2] ?? '');
|
|
267
|
+
if (!specifier || specifier.startsWith('-')) {
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
const resolvedPath = specifier.startsWith('/')
|
|
271
|
+
? resolveSdkModulePath((0, node_path_1.resolve)(specifier))
|
|
272
|
+
: resolveSdkModulePath((0, node_path_1.resolve)(task.projectDir, specifier));
|
|
273
|
+
if (resolvedPath) {
|
|
274
|
+
matches.add(resolvedPath);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
return [...matches];
|
|
279
|
+
}
|
|
280
|
+
function collectSourceSeedFiles(task) {
|
|
281
|
+
const seeds = new Set();
|
|
282
|
+
const entryRelativePath = (0, node_path_1.relative)(task.projectDir, task.filePath);
|
|
283
|
+
const entrySegments = entryRelativePath.split(/[/\\]+/);
|
|
284
|
+
const entryCrossesIgnoredSourceDirectory = entrySegments.some((segment) => isIgnoredSdkScanDirectory(segment));
|
|
285
|
+
if (entryRelativePath && !entryRelativePath.startsWith('..') && !entryCrossesIgnoredSourceDirectory) {
|
|
286
|
+
addFileIfPresent(seeds, task.filePath);
|
|
287
|
+
}
|
|
288
|
+
SDK_ENTRY_HTML_FILENAMES.forEach((filename) => {
|
|
289
|
+
addFileIfPresent(seeds, (0, node_path_1.join)(task.projectDir, filename));
|
|
290
|
+
addFileIfPresent(seeds, (0, node_path_1.join)(task.projectDir, 'public', filename));
|
|
291
|
+
});
|
|
292
|
+
const sourceExtensions = [...SDK_SCAN_EXTENSIONS].filter((extension) => extension !== '.html');
|
|
293
|
+
const addEntryBasenames = (directory) => {
|
|
294
|
+
SDK_ENTRY_SOURCE_BASENAMES.forEach((basename) => {
|
|
295
|
+
sourceExtensions.forEach((extension) => {
|
|
296
|
+
addFileIfPresent(seeds, (0, node_path_1.join)(directory, `${basename}${extension}`));
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
};
|
|
300
|
+
addEntryBasenames(task.projectDir);
|
|
301
|
+
SDK_ENTRY_ROOT_DIRS.forEach((directory) => {
|
|
302
|
+
addEntryBasenames((0, node_path_1.join)(task.projectDir, directory));
|
|
303
|
+
});
|
|
304
|
+
SDK_SPECIAL_ENTRY_PATHS.forEach((relativePath) => {
|
|
305
|
+
sourceExtensions.forEach((extension) => {
|
|
306
|
+
addFileIfPresent(seeds, (0, node_path_1.join)(task.projectDir, `${relativePath}${extension}`));
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
extractLocalScriptCommandFiles(task).forEach((filePath) => {
|
|
310
|
+
const relativePath = (0, node_path_1.relative)(task.projectDir, filePath);
|
|
311
|
+
const segments = relativePath.split(/[/\\]+/);
|
|
312
|
+
const crossesIgnoredSourceDirectory = segments.some((segment) => isIgnoredSdkScanDirectory(segment));
|
|
313
|
+
if (!relativePath || relativePath.startsWith('..') || crossesIgnoredSourceDirectory) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
seeds.add(filePath);
|
|
317
|
+
});
|
|
318
|
+
return [...seeds];
|
|
319
|
+
}
|
|
320
|
+
function collectBundleSeedFiles(task) {
|
|
321
|
+
const seeds = new Set();
|
|
322
|
+
addFileIfPresent(seeds, task.filePath);
|
|
323
|
+
return [...seeds];
|
|
324
|
+
}
|
|
325
|
+
function collectSdkDetectionFiles(task, mode) {
|
|
326
|
+
const queue = mode === 'bundle' ? collectBundleSeedFiles(task) : collectSourceSeedFiles(task);
|
|
327
|
+
const queued = new Set(queue);
|
|
328
|
+
const visited = new Set();
|
|
329
|
+
if (task.packageJsonPath) {
|
|
330
|
+
visited.add(task.packageJsonPath);
|
|
331
|
+
}
|
|
332
|
+
while (queue.length > 0) {
|
|
333
|
+
const filePath = queue.shift();
|
|
334
|
+
if (!filePath || visited.has(filePath)) {
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
if (!(0, node_fs_1.existsSync)(filePath)) {
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
let isFile = false;
|
|
341
|
+
try {
|
|
342
|
+
isFile = (0, node_fs_1.statSync)(filePath).isFile();
|
|
343
|
+
}
|
|
344
|
+
catch {
|
|
345
|
+
isFile = false;
|
|
346
|
+
}
|
|
347
|
+
if (!isFile) {
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
const extension = (0, node_path_1.extname)(filePath).toLowerCase();
|
|
351
|
+
if (extension && !SDK_SCAN_EXTENSIONS.has(extension)) {
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
visited.add(filePath);
|
|
355
|
+
const source = safeReadFile(filePath);
|
|
356
|
+
if (!source) {
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
const localReferences = extension === '.html'
|
|
360
|
+
? extractLocalHtmlScriptFiles(task, filePath, source)
|
|
361
|
+
: extractLocalImportedFiles(task, filePath, source);
|
|
362
|
+
localReferences.forEach((resolvedPath) => {
|
|
363
|
+
const relativePath = (0, node_path_1.relative)(task.projectDir, resolvedPath);
|
|
364
|
+
const segments = relativePath.split(/[/\\]+/);
|
|
365
|
+
const crossesIgnoredSourceDirectory = mode === 'source'
|
|
366
|
+
&& segments.some((segment) => isIgnoredSdkScanDirectory(segment));
|
|
367
|
+
if (!relativePath || relativePath.startsWith('..') || crossesIgnoredSourceDirectory) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
if (!queued.has(resolvedPath) && !visited.has(resolvedPath)) {
|
|
371
|
+
queue.push(resolvedPath);
|
|
372
|
+
queued.add(resolvedPath);
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
return [...visited];
|
|
377
|
+
}
|
|
378
|
+
function detectSdkUsage(task, mode) {
|
|
379
|
+
let hasSdkReference = false;
|
|
380
|
+
let hasSdkInit = false;
|
|
381
|
+
const files = collectSdkDetectionFiles(task, mode);
|
|
382
|
+
for (const filePath of files) {
|
|
383
|
+
if (hasSdkReference && hasSdkInit) {
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
const source = safeReadFile(filePath);
|
|
387
|
+
if (!source) {
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
if (!hasSdkReference) {
|
|
391
|
+
hasSdkReference = SDK_REFERENCE_PATTERNS.some((pattern) => {
|
|
392
|
+
pattern.lastIndex = 0;
|
|
393
|
+
return pattern.test(source);
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
if (!hasSdkInit) {
|
|
397
|
+
hasSdkInit = SDK_INIT_PATTERNS.some((pattern) => {
|
|
398
|
+
pattern.lastIndex = 0;
|
|
399
|
+
return pattern.test(source);
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return { hasSdkReference, hasSdkInit };
|
|
404
|
+
}
|
|
405
|
+
function collectOutdatedSdkVersionWarnings(task) {
|
|
406
|
+
if (!task.packageJsonPath) {
|
|
407
|
+
return [];
|
|
408
|
+
}
|
|
409
|
+
const rawPackageJson = safeReadFile(task.packageJsonPath);
|
|
410
|
+
if (!rawPackageJson) {
|
|
411
|
+
return [];
|
|
412
|
+
}
|
|
413
|
+
let packageJson;
|
|
414
|
+
try {
|
|
415
|
+
packageJson = JSON.parse(rawPackageJson);
|
|
416
|
+
}
|
|
417
|
+
catch {
|
|
418
|
+
return [];
|
|
419
|
+
}
|
|
420
|
+
const cliVersion = (0, clientInfo_1.getCliVersion)();
|
|
421
|
+
if (!(0, semver_1.valid)(cliVersion)) {
|
|
422
|
+
return [];
|
|
423
|
+
}
|
|
424
|
+
const sdkTypesDependency = typeof packageJson?.devDependencies?.['playdrop-sdk-types'] === 'string'
|
|
425
|
+
? packageJson.devDependencies['playdrop-sdk-types']
|
|
426
|
+
: typeof packageJson?.dependencies?.['playdrop-sdk-types'] === 'string'
|
|
427
|
+
? packageJson.dependencies['playdrop-sdk-types']
|
|
428
|
+
: '';
|
|
429
|
+
const sdkTypesVersion = typeof packageJson?.playdrop?.sdkTypesVersion === 'string'
|
|
430
|
+
? packageJson.playdrop.sdkTypesVersion.trim()
|
|
431
|
+
: '';
|
|
432
|
+
const usesVendoredSdkTypes = sdkTypesDependency.includes('playdrop-sdk-types')
|
|
433
|
+
&& (sdkTypesDependency.startsWith('file:') || sdkTypesDependency.includes('.tgz'));
|
|
434
|
+
if (!usesVendoredSdkTypes
|
|
435
|
+
|| !(0, semver_1.valid)(sdkTypesVersion)
|
|
436
|
+
|| !(0, semver_1.lt)(sdkTypesVersion, cliVersion)) {
|
|
437
|
+
return [];
|
|
438
|
+
}
|
|
439
|
+
return [
|
|
440
|
+
`[apps][validate] ${task.name} vendors playdrop-sdk-types.tgz at ${sdkTypesVersion}, which is older than this CLI version ${cliVersion}. Refresh vendor/playdrop-sdk-types.tgz and package.json playdrop.sdkTypesVersion before upload.`,
|
|
441
|
+
];
|
|
442
|
+
}
|
|
443
|
+
function collectAppValidationWarnings(task, mode = 'source') {
|
|
444
|
+
const isExternal = task.hostingMode === 'EXTERNAL' || !!task.externalUrl;
|
|
445
|
+
if (isExternal) {
|
|
446
|
+
return [];
|
|
447
|
+
}
|
|
448
|
+
const primaryDetection = detectSdkUsage(task, mode);
|
|
449
|
+
const sourceDetection = mode === 'bundle'
|
|
450
|
+
? detectSdkUsage(task, 'source')
|
|
451
|
+
: primaryDetection;
|
|
452
|
+
const hasSdkReference = primaryDetection.hasSdkReference || sourceDetection.hasSdkReference;
|
|
453
|
+
const hasSdkInit = primaryDetection.hasSdkInit || sourceDetection.hasSdkInit;
|
|
454
|
+
const warnings = [];
|
|
455
|
+
if (!hasSdkReference) {
|
|
456
|
+
warnings.push(`[apps][validate] Could not detect the Playdrop SDK loader or @playdrop/sdk import for ${task.name}. We could not find https://assets.playdrop.ai/sdk/playdrop.js or @playdrop/sdk in the app files, so it might not work once uploaded.`);
|
|
457
|
+
}
|
|
458
|
+
if (!hasSdkInit) {
|
|
459
|
+
warnings.push(`[apps][validate] Could not detect Playdrop SDK initialization for ${task.name}. We could not find playdrop.init() in the app files, so it might not work once uploaded.`);
|
|
460
|
+
}
|
|
461
|
+
return [...warnings, ...collectOutdatedSdkVersionWarnings(task)];
|
|
462
|
+
}
|
|
84
463
|
async function runFormatScript(task) {
|
|
85
464
|
if (!task.packageJsonPath || !task.hasFormatScript) {
|
|
86
465
|
return false;
|
package/dist/catalogue.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AppSurface, type AppType, type AppHostingMode, type AppAuthMode, type AppControllerMode, type AppVersionVisibility, type AppAchievementCatalogueDefinition, type AppLeaderboardCatalogueDefinition, type PlayerMetaDefinitionStatus, type CatalogueTagGroupDefinition, type AppMetadataAssetSpecSupport, type AssetSpecContract, type AssetSpecStatus, type AssetSpecValidationKind } from '@playdrop/types';
|
|
1
|
+
import { type AppSurface, type AppType, type AppHostingMode, type AppAuthMode, type AppControllerMode, type AppVersionVisibility, type AppAchievementCatalogueDefinition, type AppLeaderboardCatalogueDefinition, type PlayerMetaDefinitionStatus, type CatalogueTagGroupDefinition, type AppMetadataAssetSpecSupport, type AssetSpecContract, type AssetSpecStatus, type AssetSpecValidationKind, type ContentLicense } from '@playdrop/types';
|
|
2
2
|
import { type AssetSpecSupportDeclaration } from './assetSpecs';
|
|
3
3
|
export type CatalogueJson = {
|
|
4
4
|
apps?: Array<Record<string, unknown>>;
|
|
@@ -44,6 +44,7 @@ export type AppCatalogueEntry = {
|
|
|
44
44
|
version?: string;
|
|
45
45
|
releaseNotes?: string;
|
|
46
46
|
visibility?: string;
|
|
47
|
+
license?: string;
|
|
47
48
|
hostingMode?: string;
|
|
48
49
|
authMode?: string;
|
|
49
50
|
controllerMode?: string;
|
|
@@ -76,6 +77,7 @@ export type AssetCatalogueEntry = {
|
|
|
76
77
|
username?: string;
|
|
77
78
|
files?: Record<string, string>;
|
|
78
79
|
visibility?: string;
|
|
80
|
+
license?: string;
|
|
79
81
|
tags?: string[];
|
|
80
82
|
shopListed?: boolean;
|
|
81
83
|
shopPriceCredits?: number;
|
|
@@ -107,6 +109,7 @@ export type OwnedAssetCatalogueEntry = {
|
|
|
107
109
|
format?: string;
|
|
108
110
|
files?: Record<string, string>;
|
|
109
111
|
visibility?: string;
|
|
112
|
+
license?: string;
|
|
110
113
|
shopListed?: boolean;
|
|
111
114
|
shopPriceCredits?: number;
|
|
112
115
|
};
|
|
@@ -121,6 +124,7 @@ export type AssetPackCatalogueEntry = {
|
|
|
121
124
|
externalUrl?: string;
|
|
122
125
|
downloadUrl?: string;
|
|
123
126
|
visibility?: string;
|
|
127
|
+
license?: string;
|
|
124
128
|
releaseNotes?: string;
|
|
125
129
|
listing?: AppListingConfig;
|
|
126
130
|
tags?: string[];
|
|
@@ -176,6 +180,7 @@ export type AppTask = {
|
|
|
176
180
|
version?: string;
|
|
177
181
|
releaseNotes?: string;
|
|
178
182
|
versionVisibility?: AppVersionVisibility;
|
|
183
|
+
license: ContentLicense;
|
|
179
184
|
tags: string[];
|
|
180
185
|
hostingMode?: AppHostingMode;
|
|
181
186
|
authMode?: AppAuthMode;
|
|
@@ -212,6 +217,7 @@ export type AssetTask = {
|
|
|
212
217
|
remix?: string;
|
|
213
218
|
format?: string;
|
|
214
219
|
visibility?: string;
|
|
220
|
+
license: ContentLicense;
|
|
215
221
|
tags: string[];
|
|
216
222
|
shopListed?: boolean;
|
|
217
223
|
shopPriceCredits?: number;
|
|
@@ -255,6 +261,7 @@ export type OwnedAssetTask = {
|
|
|
255
261
|
assetSpecContract?: AssetSpecContract;
|
|
256
262
|
format?: string;
|
|
257
263
|
visibility?: string;
|
|
264
|
+
license: ContentLicense;
|
|
258
265
|
shopListed?: boolean;
|
|
259
266
|
shopPriceCredits?: number;
|
|
260
267
|
files: Record<string, string>;
|
|
@@ -269,6 +276,7 @@ export type PackOwnedAssetTask = {
|
|
|
269
276
|
assetSpecContract?: AssetSpecContract;
|
|
270
277
|
format?: string;
|
|
271
278
|
visibility?: string;
|
|
279
|
+
license: ContentLicense;
|
|
272
280
|
shopListed?: boolean;
|
|
273
281
|
shopPriceCredits?: number;
|
|
274
282
|
files: Record<string, string>;
|
|
@@ -289,6 +297,7 @@ export type AssetPackTask = {
|
|
|
289
297
|
externalUrl?: string;
|
|
290
298
|
downloadUrl?: string;
|
|
291
299
|
visibility?: string;
|
|
300
|
+
license: ContentLicense;
|
|
292
301
|
releaseNotes?: string;
|
|
293
302
|
listing?: ResolvedListingAssets;
|
|
294
303
|
relations?: Array<{
|