@playdrop/playdrop-cli 0.17.5 → 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 +2 -2
- 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/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 +13 -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/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 +2 -0
- package/dist/commands/worker/generation.js +35 -5
- 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 +2 -2
- 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/api.d.ts +37 -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/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/commands/source.js
CHANGED
|
@@ -3,25 +3,33 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.parseExactSourceRef = parseExactSourceRef;
|
|
4
4
|
exports.prepareDestination = prepareDestination;
|
|
5
5
|
exports.extractArchive = extractArchive;
|
|
6
|
+
exports.extractGitSource = extractGitSource;
|
|
6
7
|
exports.showExactContentDetail = showExactContentDetail;
|
|
7
8
|
exports.downloadContentSource = downloadContentSource;
|
|
8
9
|
const node_fs_1 = require("node:fs");
|
|
10
|
+
const promises_1 = require("node:fs/promises");
|
|
11
|
+
const node_os_1 = require("node:os");
|
|
9
12
|
const node_path_1 = require("node:path");
|
|
13
|
+
const game_source_git_1 = require("@playdrop/game-source-git");
|
|
14
|
+
const types_1 = require("@playdrop/types");
|
|
10
15
|
const fflate_1 = require("fflate");
|
|
11
16
|
const commandContext_1 = require("../commandContext");
|
|
12
17
|
const errors_1 = require("../errors");
|
|
13
18
|
const output_1 = require("../output");
|
|
19
|
+
const game_source_git_2 = require("./worker/game-source-git");
|
|
14
20
|
function parseExactSourceRef(raw, expectedKind) {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
: /^pack:([a-z0-9]+(?:-[a-z0-9]+)*)\/([a-z0-9]+(?:-[a-z0-9]+)*)@(\d+\.\d+\.\d+)$/;
|
|
18
|
-
const match = pattern.exec(raw.trim());
|
|
19
|
-
if (!match) {
|
|
21
|
+
const ref = (0, types_1.parseContentVersionRef)(raw);
|
|
22
|
+
if (!ref || ref.kind !== expectedKind) {
|
|
20
23
|
throw new Error(expectedKind === "asset"
|
|
21
24
|
? "invalid_exact_asset_ref: expected asset:creator/name@rN"
|
|
22
|
-
:
|
|
25
|
+
: `invalid_exact_${expectedKind}_ref: expected ${expectedKind}:creator/name@X.Y.Z`);
|
|
23
26
|
}
|
|
24
|
-
return {
|
|
27
|
+
return {
|
|
28
|
+
kind: expectedKind,
|
|
29
|
+
creator: ref.creatorUsername,
|
|
30
|
+
name: ref.name,
|
|
31
|
+
version: ref.kind === "asset" ? `r${ref.revision}` : ref.version,
|
|
32
|
+
};
|
|
25
33
|
}
|
|
26
34
|
function prepareDestination(path) {
|
|
27
35
|
const destination = (0, node_path_1.resolve)(path);
|
|
@@ -58,6 +66,36 @@ function extractArchive(bytes, destination) {
|
|
|
58
66
|
}
|
|
59
67
|
return fileCount;
|
|
60
68
|
}
|
|
69
|
+
async function extractGitSource(bytes, destination, options) {
|
|
70
|
+
if (!/^[0-9a-f]{40}$/.test(options.commitSha))
|
|
71
|
+
throw new Error("app_source_commit_missing");
|
|
72
|
+
const temporary = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "playdrop-app-source-"));
|
|
73
|
+
const git = (0, game_source_git_1.createGitRunner)();
|
|
74
|
+
try {
|
|
75
|
+
const bundle = (0, node_path_1.join)(temporary, "source.bundle");
|
|
76
|
+
await (0, promises_1.writeFile)(bundle, bytes);
|
|
77
|
+
await git.run(["init", "--initial-branch=main", destination]);
|
|
78
|
+
await git.run(["fetch", "--update-head-ok", bundle, "+refs/*:refs/*"], destination);
|
|
79
|
+
const tag = `refs/tags/v${options.version}`;
|
|
80
|
+
const taggedCommit = (await git.run(["rev-parse", "--verify", `${tag}^{commit}`], destination).catch(() => {
|
|
81
|
+
throw new Error(`app_source_version_missing:${options.version}: Git source is missing ${tag}`);
|
|
82
|
+
})).toString("utf8").trim();
|
|
83
|
+
if (taggedCommit !== options.commitSha)
|
|
84
|
+
throw new Error(`app_source_version_commit_mismatch:${options.version}`);
|
|
85
|
+
await git.run(["checkout", "--detach", options.commitSha], destination);
|
|
86
|
+
const lock = await (0, game_source_git_2.materializeCurrentLock)({
|
|
87
|
+
projectDir: destination,
|
|
88
|
+
workerHomeDir: options.workerHomeDir,
|
|
89
|
+
readFiles: options.readFiles,
|
|
90
|
+
cloneFile: promises_1.copyFile,
|
|
91
|
+
});
|
|
92
|
+
const trackedFiles = (await git.run(["ls-files", "-z"], destination)).toString("utf8").split("\0").filter(Boolean);
|
|
93
|
+
return new Set([...trackedFiles, ...Object.keys(lock.files)]).size;
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
await (0, promises_1.rm)(temporary, { recursive: true, force: true });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
61
99
|
async function showExactContentDetail(kind, rawRef, options = {}) {
|
|
62
100
|
const command = `${kind} detail`;
|
|
63
101
|
await (0, commandContext_1.withPublicEnvironment)(command, async ({ client, envConfig }) => {
|
|
@@ -114,20 +152,48 @@ async function showExactContentDetail(kind, rawRef, options = {}) {
|
|
|
114
152
|
}
|
|
115
153
|
async function downloadContentSource(kind, rawRef, directory, options = {}) {
|
|
116
154
|
const command = `${kind} source`;
|
|
117
|
-
await (0, commandContext_1.withPublicEnvironment)(command, async ({ client }) => {
|
|
155
|
+
await (0, commandContext_1.withPublicEnvironment)(command, async ({ client, workspaceAuth }) => {
|
|
118
156
|
try {
|
|
119
157
|
const ref = parseExactSourceRef(rawRef, kind);
|
|
120
|
-
const result = kind === "
|
|
121
|
-
? await client.
|
|
122
|
-
:
|
|
123
|
-
|
|
124
|
-
|
|
158
|
+
const result = kind === "app"
|
|
159
|
+
? await client.downloadAppSource(ref.creator, ref.name, ref.version)
|
|
160
|
+
: kind === "asset"
|
|
161
|
+
? await client.downloadAssetSource(ref.creator, ref.name, ref.version)
|
|
162
|
+
: await client.downloadAssetPackSource(ref.creator, ref.name, ref.version);
|
|
163
|
+
const bytes = new Uint8Array(await result.blob.arrayBuffer());
|
|
164
|
+
if (bytes.length !== result.metadata.sizeBytes) {
|
|
165
|
+
throw new Error(`source_download_incomplete:${rawRef}: expected ${result.metadata.sizeBytes} bytes, received ${bytes.length}`);
|
|
166
|
+
}
|
|
167
|
+
const metadata = result.metadata;
|
|
168
|
+
const isGit = "format" in metadata && metadata.format === "git-bundle";
|
|
169
|
+
const taskId = workspaceAuth?.config.taskId;
|
|
170
|
+
const workerHomeDir = process.env.PLAYDROP_WORKER_HOME?.trim();
|
|
171
|
+
if (isGit) {
|
|
172
|
+
if (process.env.PLAYDROP_WORKER_CONTEXT !== "1" || !taskId) {
|
|
173
|
+
throw new Error("app_source_task_required: Cloud Git material downloads require an active Cloud task");
|
|
174
|
+
}
|
|
175
|
+
if (!workerHomeDir || !(0, node_path_1.isAbsolute)(workerHomeDir)) {
|
|
176
|
+
throw new Error("app_source_worker_home_missing: the Cloud worker must provide its absolute PLAYDROP_WORKER_HOME cache directory");
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const destination = directory === undefined && kind === "app"
|
|
180
|
+
? await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "playdrop-app-source-"))
|
|
181
|
+
: prepareDestination(directory ?? ref.name);
|
|
182
|
+
const fileCount = isGit
|
|
183
|
+
? await extractGitSource(bytes, destination, {
|
|
184
|
+
version: ref.version,
|
|
185
|
+
commitSha: "commitSha" in metadata && typeof metadata.commitSha === "string" ? metadata.commitSha : "",
|
|
186
|
+
workerHomeDir: workerHomeDir,
|
|
187
|
+
readFiles: (files) => client.workerReadGameSourceFiles(taskId, { files }),
|
|
188
|
+
})
|
|
189
|
+
: extractArchive(bytes, destination);
|
|
125
190
|
const output = {
|
|
126
191
|
ref: rawRef,
|
|
127
192
|
destination,
|
|
128
193
|
filename: result.metadata.filename,
|
|
129
194
|
sizeBytes: result.metadata.sizeBytes,
|
|
130
195
|
files: fileCount,
|
|
196
|
+
format: isGit ? "git-bundle" : "zip",
|
|
131
197
|
};
|
|
132
198
|
if (options.json)
|
|
133
199
|
(0, output_1.printJson)(output);
|
|
@@ -135,7 +201,9 @@ async function downloadContentSource(kind, rawRef, directory, options = {}) {
|
|
|
135
201
|
console.log(`Downloaded ${rawRef} source to ${destination} (${fileCount} files).`);
|
|
136
202
|
}
|
|
137
203
|
catch (error) {
|
|
138
|
-
if (!(0, errors_1.handleCommandFailure)(error, command, "Source download"
|
|
204
|
+
if (!(0, errors_1.handleCommandFailure)(error, command, "Source download", {
|
|
205
|
+
apiMessage: (apiError) => ({ problem: `Source download rejected for ${rawRef}: ${apiError.code ?? apiError.message} (HTTP ${apiError.status}).` }),
|
|
206
|
+
}))
|
|
139
207
|
throw error;
|
|
140
208
|
}
|
|
141
209
|
}, { env: options.env });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { AppResponse } from "@playdrop/types";
|
|
2
2
|
import type { TaskCaptureSession } from "../appUrls";
|
|
3
3
|
import type { ResolvedWorkspaceAuthConfig } from "../workspaceAuth";
|
|
4
|
-
export declare function resolveTaskCaptureSession(workspaceAuth: ResolvedWorkspaceAuthConfig | null, app: AppResponse): TaskCaptureSession | null;
|
|
4
|
+
export declare function resolveTaskCaptureSession(workspaceAuth: ResolvedWorkspaceAuthConfig | null, app: AppResponse | null): TaskCaptureSession | null;
|
|
@@ -10,7 +10,7 @@ function resolveTaskCaptureSession(workspaceAuth, app) {
|
|
|
10
10
|
if (!Number.isInteger(taskId) || Number(taskId) <= 0 || !taskToken) {
|
|
11
11
|
throw new Error("worker_capture_session_incomplete");
|
|
12
12
|
}
|
|
13
|
-
if (!Number.isInteger(app.id) || Number(app.id) <= 0) {
|
|
13
|
+
if (!app || !Number.isInteger(app.id) || Number(app.id) <= 0) {
|
|
14
14
|
throw new Error("registered_app_id_missing");
|
|
15
15
|
}
|
|
16
16
|
return {
|
|
@@ -13,6 +13,7 @@ const node_crypto_1 = require("node:crypto");
|
|
|
13
13
|
const node_fs_1 = require("node:fs");
|
|
14
14
|
const node_path_1 = require("node:path");
|
|
15
15
|
const assetSpecs_1 = require("../assetSpecs");
|
|
16
|
+
const proceduralValidation_1 = require("../proceduralValidation");
|
|
16
17
|
const EXTENSION_TO_FORMAT = {
|
|
17
18
|
".png": "PNG",
|
|
18
19
|
".jpg": "JPEG",
|
|
@@ -292,7 +293,8 @@ function validateAssetSubcategoryCompatibility(task, format) {
|
|
|
292
293
|
}
|
|
293
294
|
return subcategory;
|
|
294
295
|
}
|
|
295
|
-
async function prepareAssetTaskUploadData(task) {
|
|
296
|
+
async function prepareAssetTaskUploadData(task, client) {
|
|
297
|
+
await (0, proceduralValidation_1.assertProceduralAssetIsValid)(task, { client, primaryContentType: resolveMimeType(task.filePaths.primary ?? "") });
|
|
296
298
|
if ("assetSpec" in task && typeof task.assetSpec === "string" && task.assetSpec.trim().length > 0) {
|
|
297
299
|
const uploadFiles = Object.entries(task.filePaths)
|
|
298
300
|
.sort(([leftRole], [rightRole]) => leftRole.localeCompare(rightRole))
|
|
@@ -331,7 +333,7 @@ async function prepareAssetTaskUploadData(task) {
|
|
|
331
333
|
};
|
|
332
334
|
}
|
|
333
335
|
async function uploadAssetTask(client, task, sourceAppVersionId, creatorUsername, options) {
|
|
334
|
-
const prepared = await prepareAssetTaskUploadData(task);
|
|
336
|
+
const prepared = await prepareAssetTaskUploadData(task, client);
|
|
335
337
|
const visibility = task.visibility;
|
|
336
338
|
const files = prepared.uploadFiles.map((entry) => ({
|
|
337
339
|
file: entry.file,
|
|
@@ -485,20 +487,19 @@ function describeError(error) {
|
|
|
485
487
|
}
|
|
486
488
|
return String(error);
|
|
487
489
|
}
|
|
488
|
-
async function prepareLocalPackAssets(task, mutationCreatorUsername, localAssetTasks, uploadKeyByAssetKey) {
|
|
489
|
-
const preparedLocalAssets =
|
|
490
|
-
for (const localAssetTask of localAssetTasks) {
|
|
490
|
+
async function prepareLocalPackAssets(client, task, mutationCreatorUsername, localAssetTasks, uploadKeyByAssetKey) {
|
|
491
|
+
const preparedLocalAssets = await Promise.all(localAssetTasks.map(async (localAssetTask) => {
|
|
491
492
|
const assetKey = buildAssetKey(mutationCreatorUsername, localAssetTask.name);
|
|
492
493
|
const uploadKey = uploadKeyByAssetKey.get(assetKey);
|
|
493
494
|
if (!uploadKey) {
|
|
494
495
|
throw new Error(`Asset pack "${task.name}@${task.version}" is missing upload key for local asset "${localAssetTask.name}".`);
|
|
495
496
|
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
}
|
|
501
|
-
return preparedLocalAssets;
|
|
497
|
+
return [uploadKey, {
|
|
498
|
+
task: localAssetTask,
|
|
499
|
+
prepared: await prepareAssetTaskUploadData(localAssetTask, client),
|
|
500
|
+
}];
|
|
501
|
+
}));
|
|
502
|
+
return new Map(preparedLocalAssets);
|
|
502
503
|
}
|
|
503
504
|
function buildLocalAssetManifestEntries(preparedLocalAssets) {
|
|
504
505
|
const localAssets = [];
|
|
@@ -656,7 +657,7 @@ function buildUploadedPackInfo(creatorUsername, task, versionNodeId, versionAsse
|
|
|
656
657
|
}
|
|
657
658
|
async function uploadAssetPackTask(client, task, creatorUsername, uploadedAssets, localAssetTasks, uploadKeyByAssetKey, targetCreatorUsername, options) {
|
|
658
659
|
const mutationCreatorUsername = resolveMutationCreatorUsername(task, creatorUsername, targetCreatorUsername);
|
|
659
|
-
const preparedLocalAssets = await prepareLocalPackAssets(task, mutationCreatorUsername, localAssetTasks, uploadKeyByAssetKey);
|
|
660
|
+
const preparedLocalAssets = await prepareLocalPackAssets(client, task, mutationCreatorUsername, localAssetTasks, uploadKeyByAssetKey);
|
|
660
661
|
const ownedAssets = buildLocalAssetManifestEntries(preparedLocalAssets);
|
|
661
662
|
const { existingAssets, members } = await buildPackMembersAndExistingAssets(client, task, mutationCreatorUsername, uploadedAssets, uploadKeyByAssetKey);
|
|
662
663
|
const listingUploads = buildPackListingUploads(task);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ApiClient } from "@playdrop/api-client";
|
|
2
2
|
import type { AgentExecutionTarget, AppSurface, FinalizeAppUploadRequest, UserResponse } from "@playdrop/types";
|
|
3
|
+
import type { GameServerDevServices } from "./devGameServer";
|
|
3
4
|
import { type AppTask } from "../catalogue";
|
|
4
5
|
export type UploadCommandOptions = {
|
|
5
6
|
env?: string;
|
|
@@ -28,6 +29,7 @@ export type WorkerAppPublishInput = {
|
|
|
28
29
|
token: string;
|
|
29
30
|
user: UserResponse;
|
|
30
31
|
devRouterPort?: number;
|
|
32
|
+
gameServerDev?: GameServerDevServices;
|
|
31
33
|
deterministicE2EFixture?: boolean;
|
|
32
34
|
prepareGitTransition?: (input: {
|
|
33
35
|
appId: number;
|
|
@@ -35,15 +37,6 @@ export type WorkerAppPublishInput = {
|
|
|
35
37
|
version: string;
|
|
36
38
|
}) => Promise<NonNullable<FinalizeAppUploadRequest["gitTransition"]>>;
|
|
37
39
|
};
|
|
38
|
-
export declare function resolveWorkerHostedValidationPolicy(input: {
|
|
39
|
-
kind: WorkerAppPublishInput["kind"];
|
|
40
|
-
deterministicE2EFixture?: boolean;
|
|
41
|
-
}): {
|
|
42
|
-
runLocalLoadCheck: boolean;
|
|
43
|
-
runStagedUploadLoadCheck: boolean;
|
|
44
|
-
playtestSmokeCheckRequired: boolean;
|
|
45
|
-
skipReview: boolean;
|
|
46
|
-
};
|
|
47
40
|
export type WorkerAppPublishResult = {
|
|
48
41
|
appId: number;
|
|
49
42
|
appVersionId: number;
|
package/dist/commands/upload.js
CHANGED
|
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.resolveUploadOwner = resolveUploadOwner;
|
|
4
4
|
exports.resolveDefaultUploadCreator = resolveDefaultUploadCreator;
|
|
5
5
|
exports.upload = upload;
|
|
6
|
-
exports.resolveWorkerHostedValidationPolicy = resolveWorkerHostedValidationPolicy;
|
|
7
6
|
exports.assertAgentPrimarySurfaceContract = assertAgentPrimarySurfaceContract;
|
|
8
7
|
exports.assertAgentNewGamePlaytestContract = assertAgentNewGamePlaytestContract;
|
|
9
8
|
exports.assertAgentNewGameListingIdentity = assertAgentNewGameListingIdentity;
|
|
@@ -1240,15 +1239,6 @@ async function upload(pathOrName, options) {
|
|
|
1240
1239
|
(0, uploadLog_1.printTaskSummary)(results, warnings, { action: "upload", environment: env });
|
|
1241
1240
|
(0, uploadLog_1.printPublishedAppNextSteps)(results);
|
|
1242
1241
|
}
|
|
1243
|
-
function resolveWorkerHostedValidationPolicy(input) {
|
|
1244
|
-
const browserChecksRequired = input.deterministicE2EFixture !== true;
|
|
1245
|
-
return {
|
|
1246
|
-
runLocalLoadCheck: browserChecksRequired,
|
|
1247
|
-
runStagedUploadLoadCheck: browserChecksRequired,
|
|
1248
|
-
playtestSmokeCheckRequired: browserChecksRequired && input.kind === "NEW_GAME",
|
|
1249
|
-
skipReview: !browserChecksRequired,
|
|
1250
|
-
};
|
|
1251
|
-
}
|
|
1252
1242
|
function assertAgentPrimarySurfaceContract(task, kind) {
|
|
1253
1243
|
if (!task.primarySurface) {
|
|
1254
1244
|
throw new Error(`agent_task_primary_surface_required: ${kind} catalogue.json must declare primarySurface.`);
|
|
@@ -1365,9 +1355,9 @@ async function prepareWorkerAppProject(input) {
|
|
|
1365
1355
|
throw new Error(`agent_task_remix_ref_mismatch:${catalogueRemixRef}:${expectedRemixRef}`);
|
|
1366
1356
|
}
|
|
1367
1357
|
}
|
|
1368
|
-
await (0, apps_1.validateAppTask)(task);
|
|
1358
|
+
await (0, apps_1.validateAppTask)(task, { apiBase: input.apiBase });
|
|
1369
1359
|
const isExternal = task.hostingMode === "EXTERNAL" || Boolean(task.externalUrl);
|
|
1370
|
-
const artifacts = isExternal ? null : await (0, apps_1.buildApp)(task, { includeSourceArchive: false });
|
|
1360
|
+
const artifacts = isExternal ? null : await (0, apps_1.buildApp)(task, { includeSourceArchive: false, apiBase: input.apiBase });
|
|
1371
1361
|
await assertWorkerAppLocalUploadPreflight({
|
|
1372
1362
|
task,
|
|
1373
1363
|
kind: input.kind,
|
|
@@ -1384,7 +1374,7 @@ async function prepareWorkerAppProject(input) {
|
|
|
1384
1374
|
// succeed before the agent can complete the task.
|
|
1385
1375
|
async function preflightWorkerAppProject(input) {
|
|
1386
1376
|
const prepared = await prepareWorkerAppProject(input);
|
|
1387
|
-
await (0, apps_1.prepareAppUploadFiles)(prepared.task, prepared.artifacts);
|
|
1377
|
+
await (0, apps_1.prepareAppUploadFiles)(prepared.task, prepared.artifacts, { apiBase: input.apiBase, client: input.client });
|
|
1388
1378
|
return {
|
|
1389
1379
|
appName: prepared.task.name,
|
|
1390
1380
|
creatorUsername: prepared.creatorUsername,
|
|
@@ -1409,7 +1399,6 @@ async function publishWorkerAppProject(input) {
|
|
|
1409
1399
|
displayName: fixtureDisplayName,
|
|
1410
1400
|
});
|
|
1411
1401
|
}
|
|
1412
|
-
const validationPolicy = resolveWorkerHostedValidationPolicy(input);
|
|
1413
1402
|
const { app } = await (0, registration_1.ensureRegisteredHostedAppShell)({
|
|
1414
1403
|
client: input.client,
|
|
1415
1404
|
creatorUsername,
|
|
@@ -1434,13 +1423,11 @@ async function publishWorkerAppProject(input) {
|
|
|
1434
1423
|
webBase: normalizePortalBase(input.webBase ?? undefined),
|
|
1435
1424
|
token: input.token,
|
|
1436
1425
|
user: input.user,
|
|
1437
|
-
runLocalLoadCheck: validationPolicy.runLocalLoadCheck,
|
|
1438
1426
|
devRouterPort: input.devRouterPort,
|
|
1439
|
-
|
|
1427
|
+
gameServerDev: input.gameServerDev,
|
|
1440
1428
|
ensureRegisteredAppShell: true,
|
|
1441
1429
|
agentTaskId: input.taskId,
|
|
1442
|
-
|
|
1443
|
-
skipReview: validationPolicy.skipReview,
|
|
1430
|
+
skipReview: input.deterministicE2EFixture === true,
|
|
1444
1431
|
captureSession,
|
|
1445
1432
|
prepareGitTransition: input.prepareGitTransition,
|
|
1446
1433
|
...(artifacts ? { artifacts } : {}),
|
|
@@ -1528,12 +1515,9 @@ async function publishStaticHtmlProject(input) {
|
|
|
1528
1515
|
webBase: normalizePortalBase(input.webBase ?? undefined),
|
|
1529
1516
|
token: input.token,
|
|
1530
1517
|
user: input.user,
|
|
1531
|
-
runLocalLoadCheck: true,
|
|
1532
1518
|
devRouterPort: input.devRouterPort,
|
|
1533
|
-
runStagedUploadLoadCheck: true,
|
|
1534
1519
|
ensureRegisteredAppShell: true,
|
|
1535
1520
|
agentTaskId: input.taskId,
|
|
1536
|
-
playtestSmokeCheckRequired: false,
|
|
1537
1521
|
skipReview: input.skipReview,
|
|
1538
1522
|
captureSession,
|
|
1539
1523
|
prepareGitTransition: input.prepareGitTransition,
|
|
@@ -215,7 +215,7 @@ async function validate(pathOrName, options = {}) {
|
|
|
215
215
|
kind: workerContext.kind,
|
|
216
216
|
});
|
|
217
217
|
}
|
|
218
|
-
await (0, apps_1.validateAppTask)(task);
|
|
218
|
+
await (0, apps_1.validateAppTask)(task, { environment: resolvedValidationContext?.envConfig });
|
|
219
219
|
(0, apps_1.collectAppValidationWarnings)(task, "source").forEach((warning) => warnings.add(warning));
|
|
220
220
|
}
|
|
221
221
|
else if (task.kind === "asset-spec") {
|
|
@@ -78,3 +78,9 @@ export declare function provisionWorkerGameServerDevLease(input: {
|
|
|
78
78
|
mongoAdminUrl: string;
|
|
79
79
|
requireRedisAcl?: boolean;
|
|
80
80
|
}): Promise<WorkerGameServerDevLease>;
|
|
81
|
+
export declare function provisionPublishingGameServerDevLease(input: {
|
|
82
|
+
sessionId: string;
|
|
83
|
+
environment: string;
|
|
84
|
+
redisAdminUrl: string;
|
|
85
|
+
mongoAdminUrl: string;
|
|
86
|
+
}): Promise<WorkerGameServerDevLease>;
|
|
@@ -13,6 +13,7 @@ exports.setupManagedWorkerGameServerInfrastructure = setupManagedWorkerGameServe
|
|
|
13
13
|
exports.probeManagedWorkerGameServerInfrastructure = probeManagedWorkerGameServerInfrastructure;
|
|
14
14
|
exports.cleanupStaleWorkerGameServerDevResources = cleanupStaleWorkerGameServerDevResources;
|
|
15
15
|
exports.provisionWorkerGameServerDevLease = provisionWorkerGameServerDevLease;
|
|
16
|
+
exports.provisionPublishingGameServerDevLease = provisionPublishingGameServerDevLease;
|
|
16
17
|
const node_child_process_1 = require("node:child_process");
|
|
17
18
|
const node_crypto_1 = require("node:crypto");
|
|
18
19
|
const node_fs_1 = require("node:fs");
|
|
@@ -507,6 +508,15 @@ async function cleanupStaleWorkerGameServerDevResources(input) {
|
|
|
507
508
|
}
|
|
508
509
|
async function provisionWorkerGameServerDevLease(input) {
|
|
509
510
|
const scope = normalizeTaskScope(input.taskId, input.attempt);
|
|
511
|
+
return provisionScopedGameServerDevLease({ ...input, scope });
|
|
512
|
+
}
|
|
513
|
+
async function provisionPublishingGameServerDevLease(input) {
|
|
514
|
+
if (!/^[a-zA-Z0-9-]{1,100}$/u.test(input.sessionId))
|
|
515
|
+
throw new Error("publishing_session_id_invalid");
|
|
516
|
+
return provisionScopedGameServerDevLease({ ...input, scope: `upload-${input.sessionId}`, requireRedisAcl: true });
|
|
517
|
+
}
|
|
518
|
+
async function provisionScopedGameServerDevLease(input) {
|
|
519
|
+
const scope = input.scope;
|
|
510
520
|
const environment = input.environment.trim().toLowerCase().replace(/[^a-z0-9_-]/gu, "-");
|
|
511
521
|
if (!environment)
|
|
512
522
|
throw new Error("worker_game_server_dev_environment_invalid");
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ApiClient } from "@playdrop/api-client";
|
|
2
|
-
import type
|
|
2
|
+
import { type GameSourceLockEntry, type GameSourceLockV1, type MaterializeLockOptions } from "@playdrop/game-source-git";
|
|
3
|
+
import type { WorkerGameSourceAssignmentV1, WorkerGameTaskAssignment, WorkerReadGameSourceFilesResponse } from "@playdrop/types";
|
|
3
4
|
export declare function emitGameSourceWorkerEvent(event: string, fields: Record<string, string | number | boolean | null>, write?: (line: string) => void): void;
|
|
4
5
|
export type GameSourceWorkspace = {
|
|
5
6
|
assignment: WorkerGameSourceAssignmentV1;
|
|
@@ -50,6 +51,12 @@ export type FinalizedGameSource = ReconciledGameSource & {
|
|
|
50
51
|
commitSha: string;
|
|
51
52
|
}>;
|
|
52
53
|
};
|
|
54
|
+
export declare function materializeCurrentLock(input: {
|
|
55
|
+
projectDir: string;
|
|
56
|
+
workerHomeDir: string;
|
|
57
|
+
readFiles: (files: GameSourceLockEntry[]) => Promise<WorkerReadGameSourceFilesResponse>;
|
|
58
|
+
cloneFile?: MaterializeLockOptions["cloneFile"];
|
|
59
|
+
}): Promise<GameSourceLockV1>;
|
|
53
60
|
export declare function setupGameSourceWorkspace(input: {
|
|
54
61
|
assignment: WorkerGameTaskAssignment;
|
|
55
62
|
workspaceDir: string;
|
|
@@ -58,6 +65,7 @@ export declare function setupGameSourceWorkspace(input: {
|
|
|
58
65
|
workerKey: string;
|
|
59
66
|
leaseToken: string;
|
|
60
67
|
}): Promise<GameSourceWorkspace>;
|
|
68
|
+
export declare function fingerprintGameSourcePublication(projectDir: string, includeServerOutput?: boolean): Promise<string>;
|
|
61
69
|
export declare function checkpointFailedGameSourceWorkspace(input: {
|
|
62
70
|
workspace: GameSourceWorkspace;
|
|
63
71
|
taskId: number;
|
|
@@ -4,7 +4,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.emitGameSourceWorkerEvent = emitGameSourceWorkerEvent;
|
|
7
|
+
exports.materializeCurrentLock = materializeCurrentLock;
|
|
7
8
|
exports.setupGameSourceWorkspace = setupGameSourceWorkspace;
|
|
9
|
+
exports.fingerprintGameSourcePublication = fingerprintGameSourcePublication;
|
|
8
10
|
exports.checkpointFailedGameSourceWorkspace = checkpointFailedGameSourceWorkspace;
|
|
9
11
|
exports.reconcileGameSourceWorkspace = reconcileGameSourceWorkspace;
|
|
10
12
|
exports.finalizeGameSourceRepository = finalizeGameSourceRepository;
|
|
@@ -222,36 +224,51 @@ async function reattachRetainedWorktree(input) {
|
|
|
222
224
|
}
|
|
223
225
|
}
|
|
224
226
|
async function materializeCurrentLock(input) {
|
|
225
|
-
const
|
|
227
|
+
const lockPath = node_path_1.default.join(input.projectDir, game_source_git_1.GAME_SOURCE_LOCK_PATH);
|
|
228
|
+
const lock = (0, game_source_git_1.parseGameSourceLock)(await (0, promises_1.readFile)(lockPath).catch((error) => {
|
|
229
|
+
if (error.code === "ENOENT")
|
|
230
|
+
throw new Error(`game_source_material_lock_missing:${lockPath}`);
|
|
231
|
+
throw error;
|
|
232
|
+
}));
|
|
226
233
|
const requestedByKey = new Map();
|
|
227
234
|
for (const entry of Object.values(lock.files)) {
|
|
228
235
|
const existing = requestedByKey.get(entry.key);
|
|
229
236
|
if (existing && (existing.sha256 !== entry.sha256 || existing.bytes !== entry.bytes)) {
|
|
230
237
|
throw new Error("game_source_material_lock_conflict");
|
|
231
238
|
}
|
|
232
|
-
requestedByKey.set(entry.key,
|
|
239
|
+
requestedByKey.set(entry.key, entry);
|
|
233
240
|
}
|
|
234
241
|
const requested = [...requestedByKey.values()];
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
242
|
+
const urlByKey = new Map();
|
|
243
|
+
for (let offset = 0; offset < requested.length; offset += 100) {
|
|
244
|
+
const batch = requested.slice(offset, offset + 100);
|
|
245
|
+
const response = await input.readFiles(batch);
|
|
246
|
+
for (const expected of batch) {
|
|
247
|
+
const file = response.files.find((entry) => entry.key === expected.key);
|
|
248
|
+
if (!file || file.sha256 !== expected.sha256 || file.bytes !== expected.bytes || !file.downloadUrl) {
|
|
249
|
+
throw new Error(`game_source_material_url_missing:${expected.key}`);
|
|
250
|
+
}
|
|
251
|
+
urlByKey.set(expected.key, file.downloadUrl);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
243
254
|
await (0, game_source_git_1.materializeGameSourceLock)({
|
|
244
255
|
rootDir: input.projectDir,
|
|
245
256
|
blobCacheDir: blobCacheRoot(input.workerHomeDir),
|
|
246
257
|
lock,
|
|
258
|
+
cloneFile: input.cloneFile,
|
|
247
259
|
download: async (key) => {
|
|
248
|
-
const entry = Object.values(lock.files).find((candidate) => candidate.key === key);
|
|
249
260
|
const url = urlByKey.get(key);
|
|
250
|
-
if (!
|
|
251
|
-
throw new Error(
|
|
252
|
-
|
|
261
|
+
if (!url)
|
|
262
|
+
throw new Error(`game_source_material_url_missing:${key}`);
|
|
263
|
+
try {
|
|
264
|
+
return await downloadBuffer(url);
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
throw new Error(`game_source_material_download_failed:${key}: ${error instanceof Error ? error.message : String(error)}`);
|
|
268
|
+
}
|
|
253
269
|
},
|
|
254
270
|
});
|
|
271
|
+
return lock;
|
|
255
272
|
}
|
|
256
273
|
async function writeManagedGameSourceIgnore(projectDir) {
|
|
257
274
|
const ignorePath = node_path_1.default.join(projectDir, ".gitignore");
|
|
@@ -329,7 +346,15 @@ async function setupGameSourceWorkspace(input) {
|
|
|
329
346
|
});
|
|
330
347
|
}
|
|
331
348
|
if (taskBaseSha && !resumed) {
|
|
332
|
-
await materializeCurrentLock({
|
|
349
|
+
await materializeCurrentLock({
|
|
350
|
+
projectDir,
|
|
351
|
+
workerHomeDir: input.workerHomeDir,
|
|
352
|
+
readFiles: (files) => input.client.workerReadGameSourceFiles(input.assignment.task.id, {
|
|
353
|
+
workerKey: input.workerKey,
|
|
354
|
+
leaseToken: input.leaseToken,
|
|
355
|
+
files,
|
|
356
|
+
}),
|
|
357
|
+
});
|
|
333
358
|
}
|
|
334
359
|
if (source.listingSync && !resumed) {
|
|
335
360
|
const listingFiles = new Map();
|
|
@@ -411,7 +436,7 @@ async function setupGameSourceWorkspace(input) {
|
|
|
411
436
|
pendingMaterials,
|
|
412
437
|
};
|
|
413
438
|
}
|
|
414
|
-
async function walkFiles(root, current = root) {
|
|
439
|
+
async function walkFiles(root, current = root, includeBuildOutput = false) {
|
|
415
440
|
const files = [];
|
|
416
441
|
for (const entry of await (0, promises_1.readdir)(current, { withFileTypes: true })) {
|
|
417
442
|
if (entry.name === ".git")
|
|
@@ -419,11 +444,15 @@ async function walkFiles(root, current = root) {
|
|
|
419
444
|
const absolute = node_path_1.default.join(current, entry.name);
|
|
420
445
|
const relative = normalizeRelativePath(root, absolute);
|
|
421
446
|
if (entry.isDirectory()) {
|
|
422
|
-
if (["node_modules", "
|
|
447
|
+
if (["node_modules", "coverage", ".next", ".playdrop-task-events"].includes(entry.name))
|
|
448
|
+
continue;
|
|
449
|
+
if (!includeBuildOutput && ["dist", "build"].includes(entry.name))
|
|
450
|
+
continue;
|
|
451
|
+
if (relative === ".playdrop" && !includeBuildOutput)
|
|
423
452
|
continue;
|
|
424
|
-
if (relative
|
|
453
|
+
if (relative.startsWith(".playdrop/") && relative !== ".playdrop/server")
|
|
425
454
|
continue;
|
|
426
|
-
files.push(...await walkFiles(root, absolute));
|
|
455
|
+
files.push(...await walkFiles(root, absolute, includeBuildOutput));
|
|
427
456
|
}
|
|
428
457
|
else if (entry.isFile()) {
|
|
429
458
|
files.push(absolute);
|
|
@@ -431,6 +460,23 @@ async function walkFiles(root, current = root) {
|
|
|
431
460
|
}
|
|
432
461
|
return files;
|
|
433
462
|
}
|
|
463
|
+
// Source reconciliation deliberately excludes generated builds. Publication
|
|
464
|
+
// freshness must cover those bytes too, while ignoring task feedback/logs.
|
|
465
|
+
async function fingerprintGameSourcePublication(projectDir, includeServerOutput = true) {
|
|
466
|
+
const files = [];
|
|
467
|
+
for (const absolute of await walkFiles(projectDir, projectDir, true)) {
|
|
468
|
+
const relative = normalizeRelativePath(projectDir, absolute);
|
|
469
|
+
const content = await (0, promises_1.readFile)(absolute);
|
|
470
|
+
const classification = (0, game_source_git_1.classifyGameSourceFile)(relative, content);
|
|
471
|
+
const buildOutput = relative.split("/").some((part) => ["dist", "build", "output"].includes(part));
|
|
472
|
+
const serverOutput = includeServerOutput && [".playdrop/server/bundle.mjs", ".playdrop/server/manifest.json"].includes(relative);
|
|
473
|
+
if (classification === "git" || classification === "material" || buildOutput || serverOutput || relative === "source.zip") {
|
|
474
|
+
files.push([relative, (0, game_source_git_1.sha256)(content)]);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
files.sort(([left], [right]) => left.localeCompare(right));
|
|
478
|
+
return (0, game_source_git_1.sha256)(Buffer.from(JSON.stringify(files)));
|
|
479
|
+
}
|
|
434
480
|
async function gitCommit(repoPath, message) {
|
|
435
481
|
await git.run([
|
|
436
482
|
"-c", "user.name=Playdrop Game Source",
|
|
@@ -59,7 +59,9 @@ export declare function buildSandboxedCodexArgs(input: {
|
|
|
59
59
|
outputPath: string;
|
|
60
60
|
prompt: string;
|
|
61
61
|
outputSchemaPath?: string;
|
|
62
|
+
persistSession?: boolean;
|
|
62
63
|
}): string[];
|
|
64
|
+
export declare function retainScreeningTranscript(codexHomeDir: string, taskId: number): Promise<void>;
|
|
63
65
|
export declare function falInput(request: CreateAiGenerationTaskRequest, resolvedInputs: Map<string, string>): Record<string, unknown>;
|
|
64
66
|
export declare function selectFalOutputs(kind: CreateAiGenerationTaskRequest["kind"], value: Record<string, unknown>): {
|
|
65
67
|
primary: FalFile;
|