@playdrop/playdrop-cli 0.17.21 → 0.17.24

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.
Files changed (30) hide show
  1. package/README.md +12 -0
  2. package/config/client-meta.json +4 -4
  3. package/dist/appUrls.d.ts +6 -0
  4. package/dist/appUrls.js +19 -0
  5. package/dist/commands/dev.d.ts +1 -0
  6. package/dist/commands/dev.js +161 -1
  7. package/dist/commands/devProceduralAssets.d.ts +66 -0
  8. package/dist/commands/devProceduralAssets.js +194 -0
  9. package/dist/commands/devServer.d.ts +16 -0
  10. package/dist/commands/devServer.js +140 -7
  11. package/dist/commands/validate.js +25 -2
  12. package/dist/index.js +3 -1
  13. package/node_modules/@playdrop/api-client/dist/client.d.ts +5 -2
  14. package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
  15. package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.d.ts +3 -1
  16. package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.d.ts.map +1 -1
  17. package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.js +22 -0
  18. package/node_modules/@playdrop/api-client/dist/domains/playtrade.d.ts +3 -2
  19. package/node_modules/@playdrop/api-client/dist/domains/playtrade.d.ts.map +1 -1
  20. package/node_modules/@playdrop/api-client/dist/domains/playtrade.js +12 -2
  21. package/node_modules/@playdrop/api-client/dist/index.d.ts +2 -1
  22. package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
  23. package/node_modules/@playdrop/api-client/dist/index.js +5 -0
  24. package/node_modules/@playdrop/config/client-meta.json +4 -4
  25. package/node_modules/@playdrop/types/dist/api.d.ts +24 -3
  26. package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
  27. package/node_modules/@playdrop/types/dist/api.js +10 -0
  28. package/node_modules/@playdrop/types/dist/playtrade.d.ts +36 -1
  29. package/node_modules/@playdrop/types/dist/playtrade.d.ts.map +1 -1
  30. package/package.json +1 -1
package/README.md CHANGED
@@ -123,6 +123,18 @@ playdrop project dev my-app
123
123
  playdrop project publish --env dev .
