@fugood/bricks-ctor 2.25.0-beta.6 → 2.25.0-beta.60
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/compile/__tests__/config-diff.test.js +100 -0
- package/compile/__tests__/index.test.js +461 -0
- package/compile/__tests__/util.test.js +450 -0
- package/compile/action-name-map.ts +64 -0
- package/compile/config-diff.ts +155 -0
- package/compile/index.ts +668 -352
- package/compile/util.ts +134 -10
- package/package.json +7 -3
- package/skills/bricks-ctor/SKILL.md +23 -17
- package/skills/bricks-ctor/{rules → references}/animation.md +3 -2
- package/skills/bricks-ctor/{rules → references}/architecture-patterns.md +19 -0
- package/skills/bricks-ctor/{rules → references}/automations.md +11 -0
- package/skills/bricks-ctor/references/buttress.md +245 -0
- package/skills/bricks-ctor/references/data-calculation.md +252 -0
- package/skills/bricks-ctor/{rules → references}/media-flow.md +7 -0
- package/skills/bricks-ctor/references/simulator.md +132 -0
- package/skills/bricks-ctor/references/source-editing-tools.md +81 -0
- package/skills/bricks-ctor/references/verification-toolchain.md +200 -0
- package/skills/bricks-design/SKILL.md +150 -45
- package/skills/bricks-design/references/architecture-truths.md +132 -0
- package/skills/bricks-design/references/avoiding-complexity.md +91 -0
- package/skills/bricks-design/references/design-critique.md +195 -0
- package/skills/bricks-design/references/design-languages.md +265 -0
- package/skills/bricks-design/references/performance.md +116 -0
- package/skills/bricks-design/references/presentation-and-slideshow.md +137 -0
- package/skills/bricks-design/references/translating-inputs.md +152 -0
- package/skills/bricks-design/references/variations-and-tweaks.md +124 -0
- package/skills/bricks-design/references/when-the-brief-is-branded.md +284 -0
- package/skills/bricks-design/references/when-the-brief-is-vague.md +85 -0
- package/skills/bricks-design/references/workflow.md +134 -0
- package/skills/bricks-ux/SKILL.md +114 -0
- package/skills/bricks-ux/references/accessibility.md +162 -0
- package/skills/bricks-ux/references/flow-states.md +175 -0
- package/skills/bricks-ux/references/interaction-archetypes.md +189 -0
- package/skills/bricks-ux/references/monitoring-screens.md +153 -0
- package/skills/bricks-ux/references/pressable-composition.md +126 -0
- package/skills/bricks-ux/references/user-journey.md +168 -0
- package/skills/bricks-ux/references/ux-critique.md +256 -0
- package/tools/__tests__/_cli-error.test.ts +35 -0
- package/tools/__tests__/_mcp-config.test.ts +67 -0
- package/tools/__tests__/pull.test.ts +108 -0
- package/tools/_cli-error.ts +17 -0
- package/tools/_edits-log.ts +41 -0
- package/tools/_git-author.ts +10 -2
- package/tools/_last-pushed-commit.ts +28 -0
- package/tools/_mcp-config.ts +42 -0
- package/tools/_shell.ts +8 -1
- package/tools/deploy.ts +17 -6
- package/tools/mcp-env.ts +13 -0
- package/tools/mcp-server.ts +8 -0
- package/tools/mcp-tools/__tests__/data-calc-editing.test.js +516 -0
- package/tools/mcp-tools/__tests__/entry-editing.test.js +866 -0
- package/tools/mcp-tools/__tests__/huggingface.test.ts +49 -0
- package/tools/mcp-tools/__tests__/icons.test.ts +21 -0
- package/tools/mcp-tools/__tests__/mcp-env.test.js +19 -0
- package/tools/mcp-tools/_editing-helpers.ts +98 -0
- package/tools/mcp-tools/_verify.ts +50 -0
- package/tools/mcp-tools/compile.ts +21 -9
- package/tools/mcp-tools/data-calc-editing.ts +1311 -0
- package/tools/mcp-tools/entry-editing.ts +2297 -0
- package/tools/mcp-tools/huggingface.ts +23 -13
- package/tools/mcp-tools/icons.ts +23 -7
- package/tools/mcp-tools/media.ts +4 -1
- package/tools/postinstall.ts +95 -38
- package/tools/pull.ts +100 -23
- package/tools/push-config.ts +114 -0
- package/tools/{preview-main.mjs → simulator-main.mjs} +207 -12
- package/tools/simulator-preload.cjs +16 -0
- package/tools/{preview.ts → simulator.ts} +4 -4
- package/types/{animation.ts → animation.d.ts} +24 -8
- package/types/{automation.ts → automation.d.ts} +16 -20
- package/types/{brick-base.ts → brick-base.d.ts} +1 -1
- package/types/bricks/{Camera.ts → Camera.d.ts} +8 -8
- package/types/bricks/{Chart.ts → Chart.d.ts} +4 -4
- package/types/bricks/{GenerativeMedia.ts → GenerativeMedia.d.ts} +15 -15
- package/types/bricks/{Icon.ts → Icon.d.ts} +7 -7
- package/types/bricks/{Image.ts → Image.d.ts} +21 -9
- package/types/bricks/{Items.ts → Items.d.ts} +11 -7
- package/types/bricks/{Lottie.ts → Lottie.d.ts} +10 -10
- package/types/bricks/{Maps.ts → Maps.d.ts} +11 -11
- package/types/bricks/{QrCode.ts → QrCode.d.ts} +7 -7
- package/types/bricks/{Rect.ts → Rect.d.ts} +7 -7
- package/types/bricks/{RichText.ts → RichText.d.ts} +12 -9
- package/types/bricks/{Rive.ts → Rive.d.ts} +9 -9
- package/types/bricks/Scene3D.d.ts +676 -0
- package/types/bricks/{Sketch.ts → Sketch.d.ts} +10 -8
- package/types/bricks/{Slideshow.ts → Slideshow.d.ts} +7 -7
- package/types/bricks/{Svg.ts → Svg.d.ts} +7 -7
- package/types/bricks/{Text.ts → Text.d.ts} +9 -9
- package/types/bricks/{TextInput.ts → TextInput.d.ts} +10 -10
- package/types/bricks/{Video.ts → Video.d.ts} +80 -13
- package/types/bricks/{VideoStreaming.ts → VideoStreaming.d.ts} +10 -10
- package/types/bricks/{WebRtcStream.ts → WebRtcStream.d.ts} +1 -1
- package/types/bricks/{WebView.ts → WebView.d.ts} +4 -4
- package/types/bricks/{index.ts → index.d.ts} +1 -0
- package/types/{common.ts → common.d.ts} +3 -6
- package/types/data-calc-command/base.d.ts +57 -0
- package/types/data-calc-command/collection.d.ts +418 -0
- package/types/data-calc-command/color.d.ts +432 -0
- package/types/data-calc-command/constant.d.ts +50 -0
- package/types/data-calc-command/datetime.d.ts +147 -0
- package/types/data-calc-command/file.d.ts +129 -0
- package/types/data-calc-command/index.d.ts +13 -0
- package/types/data-calc-command/iteratee.d.ts +23 -0
- package/types/data-calc-command/logictype.d.ts +190 -0
- package/types/data-calc-command/math.d.ts +275 -0
- package/types/data-calc-command/object.d.ts +119 -0
- package/types/data-calc-command/sandbox.d.ts +66 -0
- package/types/data-calc-command/string.d.ts +407 -0
- package/types/{data-calc.ts → data-calc.d.ts} +1 -0
- package/types/{data.ts → data.d.ts} +4 -2
- package/types/generators/{Assistant.ts → Assistant.d.ts} +19 -0
- package/types/generators/{HttpServer.ts → HttpServer.d.ts} +56 -2
- package/types/generators/{LlmGgml.ts → LlmGgml.d.ts} +43 -1
- package/types/generators/{LlmMlx.ts → LlmMlx.d.ts} +1 -0
- package/types/generators/{RerankerGgml.ts → RerankerGgml.d.ts} +5 -1
- package/types/generators/{SoundRecorder.ts → SoundRecorder.d.ts} +10 -1
- package/types/generators/{SpeechToTextGgml.ts → SpeechToTextGgml.d.ts} +6 -1
- package/types/generators/{SttAppleBuiltin.ts → SttAppleBuiltin.d.ts} +27 -4
- package/types/generators/{ThermalPrinter.ts → ThermalPrinter.d.ts} +9 -7
- package/types/generators/{Tick.ts → Tick.d.ts} +1 -1
- package/types/generators/{VadGgml.ts → VadGgml.d.ts} +12 -2
- package/types/{subspace.ts → subspace.d.ts} +1 -1
- package/utils/__tests__/calc.test.js +25 -0
- package/utils/__tests__/id.test.js +154 -0
- package/utils/calc.ts +5 -1
- package/utils/data.ts +5 -7
- package/utils/event-props.ts +27 -1
- package/utils/id.ts +109 -56
- package/skills/bricks-ctor/rules/buttress.md +0 -156
- package/skills/bricks-ctor/rules/data-calculation.md +0 -209
- package/skills/bricks-design/LICENSE.txt +0 -180
- package/types/data-calc-command.ts +0 -7005
- /package/skills/bricks-ctor/{rules → references}/local-sync.md +0 -0
- /package/skills/bricks-ctor/{rules → references}/remote-data-bank.md +0 -0
- /package/skills/bricks-ctor/{rules → references}/standby-transition.md +0 -0
- /package/types/{canvas.ts → canvas.d.ts} +0 -0
- /package/types/{data-calc-script.ts → data-calc-script.d.ts} +0 -0
- /package/types/generators/{AlarmClock.ts → AlarmClock.d.ts} +0 -0
- /package/types/generators/{BleCentral.ts → BleCentral.d.ts} +0 -0
- /package/types/generators/{BlePeripheral.ts → BlePeripheral.d.ts} +0 -0
- /package/types/generators/{CanvasMap.ts → CanvasMap.d.ts} +0 -0
- /package/types/generators/{CastlesPay.ts → CastlesPay.d.ts} +0 -0
- /package/types/generators/{DataBank.ts → DataBank.d.ts} +0 -0
- /package/types/generators/{File.ts → File.d.ts} +0 -0
- /package/types/generators/{GraphQl.ts → GraphQl.d.ts} +0 -0
- /package/types/generators/{Http.ts → Http.d.ts} +0 -0
- /package/types/generators/{Information.ts → Information.d.ts} +0 -0
- /package/types/generators/{Intent.ts → Intent.d.ts} +0 -0
- /package/types/generators/{Iterator.ts → Iterator.d.ts} +0 -0
- /package/types/generators/{Keyboard.ts → Keyboard.d.ts} +0 -0
- /package/types/generators/{LlmAnthropicCompat.ts → LlmAnthropicCompat.d.ts} +0 -0
- /package/types/generators/{LlmAppleBuiltin.ts → LlmAppleBuiltin.d.ts} +0 -0
- /package/types/generators/{LlmMediaTekNeuroPilot.ts → LlmMediaTekNeuroPilot.d.ts} +0 -0
- /package/types/generators/{LlmOnnx.ts → LlmOnnx.d.ts} +0 -0
- /package/types/generators/{LlmOpenAiCompat.ts → LlmOpenAiCompat.d.ts} +0 -0
- /package/types/generators/{LlmQualcommAiEngine.ts → LlmQualcommAiEngine.d.ts} +0 -0
- /package/types/generators/{Mcp.ts → Mcp.d.ts} +0 -0
- /package/types/generators/{McpServer.ts → McpServer.d.ts} +0 -0
- /package/types/generators/{MediaFlow.ts → MediaFlow.d.ts} +0 -0
- /package/types/generators/{MqttBroker.ts → MqttBroker.d.ts} +0 -0
- /package/types/generators/{MqttClient.ts → MqttClient.d.ts} +0 -0
- /package/types/generators/{Question.ts → Question.d.ts} +0 -0
- /package/types/generators/{RealtimeTranscription.ts → RealtimeTranscription.d.ts} +0 -0
- /package/types/generators/{SerialPort.ts → SerialPort.d.ts} +0 -0
- /package/types/generators/{SoundPlayer.ts → SoundPlayer.d.ts} +0 -0
- /package/types/generators/{SpeechToTextOnnx.ts → SpeechToTextOnnx.d.ts} +0 -0
- /package/types/generators/{SpeechToTextPlatform.ts → SpeechToTextPlatform.d.ts} +0 -0
- /package/types/generators/{SqLite.ts → SqLite.d.ts} +0 -0
- /package/types/generators/{Step.ts → Step.d.ts} +0 -0
- /package/types/generators/{Tcp.ts → Tcp.d.ts} +0 -0
- /package/types/generators/{TcpServer.ts → TcpServer.d.ts} +0 -0
- /package/types/generators/{TextToSpeechAppleBuiltin.ts → TextToSpeechAppleBuiltin.d.ts} +0 -0
- /package/types/generators/{TextToSpeechGgml.ts → TextToSpeechGgml.d.ts} +0 -0
- /package/types/generators/{TextToSpeechOnnx.ts → TextToSpeechOnnx.d.ts} +0 -0
- /package/types/generators/{TextToSpeechOpenAiLike.ts → TextToSpeechOpenAiLike.d.ts} +0 -0
- /package/types/generators/{Udp.ts → Udp.d.ts} +0 -0
- /package/types/generators/{VadOnnx.ts → VadOnnx.d.ts} +0 -0
- /package/types/generators/{VadTraditional.ts → VadTraditional.d.ts} +0 -0
- /package/types/generators/{VectorStore.ts → VectorStore.d.ts} +0 -0
- /package/types/generators/{Watchdog.ts → Watchdog.d.ts} +0 -0
- /package/types/generators/{WebCrawler.ts → WebCrawler.d.ts} +0 -0
- /package/types/generators/{WebRtc.ts → WebRtc.d.ts} +0 -0
- /package/types/generators/{WebSocket.ts → WebSocket.d.ts} +0 -0
- /package/types/generators/{index.ts → index.d.ts} +0 -0
- /package/types/{index.ts → index.d.ts} +0 -0
- /package/types/{switch.ts → switch.d.ts} +0 -0
- /package/types/{system.ts → system.d.ts} +0 -0
package/compile/index.ts
CHANGED
|
@@ -4,12 +4,16 @@ import upperFirst from 'lodash/upperFirst'
|
|
|
4
4
|
import snakeCase from 'lodash/snakeCase'
|
|
5
5
|
import omit from 'lodash/omit'
|
|
6
6
|
import { parse as parseAST } from 'acorn'
|
|
7
|
-
import type { ExportNamedDeclaration, FunctionDeclaration } from 'acorn'
|
|
7
|
+
import type { BlockStatement, ExportNamedDeclaration, FunctionDeclaration } from 'acorn'
|
|
8
8
|
import escodegen from 'escodegen'
|
|
9
|
-
import {
|
|
9
|
+
import { makeSeededId } from '../utils/id'
|
|
10
10
|
import { generateCalulationMap } from './util'
|
|
11
11
|
import { templateActionNameMap } from './action-name-map'
|
|
12
12
|
import { templateEventPropsMap } from '../utils/event-props'
|
|
13
|
+
import { sh } from '../tools/_shell'
|
|
14
|
+
import { computeConfigChange, readBuildConfig } from './config-diff'
|
|
15
|
+
import { appendEditRecord, editProvenance } from '../tools/_edits-log'
|
|
16
|
+
import { isTruthyEnv } from '../tools/mcp-env'
|
|
13
17
|
import type {
|
|
14
18
|
Application,
|
|
15
19
|
Data,
|
|
@@ -71,6 +75,31 @@ const assertEntryId = (
|
|
|
71
75
|
return id
|
|
72
76
|
}
|
|
73
77
|
|
|
78
|
+
// Per-compile error collection. Instead of throwing on the first bad entity, record the
|
|
79
|
+
// message and skip that entity, so a single compile surfaces every entity's first error
|
|
80
|
+
// (e.g. an invalid brick id, generator id and data-calc id all in one run). `errors` is
|
|
81
|
+
// local to each compile() call — no shared state — and compile throws an aggregated error
|
|
82
|
+
// at the end when any were collected, so a failed compile still rejects and never returns
|
|
83
|
+
// or writes a partial config.
|
|
84
|
+
const collect = <T>(errors: string[], fn: () => T, fallback: T): T => {
|
|
85
|
+
try {
|
|
86
|
+
return fn()
|
|
87
|
+
} catch (error) {
|
|
88
|
+
errors.push(error instanceof Error ? error.message : String(error))
|
|
89
|
+
return fallback
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// reduce() into an id-keyed map, recording a per-item compile error and skipping that item
|
|
94
|
+
// instead of aborting the whole compile. The callback is the last argument (and the init is
|
|
95
|
+
// always {}) so the formatter keeps its body inline rather than re-indenting it.
|
|
96
|
+
const collectReduce = <T>(
|
|
97
|
+
errors: string[],
|
|
98
|
+
items: T[],
|
|
99
|
+
fn: (acc: Record<string, unknown>, item: T, index: number) => Record<string, unknown>,
|
|
100
|
+
): Record<string, unknown> =>
|
|
101
|
+
items.reduce((acc, item, index) => collect(errors, () => fn(acc, item, index), acc), {})
|
|
102
|
+
|
|
74
103
|
const compileProperty = (property, errorReference: string, result = {}) => {
|
|
75
104
|
if (Array.isArray(property)) {
|
|
76
105
|
return property.map((p) => compileProperty(p, errorReference))
|
|
@@ -97,17 +126,28 @@ const compileProperty = (property, errorReference: string, result = {}) => {
|
|
|
97
126
|
const compileScriptCalculationCode = (code = '') => {
|
|
98
127
|
try {
|
|
99
128
|
const program = parseAST(code, { sourceType: 'module', ecmaVersion: 2020 })
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
129
|
+
// The stored config holds the bare function body, which codegen re-wraps as
|
|
130
|
+
// `export function main() { <code> }`. Unwrap it back to that body here.
|
|
131
|
+
let block = ((program.body[0] as ExportNamedDeclaration).declaration as FunctionDeclaration)
|
|
132
|
+
?.body as BlockStatement | undefined
|
|
133
|
+
if (!block) return code || ''
|
|
134
|
+
// Earlier versions emitted the whole BlockStatement (braces included), so every
|
|
135
|
+
// compile -> codegen round-trip nested the body in one more `{ }`. Emit the inner
|
|
136
|
+
// statements instead, collapsing any wrapper blocks previous round-trips added so
|
|
137
|
+
// existing over-wrapped sandboxes heal on the next compile.
|
|
138
|
+
while (block.body.length === 1 && block.body[0].type === 'BlockStatement') {
|
|
139
|
+
block = block.body[0] as BlockStatement
|
|
140
|
+
}
|
|
141
|
+
return escodegen.generate(
|
|
142
|
+
{ type: 'Program', body: block.body },
|
|
143
|
+
{
|
|
144
|
+
format: {
|
|
145
|
+
indent: { style: ' ' },
|
|
146
|
+
semicolons: false,
|
|
147
|
+
},
|
|
148
|
+
comment: true,
|
|
108
149
|
},
|
|
109
|
-
|
|
110
|
-
})
|
|
150
|
+
)
|
|
111
151
|
} catch {
|
|
112
152
|
return code || ''
|
|
113
153
|
}
|
|
@@ -154,11 +194,13 @@ const basicAnimationEvents = ['show', 'standby', 'breatheStart']
|
|
|
154
194
|
|
|
155
195
|
const compileAnimations = (
|
|
156
196
|
templateKey: string,
|
|
157
|
-
animations: { [key: string]: Animation },
|
|
197
|
+
animations: { [key: string]: Animation | (() => Animation) },
|
|
158
198
|
errorReference: string,
|
|
159
199
|
) =>
|
|
160
200
|
Object.entries(animations).reduce((acc, [key, animation]) => {
|
|
161
|
-
|
|
201
|
+
// Animation events accept either a direct Animation or a getter; unwrap.
|
|
202
|
+
const resolved = typeof animation === 'function' ? animation() : animation
|
|
203
|
+
const animationId = assertEntryId(resolved?.id, 'ANIMATION', errorReference)
|
|
162
204
|
acc[convertEventKey(basicAnimationEvents.includes(key) ? 'BRICK' : templateKey, key)] =
|
|
163
205
|
`ANIMATION#${animationId}`
|
|
164
206
|
return acc
|
|
@@ -179,10 +221,17 @@ const compileEvents = (
|
|
|
179
221
|
|
|
180
222
|
let handlerKey
|
|
181
223
|
let handlerTemplateKey
|
|
182
|
-
if (
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
224
|
+
if (typeof handler === 'string') {
|
|
225
|
+
// Only the literal 'system' handler is normalized to the SYSTEM template key.
|
|
226
|
+
// SubspaceID (SUBSPACE_*) and ItemBrickID handlers are kept verbatim: the runtime
|
|
227
|
+
// resolves them case-sensitively (see mapEventMapHandlersWithNewId), so uppercasing
|
|
228
|
+
// a mixed-case ItemBrickID would break handler-to-item event wiring.
|
|
229
|
+
if (handler === 'system') {
|
|
230
|
+
handlerKey = 'SYSTEM'
|
|
231
|
+
handlerTemplateKey = 'SYSTEM'
|
|
232
|
+
} else {
|
|
233
|
+
handlerKey = handler
|
|
234
|
+
}
|
|
186
235
|
} else if (typeof handler === 'function') {
|
|
187
236
|
let instance = handler()
|
|
188
237
|
if (instance?.id) {
|
|
@@ -301,6 +350,162 @@ const animationTypeMap = {
|
|
|
301
350
|
AnimationTimingConfig: 'timing',
|
|
302
351
|
AnimationSpringConfig: 'spring',
|
|
303
352
|
AnimationDecayConfig: 'decay',
|
|
353
|
+
} as const
|
|
354
|
+
|
|
355
|
+
type CompiledAnimationType = (typeof animationTypeMap)[keyof typeof animationTypeMap]
|
|
356
|
+
type WarningMetadata = Record<string, unknown>
|
|
357
|
+
|
|
358
|
+
const animationProperties = new Set([
|
|
359
|
+
'transform.translateX',
|
|
360
|
+
'transform.translateY',
|
|
361
|
+
'transform.scale',
|
|
362
|
+
'transform.scaleX',
|
|
363
|
+
'transform.scaleY',
|
|
364
|
+
'transform.rotate',
|
|
365
|
+
'transform.rotateX',
|
|
366
|
+
'transform.rotateY',
|
|
367
|
+
'opacity',
|
|
368
|
+
])
|
|
369
|
+
|
|
370
|
+
const animationComposeTypes = new Set(['parallel', 'sequence'])
|
|
371
|
+
const springConfigFamilies = [
|
|
372
|
+
['stiffness', 'damping', 'mass'],
|
|
373
|
+
['tension', 'friction'],
|
|
374
|
+
['bounciness', 'speed'],
|
|
375
|
+
]
|
|
376
|
+
const springConfigFamilyKeys = new Set(springConfigFamilies.flat())
|
|
377
|
+
|
|
378
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
379
|
+
Boolean(value) && typeof value === 'object' && !Array.isArray(value)
|
|
380
|
+
|
|
381
|
+
const hasDefinedConfigValue = (config: Record<string, unknown>, key: string) =>
|
|
382
|
+
config[key] !== undefined
|
|
383
|
+
|
|
384
|
+
const assertConfigValue = (
|
|
385
|
+
config: Record<string, unknown>,
|
|
386
|
+
key: string,
|
|
387
|
+
errorReference: string,
|
|
388
|
+
) => {
|
|
389
|
+
if (!hasDefinedConfigValue(config, key)) {
|
|
390
|
+
throw new Error(`Invalid animation config ${errorReference}: missing "${key}"`)
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const assertAnimationProperty = (property: unknown, errorReference: string) => {
|
|
395
|
+
if (typeof property !== 'string' || !animationProperties.has(property)) {
|
|
396
|
+
throw new Error(
|
|
397
|
+
`Invalid animation property${errorReference ? ` ${errorReference}` : ''}: ${String(
|
|
398
|
+
property,
|
|
399
|
+
)}`,
|
|
400
|
+
)
|
|
401
|
+
}
|
|
402
|
+
return property
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const getAnimationType = (config: unknown, errorReference: string): CompiledAnimationType => {
|
|
406
|
+
if (!isRecord(config)) {
|
|
407
|
+
throw new Error(`Invalid animation config ${errorReference}: config must be an object`)
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const animationType = animationTypeMap[config.__type as keyof typeof animationTypeMap]
|
|
411
|
+
if (!animationType) {
|
|
412
|
+
throw new Error(`Invalid animation config type ${errorReference}: ${String(config.__type)}`)
|
|
413
|
+
}
|
|
414
|
+
return animationType
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const assertAnimationComposeType = (composeType: unknown, errorReference: string) => {
|
|
418
|
+
if (typeof composeType !== 'string' || !animationComposeTypes.has(composeType)) {
|
|
419
|
+
throw new Error(`Invalid animation compose type ${errorReference}: ${String(composeType)}`)
|
|
420
|
+
}
|
|
421
|
+
return composeType
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const pickDefinedConfigValues = (config: Record<string, unknown>, keys: string[]) =>
|
|
425
|
+
keys.reduce((acc, key) => {
|
|
426
|
+
if (hasDefinedConfigValue(config, key)) acc[key] = config[key]
|
|
427
|
+
return acc
|
|
428
|
+
}, {})
|
|
429
|
+
|
|
430
|
+
const getDefinedConfigKeys = (config: Record<string, unknown>, keys: string[]) =>
|
|
431
|
+
keys.filter((key) => hasDefinedConfigValue(config, key))
|
|
432
|
+
|
|
433
|
+
const formatWarningMetadata = (metadata: WarningMetadata = {}) =>
|
|
434
|
+
Object.entries(metadata)
|
|
435
|
+
.filter(([, value]) => value !== undefined && value !== null && value !== '')
|
|
436
|
+
.map(([key, value]) => `${key}: ${String(value)}`)
|
|
437
|
+
.join(', ')
|
|
438
|
+
|
|
439
|
+
const formatWarningReference = (metadata?: WarningMetadata) => {
|
|
440
|
+
const metadataText = formatWarningMetadata(metadata)
|
|
441
|
+
return metadataText ? ` [${metadataText}]` : ''
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const normalizeSpringConfig = (
|
|
445
|
+
config: Record<string, unknown>,
|
|
446
|
+
errorReference: string,
|
|
447
|
+
warningMetadata?: WarningMetadata,
|
|
448
|
+
): Record<string, unknown> => {
|
|
449
|
+
assertConfigValue(config, 'toValue', errorReference)
|
|
450
|
+
|
|
451
|
+
const usedFamilies = springConfigFamilies.filter((keys) =>
|
|
452
|
+
keys.some((key) => hasDefinedConfigValue(config, key)),
|
|
453
|
+
)
|
|
454
|
+
if (usedFamilies.length <= 1) return config
|
|
455
|
+
|
|
456
|
+
const configWithoutSpringFamily = Object.entries(config).reduce((acc, [key, value]) => {
|
|
457
|
+
if (!springConfigFamilyKeys.has(key)) acc[key] = value
|
|
458
|
+
return acc
|
|
459
|
+
}, {})
|
|
460
|
+
|
|
461
|
+
// Match runtime normalization: physical spring values are most explicit,
|
|
462
|
+
// otherwise preserve BRICKS' historical tension/friction controls.
|
|
463
|
+
const resolvedFamily =
|
|
464
|
+
usedFamilies.find((keys) => keys.includes('stiffness')) ||
|
|
465
|
+
usedFamilies.find((keys) => keys.includes('tension')) ||
|
|
466
|
+
usedFamilies[0]
|
|
467
|
+
const resolvedFamilyKeys = getDefinedConfigKeys(config, resolvedFamily)
|
|
468
|
+
const droppedFamilyKeys = usedFamilies
|
|
469
|
+
.filter((keys) => keys !== resolvedFamily)
|
|
470
|
+
.flatMap((keys) => getDefinedConfigKeys(config, keys))
|
|
471
|
+
|
|
472
|
+
console.warn(
|
|
473
|
+
`[Warning] Resolved animation spring config${formatWarningReference(
|
|
474
|
+
warningMetadata,
|
|
475
|
+
)}: using ${resolvedFamilyKeys.join('/')}, dropping ${droppedFamilyKeys.join('/')}`,
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
return {
|
|
479
|
+
...configWithoutSpringFamily,
|
|
480
|
+
...pickDefinedConfigValues(config, resolvedFamily),
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const compileAnimationConfig = (
|
|
485
|
+
animationType: CompiledAnimationType,
|
|
486
|
+
config: unknown,
|
|
487
|
+
errorReference: string,
|
|
488
|
+
warningMetadata?: WarningMetadata,
|
|
489
|
+
) => {
|
|
490
|
+
if (!isRecord(config)) {
|
|
491
|
+
throw new Error(`Invalid animation config ${errorReference}: config must be an object`)
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const compiledConfig = compileProperty(omit(config, '__type'), errorReference)
|
|
495
|
+
|
|
496
|
+
if (!isRecord(compiledConfig)) {
|
|
497
|
+
throw new Error(`Invalid animation config ${errorReference}: config must compile to an object`)
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
if (animationType === 'timing') {
|
|
501
|
+
assertConfigValue(compiledConfig, 'toValue', errorReference)
|
|
502
|
+
} else if (animationType === 'spring') {
|
|
503
|
+
return normalizeSpringConfig(compiledConfig, errorReference, warningMetadata)
|
|
504
|
+
} else if (animationType === 'decay') {
|
|
505
|
+
assertConfigValue(compiledConfig, 'velocity', errorReference)
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
return compiledConfig
|
|
304
509
|
}
|
|
305
510
|
|
|
306
511
|
const compileFrame = (frame: Canvas['items'][number]['frame']) => ({
|
|
@@ -324,9 +529,12 @@ const preloadTypes = [
|
|
|
324
529
|
'media-resource-audio',
|
|
325
530
|
'media-resource-file',
|
|
326
531
|
'lottie-file-uri',
|
|
532
|
+
'rive-file-uri',
|
|
327
533
|
'ggml-model-asset',
|
|
328
534
|
'gguf-model-asset',
|
|
329
535
|
'binary-asset',
|
|
536
|
+
'mlx-model-asset',
|
|
537
|
+
'scene3d-objects',
|
|
330
538
|
]
|
|
331
539
|
|
|
332
540
|
const compileKind = (kind: Data['kind']) => {
|
|
@@ -353,9 +561,10 @@ const compileKind = (kind: Data['kind']) => {
|
|
|
353
561
|
}
|
|
354
562
|
|
|
355
563
|
const compileRemoteUpdate = (remoteUpdate: Data['remoteUpdate']) => {
|
|
356
|
-
if (!remoteUpdate) return {}
|
|
357
|
-
if (remoteUpdate.type === 'auto') return { enable_remote_update: true }
|
|
564
|
+
if (!remoteUpdate) return { bank_type: 'none' }
|
|
565
|
+
if (remoteUpdate.type === 'auto') return { bank_type: 'create', enable_remote_update: true }
|
|
358
566
|
return {
|
|
567
|
+
bank_type: remoteUpdate.type === 'device-specific' ? 'create-device-specific' : 'global',
|
|
359
568
|
enable_remote_update: true,
|
|
360
569
|
...(remoteUpdate.type === 'device-specific' ? { use_remote_id_prefix: true } : {}),
|
|
361
570
|
...(remoteUpdate.type === 'global-data' ? { global_remote_update_prop: remoteUpdate.id } : {}),
|
|
@@ -411,7 +620,7 @@ function compileRunArray(run: unknown[]): unknown[] {
|
|
|
411
620
|
/**
|
|
412
621
|
* Compile typed TestCase to raw format (strips __typename)
|
|
413
622
|
*/
|
|
414
|
-
const compileTestCase = (testCase: TestCase) => ({
|
|
623
|
+
export const compileTestCase = (testCase: TestCase) => ({
|
|
415
624
|
id: testCase.id,
|
|
416
625
|
name: testCase.name,
|
|
417
626
|
hide_short_ref: testCase.hideShortRef,
|
|
@@ -433,7 +642,10 @@ const compileTestCase = (testCase: TestCase) => ({
|
|
|
433
642
|
variable: cond.variable,
|
|
434
643
|
operator: cond.operator,
|
|
435
644
|
value: cond.value,
|
|
436
|
-
jump_to
|
|
645
|
+
// `jump_to` may be a getter (() => TestCase) for dynamic case ids (the project generator
|
|
646
|
+
// emits this form). Resolve it to its id like the `run` array does — otherwise the function
|
|
647
|
+
// is JSON-serialized to nothing and the conditional jump silently vanishes from the config.
|
|
648
|
+
jump_to: compileRunElement(cond.jump_to),
|
|
437
649
|
}
|
|
438
650
|
}),
|
|
439
651
|
})
|
|
@@ -485,6 +697,7 @@ const compileAutomationTest = (
|
|
|
485
697
|
|
|
486
698
|
return {
|
|
487
699
|
id: testId,
|
|
700
|
+
alias: test.alias,
|
|
488
701
|
title: test.title,
|
|
489
702
|
hide_short_ref: test.hideShortRef,
|
|
490
703
|
timeout: test.timeout,
|
|
@@ -538,12 +751,60 @@ const compileAutomation = (automationMap: AutomationMap) =>
|
|
|
538
751
|
}),
|
|
539
752
|
)
|
|
540
753
|
|
|
754
|
+
const buildDefaultExpandedState = (subspace: Subspace) => ({
|
|
755
|
+
brick: false,
|
|
756
|
+
generator: true,
|
|
757
|
+
canvas: (subspace.canvases || []).reduce((acc, canvas) => {
|
|
758
|
+
if (canvas?.id) acc[canvas.id] = false
|
|
759
|
+
return acc
|
|
760
|
+
}, {}),
|
|
761
|
+
property_bank: false,
|
|
762
|
+
property_bank_calc: true,
|
|
763
|
+
})
|
|
764
|
+
|
|
765
|
+
// Record the minimal compiled-config delta this compile produced to the shared audit
|
|
766
|
+
// log (`.bricks/edits.jsonl`), so editing files directly and running `bun compile`
|
|
767
|
+
// leaves the same trail as the MCP source-editing tools. Maintained only in the
|
|
768
|
+
// editing-tools context (`BRICKS_CTOR_ENABLE_EDITING_TOOLS`); the source-editing tools
|
|
769
|
+
// turn it off for their verify compiles (see _verify.ts) so a tool edit records one
|
|
770
|
+
// richer entry instead of an extra generic compile entry. Also silent when there is no
|
|
771
|
+
// prior build to diff against (fresh projects, package tests, tooling outside a project).
|
|
772
|
+
const recordConfigChange = async (previousConfig: unknown, config: unknown) => {
|
|
773
|
+
if (previousConfig == null) return
|
|
774
|
+
if (!isTruthyEnv(process.env.BRICKS_CTOR_ENABLE_EDITING_TOOLS)) return
|
|
775
|
+
// The baseline was parsed from JSON; `computeConfigChange` applies the same
|
|
776
|
+
// JSON-omitted-field rules lazily so compile avoids cloning the full config.
|
|
777
|
+
const change = computeConfigChange(previousConfig, config)
|
|
778
|
+
if (change.status !== 'ok') return
|
|
779
|
+
await appendEditRecord(process.cwd(), {
|
|
780
|
+
ts: new Date().toISOString(),
|
|
781
|
+
tool: 'compile',
|
|
782
|
+
provenance: editProvenance(),
|
|
783
|
+
outcome: 'ok',
|
|
784
|
+
summary:
|
|
785
|
+
change.opCount === 0
|
|
786
|
+
? 'compile: no config change'
|
|
787
|
+
: `compile: ${change.opCount} config op(s)`,
|
|
788
|
+
configChange: change,
|
|
789
|
+
}).catch(() => undefined)
|
|
790
|
+
}
|
|
791
|
+
|
|
541
792
|
export const compile = async (app: Application) => {
|
|
542
793
|
await new Promise((resolve) => setImmediate(resolve, 0))
|
|
794
|
+
// Collected entity-level compile errors (see collect/collectReduce). Aggregated and
|
|
795
|
+
// thrown at the end so one compile reports every entity's first error.
|
|
796
|
+
const errors: string[] = []
|
|
797
|
+
// Snapshot the prior build artifact before the caller's compile.ts overwrites it, so
|
|
798
|
+
// the config change introduced by this compile can be recorded on return.
|
|
799
|
+
const previousConfig = await readBuildConfig(process.cwd())
|
|
543
800
|
const timestamp = Date.now()
|
|
544
801
|
// Pre-index subspace ids so the canvas-item validation below stays O(1).
|
|
545
802
|
const subspaceIdSet = new Set(app.subspaces.map((s) => s.id))
|
|
546
|
-
|
|
803
|
+
let compiledAutomationMap: ReturnType<typeof compileAutomation> | null = null
|
|
804
|
+
if (app.automationMap) {
|
|
805
|
+
const { automationMap } = app
|
|
806
|
+
compiledAutomationMap = collect(errors, () => compileAutomation(automationMap), null)
|
|
807
|
+
}
|
|
547
808
|
const config = {
|
|
548
809
|
title: `${app.name || 'Unknown'}(${timestamp})`,
|
|
549
810
|
subspace_map: app.subspaces.reduce((subspaceMap, subspace, subspaceIndex) => {
|
|
@@ -559,7 +820,14 @@ export const compile = async (app: Application) => {
|
|
|
559
820
|
// validation (root_canvas_id is required before the conditional
|
|
560
821
|
// schema fix is published).
|
|
561
822
|
if (subspace.module?.link) {
|
|
562
|
-
|
|
823
|
+
// Seed the placeholder id from the (stable) subspace id. `makeId('canvas')` would take
|
|
824
|
+
// the count-fallback branch (a process-global counter that is never reset), so the
|
|
825
|
+
// placeholder id depended on how many prior count-fallback ids had been minted — making
|
|
826
|
+
// it differ between recompiles and breaking compile's byte-stable-output contract
|
|
827
|
+
// (phantom config-change ops). `makeSeededId` keeps no global state, so identical source
|
|
828
|
+
// recompiles to an identical id. (`makeId('canvas', alias)` would instead throw
|
|
829
|
+
// "Duplicate makeId alias" on the second compile in a long-lived process.)
|
|
830
|
+
const placeholderCanvasId = makeSeededId('canvas', `${subspaceId}:module-placeholder`)
|
|
563
831
|
subspaceMap[subspaceId] = {
|
|
564
832
|
title: subspace.title,
|
|
565
833
|
description: subspace.description,
|
|
@@ -611,7 +879,7 @@ export const compile = async (app: Application) => {
|
|
|
611
879
|
property_bank: !subspace.unexpanded.data,
|
|
612
880
|
property_bank_calc: !subspace.unexpanded.dataCalculation,
|
|
613
881
|
}
|
|
614
|
-
:
|
|
882
|
+
: buildDefaultExpandedState(subspace),
|
|
615
883
|
layout: {
|
|
616
884
|
width: subspace.layout?.width,
|
|
617
885
|
height: subspace.layout?.height,
|
|
@@ -622,50 +890,78 @@ export const compile = async (app: Application) => {
|
|
|
622
890
|
change_canvas: subspace.localSyncChangeCanvas,
|
|
623
891
|
}
|
|
624
892
|
: undefined,
|
|
625
|
-
animation_map:
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
893
|
+
animation_map: collectReduce(
|
|
894
|
+
errors,
|
|
895
|
+
subspace.animations,
|
|
896
|
+
(map, animation, animationIndex) => {
|
|
897
|
+
const animationId = assertEntryId(
|
|
898
|
+
animation?.id,
|
|
899
|
+
'ANIMATION',
|
|
900
|
+
`(animation index: ${animationIndex}, subspace: ${subspaceId})`,
|
|
901
|
+
)
|
|
631
902
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
903
|
+
const animationTypename = animation.__typename
|
|
904
|
+
if (animationTypename === 'Animation') {
|
|
905
|
+
const animationDef = animation as AnimationDef
|
|
906
|
+
const animationErrorReference = `(animation: ${animationId}, subspace ${subspaceId})`
|
|
907
|
+
const animationWarningMetadata = {
|
|
908
|
+
animationIndex,
|
|
909
|
+
animationTitle: animationDef.title,
|
|
910
|
+
animationAlias: animationDef.alias,
|
|
911
|
+
animationProperty: animationDef.property,
|
|
912
|
+
subspaceIndex,
|
|
913
|
+
subspaceTitle: subspace.title,
|
|
914
|
+
}
|
|
915
|
+
const animationType = getAnimationType(animationDef.config, animationErrorReference)
|
|
916
|
+
map[animationId] = {
|
|
917
|
+
alias: animationDef.alias,
|
|
918
|
+
title: animationDef.title,
|
|
919
|
+
description: animationDef.description,
|
|
920
|
+
hide_short_ref: animationDef.hideShortRef,
|
|
921
|
+
animationRunType: animationDef.runType,
|
|
922
|
+
property: assertAnimationProperty(animationDef.property, animationErrorReference),
|
|
923
|
+
type: animationType,
|
|
924
|
+
config: compileAnimationConfig(
|
|
925
|
+
animationType,
|
|
926
|
+
animationDef.config,
|
|
927
|
+
animationErrorReference,
|
|
928
|
+
animationWarningMetadata,
|
|
929
|
+
),
|
|
930
|
+
}
|
|
931
|
+
} else if (animationTypename === 'AnimationCompose') {
|
|
932
|
+
const animationDef = animation as AnimationComposeDef
|
|
933
|
+
const animationErrorReference = `(animation: ${animationId}, subspace ${subspaceId})`
|
|
934
|
+
map[animationId] = {
|
|
935
|
+
alias: animationDef.alias,
|
|
936
|
+
title: animationDef.title,
|
|
937
|
+
description: animationDef.description,
|
|
938
|
+
hide_short_ref: animationDef.hideShortRef,
|
|
939
|
+
animationRunType: animationDef.runType,
|
|
940
|
+
compose_type: assertAnimationComposeType(
|
|
941
|
+
animationDef.composeType,
|
|
942
|
+
animationErrorReference,
|
|
943
|
+
),
|
|
944
|
+
item_list: animationDef.items.map((item, index) => {
|
|
945
|
+
const innerAnimation = item()
|
|
946
|
+
const innerAnimationId = assertEntryId(
|
|
947
|
+
innerAnimation?.id,
|
|
948
|
+
'ANIMATION',
|
|
949
|
+
`(animation item index: ${index}, animation: ${animationId}, subspace ${subspaceId})`,
|
|
661
950
|
)
|
|
662
|
-
|
|
663
|
-
|
|
951
|
+
return { animation_id: innerAnimationId }
|
|
952
|
+
}),
|
|
953
|
+
}
|
|
954
|
+
} else {
|
|
955
|
+
throw new Error(
|
|
956
|
+
`Invalid animation typename (animation: ${animationId}, subspace ${subspaceId}): ${String(
|
|
957
|
+
animationTypename,
|
|
958
|
+
)}`,
|
|
959
|
+
)
|
|
664
960
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
brick_map: subspace.bricks
|
|
961
|
+
return map
|
|
962
|
+
},
|
|
963
|
+
),
|
|
964
|
+
brick_map: collectReduce(errors, subspace.bricks, (map, brick, brickIndex) => {
|
|
669
965
|
const brickId = assertEntryId(
|
|
670
966
|
brick.id,
|
|
671
967
|
'BRICK',
|
|
@@ -830,9 +1126,9 @@ export const compile = async (app: Application) => {
|
|
|
830
1126
|
}, {}),
|
|
831
1127
|
}
|
|
832
1128
|
return map
|
|
833
|
-
}
|
|
1129
|
+
}),
|
|
834
1130
|
root_canvas_id: rootCanvasId,
|
|
835
|
-
canvas_map: subspace.canvases
|
|
1131
|
+
canvas_map: collectReduce(errors, subspace.canvases, (map, canvas, canvasIndex) => {
|
|
836
1132
|
const canvasId = assertEntryId(
|
|
837
1133
|
canvas.id,
|
|
838
1134
|
'CANVAS',
|
|
@@ -912,81 +1208,85 @@ export const compile = async (app: Application) => {
|
|
|
912
1208
|
}),
|
|
913
1209
|
}
|
|
914
1210
|
return map
|
|
915
|
-
}
|
|
916
|
-
generator_map:
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
1211
|
+
}),
|
|
1212
|
+
generator_map: collectReduce(
|
|
1213
|
+
errors,
|
|
1214
|
+
subspace.generators,
|
|
1215
|
+
(map, generator, generatorIndex) => {
|
|
1216
|
+
const generatorId = assertEntryId(
|
|
1217
|
+
generator.id,
|
|
1218
|
+
'GENERATOR',
|
|
1219
|
+
`(generator index: ${generatorIndex}, subspace: ${subspaceId})`,
|
|
1220
|
+
)
|
|
922
1221
|
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
1222
|
+
map[generatorId] = {
|
|
1223
|
+
template_key: generator.templateKey,
|
|
1224
|
+
alias: generator.alias,
|
|
1225
|
+
title: generator.title,
|
|
1226
|
+
description: generator.description,
|
|
1227
|
+
hide_short_ref: generator.hideShortRef,
|
|
1228
|
+
local_sync: generator.localSyncRunMode
|
|
1229
|
+
? {
|
|
1230
|
+
run_mode: generator.localSyncRunMode,
|
|
1231
|
+
}
|
|
1232
|
+
: undefined,
|
|
1233
|
+
property: compileProperty(
|
|
1234
|
+
generator.property || {},
|
|
1235
|
+
`(generator: ${generatorId}, subspace ${subspaceId})`,
|
|
1236
|
+
),
|
|
1237
|
+
event_map: compileEvents(generator.templateKey, generator.events || {}, {
|
|
1238
|
+
camelCase: false,
|
|
1239
|
+
errorReference: `(generator: ${generatorId}, subspace ${subspaceId})`,
|
|
1240
|
+
}),
|
|
1241
|
+
outlet: compileOutlets(
|
|
1242
|
+
generator.templateKey,
|
|
1243
|
+
generator.outlets || {},
|
|
1244
|
+
`(generator: ${generatorId}, subspace ${subspaceId})`,
|
|
1245
|
+
),
|
|
1246
|
+
state_group: generator.switches?.reduce((acc, switchCase, switchIndex) => {
|
|
1247
|
+
const switchId = assertEntryId(
|
|
1248
|
+
switchCase.id,
|
|
1249
|
+
'BRICK_STATE_GROUP',
|
|
1250
|
+
`(generator: ${generatorId}, switch index: ${switchIndex}, subspace ${subspaceId})`,
|
|
1251
|
+
)
|
|
953
1252
|
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
1253
|
+
acc[switchId] = {
|
|
1254
|
+
title: switchCase.title,
|
|
1255
|
+
description: switchCase.description,
|
|
1256
|
+
break: switchCase.break,
|
|
1257
|
+
override: switchCase.override,
|
|
1258
|
+
commented: switchCase.disabled,
|
|
1259
|
+
conds: compileSwitchConds(
|
|
1260
|
+
generator.templateKey,
|
|
1261
|
+
switchCase.conds || [],
|
|
1262
|
+
`(generator: ${generatorId}, switch: ${switchId}, subspace ${subspaceId})`,
|
|
1263
|
+
),
|
|
1264
|
+
property: compileProperty(
|
|
1265
|
+
switchCase.property,
|
|
1266
|
+
`(generator: ${generatorId}, switch: ${switchId}, subspace ${subspaceId})`,
|
|
1267
|
+
),
|
|
1268
|
+
outlet: compileOutlets(
|
|
1269
|
+
generator.templateKey,
|
|
1270
|
+
switchCase.outlets || {},
|
|
1271
|
+
`(generator: ${generatorId}, switch: ${switchId}, subspace ${subspaceId})`,
|
|
1272
|
+
),
|
|
1273
|
+
event_map: compileEvents(generator.templateKey, switchCase.events || {}, {
|
|
1274
|
+
camelCase: false,
|
|
1275
|
+
errorReference: `(generator: ${generatorId}, switch: ${switchId}, subspace ${subspaceId})`,
|
|
1276
|
+
}),
|
|
1277
|
+
animation: compileAnimations(
|
|
1278
|
+
generator.templateKey,
|
|
1279
|
+
switchCase.animation || {},
|
|
1280
|
+
`(generator: ${generatorId}, switch: ${switchId}, subspace ${subspaceId})`,
|
|
1281
|
+
),
|
|
1282
|
+
}
|
|
1283
|
+
return acc
|
|
1284
|
+
}, {}),
|
|
1285
|
+
}
|
|
1286
|
+
return map
|
|
1287
|
+
},
|
|
1288
|
+
),
|
|
1289
|
+
property_bank_map: collectReduce(errors, subspace.data, (map, data, dataIndex) => {
|
|
990
1290
|
const dataId = assertEntryId(
|
|
991
1291
|
data.id,
|
|
992
1292
|
'PROPERTY_BANK_DATA_NODE',
|
|
@@ -1009,7 +1309,7 @@ export const compile = async (app: Application) => {
|
|
|
1009
1309
|
...compileRemoteUpdate(data.remoteUpdate),
|
|
1010
1310
|
routing: data.routing,
|
|
1011
1311
|
schema: data.schema,
|
|
1012
|
-
type: data.type,
|
|
1312
|
+
type: data.type === 'boolean' ? 'bool' : data.type,
|
|
1013
1313
|
...compileKind(data.kind),
|
|
1014
1314
|
value: compileProperty(data.value, `(data: ${dataId}, subspace ${subspaceId})`),
|
|
1015
1315
|
event_map: compileEvents('PROPERTY_BANK', data.events || {}, {
|
|
@@ -1020,229 +1320,229 @@ export const compile = async (app: Application) => {
|
|
|
1020
1320
|
hit_regex: data.hit_regex,
|
|
1021
1321
|
}
|
|
1022
1322
|
return map
|
|
1023
|
-
}
|
|
1024
|
-
property_bank_calc_map:
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1323
|
+
}),
|
|
1324
|
+
property_bank_calc_map: collectReduce(
|
|
1325
|
+
errors,
|
|
1326
|
+
subspace.dataCalculation,
|
|
1327
|
+
(map, dataCalc, dataCalcIndex) => {
|
|
1328
|
+
const dataCalcId = assertEntryId(
|
|
1329
|
+
dataCalc.id,
|
|
1330
|
+
'PROPERTY_BANK_COMMAND_MAP',
|
|
1331
|
+
`(data calc index: ${dataCalcIndex}, subspace: ${subspaceId})`,
|
|
1332
|
+
)
|
|
1030
1333
|
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
if (dataCalc.triggerMode) calc.trigger_type = dataCalc.triggerMode
|
|
1037
|
-
if (dataCalc.__typename === 'DataCalculationMap') {
|
|
1038
|
-
calc.type = 'general'
|
|
1039
|
-
const mapCalc = dataCalc as DataCalculationMap
|
|
1040
|
-
|
|
1041
|
-
const getNodeId = (
|
|
1042
|
-
node: DataCalculationData | DataCommand,
|
|
1043
|
-
reference = '',
|
|
1044
|
-
nodeIndex?: number,
|
|
1045
|
-
) => {
|
|
1046
|
-
const nodeReference = [
|
|
1047
|
-
`data calc: ${dataCalcId}`,
|
|
1048
|
-
`subspace: ${subspaceId}`,
|
|
1049
|
-
typeof nodeIndex === 'number' ? `node index: ${nodeIndex}` : undefined,
|
|
1050
|
-
reference || undefined,
|
|
1051
|
-
]
|
|
1052
|
-
.filter(Boolean)
|
|
1053
|
-
.join(', ')
|
|
1054
|
-
|
|
1055
|
-
if (node.__typename === 'DataCalculationData') {
|
|
1056
|
-
return assertEntryId(
|
|
1057
|
-
node.data()?.id,
|
|
1058
|
-
'PROPERTY_BANK_DATA_NODE',
|
|
1059
|
-
`(${nodeReference})`,
|
|
1060
|
-
)
|
|
1061
|
-
}
|
|
1062
|
-
if (node.__typename === 'DataCommand') {
|
|
1063
|
-
return assertEntryId(node.id, 'PROPERTY_BANK_COMMAND_NODE', `(${nodeReference})`)
|
|
1064
|
-
}
|
|
1065
|
-
throw new Error(`Invalid node: ${JSON.stringify(node)}`)
|
|
1334
|
+
const calc: any = {
|
|
1335
|
+
alias: dataCalc.alias,
|
|
1336
|
+
title: dataCalc.title,
|
|
1337
|
+
description: dataCalc.description,
|
|
1338
|
+
hide_short_ref: dataCalc.hideShortRef,
|
|
1066
1339
|
}
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1340
|
+
if (dataCalc.triggerMode) calc.trigger_type = dataCalc.triggerMode
|
|
1341
|
+
if (dataCalc.__typename === 'DataCalculationMap') {
|
|
1342
|
+
calc.type = 'general'
|
|
1343
|
+
const mapCalc = dataCalc as DataCalculationMap
|
|
1344
|
+
|
|
1345
|
+
const getNodeId = (
|
|
1346
|
+
node: DataCalculationData | DataCommand,
|
|
1347
|
+
reference = '',
|
|
1348
|
+
nodeIndex?: number,
|
|
1349
|
+
) => {
|
|
1350
|
+
const nodeReference = [
|
|
1351
|
+
`data calc: ${dataCalcId}`,
|
|
1352
|
+
`subspace: ${subspaceId}`,
|
|
1353
|
+
typeof nodeIndex === 'number' ? `node index: ${nodeIndex}` : undefined,
|
|
1354
|
+
reference || undefined,
|
|
1355
|
+
]
|
|
1356
|
+
.filter(Boolean)
|
|
1357
|
+
.join(', ')
|
|
1358
|
+
|
|
1359
|
+
if (node.__typename === 'DataCalculationData') {
|
|
1360
|
+
return assertEntryId(
|
|
1361
|
+
node.data()?.id,
|
|
1362
|
+
'PROPERTY_BANK_DATA_NODE',
|
|
1363
|
+
`(${nodeReference})`,
|
|
1364
|
+
)
|
|
1079
1365
|
}
|
|
1366
|
+
if (node.__typename === 'DataCommand') {
|
|
1367
|
+
return assertEntryId(node.id, 'PROPERTY_BANK_COMMAND_NODE', `(${nodeReference})`)
|
|
1368
|
+
}
|
|
1369
|
+
throw new Error(`Invalid node: ${JSON.stringify(node)}`)
|
|
1370
|
+
}
|
|
1080
1371
|
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1372
|
+
const generateInputPorts = (inputs) =>
|
|
1373
|
+
inputs.reduce((acc, port, portIndex) => {
|
|
1374
|
+
if (!acc[port.key]) acc[port.key] = null
|
|
1375
|
+
|
|
1376
|
+
let sourceId
|
|
1377
|
+
const sourceNode = port.source()
|
|
1378
|
+
if (
|
|
1379
|
+
sourceNode?.__typename === 'DataCalculationData' ||
|
|
1380
|
+
sourceNode?.__typename === 'DataCommand'
|
|
1381
|
+
) {
|
|
1382
|
+
sourceId = getNodeId(sourceNode, `input port index: ${portIndex}`)
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
if (!sourceId) return acc
|
|
1386
|
+
if (!acc[port.key]) acc[port.key] = []
|
|
1387
|
+
|
|
1388
|
+
acc[port.key].push({
|
|
1389
|
+
id: sourceId,
|
|
1390
|
+
port: port.sourceKey,
|
|
1391
|
+
disable_trigger_command: !port.trigger ? true : undefined,
|
|
1392
|
+
})
|
|
1393
|
+
return acc
|
|
1394
|
+
}, {})
|
|
1395
|
+
|
|
1396
|
+
const generateOutputPorts = (outputs) =>
|
|
1397
|
+
outputs.reduce((acc, port, portIndex) => {
|
|
1398
|
+
if (!acc[port.key]) acc[port.key] = null
|
|
1399
|
+
|
|
1400
|
+
let targetId
|
|
1401
|
+
const targetNode = port.target()
|
|
1402
|
+
if (
|
|
1403
|
+
targetNode?.__typename === 'DataCalculationData' ||
|
|
1404
|
+
targetNode?.__typename === 'DataCommand'
|
|
1405
|
+
) {
|
|
1406
|
+
targetId = getNodeId(targetNode, `output port index: ${portIndex}`)
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
if (!targetId) return acc
|
|
1410
|
+
if (!acc[port.key]) acc[port.key] = []
|
|
1411
|
+
|
|
1412
|
+
acc[port.key].push({
|
|
1413
|
+
id: targetId,
|
|
1414
|
+
port: port.targetKey,
|
|
1415
|
+
})
|
|
1416
|
+
return acc
|
|
1417
|
+
}, {})
|
|
1418
|
+
|
|
1419
|
+
calc.map = mapCalc.nodes.reduce((acc, node, nodeIndex) => {
|
|
1420
|
+
if (node.__typename === 'DataCalculationData') {
|
|
1421
|
+
const dataNode = node as DataCalculationData
|
|
1422
|
+
acc[getNodeId(dataNode, 'data node', nodeIndex)] = {
|
|
1423
|
+
title: dataNode.title,
|
|
1424
|
+
description: dataNode.description,
|
|
1425
|
+
hide_short_ref: dataNode.hideShortRef,
|
|
1426
|
+
type: 'data-node',
|
|
1427
|
+
properties: {},
|
|
1428
|
+
in: generateInputPorts(dataNode.inputs),
|
|
1429
|
+
out: generateOutputPorts(dataNode.outputs),
|
|
1430
|
+
}
|
|
1431
|
+
} else if (node.__typename === 'DataCommand') {
|
|
1432
|
+
const commandNode = node as DataCommand
|
|
1433
|
+
const commandName = commandNode.__commandName
|
|
1434
|
+
const type = commandName.split('_')[0].toLowerCase()
|
|
1435
|
+
|
|
1436
|
+
const args = commandNode.inputs.filter(
|
|
1437
|
+
(input) =>
|
|
1438
|
+
typeof input.source !== 'function' ||
|
|
1439
|
+
(input.source()?.__typename !== 'DataCalculationData' &&
|
|
1440
|
+
input.source()?.__typename !== 'DataCommand'),
|
|
1441
|
+
)
|
|
1442
|
+
const inputs = commandNode.inputs.filter(
|
|
1443
|
+
(input) =>
|
|
1444
|
+
typeof input.source === 'function' &&
|
|
1445
|
+
(input.source()?.__typename === 'DataCalculationData' ||
|
|
1446
|
+
input.source()?.__typename === 'DataCommand'),
|
|
1447
|
+
)
|
|
1448
|
+
acc[getNodeId(commandNode, 'command node', nodeIndex)] = {
|
|
1449
|
+
title: commandNode.title,
|
|
1450
|
+
description: commandNode.description,
|
|
1451
|
+
hide_short_ref: commandNode.hideShortRef,
|
|
1452
|
+
type: `command-node-${type}`,
|
|
1453
|
+
properties: {
|
|
1454
|
+
command: commandNode.__commandName,
|
|
1455
|
+
args: args.reduce((argsAcc, input) => {
|
|
1456
|
+
argsAcc[input.key] = input.source
|
|
1457
|
+
return argsAcc
|
|
1458
|
+
}, {}),
|
|
1459
|
+
},
|
|
1460
|
+
in: generateInputPorts(inputs),
|
|
1461
|
+
out: generateOutputPorts(commandNode.outputs),
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1089
1464
|
return acc
|
|
1090
1465
|
}, {})
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
) {
|
|
1102
|
-
targetId = getNodeId(targetNode, `output port index: ${portIndex}`)
|
|
1466
|
+
calc.editor_info = mapCalc.editorInfo.reduce((acc, editorInfo) => {
|
|
1467
|
+
acc[getNodeId(editorInfo.node, 'editor info node')] = {
|
|
1468
|
+
position: editorInfo.position,
|
|
1469
|
+
points: editorInfo.points.reduce((pointsAcc, point) => {
|
|
1470
|
+
const sourceId = getNodeId(point.source, 'editor info point source')
|
|
1471
|
+
const targetId = getNodeId(point.target, 'editor info point target')
|
|
1472
|
+
const key = `${sourceId}-${point.sourceOutputKey}-${targetId}-${point.targetInputKey}`
|
|
1473
|
+
pointsAcc[key] = point.positions
|
|
1474
|
+
return pointsAcc
|
|
1475
|
+
}, {}),
|
|
1103
1476
|
}
|
|
1104
|
-
|
|
1105
|
-
if (!targetId) return acc
|
|
1106
|
-
if (!acc[port.key]) acc[port.key] = []
|
|
1107
|
-
|
|
1108
|
-
acc[port.key].push({
|
|
1109
|
-
id: targetId,
|
|
1110
|
-
port: port.targetKey,
|
|
1111
|
-
})
|
|
1112
1477
|
return acc
|
|
1113
1478
|
}, {})
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
(input) =>
|
|
1134
|
-
typeof input.source !== 'function' ||
|
|
1135
|
-
(input.source()?.__typename !== 'DataCalculationData' &&
|
|
1136
|
-
input.source()?.__typename !== 'DataCommand'),
|
|
1137
|
-
)
|
|
1138
|
-
const inputs = commandNode.inputs.filter(
|
|
1139
|
-
(input) =>
|
|
1140
|
-
typeof input.source === 'function' &&
|
|
1141
|
-
(input.source()?.__typename === 'DataCalculationData' ||
|
|
1142
|
-
input.source()?.__typename === 'DataCommand'),
|
|
1143
|
-
)
|
|
1144
|
-
acc[getNodeId(commandNode, 'command node', nodeIndex)] = {
|
|
1145
|
-
title: commandNode.title,
|
|
1146
|
-
description: commandNode.description,
|
|
1147
|
-
hide_short_ref: commandNode.hideShortRef,
|
|
1148
|
-
type: `command-node-${type}`,
|
|
1149
|
-
properties: {
|
|
1150
|
-
command: commandNode.__commandName,
|
|
1151
|
-
args: args.reduce((argsAcc, input) => {
|
|
1152
|
-
argsAcc[input.key] = input.source
|
|
1153
|
-
return argsAcc
|
|
1154
|
-
}, {}),
|
|
1155
|
-
},
|
|
1156
|
-
in: generateInputPorts(inputs),
|
|
1157
|
-
out: generateOutputPorts(commandNode.outputs),
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
return acc
|
|
1161
|
-
}, {})
|
|
1162
|
-
calc.editor_info = mapCalc.editorInfo.reduce((acc, editorInfo) => {
|
|
1163
|
-
acc[getNodeId(editorInfo.node, 'editor info node')] = {
|
|
1164
|
-
position: editorInfo.position,
|
|
1165
|
-
points: editorInfo.points.reduce((pointsAcc, point) => {
|
|
1166
|
-
const sourceId = getNodeId(point.source, 'editor info point source')
|
|
1167
|
-
const targetId = getNodeId(point.target, 'editor info point target')
|
|
1168
|
-
const key = `${sourceId}-${point.sourceOutputKey}-${targetId}-${point.targetInputKey}`
|
|
1169
|
-
pointsAcc[key] = point.positions
|
|
1170
|
-
return pointsAcc
|
|
1479
|
+
} else if (dataCalc.__typename === 'DataCalculationScript') {
|
|
1480
|
+
const scriptCalc = dataCalc as DataCalculationScript
|
|
1481
|
+
calc.type = 'script'
|
|
1482
|
+
|
|
1483
|
+
const code = compileScriptCalculationCode(scriptCalc.code)
|
|
1484
|
+
calc.script_config = {
|
|
1485
|
+
title: scriptCalc.title ?? '',
|
|
1486
|
+
note: scriptCalc.note ?? '',
|
|
1487
|
+
code,
|
|
1488
|
+
enable_async: scriptCalc.enableAsync,
|
|
1489
|
+
trigger_mode: scriptCalc.triggerMode,
|
|
1490
|
+
inputs: scriptCalc.inputs.reduce((acc, input) => {
|
|
1491
|
+
const inputId = assertEntryId(
|
|
1492
|
+
input.data()?.id,
|
|
1493
|
+
'PROPERTY_BANK_DATA_NODE',
|
|
1494
|
+
`(data calc: ${dataCalcId}, script input: ${input.key}, subspace: ${subspaceId})`,
|
|
1495
|
+
)
|
|
1496
|
+
acc[inputId] = input.key
|
|
1497
|
+
return acc
|
|
1171
1498
|
}, {}),
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
} else if (dataCalc.__typename === 'DataCalculationScript') {
|
|
1176
|
-
const scriptCalc = dataCalc as DataCalculationScript
|
|
1177
|
-
calc.type = 'script'
|
|
1178
|
-
|
|
1179
|
-
const code = compileScriptCalculationCode(scriptCalc.code)
|
|
1180
|
-
calc.script_config = {
|
|
1181
|
-
title: scriptCalc.title ?? '',
|
|
1182
|
-
note: scriptCalc.note ?? '',
|
|
1183
|
-
code,
|
|
1184
|
-
enable_async: scriptCalc.enableAsync,
|
|
1185
|
-
trigger_mode: scriptCalc.triggerMode,
|
|
1186
|
-
inputs: scriptCalc.inputs.reduce((acc, input) => {
|
|
1187
|
-
const inputId = assertEntryId(
|
|
1188
|
-
input.data()?.id,
|
|
1189
|
-
'PROPERTY_BANK_DATA_NODE',
|
|
1190
|
-
`(data calc: ${dataCalcId}, script input: ${input.key}, subspace: ${subspaceId})`,
|
|
1191
|
-
)
|
|
1192
|
-
acc[inputId] = input.key
|
|
1193
|
-
return acc
|
|
1194
|
-
}, {}),
|
|
1195
|
-
disabled_triggers: scriptCalc.inputs.reduce((acc, input) => {
|
|
1196
|
-
const inputId = assertEntryId(
|
|
1197
|
-
input.data()?.id,
|
|
1198
|
-
'PROPERTY_BANK_DATA_NODE',
|
|
1199
|
-
`(data calc: ${dataCalcId}, script trigger input: ${input.key}, subspace: ${subspaceId})`,
|
|
1200
|
-
)
|
|
1201
|
-
acc[inputId] = !input.trigger
|
|
1202
|
-
return acc
|
|
1203
|
-
}, {}),
|
|
1204
|
-
output: scriptCalc.output
|
|
1205
|
-
? assertEntryId(
|
|
1206
|
-
scriptCalc.output()?.id,
|
|
1499
|
+
disabled_triggers: scriptCalc.inputs.reduce((acc, input) => {
|
|
1500
|
+
const inputId = assertEntryId(
|
|
1501
|
+
input.data()?.id,
|
|
1207
1502
|
'PROPERTY_BANK_DATA_NODE',
|
|
1208
|
-
`(data calc: ${dataCalcId}, script
|
|
1503
|
+
`(data calc: ${dataCalcId}, script trigger input: ${input.key}, subspace: ${subspaceId})`,
|
|
1209
1504
|
)
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1505
|
+
acc[inputId] = !input.trigger
|
|
1506
|
+
return acc
|
|
1507
|
+
}, {}),
|
|
1508
|
+
output: scriptCalc.output
|
|
1509
|
+
? assertEntryId(
|
|
1510
|
+
scriptCalc.output()?.id,
|
|
1511
|
+
'PROPERTY_BANK_DATA_NODE',
|
|
1512
|
+
`(data calc: ${dataCalcId}, script output, subspace: ${subspaceId})`,
|
|
1513
|
+
)
|
|
1514
|
+
: null,
|
|
1515
|
+
outputs: scriptCalc.outputs.reduce((acc, output) => {
|
|
1516
|
+
if (!acc[output.key]) acc[output.key] = []
|
|
1517
|
+
const outputId = assertEntryId(
|
|
1518
|
+
output.data()?.id,
|
|
1224
1519
|
'PROPERTY_BANK_DATA_NODE',
|
|
1225
|
-
`(data calc: ${dataCalcId}, script
|
|
1520
|
+
`(data calc: ${dataCalcId}, script outputs key: ${output.key}, subspace: ${subspaceId})`,
|
|
1226
1521
|
)
|
|
1227
|
-
|
|
1228
|
-
|
|
1522
|
+
acc[output.key].push(outputId)
|
|
1523
|
+
return acc
|
|
1524
|
+
}, {}),
|
|
1525
|
+
error: scriptCalc.error
|
|
1526
|
+
? assertEntryId(
|
|
1527
|
+
scriptCalc.error()?.id,
|
|
1528
|
+
'PROPERTY_BANK_DATA_NODE',
|
|
1529
|
+
`(data calc: ${dataCalcId}, script error output, subspace: ${subspaceId})`,
|
|
1530
|
+
)
|
|
1531
|
+
: null,
|
|
1532
|
+
}
|
|
1229
1533
|
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
}
|
|
1237
|
-
map[dataCalcId] = calc
|
|
1238
|
-
return map
|
|
1239
|
-
}, {}),
|
|
1534
|
+
Object.assign(calc, generateCalulationMap(calc.script_config, dataCalcId))
|
|
1535
|
+
}
|
|
1536
|
+
map[dataCalcId] = calc
|
|
1537
|
+
return map
|
|
1538
|
+
},
|
|
1539
|
+
),
|
|
1240
1540
|
action_map: subspace.actions || undefined,
|
|
1241
1541
|
event_map: compileEvents('', subspace.events || {}, {
|
|
1242
1542
|
camelCase: false,
|
|
1243
1543
|
errorReference: `(subspace ${subspaceId})`,
|
|
1244
1544
|
}),
|
|
1245
|
-
routing: subspace.dataRouting
|
|
1545
|
+
routing: collectReduce(errors, subspace.dataRouting, (acc, data, index) => {
|
|
1246
1546
|
const dataId = assertEntryId(
|
|
1247
1547
|
data?.id,
|
|
1248
1548
|
'PROPERTY_BANK_DATA_NODE',
|
|
@@ -1250,15 +1550,15 @@ export const compile = async (app: Application) => {
|
|
|
1250
1550
|
)
|
|
1251
1551
|
acc[dataId] = { enabled_routing: true }
|
|
1252
1552
|
return acc
|
|
1253
|
-
}
|
|
1553
|
+
}),
|
|
1254
1554
|
...compileModule(subspace),
|
|
1255
1555
|
}
|
|
1256
1556
|
return subspaceMap
|
|
1257
1557
|
}, {}),
|
|
1258
|
-
root_subspace_id:
|
|
1259
|
-
|
|
1260
|
-
'SUBSPACE',
|
|
1261
|
-
'
|
|
1558
|
+
root_subspace_id: collect(
|
|
1559
|
+
errors,
|
|
1560
|
+
() => assertEntryId(app.rootSubspace?.id, 'SUBSPACE', '(application root subspace)'),
|
|
1561
|
+
'',
|
|
1262
1562
|
),
|
|
1263
1563
|
fonts: app.fonts,
|
|
1264
1564
|
...compileApplicationSettings(app.settings),
|
|
@@ -1269,10 +1569,26 @@ export const compile = async (app: Application) => {
|
|
|
1269
1569
|
automation_map: compiledAutomationMap || app.metadata?.TEMP_automation_map || {},
|
|
1270
1570
|
update_timestamp: timestamp,
|
|
1271
1571
|
}
|
|
1572
|
+
if (errors.length > 0) {
|
|
1573
|
+
throw new Error(
|
|
1574
|
+
`Compile failed with ${errors.length} error(s):\n` +
|
|
1575
|
+
errors.map((message, index) => ` ${index + 1}. ${message}`).join('\n'),
|
|
1576
|
+
)
|
|
1577
|
+
}
|
|
1578
|
+
await recordConfigChange(previousConfig, config)
|
|
1272
1579
|
return config
|
|
1273
1580
|
}
|
|
1274
1581
|
|
|
1275
1582
|
export const checkConfig = async (configPath: string) => {
|
|
1276
|
-
|
|
1277
|
-
|
|
1583
|
+
// --validate-automation surfaces broken automation_map / test_map refs early,
|
|
1584
|
+
// which catches agent-authored automations that reference deleted bricks.
|
|
1585
|
+
await sh`bricks app check-config --validate-automation ${configPath}`
|
|
1586
|
+
// Doctor adds semantic lint checks after structural validation. Warnings are
|
|
1587
|
+
// surfaced in the compile log, but only errors fail by default. Older published
|
|
1588
|
+
// bricks-cli builds lack `app doctor` — skip rather than fail the compile.
|
|
1589
|
+
const doctor = await sh`bricks app doctor --validate-automation ${configPath}`.nothrow()
|
|
1590
|
+
if (doctor.exitCode !== 0) {
|
|
1591
|
+
if (/unknown command/i.test(doctor.stderr?.toString() ?? '')) return
|
|
1592
|
+
throw new Error(`bricks app doctor failed with exit ${doctor.exitCode}`)
|
|
1593
|
+
}
|
|
1278
1594
|
}
|