@playcraft/cli 0.1.66 → 0.1.68

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 (113) hide show
  1. package/dist/assets/client.js +4 -2
  2. package/dist/assets/messages.js +2 -0
  3. package/dist/audio-source/messages.js +35 -0
  4. package/dist/commands/assets.js +1 -1
  5. package/dist/commands/audio-program.js +82 -0
  6. package/dist/commands/audio-source.js +109 -0
  7. package/dist/commands/audio.js +13 -4
  8. package/dist/commands/build.js +4 -2
  9. package/dist/commands/tools-context.js +46 -0
  10. package/dist/commands/tools.js +2 -0
  11. package/dist/project-skills/builtin-import.js +69 -22
  12. package/dist/project-skills/commands.js +11 -2
  13. package/dist/project-skills/lifecycle.js +1 -1
  14. package/dist/project-skills/local-store.js +90 -31
  15. package/dist/project-skills/messages.js +16 -0
  16. package/dist/project-skills/migrate-lock.js +166 -0
  17. package/dist/project-skills/remote-cache.js +36 -5
  18. package/dist/utils/agent-api-client.js +18 -1
  19. package/dist/utils/agent-generation-provenance.js +8 -2
  20. package/dist/utils/audio-export.js +249 -0
  21. package/dist/utils/audio-processor.js +22 -99
  22. package/dist/utils/execution-context-bridge.js +28 -0
  23. package/dist/workspace-runtime/batch/control.js +13 -2
  24. package/dist/workspace-runtime/batch/generate.js +43 -2
  25. package/dist/workspace-runtime/batch/prepare.js +4 -0
  26. package/dist/workspace-runtime/batch/skill-artifacts.js +103 -0
  27. package/dist/workspace-runtime/batch/source-capture.js +6 -8
  28. package/dist/workspace-runtime/codex/app-server.js +26 -2
  29. package/dist/workspace-runtime/codex/execution-context-broker.js +61 -0
  30. package/dist/workspace-runtime/codex/execution-context-tools.js +37 -0
  31. package/dist/workspace-runtime/codex/generation-provenance-broker.js +9 -6
  32. package/dist/workspace-runtime/codex/level-editor-tools.js +18 -0
  33. package/dist/workspace-runtime/codex/model-broker.js +15 -2
  34. package/dist/workspace-runtime/codex/scheduled-task-tools.js +1 -1
  35. package/dist/workspace-runtime/previews/finalize.js +1 -1
  36. package/dist/workspace-runtime/previews/output.js +4 -1
  37. package/dist/workspace-runtime/previews/publish.js +5 -2
  38. package/dist/workspace-runtime/previews/runtime-preview.js +38 -3
  39. package/dist/workspace-runtime/serve.js +6 -0
  40. package/dist/workspace-runtime/server/accept-operation.js +85 -13
  41. package/dist/workspace-runtime/server/audio-assets.js +257 -0
  42. package/dist/workspace-runtime/server/audio-export.js +136 -0
  43. package/dist/workspace-runtime/server/directory-access.js +222 -67
  44. package/dist/workspace-runtime/server/dispatch.js +252 -29
  45. package/dist/workspace-runtime/server/http.js +97 -3
  46. package/dist/workspace-runtime/server/operation-record.js +1 -1
  47. package/dist/workspace-runtime/workspaces/artifact-files.js +10 -3
  48. package/dist/workspace-runtime/workspaces/artifacts.js +47 -6
  49. package/dist/workspace-runtime/workspaces/asset-library-import.js +27 -7
  50. package/dist/workspace-runtime/workspaces/audio-assets/artifact-package.js +104 -0
  51. package/dist/workspace-runtime/workspaces/audio-assets/binding-usage.js +71 -0
  52. package/dist/workspace-runtime/workspaces/audio-assets/build-inputs.js +80 -0
  53. package/dist/workspace-runtime/workspaces/audio-assets/library-import.js +149 -0
  54. package/dist/workspace-runtime/workspaces/audio-assets/library-package.js +26 -0
  55. package/dist/workspace-runtime/workspaces/audio-assets/program-artifact-audit.js +90 -0
  56. package/dist/workspace-runtime/workspaces/audio-assets/program-browser.js +194 -0
  57. package/dist/workspace-runtime/workspaces/audio-assets/program-build-inputs.js +122 -0
  58. package/dist/workspace-runtime/workspaces/audio-assets/program-engine.js +39 -0
  59. package/dist/workspace-runtime/workspaces/audio-assets/program-package.js +289 -0
  60. package/dist/workspace-runtime/workspaces/audio-assets/program-variant-build.js +217 -0
  61. package/dist/workspace-runtime/workspaces/audio-assets/references.js +107 -0
  62. package/dist/workspace-runtime/workspaces/audio-assets/render-worker.js +117 -0
  63. package/dist/workspace-runtime/workspaces/audio-assets/renderer.js +128 -0
  64. package/dist/workspace-runtime/workspaces/audio-assets/representations.js +73 -0
  65. package/dist/workspace-runtime/workspaces/audio-assets/service.js +459 -0
  66. package/dist/workspace-runtime/workspaces/audio-assets/storage.js +125 -0
  67. package/dist/workspace-runtime/workspaces/build-isolation.js +2 -0
  68. package/dist/workspace-runtime/workspaces/composer-context.js +40 -1
  69. package/dist/workspace-runtime/workspaces/empty-template.js +1 -1
  70. package/dist/workspace-runtime/workspaces/execution-context.js +24 -0
  71. package/dist/workspace-runtime/workspaces/files.js +3 -5
  72. package/dist/workspace-runtime/workspaces/git.js +7 -0
  73. package/dist/workspace-runtime/workspaces/level-editor-agent-store.js +143 -0
  74. package/dist/workspace-runtime/workspaces/level-editor-agent.js +410 -0
  75. package/dist/workspace-runtime/workspaces/level-editor-atomic.js +424 -0
  76. package/dist/workspace-runtime/workspaces/level-editor-extensions.js +140 -0
  77. package/dist/workspace-runtime/workspaces/level-editor-format.js +75 -0
  78. package/dist/workspace-runtime/workspaces/level-editor-history-store.js +250 -0
  79. package/dist/workspace-runtime/workspaces/level-editor-json5.js +132 -0
  80. package/dist/workspace-runtime/workspaces/level-editor-requests.js +724 -0
  81. package/dist/workspace-runtime/workspaces/level-editor.js +454 -0
  82. package/dist/workspace-runtime/workspaces/parameter-initialization-skill.js +62 -22
  83. package/dist/workspace-runtime/workspaces/parameter-production.js +1 -1
  84. package/dist/workspace-runtime/workspaces/remix-reference-skill.js +4 -3
  85. package/dist/workspace-runtime/workspaces/skill-artifact-loader.js +19 -5
  86. package/dist/workspace-runtime/workspaces/skills.js +48 -13
  87. package/package.json +9 -4
  88. package/project-template/.claude/skills/playcraft-audio-generation/SKILL.md +92 -4
  89. package/project-template/.claude/skills/playcraft-level-editor/SKILL.md +34 -0
  90. package/project-template/.claude/skills/playcraft-level-editor/references/authoring-contract.json +3475 -0
  91. package/project-template/.claude/skills/playcraft-level-editor/references/extensions.md +37 -0
  92. package/project-template/.claude/skills/playcraft-level-editor/references/preview.md +15 -0
  93. package/project-template/.claude/skills/playcraft-level-editor/references/protocol-v1.md +35 -0
  94. package/project-template/vite.config.ts +1 -1
  95. package/project-template-v2/.claude/skills/playcraft-audio-generation/SKILL.md +92 -4
  96. package/project-template-v2/.playcraft/skills.lock.json +9 -2
  97. package/project-template-v2/vite.config.ts +1 -1
  98. package/project-templates/empty/AGENTS.md +2 -0
  99. package/project-templates/phaser/.claude/skills/playcraft-audio-generation/SKILL.md +92 -4
  100. package/project-templates/phaser/vite.config.ts +1 -1
  101. package/project-templates/playable-template-phaser/vite.config.ts +1 -1
  102. package/project-templates/threejs/.claude/skills/playcraft-audio-generation/SKILL.md +92 -4
  103. package/project-templates/threejs/vite.config.ts +1 -1
  104. package/templates/preview-runtime/v1/README.md +12 -0
  105. package/templates/preview-runtime/v1/build-preview.mjs +17 -8
  106. package/templates/preview-runtime/v1/manifest.json +9 -6
  107. package/templates/preview-runtime/v1/release-guard.mjs +1 -1
  108. package/templates/preview-runtime/v1/runtime.d.mts +1 -3
  109. package/templates/preview-runtime/v1/runtime.d.ts +1 -3
  110. package/templates/preview-runtime/v1/runtime.mjs +1142 -257
  111. package/templates/preview-runtime/v1/types/host.d.ts +29 -0
  112. package/templates/preview-runtime/v1/types/index.d.ts +6 -0
  113. package/templates/preview-runtime/v1/types/level-editor.d.ts +123 -0