124
124
  ```
125
125
 
126
+ Procedural asset packs use the same dev command after their compiled `.mjs`, manifest, and preview files exist:
127
+
128
+ ```bash
129
+ npm run build
130
+ playdrop project dev .
131
+ playdrop project dev --asset vfx-cinder-fall .
132
+ playdrop project validate .
133
+ playdrop project publish .
134
+ ```
135
+
136
+ With no `--asset`, the printed hosted local-dev URL shows the pack grid. `--asset <name>` prints a URL that selects one local pack member directly in the canonical procedural viewer. Source type checking verifies `ProceduralAssetModuleV1`; `project validate` runs the compiled module unchanged in Chromium, and publish repeats that runtime gate.
137
+
126
138
  ## Game server SDK
127
139
 
128
140
  For a game with a `server` declaration, `project build`, `project check`, `project dev`, and upload preflight select the SDK reported by the environment's rooms runtime. The CLI verifies and downloads `playdrop-server-<version>.tgz` from the SDK CDN, installs its matching dependencies, and records them in `package.json`, `package-lock.json`, and `vendor/playdrop/`. Keep these files with the project. There is no npm registry release of `@playdrop/server` to install manually.
@@ -1,8 +1,8 @@
1
1
  {
2
- "version": "0.17.21",
3
- "build": 1,
4
- "runtimeSdkVersion": "0.17.21",
5
- "runtimeSdkBuild": 1,
2
+ "version": "0.17.24",
3
+ "build": 4,
4
+ "runtimeSdkVersion": "0.17.24",
5
+ "runtimeSdkBuild": 4,
6
6
  "clients": {
7
7
  "all": {
8
8
  "minimumVersion": "0.7.4"
package/dist/appUrls.d.ts CHANGED
@@ -25,6 +25,12 @@ export declare function buildPlatformDevUrl(webBase: string | null | undefined,
25
25
  localDevPort?: number | string | null;
26
26
  captureSession?: TaskCaptureSession | null;
27
27
  }): string;
28
+ export declare function buildPlatformProceduralPackDevUrl(webBase: string | null | undefined, input: {
29
+ creatorUsername: string;
30
+ packName: string;
31
+ assetName?: string | null;
32
+ localDevPort?: number | string | null;
33
+ }): string;
28
34
  export declare function buildUploadLaunchCheckUrl(webBase: string | null | undefined, input: AppUrlInput & {
29
35
  sessionId: string;
30
36
  }): string;
package/dist/appUrls.js CHANGED
@@ -8,6 +8,7 @@ exports.getAppTypeSlug = getAppTypeSlug;
8
8
  exports.buildPlatformPlayUrl = buildPlatformPlayUrl;
9
9
  exports.buildPlatformPrivateDraftPlayUrl = buildPlatformPrivateDraftPlayUrl;
10
10
  exports.buildPlatformDevUrl = buildPlatformDevUrl;
11
+ exports.buildPlatformProceduralPackDevUrl = buildPlatformProceduralPackDevUrl;
11
12
  exports.buildUploadLaunchCheckUrl = buildUploadLaunchCheckUrl;
12
13
  const DEFAULT_WEB_BASE = "https://www.playdrop.ai";
13
14
  function appendTaskCaptureSession(url, session) {
@@ -102,6 +103,24 @@ function buildPlatformDevUrl(webBase, input) {
102
103
  appendTaskCaptureSession(url, input.captureSession);
103
104
  return url.toString();
104
105
  }
106
+ function buildPlatformProceduralPackDevUrl(webBase, input) {
107
+ const base = normalizePlaydropWebBase(webBase);
108
+ const creator = encodeURIComponent(input.creatorUsername);
109
+ const packName = encodeURIComponent(input.packName);
110
+ const url = new URL(`${base}/creators/${creator}/asset-packs/${packName}/dev`);
111
+ if (input.assetName?.trim())
112
+ url.searchParams.set("asset", input.assetName.trim());
113
+ if (input.localDevPort !== null && input.localDevPort !== undefined) {
114
+ const port = typeof input.localDevPort === "number"
115
+ ? input.localDevPort
116
+ : Number.parseInt(String(input.localDevPort), 10);
117
+ if (!Number.isInteger(port) || port <= 0 || port > 65535) {
118
+ throw new Error("invalid_local_dev_port");
119
+ }
120
+ url.searchParams.set("localDevPort", String(port));
121
+ }
122
+ return url.toString();
123
+ }
105
124
  function buildUploadLaunchCheckUrl(webBase, input) {
106
125
  const base = normalizePlaydropWebBase(webBase);
107
126
  const creator = encodeURIComponent(input.creatorUsername);
@@ -3,6 +3,7 @@ export declare function formatDevRuntimeAssetManifestFailure(error: unknown): {
3
3
  suggestions: string[];
4
4
  };
5
5
  export declare function dev(targetArg: string | undefined, appOption?: string, devOptions?: {
6
+ asset?: string;
6
7
  devAuth?: string;
7
8
  player?: string | number;
8
9
  }): Promise<void>;
@@ -20,6 +20,116 @@ const tweakStaleness_1 = require("../tweakStaleness");
20
20
  const devShared_1 = require("./devShared");
21
21
  const devGameServer_1 = require("./devGameServer");
22
22
  const taskCaptureSession_1 = require("./taskCaptureSession");
23
+ const devProceduralAssets_1 = require("./devProceduralAssets");
24
+ async function devProceduralPack(target, devRouterPort) {
25
+ const cataloguePath = (0, node_path_1.resolve)(target.rootDir, target.pack.cataloguePath);
26
+ const projectInfo = (0, devShared_1.findProjectInfo)(cataloguePath);
27
+ const devScriptAvailable = Boolean(projectInfo.projectDir && projectInfo.packageJson && typeof projectInfo.packageJson.scripts?.dev === "string");
28
+ await (0, commandContext_1.withEnvironment)("project dev", "Starting the procedural asset pack dev server", async ({ client, envConfig, account, workspaceAuth }) => {
29
+ let currentUsername = workspaceAuth?.config.ownerUsername?.trim() ?? account?.username?.trim() ?? target.pack.username?.trim() ?? "";
30
+ if (!currentUsername) {
31
+ try {
32
+ currentUsername = await (0, devShared_1.fetchDevUsername)(client);
33
+ }
34
+ catch (error) {
35
+ if (error instanceof http_1.CLIUnsupportedClientError)
36
+ return;
37
+ if (error instanceof types_1.UnsupportedClientError) {
38
+ (0, http_1.handleUnsupportedError)(error, "Authentication");
39
+ process.exitCode = 1;
40
+ return;
41
+ }
42
+ if (error instanceof types_1.ApiError) {
43
+ (0, messages_1.printErrorWithHelp)(`Could not fetch your account (status ${error.status}).`, ['Run "playdrop auth login" to refresh your session.'], { command: "project dev" });
44
+ process.exitCode = 1;
45
+ return;
46
+ }
47
+ if ((0, devShared_1.isNetworkError)(error)) {
48
+ (0, messages_1.printNetworkIssue)("Could not reach the Playdrop API to resolve your account.", "project dev");
49
+ process.exitCode = 1;
50
+ return;
51
+ }
52
+ throw error;
53
+ }
54
+ }
55
+ let registration;
56
+ try {
57
+ registration = (0, devProceduralAssets_1.buildLocalProceduralPackManifest)({
58
+ creatorUsername: currentUsername,
59
+ target,
60
+ port: devRouterPort,
61
+ });
62
+ }
63
+ catch (error) {
64
+ (0, messages_1.printErrorWithHelp)(error instanceof Error ? error.message : "Could not prepare the procedural asset pack.", ['Run the pack build, then retry "playdrop project dev".'], { command: "project dev" });
65
+ process.exitCode = 1;
66
+ return;
67
+ }
68
+ let handle;
69
+ try {
70
+ handle = await (0, devServer_1.startProceduralPackDevServer)({
71
+ creatorUsername: currentUsername,
72
+ packName: target.pack.name,
73
+ cataloguePath,
74
+ rootDir: target.rootDir,
75
+ port: devRouterPort,
76
+ projectInfo,
77
+ proceduralPackManifest: registration,
78
+ });
79
+ }
80
+ catch (error) {
81
+ const message = typeof error?.message === "string" ? error.message : "";
82
+ const mountConflict = (0, devServer_1.parseMountConflictError)(message);
83
+ if (mountConflict) {
84
+ (0, messages_1.printErrorWithHelp)(`A different dev session already owns ${mountConflict.ref}.`, [`Active owner pid: ${mountConflict.ownerPid}.`, `Mounted project: ${mountConflict.repoRoot}.`], { command: "project dev" });
85
+ }
86
+ else if (message.startsWith("dev_router_incompatible:")) {
87
+ (0, messages_1.printErrorWithHelp)(`An incompatible shared dev router is already running on port ${devRouterPort}.`, ['Stop the existing dev router process, then retry "playdrop project dev".'], { command: "project dev" });
88
+ }
89
+ else {
90
+ (0, messages_1.printErrorWithHelp)(error?.message || `Failed to start the shared dev router on port ${devRouterPort}.`, ["Close the conflicting process or wait for the stale mount to exit."], { command: "project dev" });
91
+ }
92
+ process.exitCode = 1;
93
+ return;
94
+ }
95
+ let shuttingDown = false;
96
+ const handleShutdown = () => {
97
+ if (shuttingDown)
98
+ return;
99
+ shuttingDown = true;
100
+ process.off("SIGINT", handleShutdown);
101
+ process.off("SIGTERM", handleShutdown);
102
+ handle.close().catch((closeError) => {
103
+ console.error(closeError instanceof Error ? closeError.message : closeError);
104
+ });
105
+ };
106
+ process.on("SIGINT", handleShutdown);
107
+ process.on("SIGTERM", handleShutdown);
108
+ console.log(`Procedural asset pack mounted at ${handle.manifestUrl}`);
109
+ console.log(`Serving ${target.assets.length} assets from ${(0, node_path_1.relative)(process.cwd(), cataloguePath) || cataloguePath}`);
110
+ if (projectInfo.projectDir && !devScriptAvailable && projectInfo.packageJsonPath) {
111
+ const projectLabel = (0, devShared_1.formatProjectLabel)(projectInfo);
112
+ if (projectLabel) {
113
+ console.log(`package.json detected at ${projectLabel}, but no "dev" script was found. Run your build/watch scripts manually if needed.`);
114
+ }
115
+ }
116
+ if (envConfig.webBase) {
117
+ const viewerUrl = (0, appUrls_1.buildPlatformProceduralPackDevUrl)(envConfig.webBase, {
118
+ creatorUsername: currentUsername,
119
+ packName: target.pack.name,
120
+ assetName: target.selectedAssetName,
121
+ localDevPort: devRouterPort === devServer_1.DEV_ROUTER_PORT ? null : devRouterPort,
122
+ });
123
+ console.log("\nTest your procedural asset pack at:");
124
+ console.log(` ${viewerUrl}`);
125
+ console.log("\nValidate before publishing with:");
126
+ console.log(`playdrop project validate ${target.rootDir}`);
127
+ }
128
+ else {
129
+ console.log(`\nOpen ${handle.manifestUrl} to inspect the local pack manifest.`);
130
+ }
131
+ }, { workspacePath: target.rootDir });
132
+ }
23
133
  function formatDevRuntimeAssetManifestFailure(error) {
24
134
  const rawMessage = error instanceof Error ? error.message : String(error ?? "unknown_error");
25
135
  if (rawMessage.startsWith("dev_runtime_asset_ref_invalid:")) {
@@ -116,8 +226,29 @@ function formatDevRuntimeAssetManifestFailure(error) {
116
226
  async function dev(targetArg, appOption, devOptions = {}) {
117
227
  const devRouterPort = resolveCliDevRouterPort();
118
228
  let resolvedTarget;
229
+ let proceduralPackTarget;
119
230
  try {
120
- resolvedTarget = (0, devShared_1.resolveDevTarget)(targetArg, appOption);
231
+ if (appOption && devOptions.asset) {
232
+ const error = new Error("Use either --app or --asset, not both.");
233
+ error.code = "dev_selector_conflict";
234
+ throw error;
235
+ }
236
+ if (devOptions.asset) {
237
+ proceduralPackTarget = (0, devProceduralAssets_1.resolveProceduralPackDevTarget)(targetArg, devOptions.asset);
238
+ }
239
+ else {
240
+ try {
241
+ resolvedTarget = (0, devShared_1.resolveDevTarget)(targetArg, appOption);
242
+ }
243
+ catch (error) {
244
+ if (!appOption && error?.code === "empty_catalogue") {
245
+ proceduralPackTarget = (0, devProceduralAssets_1.resolveProceduralPackDevTarget)(targetArg);
246
+ }
247
+ else {
248
+ throw error;
249
+ }
250
+ }
251
+ }
121
252
  }
122
253
  catch (error) {
123
254
  const code = error?.code;
@@ -146,6 +277,23 @@ async function dev(targetArg, appOption, devOptions = {}) {
146
277
  case "invalid_catalogue":
147
278
  suggestions = ['Fix the catalogue.json file and rerun "playdrop project dev".'];
148
279
  break;
280
+ case "dev_selector_conflict":
281
+ suggestions = ["Choose an app with --app or one procedural pack member with --asset."];
282
+ break;
283
+ case "invalid_asset_target":
284
+ suggestions = ['Pass the procedural pack directory, for example "playdrop project dev --asset sword .".'];
285
+ break;
286
+ case "procedural_pack_missing":
287
+ case "procedural_pack_required":
288
+ case "procedural_pack_empty":
289
+ case "procedural_pack_remote_member":
290
+ case "procedural_pack_duplicate_member":
291
+ case "procedural_pack_asset_unsupported":
292
+ case "procedural_asset_not_found":
293
+ suggestions = [
294
+ "Use a directory containing exactly one procedural asset pack whose members are local owned assets.",
295
+ ];
296
+ break;
149
297
  default:
150
298
  suggestions = ["Provide an app name, HTML file path, or run inside a Playdrop workspace."];
151
299
  break;
@@ -154,6 +302,18 @@ async function dev(targetArg, appOption, devOptions = {}) {
154
302
  process.exitCode = 1;
155
303
  return;
156
304
  }
305
+ if (proceduralPackTarget) {
306
+ if (devOptions.devAuth || devOptions.player !== undefined) {
307
+ (0, messages_1.printErrorWithHelp)("--dev-auth and --player apply only to app development.", ["Remove the app authentication options when previewing a procedural asset pack."], { command: "project dev" });
308
+ process.exitCode = 1;
309
+ return;
310
+ }
311
+ await devProceduralPack(proceduralPackTarget, devRouterPort);
312
+ return;
313
+ }
314
+ if (!resolvedTarget) {
315
+ throw new Error("dev_target_resolution_missing");
316
+ }
157
317
  let appName = resolvedTarget.appName;
158
318
  const filePath = resolvedTarget.htmlPath;
159
319
  if (!(0, node_fs_1.existsSync)(filePath) || !(0, node_fs_1.statSync)(filePath).isFile()) {
@@ -0,0 +1,66 @@
1
+ import type { AssetPackTask, PackOwnedAssetTask } from "../catalogue";
2
+ export declare const LOCAL_PROCEDURAL_PACK_PROTOCOL: "playdrop-local-procedural-pack@1";
3
+ export type ResolvedProceduralPackDevTarget = {
4
+ rootDir: string;
5
+ pack: AssetPackTask;
6
+ assets: PackOwnedAssetTask[];
7
+ selectedAssetName: string | null;
8
+ };
9
+ export type LocalProceduralPackFileRecord = {
10
+ absolutePath: string;
11
+ contentType: string;
12
+ sizeBytes: number;
13
+ };
14
+ export type LocalProceduralPackManifest = {
15
+ protocol: typeof LOCAL_PROCEDURAL_PACK_PROTOCOL;
16
+ pack: {
17
+ creatorUsername: string;
18
+ name: string;
19
+ displayName: string;
20
+ version: string;
21
+ };
22
+ assets: Array<{
23
+ name: string;
24
+ displayName: string;
25
+ description: string | null;
26
+ category: "MODEL_3D";
27
+ subcategory: "procedural";
28
+ format: "CUSTOM";
29
+ files: {
30
+ primary: {
31
+ url: string;
32
+ contentType: "text/javascript";
33
+ };
34
+ manifest: {
35
+ url: string;
36
+ contentType: "application/json";
37
+ };
38
+ preview: {
39
+ url: string;
40
+ contentType: string;
41
+ };
42
+ };
43
+ }>;
44
+ };
45
+ export type LocalProceduralPackManifestRegistration = {
46
+ response: LocalProceduralPackManifest;
47
+ assetFiles: Record<string, Record<"primary" | "manifest" | "preview", LocalProceduralPackFileRecord>>;
48
+ };
49
+ export declare function resolveProceduralPackDevTarget(targetArg: string | undefined, assetName?: string): ResolvedProceduralPackDevTarget;
50
+ export declare function buildLocalProceduralPackManifestUrl(input: {
51
+ creatorUsername: string;
52
+ packName: string;
53
+ port: number;
54
+ }): string;
55
+ export declare function buildLocalProceduralAssetFileUrl(input: {
56
+ creatorUsername: string;
57
+ packName: string;
58
+ assetName: string;
59
+ role: "primary" | "manifest" | "preview";
60
+ port: number;
61
+ }): string;
62
+ export declare function buildLocalProceduralPackManifest(input: {
63
+ creatorUsername: string;
64
+ target: ResolvedProceduralPackDevTarget;
65
+ port: number;
66
+ }): LocalProceduralPackManifestRegistration;
@@ -0,0 +1,194 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LOCAL_PROCEDURAL_PACK_PROTOCOL = void 0;
4
+ exports.resolveProceduralPackDevTarget = resolveProceduralPackDevTarget;
5
+ exports.buildLocalProceduralPackManifestUrl = buildLocalProceduralPackManifestUrl;
6
+ exports.buildLocalProceduralAssetFileUrl = buildLocalProceduralAssetFileUrl;
7
+ exports.buildLocalProceduralPackManifest = buildLocalProceduralPackManifest;
8
+ const node_fs_1 = require("node:fs");
9
+ const node_path_1 = require("node:path");
10
+ const catalogue_1 = require("../catalogue");
11
+ const devShared_1 = require("./devShared");
12
+ exports.LOCAL_PROCEDURAL_PACK_PROTOCOL = "playdrop-local-procedural-pack@1";
13
+ function findNearestCatalogue(startDir) {
14
+ let current = (0, node_path_1.resolve)(startDir);
15
+ while (true) {
16
+ const candidate = (0, node_path_1.join)(current, "catalogue.json");
17
+ if ((0, node_fs_1.existsSync)(candidate) && (0, node_fs_1.statSync)(candidate).isFile())
18
+ return candidate;
19
+ const parent = (0, node_path_1.dirname)(current);
20
+ if (parent === current)
21
+ return null;
22
+ current = parent;
23
+ }
24
+ }
25
+ function resolveTargetRoot(targetArg) {
26
+ if (!targetArg) {
27
+ const cataloguePath = findNearestCatalogue(process.cwd());
28
+ if (!cataloguePath) {
29
+ throw (0, devShared_1.createDevError)("catalogue_missing", 'Run "playdrop project dev" inside a workspace or provide a directory containing catalogue.json.');
30
+ }
31
+ return (0, node_path_1.dirname)(cataloguePath);
32
+ }
33
+ const target = (0, node_path_1.resolve)(targetArg);
34
+ if (!(0, node_fs_1.existsSync)(target) || !(0, node_fs_1.statSync)(target).isDirectory()) {
35
+ throw (0, devShared_1.createDevError)("invalid_asset_target", "Procedural asset pack development requires a workspace directory containing catalogue.json.");
36
+ }
37
+ if (!(0, node_fs_1.existsSync)((0, node_path_1.join)(target, "catalogue.json"))) {
38
+ throw (0, devShared_1.createDevError)("catalogue_missing", `Directory ${target} does not contain catalogue.json.`);
39
+ }
40
+ return target;
41
+ }
42
+ function resolveOrderedLocalAssets(pack) {
43
+ const ownedByName = new Map(pack.ownedAssets.map((asset) => [asset.name, asset]));
44
+ const ordered = [];
45
+ const seen = new Set();
46
+ for (const member of pack.assets) {
47
+ const normalized = member.trim();
48
+ const direct = ownedByName.get(normalized);
49
+ const prefixedName = pack.username && normalized.startsWith(`${pack.username}/`)
50
+ ? normalized.slice(pack.username.length + 1)
51
+ : null;
52
+ const asset = direct ?? (prefixedName ? ownedByName.get(prefixedName) : undefined);
53
+ if (!asset) {
54
+ throw (0, devShared_1.createDevError)("procedural_pack_remote_member", `Asset pack "${pack.name}" contains non-local member "${member}". Local procedural pack dev supports owned assets only.`);
55
+ }
56
+ if (seen.has(asset.name)) {
57
+ throw (0, devShared_1.createDevError)("procedural_pack_duplicate_member", `Asset pack "${pack.name}" contains duplicate member "${asset.name}".`);
58
+ }
59
+ seen.add(asset.name);
60
+ ordered.push(asset);
61
+ }
62
+ if (ordered.length === 0) {
63
+ throw (0, devShared_1.createDevError)("procedural_pack_empty", `Asset pack "${pack.name}" has no local members to preview.`);
64
+ }
65
+ return ordered;
66
+ }
67
+ function assertProceduralAssetShape(asset) {
68
+ if (asset.category !== "MODEL_3D" || asset.subcategory !== "procedural" || asset.format !== "CUSTOM") {
69
+ throw (0, devShared_1.createDevError)("procedural_pack_asset_unsupported", `Asset "${asset.name}" must use category MODEL_3D, subcategory procedural, and format CUSTOM.`);
70
+ }
71
+ }
72
+ function resolveProceduralPackDevTarget(targetArg, assetName) {
73
+ const rootDir = resolveTargetRoot(targetArg);
74
+ const lookup = (0, catalogue_1.resolveCatalogueEntries)(rootDir);
75
+ if (lookup.errors.length > 0) {
76
+ const details = lookup.errors.join("\n");
77
+ if (/file not found|non-empty file/iu.test(details)) {
78
+ throw (0, devShared_1.createDevError)("procedural_asset_unbuilt", `${details}\nBuild the pack before running project dev.`);
79
+ }
80
+ throw (0, devShared_1.createDevError)("invalid_catalogue", details);
81
+ }
82
+ if (lookup.assetPacks.length === 0) {
83
+ throw (0, devShared_1.createDevError)("procedural_pack_missing", `No asset pack was found under ${rootDir}.`);
84
+ }
85
+ if (lookup.assetPacks.length > 1) {
86
+ throw (0, devShared_1.createDevError)("procedural_pack_required", `Multiple asset packs were found under ${rootDir}. Run the command from a directory containing one pack.`);
87
+ }
88
+ const pack = lookup.assetPacks[0];
89
+ const assets = resolveOrderedLocalAssets(pack);
90
+ assets.forEach(assertProceduralAssetShape);
91
+ const selectedAssetName = assetName?.trim() || null;
92
+ if (selectedAssetName && !assets.some((asset) => asset.name === selectedAssetName)) {
93
+ throw (0, devShared_1.createDevError)("procedural_asset_not_found", `Asset "${selectedAssetName}" was not found in local members of pack "${pack.name}".`);
94
+ }
95
+ return { rootDir, pack, assets, selectedAssetName };
96
+ }
97
+ function buildLocalProceduralPackManifestUrl(input) {
98
+ return `http://127.0.0.1:${input.port}/_playdrop/asset-packs/dev/${encodeURIComponent(input.creatorUsername)}/${encodeURIComponent(input.packName)}/manifest`;
99
+ }
100
+ function buildLocalProceduralAssetFileUrl(input) {
101
+ return `http://127.0.0.1:${input.port}/_playdrop/asset-packs/dev/${encodeURIComponent(input.creatorUsername)}/${encodeURIComponent(input.packName)}/assets/${encodeURIComponent(input.assetName)}/files/${input.role}`;
102
+ }
103
+ function previewContentType(path) {
104
+ switch ((0, node_path_1.extname)(path).toLowerCase()) {
105
+ case ".png":
106
+ return "image/png";
107
+ case ".jpg":
108
+ case ".jpeg":
109
+ return "image/jpeg";
110
+ case ".webp":
111
+ return "image/webp";
112
+ default:
113
+ throw (0, devShared_1.createDevError)("procedural_preview_unsupported", `Procedural preview ${path} must be PNG, JPEG, or WebP.`);
114
+ }
115
+ }
116
+ function requireBuiltFile(asset, role, rootDir) {
117
+ const absolutePath = asset.filePaths[role];
118
+ if (!absolutePath) {
119
+ throw (0, devShared_1.createDevError)("procedural_asset_unbuilt", `Asset "${asset.name}" is missing files.${role}. Build the pack before running project dev.`);
120
+ }
121
+ let stats;
122
+ try {
123
+ stats = (0, node_fs_1.statSync)(absolutePath);
124
+ }
125
+ catch {
126
+ throw (0, devShared_1.createDevError)("procedural_asset_unbuilt", `Asset "${asset.name}" files.${role} was not found at ${absolutePath}. Build the pack before running project dev.`);
127
+ }
128
+ if (!stats.isFile() || stats.size === 0) {
129
+ throw (0, devShared_1.createDevError)("procedural_asset_unbuilt", `Asset "${asset.name}" files.${role} must be an existing, non-empty file.`);
130
+ }
131
+ let canonicalRoot;
132
+ let canonicalPath;
133
+ try {
134
+ canonicalRoot = (0, node_fs_1.realpathSync)(rootDir);
135
+ canonicalPath = (0, node_fs_1.realpathSync)(absolutePath);
136
+ }
137
+ catch {
138
+ throw (0, devShared_1.createDevError)("procedural_asset_unbuilt", `Asset "${asset.name}" files.${role} is no longer accessible. Build the pack before running project dev.`);
139
+ }
140
+ const pathFromRoot = (0, node_path_1.relative)(canonicalRoot, canonicalPath);
141
+ if (pathFromRoot === ".." || pathFromRoot.startsWith(`..${node_path_1.sep}`) || (0, node_path_1.isAbsolute)(pathFromRoot)) {
142
+ throw (0, devShared_1.createDevError)("procedural_asset_outside_root", `Asset "${asset.name}" files.${role} must stay inside the procedural pack directory.`);
143
+ }
144
+ if (role === "primary" && (0, node_path_1.extname)(absolutePath).toLowerCase() !== ".mjs") {
145
+ throw (0, devShared_1.createDevError)("procedural_asset_unbuilt", `Asset "${asset.name}" files.primary must be a compiled .mjs file.`);
146
+ }
147
+ return {
148
+ absolutePath,
149
+ contentType: role === "primary" ? "text/javascript" : role === "manifest" ? "application/json" : previewContentType(absolutePath),
150
+ sizeBytes: stats.size,
151
+ };
152
+ }
153
+ function buildLocalProceduralPackManifest(input) {
154
+ const assetFiles = {};
155
+ const assets = input.target.assets.map((asset) => {
156
+ const primary = requireBuiltFile(asset, "primary", input.target.rootDir);
157
+ const manifest = requireBuiltFile(asset, "manifest", input.target.rootDir);
158
+ const preview = requireBuiltFile(asset, "preview", input.target.rootDir);
159
+ assetFiles[asset.name] = { primary, manifest, preview };
160
+ const buildUrl = (role) => buildLocalProceduralAssetFileUrl({
161
+ creatorUsername: input.creatorUsername,
162
+ packName: input.target.pack.name,
163
+ assetName: asset.name,
164
+ role,
165
+ port: input.port,
166
+ });
167
+ return {
168
+ name: asset.name,
169
+ displayName: asset.displayName?.trim() || asset.name,
170
+ description: asset.description?.trim() || null,
171
+ category: "MODEL_3D",
172
+ subcategory: "procedural",
173
+ format: "CUSTOM",
174
+ files: {
175
+ primary: { url: buildUrl("primary"), contentType: "text/javascript" },
176
+ manifest: { url: buildUrl("manifest"), contentType: "application/json" },
177
+ preview: { url: buildUrl("preview"), contentType: preview.contentType },
178
+ },
179
+ };
180
+ });
181
+ return {
182
+ response: {
183
+ protocol: exports.LOCAL_PROCEDURAL_PACK_PROTOCOL,
184
+ pack: {
185
+ creatorUsername: input.creatorUsername,
186
+ name: input.target.pack.name,
187
+ displayName: input.target.pack.displayName?.trim() || input.target.pack.name,
188
+ version: input.target.pack.version,
189
+ },
190
+ assets,
191
+ },
192
+ assetFiles,
193
+ };
194
+ }
@@ -1,6 +1,7 @@
1
1
  import { type ChildProcess } from "node:child_process";
