phibelle-kit 1.0.32 → 1.0.34
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/dist/commands/init-scene.d.ts +1 -1
- package/dist/commands/init-scene.js +80 -11
- package/dist/commands/watch-scene.js +44 -28
- package/dist/index.js +6 -1
- package/dist/lib/manifest.d.ts +8 -1
- package/dist/lib/manifest.js +13 -4
- package/dist/lib/minimal-scene.d.ts +2 -0
- package/dist/lib/minimal-scene.js +28 -0
- package/dist/lib/scene/scene-packaging.d.ts +1 -1
- package/dist/lib/scene/scene-packaging.js +11 -3
- package/dist/lib/template-ids.d.ts +6 -0
- package/dist/lib/template-ids.js +9 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function initSceneCommand(): void
|
|
1
|
+
export declare function initSceneCommand(): Promise<void>;
|
|
@@ -1,16 +1,85 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import * as fs from "node:fs/promises";
|
|
4
|
+
import readline from "node:readline";
|
|
5
|
+
import { SCENE_FILE_NAME } from "../lib/constants.js";
|
|
6
|
+
import { unpackageScene } from "../lib/scene/scene-packaging.js";
|
|
7
|
+
import { setupSceneDirectory } from "../scene-setup/setup.js";
|
|
8
|
+
import { MINIMAL_SCENE_JSON } from "../lib/minimal-scene.js";
|
|
9
|
+
import { TEMPLATE_IDS } from "../lib/template-ids.js";
|
|
10
|
+
function askQuestion(rl, prompt) {
|
|
11
|
+
return new Promise((resolve) => {
|
|
12
|
+
rl.question(prompt, resolve);
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function sanitizeProjectName(input) {
|
|
16
|
+
return input
|
|
17
|
+
.trim()
|
|
18
|
+
.replace(/[<>:"/\\|?*\x00-\x1F]/g, "-")
|
|
19
|
+
.replace(/\s+/g, "-")
|
|
20
|
+
.replace(/\.+$/g, "")
|
|
21
|
+
.replace(/-+/g, "-")
|
|
22
|
+
.replace(/^-|-$/g, "");
|
|
23
|
+
}
|
|
24
|
+
export async function initSceneCommand() {
|
|
25
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
26
|
+
const parentDir = process.cwd();
|
|
5
27
|
console.log();
|
|
6
|
-
console.log(chalk.bold.cyan("
|
|
7
|
-
console.log(chalk.gray(`
|
|
28
|
+
console.log(chalk.bold.cyan(" Initialize a new Phibelle scene project"));
|
|
29
|
+
console.log(chalk.gray(` Parent dir: ${parentDir}`));
|
|
8
30
|
console.log();
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
31
|
+
const projectName = await promptProjectName(rl);
|
|
32
|
+
const templateId = await promptTemplateId(rl);
|
|
33
|
+
const safeDirName = sanitizeProjectName(projectName) || "phibelle-scene";
|
|
34
|
+
const sceneDir = path.join(parentDir, safeDirName);
|
|
35
|
+
await ensureDirectoryDoesNotExist(sceneDir);
|
|
36
|
+
await fs.mkdir(sceneDir, { recursive: false });
|
|
37
|
+
setupSceneDirectory(sceneDir);
|
|
38
|
+
await fs.writeFile(path.join(sceneDir, SCENE_FILE_NAME), MINIMAL_SCENE_JSON, "utf8");
|
|
39
|
+
await unpackageScene(MINIMAL_SCENE_JSON, sceneDir, "template", templateId);
|
|
40
|
+
console.log(chalk.green(" ✓ Project initialized"));
|
|
41
|
+
console.log(chalk.gray(` Directory: ${sceneDir}`));
|
|
42
|
+
console.log(chalk.yellow(" Next steps:"));
|
|
43
|
+
console.log(chalk.cyan(` 1. cd "${safeDirName}"`));
|
|
44
|
+
console.log(chalk.cyan(` 2. npm install`));
|
|
45
|
+
console.log(chalk.cyan(` 3. npm run watch`));
|
|
46
|
+
console.log(chalk.gray(` Then open the editor URL shown by watch and accept the template scene to sync.`));
|
|
15
47
|
console.log();
|
|
48
|
+
rl.close();
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
51
|
+
async function promptProjectName(rl) {
|
|
52
|
+
while (true) {
|
|
53
|
+
const answer = await askQuestion(rl, chalk.cyan(" Project name: "));
|
|
54
|
+
const trimmed = answer.trim();
|
|
55
|
+
if (trimmed)
|
|
56
|
+
return trimmed;
|
|
57
|
+
console.log(chalk.yellow(" Please enter a non-empty project name."));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async function promptTemplateId(rl) {
|
|
61
|
+
console.log(chalk.cyan(" Template:"));
|
|
62
|
+
TEMPLATE_IDS.forEach((id, i) => {
|
|
63
|
+
console.log(chalk.gray(` ${i + 1}. ${id}`));
|
|
64
|
+
});
|
|
65
|
+
while (true) {
|
|
66
|
+
const answer = (await askQuestion(rl, chalk.cyan(" Choose template [1-" + TEMPLATE_IDS.length + "]: "))).trim();
|
|
67
|
+
const num = parseInt(answer, 10);
|
|
68
|
+
if (num >= 1 && num <= TEMPLATE_IDS.length) {
|
|
69
|
+
return TEMPLATE_IDS[num - 1];
|
|
70
|
+
}
|
|
71
|
+
if (TEMPLATE_IDS.includes(answer)) {
|
|
72
|
+
return answer;
|
|
73
|
+
}
|
|
74
|
+
console.log(chalk.yellow(` Please enter 1-${TEMPLATE_IDS.length} or a template ID.`));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async function ensureDirectoryDoesNotExist(sceneDir) {
|
|
78
|
+
try {
|
|
79
|
+
await fs.access(sceneDir);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
throw new Error(`Target folder already exists: ${sceneDir}. Choose a different project name.`);
|
|
16
85
|
}
|
|
@@ -4,20 +4,19 @@ import * as fs from "node:fs";
|
|
|
4
4
|
import readline from "node:readline";
|
|
5
5
|
import { WebSocketServer } from "ws";
|
|
6
6
|
import { WS_PORT, SCENE_SYNC_TYPE, SCENE_FILE_NAME } from "../lib/constants.js";
|
|
7
|
-
import {
|
|
7
|
+
import { getRequiredManifestIds } from "../lib/manifest.js";
|
|
8
8
|
import { unpackageScene, parseSceneJson, packageSceneFromFilesystem } from "../lib/scene/scene-packaging.js";
|
|
9
9
|
import { createScriptWatcher } from "../lib/scene/script-watcher.js";
|
|
10
10
|
import { toErrorMessage } from "../lib/utils.js";
|
|
11
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
12
11
|
export const BASE_URL = process.env.NODE_ENV === "development" ? "http://localhost:3131" : "https://phibelle.studio";
|
|
13
12
|
export async function watchSceneCommand() {
|
|
14
13
|
const sceneDir = process.cwd();
|
|
15
|
-
const
|
|
14
|
+
const { sceneId, templateId } = getRequiredManifestIds(sceneDir);
|
|
16
15
|
let lastPrintedEditorLink = null;
|
|
17
16
|
console.log();
|
|
18
17
|
console.log(chalk.bold.cyan(" Watching for scene sync (WebSocket)"));
|
|
19
18
|
console.log(chalk.gray(` Dir: ${sceneDir}`));
|
|
20
|
-
lastPrintedEditorLink = printEditorLink(
|
|
19
|
+
lastPrintedEditorLink = printEditorLink(sceneId, templateId, lastPrintedEditorLink);
|
|
21
20
|
console.log(chalk.yellow(" Waiting for connection from local development server..."));
|
|
22
21
|
console.log();
|
|
23
22
|
console.log(chalk.gray(" Press Ctrl+C to stop"));
|
|
@@ -70,11 +69,13 @@ export async function watchSceneCommand() {
|
|
|
70
69
|
startWatcher();
|
|
71
70
|
return;
|
|
72
71
|
}
|
|
73
|
-
const choice =
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
72
|
+
const choice = sceneId === "template"
|
|
73
|
+
? "push-local"
|
|
74
|
+
: await promptForInitialSyncChoice().catch((error) => {
|
|
75
|
+
console.log(chalk.red(" ✖ " + toErrorMessage(error)));
|
|
76
|
+
ws.close();
|
|
77
|
+
return null;
|
|
78
|
+
});
|
|
78
79
|
if (!choice || currentClient !== ws)
|
|
79
80
|
return;
|
|
80
81
|
if (choice === "push-local") {
|
|
@@ -96,11 +97,13 @@ export async function watchSceneCommand() {
|
|
|
96
97
|
latestSceneJson = incoming.sceneData;
|
|
97
98
|
const scenePath = path.join(sceneDir, SCENE_FILE_NAME);
|
|
98
99
|
fs.writeFileSync(scenePath, incoming.sceneData, "utf8");
|
|
100
|
+
const resolvedTemplateId = incoming.templateId ??
|
|
101
|
+
(incoming.sceneId === "template" ? getRequiredManifestIds(sceneDir).templateId : undefined);
|
|
99
102
|
try {
|
|
100
|
-
await unpackageScene(incoming.sceneData, sceneDir, incoming.sceneId);
|
|
103
|
+
await unpackageScene(incoming.sceneData, sceneDir, incoming.sceneId, resolvedTemplateId);
|
|
101
104
|
console.log(chalk.green(" ✓ Synced scene from app"));
|
|
102
|
-
const
|
|
103
|
-
lastPrintedEditorLink = printEditorLink(
|
|
105
|
+
const updatedIds = getRequiredManifestIds(sceneDir);
|
|
106
|
+
lastPrintedEditorLink = printEditorLink(updatedIds.sceneId, updatedIds.templateId, lastPrintedEditorLink);
|
|
104
107
|
}
|
|
105
108
|
catch (e) {
|
|
106
109
|
console.log(chalk.yellow(" ⚠ Unpackage: " + toErrorMessage(e)));
|
|
@@ -117,7 +120,6 @@ export async function watchSceneCommand() {
|
|
|
117
120
|
scriptWatcher?.close();
|
|
118
121
|
scriptWatcher = null;
|
|
119
122
|
wss.close();
|
|
120
|
-
rl.close();
|
|
121
123
|
console.log(chalk.gray(" Goodbye"));
|
|
122
124
|
console.log();
|
|
123
125
|
process.exit(0);
|
|
@@ -128,7 +130,11 @@ function parseIncomingSceneMessage(raw) {
|
|
|
128
130
|
try {
|
|
129
131
|
const parsed = JSON.parse(raw);
|
|
130
132
|
if (isSceneSyncMessage(parsed)) {
|
|
131
|
-
return {
|
|
133
|
+
return {
|
|
134
|
+
sceneData: parsed.sceneData,
|
|
135
|
+
sceneId: parsed.sceneId?.trim() || undefined,
|
|
136
|
+
templateId: parsed.templateId?.trim() || undefined,
|
|
137
|
+
};
|
|
132
138
|
}
|
|
133
139
|
}
|
|
134
140
|
catch {
|
|
@@ -142,10 +148,14 @@ function isSceneSyncMessage(value) {
|
|
|
142
148
|
const obj = value;
|
|
143
149
|
return (obj.type === SCENE_SYNC_TYPE &&
|
|
144
150
|
typeof obj.sceneData === "string" &&
|
|
145
|
-
(obj.sceneId === undefined || typeof obj.sceneId === "string")
|
|
151
|
+
(obj.sceneId === undefined || typeof obj.sceneId === "string") &&
|
|
152
|
+
(obj.templateId === undefined || typeof obj.templateId === "string"));
|
|
146
153
|
}
|
|
147
|
-
function printEditorLink(sceneId, lastPrintedLink) {
|
|
148
|
-
|
|
154
|
+
function printEditorLink(sceneId, templateId, lastPrintedLink) {
|
|
155
|
+
let editorUrl = `${BASE_URL}/editor?sceneId=${encodeURIComponent(sceneId)}&wsEnabled=true`;
|
|
156
|
+
if (sceneId === "template" && templateId) {
|
|
157
|
+
editorUrl += `&id=${encodeURIComponent(templateId)}`;
|
|
158
|
+
}
|
|
149
159
|
if (editorUrl === lastPrintedLink)
|
|
150
160
|
return lastPrintedLink;
|
|
151
161
|
console.log(chalk.cyan(` Editor: ${editorUrl}`));
|
|
@@ -167,19 +177,25 @@ async function packageAndPersistLocalScene(sceneDir, sceneJson) {
|
|
|
167
177
|
return packagedSceneJson;
|
|
168
178
|
}
|
|
169
179
|
async function promptForInitialSyncChoice() {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
181
|
+
try {
|
|
182
|
+
console.log(chalk.yellow(" Choose initial sync direction:"));
|
|
183
|
+
console.log(chalk.cyan(" [p] Push local scene to the web app"));
|
|
184
|
+
console.log(chalk.cyan(" [a] Accept the current web app scene"));
|
|
185
|
+
while (true) {
|
|
186
|
+
const answer = (await askQuestion(rl, " Initial sync [p/a]: ")).trim().toLowerCase();
|
|
187
|
+
if (answer === "p" || answer === "push" || answer === "local")
|
|
188
|
+
return "push-local";
|
|
189
|
+
if (answer === "a" || answer === "accept" || answer === "web" || answer === "app")
|
|
190
|
+
return "accept-web";
|
|
191
|
+
console.log(chalk.yellow(" Please enter 'p' to push local or 'a' to accept the web app scene."));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
finally {
|
|
195
|
+
rl.close();
|
|
180
196
|
}
|
|
181
197
|
}
|
|
182
|
-
function askQuestion(prompt) {
|
|
198
|
+
function askQuestion(rl, prompt) {
|
|
183
199
|
return new Promise((resolve) => {
|
|
184
200
|
rl.question(prompt, resolve);
|
|
185
201
|
});
|
package/dist/index.js
CHANGED
|
@@ -2,9 +2,14 @@
|
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { watchSceneCommand } from "./commands/watch-scene.js";
|
|
4
4
|
import { cloneSceneCommand } from "./commands/clone-scene.js";
|
|
5
|
-
|
|
5
|
+
import { initSceneCommand } from "./commands/init-scene.js";
|
|
6
|
+
const USAGE = " Usage: phibelle-kit init | phibelle-kit watch | phibelle-kit clone";
|
|
6
7
|
async function main() {
|
|
7
8
|
const command = process.argv[2];
|
|
9
|
+
if (command === "init") {
|
|
10
|
+
await initSceneCommand();
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
8
13
|
if (command === "watch") {
|
|
9
14
|
await watchSceneCommand();
|
|
10
15
|
return;
|
package/dist/lib/manifest.d.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
export type ManifestIds = {
|
|
2
|
+
sceneId: string;
|
|
3
|
+
/** Only present when sceneId === "template" */
|
|
4
|
+
templateId?: string;
|
|
5
|
+
};
|
|
1
6
|
/**
|
|
2
|
-
* Read manifest.json from sceneDir and return
|
|
7
|
+
* Read manifest.json from sceneDir and return sceneId and optional templateId.
|
|
3
8
|
* Throws with user-facing errors if manifest is missing, unreadable, invalid JSON, or missing sceneId.
|
|
4
9
|
*/
|
|
10
|
+
export declare function getRequiredManifestIds(sceneDir: string): ManifestIds;
|
|
11
|
+
/** @deprecated Use getRequiredManifestIds instead */
|
|
5
12
|
export declare function getRequiredManifestSceneId(sceneDir: string): string;
|
package/dist/lib/manifest.js
CHANGED
|
@@ -2,13 +2,13 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { MANIFEST_FILE_NAME } from "./constants.js";
|
|
4
4
|
/**
|
|
5
|
-
* Read manifest.json from sceneDir and return
|
|
5
|
+
* Read manifest.json from sceneDir and return sceneId and optional templateId.
|
|
6
6
|
* Throws with user-facing errors if manifest is missing, unreadable, invalid JSON, or missing sceneId.
|
|
7
7
|
*/
|
|
8
|
-
export function
|
|
8
|
+
export function getRequiredManifestIds(sceneDir) {
|
|
9
9
|
const manifestPath = path.join(sceneDir, MANIFEST_FILE_NAME);
|
|
10
10
|
if (!fs.existsSync(manifestPath)) {
|
|
11
|
-
throw new Error(`Missing ${MANIFEST_FILE_NAME} in ${sceneDir}. Run "phibelle-kit
|
|
11
|
+
throw new Error(`Missing ${MANIFEST_FILE_NAME} in ${sceneDir}. Run "phibelle-kit init" or "phibelle-kit clone" first.`);
|
|
12
12
|
}
|
|
13
13
|
let manifestRaw;
|
|
14
14
|
try {
|
|
@@ -27,5 +27,14 @@ export function getRequiredManifestSceneId(sceneDir) {
|
|
|
27
27
|
if (typeof manifest.sceneId !== "string" || !manifest.sceneId.trim()) {
|
|
28
28
|
throw new Error(`Invalid ${MANIFEST_FILE_NAME}: missing required "sceneId".`);
|
|
29
29
|
}
|
|
30
|
-
|
|
30
|
+
const sceneId = manifest.sceneId.trim();
|
|
31
|
+
const result = { sceneId };
|
|
32
|
+
if (sceneId === "template" && typeof manifest.templateId === "string" && manifest.templateId.trim()) {
|
|
33
|
+
result.templateId = manifest.templateId.trim();
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
/** @deprecated Use getRequiredManifestIds instead */
|
|
38
|
+
export function getRequiredManifestSceneId(sceneDir) {
|
|
39
|
+
return getRequiredManifestIds(sceneDir).sceneId;
|
|
31
40
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Minimal valid scene JSON for init (empty entities; watch will sync from editor). */
|
|
2
|
+
export const MINIMAL_SCENE_JSON = JSON.stringify({
|
|
3
|
+
entityStore: {
|
|
4
|
+
_currId: 0,
|
|
5
|
+
rootEntities: [],
|
|
6
|
+
entities: {},
|
|
7
|
+
rootRenderVersion: 0,
|
|
8
|
+
lastRenderedVersion: 0,
|
|
9
|
+
selectedEntityIds: [],
|
|
10
|
+
},
|
|
11
|
+
editorCameraStore: {
|
|
12
|
+
orthographic: false,
|
|
13
|
+
cameraPosition: [0, 0, 0],
|
|
14
|
+
cameraRotation: [0, 0, 0, "XYZ"],
|
|
15
|
+
targetPosition: [0, 0, 0],
|
|
16
|
+
},
|
|
17
|
+
errorStore: {},
|
|
18
|
+
sceneScriptStore: {
|
|
19
|
+
sceneScript: "// Empty scene script. Run phibelle-kit watch to start the scene.\n",
|
|
20
|
+
sceneProperties: [],
|
|
21
|
+
lastModifiedSceneScript: 0,
|
|
22
|
+
},
|
|
23
|
+
engineModeStore: {
|
|
24
|
+
engineMode: "edit",
|
|
25
|
+
transformMode: "translate",
|
|
26
|
+
},
|
|
27
|
+
version: "1",
|
|
28
|
+
});
|
|
@@ -12,6 +12,6 @@ export declare function entityNameToSlug(name: string): string;
|
|
|
12
12
|
/** Convert slug to display name: "main-enemy" -> "Main Enemy". */
|
|
13
13
|
export declare function slugToEntityName(slug: string): string;
|
|
14
14
|
/** Unpackage scene JSON to filesystem. No DB; accepts full scene JSON string. */
|
|
15
|
-
export declare function unpackageScene(sceneJson: string, sceneDir: string, sceneId?: string): Promise<void>;
|
|
15
|
+
export declare function unpackageScene(sceneJson: string, sceneDir: string, sceneId?: string, templateId?: string): Promise<void>;
|
|
16
16
|
export declare function patchSceneFromFile(sceneJson: string, sceneDir: string, _relativePath: string, _eventType: "change" | "add" | "unlink" | "addDir" | "unlinkDir"): Promise<string>;
|
|
17
17
|
export declare function packageSceneFromFilesystem(sceneJson: string, sceneDir: string): Promise<string>;
|
|
@@ -40,7 +40,7 @@ export function slugToEntityName(slug) {
|
|
|
40
40
|
.join(" ");
|
|
41
41
|
}
|
|
42
42
|
/** Unpackage scene JSON to filesystem. No DB; accepts full scene JSON string. */
|
|
43
|
-
export async function unpackageScene(sceneJson, sceneDir, sceneId) {
|
|
43
|
+
export async function unpackageScene(sceneJson, sceneDir, sceneId, templateId) {
|
|
44
44
|
const parsed = parseSceneJson(sceneJson);
|
|
45
45
|
const parsedSceneId = typeof parsed._id === "string"
|
|
46
46
|
? parsed._id
|
|
@@ -50,8 +50,12 @@ export async function unpackageScene(sceneJson, sceneDir, sceneId) {
|
|
|
50
50
|
await fs.mkdir(appDir, { recursive: true });
|
|
51
51
|
await cleanupKnownEntityDirectories(appDir, previousManifest);
|
|
52
52
|
await cleanupLegacySceneFiles(appDir);
|
|
53
|
+
const resolvedSceneId = sceneId?.trim() || parsedSceneId;
|
|
53
54
|
const manifest = {
|
|
54
|
-
sceneId:
|
|
55
|
+
sceneId: resolvedSceneId,
|
|
56
|
+
...(resolvedSceneId === "template" && templateId?.trim()
|
|
57
|
+
? { templateId: templateId.trim() }
|
|
58
|
+
: {}),
|
|
55
59
|
sceneScript: SCENE_SCRIPT_FILE,
|
|
56
60
|
sceneProperties: SCENE_PROPERTIES_FILE,
|
|
57
61
|
entities: {},
|
|
@@ -136,8 +140,12 @@ async function readManifest(sceneDir) {
|
|
|
136
140
|
try {
|
|
137
141
|
const manifestRaw = await fs.readFile(path.join(sceneDir, MANIFEST_FILE_NAME), "utf8");
|
|
138
142
|
const parsed = JSON.parse(manifestRaw);
|
|
143
|
+
const sceneId = typeof parsed.sceneId === "string" ? parsed.sceneId : "";
|
|
139
144
|
return {
|
|
140
|
-
sceneId
|
|
145
|
+
sceneId,
|
|
146
|
+
...(sceneId === "template" && typeof parsed.templateId === "string" && parsed.templateId.trim()
|
|
147
|
+
? { templateId: parsed.templateId.trim() }
|
|
148
|
+
: {}),
|
|
141
149
|
sceneScript: typeof parsed.sceneScript === "string" ? parsed.sceneScript : SCENE_SCRIPT_FILE,
|
|
142
150
|
sceneProperties: typeof parsed.sceneProperties === "string" ? parsed.sceneProperties : SCENE_PROPERTIES_FILE,
|
|
143
151
|
entities: parsed.entities ?? {},
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template IDs - must match src/lib/templates/template-ids.ts in the main app.
|
|
3
|
+
* Used by init command for template selection.
|
|
4
|
+
*/
|
|
5
|
+
export declare const TEMPLATE_IDS: readonly ["basic-cube", "maze", "blocks-destroyer"];
|
|
6
|
+
export type TemplateId = (typeof TEMPLATE_IDS)[number];
|