@playdrop/playdrop-cli 0.5.2 → 0.5.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/config/client-meta.json +4 -4
- package/dist/apps/build.js +49 -6
- package/dist/apps/index.d.ts +2 -0
- package/dist/apps/index.js +2 -0
- package/dist/apps/upload.d.ts +2 -0
- package/dist/apps/upload.js +126 -28
- package/dist/assetSpecs.d.ts +16 -0
- package/dist/assetSpecs.js +263 -0
- package/dist/assets/model-artifacts.js +3 -0
- package/dist/catalogue.d.ts +57 -3
- package/dist/catalogue.js +342 -16
- package/dist/commandContext.d.ts +6 -2
- package/dist/commandContext.js +144 -20
- package/dist/commands/accounts.d.ts +2 -0
- package/dist/commands/accounts.js +48 -0
- package/dist/commands/ads.d.ts +8 -0
- package/dist/commands/ads.js +124 -0
- package/dist/commands/boosts.d.ts +25 -0
- package/dist/commands/boosts.js +209 -0
- package/dist/commands/browse.d.ts +6 -1
- package/dist/commands/browse.js +365 -124
- package/dist/commands/capture.js +30 -9
- package/dist/commands/captureListing.d.ts +53 -0
- package/dist/commands/captureListing.js +815 -0
- package/dist/commands/create.d.ts +1 -0
- package/dist/commands/create.js +183 -3
- package/dist/commands/credits.d.ts +6 -0
- package/dist/commands/credits.js +47 -1
- package/dist/commands/detail.js +38 -4
- package/dist/commands/dev.js +169 -192
- package/dist/commands/devServer.d.ts +26 -3
- package/dist/commands/devServer.js +415 -72
- package/dist/commands/login.js +10 -2
- package/dist/commands/logout.d.ts +6 -1
- package/dist/commands/logout.js +25 -3
- package/dist/commands/search.d.ts +5 -0
- package/dist/commands/search.js +139 -17
- package/dist/commands/tags.d.ts +7 -0
- package/dist/commands/tags.js +63 -0
- package/dist/commands/upload-content.d.ts +13 -3
- package/dist/commands/upload-content.js +86 -20
- package/dist/commands/upload.d.ts +2 -0
- package/dist/commands/upload.js +187 -11
- package/dist/commands/validate.js +163 -2
- package/dist/commands/versionsBrowse.js +128 -91
- package/dist/commands/whoami.js +10 -2
- package/dist/config.d.ts +37 -0
- package/dist/config.js +205 -3
- package/dist/index.js +177 -5
- package/dist/refs.d.ts +2 -2
- package/dist/refs.js +13 -1
- package/dist/taskSelection.js +6 -3
- package/dist/taskUtils.d.ts +2 -2
- package/dist/taskUtils.js +1 -0
- package/dist/uploadLog.d.ts +1 -1
- package/dist/uploadLog.js +2 -2
- package/dist/workspaceAuth.d.ts +14 -0
- package/dist/workspaceAuth.js +75 -0
- package/node_modules/@playdrop/ai-client/package.json +1 -1
- package/node_modules/@playdrop/api-client/dist/client.d.ts +139 -10
- package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/client.js +6 -0
- package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts +9 -1
- package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/admin.js +45 -0
- package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts +7 -1
- package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/apps.js +58 -0
- package/node_modules/@playdrop/api-client/dist/domains/asset-packs.d.ts +2 -0
- package/node_modules/@playdrop/api-client/dist/domains/asset-packs.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/asset-packs.js +16 -0
- package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts +44 -2
- package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/assets.js +260 -3
- package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts +22 -1
- package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/payments.js +228 -0
- package/node_modules/@playdrop/api-client/dist/domains/search.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/search.js +39 -11
- package/node_modules/@playdrop/api-client/dist/domains/tags.d.ts +34 -0
- package/node_modules/@playdrop/api-client/dist/domains/tags.d.ts.map +1 -0
- package/node_modules/@playdrop/api-client/dist/domains/tags.js +111 -0
- package/node_modules/@playdrop/api-client/dist/index.d.ts +69 -1
- package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/index.js +74 -0
- package/node_modules/@playdrop/api-client/package.json +1 -1
- package/node_modules/@playdrop/boxel-core/package.json +1 -1
- package/node_modules/@playdrop/boxel-three/package.json +1 -1
- package/node_modules/@playdrop/config/client-meta.json +4 -4
- package/node_modules/@playdrop/config/dist/src/constants.d.ts +11 -0
- package/node_modules/@playdrop/config/dist/src/constants.d.ts.map +1 -1
- package/node_modules/@playdrop/config/dist/src/constants.js +12 -1
- package/node_modules/@playdrop/config/dist/tsconfig.tsbuildinfo +1 -1
- package/node_modules/@playdrop/config/package.json +1 -1
- package/node_modules/@playdrop/types/dist/api.d.ts +366 -6
- package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/api.js +52 -1
- package/node_modules/@playdrop/types/dist/asset-pack.d.ts +7 -1
- package/node_modules/@playdrop/types/dist/asset-pack.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/asset-spec-contract-meta-schema.json +86 -0
- package/node_modules/@playdrop/types/dist/asset-spec.d.ts +163 -0
- package/node_modules/@playdrop/types/dist/asset-spec.d.ts.map +1 -0
- package/node_modules/@playdrop/types/dist/asset-spec.js +101 -0
- package/node_modules/@playdrop/types/dist/asset.d.ts +23 -6
- package/node_modules/@playdrop/types/dist/asset.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/asset.js +4 -1
- package/node_modules/@playdrop/types/dist/graph.d.ts +4 -2
- package/node_modules/@playdrop/types/dist/graph.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/graph.js +9 -2
- 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/version.d.ts +13 -0
- package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/version.js +21 -0
- package/node_modules/@playdrop/types/package.json +6 -1
- package/node_modules/@playdrop/vox-three/package.json +1 -1
- package/package.json +3 -1
|
@@ -3,12 +3,21 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.DEV_ROUTER_PORT = void 0;
|
|
7
|
+
exports.parseMountConflictError = parseMountConflictError;
|
|
8
|
+
exports.buildLocalDevAppUrl = buildLocalDevAppUrl;
|
|
9
|
+
exports.ensureDevRouterRunning = ensureDevRouterRunning;
|
|
6
10
|
exports.startDevServer = startDevServer;
|
|
7
11
|
exports.isDevServerAvailable = isDevServerAvailable;
|
|
12
|
+
exports.runDevRouterServer = runDevRouterServer;
|
|
8
13
|
const node_http_1 = __importDefault(require("node:http"));
|
|
9
14
|
const node_fs_1 = require("node:fs");
|
|
10
15
|
const node_path_1 = require("node:path");
|
|
11
16
|
const node_child_process_1 = require("node:child_process");
|
|
17
|
+
const node_crypto_1 = require("node:crypto");
|
|
18
|
+
exports.DEV_ROUTER_PORT = 8888;
|
|
19
|
+
const DEV_ROUTER_HOST = '127.0.0.1';
|
|
20
|
+
const CONTROL_PREFIX = '/_playdrop';
|
|
12
21
|
const CONTENT_TYPE_BY_EXTENSION = {
|
|
13
22
|
'.html': 'text/html; charset=utf-8',
|
|
14
23
|
'.js': 'application/javascript; charset=utf-8',
|
|
@@ -27,14 +36,57 @@ const CONTENT_TYPE_BY_EXTENSION = {
|
|
|
27
36
|
'.mp4': 'video/mp4',
|
|
28
37
|
'.webm': 'video/webm',
|
|
29
38
|
};
|
|
39
|
+
const routerMountsById = new Map();
|
|
40
|
+
const routerMountIdsByKey = new Map();
|
|
30
41
|
function resolveContentType(filePath) {
|
|
31
42
|
const extension = (0, node_path_1.extname)(filePath).toLowerCase();
|
|
32
43
|
return CONTENT_TYPE_BY_EXTENSION[extension] || 'application/octet-stream';
|
|
33
44
|
}
|
|
45
|
+
function getCliEntrypointPath() {
|
|
46
|
+
return (0, node_path_1.resolve)(__dirname, '..', 'index.js');
|
|
47
|
+
}
|
|
48
|
+
function buildMountKey(creatorUsername, appType, appName) {
|
|
49
|
+
return `${creatorUsername}:${appType}:${appName}`;
|
|
50
|
+
}
|
|
51
|
+
function encodeMountConflictError(details) {
|
|
52
|
+
const encoded = Buffer.from(JSON.stringify(details), 'utf8').toString('base64url');
|
|
53
|
+
return `mount_conflict:${encoded}`;
|
|
54
|
+
}
|
|
55
|
+
function parseMountConflictError(message) {
|
|
56
|
+
if (!message.startsWith('mount_conflict:')) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const decoded = Buffer.from(message.slice('mount_conflict:'.length), 'base64url').toString('utf8');
|
|
61
|
+
const payload = JSON.parse(decoded);
|
|
62
|
+
if (typeof payload.ref !== 'string'
|
|
63
|
+
|| typeof payload.ownerPid !== 'number'
|
|
64
|
+
|| !Number.isInteger(payload.ownerPid)
|
|
65
|
+
|| payload.ownerPid <= 0
|
|
66
|
+
|| typeof payload.repoRoot !== 'string'
|
|
67
|
+
|| typeof payload.htmlPath !== 'string') {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
ref: payload.ref,
|
|
72
|
+
ownerPid: payload.ownerPid,
|
|
73
|
+
repoRoot: payload.repoRoot,
|
|
74
|
+
htmlPath: payload.htmlPath,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function buildLocalDevAppUrl(input) {
|
|
82
|
+
const port = input.port ?? exports.DEV_ROUTER_PORT;
|
|
83
|
+
return `http://${DEV_ROUTER_HOST}:${port}/apps/dev/${encodeURIComponent(input.creatorUsername)}/${encodeURIComponent(input.appType)}/${encodeURIComponent(input.appName)}/index.html`;
|
|
84
|
+
}
|
|
34
85
|
function respondWithBuffer(res, method, statusCode, contentType, payload) {
|
|
35
86
|
res.statusCode = statusCode;
|
|
36
87
|
res.setHeader('Content-Type', contentType);
|
|
37
88
|
res.setHeader('Content-Length', String(payload.length));
|
|
89
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
38
90
|
if (method === 'HEAD') {
|
|
39
91
|
res.end();
|
|
40
92
|
return;
|
|
@@ -45,6 +97,37 @@ function respondWithText(res, method, statusCode, message) {
|
|
|
45
97
|
const payload = Buffer.from(message, 'utf8');
|
|
46
98
|
respondWithBuffer(res, method, statusCode, 'text/plain; charset=utf-8', payload);
|
|
47
99
|
}
|
|
100
|
+
function sendJson(res, statusCode, payload) {
|
|
101
|
+
const body = Buffer.from(JSON.stringify(payload), 'utf8');
|
|
102
|
+
respondWithBuffer(res, 'GET', statusCode, 'application/json; charset=utf-8', body);
|
|
103
|
+
}
|
|
104
|
+
function isPidAlive(pid) {
|
|
105
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
process.kill(pid, 0);
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function removeRouterMount(mountId) {
|
|
117
|
+
const mount = routerMountsById.get(mountId);
|
|
118
|
+
if (!mount) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
routerMountsById.delete(mountId);
|
|
122
|
+
routerMountIdsByKey.delete(mount.key);
|
|
123
|
+
}
|
|
124
|
+
function cleanupStaleRouterMounts() {
|
|
125
|
+
for (const [mountId, mount] of routerMountsById.entries()) {
|
|
126
|
+
if (!isPidAlive(mount.ownerPid)) {
|
|
127
|
+
removeRouterMount(mountId);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
48
131
|
function resolveStaticPath(staticRoot, rawRelativePath) {
|
|
49
132
|
const normalizedRelativePath = (0, node_path_1.normalize)(rawRelativePath.replace(/^\/+/, ''));
|
|
50
133
|
if (!normalizedRelativePath || normalizedRelativePath === '.' || normalizedRelativePath.startsWith('..')) {
|
|
@@ -57,36 +140,227 @@ function resolveStaticPath(staticRoot, rawRelativePath) {
|
|
|
57
140
|
}
|
|
58
141
|
return absolutePath;
|
|
59
142
|
}
|
|
143
|
+
function parseMountPath(pathname) {
|
|
144
|
+
if (!pathname.startsWith('/apps/dev/')) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
const segments = pathname.split('/').filter(Boolean);
|
|
148
|
+
if (segments.length < 6) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
// filter(Boolean) removes the leading empty segment from absolute paths, so the
|
|
152
|
+
// creator tuple starts at indices 2..4 for /apps/dev/<creator>/<type>/<name>/...
|
|
153
|
+
const [, , creatorUsername, appType, appName, ...rest] = segments;
|
|
154
|
+
if (!creatorUsername || !appType || !appName || rest.length === 0) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
key: buildMountKey(decodeURIComponent(creatorUsername), decodeURIComponent(appType), decodeURIComponent(appName)),
|
|
159
|
+
assetPath: rest.map((entry) => decodeURIComponent(entry)).join('/'),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
async function readJsonBody(req) {
|
|
163
|
+
const chunks = [];
|
|
164
|
+
for await (const chunk of req) {
|
|
165
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
166
|
+
}
|
|
167
|
+
const raw = Buffer.concat(chunks).toString('utf8').trim();
|
|
168
|
+
return raw ? JSON.parse(raw) : {};
|
|
169
|
+
}
|
|
60
170
|
function spawnDevScript(projectInfo) {
|
|
61
171
|
if (!projectInfo.projectDir || !projectInfo.packageJson || typeof projectInfo.packageJson.scripts?.dev !== 'string') {
|
|
62
172
|
return null;
|
|
63
173
|
}
|
|
64
174
|
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
65
|
-
const projectLabel = (0, node_path_1.relative)(process.cwd(), projectInfo.projectDir) || projectInfo.projectDir;
|
|
66
|
-
console.log(`▶ Running "npm run dev" in ${projectLabel}`);
|
|
67
175
|
const child = (0, node_child_process_1.spawn)(npmCommand, ['run', 'dev'], {
|
|
68
176
|
cwd: projectInfo.projectDir,
|
|
69
177
|
stdio: 'inherit',
|
|
70
178
|
env: { ...process.env },
|
|
71
179
|
});
|
|
72
|
-
child
|
|
73
|
-
|
|
74
|
-
|
|
180
|
+
return child;
|
|
181
|
+
}
|
|
182
|
+
async function fetchRouterJson(path, init = {}, port = exports.DEV_ROUTER_PORT) {
|
|
183
|
+
const response = await fetch(`http://${DEV_ROUTER_HOST}:${port}${path}`, init);
|
|
184
|
+
if (!response.ok) {
|
|
185
|
+
if (response.status === 404) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
const payload = await response.json().catch(() => null);
|
|
189
|
+
throw new Error(typeof payload?.error === 'string'
|
|
190
|
+
? payload.error
|
|
191
|
+
: `router_request_failed:${response.status}`);
|
|
192
|
+
}
|
|
193
|
+
return await response.json();
|
|
194
|
+
}
|
|
195
|
+
async function isRouterHealthy(port = exports.DEV_ROUTER_PORT, timeoutMs = 400) {
|
|
196
|
+
const controller = new AbortController();
|
|
197
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
198
|
+
timeout.unref?.();
|
|
199
|
+
try {
|
|
200
|
+
const response = await fetch(`http://${DEV_ROUTER_HOST}:${port}${CONTROL_PREFIX}/health`, {
|
|
201
|
+
method: 'GET',
|
|
202
|
+
signal: controller.signal,
|
|
203
|
+
});
|
|
204
|
+
return response.ok;
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
finally {
|
|
210
|
+
clearTimeout(timeout);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async function ensureDevRouterRunning(port = exports.DEV_ROUTER_PORT) {
|
|
214
|
+
if (await isRouterHealthy(port)) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const cliEntrypoint = getCliEntrypointPath();
|
|
218
|
+
const child = (0, node_child_process_1.spawn)(process.execPath, [cliEntrypoint, 'project', '_dev-router', 'serve'], {
|
|
219
|
+
detached: true,
|
|
220
|
+
stdio: 'ignore',
|
|
221
|
+
env: {
|
|
222
|
+
...process.env,
|
|
223
|
+
PLAYDROP_DEV_ROUTER_PORT: String(port),
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
child.unref();
|
|
227
|
+
const deadline = Date.now() + 5000;
|
|
228
|
+
while (Date.now() < deadline) {
|
|
229
|
+
if (await isRouterHealthy(port, 250)) {
|
|
230
|
+
return;
|
|
75
231
|
}
|
|
232
|
+
await new Promise((resolveTimeout) => setTimeout(resolveTimeout, 150));
|
|
233
|
+
}
|
|
234
|
+
throw new Error('dev_router_start_failed');
|
|
235
|
+
}
|
|
236
|
+
async function registerDevMount(input, port = exports.DEV_ROUTER_PORT) {
|
|
237
|
+
const response = await fetch(`http://${DEV_ROUTER_HOST}:${port}${CONTROL_PREFIX}/mounts`, {
|
|
238
|
+
method: 'POST',
|
|
239
|
+
headers: { 'Content-Type': 'application/json' },
|
|
240
|
+
body: JSON.stringify(input),
|
|
76
241
|
});
|
|
77
|
-
|
|
242
|
+
const payload = await response.json().catch(() => null);
|
|
243
|
+
if (!response.ok || !payload) {
|
|
244
|
+
throw new Error(`mount_register_failed:${response.status}`);
|
|
245
|
+
}
|
|
246
|
+
if ('error' in payload) {
|
|
247
|
+
throw new Error(encodeMountConflictError({
|
|
248
|
+
ref: `${payload.creatorUsername}/${payload.appType}/${payload.appName}`,
|
|
249
|
+
ownerPid: payload.ownerPid,
|
|
250
|
+
repoRoot: payload.repoRoot,
|
|
251
|
+
htmlPath: payload.htmlPath,
|
|
252
|
+
}));
|
|
253
|
+
}
|
|
254
|
+
return {
|
|
255
|
+
mountId: payload.mountId,
|
|
256
|
+
appUrl: payload.appUrl,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
async function heartbeatDevMount(mountId, ownerPid, port = exports.DEV_ROUTER_PORT) {
|
|
260
|
+
await fetchRouterJson(`${CONTROL_PREFIX}/mounts/${encodeURIComponent(mountId)}/heartbeat`, {
|
|
261
|
+
method: 'POST',
|
|
262
|
+
headers: { 'Content-Type': 'application/json' },
|
|
263
|
+
body: JSON.stringify({ ownerPid }),
|
|
264
|
+
}, port);
|
|
265
|
+
}
|
|
266
|
+
async function unregisterDevMount(mountId, port = exports.DEV_ROUTER_PORT) {
|
|
267
|
+
const response = await fetch(`http://${DEV_ROUTER_HOST}:${port}${CONTROL_PREFIX}/mounts/${encodeURIComponent(mountId)}`, {
|
|
268
|
+
method: 'DELETE',
|
|
269
|
+
});
|
|
270
|
+
if (!response.ok && response.status !== 404) {
|
|
271
|
+
throw new Error(`mount_unregister_failed:${response.status}`);
|
|
272
|
+
}
|
|
78
273
|
}
|
|
79
274
|
async function startDevServer(options) {
|
|
80
|
-
const { appName, htmlPath, port, projectInfo } = options;
|
|
275
|
+
const { appName, appType, creatorUsername, htmlPath, port, projectInfo } = options;
|
|
276
|
+
const ownerPid = process.pid;
|
|
81
277
|
let closing = false;
|
|
82
278
|
const devProcess = spawnDevScript(projectInfo);
|
|
279
|
+
if (devProcess) {
|
|
280
|
+
devProcess.on('exit', code => {
|
|
281
|
+
if (closing) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (code !== 0 && code !== null) {
|
|
285
|
+
console.warn(`npm run dev exited with code ${code}.`);
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
}
|
|
83
289
|
const startedDevProcess = Boolean(devProcess);
|
|
84
290
|
const staticRoot = (0, node_path_1.resolve)((0, node_path_1.dirname)(htmlPath));
|
|
85
|
-
const
|
|
291
|
+
const repoRoot = (0, node_path_1.resolve)(projectInfo.projectDir ?? (0, node_path_1.dirname)(htmlPath));
|
|
292
|
+
let mountId = '';
|
|
293
|
+
let appUrl = '';
|
|
294
|
+
try {
|
|
295
|
+
await ensureDevRouterRunning(port);
|
|
296
|
+
const registeredMount = await registerDevMount({
|
|
297
|
+
creatorUsername,
|
|
298
|
+
appType,
|
|
299
|
+
appName,
|
|
300
|
+
htmlPath,
|
|
301
|
+
staticRoot,
|
|
302
|
+
repoRoot,
|
|
303
|
+
ownerPid,
|
|
304
|
+
}, port);
|
|
305
|
+
mountId = registeredMount.mountId;
|
|
306
|
+
appUrl = registeredMount.appUrl;
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
if (devProcess && !devProcess.killed) {
|
|
310
|
+
devProcess.kill();
|
|
311
|
+
}
|
|
312
|
+
throw error;
|
|
313
|
+
}
|
|
314
|
+
const heartbeat = setInterval(() => {
|
|
315
|
+
heartbeatDevMount(mountId, ownerPid, port).catch(() => {
|
|
316
|
+
// Let the serving request fail naturally if the router disappears.
|
|
317
|
+
});
|
|
318
|
+
}, 2000);
|
|
319
|
+
return {
|
|
320
|
+
server: null,
|
|
321
|
+
devProcess,
|
|
322
|
+
startedDevProcess,
|
|
323
|
+
appUrl,
|
|
324
|
+
close: async () => {
|
|
325
|
+
if (closing)
|
|
326
|
+
return;
|
|
327
|
+
closing = true;
|
|
328
|
+
clearInterval(heartbeat);
|
|
329
|
+
try {
|
|
330
|
+
await unregisterDevMount(mountId, port);
|
|
331
|
+
}
|
|
332
|
+
finally {
|
|
333
|
+
if (devProcess && !devProcess.killed) {
|
|
334
|
+
devProcess.kill();
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
async function isDevServerAvailable(input, timeoutMs = 1000) {
|
|
341
|
+
const controller = new AbortController();
|
|
342
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
343
|
+
timeout.unref?.();
|
|
344
|
+
try {
|
|
345
|
+
await fetch(buildLocalDevAppUrl(input), {
|
|
346
|
+
method: 'GET',
|
|
347
|
+
signal: controller.signal,
|
|
348
|
+
});
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
finally {
|
|
355
|
+
clearTimeout(timeout);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
async function runDevRouterServer(port = Number(process.env.PLAYDROP_DEV_ROUTER_PORT) || exports.DEV_ROUTER_PORT) {
|
|
359
|
+
// eslint-disable-next-line complexity
|
|
360
|
+
const handleRequest = async (req, res) => {
|
|
86
361
|
const method = req.method || 'GET';
|
|
87
|
-
// Enable CORS for development
|
|
88
362
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
89
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
363
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
|
|
90
364
|
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');
|
|
91
365
|
res.setHeader('Access-Control-Allow-Private-Network', 'true');
|
|
92
366
|
if (method === 'OPTIONS') {
|
|
@@ -95,23 +369,134 @@ async function startDevServer(options) {
|
|
|
95
369
|
res.end();
|
|
96
370
|
return;
|
|
97
371
|
}
|
|
372
|
+
let pathname = '/';
|
|
373
|
+
try {
|
|
374
|
+
pathname = decodeURIComponent(new URL(req.url || '/', `http://${DEV_ROUTER_HOST}:${port}`).pathname);
|
|
375
|
+
}
|
|
376
|
+
catch {
|
|
377
|
+
respondWithText(res, method, 400, 'Invalid URL path');
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
if (pathname === `${CONTROL_PREFIX}/health` && method === 'GET') {
|
|
381
|
+
sendJson(res, 200, { status: 'ok' });
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
if (pathname === `${CONTROL_PREFIX}/mounts` && method === 'POST') {
|
|
385
|
+
cleanupStaleRouterMounts();
|
|
386
|
+
const body = await readJsonBody(req);
|
|
387
|
+
const creatorUsername = body.creatorUsername?.trim();
|
|
388
|
+
const appType = body.appType?.trim();
|
|
389
|
+
const appName = body.appName?.trim();
|
|
390
|
+
const htmlPath = body.htmlPath?.trim();
|
|
391
|
+
const staticRoot = body.staticRoot?.trim();
|
|
392
|
+
const repoRoot = body.repoRoot?.trim();
|
|
393
|
+
const ownerPid = Number(body.ownerPid);
|
|
394
|
+
if (!creatorUsername || !appType || !appName || !htmlPath || !staticRoot || !repoRoot || !Number.isInteger(ownerPid) || ownerPid <= 0) {
|
|
395
|
+
sendJson(res, 400, { error: 'invalid_mount_request' });
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
const key = buildMountKey(creatorUsername, appType, appName);
|
|
399
|
+
const existingMountId = routerMountIdsByKey.get(key);
|
|
400
|
+
if (existingMountId) {
|
|
401
|
+
const existingMount = routerMountsById.get(existingMountId);
|
|
402
|
+
if (existingMount && isPidAlive(existingMount.ownerPid)) {
|
|
403
|
+
if (existingMount.htmlPath !== htmlPath || existingMount.staticRoot !== staticRoot) {
|
|
404
|
+
sendJson(res, 409, {
|
|
405
|
+
error: 'mount_conflict',
|
|
406
|
+
creatorUsername,
|
|
407
|
+
appType,
|
|
408
|
+
appName,
|
|
409
|
+
ownerPid: existingMount.ownerPid,
|
|
410
|
+
repoRoot: existingMount.repoRoot,
|
|
411
|
+
htmlPath: existingMount.htmlPath,
|
|
412
|
+
});
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
existingMount.ownerPid = ownerPid;
|
|
416
|
+
existingMount.updatedAt = Date.now();
|
|
417
|
+
sendJson(res, 200, {
|
|
418
|
+
mountId: existingMount.id,
|
|
419
|
+
appUrl: buildLocalDevAppUrl({ creatorUsername, appType, appName, port }),
|
|
420
|
+
creatorUsername,
|
|
421
|
+
appType,
|
|
422
|
+
appName,
|
|
423
|
+
});
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
removeRouterMount(existingMountId);
|
|
427
|
+
}
|
|
428
|
+
const mount = {
|
|
429
|
+
id: (0, node_crypto_1.randomUUID)(),
|
|
430
|
+
key,
|
|
431
|
+
creatorUsername,
|
|
432
|
+
appType,
|
|
433
|
+
appName,
|
|
434
|
+
htmlPath,
|
|
435
|
+
staticRoot,
|
|
436
|
+
repoRoot,
|
|
437
|
+
ownerPid,
|
|
438
|
+
updatedAt: Date.now(),
|
|
439
|
+
};
|
|
440
|
+
routerMountsById.set(mount.id, mount);
|
|
441
|
+
routerMountIdsByKey.set(key, mount.id);
|
|
442
|
+
sendJson(res, 200, {
|
|
443
|
+
mountId: mount.id,
|
|
444
|
+
appUrl: buildLocalDevAppUrl({ creatorUsername, appType, appName, port }),
|
|
445
|
+
creatorUsername,
|
|
446
|
+
appType,
|
|
447
|
+
appName,
|
|
448
|
+
});
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
if (pathname.startsWith(`${CONTROL_PREFIX}/mounts/`) && pathname.endsWith('/heartbeat') && method === 'POST') {
|
|
452
|
+
cleanupStaleRouterMounts();
|
|
453
|
+
const mountId = pathname.slice(`${CONTROL_PREFIX}/mounts/`.length, -'/heartbeat'.length).replace(/\/$/, '');
|
|
454
|
+
const mount = routerMountsById.get(mountId);
|
|
455
|
+
if (!mount) {
|
|
456
|
+
sendJson(res, 404, { error: 'mount_not_found' });
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
const body = await readJsonBody(req);
|
|
460
|
+
const ownerPid = Number(body.ownerPid);
|
|
461
|
+
if (!Number.isInteger(ownerPid) || ownerPid <= 0) {
|
|
462
|
+
sendJson(res, 400, { error: 'invalid_mount_owner_pid' });
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
if (ownerPid !== mount.ownerPid) {
|
|
466
|
+
sendJson(res, 409, { error: 'mount_owner_pid_mismatch' });
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
mount.ownerPid = ownerPid;
|
|
470
|
+
mount.updatedAt = Date.now();
|
|
471
|
+
sendJson(res, 200, { ok: true });
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
if (pathname.startsWith(`${CONTROL_PREFIX}/mounts/`) && method === 'DELETE') {
|
|
475
|
+
const mountId = pathname.slice(`${CONTROL_PREFIX}/mounts/`.length);
|
|
476
|
+
removeRouterMount(mountId);
|
|
477
|
+
sendJson(res, 200, { ok: true });
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
98
480
|
if (method !== 'GET' && method !== 'HEAD') {
|
|
99
|
-
res.setHeader('Allow', 'GET, HEAD, OPTIONS');
|
|
481
|
+
res.setHeader('Allow', 'GET, HEAD, POST, DELETE, OPTIONS');
|
|
100
482
|
respondWithText(res, method, 405, 'Method not allowed');
|
|
101
483
|
return;
|
|
102
484
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
485
|
+
cleanupStaleRouterMounts();
|
|
486
|
+
const parsedMountPath = parseMountPath(pathname);
|
|
487
|
+
if (!parsedMountPath) {
|
|
488
|
+
respondWithText(res, method, 404, 'Not found');
|
|
489
|
+
return;
|
|
107
490
|
}
|
|
108
|
-
|
|
109
|
-
|
|
491
|
+
const mountId = routerMountIdsByKey.get(parsedMountPath.key);
|
|
492
|
+
const mount = mountId ? routerMountsById.get(mountId) : null;
|
|
493
|
+
if (!mount) {
|
|
494
|
+
respondWithText(res, method, 404, 'Not found');
|
|
110
495
|
return;
|
|
111
496
|
}
|
|
112
|
-
if (
|
|
497
|
+
if (parsedMountPath.assetPath === 'index.html') {
|
|
113
498
|
try {
|
|
114
|
-
const freshHtml = (0, node_fs_1.readFileSync)(htmlPath);
|
|
499
|
+
const freshHtml = (0, node_fs_1.readFileSync)(mount.htmlPath);
|
|
115
500
|
respondWithBuffer(res, method, 200, 'text/html; charset=utf-8', freshHtml);
|
|
116
501
|
}
|
|
117
502
|
catch (error) {
|
|
@@ -119,12 +504,7 @@ async function startDevServer(options) {
|
|
|
119
504
|
}
|
|
120
505
|
return;
|
|
121
506
|
}
|
|
122
|
-
|
|
123
|
-
respondWithText(res, method, 404, 'Not found');
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
const relativePath = pathname.slice('/apps/'.length);
|
|
127
|
-
const absolutePath = resolveStaticPath(staticRoot, relativePath);
|
|
507
|
+
const absolutePath = resolveStaticPath(mount.staticRoot, parsedMountPath.assetPath);
|
|
128
508
|
if (!absolutePath) {
|
|
129
509
|
respondWithText(res, method, 400, 'Invalid static asset path');
|
|
130
510
|
return;
|
|
@@ -141,61 +521,24 @@ async function startDevServer(options) {
|
|
|
141
521
|
catch {
|
|
142
522
|
respondWithText(res, method, 404, 'Not found');
|
|
143
523
|
}
|
|
524
|
+
};
|
|
525
|
+
const server = node_http_1.default.createServer((req, res) => {
|
|
526
|
+
void handleRequest(req, res);
|
|
144
527
|
});
|
|
145
|
-
await new Promise((
|
|
528
|
+
await new Promise((resolveListen, rejectListen) => {
|
|
146
529
|
const onError = (error) => {
|
|
147
530
|
server.off('listening', onListening);
|
|
148
|
-
|
|
531
|
+
rejectListen(error);
|
|
149
532
|
};
|
|
150
533
|
const onListening = () => {
|
|
151
534
|
server.off('error', onError);
|
|
152
|
-
|
|
535
|
+
resolveListen();
|
|
153
536
|
};
|
|
154
537
|
server.once('error', onError);
|
|
155
538
|
server.once('listening', onListening);
|
|
156
|
-
server.listen(port);
|
|
539
|
+
server.listen(port, DEV_ROUTER_HOST);
|
|
540
|
+
});
|
|
541
|
+
await new Promise(() => {
|
|
542
|
+
// Keep the router alive until the process exits.
|
|
157
543
|
});
|
|
158
|
-
const handle = {
|
|
159
|
-
server,
|
|
160
|
-
devProcess,
|
|
161
|
-
startedDevProcess,
|
|
162
|
-
appUrl: `http://localhost:${port}/apps/${appName}.html`,
|
|
163
|
-
close: async () => {
|
|
164
|
-
if (closing)
|
|
165
|
-
return;
|
|
166
|
-
closing = true;
|
|
167
|
-
await new Promise((resolveClose, rejectClose) => {
|
|
168
|
-
server.close(error => {
|
|
169
|
-
if (error) {
|
|
170
|
-
rejectClose(error);
|
|
171
|
-
}
|
|
172
|
-
else {
|
|
173
|
-
resolveClose();
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
});
|
|
177
|
-
if (devProcess && !devProcess.killed) {
|
|
178
|
-
devProcess.kill();
|
|
179
|
-
}
|
|
180
|
-
},
|
|
181
|
-
};
|
|
182
|
-
return handle;
|
|
183
|
-
}
|
|
184
|
-
async function isDevServerAvailable(appName, port, timeoutMs = 1000) {
|
|
185
|
-
const controller = new AbortController();
|
|
186
|
-
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
187
|
-
timeout.unref?.();
|
|
188
|
-
try {
|
|
189
|
-
await fetch(`http://127.0.0.1:${port}/apps/${encodeURIComponent(appName)}.html`, {
|
|
190
|
-
method: 'GET',
|
|
191
|
-
signal: controller.signal,
|
|
192
|
-
});
|
|
193
|
-
return true;
|
|
194
|
-
}
|
|
195
|
-
catch {
|
|
196
|
-
return false;
|
|
197
|
-
}
|
|
198
|
-
finally {
|
|
199
|
-
clearTimeout(timeout);
|
|
200
|
-
}
|
|
201
544
|
}
|
package/dist/commands/login.js
CHANGED
|
@@ -31,8 +31,16 @@ function storeLogin(env, data) {
|
|
|
31
31
|
process.exitCode = 1;
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
const username = typeof data.user?.username === 'string' ? data.user.username.trim() : '';
|
|
35
|
+
if (!username) {
|
|
36
|
+
(0, messages_1.printErrorWithHelp)('Login succeeded but the account username was missing.', [
|
|
37
|
+
'Please try again in a moment.',
|
|
38
|
+
'If the issue persists, contact the Playdrop team.',
|
|
39
|
+
], { command: 'login' });
|
|
40
|
+
process.exitCode = 1;
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
(0, config_1.saveAccountSession)({ username, env, token });
|
|
36
44
|
console.log(`Logged in as ${username} on ${env}.`);
|
|
37
45
|
console.log('Next: run "playdrop auth whoami" to confirm your session.');
|
|
38
46
|
}
|
package/dist/commands/logout.js
CHANGED
|
@@ -2,8 +2,30 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.logout = logout;
|
|
4
4
|
const config_1 = require("../config");
|
|
5
|
-
function logout() {
|
|
6
|
-
(0, config_1.
|
|
7
|
-
|
|
5
|
+
function logout(options = {}) {
|
|
6
|
+
const current = (0, config_1.getCurrentAccountSession)();
|
|
7
|
+
const username = options.username?.trim() || current?.username || '';
|
|
8
|
+
const env = options.env?.trim() || current?.env || '';
|
|
9
|
+
const existingSession = username ? (0, config_1.findAccountSession)(username, env || undefined) : null;
|
|
10
|
+
if (!existingSession) {
|
|
11
|
+
if (username && env) {
|
|
12
|
+
console.log(`No stored session found for ${username} on ${env}.`);
|
|
13
|
+
}
|
|
14
|
+
else if (username) {
|
|
15
|
+
console.log(`No stored session found for ${username}.`);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
console.log('No stored session found.');
|
|
19
|
+
}
|
|
20
|
+
console.log('Next: run "playdrop auth login" when you need a new session.');
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
(0, config_1.removeAccountSession)({ username, env });
|
|
24
|
+
if (username && env) {
|
|
25
|
+
console.log(`Logged out ${username} on ${env}.`);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
console.log('Logged out.');
|
|
29
|
+
}
|
|
8
30
|
console.log('Next: run "playdrop auth login" when you need a new session.');
|
|
9
31
|
}
|
|
@@ -3,10 +3,15 @@ type SearchOptions = {
|
|
|
3
3
|
appType?: string;
|
|
4
4
|
assetCategory?: string;
|
|
5
5
|
assetSubcategory?: string;
|
|
6
|
+
assetSpec?: string;
|
|
7
|
+
assetSpecOwner?: string;
|
|
8
|
+
assetSpecName?: string;
|
|
6
9
|
packContainsCategory?: string;
|
|
7
10
|
packContainsSubcategory?: string;
|
|
11
|
+
sort?: string;
|
|
8
12
|
limit?: string | number;
|
|
9
13
|
offset?: string | number;
|
|
14
|
+
tag?: string[];
|
|
10
15
|
json?: boolean;
|
|
11
16
|
};
|
|
12
17
|
export declare function search(query: string | undefined, options?: SearchOptions): Promise<void>;
|