@playdrop/playdrop-cli 0.12.41 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/client-meta.json +2 -2
- package/dist/apps/index.d.ts +3 -0
- package/dist/apps/index.js +3 -1
- package/dist/apps/loadCheck.d.ts +21 -1
- package/dist/apps/loadCheck.js +253 -1
- package/dist/apps/playtestContract.d.ts +2 -0
- package/dist/apps/playtestContract.js +77 -0
- package/dist/apps/staticHtml.d.ts +28 -0
- package/dist/apps/staticHtml.js +343 -0
- package/dist/apps/upload.d.ts +1 -0
- package/dist/apps/upload.js +17 -0
- package/dist/apps/validate.js +2 -1
- package/dist/commands/devServer.js +19 -0
- package/dist/commands/feedback.d.ts +2 -0
- package/dist/commands/feedback.js +44 -2
- package/dist/commands/upload.d.ts +24 -0
- package/dist/commands/upload.js +131 -60
- package/dist/commands/worker/instrument-evidence.js +1 -1
- package/dist/commands/worker/runtime.d.ts +14 -0
- package/dist/commands/worker/runtime.js +97 -8
- package/dist/commands/worker.d.ts +27 -2
- package/dist/commands/worker.js +688 -146
- package/dist/index.js +7 -0
- package/dist/listingPreflight.js +15 -4
- package/node_modules/@playdrop/api-client/dist/client.d.ts +6 -2
- package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.d.ts +3 -2
- package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.js +13 -2
- package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts +3 -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 +11 -0
- package/node_modules/@playdrop/api-client/dist/domains/me.d.ts +2 -1
- package/node_modules/@playdrop/api-client/dist/domains/me.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/me.js +11 -0
- package/node_modules/@playdrop/api-client/dist/index.d.ts +6 -3
- package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/index.js +14 -2
- package/node_modules/@playdrop/config/client-meta.json +2 -2
- package/node_modules/@playdrop/config/dist/src/constants.d.ts +1 -0
- package/node_modules/@playdrop/config/dist/src/constants.d.ts.map +1 -1
- package/node_modules/@playdrop/config/dist/src/constants.js +1 -0
- package/node_modules/@playdrop/config/dist/tsconfig.tsbuildinfo +1 -1
- package/node_modules/@playdrop/types/dist/api.d.ts +195 -4
- package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/api.js +25 -2
- package/node_modules/@playdrop/types/dist/app-playtest-tape.d.ts +4 -0
- package/node_modules/@playdrop/types/dist/app-playtest-tape.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/app-playtest-tape.js +56 -0
- package/node_modules/@playdrop/types/dist/version.d.ts +49 -1
- package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
- package/node_modules/entities/decode-codepoint.d.ts +19 -0
- package/node_modules/entities/decode-codepoint.d.ts.map +1 -0
- package/node_modules/entities/decode-codepoint.js +77 -0
- package/node_modules/entities/decode-codepoint.js.map +1 -0
- package/node_modules/entities/decode.d.ts +209 -0
- package/node_modules/entities/decode.d.ts.map +1 -0
- package/node_modules/entities/decode.js +511 -0
- package/node_modules/entities/decode.js.map +1 -0
- package/node_modules/entities/encode.d.ts +22 -0
- package/node_modules/entities/encode.d.ts.map +1 -0
- package/node_modules/entities/encode.js +73 -0
- package/node_modules/entities/encode.js.map +1 -0
- package/node_modules/entities/escape.d.ts +43 -0
- package/node_modules/entities/escape.d.ts.map +1 -0
- package/node_modules/entities/escape.js +121 -0
- package/node_modules/entities/escape.js.map +1 -0
- package/node_modules/entities/generated/decode-data-html.d.ts +2 -0
- package/node_modules/entities/generated/decode-data-html.d.ts.map +1 -0
- package/node_modules/entities/generated/decode-data-html.js +10 -0
- package/node_modules/entities/generated/decode-data-html.js.map +1 -0
- package/node_modules/entities/generated/decode-data-xml.d.ts +2 -0
- package/node_modules/entities/generated/decode-data-xml.d.ts.map +1 -0
- package/node_modules/entities/generated/decode-data-xml.js +10 -0
- package/node_modules/entities/generated/decode-data-xml.js.map +1 -0
- package/node_modules/entities/generated/encode-html.d.ts +8 -0
- package/node_modules/entities/generated/encode-html.d.ts.map +1 -0
- package/node_modules/entities/generated/encode-html.js +13 -0
- package/node_modules/entities/generated/encode-html.js.map +1 -0
- package/node_modules/entities/index.d.ts +96 -0
- package/node_modules/entities/index.d.ts.map +1 -0
- package/node_modules/entities/index.js +131 -0
- package/node_modules/entities/index.js.map +1 -0
- package/node_modules/entities/package.json +3 -0
- package/node_modules/parse5/common/doctype.d.ts +4 -0
- package/node_modules/parse5/common/doctype.js +118 -0
- package/node_modules/parse5/common/error-codes.d.ts +67 -0
- package/node_modules/parse5/common/error-codes.js +66 -0
- package/node_modules/parse5/common/foreign-content.d.ts +9 -0
- package/node_modules/parse5/common/foreign-content.js +237 -0
- package/node_modules/parse5/common/html.d.ts +289 -0
- package/node_modules/parse5/common/html.js +528 -0
- package/node_modules/parse5/common/token.d.ts +84 -0
- package/node_modules/parse5/common/token.js +24 -0
- package/node_modules/parse5/common/unicode.d.ts +42 -0
- package/node_modules/parse5/common/unicode.js +70 -0
- package/node_modules/parse5/index.d.ts +71 -0
- package/node_modules/parse5/index.js +54 -0
- package/node_modules/parse5/package.json +1 -0
- package/node_modules/parse5/parser/formatting-element-list.d.ts +36 -0
- package/node_modules/parse5/parser/formatting-element-list.js +114 -0
- package/node_modules/parse5/parser/index.d.ts +221 -0
- package/node_modules/parse5/parser/index.js +3240 -0
- package/node_modules/parse5/parser/open-element-stack.d.ts +53 -0
- package/node_modules/parse5/parser/open-element-stack.js +328 -0
- package/node_modules/parse5/serializer/index.d.ts +60 -0
- package/node_modules/parse5/serializer/index.js +172 -0
- package/node_modules/parse5/tokenizer/index.d.ts +247 -0
- package/node_modules/parse5/tokenizer/index.js +2714 -0
- package/node_modules/parse5/tokenizer/preprocessor.d.ts +36 -0
- package/node_modules/parse5/tokenizer/preprocessor.js +200 -0
- package/node_modules/parse5/tree-adapters/default.d.ts +84 -0
- package/node_modules/parse5/tree-adapters/default.js +176 -0
- package/node_modules/parse5/tree-adapters/interface.d.ts +255 -0
- package/node_modules/parse5/tree-adapters/interface.js +2 -0
- package/package.json +7 -1
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseStaticHtmlMetadata = parseStaticHtmlMetadata;
|
|
4
|
+
exports.buildStaticGameSlug = buildStaticGameSlug;
|
|
5
|
+
exports.prepareStaticHtmlProject = prepareStaticHtmlProject;
|
|
6
|
+
exports.sha256File = sha256File;
|
|
7
|
+
const node_crypto_1 = require("node:crypto");
|
|
8
|
+
const node_fs_1 = require("node:fs");
|
|
9
|
+
const promises_1 = require("node:fs/promises");
|
|
10
|
+
const node_os_1 = require("node:os");
|
|
11
|
+
const node_path_1 = require("node:path");
|
|
12
|
+
const parse5_1 = require("parse5");
|
|
13
|
+
const build_1 = require("./build");
|
|
14
|
+
const STATIC_GAME_SURFACES = ['DESKTOP', 'MOBILE_LANDSCAPE', 'MOBILE_PORTRAIT'];
|
|
15
|
+
const STATIC_SOURCE_EXCLUDES = ['AGENTS.md', 'CLAUDE.md', '.playdrop/', 'bin/playdrop'];
|
|
16
|
+
const STATIC_BUNDLE_EXCLUDES = [...STATIC_SOURCE_EXCLUDES];
|
|
17
|
+
function attribute(node, name) {
|
|
18
|
+
return node.attrs?.find((entry) => entry.name.toLowerCase() === name)?.value.trim() ?? null;
|
|
19
|
+
}
|
|
20
|
+
function walk(node, visit) {
|
|
21
|
+
visit(node);
|
|
22
|
+
for (const child of node.childNodes ?? []) {
|
|
23
|
+
walk(child, visit);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function findElement(node, tagName) {
|
|
27
|
+
if (node.tagName === tagName)
|
|
28
|
+
return node;
|
|
29
|
+
for (const child of node.childNodes ?? []) {
|
|
30
|
+
const match = findElement(child, tagName);
|
|
31
|
+
if (match)
|
|
32
|
+
return match;
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
function textContent(node) {
|
|
37
|
+
if (typeof node.value === 'string') {
|
|
38
|
+
return node.value;
|
|
39
|
+
}
|
|
40
|
+
return (node.childNodes ?? []).map(textContent).join('');
|
|
41
|
+
}
|
|
42
|
+
function isPngFile(filePath) {
|
|
43
|
+
const signature = (0, node_fs_1.readFileSync)(filePath).subarray(0, 8);
|
|
44
|
+
return signature.length === 8
|
|
45
|
+
&& signature.equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]));
|
|
46
|
+
}
|
|
47
|
+
function isInside(root, candidate) {
|
|
48
|
+
const rel = (0, node_path_1.relative)(root, candidate);
|
|
49
|
+
return rel === '' || (!rel.startsWith(`..${node_path_1.sep}`) && rel !== '..' && !(0, node_path_1.isAbsolute)(rel));
|
|
50
|
+
}
|
|
51
|
+
function localReferencePath(projectDir, rawReference, code) {
|
|
52
|
+
const reference = rawReference.trim();
|
|
53
|
+
if (!reference || reference.startsWith('#') || reference.startsWith('data:') || reference.startsWith('blob:')) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
if (/^(?:https?:)?\/\//i.test(reference) || /^[a-z][a-z0-9+.-]*:/i.test(reference)) {
|
|
57
|
+
throw new Error(`${code}:remote_reference:${reference}`);
|
|
58
|
+
}
|
|
59
|
+
if (reference.startsWith('/')) {
|
|
60
|
+
throw new Error(`${code}:root_reference:${reference}`);
|
|
61
|
+
}
|
|
62
|
+
let decoded;
|
|
63
|
+
try {
|
|
64
|
+
decoded = decodeURIComponent(reference.split(/[?#]/, 1)[0] ?? '');
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
throw new Error(`${code}:invalid_reference:${reference}`);
|
|
68
|
+
}
|
|
69
|
+
const absoluteRoot = (0, node_path_1.resolve)(projectDir);
|
|
70
|
+
const resolvedPath = (0, node_path_1.resolve)(absoluteRoot, decoded);
|
|
71
|
+
const realRoot = (0, node_fs_1.realpathSync)(absoluteRoot);
|
|
72
|
+
if (!isInside(absoluteRoot, resolvedPath) || !(0, node_fs_1.existsSync)(resolvedPath) || !(0, node_fs_1.statSync)(resolvedPath).isFile()) {
|
|
73
|
+
throw new Error(`${code}:missing_or_outside:${reference}`);
|
|
74
|
+
}
|
|
75
|
+
const realFile = (0, node_fs_1.realpathSync)(resolvedPath);
|
|
76
|
+
if (!isInside(realRoot, realFile)) {
|
|
77
|
+
throw new Error(`${code}:outside_project:${reference}`);
|
|
78
|
+
}
|
|
79
|
+
return realFile;
|
|
80
|
+
}
|
|
81
|
+
function validateHtmlRuntimeReferences(projectDir, document) {
|
|
82
|
+
walk(document, (node) => {
|
|
83
|
+
const tag = node.tagName?.toLowerCase();
|
|
84
|
+
if (!tag)
|
|
85
|
+
return;
|
|
86
|
+
if (tag === 'base') {
|
|
87
|
+
throw new Error('static_game_artifact_base_tag_unsupported');
|
|
88
|
+
}
|
|
89
|
+
const rel = attribute(node, 'rel')?.toLowerCase().split(/\s+/) ?? [];
|
|
90
|
+
const reference = tag === 'script'
|
|
91
|
+
? attribute(node, 'src')
|
|
92
|
+
: tag === 'link' && (rel.includes('stylesheet') || rel.includes('icon') || rel.includes('preload'))
|
|
93
|
+
? attribute(node, 'href')
|
|
94
|
+
: ['img', 'audio', 'video', 'source', 'track', 'embed', 'object', 'iframe'].includes(tag)
|
|
95
|
+
? attribute(node, tag === 'object' ? 'data' : 'src')
|
|
96
|
+
: null;
|
|
97
|
+
if (reference) {
|
|
98
|
+
localReferencePath(projectDir, reference, 'static_game_artifact_invalid_local_path');
|
|
99
|
+
}
|
|
100
|
+
const srcset = tag === 'img' || tag === 'source' ? attribute(node, 'srcset') : null;
|
|
101
|
+
for (const candidate of srcset?.split(',') ?? []) {
|
|
102
|
+
const referenceValue = candidate.trim().split(/\s+/, 1)[0];
|
|
103
|
+
if (referenceValue) {
|
|
104
|
+
localReferencePath(projectDir, referenceValue, 'static_game_artifact_invalid_local_path');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
function parseStaticHtmlProject(projectDir, entryPoint = 'index.html') {
|
|
110
|
+
const root = (0, node_path_1.resolve)(projectDir);
|
|
111
|
+
const entryPath = (0, node_path_1.resolve)(root, entryPoint);
|
|
112
|
+
if (!isInside(root, entryPath) || (0, node_path_1.basename)(entryPath).toLowerCase() !== 'index.html') {
|
|
113
|
+
throw new Error('static_game_artifact_invalid_entry_point');
|
|
114
|
+
}
|
|
115
|
+
if (!(0, node_fs_1.existsSync)(entryPath) || !(0, node_fs_1.statSync)(entryPath).isFile()) {
|
|
116
|
+
throw new Error('static_game_artifact_index_missing');
|
|
117
|
+
}
|
|
118
|
+
const html = (0, node_fs_1.readFileSync)(entryPath, 'utf8');
|
|
119
|
+
if (html.includes('data-playdrop-static-sdk') || html.includes('data-playdrop-static-bootstrap')) {
|
|
120
|
+
throw new Error('static_game_artifact_injected_sdk_already_present');
|
|
121
|
+
}
|
|
122
|
+
const document = (0, parse5_1.parse)(html, { sourceCodeLocationInfo: true });
|
|
123
|
+
const head = findElement(document, 'head');
|
|
124
|
+
const body = findElement(document, 'body');
|
|
125
|
+
if (!head) {
|
|
126
|
+
throw new Error('static_game_artifact_head_missing');
|
|
127
|
+
}
|
|
128
|
+
const titles = [];
|
|
129
|
+
const metas = [];
|
|
130
|
+
let headEndOffset = null;
|
|
131
|
+
walk(head, (node) => {
|
|
132
|
+
if (node.tagName === 'title')
|
|
133
|
+
titles.push(node);
|
|
134
|
+
if (node.tagName === 'meta')
|
|
135
|
+
metas.push(node);
|
|
136
|
+
if (node.tagName === 'head' && Number.isInteger(node.sourceCodeLocation?.endTag?.startOffset)) {
|
|
137
|
+
headEndOffset = Number(node.sourceCodeLocation.endTag.startOffset);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
if (titles.length !== 1) {
|
|
141
|
+
throw new Error(`static_game_artifact_title_count:${titles.length}`);
|
|
142
|
+
}
|
|
143
|
+
const title = textContent(titles[0]).replace(/\s+/g, ' ').trim();
|
|
144
|
+
if (!title || title.length > 120) {
|
|
145
|
+
throw new Error('static_game_artifact_title_invalid');
|
|
146
|
+
}
|
|
147
|
+
if (headEndOffset === null && body) {
|
|
148
|
+
const explicitBodyStart = body.sourceCodeLocation?.startTag?.startOffset
|
|
149
|
+
?? body.sourceCodeLocation?.startOffset;
|
|
150
|
+
const firstBodyChildStart = (body.childNodes ?? [])
|
|
151
|
+
.map((node) => node.sourceCodeLocation?.startOffset)
|
|
152
|
+
.find((offset) => Number.isInteger(offset));
|
|
153
|
+
const finalHeadChildEnd = [...(head.childNodes ?? [])]
|
|
154
|
+
.reverse()
|
|
155
|
+
.map((node) => node.sourceCodeLocation?.endOffset)
|
|
156
|
+
.find((offset) => Number.isInteger(offset));
|
|
157
|
+
headEndOffset = explicitBodyStart ?? firstBodyChildStart ?? finalHeadChildEnd ?? null;
|
|
158
|
+
}
|
|
159
|
+
if (headEndOffset === null) {
|
|
160
|
+
throw new Error('static_game_artifact_html_injection_point_missing');
|
|
161
|
+
}
|
|
162
|
+
for (const meta of metas) {
|
|
163
|
+
const httpEquiv = attribute(meta, 'http-equiv')?.toLowerCase();
|
|
164
|
+
if (httpEquiv === 'content-security-policy') {
|
|
165
|
+
throw new Error('static_game_artifact_blocking_csp');
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
validateHtmlRuntimeReferences(root, document);
|
|
169
|
+
const readMeta = (...keys) => {
|
|
170
|
+
for (const key of keys) {
|
|
171
|
+
const match = metas.find((node) => (attribute(node, 'name')?.toLowerCase() === key
|
|
172
|
+
|| attribute(node, 'property')?.toLowerCase() === key));
|
|
173
|
+
const value = match ? attribute(match, 'content') : null;
|
|
174
|
+
if (value)
|
|
175
|
+
return value;
|
|
176
|
+
}
|
|
177
|
+
return null;
|
|
178
|
+
};
|
|
179
|
+
const description = readMeta('description', 'og:description');
|
|
180
|
+
const image = readMeta('og:image', 'twitter:image');
|
|
181
|
+
const warnings = [];
|
|
182
|
+
let imagePath = null;
|
|
183
|
+
if (image) {
|
|
184
|
+
try {
|
|
185
|
+
const resolvedImage = localReferencePath(root, image, 'static_game_artifact_image_invalid');
|
|
186
|
+
if (resolvedImage && (0, node_path_1.extname)(resolvedImage).toLowerCase() === '.png' && isPngFile(resolvedImage)) {
|
|
187
|
+
imagePath = resolvedImage;
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
warnings.push('Optional HTML image metadata was ignored because it is not a local PNG.');
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
warnings.push('Optional HTML image metadata was ignored because it does not resolve to a local PNG.');
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
html,
|
|
199
|
+
headEndOffset,
|
|
200
|
+
metadata: { title, description, imagePath, warnings },
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
function parseStaticHtmlMetadata(projectDir, entryPoint = 'index.html') {
|
|
204
|
+
return parseStaticHtmlProject(projectDir, entryPoint).metadata;
|
|
205
|
+
}
|
|
206
|
+
function buildStaticGameSlug(title, taskId) {
|
|
207
|
+
const base = title.normalize('NFKD')
|
|
208
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
209
|
+
.toLowerCase()
|
|
210
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
211
|
+
.replace(/^-+|-+$/g, '') || 'game';
|
|
212
|
+
const suffix = taskId === undefined ? '' : `-t${taskId}`;
|
|
213
|
+
return `${base.slice(0, 128 - suffix.length).replace(/-+$/g, '')}${suffix}`;
|
|
214
|
+
}
|
|
215
|
+
function buildListing(imagePath, primarySurface) {
|
|
216
|
+
if (!imagePath)
|
|
217
|
+
return undefined;
|
|
218
|
+
return {
|
|
219
|
+
...(primarySurface === 'MOBILE_PORTRAIT'
|
|
220
|
+
? { heroPortraitPath: imagePath }
|
|
221
|
+
: { heroLandscapePath: imagePath }),
|
|
222
|
+
screenshotPortraitPaths: [],
|
|
223
|
+
screenshotLandscapePaths: [],
|
|
224
|
+
videoPortraitPaths: [],
|
|
225
|
+
videoLandscapePaths: [],
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
function buildStaticAppTask(input) {
|
|
229
|
+
return {
|
|
230
|
+
kind: 'app',
|
|
231
|
+
name: input.name,
|
|
232
|
+
cataloguePath: input.entryPath,
|
|
233
|
+
catalogueAbsolutePath: input.entryPath,
|
|
234
|
+
catalogueDir: input.projectDir,
|
|
235
|
+
filePath: input.entryPath,
|
|
236
|
+
relativeFile: 'index.html',
|
|
237
|
+
projectDir: input.projectDir,
|
|
238
|
+
packageJsonPath: null,
|
|
239
|
+
isTypescriptProject: false,
|
|
240
|
+
hasValidateScript: false,
|
|
241
|
+
hasFormatScript: false,
|
|
242
|
+
displayName: input.metadata.title,
|
|
243
|
+
...(input.metadata.description ? { description: input.metadata.description } : {}),
|
|
244
|
+
type: 'GAME',
|
|
245
|
+
surfaceTargets: [...STATIC_GAME_SURFACES],
|
|
246
|
+
primarySurface: input.primarySurface,
|
|
247
|
+
missingMetadata: [],
|
|
248
|
+
version: '1.0.0',
|
|
249
|
+
releaseNotes: 'Initial static game upload',
|
|
250
|
+
versionVisibility: 'PRIVATE',
|
|
251
|
+
sourceArchiveExcludeRelativeFiles: STATIC_SOURCE_EXCLUDES,
|
|
252
|
+
bundleArchiveExcludeRelativeFiles: STATIC_BUNDLE_EXCLUDES,
|
|
253
|
+
license: 'CLOSED',
|
|
254
|
+
tags: [],
|
|
255
|
+
hostingMode: 'HOSTED',
|
|
256
|
+
authMode: 'NONE',
|
|
257
|
+
previewable: true,
|
|
258
|
+
...(buildListing(input.metadata.imagePath, input.primarySurface)
|
|
259
|
+
? { listing: buildListing(input.metadata.imagePath, input.primarySurface) }
|
|
260
|
+
: {}),
|
|
261
|
+
achievements: [],
|
|
262
|
+
leaderboards: [],
|
|
263
|
+
ownedAssets: [],
|
|
264
|
+
assetSpecSupport: [],
|
|
265
|
+
uses: { assets: [], packs: [] },
|
|
266
|
+
graph: { relations: [] },
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
const STATIC_SDK_BOOTSTRAP = [
|
|
270
|
+
'<script src="/sdk/playdrop.js" data-playdrop-static-sdk></script>',
|
|
271
|
+
'<script data-playdrop-static-bootstrap>',
|
|
272
|
+
'window.addEventListener("load", async () => {',
|
|
273
|
+
' const sdk = await window.playdrop.init();',
|
|
274
|
+
' await sdk.host.ready();',
|
|
275
|
+
'}, { once: true });',
|
|
276
|
+
'</script>',
|
|
277
|
+
].join('\n');
|
|
278
|
+
async function prepareStaticHtmlProject(input) {
|
|
279
|
+
const sourceRoot = (0, node_fs_1.realpathSync)((0, node_path_1.resolve)(input.projectDir));
|
|
280
|
+
const sourceEntryPath = (0, node_path_1.join)(sourceRoot, 'index.html');
|
|
281
|
+
const parsedProject = parseStaticHtmlProject(sourceRoot);
|
|
282
|
+
const metadata = parsedProject.metadata;
|
|
283
|
+
const requestedAppName = input.appName?.trim() ?? '';
|
|
284
|
+
if (requestedAppName && (requestedAppName.length > 128 || !/^[a-z0-9]+(?:[._-][a-z0-9]+)*$/.test(requestedAppName))) {
|
|
285
|
+
throw new Error('static_game_artifact_claimed_slug_invalid');
|
|
286
|
+
}
|
|
287
|
+
const name = requestedAppName || buildStaticGameSlug(metadata.title, input.taskId);
|
|
288
|
+
const sourceTask = buildStaticAppTask({
|
|
289
|
+
projectDir: sourceRoot,
|
|
290
|
+
entryPath: sourceEntryPath,
|
|
291
|
+
name,
|
|
292
|
+
metadata,
|
|
293
|
+
primarySurface: input.primarySurface,
|
|
294
|
+
});
|
|
295
|
+
const sourceArtifacts = await (0, build_1.buildApp)(sourceTask);
|
|
296
|
+
const runtimeDir = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), `playdrop-static-${name}-`));
|
|
297
|
+
try {
|
|
298
|
+
await (0, promises_1.cp)(sourceRoot, runtimeDir, {
|
|
299
|
+
recursive: true,
|
|
300
|
+
filter: (source) => {
|
|
301
|
+
const rel = (0, node_path_1.relative)(sourceRoot, source).replace(/\\/g, '/');
|
|
302
|
+
return !rel || !STATIC_SOURCE_EXCLUDES.some((excluded) => (excluded.endsWith('/') ? rel === excluded.slice(0, -1) || rel.startsWith(excluded) : rel === excluded));
|
|
303
|
+
},
|
|
304
|
+
});
|
|
305
|
+
const runtimeEntryPath = (0, node_path_1.join)(runtimeDir, 'index.html');
|
|
306
|
+
const sourceHtml = parsedProject.html;
|
|
307
|
+
const runtimeHtml = `${sourceHtml.slice(0, parsedProject.headEndOffset)}${STATIC_SDK_BOOTSTRAP}\n${sourceHtml.slice(parsedProject.headEndOffset)}`;
|
|
308
|
+
await (0, promises_1.writeFile)(runtimeEntryPath, runtimeHtml, 'utf8');
|
|
309
|
+
const runtimeTask = buildStaticAppTask({
|
|
310
|
+
projectDir: runtimeDir,
|
|
311
|
+
entryPath: runtimeEntryPath,
|
|
312
|
+
name,
|
|
313
|
+
metadata: {
|
|
314
|
+
...metadata,
|
|
315
|
+
imagePath: metadata.imagePath ? (0, node_path_1.join)(runtimeDir, (0, node_path_1.relative)(sourceRoot, metadata.imagePath)) : null,
|
|
316
|
+
},
|
|
317
|
+
primarySurface: input.primarySurface,
|
|
318
|
+
});
|
|
319
|
+
const runtimeArtifacts = await (0, build_1.buildApp)(runtimeTask);
|
|
320
|
+
const artifacts = {
|
|
321
|
+
...runtimeArtifacts,
|
|
322
|
+
sourceFile: sourceArtifacts.sourceFile,
|
|
323
|
+
sourceHash: sourceArtifacts.sourceHash,
|
|
324
|
+
};
|
|
325
|
+
return {
|
|
326
|
+
sourceTask,
|
|
327
|
+
runtimeTask,
|
|
328
|
+
artifacts,
|
|
329
|
+
sourceHash: sourceArtifacts.sourceHash,
|
|
330
|
+
runtimeHash: runtimeArtifacts.bundleHash,
|
|
331
|
+
metadata,
|
|
332
|
+
runtimeDir,
|
|
333
|
+
cleanup: async () => { await (0, promises_1.rm)(runtimeDir, { recursive: true, force: true }); },
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
catch (error) {
|
|
337
|
+
await (0, promises_1.rm)(runtimeDir, { recursive: true, force: true });
|
|
338
|
+
throw error;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
function sha256File(filePath) {
|
|
342
|
+
return (0, node_crypto_1.createHash)('sha256').update((0, node_fs_1.readFileSync)(filePath)).digest('hex');
|
|
343
|
+
}
|
package/dist/apps/upload.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export type AppUploadOptions = {
|
|
|
23
23
|
agentTaskId?: number;
|
|
24
24
|
playtestProof?: AgentTaskPlaytestProof;
|
|
25
25
|
mediaCaptureRequired?: boolean;
|
|
26
|
+
machineCausalGateRequired?: boolean;
|
|
26
27
|
captureSession?: TaskCaptureSession | null;
|
|
27
28
|
};
|
|
28
29
|
export declare function uploadApp(client: ApiClient, task: AppTask, artifacts: AppBuildArtifacts | null, options?: AppUploadOptions): Promise<AppUploadResult>;
|
package/dist/apps/upload.js
CHANGED
|
@@ -9,6 +9,7 @@ const node_path_1 = require("node:path");
|
|
|
9
9
|
const http_1 = require("../http");
|
|
10
10
|
const loadCheck_1 = require("./loadCheck");
|
|
11
11
|
const listingPreflight_1 = require("../listingPreflight");
|
|
12
|
+
const playtestContract_1 = require("./playtestContract");
|
|
12
13
|
const EXTENSION_TO_MIME = {
|
|
13
14
|
'.png': 'image/png',
|
|
14
15
|
'.jpg': 'image/jpeg',
|
|
@@ -353,6 +354,22 @@ async function uploadAppVersion(client, task, artifacts, options) {
|
|
|
353
354
|
await client.recordAppUploadMediaCapture(creatorUsername, task.name, sessionId, mediaCaptureProof);
|
|
354
355
|
}
|
|
355
356
|
if (task.hostingMode !== 'EXTERNAL'
|
|
357
|
+
&& !task.externalUrl
|
|
358
|
+
&& options?.machineCausalGateRequired) {
|
|
359
|
+
const teachingText = (0, playtestContract_1.assertPrimaryVerbTeachingParity)(task);
|
|
360
|
+
await (0, loadCheck_1.runUploadedHostedCausalCheck)({
|
|
361
|
+
client,
|
|
362
|
+
creatorUsername,
|
|
363
|
+
task,
|
|
364
|
+
sessionId,
|
|
365
|
+
teachingText,
|
|
366
|
+
timeoutMs: options.loadCheckTimeoutMs,
|
|
367
|
+
token: options?.token ?? null,
|
|
368
|
+
currentUser: options?.user ?? null,
|
|
369
|
+
captureSession: options?.captureSession,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
else if (task.hostingMode !== 'EXTERNAL'
|
|
356
373
|
&& !task.externalUrl
|
|
357
374
|
&& options?.runStagedUploadLoadCheck) {
|
|
358
375
|
const loadCheck = await (0, loadCheck_1.runUploadedHostedLoadCheck)({
|
package/dist/apps/validate.js
CHANGED
|
@@ -43,6 +43,7 @@ const LEGACY_SDK_SYMBOL_PATTERNS = [
|
|
|
43
43
|
];
|
|
44
44
|
const SDK_REFERENCE_PATTERNS = [
|
|
45
45
|
/https:\/\/assets\.playdrop\.ai\/sdk\/playdrop\.js/g,
|
|
46
|
+
/["']\/sdk\/playdrop\.js(?:[?"'])/g,
|
|
46
47
|
/@playdrop\/sdk(?:\/browser)?/g,
|
|
47
48
|
];
|
|
48
49
|
const SDK_INIT_PATTERNS = [
|
|
@@ -487,7 +488,7 @@ function collectAppValidationWarnings(task, mode = 'source') {
|
|
|
487
488
|
const hasSdkReady = primaryDetection.hasSdkReady || sourceDetection.hasSdkReady;
|
|
488
489
|
const warnings = [];
|
|
489
490
|
if (!hasSdkReference) {
|
|
490
|
-
warnings.push(`[apps][validate] Could not detect the Playdrop SDK loader or @playdrop/sdk import for ${task.name}. We could not find https://assets.playdrop.ai/sdk/playdrop.js or @playdrop/sdk in the app files, so it might not work once uploaded.`);
|
|
491
|
+
warnings.push(`[apps][validate] Could not detect the Playdrop SDK loader or @playdrop/sdk import for ${task.name}. We could not find /sdk/playdrop.js, https://assets.playdrop.ai/sdk/playdrop.js, or @playdrop/sdk in the app files, so it might not work once uploaded.`);
|
|
491
492
|
}
|
|
492
493
|
if (!hasSdkInit) {
|
|
493
494
|
warnings.push(`[apps][validate] Could not detect Playdrop SDK initialization for ${task.name}. We could not find playdrop.init() in the app files, so it might not work once uploaded.`);
|
|
@@ -651,6 +651,25 @@ function createDevRouterServer(initialPort = exports.DEV_ROUTER_PORT) {
|
|
|
651
651
|
});
|
|
652
652
|
return;
|
|
653
653
|
}
|
|
654
|
+
if (pathname.startsWith('/sdk/') && (method === 'GET' || method === 'HEAD')) {
|
|
655
|
+
const assetsBase = process.env.ASSETS_BASE_URL?.trim();
|
|
656
|
+
if (!assetsBase) {
|
|
657
|
+
respondWithText(res, method, 500, 'ASSETS_BASE_URL is required to serve the development SDK.');
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
let sdkUrl;
|
|
661
|
+
try {
|
|
662
|
+
sdkUrl = new URL(`${pathname}${requestUrl.search}`, `${assetsBase.replace(/\/$/, '')}/`);
|
|
663
|
+
}
|
|
664
|
+
catch {
|
|
665
|
+
respondWithText(res, method, 500, 'ASSETS_BASE_URL is invalid.');
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
res.statusCode = 307;
|
|
669
|
+
res.setHeader('Location', sdkUrl.toString());
|
|
670
|
+
res.end();
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
654
673
|
if (pathname.startsWith(`${CONTROL_PREFIX}/apps/dev/`) && pathname.endsWith('/runtime-assets') && method === 'GET') {
|
|
655
674
|
cleanupStaleRouterMounts();
|
|
656
675
|
const segments = pathname.split('/').filter(Boolean);
|
|
@@ -6,9 +6,11 @@ type FeedbackSendOptions = {
|
|
|
6
6
|
json?: boolean;
|
|
7
7
|
};
|
|
8
8
|
type FeedbackBrowseOptions = {
|
|
9
|
+
admin?: boolean;
|
|
9
10
|
limit?: string | number;
|
|
10
11
|
offset?: string | number;
|
|
11
12
|
client?: string;
|
|
13
|
+
sinceHours?: string | number;
|
|
12
14
|
userId?: string | number;
|
|
13
15
|
json?: boolean;
|
|
14
16
|
};
|
|
@@ -16,7 +16,7 @@ const output_1 = require("../output");
|
|
|
16
16
|
const clientInfo_1 = require("../clientInfo");
|
|
17
17
|
const MAX_FEEDBACK_TITLE_LENGTH = 70;
|
|
18
18
|
const MAX_FEEDBACK_COMMENT_LENGTH = 10000;
|
|
19
|
-
const FEEDBACK_CATEGORY_SET = new Set(types_1.
|
|
19
|
+
const FEEDBACK_CATEGORY_SET = new Set(types_1.MANUAL_FEEDBACK_CATEGORY_VALUES);
|
|
20
20
|
function parsePositiveInteger(raw, label, command, options = {}) {
|
|
21
21
|
if (raw === undefined) {
|
|
22
22
|
return null;
|
|
@@ -32,7 +32,7 @@ function parsePositiveInteger(raw, label, command, options = {}) {
|
|
|
32
32
|
function parseFeedbackCategory(raw, command) {
|
|
33
33
|
const normalized = typeof raw === 'string' && raw.trim().length > 0 ? raw.trim() : 'other';
|
|
34
34
|
if (!FEEDBACK_CATEGORY_SET.has(normalized)) {
|
|
35
|
-
(0, messages_1.printErrorWithHelp)(`Feedback category must be one of: ${types_1.
|
|
35
|
+
(0, messages_1.printErrorWithHelp)(`Feedback category must be one of: ${types_1.MANUAL_FEEDBACK_CATEGORY_VALUES.join(', ')}.`, [], { command });
|
|
36
36
|
process.exitCode = 1;
|
|
37
37
|
return null;
|
|
38
38
|
}
|
|
@@ -137,8 +137,50 @@ async function browseFeedback(options = {}) {
|
|
|
137
137
|
process.exitCode = 1;
|
|
138
138
|
return;
|
|
139
139
|
}
|
|
140
|
+
const sinceHours = options.sinceHours !== undefined
|
|
141
|
+
? parsePositiveInteger(options.sinceHours, '--since-hours', 'feedback browse')
|
|
142
|
+
: null;
|
|
143
|
+
if (options.sinceHours !== undefined && sinceHours === null) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
140
146
|
await (0, commandContext_1.withEnvironment)('feedback browse', 'Browsing feedback', async ({ client }) => {
|
|
141
147
|
try {
|
|
148
|
+
if (options.admin) {
|
|
149
|
+
const cutoff = sinceHours === null ? null : Date.now() - sinceHours * 60 * 60 * 1000;
|
|
150
|
+
const items = [];
|
|
151
|
+
let nextOffset = offset ?? 0;
|
|
152
|
+
let hasMore = true;
|
|
153
|
+
while (hasMore && (limit === null || items.length < limit)) {
|
|
154
|
+
const page = await client.fetchAdminFeedback({ limit: 100, offset: nextOffset });
|
|
155
|
+
for (const item of page.feedback ?? []) {
|
|
156
|
+
if (cutoff !== null && new Date(item.createdAt).getTime() < cutoff) {
|
|
157
|
+
hasMore = false;
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
items.push(item);
|
|
161
|
+
if (limit !== null && items.length >= limit) {
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
nextOffset += page.feedback?.length ?? 0;
|
|
166
|
+
hasMore = hasMore && Boolean(page.pagination?.hasMore) && (page.feedback?.length ?? 0) > 0;
|
|
167
|
+
}
|
|
168
|
+
if (options.json) {
|
|
169
|
+
(0, output_1.printJson)({ items, sinceHours, count: items.length });
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (items.length === 0) {
|
|
173
|
+
console.log('No feedback entries found.');
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
console.log('Feedback:\n');
|
|
177
|
+
for (const item of items) {
|
|
178
|
+
console.log(`#${item.id} | ${item.category} | ${item.client} | ${(0, output_1.formatTimestamp)(item.createdAt)}`);
|
|
179
|
+
console.log(` ${item.title}`);
|
|
180
|
+
console.log(` ${item.comment}`);
|
|
181
|
+
}
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
142
184
|
const response = await client.listFeedback({
|
|
143
185
|
limit: limit ?? undefined,
|
|
144
186
|
offset: offset ?? undefined,
|
|
@@ -5,6 +5,7 @@ export type UploadCommandOptions = {
|
|
|
5
5
|
env?: string;
|
|
6
6
|
skipReview?: boolean;
|
|
7
7
|
clearTags?: boolean;
|
|
8
|
+
json?: boolean;
|
|
8
9
|
};
|
|
9
10
|
export declare function resolveUploadOwner(actorUsername: string | null | undefined, workspaceOwnerUsername: string | null | undefined): string;
|
|
10
11
|
export declare function resolveDefaultUploadCreator(owner: string | null | undefined, actorUsername: string | null | undefined): string;
|
|
@@ -35,6 +36,28 @@ export type WorkerAppPublishResult = {
|
|
|
35
36
|
creatorUsername: string;
|
|
36
37
|
warnings: string[];
|
|
37
38
|
};
|
|
39
|
+
export type StaticHtmlPublishInput = {
|
|
40
|
+
client: ApiClient;
|
|
41
|
+
projectDir: string;
|
|
42
|
+
creatorUsername: string;
|
|
43
|
+
primarySurface: 'DESKTOP' | 'MOBILE_LANDSCAPE' | 'MOBILE_PORTRAIT';
|
|
44
|
+
apiBase: string;
|
|
45
|
+
webBase?: string | null;
|
|
46
|
+
token: string;
|
|
47
|
+
user: UserResponse;
|
|
48
|
+
taskId?: number;
|
|
49
|
+
taskToken?: string;
|
|
50
|
+
claimedAppName?: string | null;
|
|
51
|
+
devRouterPort?: number;
|
|
52
|
+
};
|
|
53
|
+
export type StaticHtmlPublishResult = WorkerAppPublishResult & {
|
|
54
|
+
sourceHash: string;
|
|
55
|
+
runtimeHash: string;
|
|
56
|
+
playUrl: string;
|
|
57
|
+
sourceContainsInjectedSdk: false;
|
|
58
|
+
runtimeContainsInjectedSdk: true;
|
|
59
|
+
hostedLoadCheckPassed: true;
|
|
60
|
+
};
|
|
38
61
|
export type WorkerPlaydropAssetRequirement = 'PACK' | 'ASSET_OR_PACK';
|
|
39
62
|
export declare function resolveWorkerPlaydropAssetRequirement(value: unknown): WorkerPlaydropAssetRequirement | null;
|
|
40
63
|
export declare function assertAgentNewGamePlaytestContract(task: AppTask): void;
|
|
@@ -50,3 +73,4 @@ export declare function assertWorkerAppLocalUploadPreflight(input: {
|
|
|
50
73
|
creatorRequest?: string | null;
|
|
51
74
|
}): Promise<AgentTaskPlaytestProof | null>;
|
|
52
75
|
export declare function publishWorkerAppProject(input: WorkerAppPublishInput): Promise<WorkerAppPublishResult>;
|
|
76
|
+
export declare function publishStaticHtmlProject(input: StaticHtmlPublishInput): Promise<StaticHtmlPublishResult>;
|