@playdrop/playdrop-cli 0.12.27 → 0.12.29

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.
Files changed (32) hide show
  1. package/config/client-meta.json +1 -1
  2. package/dist/catalogue.js +1 -3
  3. package/dist/commands/devRuntimeAssets.d.ts +1 -1
  4. package/dist/commands/devRuntimeAssets.js +28 -123
  5. package/dist/commands/init.d.ts +1 -1
  6. package/dist/commands/init.js +11 -6
  7. package/dist/commands/ownedRuntimeImageValidation.d.ts +7 -0
  8. package/dist/commands/ownedRuntimeImageValidation.js +76 -0
  9. package/dist/commands/upload.d.ts +11 -0
  10. package/dist/commands/upload.js +72 -447
  11. package/dist/commands/validate.js +15 -1
  12. package/dist/commands/worker.d.ts +16 -0
  13. package/dist/commands/worker.js +165 -67
  14. package/dist/listingPreflight.d.ts +10 -0
  15. package/dist/listingPreflight.js +34 -10
  16. package/node_modules/@playdrop/api-client/dist/client.d.ts +2 -1
  17. package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
  18. package/node_modules/@playdrop/api-client/dist/core/errors.d.ts.map +1 -1
  19. package/node_modules/@playdrop/api-client/dist/core/errors.js +3 -1
  20. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts +2 -1
  21. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts.map +1 -1
  22. package/node_modules/@playdrop/api-client/dist/domains/apps.js +11 -0
  23. package/node_modules/@playdrop/api-client/dist/index.d.ts +2 -1
  24. package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
  25. package/node_modules/@playdrop/api-client/dist/index.js +5 -0
  26. package/node_modules/@playdrop/config/client-meta.json +1 -1
  27. package/node_modules/@playdrop/types/dist/api.d.ts +13 -0
  28. package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
  29. package/node_modules/@playdrop/types/dist/version.d.ts +6 -12
  30. package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
  31. package/node_modules/@playdrop/types/dist/version.js +48 -88
  32. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": "0.12.8",
3
- "build": 28,
3
+ "build": 32,
4
4
  "runtimeSdkVersion": "0.12.5",
5
5
  "runtimeSdkBuild": 2,
