@playdrop/playdrop-cli 0.10.20 → 0.11.0
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/config/client-meta.json +5 -1
- package/dist/appUrls.d.ts +3 -0
- package/dist/appUrls.js +12 -0
- package/dist/apps/build.js +3 -2
- package/dist/apps/launchCheck.d.ts +8 -1
- package/dist/apps/launchCheck.js +96 -10
- package/dist/apps/upload.js +5 -0
- package/dist/catalogue.d.ts +3 -1
- package/dist/catalogue.js +12 -0
- package/dist/commands/agents.js +30 -3
- package/dist/commands/clients.js +98 -1
- package/dist/commands/doctor.d.ts +8 -0
- package/dist/commands/doctor.js +50 -0
- package/dist/commands/generation.js +3 -0
- package/dist/commands/upload.js +346 -101
- package/dist/commands/worker/runtime.js +6 -0
- package/dist/commands/worker.d.ts +17 -1
- package/dist/commands/worker.js +155 -21
- package/dist/commands/workspaces.js +2 -1
- package/node_modules/@playdrop/config/client-meta.json +5 -1
- package/node_modules/@playdrop/config/dist/test/validateClientEnvironment.test.js +39 -0
- package/node_modules/@playdrop/config/dist/tsconfig.tsbuildinfo +1 -1
- package/node_modules/@playdrop/types/dist/api.d.ts +7 -2
- package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/api.js +2 -1
- package/node_modules/@playdrop/types/dist/version.d.ts +22 -0
- package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/version.js +108 -0
- package/package.json +5 -5
package/config/client-meta.json
CHANGED
package/dist/appUrls.d.ts
CHANGED
|
@@ -7,6 +7,9 @@ export type AppUrlInput = {
|
|
|
7
7
|
export declare function normalizePlaydropWebBase(webBase?: string | null): string;
|
|
8
8
|
export declare function getAppTypeSlug(type: AppType | string | null | undefined): string;
|
|
9
9
|
export declare function buildPlatformPlayUrl(webBase: string | null | undefined, input: AppUrlInput): string;
|
|
10
|
+
export declare function buildPlatformPrivateDraftPlayUrl(webBase: string | null | undefined, input: AppUrlInput & {
|
|
11
|
+
version: string;
|
|
12
|
+
}): string;
|
|
10
13
|
export declare function buildPlatformDevUrl(webBase: string | null | undefined, input: AppUrlInput & {
|
|
11
14
|
devAuth?: 'prompt' | 'anonymous' | 'viewer' | 'player';
|
|
12
15
|
player?: string | null;
|
package/dist/appUrls.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.normalizePlaydropWebBase = normalizePlaydropWebBase;
|
|
4
4
|
exports.getAppTypeSlug = getAppTypeSlug;
|
|
5
5
|
exports.buildPlatformPlayUrl = buildPlatformPlayUrl;
|
|
6
|
+
exports.buildPlatformPrivateDraftPlayUrl = buildPlatformPrivateDraftPlayUrl;
|
|
6
7
|
exports.buildPlatformDevUrl = buildPlatformDevUrl;
|
|
7
8
|
exports.buildUploadLaunchCheckUrl = buildUploadLaunchCheckUrl;
|
|
8
9
|
const DEFAULT_WEB_BASE = 'https://www.playdrop.ai';
|
|
@@ -33,6 +34,17 @@ function buildPlatformPlayUrl(webBase, input) {
|
|
|
33
34
|
const appName = encodeURIComponent(input.appName);
|
|
34
35
|
return `${base}/creators/${creator}/apps/${typeSlug}/${appName}/play`;
|
|
35
36
|
}
|
|
37
|
+
function buildPlatformPrivateDraftPlayUrl(webBase, input) {
|
|
38
|
+
const base = normalizePlaydropWebBase(webBase);
|
|
39
|
+
const creator = encodeURIComponent(input.creatorUsername);
|
|
40
|
+
const typeSlug = getAppTypeSlug(input.appType);
|
|
41
|
+
const appName = encodeURIComponent(input.appName);
|
|
42
|
+
const version = encodeURIComponent(input.version.trim());
|
|
43
|
+
if (!version) {
|
|
44
|
+
throw new Error('invalid_private_draft_play_version');
|
|
45
|
+
}
|
|
46
|
+
return `${base}/_auth/creators/${creator}/apps/${typeSlug}/${appName}/play?v=${version}`;
|
|
47
|
+
}
|
|
36
48
|
function buildPlatformDevUrl(webBase, input) {
|
|
37
49
|
const base = normalizePlaydropWebBase(webBase);
|
|
38
50
|
const creator = encodeURIComponent(input.creatorUsername);
|
package/dist/apps/build.js
CHANGED
|
@@ -33,9 +33,10 @@ const projectBuildCache = new Map();
|
|
|
33
33
|
const typescriptSourceCache = new Map();
|
|
34
34
|
async function runNpmScript(projectDir, script) {
|
|
35
35
|
await (0, promises_1.mkdir)(projectDir, { recursive: true });
|
|
36
|
-
const npmCommand = process.platform === 'win32' ? '
|
|
36
|
+
const npmCommand = process.platform === 'win32' ? process.env.ComSpec || 'cmd.exe' : 'npm';
|
|
37
|
+
const npmArgs = process.platform === 'win32' ? ['/d', '/s', '/c', `npm run ${script}`] : ['run', script];
|
|
37
38
|
await new Promise((resolveRun, rejectRun) => {
|
|
38
|
-
const child = (0, node_child_process_1.spawn)(npmCommand,
|
|
39
|
+
const child = (0, node_child_process_1.spawn)(npmCommand, npmArgs, {
|
|
39
40
|
cwd: projectDir,
|
|
40
41
|
stdio: 'inherit',
|
|
41
42
|
env: { ...process.env },
|
|
@@ -8,7 +8,7 @@ export type HostedLaunchCheckResult = {
|
|
|
8
8
|
checkedAt: string;
|
|
9
9
|
artifactFingerprint: string | null;
|
|
10
10
|
};
|
|
11
|
-
export declare function formatHostedLaunchCheckFailure(taskName: string, result: HostedLaunchCheckResult, scope?: 'local' | 'staged'): string;
|
|
11
|
+
export declare function formatHostedLaunchCheckFailure(taskName: string, result: HostedLaunchCheckResult, scope?: 'local' | 'staged' | 'final'): string;
|
|
12
12
|
export declare function runLocalHostedLaunchCheck(input: {
|
|
13
13
|
client: ApiClient;
|
|
14
14
|
apiBase: string;
|
|
@@ -30,3 +30,10 @@ export declare function runUploadedHostedLaunchCheck(input: {
|
|
|
30
30
|
token?: string | null;
|
|
31
31
|
currentUser?: UserResponse | null;
|
|
32
32
|
}): Promise<HostedLaunchCheckResult>;
|
|
33
|
+
export declare function runPublishedHostedPlayCheck(input: {
|
|
34
|
+
targetUrl: string;
|
|
35
|
+
surfaceTargets?: unknown;
|
|
36
|
+
timeoutMs?: number;
|
|
37
|
+
token?: string | null;
|
|
38
|
+
currentUser?: UserResponse | null;
|
|
39
|
+
}): Promise<HostedLaunchCheckResult>;
|
package/dist/apps/launchCheck.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.formatHostedLaunchCheckFailure = formatHostedLaunchCheckFailure;
|
|
4
4
|
exports.runLocalHostedLaunchCheck = runLocalHostedLaunchCheck;
|
|
5
5
|
exports.runUploadedHostedLaunchCheck = runUploadedHostedLaunchCheck;
|
|
6
|
+
exports.runPublishedHostedPlayCheck = runPublishedHostedPlayCheck;
|
|
6
7
|
const appUrls_1 = require("../appUrls");
|
|
7
8
|
const captureRuntime_1 = require("../captureRuntime");
|
|
8
9
|
const devShared_1 = require("../commands/devShared");
|
|
@@ -13,13 +14,67 @@ const devRuntimeAssets_1 = require("../commands/devRuntimeAssets");
|
|
|
13
14
|
const registration_1 = require("./registration");
|
|
14
15
|
const DEFAULT_HOSTED_LAUNCH_TIMEOUT_MS = 15000;
|
|
15
16
|
const POST_READY_SETTLE_MS = 750;
|
|
16
|
-
const
|
|
17
|
+
const MOBILE_USER_AGENT = 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1';
|
|
18
|
+
const LAUNCH_CHECK_SURFACE_ORDER = ['DESKTOP', 'MOBILE_LANDSCAPE', 'MOBILE_PORTRAIT'];
|
|
19
|
+
const LAUNCH_CHECK_SURFACE_CONTEXT_OPTIONS = {
|
|
20
|
+
DESKTOP: {
|
|
21
|
+
viewport: { width: 1280, height: 720 },
|
|
22
|
+
deviceScaleFactor: 1,
|
|
23
|
+
isMobile: false,
|
|
24
|
+
hasTouch: false,
|
|
25
|
+
},
|
|
26
|
+
MOBILE_LANDSCAPE: {
|
|
27
|
+
viewport: { width: 896, height: 414 },
|
|
28
|
+
deviceScaleFactor: 2,
|
|
29
|
+
isMobile: true,
|
|
30
|
+
hasTouch: true,
|
|
31
|
+
userAgent: MOBILE_USER_AGENT,
|
|
32
|
+
},
|
|
33
|
+
MOBILE_PORTRAIT: {
|
|
34
|
+
viewport: { width: 414, height: 896 },
|
|
35
|
+
deviceScaleFactor: 2,
|
|
36
|
+
isMobile: true,
|
|
37
|
+
hasTouch: true,
|
|
38
|
+
userAgent: MOBILE_USER_AGENT,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
function normalizeLaunchCheckSurface(value) {
|
|
42
|
+
if (typeof value !== 'string') {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
const normalized = value.trim().toUpperCase();
|
|
46
|
+
return LAUNCH_CHECK_SURFACE_ORDER.includes(normalized)
|
|
47
|
+
? normalized
|
|
48
|
+
: null;
|
|
49
|
+
}
|
|
50
|
+
function normalizeLaunchCheckSurfaceTargets(value) {
|
|
51
|
+
if (!Array.isArray(value)) {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
return Array.from(new Set(value.map(normalizeLaunchCheckSurface).filter((entry) => Boolean(entry))));
|
|
55
|
+
}
|
|
56
|
+
function resolveLaunchCheckSurface(surfaceTargets) {
|
|
57
|
+
const targets = normalizeLaunchCheckSurfaceTargets(surfaceTargets);
|
|
58
|
+
for (const surface of LAUNCH_CHECK_SURFACE_ORDER) {
|
|
59
|
+
if (targets.includes(surface)) {
|
|
60
|
+
return surface;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return 'DESKTOP';
|
|
64
|
+
}
|
|
65
|
+
function cloneLaunchCheckContextOptions(surface) {
|
|
66
|
+
const source = LAUNCH_CHECK_SURFACE_CONTEXT_OPTIONS[surface];
|
|
67
|
+
return {
|
|
68
|
+
...source,
|
|
69
|
+
viewport: source.viewport ? { ...source.viewport } : source.viewport,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
17
72
|
function resolvePostAuthHostedLaunchState(controllerMode) {
|
|
18
73
|
return controllerMode === 'REQUIRED' ? 'controller_required' : 'ready';
|
|
19
74
|
}
|
|
20
75
|
function resolveAnonymousHostedLaunchState(input) {
|
|
21
|
-
const surfaceTargets =
|
|
22
|
-
if (!surfaceTargets.includes(
|
|
76
|
+
const surfaceTargets = normalizeLaunchCheckSurfaceTargets(input.surfaceTargets);
|
|
77
|
+
if (!surfaceTargets.includes(input.captureSurface)) {
|
|
23
78
|
return 'surface_unsupported';
|
|
24
79
|
}
|
|
25
80
|
if (input.authMode === 'REQUIRED') {
|
|
@@ -64,7 +119,11 @@ function formatCaptureErrors(errors) {
|
|
|
64
119
|
return ` First error${normalized.length === 1 ? '' : 's'}: ${normalized.join(' | ')}`;
|
|
65
120
|
}
|
|
66
121
|
function formatHostedLaunchCheckFailure(taskName, result, scope = 'local') {
|
|
67
|
-
const scopeLabel = scope === '
|
|
122
|
+
const scopeLabel = scope === 'final'
|
|
123
|
+
? 'final owner play route'
|
|
124
|
+
: scope === 'staged'
|
|
125
|
+
? 'staged upload preview'
|
|
126
|
+
: 'local hosted preview';
|
|
68
127
|
const errorCode = result.errorCode?.trim() || 'startup_failure';
|
|
69
128
|
const message = normalizeLaunchCheckMessage(result.message);
|
|
70
129
|
return `hosted_launch_check_failed: ${taskName} failed the ${scopeLabel} launch-check (${errorCode}). ${message}`;
|
|
@@ -102,13 +161,10 @@ function buildRegistrationRequiredLaunchCheckFailure(taskName) {
|
|
|
102
161
|
return buildFailedLaunchCheckResult('app_registration_required_for_auth_validation', `Auth-required hosted app "${taskName}" must be registered on PlayDrop before viewer launch validation can run. Run "playdrop project create app ${taskName}" and try again.`, null);
|
|
103
162
|
}
|
|
104
163
|
function appendLaunchCheckParams(targetUrl, devAuth) {
|
|
105
|
-
|
|
164
|
+
return (0, devAuth_1.applyHostedDevAuthSelectionToUrl)(targetUrl, {
|
|
106
165
|
devAuth,
|
|
107
166
|
player: null,
|
|
108
167
|
});
|
|
109
|
-
const nextUrl = new URL(withAuth);
|
|
110
|
-
nextUrl.searchParams.set('launchCheck', '1');
|
|
111
|
-
return nextUrl.toString();
|
|
112
168
|
}
|
|
113
169
|
async function runHostedLaunchCheckWithCapture(options) {
|
|
114
170
|
const expectedHostedLaunchState = options.expectedHostedLaunchState ?? 'ready';
|
|
@@ -125,6 +181,7 @@ async function runHostedLaunchCheckWithCapture(options) {
|
|
|
125
181
|
token: options.token ?? null,
|
|
126
182
|
user: options.user ?? null,
|
|
127
183
|
savedSessionBootstrap: options.savedSessionBootstrap ?? false,
|
|
184
|
+
contextOptions: options.contextOptions,
|
|
128
185
|
});
|
|
129
186
|
if (result.errorCount > 0) {
|
|
130
187
|
return buildFailedLaunchCheckResult('startup_failure', `Hosted app emitted ${result.errorCount} console, page, or network error(s) during startup.${formatCaptureErrors(result.errors)}`, options.artifactFingerprint ?? null);
|
|
@@ -210,7 +267,12 @@ async function runLocalHostedLaunchCheck(input) {
|
|
|
210
267
|
? null
|
|
211
268
|
: input.devRouterPort;
|
|
212
269
|
const localAppMetadata = mountedRuntime.runtimeAssetManifest.response.localAppMetadata;
|
|
213
|
-
const
|
|
270
|
+
const captureSurface = resolveLaunchCheckSurface(localAppMetadata.surfaceTargets);
|
|
271
|
+
const contextOptions = cloneLaunchCheckContextOptions(captureSurface);
|
|
272
|
+
const anonymousExpectedState = resolveAnonymousHostedLaunchState({
|
|
273
|
+
...localAppMetadata,
|
|
274
|
+
captureSurface,
|
|
275
|
+
});
|
|
214
276
|
if (anonymousExpectedState === 'login_required') {
|
|
215
277
|
const registeredApp = await (0, registration_1.fetchRegisteredAppShell)(input.client, input.creatorUsername, input.task.name);
|
|
216
278
|
const canLaunchUnregisteredViewer = input.allowUnregisteredViewerLaunch === true
|
|
@@ -230,6 +292,7 @@ async function runLocalHostedLaunchCheck(input) {
|
|
|
230
292
|
}),
|
|
231
293
|
timeoutMs: input.timeoutMs,
|
|
232
294
|
expectedHostedLaunchState: anonymousExpectedState,
|
|
295
|
+
contextOptions,
|
|
233
296
|
});
|
|
234
297
|
if (anonymousGateResult.status !== 'PASSED') {
|
|
235
298
|
return anonymousGateResult;
|
|
@@ -248,6 +311,7 @@ async function runLocalHostedLaunchCheck(input) {
|
|
|
248
311
|
token: input.token ?? null,
|
|
249
312
|
user: input.currentUser ?? null,
|
|
250
313
|
savedSessionBootstrap: true,
|
|
314
|
+
contextOptions,
|
|
251
315
|
});
|
|
252
316
|
}
|
|
253
317
|
return await runHostedLaunchCheckWithCapture({
|
|
@@ -261,6 +325,7 @@ async function runLocalHostedLaunchCheck(input) {
|
|
|
261
325
|
}),
|
|
262
326
|
timeoutMs: input.timeoutMs,
|
|
263
327
|
expectedHostedLaunchState: anonymousExpectedState,
|
|
328
|
+
contextOptions,
|
|
264
329
|
});
|
|
265
330
|
}
|
|
266
331
|
finally {
|
|
@@ -271,7 +336,12 @@ async function runLocalHostedLaunchCheck(input) {
|
|
|
271
336
|
}
|
|
272
337
|
async function runUploadedHostedLaunchCheck(input) {
|
|
273
338
|
const preview = await input.client.fetchAppUploadLaunchCheckPreview(input.creatorUsername, input.task.name, input.sessionId);
|
|
274
|
-
const
|
|
339
|
+
const captureSurface = resolveLaunchCheckSurface(preview.localAppMetadata.surfaceTargets);
|
|
340
|
+
const contextOptions = cloneLaunchCheckContextOptions(captureSurface);
|
|
341
|
+
const anonymousExpectedState = resolveAnonymousHostedLaunchState({
|
|
342
|
+
...preview.localAppMetadata,
|
|
343
|
+
captureSurface,
|
|
344
|
+
});
|
|
275
345
|
let result;
|
|
276
346
|
if (anonymousExpectedState === 'login_required') {
|
|
277
347
|
if (!preview.app?.id) {
|
|
@@ -283,6 +353,7 @@ async function runUploadedHostedLaunchCheck(input) {
|
|
|
283
353
|
artifactFingerprint: preview.session.manifestHash,
|
|
284
354
|
timeoutMs: input.timeoutMs,
|
|
285
355
|
expectedHostedLaunchState: anonymousExpectedState,
|
|
356
|
+
contextOptions,
|
|
286
357
|
});
|
|
287
358
|
if (anonymousGateResult.status !== 'PASSED') {
|
|
288
359
|
result = anonymousGateResult;
|
|
@@ -296,6 +367,7 @@ async function runUploadedHostedLaunchCheck(input) {
|
|
|
296
367
|
token: input.token ?? null,
|
|
297
368
|
user: input.currentUser ?? null,
|
|
298
369
|
savedSessionBootstrap: true,
|
|
370
|
+
contextOptions,
|
|
299
371
|
});
|
|
300
372
|
}
|
|
301
373
|
}
|
|
@@ -306,6 +378,7 @@ async function runUploadedHostedLaunchCheck(input) {
|
|
|
306
378
|
artifactFingerprint: preview.session.manifestHash,
|
|
307
379
|
timeoutMs: input.timeoutMs,
|
|
308
380
|
expectedHostedLaunchState: anonymousExpectedState,
|
|
381
|
+
contextOptions,
|
|
309
382
|
});
|
|
310
383
|
}
|
|
311
384
|
const recorded = await input.client.recordAppUploadLaunchCheck(input.creatorUsername, input.task.name, input.sessionId, {
|
|
@@ -326,3 +399,16 @@ async function runUploadedHostedLaunchCheck(input) {
|
|
|
326
399
|
artifactFingerprint: recordedLaunchCheck.artifactFingerprint,
|
|
327
400
|
};
|
|
328
401
|
}
|
|
402
|
+
async function runPublishedHostedPlayCheck(input) {
|
|
403
|
+
const captureSurface = resolveLaunchCheckSurface(input.surfaceTargets);
|
|
404
|
+
return runHostedLaunchCheckWithCapture({
|
|
405
|
+
targetUrl: input.targetUrl,
|
|
406
|
+
artifactFingerprint: null,
|
|
407
|
+
timeoutMs: input.timeoutMs,
|
|
408
|
+
expectedHostedLaunchState: 'ready',
|
|
409
|
+
token: input.token ?? null,
|
|
410
|
+
user: input.currentUser ?? null,
|
|
411
|
+
savedSessionBootstrap: true,
|
|
412
|
+
contextOptions: cloneLaunchCheckContextOptions(captureSurface),
|
|
413
|
+
});
|
|
414
|
+
}
|
package/dist/apps/upload.js
CHANGED
|
@@ -253,6 +253,7 @@ async function uploadAppVersion(client, task, artifacts, options) {
|
|
|
253
253
|
})),
|
|
254
254
|
usesPacks: task.uses.packs,
|
|
255
255
|
tags: task.tags,
|
|
256
|
+
design: task.design,
|
|
256
257
|
clearTags: options?.clearTags,
|
|
257
258
|
surfaceTargets: task.surfaceTargets,
|
|
258
259
|
assetSpecSupport: (task.assetSpecSupport ?? []).map((row) => ({
|
|
@@ -409,6 +410,10 @@ async function uploadAppVersion(client, task, artifacts, options) {
|
|
|
409
410
|
+ `Delete one old version: playdrop creations apps versions delete ${task.name} <version>`);
|
|
410
411
|
}
|
|
411
412
|
if (unknownError.status === 409 && unknownError.code === 'version_exists') {
|
|
413
|
+
if (options?.agentTaskId !== undefined) {
|
|
414
|
+
throw new Error(`Version ${task.version} already exists for app "${task.name}".\n` +
|
|
415
|
+
'This task has a fixed outputVersion; do not bump catalogue.json. The required task version has already been consumed by an upload attempt, so fail the task with the operational reason.');
|
|
416
|
+
}
|
|
412
417
|
throw new Error(`Version ${task.version} already exists for app "${task.name}".\n` +
|
|
413
418
|
`Hint: Update "version" in catalogue.json to a new version (e.g., bump to the next patch).`);
|
|
414
419
|
}
|
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, type ContentLicense } from '@playdrop/types';
|
|
1
|
+
import { type AppSurface, type AppType, type AppHostingMode, type AppAuthMode, type AppControllerMode, type AppVersionVisibility, type AppVersionDesign, 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>>;
|
|
@@ -41,6 +41,7 @@ export type AppCatalogueEntry = {
|
|
|
41
41
|
remixSource?: string | null;
|
|
42
42
|
remix?: string;
|
|
43
43
|
surfaceTargets?: Record<string, unknown> | AppSurface[] | null;
|
|
44
|
+
design?: unknown;
|
|
44
45
|
version?: string;
|
|
45
46
|
releaseNotes?: string;
|
|
46
47
|
visibility?: string;
|
|
@@ -177,6 +178,7 @@ export type AppTask = {
|
|
|
177
178
|
emoji?: string | null;
|
|
178
179
|
color?: string | null;
|
|
179
180
|
surfaceTargets: AppSurface[];
|
|
181
|
+
design?: AppVersionDesign;
|
|
180
182
|
missingMetadata: string[];
|
|
181
183
|
ecsPath?: string;
|
|
182
184
|
serverPath?: string;
|
package/dist/catalogue.js
CHANGED
|
@@ -1300,6 +1300,17 @@ function buildAppTasks(rootDir, catalogues, options) {
|
|
|
1300
1300
|
if (errors.length > errorCountBeforeDependencies) {
|
|
1301
1301
|
continue;
|
|
1302
1302
|
}
|
|
1303
|
+
let design;
|
|
1304
|
+
if (Object.prototype.hasOwnProperty.call(entry, 'design')) {
|
|
1305
|
+
const parsedDesign = (0, types_1.normalizeAppVersionDesign)(entry.design, {
|
|
1306
|
+
allowedPackRefs: usesPacks,
|
|
1307
|
+
});
|
|
1308
|
+
if (!parsedDesign.ok) {
|
|
1309
|
+
errors.push(`[${label}] App "${rawName}" ${parsedDesign.error}`);
|
|
1310
|
+
continue;
|
|
1311
|
+
}
|
|
1312
|
+
design = parsedDesign.value;
|
|
1313
|
+
}
|
|
1303
1314
|
const errorCountBeforeTagMetadata = errors.length;
|
|
1304
1315
|
const remix = normalizeCatalogueRemixRef(entry.remix, 'app', errors, `[${label}] App "${rawName}"`);
|
|
1305
1316
|
const tags = normalizeCatalogueTags(entry.tags, errors, `[${label}] App "${rawName}"`);
|
|
@@ -1500,6 +1511,7 @@ function buildAppTasks(rootDir, catalogues, options) {
|
|
|
1500
1511
|
emoji: metadata.emoji === undefined ? undefined : metadata.emoji,
|
|
1501
1512
|
color: metadata.color === undefined ? undefined : metadata.color,
|
|
1502
1513
|
surfaceTargets: metadata.surfaceTargets,
|
|
1514
|
+
design,
|
|
1503
1515
|
missingMetadata: metadata.missingFields,
|
|
1504
1516
|
ecsPath,
|
|
1505
1517
|
serverPath,
|
package/dist/commands/agents.js
CHANGED
|
@@ -88,11 +88,16 @@ async function detectCli(command, runShell) {
|
|
|
88
88
|
if (pathProbe.exitCode !== 0 || pathProbe.output.trim().length === 0) {
|
|
89
89
|
return { installed: false };
|
|
90
90
|
}
|
|
91
|
+
const path = pathProbe.output.split('\n')[0]?.trim();
|
|
91
92
|
const versionProbe = await runShell(`${command} --version`);
|
|
93
|
+
const version = versionProbe.output.split('\n')[0]?.trim() || undefined;
|
|
94
|
+
if (versionProbe.exitCode !== 0 && !/\d+\.\d+\.\d+/.test(versionProbe.output)) {
|
|
95
|
+
return { installed: false, path };
|
|
96
|
+
}
|
|
92
97
|
return {
|
|
93
98
|
installed: true,
|
|
94
|
-
path
|
|
95
|
-
version
|
|
99
|
+
path,
|
|
100
|
+
version,
|
|
96
101
|
};
|
|
97
102
|
}
|
|
98
103
|
function detectApp(appNames, homeDir, appExists) {
|
|
@@ -285,6 +290,9 @@ function printAgentsStatus(status) {
|
|
|
285
290
|
}
|
|
286
291
|
}
|
|
287
292
|
function runCodexPluginAction(action, runCommand) {
|
|
293
|
+
if (!ensureAgentCliUsable('codex', runCommand)) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
288
296
|
const marketplaceCommand = action === 'install-plugin'
|
|
289
297
|
? ['plugin', 'marketplace', 'add', PLAYDROP_PLUGIN_MARKETPLACE]
|
|
290
298
|
: ['plugin', 'marketplace', 'upgrade', 'playdrop'];
|
|
@@ -340,6 +348,9 @@ function runCodexPluginAction(action, runCommand) {
|
|
|
340
348
|
console.log('Open Codex and run /plugins, then install or enable PlayDrop.');
|
|
341
349
|
}
|
|
342
350
|
function runClaudePluginAction(action, runCommand) {
|
|
351
|
+
if (!ensureAgentCliUsable('claude', runCommand)) {
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
343
354
|
if (action === 'install-plugin') {
|
|
344
355
|
const marketplace = runCommand('claude', ['plugin', 'marketplace', 'add', PLAYDROP_PLUGIN_MARKETPLACE]);
|
|
345
356
|
if (marketplace.status !== 0) {
|
|
@@ -379,6 +390,21 @@ function runClaudePluginAction(action, runCommand) {
|
|
|
379
390
|
console.log('Updated the PlayDrop plugin for Claude.');
|
|
380
391
|
console.log('Run /reload-plugins in Claude Code or restart Claude Code to apply it.');
|
|
381
392
|
}
|
|
393
|
+
function ensureAgentCliUsable(command, runCommand) {
|
|
394
|
+
const result = runCommand(command, ['--version']);
|
|
395
|
+
const output = commandOutput(result).trim();
|
|
396
|
+
if (result.status === 0 || /\d+\.\d+\.\d+/.test(output)) {
|
|
397
|
+
return true;
|
|
398
|
+
}
|
|
399
|
+
const label = command === 'codex' ? 'Codex' : 'Claude';
|
|
400
|
+
console.error(`${label} CLI is missing or not runnable.`);
|
|
401
|
+
console.error(`${command} --version failed with code ${result.status ?? 'unknown'}.`);
|
|
402
|
+
if (output) {
|
|
403
|
+
console.error(`Output: ${output}`);
|
|
404
|
+
}
|
|
405
|
+
process.exitCode = 1;
|
|
406
|
+
return false;
|
|
407
|
+
}
|
|
382
408
|
function readInstalledPlayDropPlugin(command, runCommand) {
|
|
383
409
|
const list = runCommand(command, ['plugin', 'list', '--json']);
|
|
384
410
|
if (list.status !== 0) {
|
|
@@ -477,9 +503,10 @@ async function runLoginShell(command) {
|
|
|
477
503
|
}
|
|
478
504
|
function runCommandSync(command, args) {
|
|
479
505
|
const result = (0, node_child_process_1.spawnSync)(command, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
506
|
+
const spawnError = result.error instanceof Error ? result.error.message : '';
|
|
480
507
|
return {
|
|
481
508
|
status: result.status,
|
|
482
509
|
stdout: result.stdout ?? '',
|
|
483
|
-
stderr: result.stderr ?? '',
|
|
510
|
+
stderr: [result.stderr ?? '', spawnError].filter(Boolean).join('\n'),
|
|
484
511
|
};
|
|
485
512
|
}
|
package/dist/commands/clients.js
CHANGED
|
@@ -17,6 +17,9 @@ const output_1 = require("../output");
|
|
|
17
17
|
const versionCompare_1 = require("../versionCompare");
|
|
18
18
|
const MAC_APP_PATH = '/Applications/PlayDrop.app';
|
|
19
19
|
const MAC_CLIENT_LATEST_URL = 'https://www.playdrop.ai/api/mac-client/latest';
|
|
20
|
+
const WINDOWS_CLIENT_EXECUTABLE = 'PlayDrop.exe';
|
|
21
|
+
const WINDOWS_CLIENT_INSTALL_HINT = 'Install or update PlayDrop for Windows with the Windows installer or the in-app updater.';
|
|
22
|
+
const WINDOWS_CLIENT_PATH_ENV = 'PLAYDROP_WINDOWS_CLIENT_PATH';
|
|
20
23
|
async function showClientStatus(options = {}) {
|
|
21
24
|
const status = await getClientStatus(options);
|
|
22
25
|
if (options.json) {
|
|
@@ -29,6 +32,9 @@ async function showClientStatus(options = {}) {
|
|
|
29
32
|
}
|
|
30
33
|
async function getClientStatus(options = {}) {
|
|
31
34
|
const platform = options.platform ?? process.platform;
|
|
35
|
+
if (platform === 'win32') {
|
|
36
|
+
return getWindowsClientStatus(options);
|
|
37
|
+
}
|
|
32
38
|
if (platform !== 'darwin') {
|
|
33
39
|
return {
|
|
34
40
|
platform,
|
|
@@ -65,6 +71,24 @@ async function updateClient(options = {}) {
|
|
|
65
71
|
}
|
|
66
72
|
async function launchClient(options = {}) {
|
|
67
73
|
const platform = options.platform ?? process.platform;
|
|
74
|
+
if (platform === 'win32') {
|
|
75
|
+
const status = await getWindowsClientStatus(options);
|
|
76
|
+
if (!status.installed || !status.appPath) {
|
|
77
|
+
console.error('PlayDrop for Windows is not installed.');
|
|
78
|
+
console.error(WINDOWS_CLIENT_INSTALL_HINT);
|
|
79
|
+
process.exitCode = 1;
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const runCommand = options.runCommand ?? runCommandSync;
|
|
83
|
+
const result = runCommand(status.appPath, []);
|
|
84
|
+
if (result.status !== 0) {
|
|
85
|
+
console.error(`Failed to launch PlayDrop from ${status.appPath}.`);
|
|
86
|
+
process.exitCode = 1;
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
console.log('Launched PlayDrop.');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
68
92
|
if (platform !== 'darwin') {
|
|
69
93
|
printUnsupportedPlatform(platform);
|
|
70
94
|
return;
|
|
@@ -87,6 +111,12 @@ async function launchClient(options = {}) {
|
|
|
87
111
|
}
|
|
88
112
|
async function installOrUpdateClient(action, options) {
|
|
89
113
|
const platform = options.platform ?? process.platform;
|
|
114
|
+
if (platform === 'win32') {
|
|
115
|
+
console.error(`PlayDrop for Windows ${action} is not available through the CLI yet.`);
|
|
116
|
+
console.error(WINDOWS_CLIENT_INSTALL_HINT);
|
|
117
|
+
process.exitCode = 1;
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
90
120
|
if (platform !== 'darwin') {
|
|
91
121
|
printUnsupportedPlatform(platform);
|
|
92
122
|
return;
|
|
@@ -129,7 +159,12 @@ function printClientStatus(status) {
|
|
|
129
159
|
}
|
|
130
160
|
if (!status.installed) {
|
|
131
161
|
console.log('PlayDrop client: missing');
|
|
132
|
-
|
|
162
|
+
if (status.nextAction) {
|
|
163
|
+
console.log(`Run: ${status.nextAction}`);
|
|
164
|
+
}
|
|
165
|
+
else if (status.reason) {
|
|
166
|
+
console.log(status.reason);
|
|
167
|
+
}
|
|
133
168
|
return;
|
|
134
169
|
}
|
|
135
170
|
const version = status.version ? ` ${status.version}` : '';
|
|
@@ -141,6 +176,68 @@ function printClientStatus(status) {
|
|
|
141
176
|
console.log('Run: playdrop clients update');
|
|
142
177
|
}
|
|
143
178
|
}
|
|
179
|
+
async function getWindowsClientStatus(options) {
|
|
180
|
+
const appPath = resolveWindowsClientPath(options.appPath);
|
|
181
|
+
if (!appPath) {
|
|
182
|
+
return {
|
|
183
|
+
platform: 'win32',
|
|
184
|
+
supported: true,
|
|
185
|
+
status: 'missing',
|
|
186
|
+
installed: false,
|
|
187
|
+
reason: WINDOWS_CLIENT_INSTALL_HINT,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
const bundle = readWindowsClientInfo(appPath);
|
|
191
|
+
return {
|
|
192
|
+
platform: 'win32',
|
|
193
|
+
supported: true,
|
|
194
|
+
status: 'installed',
|
|
195
|
+
installed: true,
|
|
196
|
+
appPath,
|
|
197
|
+
version: bundle.version,
|
|
198
|
+
build: bundle.build,
|
|
199
|
+
updateAvailable: false,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function resolveWindowsClientPath(explicitPath) {
|
|
203
|
+
const candidates = windowsClientPathCandidates(explicitPath);
|
|
204
|
+
return candidates.find((candidate) => (0, node_fs_1.existsSync)(candidate));
|
|
205
|
+
}
|
|
206
|
+
function windowsClientPathCandidates(explicitPath) {
|
|
207
|
+
const candidates = [];
|
|
208
|
+
const add = (value) => {
|
|
209
|
+
if (value && value.trim().length > 0) {
|
|
210
|
+
candidates.push(value.trim());
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
add(explicitPath);
|
|
214
|
+
add(process.env[WINDOWS_CLIENT_PATH_ENV]);
|
|
215
|
+
const localAppData = process.env.LOCALAPPDATA;
|
|
216
|
+
if (localAppData) {
|
|
217
|
+
add((0, node_path_1.join)(localAppData, 'PlayDrop.Windows', 'current', WINDOWS_CLIENT_EXECUTABLE));
|
|
218
|
+
add((0, node_path_1.join)(localAppData, 'PlayDrop', 'current', WINDOWS_CLIENT_EXECUTABLE));
|
|
219
|
+
}
|
|
220
|
+
add(process.env.ProgramFiles ? (0, node_path_1.join)(process.env.ProgramFiles, 'PlayDrop', WINDOWS_CLIENT_EXECUTABLE) : undefined);
|
|
221
|
+
add(process.env['ProgramFiles(x86)'] ? (0, node_path_1.join)(process.env['ProgramFiles(x86)'], 'PlayDrop', WINDOWS_CLIENT_EXECUTABLE) : undefined);
|
|
222
|
+
return [...new Set(candidates)];
|
|
223
|
+
}
|
|
224
|
+
function readWindowsClientInfo(appPath) {
|
|
225
|
+
for (const versionPath of [
|
|
226
|
+
(0, node_path_1.join)((0, node_path_1.dirname)(appPath), 'sq.version'),
|
|
227
|
+
(0, node_path_1.join)((0, node_path_1.dirname)((0, node_path_1.dirname)(appPath)), 'sq.version'),
|
|
228
|
+
]) {
|
|
229
|
+
try {
|
|
230
|
+
const version = (0, node_fs_1.readFileSync)(versionPath, 'utf8').trim();
|
|
231
|
+
if (version.length > 0) {
|
|
232
|
+
return { version };
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
// Development and portable builds may not have a Velopack version file.
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return {};
|
|
240
|
+
}
|
|
144
241
|
async function fetchLatestMacClientManifest() {
|
|
145
242
|
try {
|
|
146
243
|
const response = await fetch(MAC_CLIENT_LATEST_URL, { headers: { Accept: 'application/json' } });
|
|
@@ -22,6 +22,14 @@ interface CliHealth {
|
|
|
22
22
|
updateStatus: 'current' | 'update-available' | 'unknown';
|
|
23
23
|
updateAvailable?: boolean;
|
|
24
24
|
nextAction?: string;
|
|
25
|
+
command?: CliCommandHealth;
|
|
26
|
+
}
|
|
27
|
+
interface CliCommandHealth {
|
|
28
|
+
available: boolean;
|
|
29
|
+
path?: string;
|
|
30
|
+
npmGlobalBin?: string;
|
|
31
|
+
npmShimPath?: string;
|
|
32
|
+
issue?: 'not-on-path' | 'shim-missing';
|
|
25
33
|
}
|
|
26
34
|
export interface DoctorReport {
|
|
27
35
|
cli: CliHealth;
|