@@ -0,0 +1,250 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { mkdir, lstat, open, readdir, realpath, rename, rm } from 'node:fs/promises';
3
+ import { constants } from 'node:fs';
4
+ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
5
+ import { LevelEditorError, isLevelEditorJson, levelEditorTransactionRequestSchema, workspaceRelativePathSchema, } from '@playcraft/common/cloud-workspace/runtime';
6
+ const digest = (value) => createHash('sha256').update(value).digest('hex');
7
+ const MAX_RECORD_BYTES = 16 * 1024 * 1024;
8
+ const MAX_HISTORY_BYTES = 32 * 1024 * 1024;
9
+ const MAX_HISTORY_RECORDS = 128;
10
+ export async function readHistoryText(file) {
11
+ const stat = await lstat(file);
12
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1 || stat.size > MAX_RECORD_BYTES)
13
+ throw new LevelEditorError('level_editor_history_state_invalid');
14
+ const handle = await open(file, constants.O_RDONLY | constants.O_NOFOLLOW);
15
+ try {
16
+ const opened = await handle.stat();
17
+ if (opened.ino !== stat.ino || opened.dev !== stat.dev || opened.size !== stat.size)
18
+ throw new LevelEditorError('level_editor_history_state_invalid');
19
+ const bytes = Buffer.alloc(stat.size + 1);
20
+ let size = 0;
21
+ while (size < bytes.length) {
22
+ const read = await handle.read(bytes, size, bytes.length - size, null);
23
+ if (!read.bytesRead)
24
+ break;
25
+ size += read.bytesRead;
26
+ }
27
+ if (size !== stat.size)
28
+ throw new LevelEditorError('level_editor_history_state_invalid');
29
+ const text = bytes.subarray(0, size).toString('utf8');
30
+ if (Buffer.byteLength(text) !== size)
31
+ throw new LevelEditorError('level_editor_history_state_invalid');
32
+ return text;
33
+ }
34
+ finally {
35
+ await handle.close();
36
+ }
37
+ }
38
+ export function assertLevelEditorHistoryScope(context) {
39
+ const { state } = context;
40
+ const operation = 'operation' in context
41
+ ? context.operation
42
+ : {
43
+ environment: state.config.environment,
44
+ userId: state.config.userId,
45
+ generation: state.config.generation,
46
+ runtimeEpoch: state.runtimeEpoch,
47
+ workspaceId: context.workspaceId,
48
+ directoryName: context.directoryName,
49
+ };
50
+ if (operation.environment !== state.config.environment ||
51
+ operation.userId !== state.config.userId ||
52
+ operation.generation !== state.config.generation ||
53
+ operation.runtimeEpoch !== state.runtimeEpoch ||
54
+ operation.directoryName === 'root' ||
55
+ (!operation.workspaceId && state.config.workspaceMode === 'multi'))
56
+ throw new LevelEditorError('level_editor_history_scope_invalid');
57
+ }
58
+ export async function historyDirectory(root, context) {
59
+ assertLevelEditorHistoryScope(context);
60
+ const { state } = context;
61
+ const operation = 'operation' in context ? context.operation : context;
62
+ const requested = resolve(state.config.stateDir, 'level-editor-history');
63
+ const project = await realpath(root);
64
+ let ancestor = requested;
65
+ const missing = [];
66
+ for (;;) {
67
+ try {
68
+ ancestor = await realpath(ancestor);
69
+ break;
70
+ }
71
+ catch (error) {
72
+ if (error.code !== 'ENOENT')
73
+ throw error;
74
+ missing.unshift(basename(ancestor));
75
+ ancestor = dirname(ancestor);
76
+ }
77
+ }
78
+ const anticipated = relative(project, join(ancestor, ...missing));
79
+ if (!isAbsolute(anticipated) && anticipated !== '..' && !anticipated.startsWith(`..${sep}`))
80
+ throw new LevelEditorError('level_editor_history_state_not_private');
81
+ // private
82
+ await mkdir(requested, { recursive: true, mode: 0o700 });
83
+ if ((await lstat(requested)).isSymbolicLink())
84
+ throw new LevelEditorError('level_editor_history_state_invalid');
85
+ const parent = await realpath(requested);
86
+ const rel = relative(project, parent);
87
+ if (!isAbsolute(rel) && rel !== '..' && !rel.startsWith(`..${sep}`))
88
+ throw new LevelEditorError('level_editor_history_state_not_private');
89
+ const scope = [
90
+ state.config.environment,
91
+ state.config.userId,
92
+ state.config.runtimeId,
93
+ operation.workspaceId ?? 'local',
94
+ operation.directoryName ?? 'basic',
95
+ state.config.generation,
96
+ state.runtimeEpoch,
97
+ ];
98
+ const directory = join(parent, digest(JSON.stringify(scope)));
99
+ // private
100
+ await mkdir(directory, { recursive: true, mode: 0o700 });
101
+ const stat = await lstat(directory);
102
+ if (!stat.isDirectory() || stat.isSymbolicLink())
103
+ throw new LevelEditorError('level_editor_history_state_invalid');
104
+ return directory;
105
+ }
106
+ /** The digest is a filename only; the exact original request remains part of the checked record. */
107
+ export async function readLevelEditorHistory(root, context, requestId) {
108
+ const file = join(await historyDirectory(root, context), `${digest(requestId)}.json`);
109
+ try {
110
+ const value = JSON.parse(await readHistoryText(file));
111
+ const request = levelEditorTransactionRequestSchema.parse(value.request);
112
+ if (request.requestId !== requestId ||
113
+ value.requestDigest !== digest(JSON.stringify(request)) ||
114
+ (value.phase !== 'prepared' && value.phase !== 'confirmed') ||
115
+ !isLevelEditorJson(value.before) ||
116
+ !isLevelEditorJson(value.after) ||
117
+ (request.version === 1 && value.documentId !== request.documentId) ||
118
+ (request.version === 2 && !value.batch) ||
119
+ !/^[a-f0-9]{64}$/.test(value.contentHash) ||
120
+ !Number.isSafeInteger(value.size) ||
121
+ value.size < 0 ||
122
+ typeof value.path !== 'string' ||
123
+ JSON.stringify(value.command) !==
124
+ JSON.stringify(request.version === 1
125
+ ? {
126
+ commandId: request.commandId,
127
+ selection: request.selection,
128
+ input: request.input,
129
+ }
130
+ : request.commands[0]))
131
+ throw new LevelEditorError('level_editor_history_state_invalid');
132
+ if (value.batch) {
133
+ const batch = value.batch;
134
+ if (request.version !== 2 ||
135
+ JSON.stringify(batch.commands) !== JSON.stringify(request.commands) ||
136
+ !Array.isArray(batch.documents) ||
137
+ !batch.documents.length ||
138
+ !Array.isArray(batch.files) ||
139
+ batch.files.length !== batch.documents.length ||
140
+ !batch.baselines ||
141
+ typeof batch.baselines !== 'object' ||
142
+ Array.isArray(batch.baselines) ||
143
+ typeof batch.operationId !== 'string' ||
144
+ !batch.operationId ||
145
+ batch.operationId.length > 256 ||
146
+ JSON.stringify(Object.keys(batch.baselines).sort()) !==
147
+ JSON.stringify(Object.keys(request.expectedDocuments).sort()) ||
148
+ Object.entries(batch.baselines).some(([id, baseline]) => !baseline ||
149
+ !workspaceRelativePathSchema.safeParse(baseline.path).success ||
150
+ baseline.contentHash !== request.expectedDocuments[id]?.contentHash ||
151
+ baseline.revision !== request.expectedDocuments[id]?.revision) ||
152
+ new Set(batch.files.map((file) => file.documentId)).size !== batch.files.length ||
153
+ new Set(batch.documents.map((item) => item.documentId)).size !== batch.documents.length ||
154
+ batch.documents.some((item) => typeof item.documentId !== 'string' ||
155
+ !isLevelEditorJson(item.before) ||
156
+ !isLevelEditorJson(item.after)) ||
157
+ batch.files.some((file) => !workspaceRelativePathSchema.safeParse(file.path).success ||
158
+ typeof file.beforeBytes !== 'string' ||
159
+ typeof file.afterBytes !== 'string' ||
160
+ file.path !== batch.baselines[file.documentId]?.path ||
161
+ file.revision !== batch.baselines[file.documentId].revision + 1 ||
162
+ !Number.isSafeInteger(file.size) ||
163
+ file.size < 0 ||
164
+ !/^[a-f0-9]{64}$/.test(file.contentHash) ||
165
+ Buffer.from(file.beforeBytes, 'base64').toString('base64') !== file.beforeBytes ||
166
+ Buffer.from(file.afterBytes, 'base64').toString('base64') !== file.afterBytes ||
167
+ digest(Buffer.from(file.beforeBytes, 'base64').toString('utf8')) !==
168
+ batch.baselines[file.documentId]?.contentHash ||
169
+ digest(Buffer.from(file.afterBytes, 'base64').toString('utf8')) !== file.contentHash ||
170
+ Buffer.from(file.afterBytes, 'base64').length !== file.size ||
171
+ !batch.documents.some((item) => item.documentId === file.documentId)))
172
+ throw new LevelEditorError('level_editor_history_state_invalid');
173
+ }
174
+ return { ...value, request };
175
+ }
176
+ catch (error) {
177
+ if (error.code === 'ENOENT')
178
+ return undefined;
179
+ if (error instanceof LevelEditorError)
180
+ throw error;
181
+ throw new LevelEditorError('level_editor_history_state_invalid');
182
+ }
183
+ }
184
+ export const levelEditorHistoryRequestDigest = (request) => digest(JSON.stringify(request));
185
+ async function reserveHistoryCapacity(directory, destination, bytes, referenced) {
186
+ const records = [];
187
+ for (const name of await readdir(directory)) {
188
+ if (!/^[a-f0-9]{64}\.json$/.test(name))
189
+ continue;
190
+ const path = join(directory, name), stat = await lstat(path);
191
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1 || stat.size > MAX_RECORD_BYTES)
192
+ throw new LevelEditorError('level_editor_history_state_invalid');
193
+ records.push({ path, size: stat.size, time: stat.mtimeMs });
194
+ }
195
+ let total = records.reduce((sum, item) => sum + (item.path === destination ? 0 : item.size), bytes);
196
+ let count = records.length + (records.some((item) => item.path === destination) ? 0 : 1);
197
+ if (count <= MAX_HISTORY_RECORDS && total <= MAX_HISTORY_BYTES)
198
+ return;
199
+ const candidates = [];
200
+ for (const item of records) {
201
+ if (item.path === destination || item.path === referenced)
202
+ continue;
203
+ const old = JSON.parse(await readHistoryText(item.path));
204
+ if (old.phase === 'confirmed')
205
+ candidates.push({ ...item, inverse: Boolean(old.request?.history) });
206
+ }
207
+ // Preserve original undo sources ahead of metadata for already-observed inverse requests.
208
+ for (const item of candidates.sort((left, right) => Number(right.inverse) - Number(left.inverse) || left.time - right.time)) {
209
+ if (count <= MAX_HISTORY_RECORDS && total <= MAX_HISTORY_BYTES)
210
+ break;
211
+ await rm(item.path);
212
+ total -= item.size;
213
+ count--;
214
+ }
215
+ if (count > MAX_HISTORY_RECORDS || total > MAX_HISTORY_BYTES)
216
+ throw new LevelEditorError('level_editor_history_budget');
217
+ }
218
+ /** Prepared evidence is durable before the engineering file is replaced. */
219
+ export async function writeLevelEditorHistory(root, context, value) {
220
+ const directory = await historyDirectory(root, context);
221
+ const text = JSON.stringify(value);
222
+ if (Buffer.byteLength(text) > MAX_RECORD_BYTES)
223
+ throw new LevelEditorError('level_editor_history_budget');
224
+ const temporary = join(directory, `.${randomUUID()}.tmp`), destination = join(directory, `${digest(value.request.requestId)}.json`);
225
+ await reserveHistoryCapacity(directory, destination, Buffer.byteLength(text), value.request.history
226
+ ? join(directory, `${digest(value.request.history.requestId)}.json`)
227
+ : undefined);
228
+ try {
229
+ // private
230
+ const handle = await open(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
231
+ try {
232
+ await handle.writeFile(text);
233
+ await handle.sync();
234
+ }
235
+ finally {
236
+ await handle.close();
237
+ }
238
+ await rename(temporary, destination);
239
+ const parent = await open(directory, constants.O_RDONLY);
240
+ try {
241
+ await parent.sync();
242
+ }
243
+ finally {
244
+ await parent.close();
245
+ }
246
+ }
247
+ finally {
248
+ await rm(temporary, { force: true });
249
+ }
250
+ }
@@ -0,0 +1,132 @@
1
+ import JSON5 from 'json5';
2
+ import { LevelEditorError, levelEditorCanonicalJson } from '@playcraft/common/cloud-workspace/runtime';
3
+ /** Keep untouched author bytes; refuse structural replacement that would discard authored comments. */
4
+ export function editLevelEditorJson5(source, changes, expected) {
5
+ JSON5.parse(source);
6
+ let output = source;
7
+ for (const change of changes) {
8
+ const root = tree(output);
9
+ let span = root;
10
+ let parent;
11
+ for (const key of change.path) {
12
+ parent = span;
13
+ span = span?.children?.get(key);
14
+ }
15
+ if (!span || change.op === 'remove') {
16
+ if (!parent || parent.commented)
17
+ throw new LevelEditorError('level_editor_comment_preservation_required');
18
+ const value = JSON5.parse(output.slice(parent.start, parent.end));
19
+ const key = change.path.at(-1);
20
+ if (change.op === 'remove') {
21
+ if (Array.isArray(value) && typeof key === 'number')
22
+ value.splice(key, 1);
23
+ else
24
+ delete value[key];
25
+ }
26
+ else
27
+ value[key] = change.value;
28
+ output = output.slice(0, parent.start) + JSON.stringify(value) + output.slice(parent.end);
29
+ }
30
+ else {
31
+ if (span.commented)
32
+ throw new LevelEditorError('level_editor_comment_preservation_required');
33
+ output = output.slice(0, span.start) + JSON.stringify(change.value) + output.slice(span.end);
34
+ }
35
+ }
36
+ if (levelEditorCanonicalJson(JSON5.parse(output)) !== levelEditorCanonicalJson(expected))
37
+ throw new LevelEditorError('level_editor_serialization_mismatch');
38
+ return output;
39
+ }
40
+ function tree(source) {
41
+ let cursor = 0;
42
+ let comments = 0;
43
+ function trivia() {
44
+ for (;;) {
45
+ while (/\s/.test(source[cursor] ?? '') && cursor < source.length)
46
+ cursor++;
47
+ if (source.slice(cursor, cursor + 2) === '//') {
48
+ comments++;
49
+ cursor = source.indexOf('\n', cursor + 2);
50
+ if (cursor < 0)
51
+ cursor = source.length;
52
+ }
53
+ else if (source.slice(cursor, cursor + 2) === '/*') {
54
+ comments++;
55
+ const end = source.indexOf('*/', cursor + 2);
56
+ if (end < 0)
57
+ throw new LevelEditorError('level_editor_invalid_json');
58
+ cursor = end + 2;
59
+ }
60
+ else
61
+ break;
62
+ }
63
+ }
64
+ function quoted() {
65
+ const quote = source[cursor++];
66
+ while (cursor < source.length) {
67
+ const char = source[cursor++];
68
+ if (char === '\\')
69
+ cursor++;
70
+ else if (char === quote)
71
+ return;
72
+ }
73
+ throw new LevelEditorError('level_editor_invalid_json');
74
+ }
75
+ function value(depth) {
76
+ if (depth > 64)
77
+ throw new LevelEditorError('level_editor_document_budget');
78
+ trivia();
79
+ const start = cursor, before = comments;
80
+ const first = source[cursor];
81
+ let children;
82
+ if (first === '{' || first === '[') {
83
+ children = new Map();
84
+ cursor++;
85
+ trivia();
86
+ let index = 0;
87
+ const close = first === '{' ? '}' : ']';
88
+ while (source[cursor] !== close) {
89
+ if (cursor >= source.length)
90
+ throw new LevelEditorError('level_editor_invalid_json');
91
+ let key = index++;
92
+ if (first === '{') {
93
+ const keyStart = cursor;
94
+ if (source[cursor] === '"' || source[cursor] === "'")
95
+ quoted();
96
+ else
97
+ while (cursor < source.length && !/[\s:]/.test(source[cursor]))
98
+ cursor++;
99
+ key = Object.keys(JSON5.parse(`{${source.slice(keyStart, cursor)}:null}`))[0];
100
+ trivia();
101
+ if (source[cursor++] !== ':')
102
+ throw new LevelEditorError('level_editor_invalid_json');
103
+ }
104
+ if (children.has(key))
105
+ throw new LevelEditorError('level_editor_duplicate_json_member');
106
+ children.set(key, value(depth + 1));
107
+ trivia();
108
+ if (source[cursor] === ',') {
109
+ cursor++;
110
+ trivia();
111
+ }
112
+ else if (source[cursor] !== close)
113
+ throw new LevelEditorError('level_editor_invalid_json');
114
+ }
115
+ cursor++;
116
+ }
117
+ else if (first === '"' || first === "'")
118
+ quoted();
119
+ else {
120
+ while (cursor < source.length && !/[\s,\]}]/.test(source[cursor]) && source[cursor] !== '/')
121
+ cursor++;
122
+ }
123
+ if (cursor === start)
124
+ throw new LevelEditorError('level_editor_invalid_json');
125
+ return { start, end: cursor, children, commented: comments !== before };
126
+ }
127
+ const root = value(0);
128
+ trivia();
129
+ if (cursor !== source.length)
130
+ throw new LevelEditorError('level_editor_invalid_json');
131
+ return root;
132
+ }