6
6
  "clients": {
package/dist/catalogue.js CHANGED
@@ -1279,9 +1279,7 @@ function buildAppTasks(rootDir, catalogues, options) {
1279
1279
  }
1280
1280
  let design;
1281
1281
  if (Object.prototype.hasOwnProperty.call(entry, 'design')) {
1282
- const parsedDesign = (0, types_1.normalizeAppVersionDesign)(entry.design, {
1283
- allowedPackRefs: usesPacks,
1284
- });
1282
+ const parsedDesign = (0, types_1.normalizeAppVersionDesign)(entry.design);
1285
1283
  if (!parsedDesign.ok) {
1286
1284
  errors.push(`[${label}] App "${rawName}" ${parsedDesign.error}`);
1287
1285
  continue;
@@ -26,7 +26,7 @@ export declare function createEmptyDevRuntimeAssetManifest(): DevRuntimeAssetMan
26
26
  export declare function buildLocalDevRuntimeAssetsUrl(appBaseUrl: string): string;
27
27
  export declare function buildLocalDevRuntimeAssetFileUrl(appBaseUrl: string, ownedAssetName: string, role: string): string;
28
28
  export declare function buildDevRuntimeAssetManifest(input: {
29
- client: ApiClient;
29
+ client?: Pick<ApiClient, 'resolveRuntimeAssets'>;
30
30
  apiBase: string;
31
31
  task: AppTask;
32
32
  creatorUsername: string;
@@ -37,6 +37,7 @@ const CONTENT_TYPE_BY_EXTENSION = {
37
37
  '.png': 'image/png',
38
38
  '.jpg': 'image/jpeg',
39
39
  '.jpeg': 'image/jpeg',
40
+ '.gif': 'image/gif',
40
41
  '.svg': 'image/svg+xml',
41
42
  '.webp': 'image/webp',
42
43
  '.glb': 'model/gltf-binary',
@@ -52,25 +53,6 @@ function inferContentType(filePath) {
52
53
  const extension = (0, node_path_1.extname)(filePath).toLowerCase();
53
54
  return CONTENT_TYPE_BY_EXTENSION[extension] ?? null;
54
55
  }
55
- function normalizeManifestFiles(fileManifest) {
56
- const files = fileManifest && typeof fileManifest === 'object' && Array.isArray(fileManifest.files)
57
- ? fileManifest.files
58
- : [];
59
- return files
60
- .filter((entry) => Boolean(entry && typeof entry === 'object'))
61
- .map((entry) => ({
62
- role: typeof entry.role === 'string' ? entry.role.trim().toLowerCase() : '',
63
- key: typeof entry.key === 'string' ? entry.key.trim() : '',
64
- contentType: typeof entry.contentType === 'string' ? entry.contentType : null,
65
- sizeBytes: typeof entry.sizeBytes === 'number' && Number.isFinite(entry.sizeBytes) ? entry.sizeBytes : null,
66
- sha256: typeof entry.sha256 === 'string' ? entry.sha256 : null,
67
- }))
68
- .filter((entry) => entry.role.length > 0);
69
- }
70
- function buildPublishedAssetFileUrl(apiBase, creatorUsername, assetName, revision, role) {
71
- const path = `/assets/${encodeURIComponent(creatorUsername)}/${encodeURIComponent(assetName)}/versions/${encodeURIComponent(`r${revision}`)}/file?role=${encodeURIComponent(role)}`;
72
- return new URL(path, apiBase).toString();
73
- }
74
56
  function buildSyntheticLocalAssetRef(creatorUsername, appName, ownedAssetName) {
75
57
  return (0, types_1.formatContentVersionRef)({
76
58
  kind: 'asset',
@@ -87,86 +69,6 @@ function buildLocalDevRuntimeAssetFileUrl(appBaseUrl, ownedAssetName, role) {
87
69
  const base = appBaseUrl.endsWith('/') ? appBaseUrl.slice(0, -1) : appBaseUrl;
88
70
  return `${base}/_playdrop/runtime-assets/files/${encodeURIComponent(ownedAssetName)}?role=${encodeURIComponent(role)}`;
89
71
  }
90
- async function findAssetVersionByRevision(client, creatorUsername, assetName, revision) {
91
- let offset = 0;
92
- for (let page = 0; page < 200; page += 1) {
93
- const response = await client.listAssetVersions(creatorUsername, assetName, {
94
- limit: 100,
95
- offset,
96
- });
97
- const version = (response.versions ?? []).find((entry) => entry.revision === revision);
98
- if (version) {
99
- return version;
100
- }
101
- if (!response.pagination?.hasMore) {
102
- break;
103
- }
104
- offset += response.pagination.limit;
105
- }
106
- throw new Error(`dev_runtime_asset_version_not_found:${creatorUsername}/${assetName}@r${revision}`);
107
- }
108
- async function findPackVersion(client, creatorUsername, packName, versionString) {
109
- let offset = 0;
110
- for (let page = 0; page < 200; page += 1) {
111
- const response = await client.listAssetPackVersions(creatorUsername, packName, {
112
- limit: 100,
113
- offset,
114
- });
115
- const version = (response.versions ?? []).find((entry) => entry.version === versionString);
116
- if (version) {
117
- return version;
118
- }
119
- if (!response.pagination?.hasMore) {
120
- break;
121
- }
122
- offset += response.pagination.limit;
123
- }
124
- throw new Error(`dev_runtime_pack_version_not_found:${creatorUsername}/${packName}@${versionString}`);
125
- }
126
- function createPublishedAssetSummary(input) {
127
- const files = normalizeManifestFiles(input.version.fileManifest).map((file) => ({
128
- role: file.role,
129
- url: buildPublishedAssetFileUrl(input.apiBase, input.creatorUsername, input.assetName, input.revision, file.role),
130
- contentType: file.contentType ?? null,
131
- sizeBytes: file.sizeBytes ?? null,
132
- sha256: file.sha256 ?? null,
133
- }));
134
- if (files.length === 0) {
135
- throw new Error(`dev_runtime_asset_files_missing:${input.creatorUsername}/${input.assetName}@r${input.revision}`);
136
- }
137
- return {
138
- assetRef: (0, types_1.formatContentVersionRef)({
139
- kind: 'asset',
140
- creatorUsername: input.creatorUsername,
141
- name: input.assetName,
142
- revision: input.revision,
143
- }),
144
- runtimeKey: input.runtimeKey?.trim() || null,
145
- sourceType: input.sourceType,
146
- ...(input.sourcePackRef ? { sourcePackRef: input.sourcePackRef } : {}),
147
- files,
148
- };
149
- }
150
- async function resolvePublishedAssetSummary(client, apiBase, assetRef, options) {
151
- const parsed = (0, types_1.parseContentVersionRef)(assetRef);
152
- if (!parsed || parsed.kind !== 'asset') {
153
- throw new Error(`dev_runtime_asset_ref_invalid:${assetRef}`);
154
- }
155
- const version = await findAssetVersionByRevision(client, parsed.creatorUsername, parsed.name, parsed.revision);
156
- if (version.visibility !== 'PUBLIC') {
157
- throw new Error(`dev_runtime_asset_not_public:${assetRef}`);
158
- }
159
- return createPublishedAssetSummary({
160
- apiBase,
161
- creatorUsername: parsed.creatorUsername,
162
- assetName: parsed.name,
163
- revision: parsed.revision,
164
- version,
165
- runtimeKey: options.runtimeKey,
166
- sourceType: options.sourceType,
167
- sourcePackRef: options.sourcePackRef,
168
- });
169
- }
170
72
  function createLocalOwnedAssetSummary(task, creatorUsername, appBaseUrl, ownedAsset) {
171
73
  const files = {};
172
74
  const runtimeFiles = [];
@@ -260,41 +162,44 @@ async function buildDevRuntimeAssetManifest(input) {
260
162
  assets.push(localAsset.asset);
261
163
  ownedAssetFiles[ownedAsset.name] = localAsset.files;
262
164
  }
263
- for (const dependency of input.task.uses.assets) {
165
+ const directDependencies = input.task.uses.assets.map((dependency) => {
264
166
  assertRuntimeKeyAvailable(seenRuntimeKeys, dependency.runtimeKey, dependency.ref);
265
167
  const parsedDependencyRef = (0, types_1.parseContentVersionRef)(dependency.ref);
266
168
  if (!parsedDependencyRef || parsedDependencyRef.kind !== 'asset') {
267
169
  throw new Error(`dev_runtime_asset_ref_invalid:${dependency.ref}`);
268
170
  }
269
- const dependencyVersion = await findAssetVersionByRevision(input.client, parsedDependencyRef.creatorUsername, parsedDependencyRef.name, parsedDependencyRef.revision);
270
- assertCustomAssetVersionSupported(input.task, dependency.ref, dependencyVersion);
271
- assets.push(await resolvePublishedAssetSummary(input.client, input.apiBase, dependency.ref, {
272
- runtimeKey: dependency.runtimeKey,
273
- sourceType: 'DIRECT',
274
- }));
275
- }
276
- for (const packRef of input.task.uses.packs) {
171
+ return {
172
+ ref: (0, types_1.formatContentVersionRef)(parsedDependencyRef),
173
+ runtimeKey: dependency.runtimeKey?.trim() || null,
174
+ };
175
+ });
176
+ const packDependencies = input.task.uses.packs.map((packRef) => {
277
177
  const parsedPackRef = (0, types_1.parseContentVersionRef)(packRef);
278
178
  if (!parsedPackRef || parsedPackRef.kind !== 'pack') {
279
179
  throw new Error(`dev_runtime_pack_ref_invalid:${packRef}`);
280
180
  }
281
- const packVersion = await findPackVersion(input.client, parsedPackRef.creatorUsername, parsedPackRef.name, parsedPackRef.version);
282
- if (packVersion.visibility !== 'PUBLIC') {
283
- throw new Error(`dev_runtime_pack_not_public:${packRef}`);
181
+ return (0, types_1.formatContentVersionRef)(parsedPackRef);
182
+ });
183
+ if (directDependencies.length > 0 || packDependencies.length > 0) {
184
+ if (!input.client) {
185
+ throw new Error('dev_runtime_asset_resolver_missing');
284
186
  }
285
- for (const member of packVersion.assets) {
286
- const memberRef = (0, types_1.formatContentVersionRef)({
287
- kind: 'asset',
288
- creatorUsername: member.creatorUsername,
289
- name: member.name,
290
- revision: member.revision,
187
+ const resolved = await input.client.resolveRuntimeAssets({
188
+ assets: directDependencies,
189
+ packs: packDependencies,
190
+ });
191
+ for (const entry of resolved.assets) {
192
+ if (entry.sourceType !== 'DIRECT' && entry.sourceType !== 'PACK') {
193
+ throw new Error(`dev_runtime_asset_source_type_invalid:${entry.assetRef}:${entry.sourceType}`);
194
+ }
195
+ assertCustomAssetVersionSupported(input.task, entry.assetRef, entry);
196
+ assets.push({
197
+ assetRef: entry.assetRef,
198
+ runtimeKey: entry.runtimeKey,
199
+ sourceType: entry.sourceType,
200
+ ...(entry.sourcePackRef ? { sourcePackRef: entry.sourcePackRef } : {}),
201
+ files: entry.files,
291
202
  });
292
- const memberVersion = await findAssetVersionByRevision(input.client, member.creatorUsername, member.name, member.revision);
293
- assertCustomAssetVersionSupported(input.task, memberRef, memberVersion);
294
- assets.push(await resolvePublishedAssetSummary(input.client, input.apiBase, memberRef, {
295
- sourceType: 'PACK',
296
- sourcePackRef: packRef,
297
- }));
298
203
  }
299
204
  }
300
205
  return {
@@ -5,7 +5,7 @@ export type InitSummary = {
5
5
  directory: string;
6
6
  cataloguePath: string;
7
7
  readmeStatus: 'created' | 'updated';
8
- agentsStatus: 'created' | 'updated';
8
+ agentsStatus: 'created' | 'skipped';
9
9
  catalogueStatus: 'created' | 'skipped';
10
10
  gitStatus: 'initialized' | 'skipped' | 'declined';
11
11
  };
@@ -246,12 +246,17 @@ async function init(targetPath, options = {}) {
246
246
  const renderedReadme = ensureTrailingNewline(renderReadmeTemplate(readmeDownload, replacements));
247
247
  (0, node_fs_1.writeFileSync)(readmePath, renderedReadme, 'utf8');
248
248
  actions.push(readmeExisted ? 'Updated README.md' : 'Created README.md');
249
- const agentsDownload = await downloadBootstrapAsset(envConfig.webBase, normalized.agentsUri, 'AGENTS.md');
250
- if (!agentsDownload) {
251
- return null;
249
+ if (agentsExisted) {
250
+ actions.push('Skipped existing AGENTS.md');
251
+ }
252
+ else {
253
+ const agentsDownload = await downloadBootstrapAsset(envConfig.webBase, normalized.agentsUri, 'AGENTS.md');
254
+ if (!agentsDownload) {
255
+ return null;
256
+ }
257
+ (0, node_fs_1.writeFileSync)(agentsPath, agentsDownload, 'utf8');
258
+ actions.push('Created AGENTS.md');
252
259
  }
253
- (0, node_fs_1.writeFileSync)(agentsPath, agentsDownload, 'utf8');
254
- actions.push(agentsExisted ? 'Updated AGENTS.md' : 'Created AGENTS.md');
255
260
  if (!catalogueExisted) {
256
261
  (0, node_fs_1.writeFileSync)(cataloguePath, normalized.catalogue, 'utf8');
257
262
  actions.push('Created catalogue.json');
@@ -294,7 +299,7 @@ async function init(targetPath, options = {}) {
294
299
  directory: resolved,
295
300
  cataloguePath,
296
301
  readmeStatus: readmeExisted ? 'updated' : 'created',
297
- agentsStatus: agentsExisted ? 'updated' : 'created',
302
+ agentsStatus: agentsExisted ? 'skipped' : 'created',
298
303
  catalogueStatus: catalogueExisted ? 'skipped' : 'created',
299
304
  gitStatus,
300
305
  };
@@ -0,0 +1,7 @@
1
+ import type { AppTask } from '../catalogue';
2
+ export declare function validateOwnedRuntimeImageFile(input: {
3
+ assetName: string;
4
+ filePath: string;
5
+ declaredFormat?: string;
6
+ }): Promise<void>;
7
+ export declare function assertOwnedRuntimeImagesAreValid(task: AppTask): Promise<void>;
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.validateOwnedRuntimeImageFile = validateOwnedRuntimeImageFile;
7
+ exports.assertOwnedRuntimeImagesAreValid = assertOwnedRuntimeImagesAreValid;
8
+ const node_fs_1 = require("node:fs");
9
+ const node_path_1 = require("node:path");
10
+ const sharp_1 = __importDefault(require("sharp"));
11
+ const IMAGE_BY_EXTENSION = {
12
+ '.png': { format: 'png', contentType: 'image/png' },
13
+ '.jpg': { format: 'jpeg', contentType: 'image/jpeg' },
14
+ '.jpeg': { format: 'jpeg', contentType: 'image/jpeg' },
15
+ '.webp': { format: 'webp', contentType: 'image/webp' },
16
+ '.gif': { format: 'gif', contentType: 'image/gif' },
17
+ '.svg': { format: 'svg', contentType: 'image/svg+xml' },
18
+ };
19
+ const SHARP_FORMAT_BY_DECLARED_FORMAT = {
20
+ PNG: 'png',
21
+ JPEG: 'jpeg',
22
+ WEBP: 'webp',
23
+ GIF: 'gif',
24
+ };
25
+ async function validateOwnedRuntimeImageFile(input) {
26
+ let stats;
27
+ try {
28
+ stats = (0, node_fs_1.statSync)(input.filePath);
29
+ }
30
+ catch {
31
+ throw new Error(`agent_task_owned_image_file_missing:${input.assetName}:${input.filePath}`);
32
+ }
33
+ if (!stats.isFile()) {
34
+ throw new Error(`agent_task_owned_image_file_missing:${input.assetName}:${input.filePath}`);
35
+ }
36
+ const extension = (0, node_path_1.extname)(input.filePath).toLowerCase();
37
+ const expected = IMAGE_BY_EXTENSION[extension];
38
+ if (!expected) {
39
+ throw new Error(`agent_task_owned_image_format_unsupported:${input.assetName}:${extension || 'missing_extension'}`);
40
+ }
41
+ let metadata;
42
+ try {
43
+ metadata = await (0, sharp_1.default)(input.filePath, { failOn: 'error' }).metadata();
44
+ }
45
+ catch {
46
+ throw new Error(`agent_task_owned_image_decode_failed:${input.assetName}:${input.filePath}`);
47
+ }
48
+ if (!Number.isInteger(metadata.width) || Number(metadata.width) <= 0
49
+ || !Number.isInteger(metadata.height) || Number(metadata.height) <= 0) {
50
+ throw new Error(`agent_task_owned_image_dimensions_invalid:${input.assetName}:${input.filePath}`);
51
+ }
52
+ if (metadata.format !== expected.format) {
53
+ throw new Error(`agent_task_owned_image_format_mismatch:${input.assetName}:extension=${extension}:contentType=${expected.contentType}:decoded=${metadata.format ?? 'unknown'}`);
54
+ }
55
+ const declaredFormat = input.declaredFormat?.trim().toUpperCase() ?? '';
56
+ const declaredSharpFormat = SHARP_FORMAT_BY_DECLARED_FORMAT[declaredFormat];
57
+ if (declaredSharpFormat && declaredSharpFormat !== metadata.format) {
58
+ throw new Error(`agent_task_owned_image_format_mismatch:${input.assetName}:declared=${declaredFormat}:decoded=${metadata.format}`);
59
+ }
60
+ }
61
+ async function assertOwnedRuntimeImagesAreValid(task) {
62
+ for (const ownedAsset of task.ownedAssets) {
63
+ if (!ownedAsset.runtimeKey || ownedAsset.category !== 'IMAGE') {
64
+ continue;
65
+ }
66
+ const primaryPath = ownedAsset.filePaths?.primary;
67
+ if (!primaryPath) {
68
+ throw new Error(`agent_task_owned_image_file_missing:${ownedAsset.name}:primary`);
69
+ }
70
+ await validateOwnedRuntimeImageFile({
71
+ assetName: ownedAsset.name,
72
+ filePath: primaryPath,
73
+ declaredFormat: ownedAsset.format,
74
+ });
75
+ }
76
+ }
@@ -6,6 +6,8 @@ export type UploadCommandOptions = {
6
6
  skipReview?: boolean;
7
7
  clearTags?: boolean;
8
8
  };
9
+ export declare function resolveUploadOwner(actorUsername: string | null | undefined, workspaceOwnerUsername: string | null | undefined): string;
10
+ export declare function resolveDefaultUploadCreator(owner: string | null | undefined, actorUsername: string | null | undefined): string;
9
11
  export declare function upload(pathOrName: string, options?: UploadCommandOptions): Promise<void>;
10
12
  export type WorkerAppPublishInput = {
11
13
  client: ApiClient;
@@ -34,7 +36,16 @@ export type WorkerAppPublishResult = {
34
36
  warnings: string[];
35
37
  };
36
38
  export type WorkerPlaydropAssetRequirement = 'PACK' | 'ASSET_OR_PACK';
39
+ export declare function resolveWorkerPlaydropAssetRequirement(value: unknown): WorkerPlaydropAssetRequirement | null;
37
40
  export declare function assertTaskPlaytestEvidenceManifest(task: AppTask, options?: {
38
41
  requireOutcomeProof?: boolean;
39
42
  }): AgentTaskPlaytestProof | null;
43
+ export declare function assertWorkerAppLocalUploadPreflight(input: {
44
+ task: AppTask;
45
+ kind: 'NEW_GAME' | 'REMIX_GAME' | 'GAME_UPDATE';
46
+ executionTarget?: AgentExecutionTarget;
47
+ creatorUsername?: string | null;
48
+ playdropAssetRequirement?: WorkerPlaydropAssetRequirement | null;
49
+ creatorRequest?: string | null;
50
+ }): Promise<AgentTaskPlaytestProof | null>;
40
51
  export declare function publishWorkerAppProject(input: WorkerAppPublishInput): Promise<WorkerAppPublishResult>;