@playdrop/playdrop-cli 0.17.4 → 0.17.14
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 +12 -0
- package/config/client-meta.json +4 -4
- package/dist/apps/build.d.ts +2 -1
- package/dist/apps/build.js +6 -2
- package/dist/apps/index.d.ts +2 -3
- package/dist/apps/index.js +17 -49
- package/dist/apps/loadCheck.d.ts +3 -1
- package/dist/apps/loadCheck.js +123 -64
- package/dist/apps/publishingRuntime.d.ts +18 -0
- package/dist/apps/publishingRuntime.js +111 -0
- package/dist/apps/runtimeSyntax.d.ts +7 -0
- package/dist/apps/runtimeSyntax.js +154 -0
- package/dist/apps/serverBuild.d.ts +3 -3
- package/dist/apps/serverBuild.js +8 -43
- package/dist/apps/serverSdk.d.ts +23 -0
- package/dist/apps/serverSdk.js +157 -0
- package/dist/apps/upload.d.ts +9 -3
- package/dist/apps/upload.js +71 -25
- package/dist/apps/validate.d.ts +2 -1
- package/dist/apps/validate.js +3 -1
- package/dist/assetSpecs.js +2 -0
- package/dist/commandContext.js +34 -10
- package/dist/commands/build.js +3 -1
- package/dist/commands/create.js +35 -16
- package/dist/commands/dev.js +11 -0
- package/dist/commands/devGameServer.d.ts +8 -0
- package/dist/commands/devGameServer.js +12 -9
- package/dist/commands/devServer.d.ts +1 -0
- package/dist/commands/devServer.js +21 -3
- package/dist/commands/source.d.ts +11 -3
- package/dist/commands/source.js +82 -14
- package/dist/commands/taskCaptureSession.d.ts +1 -1
- package/dist/commands/taskCaptureSession.js +1 -1
- package/dist/commands/upload-content.js +15 -12
- package/dist/commands/upload.d.ts +2 -9
- package/dist/commands/upload.js +5 -21
- package/dist/commands/validate.js +1 -1
- package/dist/commands/worker/classification.d.ts +1 -0
- package/dist/commands/worker/classification.js +57 -0
- package/dist/commands/worker/game-server-infra.d.ts +6 -0
- package/dist/commands/worker/game-server-infra.js +10 -0
- package/dist/commands/worker/game-source-git.d.ts +9 -1
- package/dist/commands/worker/game-source-git.js +65 -19
- package/dist/commands/worker/generation.d.ts +4 -0
- package/dist/commands/worker/generation.js +70 -7
- package/dist/commands/worker.d.ts +1 -1
- package/dist/commands/worker.js +57 -17
- package/dist/environment.d.ts +1 -0
- package/dist/environment.js +7 -0
- package/dist/index.js +10 -2
- package/dist/playwright.d.ts +6 -4
- package/dist/playwright.js +24 -9
- package/dist/proceduralValidation.d.ts +16 -0
- package/dist/proceduralValidation.js +227 -0
- package/dist/proceduralValidationBrowser.d.mts +3 -0
- package/dist/proceduralValidationBrowser.mjs +67 -0
- package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/apps.js +11 -1
- package/node_modules/@playdrop/api-client/dist/domains/chat.d.ts +6 -1
- package/node_modules/@playdrop/api-client/dist/domains/chat.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/chat.js +13 -1
- package/node_modules/@playdrop/config/client-meta.json +4 -4
- package/node_modules/@playdrop/procedural/dist/index.d.ts +201 -0
- package/node_modules/@playdrop/procedural/dist/index.d.ts.map +1 -0
- package/node_modules/@playdrop/procedural/dist/index.js +763 -0
- package/node_modules/@playdrop/procedural/package.json +36 -0
- package/node_modules/@playdrop/types/dist/agent-task-classification.d.ts +68 -0
- package/node_modules/@playdrop/types/dist/agent-task-classification.d.ts.map +1 -0
- package/node_modules/@playdrop/types/dist/agent-task-classification.js +96 -0
- package/node_modules/@playdrop/types/dist/api.d.ts +39 -4
- package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/chat.d.ts +7 -1
- package/node_modules/@playdrop/types/dist/chat.d.ts.map +1 -1
- 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 +2 -0
- package/node_modules/@playdrop/types/dist/social.d.ts +20 -0
- package/node_modules/@playdrop/types/dist/social.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/version.d.ts +2 -0
- package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
- package/package.json +7 -4
package/dist/apps/upload.js
CHANGED
|
@@ -8,7 +8,10 @@ const node_crypto_1 = require("node:crypto");
|
|
|
8
8
|
const node_fs_1 = require("node:fs");
|
|
9
9
|
const node_path_1 = require("node:path");
|
|
10
10
|
const http_1 = require("../http");
|
|
11
|
+
const proceduralValidation_1 = require("../proceduralValidation");
|
|
12
|
+
const game_source_git_1 = require("../commands/worker/game-source-git");
|
|
11
13
|
const serverBuild_1 = require("./serverBuild");
|
|
14
|
+
const publishingRuntime_1 = require("./publishingRuntime");
|
|
12
15
|
const loadCheck_1 = require("./loadCheck");
|
|
13
16
|
const EXTENSION_TO_MIME = {
|
|
14
17
|
".png": "image/png",
|
|
@@ -114,7 +117,8 @@ function createGenericFileFromPath(filePath) {
|
|
|
114
117
|
const name = (0, node_path_1.basename)(filePath);
|
|
115
118
|
return new File([content], name, { type: resolveMimeType(name) });
|
|
116
119
|
}
|
|
117
|
-
async function prepareOwnedAssetUpload(task) {
|
|
120
|
+
async function prepareOwnedAssetUpload(task, client) {
|
|
121
|
+
await (0, proceduralValidation_1.assertProceduralAssetIsValid)(task, { client, primaryContentType: resolveMimeType(task.filePaths.primary ?? "") });
|
|
118
122
|
const files = await Promise.all(Object.entries(task.filePaths).map(async ([role, filePath]) => {
|
|
119
123
|
const file = createGenericFileFromPath(filePath);
|
|
120
124
|
const buffer = await bufferFromFile(file);
|
|
@@ -174,15 +178,8 @@ function pushPreparedArtifact(task, currentTotal, file, maxBytes, code, label) {
|
|
|
174
178
|
const bytes = ensurePreparedArtifactSize(file, maxBytes, code, label);
|
|
175
179
|
return accumulateUploadBytes(task.name, currentTotal, bytes);
|
|
176
180
|
}
|
|
177
|
-
async function uploadSignedObject(client, creatorUsername, appName, sessionId, file) {
|
|
181
|
+
async function uploadSignedObject(client, creatorUsername, appName, sessionId, file, descriptor) {
|
|
178
182
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
179
|
-
const signed = await client.signAppUploadObjects(creatorUsername, appName, sessionId, {
|
|
180
|
-
objectIds: [file.id],
|
|
181
|
-
});
|
|
182
|
-
const descriptor = signed.uploads.find((entry) => entry.id === file.id);
|
|
183
|
-
if (!descriptor) {
|
|
184
|
-
throw new Error(`app_upload_signed_object_missing:${file.id}`);
|
|
185
|
-
}
|
|
186
183
|
const response = await fetch(descriptor.url, {
|
|
187
184
|
method: descriptor.method,
|
|
188
185
|
headers: descriptor.headers,
|
|
@@ -192,6 +189,14 @@ async function uploadSignedObject(client, creatorUsername, appName, sessionId, f
|
|
|
192
189
|
return;
|
|
193
190
|
}
|
|
194
191
|
if ((response.status === 401 || response.status === 403) && attempt === 0) {
|
|
192
|
+
const signed = await client.signAppUploadObjects(creatorUsername, appName, sessionId, {
|
|
193
|
+
objectIds: [file.id],
|
|
194
|
+
});
|
|
195
|
+
const refreshed = signed.uploads.find((entry) => entry.id === file.id);
|
|
196
|
+
// Signing omits objects whose stored bytes already match the manifest.
|
|
197
|
+
if (!refreshed)
|
|
198
|
+
return;
|
|
199
|
+
descriptor = refreshed;
|
|
195
200
|
continue;
|
|
196
201
|
}
|
|
197
202
|
const detail = (await response.text()).trim();
|
|
@@ -199,21 +204,30 @@ async function uploadSignedObject(client, creatorUsername, appName, sessionId, f
|
|
|
199
204
|
}
|
|
200
205
|
}
|
|
201
206
|
async function uploadSignedObjects(client, creatorUsername, appName, sessionId, files) {
|
|
202
|
-
let
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
207
|
+
for (let offset = 0; offset < files.length; offset += 100) {
|
|
208
|
+
const batch = files.slice(offset, offset + 100);
|
|
209
|
+
const signed = await client.signAppUploadObjects(creatorUsername, appName, sessionId, {
|
|
210
|
+
objectIds: batch.map((file) => file.id),
|
|
211
|
+
});
|
|
212
|
+
const descriptors = new Map(signed.uploads.map((entry) => [entry.id, entry]));
|
|
213
|
+
let cursor = 0;
|
|
214
|
+
const workers = Array.from({ length: Math.min(8, batch.length) }, async () => {
|
|
215
|
+
while (cursor < batch.length) {
|
|
216
|
+
const file = batch[cursor++];
|
|
217
|
+
const descriptor = descriptors.get(file.id);
|
|
218
|
+
// A missing descriptor means the API verified this object is stored.
|
|
219
|
+
if (descriptor)
|
|
220
|
+
await uploadSignedObject(client, creatorUsername, appName, sessionId, file, descriptor);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
await Promise.all(workers);
|
|
224
|
+
}
|
|
211
225
|
}
|
|
212
|
-
async function prepareAppUploadFiles(task, artifacts) {
|
|
226
|
+
async function prepareAppUploadFiles(task, artifacts, options = {}) {
|
|
213
227
|
let totalUploadBytes = 0;
|
|
214
228
|
const listing = task.listing;
|
|
215
229
|
const sessionFiles = [];
|
|
216
|
-
const ownedAssets = await Promise.all(task.ownedAssets.map((ownedAsset) => prepareOwnedAssetUpload(ownedAsset)));
|
|
230
|
+
const ownedAssets = await Promise.all(task.ownedAssets.map((ownedAsset) => prepareOwnedAssetUpload(ownedAsset, options.client)));
|
|
217
231
|
for (const ownedAsset of ownedAssets) {
|
|
218
232
|
for (const file of ownedAsset.files) {
|
|
219
233
|
totalUploadBytes = accumulateUploadBytes(task.name, totalUploadBytes, file.sizeBytes);
|
|
@@ -241,7 +255,7 @@ async function prepareAppUploadFiles(task, artifacts) {
|
|
|
241
255
|
sessionFiles.push(await buildPreparedSessionFile("SOURCE", "source.zip", artifacts.sourceFile));
|
|
242
256
|
}
|
|
243
257
|
}
|
|
244
|
-
const gameServer = await (0, serverBuild_1.buildGameServer)(task);
|
|
258
|
+
const gameServer = await (0, serverBuild_1.buildGameServer)(task, options);
|
|
245
259
|
if (gameServer) {
|
|
246
260
|
const bundle = createFileFromPath(gameServer.bundlePath, "application/javascript", serverBuild_1.GAME_SERVER_BUNDLE_MAX_BYTES, "server_bundle_too_large", "invalid_server_bundle", "Game server bundle");
|
|
247
261
|
const manifest = createFileFromPath(gameServer.manifestPath, "application/json", 16 * 1024, "server_manifest_too_large", "invalid_server_manifest", "Game server manifest");
|
|
@@ -289,7 +303,7 @@ async function prepareAppUploadFiles(task, artifacts) {
|
|
|
289
303
|
totalUploadBytes = pushPreparedFile(task.name, totalUploadBytes, prepared);
|
|
290
304
|
sessionFiles.push(await buildPreparedSessionFile("LISTING", `listing/videos/landscape/${index}.mp4`, prepared.file));
|
|
291
305
|
}
|
|
292
|
-
return { sessionFiles, ownedAssets };
|
|
306
|
+
return { sessionFiles, ownedAssets, gameServerBuild: gameServer };
|
|
293
307
|
}
|
|
294
308
|
async function uploadApp(client, task, artifacts, options) {
|
|
295
309
|
// Version is required - no legacy fallback
|
|
@@ -310,7 +324,23 @@ async function uploadAppVersion(client, task, artifacts, options) {
|
|
|
310
324
|
const achievements = task.achievements ?? [];
|
|
311
325
|
const leaderboards = task.leaderboards ?? [];
|
|
312
326
|
const listing = task.listing;
|
|
313
|
-
|
|
327
|
+
// Preparation builds the server. Freeze its inputs first, then include the
|
|
328
|
+
// generated server files in the snapshot checked before source publication.
|
|
329
|
+
const inputFingerprint = options?.prepareGitTransition
|
|
330
|
+
? await (0, game_source_git_1.fingerprintGameSourcePublication)(task.projectDir, false)
|
|
331
|
+
: null;
|
|
332
|
+
const assertPublicationUnchanged = async (expected, includeServerOutput = true) => {
|
|
333
|
+
if (expected !== null &&
|
|
334
|
+
await (0, game_source_git_1.fingerprintGameSourcePublication)(task.projectDir, includeServerOutput) !== expected) {
|
|
335
|
+
throw new Error("task_output_changed_during_upload: Source or build files changed after upload preparation started. " +
|
|
336
|
+
"The prepared upload does not match the project; finish edits and checks before retrying.");
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
const { sessionFiles: preparedSessionFiles, ownedAssets: preparedOwnedAssets, gameServerBuild } = await prepareAppUploadFiles(task, artifacts, { apiBase: options?.apiBase, client });
|
|
340
|
+
await assertPublicationUnchanged(inputFingerprint, false);
|
|
341
|
+
const publicationFingerprint = options?.prepareGitTransition
|
|
342
|
+
? await (0, game_source_git_1.fingerprintGameSourcePublication)(task.projectDir)
|
|
343
|
+
: null;
|
|
314
344
|
const preparedOwnedAssetFiles = preparedOwnedAssets.flatMap((ownedAsset) => ownedAsset.files.map((file) => ({
|
|
315
345
|
id: file.objectId,
|
|
316
346
|
role: "OWNED_ASSET",
|
|
@@ -424,13 +454,22 @@ async function uploadAppVersion(client, task, artifacts, options) {
|
|
|
424
454
|
})),
|
|
425
455
|
};
|
|
426
456
|
let sessionId = null;
|
|
457
|
+
let publishingRuntime = null;
|
|
427
458
|
try {
|
|
428
459
|
const initialized = await client.initializeAppUpload(creatorUsername, task.name, initializeRequest);
|
|
429
460
|
sessionId = initialized.session.id;
|
|
430
461
|
if (initialized.status !== "finalized") {
|
|
431
462
|
const missingIds = new Set(initialized.missingObjectIds);
|
|
432
463
|
await uploadSignedObjects(client, creatorUsername, task.name, sessionId, preparedObjects.filter((file) => missingIds.has(file.id)));
|
|
433
|
-
if (
|
|
464
|
+
if (gameServerBuild) {
|
|
465
|
+
publishingRuntime = await (0, publishingRuntime_1.startPublishingRuntime)({
|
|
466
|
+
client, task, creatorUsername, appId: initialized.session.appId, sessionId,
|
|
467
|
+
apiBase: options?.apiBase, gameServerBuild, gameServerDev: options?.gameServerDev,
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
// These checks are part of publishing, not optional Cloud-only policy.
|
|
471
|
+
// A declared playtest tape always runs, regardless of who built the game.
|
|
472
|
+
if (task.hostingMode !== "EXTERNAL" && !task.externalUrl && task.playtestTapes) {
|
|
434
473
|
await (0, loadCheck_1.runUploadedHostedPlaytestSmokeCheck)({
|
|
435
474
|
client,
|
|
436
475
|
creatorUsername,
|
|
@@ -440,9 +479,10 @@ async function uploadAppVersion(client, task, artifacts, options) {
|
|
|
440
479
|
token: options?.token ?? null,
|
|
441
480
|
currentUser: options?.user ?? null,
|
|
442
481
|
captureSession: options?.captureSession,
|
|
482
|
+
localDevPort: publishingRuntime?.localDevPort,
|
|
443
483
|
});
|
|
444
484
|
}
|
|
445
|
-
else if (task.hostingMode !== "EXTERNAL" && !task.externalUrl
|
|
485
|
+
else if (task.hostingMode !== "EXTERNAL" && !task.externalUrl) {
|
|
446
486
|
const loadCheck = await (0, loadCheck_1.runUploadedHostedLoadCheck)({
|
|
447
487
|
client,
|
|
448
488
|
creatorUsername,
|
|
@@ -452,6 +492,7 @@ async function uploadAppVersion(client, task, artifacts, options) {
|
|
|
452
492
|
token: options?.token ?? null,
|
|
453
493
|
currentUser: options?.user ?? null,
|
|
454
494
|
captureSession: options?.captureSession,
|
|
495
|
+
localDevPort: publishingRuntime?.localDevPort,
|
|
455
496
|
});
|
|
456
497
|
if (loadCheck.status !== "PASSED") {
|
|
457
498
|
throw new Error((0, loadCheck_1.formatHostedLoadCheckFailure)(task.name, loadCheck, "staged"));
|
|
@@ -467,6 +508,7 @@ async function uploadAppVersion(client, task, artifacts, options) {
|
|
|
467
508
|
token: options?.token ?? null,
|
|
468
509
|
currentUser: options?.user ?? null,
|
|
469
510
|
captureSession: options?.captureSession,
|
|
511
|
+
localDevPort: publishingRuntime?.localDevPort,
|
|
470
512
|
phase: "editor",
|
|
471
513
|
verifyEditorPhaseRoundTrip: true,
|
|
472
514
|
});
|
|
@@ -475,6 +517,7 @@ async function uploadAppVersion(client, task, artifacts, options) {
|
|
|
475
517
|
}
|
|
476
518
|
}
|
|
477
519
|
}
|
|
520
|
+
await assertPublicationUnchanged(publicationFingerprint);
|
|
478
521
|
const gitTransition = !options?.prepareGitTransition
|
|
479
522
|
? undefined
|
|
480
523
|
: await options.prepareGitTransition({
|
|
@@ -554,4 +597,7 @@ async function uploadAppVersion(client, task, artifacts, options) {
|
|
|
554
597
|
}
|
|
555
598
|
throw unknownError;
|
|
556
599
|
}
|
|
600
|
+
finally {
|
|
601
|
+
await publishingRuntime?.close();
|
|
602
|
+
}
|
|
557
603
|
}
|
package/dist/apps/validate.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AppTask } from "../catalogue";
|
|
2
|
+
import { type ServerSdkOptions } from "./serverSdk";
|
|
2
3
|
export type AppValidationWarningMode = "source" | "bundle";
|
|
3
4
|
export declare function collectAppValidationWarnings(task: AppTask, _mode?: AppValidationWarningMode): string[];
|
|
4
5
|
export declare function runFormatScript(task: AppTask): Promise<boolean>;
|
|
5
|
-
export declare function validateAppTask(task: AppTask): Promise<void>;
|
|
6
|
+
export declare function validateAppTask(task: AppTask, options?: ServerSdkOptions): Promise<void>;
|
package/dist/apps/validate.js
CHANGED
|
@@ -8,6 +8,7 @@ const node_path_1 = require("node:path");
|
|
|
8
8
|
const semver_1 = require("semver");
|
|
9
9
|
const clientInfo_1 = require("../clientInfo");
|
|
10
10
|
const build_1 = require("./build");
|
|
11
|
+
const serverSdk_1 = require("./serverSdk");
|
|
11
12
|
const projectValidateCache = new Map();
|
|
12
13
|
const projectFormatCache = new Map();
|
|
13
14
|
function ensureWithinProject(task) {
|
|
@@ -86,7 +87,7 @@ async function runFormatScript(task) {
|
|
|
86
87
|
await projectFormatCache.get(task.projectDir);
|
|
87
88
|
return true;
|
|
88
89
|
}
|
|
89
|
-
async function validateAppTask(task) {
|
|
90
|
+
async function validateAppTask(task, options = {}) {
|
|
90
91
|
if (!task.name) {
|
|
91
92
|
throw new Error("[apps][validate] App entry is missing a name.");
|
|
92
93
|
}
|
|
@@ -109,6 +110,7 @@ async function validateAppTask(task) {
|
|
|
109
110
|
throw new Error(`[apps][validate] ${task.name} is missing a required "validate" script in package.json. Define a validate command (for example "validate": "npm run build").`);
|
|
110
111
|
}
|
|
111
112
|
ensureWithinProject(task);
|
|
113
|
+
await (0, serverSdk_1.ensureGameServerSdk)(task, options);
|
|
112
114
|
if (!task.isTypescriptProject) {
|
|
113
115
|
if (!(0, node_fs_1.existsSync)(task.filePath)) {
|
|
114
116
|
throw new Error(`[apps][validate] Bundle not found for ${task.name}: ${task.relativeFile}. Run the app build locally before uploading.`);
|
package/dist/assetSpecs.js
CHANGED
package/dist/commandContext.js
CHANGED
|
@@ -186,13 +186,15 @@ async function loadWorkspaceAwareConfig(command, options) {
|
|
|
186
186
|
let workspaceAuth = null;
|
|
187
187
|
const workspacePath = options.workspacePath ?? (process.env.PLAYDROP_WORKER_CONTEXT === "1" ? process.cwd() : undefined);
|
|
188
188
|
try {
|
|
189
|
-
workspaceAuth = workspacePath
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
}
|
|
189
|
+
workspaceAuth = workspacePath && process.env.PLAYDROP_WORKER_CONTEXT === "1"
|
|
190
|
+
? (0, workspaceAuth_1.findWorkerTaskWorkspaceAuthConfig)(workspacePath)
|
|
191
|
+
: workspacePath ? (0, workspaceAuth_1.findWorkspaceAuthConfig)(workspacePath) : null;
|
|
193
192
|
}
|
|
194
193
|
catch (error) {
|
|
195
194
|
if (error instanceof workspaceAuth_1.WorkspaceAuthConfigError) {
|
|
195
|
+
if (process.env.PLAYDROP_WORKER_CONTEXT === "1") {
|
|
196
|
+
throw new Error(`worker_task_context_invalid:${error.filePath}: expected the assigned protocolVersion 2 task document with task credentials and metadata.playdrop.creatorUsername`);
|
|
197
|
+
}
|
|
196
198
|
(0, messages_1.printErrorWithHelp)(`Invalid .playdrop.json at ${error.filePath}.`, ["The file must be valid JSON.", 'It must include a non-empty "ownerUsername" string.'], { command });
|
|
197
199
|
process.exitCode = 1;
|
|
198
200
|
return null;
|
|
@@ -421,12 +423,28 @@ async function withEnvironment(command, actionLabel, callback, options = {}) {
|
|
|
421
423
|
await callback(ctx);
|
|
422
424
|
}
|
|
423
425
|
async function withPublicEnvironment(command, callback, options = {}) {
|
|
426
|
+
if (process.env.PLAYDROP_WORKER_CONTEXT === "1") {
|
|
427
|
+
const resolved = await resolveAuthenticatedEnvironment(command, "Cloud content access", {
|
|
428
|
+
workspacePath: process.cwd(),
|
|
429
|
+
env: options.env,
|
|
430
|
+
});
|
|
431
|
+
if (!resolved)
|
|
432
|
+
return;
|
|
433
|
+
const task = resolved.workspaceAuth?.config;
|
|
434
|
+
if (!task?.taskToken || !task.taskId || !task.taskAttempt) {
|
|
435
|
+
throw new Error("worker_task_context_missing: run this command inside the assigned Cloud task workspace");
|
|
436
|
+
}
|
|
437
|
+
if (task.env !== resolved.envConfig.name) {
|
|
438
|
+
throw new Error(`worker_task_environment_mismatch: this task uses ${task.env}`);
|
|
439
|
+
}
|
|
440
|
+
// Task credentials are checked by the server and cannot be refreshed through account login.
|
|
441
|
+
await callback(buildContext(resolved.cfg, resolved.envConfig, resolved.account, resolved.workspaceAuth));
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
424
444
|
const cfg = (0, config_1.loadConfig)();
|
|
425
|
-
const currentAccount = (0, config_1.getCurrentAccountSession)(cfg);
|
|
426
|
-
const requestedEnv = options.env
|
|
427
|
-
const account =
|
|
428
|
-
? (0, config_1.findAccountSession)(currentAccount.username, requestedEnv, cfg)
|
|
429
|
-
: currentAccount;
|
|
445
|
+
const currentAccount = (0, config_1.getCurrentAccountSession)(cfg) ?? buildLegacyAccountSession(cfg);
|
|
446
|
+
const requestedEnv = normalizeRequestedEnv(options.env);
|
|
447
|
+
const account = resolveRequestedCurrentAccount(cfg, currentAccount, requestedEnv);
|
|
430
448
|
const envConfig = resolveConfiguredEnvironment(requestedEnv ?? account?.env ?? cfg.env, command, {
|
|
431
449
|
requireAuth: false,
|
|
432
450
|
allowDefaultPublicEnv: true,
|
|
@@ -434,5 +452,11 @@ async function withPublicEnvironment(command, callback, options = {}) {
|
|
|
434
452
|
if (!envConfig) {
|
|
435
453
|
return;
|
|
436
454
|
}
|
|
437
|
-
|
|
455
|
+
if (currentAccount && !account) {
|
|
456
|
+
(0, messages_1.printErrorWithHelp)(`${currentAccount.username || "This CLI session"} is not logged in on ${requestedEnv}.`, [`Run "playdrop auth login --env ${requestedEnv}" to authenticate in that environment.`], { command });
|
|
457
|
+
process.exitCode = 1;
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
const refreshedAccount = account ? await refreshStoredAccountSession({ account, envConfig }) : null;
|
|
461
|
+
await callback(buildContext(refreshedAccount ? (0, config_1.loadConfig)() : cfg, envConfig, refreshedAccount));
|
|
438
462
|
}
|
package/dist/commands/build.js
CHANGED
|
@@ -5,6 +5,7 @@ const taskSelection_1 = require("../taskSelection");
|
|
|
5
5
|
const taskUtils_1 = require("../taskUtils");
|
|
6
6
|
const uploadLog_1 = require("../uploadLog");
|
|
7
7
|
const apps_1 = require("../apps");
|
|
8
|
+
const serverBuild_1 = require("../apps/serverBuild");
|
|
8
9
|
async function build(pathOrName) {
|
|
9
10
|
const selection = (0, taskSelection_1.selectTasks)(pathOrName);
|
|
10
11
|
if (selection.errors.length > 0) {
|
|
@@ -26,7 +27,8 @@ async function build(pathOrName) {
|
|
|
26
27
|
if (task.kind === "app") {
|
|
27
28
|
await (0, apps_1.validateAppTask)(task);
|
|
28
29
|
const artifacts = await (0, apps_1.buildApp)(task);
|
|
29
|
-
|
|
30
|
+
const server = await (0, serverBuild_1.buildGameServer)(task);
|
|
31
|
+
detail = `bundleHash=${artifacts.bundleHash}${server ? ` serverHash=${server.manifest.sha256}` : ""}`;
|
|
30
32
|
}
|
|
31
33
|
else {
|
|
32
34
|
throw new Error(`project build does not support ${task.kind} "${entityId}".`);
|
package/dist/commands/create.js
CHANGED
|
@@ -17,7 +17,7 @@ const node_process_1 = require("node:process");
|
|
|
17
17
|
const promises_1 = __importDefault(require("node:readline/promises"));
|
|
18
18
|
const catalogue_1 = require("../catalogue");
|
|
19
19
|
const catalogue_utils_1 = require("../catalogue-utils");
|
|
20
|
-
const
|
|
20
|
+
const apiClient_1 = require("../apiClient");
|
|
21
21
|
const commandContext_1 = require("../commandContext");
|
|
22
22
|
const http_1 = require("../http");
|
|
23
23
|
const listingPreflight_1 = require("../listingPreflight");
|
|
@@ -30,26 +30,45 @@ const taskCaptureSession_1 = require("./taskCaptureSession");
|
|
|
30
30
|
const CATALOGUE_FILENAME = "catalogue.json";
|
|
31
31
|
const LEGACY_CATALOGUE_VERSION_KEY = ["schema", "Version"].join("");
|
|
32
32
|
const ALLOWED_CATALOGUE_TOP_LEVEL_KEYS = new Set(["apps", "assetSpecs", "assets", "assetPacks"]);
|
|
33
|
-
function buildArchiveDownloadHeaders(url,
|
|
34
|
-
const targetUrl = new URL(url, apiBase);
|
|
35
|
-
const apiOrigin = new URL(apiBase).origin;
|
|
33
|
+
function buildArchiveDownloadHeaders(url, ctx) {
|
|
34
|
+
const targetUrl = new URL(url, ctx.envConfig.apiBase);
|
|
35
|
+
const apiOrigin = new URL(ctx.envConfig.apiBase).origin;
|
|
36
36
|
if (targetUrl.origin !== apiOrigin) {
|
|
37
37
|
return undefined;
|
|
38
38
|
}
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
const workspaceAuth = ctx.workspaceAuth?.config;
|
|
40
|
+
const headers = new Headers((0, apiClient_1.createCliClientHeaders)({
|
|
41
|
+
onBehalfCreatorUsername: workspaceAuth?.ownerUsername,
|
|
42
|
+
agentTaskToken: workspaceAuth?.taskToken,
|
|
43
|
+
agentTaskId: workspaceAuth?.taskId,
|
|
44
|
+
agentTaskAttempt: workspaceAuth?.taskAttempt,
|
|
45
|
+
}));
|
|
46
|
+
if (ctx.token.trim().length > 0) {
|
|
47
|
+
headers.set("authorization", `Bearer ${ctx.token}`);
|
|
42
48
|
}
|
|
43
49
|
return headers;
|
|
44
50
|
}
|
|
45
|
-
async function downloadArchive(url,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
async function downloadArchive(url, ctx) {
|
|
52
|
+
let downloadUrl = url;
|
|
53
|
+
for (let redirects = 0; redirects <= 20; redirects += 1) {
|
|
54
|
+
const headers = buildArchiveDownloadHeaders(downloadUrl, ctx);
|
|
55
|
+
// Recompute headers on each hop: Fetch forwards custom task headers across origins.
|
|
56
|
+
const response = await fetch(downloadUrl, { redirect: "manual", ...(headers ? { headers } : {}) });
|
|
57
|
+
if ([301, 302, 303, 307, 308].includes(response.status)) {
|
|
58
|
+
const location = response.headers.get("location");
|
|
59
|
+
await response.body?.cancel();
|
|
60
|
+
if (!location)
|
|
61
|
+
throw new Error("Project archive redirect is missing its location.");
|
|
62
|
+
downloadUrl = new URL(location, downloadUrl).toString();
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
throw new Error(`Failed to download project archive (${response.status})`);
|
|
67
|
+
}
|
|
68
|
+
const buffer = await response.arrayBuffer();
|
|
69
|
+
return new Uint8Array(buffer);
|
|
70
|
+
}
|
|
71
|
+
throw new Error("Too many project archive redirects.");
|
|
53
72
|
}
|
|
54
73
|
function resolveArchiveDownloadUrl(url, apiBase) {
|
|
55
74
|
return new URL(url, apiBase).toString();
|
|
@@ -1043,7 +1062,7 @@ async function create(name, options = {}) {
|
|
|
1043
1062
|
}
|
|
1044
1063
|
}
|
|
1045
1064
|
try {
|
|
1046
|
-
const archiveBuffer = await downloadArchive(resolveArchiveDownloadUrl(scaffold.archiveUrl, ctx.envConfig.apiBase), ctx
|
|
1065
|
+
const archiveBuffer = await downloadArchive(resolveArchiveDownloadUrl(scaffold.archiveUrl, ctx.envConfig.apiBase), ctx);
|
|
1047
1066
|
const extraction = extractProjectArchive(archiveBuffer, projectDir);
|
|
1048
1067
|
writeCreatedProjectWorkspaceAuth(projectDir, ctx);
|
|
1049
1068
|
const extractedCatalogue = (0, node_path_1.resolve)(projectDir, CATALOGUE_FILENAME);
|
package/dist/commands/dev.js
CHANGED
|
@@ -19,6 +19,7 @@ const devServer_1 = require("./devServer");
|
|
|
19
19
|
const tweakStaleness_1 = require("../tweakStaleness");
|
|
20
20
|
const devShared_1 = require("./devShared");
|
|
21
21
|
const devGameServer_1 = require("./devGameServer");
|
|
22
|
+
const taskCaptureSession_1 = require("./taskCaptureSession");
|
|
22
23
|
function formatDevRuntimeAssetManifestFailure(error) {
|
|
23
24
|
const rawMessage = error instanceof Error ? error.message : String(error ?? "unknown_error");
|
|
24
25
|
if (rawMessage.startsWith("dev_runtime_asset_ref_invalid:")) {
|
|
@@ -265,6 +266,15 @@ async function dev(targetArg, appOption, devOptions = {}) {
|
|
|
265
266
|
throw error;
|
|
266
267
|
}
|
|
267
268
|
}
|
|
269
|
+
let captureSession;
|
|
270
|
+
try {
|
|
271
|
+
captureSession = (0, taskCaptureSession_1.resolveTaskCaptureSession)(workspaceAuth, registeredApp);
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
(0, messages_1.printErrorWithHelp)(error instanceof Error ? error.message : "Worker capture session is invalid.", ["Run this command from the active worker task workspace."], { command: "project dev" });
|
|
275
|
+
process.exitCode = 1;
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
268
278
|
const projectInfo = (0, devShared_1.findProjectInfo)(filePath);
|
|
269
279
|
const devScriptAvailable = Boolean(projectInfo.projectDir && projectInfo.packageJson && typeof projectInfo.packageJson.scripts?.dev === "string");
|
|
270
280
|
const taskLookup = (0, catalogue_1.findAppTaskByFile)(filePath);
|
|
@@ -407,6 +417,7 @@ async function dev(targetArg, appOption, devOptions = {}) {
|
|
|
407
417
|
appType: appTypeSlug,
|
|
408
418
|
appName,
|
|
409
419
|
localDevPort: devRouterPort === devServer_1.DEV_ROUTER_PORT ? null : devRouterPort,
|
|
420
|
+
captureSession,
|
|
410
421
|
}), devAuthSelection);
|
|
411
422
|
console.log("\nTest your app at:");
|
|
412
423
|
console.log(` ${iframeUrl}`);
|
|
@@ -7,10 +7,18 @@ export interface DevGameServerHandle {
|
|
|
7
7
|
port: number;
|
|
8
8
|
build: GameServerBuild;
|
|
9
9
|
}
|
|
10
|
+
export type GameServerDevServices = {
|
|
11
|
+
redisUrl: string;
|
|
12
|
+
mongoUrl: string;
|
|
13
|
+
redisNamespace: string;
|
|
14
|
+
};
|
|
10
15
|
export declare function isValidDevRedisNamespace(namespace: string): boolean;
|
|
16
|
+
export declare function reserveLocalGameServerPort(): Promise<number>;
|
|
11
17
|
export declare function startDevGameServer(input: {
|
|
12
18
|
task: AppTask;
|
|
13
19
|
credential: DevGameServerCredentialResponse;
|
|
14
20
|
platformApiUrl: string;
|
|
15
21
|
publicAddress: string;
|
|
22
|
+
gameServerDev?: GameServerDevServices;
|
|
23
|
+
gameServerBuild?: GameServerBuild;
|
|
16
24
|
}): Promise<DevGameServerHandle | null>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.isValidDevRedisNamespace = isValidDevRedisNamespace;
|
|
4
|
+
exports.reserveLocalGameServerPort = reserveLocalGameServerPort;
|
|
4
5
|
exports.startDevGameServer = startDevGameServer;
|
|
5
6
|
const node_child_process_1 = require("node:child_process");
|
|
6
7
|
const node_module_1 = require("node:module");
|
|
@@ -11,7 +12,7 @@ const serverBuild_1 = require("../apps/serverBuild");
|
|
|
11
12
|
const EXPECTED_REDIS_VERSION = "7.4.5";
|
|
12
13
|
const EXPECTED_MONGODB_VERSION = "8.0.26";
|
|
13
14
|
function isValidDevRedisNamespace(namespace) {
|
|
14
|
-
return /^colyseus:[a-z0-9_.-]+:[a-z0-9_.-]
|
|
15
|
+
return /^colyseus:[a-z0-9_.-]+:[a-z0-9_.-]+:(?:upload-[a-zA-Z0-9-]{1,100}:)?$/u.test(namespace);
|
|
15
16
|
}
|
|
16
17
|
function requireServiceUrl(name) {
|
|
17
18
|
const value = process.env[name]?.trim() ?? "";
|
|
@@ -126,7 +127,7 @@ async function provisionScopedMongoUrl(mongoUrl, appId) {
|
|
|
126
127
|
await mongo.close().catch(() => undefined);
|
|
127
128
|
}
|
|
128
129
|
}
|
|
129
|
-
async function
|
|
130
|
+
async function reserveLocalGameServerPort() {
|
|
130
131
|
const server = (0, node_net_1.createServer)();
|
|
131
132
|
await new Promise((resolveListen, rejectListen) => {
|
|
132
133
|
server.once("error", rejectListen);
|
|
@@ -184,25 +185,27 @@ async function waitForReady(child, timeoutMs = 10000) {
|
|
|
184
185
|
async function startDevGameServer(input) {
|
|
185
186
|
if (!input.task.server)
|
|
186
187
|
return null;
|
|
187
|
-
const redisUrl = requireServiceUrl("PLAYDROP_DEV_REDIS_URL");
|
|
188
|
-
const baseMongoUrl = requireServiceUrl("PLAYDROP_DEV_MONGO_URL");
|
|
188
|
+
const redisUrl = input.gameServerDev ? input.gameServerDev.redisUrl : requireServiceUrl("PLAYDROP_DEV_REDIS_URL");
|
|
189
|
+
const baseMongoUrl = input.gameServerDev ? input.gameServerDev.mongoUrl : requireServiceUrl("PLAYDROP_DEV_MONGO_URL");
|
|
189
190
|
const workerScoped = process.env.PLAYDROP_WORKER_CONTEXT === "1";
|
|
190
|
-
const redisNamespace =
|
|
191
|
-
|
|
191
|
+
const redisNamespace = input.gameServerDev
|
|
192
|
+
? input.gameServerDev.redisNamespace
|
|
193
|
+
: process.env.PLAYDROP_DEV_REDIS_NAMESPACE?.trim() ||
|
|
194
|
+
`colyseus:${input.credential.environment}:${input.credential.deploymentId}:`;
|
|
192
195
|
if (!isValidDevRedisNamespace(redisNamespace)) {
|
|
193
196
|
throw new Error("playdrop_dev_redis_namespace_invalid");
|
|
194
197
|
}
|
|
195
|
-
const scopedMongoUrl = workerScoped
|
|
198
|
+
const scopedMongoUrl = input.gameServerDev || workerScoped
|
|
196
199
|
? baseMongoUrl
|
|
197
200
|
: await provisionScopedMongoUrl(baseMongoUrl, input.credential.appId);
|
|
198
201
|
await Promise.all([
|
|
199
202
|
verifyRedis(redisUrl, redisNamespace),
|
|
200
203
|
verifyScopedMongoUrl(scopedMongoUrl),
|
|
201
204
|
]);
|
|
202
|
-
const build = await (0, serverBuild_1.buildGameServer)(input.task);
|
|
205
|
+
const build = input.gameServerBuild ?? await (0, serverBuild_1.buildGameServer)(input.task, { apiBase: input.platformApiUrl });
|
|
203
206
|
if (!build)
|
|
204
207
|
throw new Error("server_build_missing");
|
|
205
|
-
const port = await
|
|
208
|
+
const port = await reserveLocalGameServerPort();
|
|
206
209
|
const runtimeEntry = resolveDevRuntimeEntry(input.task);
|
|
207
210
|
const child = (0, node_child_process_1.spawn)(process.execPath, ["--max-old-space-size=192", runtimeEntry], {
|
|
208
211
|
cwd: input.task.projectDir,
|
|
@@ -37,6 +37,7 @@ export declare function buildLocalDevAppUrl(input: {
|
|
|
37
37
|
}): string;
|
|
38
38
|
export declare function terminateChildProcessTree(child: ChildProcess | null, timeoutMs?: number): Promise<void>;
|
|
39
39
|
export declare function ensureDevRouterRunning(port?: number): Promise<void>;
|
|
40
|
+
export declare function startOwnedDevRouter(port: number): Promise<ChildProcess>;
|
|
40
41
|
export declare function resolveDevRouterWorkingDirectory(cliEntrypoint: string): string;
|
|
41
42
|
export declare function updateMountedDevRuntimeAssetManifest(input: {
|
|
42
43
|
creatorUsername: string;
|
|
@@ -8,6 +8,7 @@ exports.parseMountConflictError = parseMountConflictError;
|
|
|
8
8
|
exports.buildLocalDevAppUrl = buildLocalDevAppUrl;
|
|
9
9
|
exports.terminateChildProcessTree = terminateChildProcessTree;
|
|
10
10
|
exports.ensureDevRouterRunning = ensureDevRouterRunning;
|
|
11
|
+
exports.startOwnedDevRouter = startOwnedDevRouter;
|
|
11
12
|
exports.resolveDevRouterWorkingDirectory = resolveDevRouterWorkingDirectory;
|
|
12
13
|
exports.updateMountedDevRuntimeAssetManifest = updateMountedDevRuntimeAssetManifest;
|
|
13
14
|
exports.startDevServer = startDevServer;
|
|
@@ -25,7 +26,11 @@ exports.DEV_ROUTER_PORT = 8888;
|
|
|
25
26
|
const DEV_ROUTER_HOST = "127.0.0.1";
|
|
26
27
|
const CONTROL_PREFIX = "/_playdrop";
|
|
27
28
|
const DEV_ROUTER_PROTOCOL_VERSION = 4;
|
|
28
|
-
const
|
|
29
|
+
const APPROVED_PLAYDROP_ROUTER_ORIGINS = new Set([
|
|
30
|
+
"https://playdrop.ai",
|
|
31
|
+
"https://www.playdrop.ai",
|
|
32
|
+
"https://assets.playdrop.ai",
|
|
33
|
+
]);
|
|
29
34
|
const CONTENT_TYPE_BY_EXTENSION = {
|
|
30
35
|
".html": "text/html; charset=utf-8",
|
|
31
36
|
".js": "application/javascript; charset=utf-8",
|
|
@@ -58,7 +63,7 @@ function resolveApprovedDevRouterOrigin(originHeader) {
|
|
|
58
63
|
catch {
|
|
59
64
|
return null;
|
|
60
65
|
}
|
|
61
|
-
if (
|
|
66
|
+
if (APPROVED_PLAYDROP_ROUTER_ORIGINS.has(parsedOrigin.origin)) {
|
|
62
67
|
return parsedOrigin.origin;
|
|
63
68
|
}
|
|
64
69
|
const isLocalWebOrigin = (parsedOrigin.protocol === "http:" || parsedOrigin.protocol === "https:") &&
|
|
@@ -267,6 +272,10 @@ function proxyRoomsRequest(req, res, target) {
|
|
|
267
272
|
}, (upstream) => {
|
|
268
273
|
res.statusCode = upstream.statusCode ?? 502;
|
|
269
274
|
for (const [name, value] of Object.entries(upstream.headers)) {
|
|
275
|
+
// The loopback router owns its exact origin policy, including credentialed
|
|
276
|
+
// Colyseus requests. An upstream wildcard must not replace that policy.
|
|
277
|
+
if (name.startsWith("access-control-") || name === "vary")
|
|
278
|
+
continue;
|
|
270
279
|
if (value !== undefined)
|
|
271
280
|
res.setHeader(name, value);
|
|
272
281
|
}
|
|
@@ -524,6 +533,13 @@ async function ensureDevRouterRunning(port = exports.DEV_ROUTER_PORT) {
|
|
|
524
533
|
}
|
|
525
534
|
throw new Error(`dev_router_incompatible:port_${port}`);
|
|
526
535
|
}
|
|
536
|
+
await startOwnedDevRouter(port);
|
|
537
|
+
}
|
|
538
|
+
// Publication checks retain this handle and stop only their own router.
|
|
539
|
+
// Ordinary project dev intentionally keeps its shared router running.
|
|
540
|
+
async function startOwnedDevRouter(port) {
|
|
541
|
+
if (await fetchRouterHealth(port))
|
|
542
|
+
throw new Error(`publishing_dev_router_port_in_use:${port}`);
|
|
527
543
|
const cliEntrypoint = getCliEntrypointPath();
|
|
528
544
|
const childEnv = { ...process.env };
|
|
529
545
|
delete childEnv.PLAYDROP_WORKER_CONTEXT;
|
|
@@ -541,10 +557,11 @@ async function ensureDevRouterRunning(port = exports.DEV_ROUTER_PORT) {
|
|
|
541
557
|
while (Date.now() < deadline) {
|
|
542
558
|
const health = await fetchRouterHealth(port, 250);
|
|
543
559
|
if (health && isCompatibleRouterHealth(health)) {
|
|
544
|
-
return;
|
|
560
|
+
return child;
|
|
545
561
|
}
|
|
546
562
|
await new Promise((resolveTimeout) => setTimeout(resolveTimeout, 150));
|
|
547
563
|
}
|
|
564
|
+
await terminateChildProcessTree(child);
|
|
548
565
|
throw new Error("dev_router_start_failed");
|
|
549
566
|
}
|
|
550
567
|
function resolveDevRouterWorkingDirectory(cliEntrypoint) {
|
|
@@ -703,6 +720,7 @@ function createDevRouterServer(initialPort = exports.DEV_ROUTER_PORT) {
|
|
|
703
720
|
const allowedOrigin = resolveApprovedDevRouterOrigin(typeof req.headers.origin === "string" ? req.headers.origin : undefined);
|
|
704
721
|
if (allowedOrigin) {
|
|
705
722
|
res.setHeader("Access-Control-Allow-Origin", allowedOrigin);
|
|
723
|
+
res.setHeader("Access-Control-Allow-Credentials", "true");
|
|
706
724
|
}
|
|
707
725
|
res.setHeader("Vary", "Origin");
|
|
708
726
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
type
|
|
1
|
+
import { type GameSourceLockEntry } from "@playdrop/game-source-git";
|
|
2
|
+
import { type WorkerReadGameSourceFilesResponse } from "@playdrop/types";
|
|
3
|
+
type SourceKind = "app" | "asset" | "pack";
|
|
2
4
|
type ExactSourceRef = {
|
|
3
5
|
kind: SourceKind;
|
|
4
6
|
creator: string;
|
|
@@ -8,11 +10,17 @@ type ExactSourceRef = {
|
|
|
8
10
|
export declare function parseExactSourceRef(raw: string, expectedKind: SourceKind): ExactSourceRef;
|
|
9
11
|
export declare function prepareDestination(path: string): string;
|
|
10
12
|
export declare function extractArchive(bytes: Uint8Array, destination: string): number;
|
|
11
|
-
export declare function
|
|
13
|
+
export declare function extractGitSource(bytes: Uint8Array, destination: string, options: {
|
|
14
|
+
version: string;
|
|
15
|
+
commitSha: string;
|
|
16
|
+
workerHomeDir: string;
|
|
17
|
+
readFiles: (files: GameSourceLockEntry[]) => Promise<WorkerReadGameSourceFilesResponse>;
|
|
18
|
+
}): Promise<number>;
|
|
19
|
+
export declare function showExactContentDetail(kind: Exclude<SourceKind, "app">, rawRef: string, options?: {
|
|
12
20
|
env?: string;
|
|
13
21
|
json?: boolean;
|
|
14
22
|
}): Promise<void>;
|
|
15
|
-
export declare function downloadContentSource(kind: SourceKind, rawRef: string, directory
|
|
23
|
+
export declare function downloadContentSource(kind: SourceKind, rawRef: string, directory?: string, options?: {
|
|
16
24
|
env?: string;
|
|
17
25
|
json?: boolean;
|
|
18
26
|
}): Promise<void>;
|