@playdrop/playdrop-cli 0.12.42 → 0.13.1
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 +2 -0
- package/dist/apps/index.js +2 -1
- package/dist/apps/loadCheck.d.ts +4 -1
- package/dist/apps/loadCheck.js +40 -10
- package/dist/apps/staticHtml.d.ts +28 -0
- package/dist/apps/staticHtml.js +343 -0
- package/dist/apps/validate.js +2 -1
- package/dist/commands/captureListing.d.ts +1 -1
- package/dist/commands/captureListing.js +4 -3
- 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 +122 -4
- package/dist/commands/worker/instrument-evidence.js +1 -1
- package/dist/commands/worker/runtime.d.ts +15 -0
- package/dist/commands/worker/runtime.js +121 -11
- package/dist/commands/worker.d.ts +28 -2
- package/dist/commands/worker.js +843 -170
- package/dist/index.js +7 -0
- package/dist/listingPreflight.js +15 -4
- package/dist/recorderRelease.d.ts +18 -0
- package/dist/recorderRelease.js +90 -0
- package/node_modules/@playdrop/api-client/dist/client.d.ts +5 -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 +2 -1
- package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts.map +1 -1
- 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/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 +8 -1
package/config/client-meta.json
CHANGED
package/dist/apps/index.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export type AppPipelineOptions = {
|
|
|
19
19
|
token?: string | null;
|
|
20
20
|
user?: UserResponse | null;
|
|
21
21
|
runLocalLoadCheck?: boolean;
|
|
22
|
+
devRouterPort?: number;
|
|
22
23
|
runStagedUploadLoadCheck?: boolean;
|
|
23
24
|
loadCheckTimeoutMs?: number;
|
|
24
25
|
ensureRegisteredAppShell?: boolean;
|
|
@@ -27,6 +28,7 @@ export type AppPipelineOptions = {
|
|
|
27
28
|
mediaCaptureRequired?: boolean;
|
|
28
29
|
machineCausalGateRequired?: boolean;
|
|
29
30
|
captureSession?: TaskCaptureSession | null;
|
|
31
|
+
artifacts?: AppBuildArtifacts;
|
|
30
32
|
};
|
|
31
33
|
export declare function runAppPipeline(client: ApiClient, task: AppTask, options?: AppPipelineOptions): Promise<AppPipelineResult>;
|
|
32
34
|
export { collectAppValidationWarnings, validateAppTask, buildApp, uploadApp, runFormatScript };
|
package/dist/apps/index.js
CHANGED
|
@@ -29,7 +29,7 @@ async function runAppPipeline(client, task, options) {
|
|
|
29
29
|
agentTaskId: options.agentTaskId,
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
|
-
const artifacts = isExternal ? null : await (0, build_1.buildApp)(task);
|
|
32
|
+
const artifacts = isExternal ? null : options?.artifacts ?? await (0, build_1.buildApp)(task);
|
|
33
33
|
const warnings = (0, validate_1.collectAppValidationWarnings)(task, isExternal ? 'source' : 'bundle');
|
|
34
34
|
if (!isExternal && options?.runLocalLoadCheck) {
|
|
35
35
|
const creatorUsername = typeof options.creatorUsername === 'string' ? options.creatorUsername.trim() : '';
|
|
@@ -46,6 +46,7 @@ async function runAppPipeline(client, task, options) {
|
|
|
46
46
|
webBase: options.webBase,
|
|
47
47
|
creatorUsername,
|
|
48
48
|
task,
|
|
49
|
+
devRouterPort: options.devRouterPort,
|
|
49
50
|
timeoutMs: options.loadCheckTimeoutMs,
|
|
50
51
|
token: options.token ?? null,
|
|
51
52
|
currentUser: options.user ?? null,
|
package/dist/apps/loadCheck.d.ts
CHANGED
|
@@ -90,7 +90,10 @@ export declare function runUploadedHostedLoadCheck(input: {
|
|
|
90
90
|
postReadyWaitMs?: number;
|
|
91
91
|
}): Promise<HostedLoadCheckResult>;
|
|
92
92
|
export declare function sliceFirstPrimaryVerbAction(tape: AppPlaytestTape): AppPlaytestTape;
|
|
93
|
-
export declare function compareCausalFrames(firstPath: string, secondPath: string
|
|
93
|
+
export declare function compareCausalFrames(firstPath: string, secondPath: string, thresholds?: {
|
|
94
|
+
meanDeltaMin: number;
|
|
95
|
+
changedPixelRatioMin: number;
|
|
96
|
+
}): Promise<AppUploadCausalComparison>;
|
|
94
97
|
export declare function runUploadedHostedCausalCheck(input: {
|
|
95
98
|
client: ApiClient;
|
|
96
99
|
creatorUsername: string;
|
package/dist/apps/loadCheck.js
CHANGED
|
@@ -41,6 +41,10 @@ const DEFAULT_HOSTED_LOAD_TIMEOUT_MS = 15000;
|
|
|
41
41
|
const POST_READY_SETTLE_MS = 750;
|
|
42
42
|
const CAUSAL_FRAME_MEAN_DELTA_MIN = 0.025;
|
|
43
43
|
const CAUSAL_FRAME_CHANGED_PIXEL_RATIO_MIN = 0.12;
|
|
44
|
+
const CAUSAL_START_FRAME_MEAN_DELTA_MIN = 0.02;
|
|
45
|
+
const CAUSAL_START_FRAME_CHANGED_PIXEL_RATIO_MIN = 0.08;
|
|
46
|
+
const CAUSAL_PROGRESS_FRAME_MEAN_DELTA_MIN = 0.012;
|
|
47
|
+
const CAUSAL_PROGRESS_FRAME_CHANGED_PIXEL_RATIO_MIN = 0.05;
|
|
44
48
|
const LOAD_CHECK_SURFACE_ORDER = ['DESKTOP', 'MOBILE_LANDSCAPE', 'MOBILE_PORTRAIT'];
|
|
45
49
|
const GOOGLE_TELEMETRY_HOSTS = [
|
|
46
50
|
'analytics.google.com',
|
|
@@ -1124,7 +1128,10 @@ function sliceFirstPrimaryVerbAction(tape) {
|
|
|
1124
1128
|
async function hashFile(path) {
|
|
1125
1129
|
return (0, node_crypto_1.createHash)('sha256').update(await (0, promises_1.readFile)(path)).digest('hex');
|
|
1126
1130
|
}
|
|
1127
|
-
async function compareCausalFrames(firstPath, secondPath
|
|
1131
|
+
async function compareCausalFrames(firstPath, secondPath, thresholds = {
|
|
1132
|
+
meanDeltaMin: CAUSAL_FRAME_MEAN_DELTA_MIN,
|
|
1133
|
+
changedPixelRatioMin: CAUSAL_FRAME_CHANGED_PIXEL_RATIO_MIN,
|
|
1134
|
+
}) {
|
|
1128
1135
|
const readPixels = async (path) => {
|
|
1129
1136
|
return await (0, sharp_1.default)(path)
|
|
1130
1137
|
.resize(64, 64, { fit: 'fill' })
|
|
@@ -1149,7 +1156,7 @@ async function compareCausalFrames(firstPath, secondPath) {
|
|
|
1149
1156
|
return {
|
|
1150
1157
|
meanDelta,
|
|
1151
1158
|
changedPixelRatio,
|
|
1152
|
-
passed: meanDelta >=
|
|
1159
|
+
passed: meanDelta >= thresholds.meanDeltaMin || changedPixelRatio >= thresholds.changedPixelRatioMin,
|
|
1153
1160
|
};
|
|
1154
1161
|
}
|
|
1155
1162
|
async function runUploadedHostedCausalCheck(input) {
|
|
@@ -1218,20 +1225,43 @@ async function runUploadedHostedCausalCheck(input) {
|
|
|
1218
1225
|
throw new Error('agent_task_machine_causal_gate_artifact_changed_during_check');
|
|
1219
1226
|
}
|
|
1220
1227
|
const comparisons = {
|
|
1221
|
-
startOnlyVsReady: await compareCausalFrames(paths.startOnlyReady, paths.startOnly
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1228
|
+
startOnlyVsReady: await compareCausalFrames(paths.startOnlyReady, paths.startOnly, {
|
|
1229
|
+
meanDeltaMin: CAUSAL_START_FRAME_MEAN_DELTA_MIN,
|
|
1230
|
+
changedPixelRatioMin: CAUSAL_START_FRAME_CHANGED_PIXEL_RATIO_MIN,
|
|
1231
|
+
}),
|
|
1232
|
+
startOnlyVsZeroInput: await compareCausalFrames(paths.zeroInput, paths.startOnly, {
|
|
1233
|
+
meanDeltaMin: CAUSAL_START_FRAME_MEAN_DELTA_MIN,
|
|
1234
|
+
changedPixelRatioMin: CAUSAL_START_FRAME_CHANGED_PIXEL_RATIO_MIN,
|
|
1235
|
+
}),
|
|
1236
|
+
fullTapeVsZeroInput: await compareCausalFrames(paths.zeroInput, paths.fullTape, {
|
|
1237
|
+
meanDeltaMin: CAUSAL_PROGRESS_FRAME_MEAN_DELTA_MIN,
|
|
1238
|
+
changedPixelRatioMin: CAUSAL_PROGRESS_FRAME_CHANGED_PIXEL_RATIO_MIN,
|
|
1239
|
+
}),
|
|
1240
|
+
fullTapeVsStartOnly: await compareCausalFrames(paths.startOnly, paths.fullTape, {
|
|
1241
|
+
meanDeltaMin: CAUSAL_PROGRESS_FRAME_MEAN_DELTA_MIN,
|
|
1242
|
+
changedPixelRatioMin: CAUSAL_PROGRESS_FRAME_CHANGED_PIXEL_RATIO_MIN,
|
|
1243
|
+
}),
|
|
1244
|
+
fullTapeVsFirstAction: await compareCausalFrames(paths.firstAction, paths.fullTape, {
|
|
1245
|
+
meanDeltaMin: CAUSAL_PROGRESS_FRAME_MEAN_DELTA_MIN,
|
|
1246
|
+
changedPixelRatioMin: CAUSAL_PROGRESS_FRAME_CHANGED_PIXEL_RATIO_MIN,
|
|
1247
|
+
}),
|
|
1226
1248
|
};
|
|
1227
1249
|
if (!comparisons.startOnlyVsReady.passed || !comparisons.startOnlyVsZeroInput.passed) {
|
|
1228
|
-
throw new Error(
|
|
1250
|
+
throw new Error(`agent_task_machine_causal_gate_failed:start-only did not provably leave the title state. `
|
|
1251
|
+
+ `ready(mean=${comparisons.startOnlyVsReady.meanDelta.toFixed(4)},changed=${comparisons.startOnlyVsReady.changedPixelRatio.toFixed(4)}); `
|
|
1252
|
+
+ `zero(mean=${comparisons.startOnlyVsZeroInput.meanDelta.toFixed(4)},changed=${comparisons.startOnlyVsZeroInput.changedPixelRatio.toFixed(4)}). `
|
|
1253
|
+
+ 'Fix the start action, visible start-only outcome, or ready-relative timing.');
|
|
1229
1254
|
}
|
|
1230
1255
|
if (!comparisons.fullTapeVsZeroInput.passed || !comparisons.fullTapeVsStartOnly.passed) {
|
|
1231
|
-
throw new Error(
|
|
1256
|
+
throw new Error(`agent_task_machine_causal_gate_failed:full tape did not meaningfully beat zero-input and start-only controls. `
|
|
1257
|
+
+ `zero(mean=${comparisons.fullTapeVsZeroInput.meanDelta.toFixed(4)},changed=${comparisons.fullTapeVsZeroInput.changedPixelRatio.toFixed(4)}); `
|
|
1258
|
+
+ `start(mean=${comparisons.fullTapeVsStartOnly.meanDelta.toFixed(4)},changed=${comparisons.fullTapeVsStartOnly.changedPixelRatio.toFixed(4)}). `
|
|
1259
|
+
+ 'Fix the game or tape before upload.');
|
|
1232
1260
|
}
|
|
1233
1261
|
if (!comparisons.fullTapeVsFirstAction.passed) {
|
|
1234
|
-
throw new Error(
|
|
1262
|
+
throw new Error(`agent_task_machine_causal_gate_failed:first primary action trivially reached the full-tape outcome. `
|
|
1263
|
+
+ `action(mean=${comparisons.fullTapeVsFirstAction.meanDelta.toFixed(4)},changed=${comparisons.fullTapeVsFirstAction.changedPixelRatio.toFixed(4)}). `
|
|
1264
|
+
+ 'Add a sustained causal loop before upload.');
|
|
1235
1265
|
}
|
|
1236
1266
|
const teachingText = input.teachingText.trim();
|
|
1237
1267
|
const evidence = {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { AppSurface } from '@playdrop/types';
|
|
2
|
+
import type { AppTask } from '../catalogue';
|
|
3
|
+
import { type AppBuildArtifacts } from './build';
|
|
4
|
+
export type StaticHtmlMetadata = {
|
|
5
|
+
title: string;
|
|
6
|
+
description: string | null;
|
|
7
|
+
imagePath: string | null;
|
|
8
|
+
warnings: string[];
|
|
9
|
+
};
|
|
10
|
+
export type PreparedStaticHtmlProject = {
|
|
11
|
+
sourceTask: AppTask;
|
|
12
|
+
runtimeTask: AppTask;
|
|
13
|
+
artifacts: AppBuildArtifacts;
|
|
14
|
+
sourceHash: string;
|
|
15
|
+
runtimeHash: string;
|
|
16
|
+
metadata: StaticHtmlMetadata;
|
|
17
|
+
runtimeDir: string;
|
|
18
|
+
cleanup: () => Promise<void>;
|
|
19
|
+
};
|
|
20
|
+
export declare function parseStaticHtmlMetadata(projectDir: string, entryPoint?: string): StaticHtmlMetadata;
|
|
21
|
+
export declare function buildStaticGameSlug(title: string, taskId?: number): string;
|
|
22
|
+
export declare function prepareStaticHtmlProject(input: {
|
|
23
|
+
projectDir: string;
|
|
24
|
+
taskId?: number;
|
|
25
|
+
appName?: string;
|
|
26
|
+
primarySurface: AppSurface;
|
|
27
|
+
}): Promise<PreparedStaticHtmlProject>;
|
|
28
|
+
export declare function sha256File(filePath: string): string;
|
|
@@ -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/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.`);
|
|
@@ -85,7 +85,7 @@ export declare function buildListingCaptureFrameUrl({ webBase, currentUsername,
|
|
|
85
85
|
export declare function resolveListingCaptureSceneId(width: number, height: number): 'listing-landscape' | 'listing-portrait';
|
|
86
86
|
export declare function parseCaptureListingOptions(targetArg: string | undefined, options?: CaptureListingOptions): ParsedCaptureListingOptions;
|
|
87
87
|
export declare function assertSupportedListingEnvironment(platform?: NodeJS.Platform, macosVersion?: string): void;
|
|
88
|
-
export declare function resolveListingRecorderPath(
|
|
88
|
+
export declare function resolveListingRecorderPath(workerHome?: string): string;
|
|
89
89
|
export declare function buildSurfaceOutputPaths(outputDir: string, surface: AppSurface, useSurfacePrefix: boolean): {
|
|
90
90
|
rawVideoPath: string;
|
|
91
91
|
metadataPath: string;
|
|
@@ -59,6 +59,7 @@ const commandContext_1 = require("../commandContext");
|
|
|
59
59
|
const http_1 = require("../http");
|
|
60
60
|
const messages_1 = require("../messages");
|
|
61
61
|
const playwright_1 = require("../playwright");
|
|
62
|
+
const recorderRelease_1 = require("../recorderRelease");
|
|
62
63
|
const listingCaptureLease_1 = require("../listingCaptureLease");
|
|
63
64
|
const dev_1 = require("./dev");
|
|
64
65
|
const devRuntimeAssets_1 = require("./devRuntimeAssets");
|
|
@@ -204,8 +205,8 @@ function commandExists(command) {
|
|
|
204
205
|
async function ensureExecutableFile(filePath) {
|
|
205
206
|
await (0, promises_1.access)(filePath);
|
|
206
207
|
}
|
|
207
|
-
function resolveListingRecorderPath(
|
|
208
|
-
return (0,
|
|
208
|
+
function resolveListingRecorderPath(workerHome) {
|
|
209
|
+
return (0, recorderRelease_1.resolveMacWorkerRecorderPath)(workerHome);
|
|
209
210
|
}
|
|
210
211
|
function buildTimestampFolder(date = new Date()) {
|
|
211
212
|
const iso = date.toISOString();
|
|
@@ -755,7 +756,7 @@ async function ensureListingRecorderAvailable(recorderPath) {
|
|
|
755
756
|
return true;
|
|
756
757
|
}
|
|
757
758
|
catch {
|
|
758
|
-
(0, messages_1.printErrorWithHelp)(`Native recorder binary was not found at ${recorderPath}.`, ['
|
|
759
|
+
(0, messages_1.printErrorWithHelp)(`Native recorder binary was not found at ${recorderPath}.`, ['Start the PlayDrop worker once on this Mac to install the recorder from the PlayDrop CDN.'], { command: 'project capture' });
|
|
759
760
|
process.exitCode = 1;
|
|
760
761
|
return false;
|
|
761
762
|
}
|
|
@@ -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
|
};
|