2
2
  import http from "node:http";
3
3
  import type { DevRuntimeAssetManifestRegistration } from "./devRuntimeAssets";
4
+ import { type LocalProceduralPackManifestRegistration } from "./devProceduralAssets";
4
5
  import type { ProjectInfo } from "./devShared";
5
6
  export interface StartDevServerOptions {
6
7
  appName: string;
@@ -21,6 +22,12 @@ export interface DevServerHandle {
21
22
  appUrl: string;
22
23
  close(): Promise<void>;
23
24
  }
25
+ export interface ProceduralPackDevServerHandle {
26
+ devProcess: ChildProcess | null;
27
+ startedDevProcess: boolean;
28
+ manifestUrl: string;
29
+ close(): Promise<void>;
30
+ }
24
31
  export declare const DEV_ROUTER_PORT = 8888;
25
32
  type MountConflictDetails = {
26
33
  ref: string;
@@ -46,6 +53,15 @@ export declare function updateMountedDevRuntimeAssetManifest(input: {
46
53
  port?: number;
47
54
  }): Promise<void>;
48
55
  export declare function startDevServer(options: StartDevServerOptions): Promise<DevServerHandle>;
56
+ export declare function startProceduralPackDevServer(options: {
57
+ creatorUsername: string;
58
+ packName: string;
59
+ cataloguePath: string;
60
+ rootDir: string;
61
+ port: number;
62
+ projectInfo: ProjectInfo;
63
+ proceduralPackManifest: LocalProceduralPackManifestRegistration;
64
+ }): Promise<ProceduralPackDevServerHandle>;
49
65
  export declare function isDevServerAvailable(input: {
50
66
  creatorUsername: string;
51
67
  appType: string;