@playdrop/playdrop-cli 0.7.0 → 0.7.2
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 +1 -0
- package/config/client-meta.json +9 -9
- package/dist/commands/devBrowser.d.ts +7 -0
- package/dist/commands/devBrowser.js +360 -0
- package/dist/commands/devServer.js +28 -1
- package/dist/commands/generation.d.ts +3 -0
- package/dist/commands/generation.js +84 -4
- package/dist/commands/versionsBrowse.js +63 -4
- package/dist/environment.d.ts +1 -1
- package/dist/environment.js +24 -1
- package/dist/index.js +32 -2
- package/dist/playwright.d.ts +7 -0
- package/dist/playwright.js +21 -2
- package/node_modules/@playdrop/ai-client/dist/index.d.ts +41 -5
- package/node_modules/@playdrop/ai-client/dist/index.d.ts.map +1 -1
- package/node_modules/@playdrop/ai-client/dist/index.js +11 -1
- package/node_modules/@playdrop/ai-client/package.json +1 -1
- package/node_modules/@playdrop/api-client/dist/client.d.ts +10 -2
- package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/client.js +74 -0
- package/node_modules/@playdrop/api-client/dist/core/request.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/core/request.js +64 -12
- package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts +1 -2
- package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/admin.js +0 -11
- package/node_modules/@playdrop/api-client/dist/domains/comments.d.ts +4 -1
- package/node_modules/@playdrop/api-client/dist/domains/comments.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/comments.js +34 -0
- package/node_modules/@playdrop/api-client/dist/index.d.ts +11 -4
- package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/index.js +27 -3
- package/node_modules/@playdrop/api-client/package.json +1 -1
- package/node_modules/@playdrop/boxel-core/package.json +3 -4
- package/node_modules/@playdrop/boxel-three/package.json +1 -1
- package/node_modules/@playdrop/config/client-meta.json +9 -9
- package/node_modules/@playdrop/config/dist/src/deployment.d.ts +27 -0
- package/node_modules/@playdrop/config/dist/src/deployment.d.ts.map +1 -0
- package/node_modules/@playdrop/config/dist/src/deployment.js +199 -0
- package/node_modules/@playdrop/config/dist/src/index.d.ts +2 -0
- package/node_modules/@playdrop/config/dist/src/index.d.ts.map +1 -1
- package/node_modules/@playdrop/config/dist/src/index.js +2 -0
- package/node_modules/@playdrop/config/dist/src/public-deployment.d.ts +26 -0
- package/node_modules/@playdrop/config/dist/src/public-deployment.d.ts.map +1 -0
- package/node_modules/@playdrop/config/dist/src/public-deployment.js +148 -0
- package/node_modules/@playdrop/config/dist/src/server/fastify.d.ts +60 -1
- package/node_modules/@playdrop/config/dist/src/server/fastify.d.ts.map +1 -1
- package/node_modules/@playdrop/config/dist/src/server/fastify.js +119 -0
- package/node_modules/@playdrop/config/dist/src/server/logging.d.ts +3 -0
- package/node_modules/@playdrop/config/dist/src/server/logging.d.ts.map +1 -0
- package/node_modules/@playdrop/config/dist/src/server/logging.js +46 -0
- package/node_modules/@playdrop/config/dist/test/deployment.test.d.ts +2 -0
- package/node_modules/@playdrop/config/dist/test/deployment.test.d.ts.map +1 -0
- package/node_modules/@playdrop/config/dist/test/deployment.test.js +74 -0
- package/node_modules/@playdrop/config/dist/test/logging.test.d.ts +2 -0
- package/node_modules/@playdrop/config/dist/test/logging.test.d.ts.map +1 -0
- package/node_modules/@playdrop/config/dist/test/logging.test.js +17 -0
- package/node_modules/@playdrop/config/dist/tsconfig.tsbuildinfo +1 -1
- package/node_modules/@playdrop/config/package.json +14 -2
- package/node_modules/@playdrop/types/dist/api.d.ts +27 -10
- package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/api.js +11 -0
- package/node_modules/@playdrop/types/dist/asset-pack.d.ts +1 -0
- package/node_modules/@playdrop/types/dist/asset-pack.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/asset.d.ts +36 -4
- package/node_modules/@playdrop/types/dist/asset.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/asset.js +20 -1
- package/node_modules/@playdrop/types/dist/version.d.ts +3 -1
- package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/version.js +2 -0
- package/node_modules/@playdrop/types/package.json +1 -1
- package/node_modules/@playdrop/vox-three/package.json +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -61,6 +61,7 @@ playdrop credits balance
|
|
|
61
61
|
playdrop notifications browse
|
|
62
62
|
playdrop creations browse
|
|
63
63
|
playdrop ai create image "Pixel art hero portrait"
|
|
64
|
+
playdrop ai create image "Wide fantasy vista" --ratio 16:9 --size 4K
|
|
64
65
|
playdrop project init .
|
|
65
66
|
playdrop project create app my-app --template playdrop/template/html_template
|
|
66
67
|
playdrop project dev my-app
|
package/config/client-meta.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.7.
|
|
3
|
-
"build":
|
|
2
|
+
"version": "0.7.2",
|
|
3
|
+
"build": 12,
|
|
4
4
|
"platforms": {
|
|
5
5
|
"ios": {
|
|
6
6
|
"minimumVersion": "16.0"
|
|
@@ -26,12 +26,12 @@
|
|
|
26
26
|
},
|
|
27
27
|
"clients": {
|
|
28
28
|
"web": {
|
|
29
|
-
"minimumVersion": "0.7.
|
|
30
|
-
"minimumBuild":
|
|
29
|
+
"minimumVersion": "0.7.2",
|
|
30
|
+
"minimumBuild": 12
|
|
31
31
|
},
|
|
32
32
|
"admin": {
|
|
33
|
-
"minimumVersion": "0.7.
|
|
34
|
-
"minimumBuild":
|
|
33
|
+
"minimumVersion": "0.7.2",
|
|
34
|
+
"minimumBuild": 12
|
|
35
35
|
},
|
|
36
36
|
"apple": {
|
|
37
37
|
"minimumVersion": "0.3.10",
|
|
@@ -46,11 +46,11 @@
|
|
|
46
46
|
"minimumBuild": 1
|
|
47
47
|
},
|
|
48
48
|
"android-games": {
|
|
49
|
-
"minimumVersion": "0.1
|
|
50
|
-
"minimumBuild":
|
|
49
|
+
"minimumVersion": "0.7.1",
|
|
50
|
+
"minimumBuild": 5
|
|
51
51
|
},
|
|
52
52
|
"cli": {
|
|
53
|
-
"minimumVersion": "0.7.
|
|
53
|
+
"minimumVersion": "0.7.2"
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
}
|
|
@@ -0,0 +1,360 @@
|
|
|
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.devBrowser = devBrowser;
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
9
|
+
const node_path_1 = require("node:path");
|
|
10
|
+
const types_1 = require("@playdrop/types");
|
|
11
|
+
const commandContext_1 = require("../commandContext");
|
|
12
|
+
const http_1 = require("../http");
|
|
13
|
+
const messages_1 = require("../messages");
|
|
14
|
+
const catalogue_1 = require("../catalogue");
|
|
15
|
+
const catalogue_utils_1 = require("../catalogue-utils");
|
|
16
|
+
const devShared_1 = require("./devShared");
|
|
17
|
+
const appUrls_1 = require("../appUrls");
|
|
18
|
+
const devServer_1 = require("./devServer");
|
|
19
|
+
const devRuntimeAssets_1 = require("./devRuntimeAssets");
|
|
20
|
+
const devAuth_1 = require("../devAuth");
|
|
21
|
+
const dev_1 = require("./dev");
|
|
22
|
+
const playwright_1 = require("../playwright");
|
|
23
|
+
function resolveDevBrowserAuthSelection(options) {
|
|
24
|
+
const normalizedMode = options.devAuth?.trim().toLowerCase() || 'prompt';
|
|
25
|
+
if (options.player !== undefined && normalizedMode !== 'player') {
|
|
26
|
+
throw new Error('dev_auth_player_requires_player_mode');
|
|
27
|
+
}
|
|
28
|
+
return (0, devAuth_1.parseHostedDevAuthSelection)({
|
|
29
|
+
devAuth: options.devAuth,
|
|
30
|
+
player: options.player,
|
|
31
|
+
defaultMode: 'prompt',
|
|
32
|
+
allowPrompt: true,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function waitForContextClose(context) {
|
|
36
|
+
return new Promise((resolve) => {
|
|
37
|
+
context.once?.('close', () => {
|
|
38
|
+
resolve();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
async function devBrowser(targetArg, options = {}) {
|
|
43
|
+
let resolvedTarget;
|
|
44
|
+
let devAuthSelection;
|
|
45
|
+
try {
|
|
46
|
+
resolvedTarget = (0, devShared_1.resolveDevTarget)(targetArg, options.app);
|
|
47
|
+
devAuthSelection = resolveDevBrowserAuthSelection(options);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
const message = error?.message || 'Unable to start the dev browser.';
|
|
51
|
+
if (message === 'dev_auth_player_requires_player_mode') {
|
|
52
|
+
(0, messages_1.printErrorWithHelp)('Use --player only with --dev-auth player.', [], { command: 'project dev-browser' });
|
|
53
|
+
process.exitCode = 1;
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (message === 'dev_auth_player_slot_required') {
|
|
57
|
+
(0, messages_1.printErrorWithHelp)('Use --player 1, 2, 3, or 4 when --dev-auth player is selected.', [], { command: 'project dev-browser' });
|
|
58
|
+
process.exitCode = 1;
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const code = error?.code;
|
|
62
|
+
let suggestions = [];
|
|
63
|
+
switch (code) {
|
|
64
|
+
case 'app_required':
|
|
65
|
+
suggestions = ['Use "--app <name>" to select which app to run.'];
|
|
66
|
+
break;
|
|
67
|
+
case 'app_not_found':
|
|
68
|
+
suggestions = ['Check the app name in catalogue.json or run "playdrop project create app" to register a new app.'];
|
|
69
|
+
break;
|
|
70
|
+
case 'catalogue_missing':
|
|
71
|
+
suggestions = ['Run "playdrop project dev-browser" from a workspace with catalogue.json or provide the path to an HTML file.'];
|
|
72
|
+
break;
|
|
73
|
+
case 'invalid_target':
|
|
74
|
+
suggestions = ['Provide a path to an HTML file or directory containing a catalogue.json.'];
|
|
75
|
+
break;
|
|
76
|
+
case 'empty_catalogue':
|
|
77
|
+
suggestions = ['Add apps to catalogue.json before running "playdrop project dev-browser".'];
|
|
78
|
+
break;
|
|
79
|
+
case 'invalid_catalogue':
|
|
80
|
+
suggestions = ['Fix the catalogue.json file and rerun "playdrop project dev-browser".'];
|
|
81
|
+
break;
|
|
82
|
+
default:
|
|
83
|
+
suggestions = ['Use --dev-auth prompt, anonymous, viewer, or player.', 'If you choose --dev-auth player, also pass --player 1, 2, 3, or 4.'];
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
(0, messages_1.printErrorWithHelp)(message, suggestions, { command: 'project dev-browser' });
|
|
87
|
+
process.exitCode = 1;
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
let appName = resolvedTarget.appName;
|
|
91
|
+
const filePath = resolvedTarget.htmlPath;
|
|
92
|
+
if (!(0, node_fs_1.existsSync)(filePath) || !(0, node_fs_1.statSync)(filePath).isFile()) {
|
|
93
|
+
(0, messages_1.printErrorWithHelp)(`App HTML was not found at ${filePath}.`, ['Ensure the catalogue entry points to a compiled HTML file or provide the correct file path.'], { command: 'project dev-browser' });
|
|
94
|
+
process.exitCode = 1;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
let appTypeSlug = 'game';
|
|
98
|
+
try {
|
|
99
|
+
const match = (0, catalogue_utils_1.findAppDefinition)(filePath);
|
|
100
|
+
appName = match.name;
|
|
101
|
+
appTypeSlug = (0, appUrls_1.getAppTypeSlug)(match.type);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
if (resolvedTarget.cataloguePath) {
|
|
105
|
+
(0, messages_1.printErrorWithHelp)(error?.message || 'Catalogue lookup failed.', [
|
|
106
|
+
'Ensure the HTML file is listed exactly once in catalogue.json with a valid type.',
|
|
107
|
+
'Run "playdrop project create app <name>" to scaffold a new entry if needed.',
|
|
108
|
+
], { command: 'project dev-browser' });
|
|
109
|
+
process.exitCode = 1;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const workspacePath = resolvedTarget.cataloguePath ?? (0, node_path_1.dirname)(filePath);
|
|
114
|
+
await (0, commandContext_1.withEnvironment)('project dev-browser', 'Launching the Playdrop dev browser', async ({ client, env, envConfig, account }) => {
|
|
115
|
+
let currentUsername = account?.username?.trim() ?? '';
|
|
116
|
+
if (!currentUsername) {
|
|
117
|
+
try {
|
|
118
|
+
currentUsername = await (0, devShared_1.fetchDevUsername)(client);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
if (error instanceof http_1.CLIUnsupportedClientError) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (error instanceof types_1.UnsupportedClientError) {
|
|
125
|
+
(0, http_1.handleUnsupportedError)(error, 'Authentication');
|
|
126
|
+
process.exitCode = 1;
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (error instanceof types_1.ApiError) {
|
|
130
|
+
(0, messages_1.printErrorWithHelp)(`Could not fetch your account (status ${error.status}).`, [
|
|
131
|
+
'Run "playdrop auth login" to refresh your session and ensure the API is reachable.',
|
|
132
|
+
'Use "playdrop auth whoami" afterwards to confirm your status.',
|
|
133
|
+
], { command: 'project dev-browser' });
|
|
134
|
+
process.exitCode = 1;
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if ((0, devShared_1.isNetworkError)(error)) {
|
|
138
|
+
(0, messages_1.printNetworkIssue)('Could not reach the Playdrop API to resolve your account.', 'project dev-browser');
|
|
139
|
+
process.exitCode = 1;
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (resolvedTarget.cataloguePath) {
|
|
146
|
+
try {
|
|
147
|
+
await (0, devShared_1.assertAppRegistered)(client, currentUsername, appName);
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
if (error instanceof http_1.CLIUnsupportedClientError) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (error instanceof types_1.UnsupportedClientError) {
|
|
154
|
+
(0, http_1.handleUnsupportedError)(error, 'Dev browser');
|
|
155
|
+
process.exitCode = 1;
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (error instanceof types_1.ApiError) {
|
|
159
|
+
if (error.status === 404) {
|
|
160
|
+
(0, messages_1.printErrorWithHelp)(`App ${currentUsername}/${appName} is not registered on ${env}.`, [
|
|
161
|
+
`Run "playdrop project create app ${appName}" to register the app before opening the dev browser.`,
|
|
162
|
+
'If you expected it to exist, ensure you are logged into the correct environment.',
|
|
163
|
+
], { command: 'project dev-browser' });
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
(0, messages_1.printErrorWithHelp)(`Failed to verify app registration (status ${error.status}).`, ['Retry in a moment.', 'If the issue persists, contact the Playdrop team.'], { command: 'project dev-browser' });
|
|
167
|
+
}
|
|
168
|
+
process.exitCode = 1;
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if ((0, devShared_1.isNetworkError)(error)) {
|
|
172
|
+
(0, messages_1.printNetworkIssue)('Could not reach the Playdrop API to verify the app registration.', 'project dev-browser');
|
|
173
|
+
process.exitCode = 1;
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const projectInfo = (0, devShared_1.findProjectInfo)(filePath);
|
|
180
|
+
const devScriptAvailable = Boolean(projectInfo.projectDir
|
|
181
|
+
&& projectInfo.packageJson
|
|
182
|
+
&& typeof projectInfo.packageJson.scripts?.dev === 'string');
|
|
183
|
+
const taskLookup = (0, catalogue_1.findAppTaskByFile)(filePath);
|
|
184
|
+
if (taskLookup.errors.length > 0) {
|
|
185
|
+
(0, messages_1.printErrorWithHelp)(taskLookup.errors[0] || 'Failed to resolve the app task from catalogue.json.', taskLookup.errors.slice(1), { command: 'project dev-browser' });
|
|
186
|
+
process.exitCode = 1;
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const localDevAppUrl = (0, devServer_1.buildLocalDevAppUrl)({
|
|
190
|
+
creatorUsername: currentUsername,
|
|
191
|
+
appType: appTypeSlug,
|
|
192
|
+
appName,
|
|
193
|
+
port: devServer_1.DEV_ROUTER_PORT,
|
|
194
|
+
});
|
|
195
|
+
let runtimeAssetManifest = (0, devRuntimeAssets_1.createEmptyDevRuntimeAssetManifest)();
|
|
196
|
+
if (taskLookup.task) {
|
|
197
|
+
try {
|
|
198
|
+
runtimeAssetManifest = await (0, devRuntimeAssets_1.buildDevRuntimeAssetManifest)({
|
|
199
|
+
client,
|
|
200
|
+
apiBase: envConfig.apiBase,
|
|
201
|
+
task: taskLookup.task,
|
|
202
|
+
creatorUsername: currentUsername,
|
|
203
|
+
appBaseUrl: new URL('.', localDevAppUrl).toString(),
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
const formatted = (0, dev_1.formatDevRuntimeAssetManifestFailure)(error);
|
|
208
|
+
(0, messages_1.printErrorWithHelp)(formatted.message, formatted.suggestions, { command: 'project dev-browser' });
|
|
209
|
+
process.exitCode = 1;
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
let handle;
|
|
214
|
+
try {
|
|
215
|
+
handle = await (0, devServer_1.startDevServer)({
|
|
216
|
+
appName,
|
|
217
|
+
appType: appTypeSlug,
|
|
218
|
+
creatorUsername: currentUsername,
|
|
219
|
+
htmlPath: filePath,
|
|
220
|
+
port: devServer_1.DEV_ROUTER_PORT,
|
|
221
|
+
projectInfo,
|
|
222
|
+
runtimeAssetManifest,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
const message = typeof error?.message === 'string' ? error.message : '';
|
|
227
|
+
const mountConflict = (0, devServer_1.parseMountConflictError)(message);
|
|
228
|
+
if (mountConflict) {
|
|
229
|
+
(0, messages_1.printErrorWithHelp)(`A different dev session already owns ${mountConflict.ref}.`, [
|
|
230
|
+
`Active owner pid: ${mountConflict.ownerPid}.`,
|
|
231
|
+
`Mounted repo root: ${mountConflict.repoRoot}.`,
|
|
232
|
+
`Mounted HTML path: ${mountConflict.htmlPath}.`,
|
|
233
|
+
], { command: 'project dev-browser' });
|
|
234
|
+
}
|
|
235
|
+
else if (message.startsWith('dev_router_incompatible:')) {
|
|
236
|
+
(0, messages_1.printErrorWithHelp)(`An incompatible shared dev router is already running on port ${devServer_1.DEV_ROUTER_PORT}.`, [
|
|
237
|
+
'Stop the existing dev router process, then retry "playdrop project dev-browser".',
|
|
238
|
+
'If needed, rebuild the local CLI first with "npm run build --workspace @playdrop/playdrop-cli".',
|
|
239
|
+
], { command: 'project dev-browser' });
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
(0, messages_1.printErrorWithHelp)(error?.message || `Failed to start the shared dev router on port ${devServer_1.DEV_ROUTER_PORT}.`, [
|
|
243
|
+
'Close the conflicting process or wait for the stale mount to exit.',
|
|
244
|
+
'Ensure the app HTML file exists and is readable.',
|
|
245
|
+
], { command: 'project dev-browser' });
|
|
246
|
+
}
|
|
247
|
+
process.exitCode = 1;
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
const devUrl = (0, appUrls_1.buildPlatformDevUrl)(envConfig.webBase, {
|
|
251
|
+
creatorUsername: currentUsername,
|
|
252
|
+
appName,
|
|
253
|
+
appType: appTypeSlug,
|
|
254
|
+
devAuth: devAuthSelection.devAuth,
|
|
255
|
+
player: devAuthSelection.player ? String(devAuthSelection.player) : null,
|
|
256
|
+
});
|
|
257
|
+
let launchUrl = devUrl;
|
|
258
|
+
if (devAuthSelection.devAuth !== 'anonymous') {
|
|
259
|
+
try {
|
|
260
|
+
const target = new URL(devUrl);
|
|
261
|
+
const launch = await client.startCliWebLaunch({
|
|
262
|
+
redirectPath: `${target.pathname}${target.search}`,
|
|
263
|
+
});
|
|
264
|
+
launchUrl = launch.launchUrl;
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
await handle.close().catch(() => { });
|
|
268
|
+
if (error instanceof http_1.CLIUnsupportedClientError) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (error instanceof types_1.UnsupportedClientError) {
|
|
272
|
+
(0, http_1.handleUnsupportedError)(error, 'Dev browser launch');
|
|
273
|
+
process.exitCode = 1;
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (error instanceof types_1.ApiError) {
|
|
277
|
+
(0, messages_1.printErrorWithHelp)(`Could not prepare the Playdrop browser session (status ${error.status}).`, ['Run "playdrop auth login" and retry the dev browser command.'], { command: 'project dev-browser' });
|
|
278
|
+
process.exitCode = 1;
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if ((0, devShared_1.isNetworkError)(error)) {
|
|
282
|
+
(0, messages_1.printNetworkIssue)('Could not reach the Playdrop API to prepare the browser session.', 'project dev-browser');
|
|
283
|
+
process.exitCode = 1;
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
throw error;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const entryLabel = (0, node_path_1.relative)(process.cwd(), filePath) || filePath;
|
|
290
|
+
const runtimeRootLabel = (0, node_path_1.relative)(process.cwd(), (0, node_path_1.dirname)(filePath)) || (0, node_path_1.dirname)(filePath);
|
|
291
|
+
const profileDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)(node_os_1.default.tmpdir(), 'playdrop-dev-browser-'));
|
|
292
|
+
console.log(`App dev router mounted at ${handle.appUrl}`);
|
|
293
|
+
console.log(`Serving ${entryLabel}`);
|
|
294
|
+
console.log(`Static root ${runtimeRootLabel}`);
|
|
295
|
+
if (projectInfo.projectDir && !devScriptAvailable && projectInfo.packageJsonPath) {
|
|
296
|
+
const projectLabel = (0, devShared_1.formatProjectLabel)(projectInfo);
|
|
297
|
+
if (projectLabel) {
|
|
298
|
+
console.log(`package.json detected at ${projectLabel}, but no "dev" script was found. Run your build/watch scripts manually if needed.`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
console.log(`Dev URL: ${devUrl}`);
|
|
302
|
+
if (launchUrl !== devUrl) {
|
|
303
|
+
console.log(`Launch URL: ${launchUrl}`);
|
|
304
|
+
}
|
|
305
|
+
console.log('Opening an isolated Chromium dev window.');
|
|
306
|
+
console.log('Close the browser window or press Ctrl+C to stop.');
|
|
307
|
+
let context = null;
|
|
308
|
+
let cleanedUp = false;
|
|
309
|
+
const cleanup = async () => {
|
|
310
|
+
if (cleanedUp) {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
cleanedUp = true;
|
|
314
|
+
process.off('SIGINT', handleSignal);
|
|
315
|
+
process.off('SIGTERM', handleSignal);
|
|
316
|
+
if (context) {
|
|
317
|
+
try {
|
|
318
|
+
await context.close();
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
// Ignore browser shutdown noise during cleanup.
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
try {
|
|
325
|
+
await handle.close();
|
|
326
|
+
}
|
|
327
|
+
finally {
|
|
328
|
+
(0, node_fs_1.rmSync)(profileDir, { recursive: true, force: true });
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
const handleSignal = () => {
|
|
332
|
+
void cleanup();
|
|
333
|
+
};
|
|
334
|
+
process.on('SIGINT', handleSignal);
|
|
335
|
+
process.on('SIGTERM', handleSignal);
|
|
336
|
+
try {
|
|
337
|
+
context = await (0, playwright_1.launchPersistentChromiumContext)(profileDir, {
|
|
338
|
+
viewport: { width: 1440, height: 960 },
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
catch (error) {
|
|
342
|
+
await cleanup();
|
|
343
|
+
(0, messages_1.printErrorWithHelp)(error?.message || 'Failed to launch the Playdrop dev browser.', ['Install Playwright Chromium with "npx playwright install chromium" and retry.'], { command: 'project dev-browser' });
|
|
344
|
+
process.exitCode = 1;
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
try {
|
|
348
|
+
const page = context.pages()[0] ?? await context.newPage();
|
|
349
|
+
await page.goto(launchUrl, { waitUntil: 'domcontentloaded' });
|
|
350
|
+
await waitForContextClose(context);
|
|
351
|
+
}
|
|
352
|
+
catch (error) {
|
|
353
|
+
await cleanup();
|
|
354
|
+
(0, messages_1.printErrorWithHelp)(error?.message || 'Failed to open the Playdrop dev browser window.', [], { command: 'project dev-browser' });
|
|
355
|
+
process.exitCode = 1;
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
await cleanup();
|
|
359
|
+
}, { workspacePath });
|
|
360
|
+
}
|
|
@@ -22,6 +22,10 @@ exports.DEV_ROUTER_PORT = 8888;
|
|
|
22
22
|
const DEV_ROUTER_HOST = '127.0.0.1';
|
|
23
23
|
const CONTROL_PREFIX = '/_playdrop';
|
|
24
24
|
const DEV_ROUTER_PROTOCOL_VERSION = 3;
|
|
25
|
+
const APPROVED_PLAYDROP_WEB_ORIGINS = new Set([
|
|
26
|
+
'https://playdrop.ai',
|
|
27
|
+
'https://www.playdrop.ai',
|
|
28
|
+
]);
|
|
25
29
|
const CONTENT_TYPE_BY_EXTENSION = {
|
|
26
30
|
'.html': 'text/html; charset=utf-8',
|
|
27
31
|
'.js': 'application/javascript; charset=utf-8',
|
|
@@ -42,6 +46,25 @@ const CONTENT_TYPE_BY_EXTENSION = {
|
|
|
42
46
|
};
|
|
43
47
|
const routerMountsById = new Map();
|
|
44
48
|
const routerMountIdsByKey = new Map();
|
|
49
|
+
function resolveApprovedDevRouterOrigin(originHeader) {
|
|
50
|
+
const origin = originHeader?.trim();
|
|
51
|
+
if (!origin) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
let parsedOrigin;
|
|
55
|
+
try {
|
|
56
|
+
parsedOrigin = new URL(origin);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
if (APPROVED_PLAYDROP_WEB_ORIGINS.has(parsedOrigin.origin)) {
|
|
62
|
+
return parsedOrigin.origin;
|
|
63
|
+
}
|
|
64
|
+
const isLocalWebOrigin = (parsedOrigin.protocol === 'http:' || parsedOrigin.protocol === 'https:')
|
|
65
|
+
&& (parsedOrigin.hostname === 'localhost' || parsedOrigin.hostname === '127.0.0.1');
|
|
66
|
+
return isLocalWebOrigin ? parsedOrigin.origin : null;
|
|
67
|
+
}
|
|
45
68
|
function resolveContentType(filePath) {
|
|
46
69
|
const extension = (0, node_path_1.extname)(filePath).toLowerCase();
|
|
47
70
|
return CONTENT_TYPE_BY_EXTENSION[extension] || 'application/octet-stream';
|
|
@@ -426,7 +449,11 @@ function createDevRouterServer(initialPort = exports.DEV_ROUTER_PORT) {
|
|
|
426
449
|
// eslint-disable-next-line complexity
|
|
427
450
|
const handleRequest = async (req, res) => {
|
|
428
451
|
const method = req.method || 'GET';
|
|
429
|
-
|
|
452
|
+
const allowedOrigin = resolveApprovedDevRouterOrigin(typeof req.headers.origin === 'string' ? req.headers.origin : undefined);
|
|
453
|
+
if (allowedOrigin) {
|
|
454
|
+
res.setHeader('Access-Control-Allow-Origin', allowedOrigin);
|
|
455
|
+
}
|
|
456
|
+
res.setHeader('Vary', 'Origin');
|
|
430
457
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
|
|
431
458
|
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Playdrop-Client, X-Playdrop-Client-Version, X-Playdrop-Client-Build, X-Playdrop-Platform, X-Playdrop-Platform-Version');
|
|
432
459
|
res.setHeader('Access-Control-Allow-Private-Network', 'true');
|
|
@@ -13,15 +13,18 @@ const errors_1 = require("../errors");
|
|
|
13
13
|
const messages_1 = require("../messages");
|
|
14
14
|
const output_1 = require("../output");
|
|
15
15
|
const IMAGE_ASPECT_RATIO_SET = new Set(['1:1', '3:4', '9:16', '4:3', '16:9']);
|
|
16
|
+
const IMAGE_SIZE_SET = new Set(['1K', '4K']);
|
|
16
17
|
const VIDEO_ASPECT_RATIO_SET = new Set(['16:9', '9:16']);
|
|
17
18
|
const DEFAULT_SUBCATEGORY_BY_MODALITY = {
|
|
18
19
|
IMAGE: 'generic',
|
|
19
20
|
MUSIC: 'music',
|
|
20
21
|
SFX: 'sfx',
|
|
22
|
+
SPEECH: 'speech',
|
|
21
23
|
VIDEO: 'generic',
|
|
22
24
|
MODEL_3D: 'generic',
|
|
23
25
|
};
|
|
24
26
|
const DEFAULT_IMAGE_ASPECT_RATIO = '1:1';
|
|
27
|
+
const DEFAULT_IMAGE_SIZE = '1K';
|
|
25
28
|
const DEFAULT_VIDEO_ASPECT_RATIO = '16:9';
|
|
26
29
|
const DEFAULT_VIDEO_DURATION_SECONDS = 4;
|
|
27
30
|
const VIDEO_FIRST_LAST_FRAME_DURATION_SECONDS = 8;
|
|
@@ -37,11 +40,14 @@ const LOCAL_IMAGE_MIME_BY_EXTENSION = {
|
|
|
37
40
|
'.jpeg': 'image/jpeg',
|
|
38
41
|
'.webp': 'image/webp',
|
|
39
42
|
};
|
|
43
|
+
function isPlaydropSpeechVoiceId(value) {
|
|
44
|
+
return types_1.PLAYDROP_SPEECH_VOICE_IDS.includes(value);
|
|
45
|
+
}
|
|
40
46
|
function handleGenerateBuildRequestError(message) {
|
|
41
47
|
const errorByCode = {
|
|
42
48
|
missing_type: {
|
|
43
49
|
title: 'A generation type is required.',
|
|
44
|
-
details: ['Use one of: image, music, sfx, video, model_3d.'],
|
|
50
|
+
details: ['Use one of: image, music, sfx, speech, video, model_3d.'],
|
|
45
51
|
},
|
|
46
52
|
missing_prompt: {
|
|
47
53
|
title: 'A prompt is required.',
|
|
@@ -49,7 +55,7 @@ function handleGenerateBuildRequestError(message) {
|
|
|
49
55
|
},
|
|
50
56
|
invalid_type: {
|
|
51
57
|
title: 'The generation type is invalid.',
|
|
52
|
-
details: ['Use one of: image, music, sfx, video, model_3d.'],
|
|
58
|
+
details: ['Use one of: image, music, sfx, speech, video, model_3d.'],
|
|
53
59
|
},
|
|
54
60
|
invalid_duration: {
|
|
55
61
|
title: 'The --duration value is invalid.',
|
|
@@ -63,6 +69,14 @@ function handleGenerateBuildRequestError(message) {
|
|
|
63
69
|
title: 'The --ratio value is invalid.',
|
|
64
70
|
details: ['Image: 1:1, 3:4, 9:16, 4:3, 16:9. Video: 16:9, 9:16.'],
|
|
65
71
|
},
|
|
72
|
+
invalid_image_size: {
|
|
73
|
+
title: 'The --size value is invalid.',
|
|
74
|
+
details: ['Image size must be 1K or 4K.'],
|
|
75
|
+
},
|
|
76
|
+
image_size_not_supported_for_modality: {
|
|
77
|
+
title: 'The --size option is only supported for image generation.',
|
|
78
|
+
details: ['Use --size 1K or --size 4K with "playdrop ai create image ...".'],
|
|
79
|
+
},
|
|
66
80
|
missing_model3d_images: {
|
|
67
81
|
title: 'Model 3D IMAGE source mode requires at least one reference image.',
|
|
68
82
|
details: ['Provide --image1 <url> or --image2 <url>, or switch to --source-mode TEXT.'],
|
|
@@ -75,6 +89,26 @@ function handleGenerateBuildRequestError(message) {
|
|
|
75
89
|
title: 'The --subcategory value is invalid.',
|
|
76
90
|
details: ['Use a lowercase slug such as generic, music, sfx, or avatar.'],
|
|
77
91
|
},
|
|
92
|
+
speech_disallows_subcategory: {
|
|
93
|
+
title: 'Speech does not accept --subcategory.',
|
|
94
|
+
details: ['Speech jobs always create AUDIO assets with subcategory speech.'],
|
|
95
|
+
},
|
|
96
|
+
invalid_speech_voice_selection: {
|
|
97
|
+
title: 'Speech voice options are invalid.',
|
|
98
|
+
details: ['Use either --voice <alias> or --provider-voice-id <id>, but not both.'],
|
|
99
|
+
},
|
|
100
|
+
invalid_speech_voice: {
|
|
101
|
+
title: 'The --voice value is invalid.',
|
|
102
|
+
details: [`Use one of: ${types_1.PLAYDROP_SPEECH_VOICE_IDS.join(', ')}.`],
|
|
103
|
+
},
|
|
104
|
+
speech_voice_unavailable: {
|
|
105
|
+
title: 'The provider voice is unavailable.',
|
|
106
|
+
details: ['Use a different --provider-voice-id or a curated --voice alias.'],
|
|
107
|
+
},
|
|
108
|
+
speech_input_too_long: {
|
|
109
|
+
title: 'The speech input is too long.',
|
|
110
|
+
details: [`Use ${types_1.PLAYDROP_SPEECH_MAX_WORDS} words or fewer.`],
|
|
111
|
+
},
|
|
78
112
|
};
|
|
79
113
|
const entry = errorByCode[message];
|
|
80
114
|
if (!entry) {
|
|
@@ -139,6 +173,8 @@ function normalizeModality(value) {
|
|
|
139
173
|
return 'MUSIC';
|
|
140
174
|
if (normalized === 'SFX')
|
|
141
175
|
return 'SFX';
|
|
176
|
+
if (normalized === 'SPEECH')
|
|
177
|
+
return 'SPEECH';
|
|
142
178
|
if (normalized === 'VIDEO')
|
|
143
179
|
return 'VIDEO';
|
|
144
180
|
if (normalized === 'MODEL3D' || normalized === 'MODEL_3D')
|
|
@@ -271,6 +307,7 @@ function parseListType(raw) {
|
|
|
271
307
|
|| normalized === 'image'
|
|
272
308
|
|| normalized === 'music'
|
|
273
309
|
|| normalized === 'sfx'
|
|
310
|
+
|| normalized === 'speech'
|
|
274
311
|
|| normalized === 'video'
|
|
275
312
|
|| normalized === 'model_3d') {
|
|
276
313
|
return normalized;
|
|
@@ -369,10 +406,17 @@ function buildGenerateRequest(typeInput, promptInput, options) {
|
|
|
369
406
|
if (!modality) {
|
|
370
407
|
throw new Error('invalid_type');
|
|
371
408
|
}
|
|
372
|
-
const prompt =
|
|
409
|
+
const prompt = modality === 'SPEECH'
|
|
410
|
+
? (0, types_1.normalizeSpeechText)(typeof promptInput === 'string' ? promptInput : '')
|
|
411
|
+
: typeof promptInput === 'string'
|
|
412
|
+
? promptInput.trim()
|
|
413
|
+
: '';
|
|
373
414
|
if (!prompt.length) {
|
|
374
415
|
throw new Error('missing_prompt');
|
|
375
416
|
}
|
|
417
|
+
if (modality === 'SPEECH' && (0, types_1.countSpeechWords)(prompt) > types_1.PLAYDROP_SPEECH_MAX_WORDS) {
|
|
418
|
+
throw new Error('speech_input_too_long');
|
|
419
|
+
}
|
|
376
420
|
const visibility = normalizeVisibility(options.visibility);
|
|
377
421
|
if (options.visibility && !visibility) {
|
|
378
422
|
throw new Error('invalid_visibility');
|
|
@@ -386,7 +430,6 @@ function buildGenerateRequest(typeInput, promptInput, options) {
|
|
|
386
430
|
const request = {
|
|
387
431
|
modality,
|
|
388
432
|
input: prompt,
|
|
389
|
-
assetSubcategory: parseAssetSubcategory(options.subcategory) ?? DEFAULT_SUBCATEGORY_BY_MODALITY[modality],
|
|
390
433
|
assetName: typeof options.assetName === 'string' && options.assetName.trim().length > 0 ? options.assetName.trim() : undefined,
|
|
391
434
|
assetDisplayName: typeof options.assetDisplayName === 'string' && options.assetDisplayName.trim().length > 0
|
|
392
435
|
? options.assetDisplayName.trim()
|
|
@@ -397,6 +440,12 @@ function buildGenerateRequest(typeInput, promptInput, options) {
|
|
|
397
440
|
assetVisibility: visibility ?? 'PUBLIC',
|
|
398
441
|
attachAppVersionId: parseOptionalPositiveInt(options.attachAppVersionId, 'attach_app_version_id'),
|
|
399
442
|
};
|
|
443
|
+
if (modality !== 'SPEECH') {
|
|
444
|
+
request.assetSubcategory = parseAssetSubcategory(options.subcategory) ?? DEFAULT_SUBCATEGORY_BY_MODALITY[modality];
|
|
445
|
+
}
|
|
446
|
+
else if (typeof options.subcategory === 'string' && options.subcategory.trim().length > 0) {
|
|
447
|
+
throw new Error('speech_disallows_subcategory');
|
|
448
|
+
}
|
|
400
449
|
if (modality === 'IMAGE') {
|
|
401
450
|
request.image1 = resolveImageAttachment(options.image1, 'image1');
|
|
402
451
|
request.image2 = resolveImageAttachment(options.image2, 'image2');
|
|
@@ -410,6 +459,19 @@ function buildGenerateRequest(typeInput, promptInput, options) {
|
|
|
410
459
|
else {
|
|
411
460
|
request.aspectRatio = DEFAULT_IMAGE_ASPECT_RATIO;
|
|
412
461
|
}
|
|
462
|
+
if (typeof options.size === 'string' && options.size.trim().length > 0) {
|
|
463
|
+
const imageSize = options.size.trim().toUpperCase();
|
|
464
|
+
if (!IMAGE_SIZE_SET.has(imageSize)) {
|
|
465
|
+
throw new Error('invalid_image_size');
|
|
466
|
+
}
|
|
467
|
+
request.imageSize = imageSize;
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
request.imageSize = DEFAULT_IMAGE_SIZE;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
else if (typeof options.size === 'string' && options.size.trim().length > 0) {
|
|
474
|
+
throw new Error('image_size_not_supported_for_modality');
|
|
413
475
|
}
|
|
414
476
|
if (modality === 'MUSIC') {
|
|
415
477
|
request.durationMs = parseMusicDurationMs(duration);
|
|
@@ -421,6 +483,24 @@ function buildGenerateRequest(typeInput, promptInput, options) {
|
|
|
421
483
|
request.loop = Boolean(options.loop);
|
|
422
484
|
request.coverPrompt = typeof options.coverPrompt === 'string' && options.coverPrompt.trim().length > 0 ? options.coverPrompt.trim() : undefined;
|
|
423
485
|
}
|
|
486
|
+
if (modality === 'SPEECH') {
|
|
487
|
+
const speechVoice = typeof options.voice === 'string' && options.voice.trim().length > 0 ? options.voice.trim() : undefined;
|
|
488
|
+
const speechProviderVoiceId = typeof options.providerVoiceId === 'string' && options.providerVoiceId.trim().length > 0
|
|
489
|
+
? options.providerVoiceId.trim()
|
|
490
|
+
: undefined;
|
|
491
|
+
if (speechVoice && speechProviderVoiceId) {
|
|
492
|
+
throw new Error('invalid_speech_voice_selection');
|
|
493
|
+
}
|
|
494
|
+
let validatedSpeechVoice;
|
|
495
|
+
if (speechVoice) {
|
|
496
|
+
if (!isPlaydropSpeechVoiceId(speechVoice)) {
|
|
497
|
+
throw new Error('invalid_speech_voice');
|
|
498
|
+
}
|
|
499
|
+
validatedSpeechVoice = speechVoice;
|
|
500
|
+
}
|
|
501
|
+
request.speechVoice = validatedSpeechVoice;
|
|
502
|
+
request.speechProviderVoiceId = speechProviderVoiceId;
|
|
503
|
+
}
|
|
424
504
|
if (modality === 'VIDEO') {
|
|
425
505
|
request.image1 = resolveImageAttachment(options.image1, 'image1');
|
|
426
506
|
request.image2 = resolveImageAttachment(options.image2, 'image2');
|