@fugood/bricks-ctor 2.25.0-beta.60 → 2.25.0-beta.62

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 (192) hide show
  1. package/package.json +4 -28
  2. package/tools/__tests__/legacy-forwarder.test.js +91 -0
  3. package/tools/_forward.ts +26 -0
  4. package/tools/deploy.ts +3 -175
  5. package/tools/mcp-server.ts +3 -35
  6. package/tools/postinstall.ts +3 -291
  7. package/tools/pull.ts +3 -198
  8. package/tools/push-config.ts +3 -113
  9. package/tools/simulator.ts +3 -149
  10. package/compile/__tests__/config-diff.test.js +0 -100
  11. package/compile/__tests__/index.test.js +0 -461
  12. package/compile/__tests__/util.test.js +0 -450
  13. package/compile/action-name-map.ts +0 -1079
  14. package/compile/config-diff.ts +0 -155
  15. package/compile/index.ts +0 -1594
  16. package/compile/util.ts +0 -482
  17. package/index.ts +0 -6
  18. package/skills/bricks-ctor/SKILL.md +0 -38
  19. package/skills/bricks-ctor/references/animation.md +0 -160
  20. package/skills/bricks-ctor/references/architecture-patterns.md +0 -88
  21. package/skills/bricks-ctor/references/automations.md +0 -232
  22. package/skills/bricks-ctor/references/buttress.md +0 -245
  23. package/skills/bricks-ctor/references/data-calculation.md +0 -252
  24. package/skills/bricks-ctor/references/local-sync.md +0 -129
  25. package/skills/bricks-ctor/references/media-flow.md +0 -165
  26. package/skills/bricks-ctor/references/remote-data-bank.md +0 -196
  27. package/skills/bricks-ctor/references/simulator.md +0 -132
  28. package/skills/bricks-ctor/references/source-editing-tools.md +0 -81
  29. package/skills/bricks-ctor/references/standby-transition.md +0 -124
  30. package/skills/bricks-ctor/references/verification-toolchain.md +0 -200
  31. package/skills/bricks-design/SKILL.md +0 -171
  32. package/skills/bricks-design/references/architecture-truths.md +0 -132
  33. package/skills/bricks-design/references/avoiding-complexity.md +0 -91
  34. package/skills/bricks-design/references/design-critique.md +0 -195
  35. package/skills/bricks-design/references/design-languages.md +0 -265
  36. package/skills/bricks-design/references/performance.md +0 -116
  37. package/skills/bricks-design/references/presentation-and-slideshow.md +0 -137
  38. package/skills/bricks-design/references/translating-inputs.md +0 -152
  39. package/skills/bricks-design/references/variations-and-tweaks.md +0 -124
  40. package/skills/bricks-design/references/when-the-brief-is-branded.md +0 -284
  41. package/skills/bricks-design/references/when-the-brief-is-vague.md +0 -85
  42. package/skills/bricks-design/references/workflow.md +0 -134
  43. package/skills/bricks-ux/SKILL.md +0 -114
  44. package/skills/bricks-ux/references/accessibility.md +0 -162
  45. package/skills/bricks-ux/references/flow-states.md +0 -175
  46. package/skills/bricks-ux/references/interaction-archetypes.md +0 -189
  47. package/skills/bricks-ux/references/monitoring-screens.md +0 -153
  48. package/skills/bricks-ux/references/pressable-composition.md +0 -126
  49. package/skills/bricks-ux/references/user-journey.md +0 -168
  50. package/skills/bricks-ux/references/ux-critique.md +0 -256
  51. package/skills/rive-marketplace/SKILL.md +0 -99
  52. package/tools/__tests__/_cli-error.test.ts +0 -35
  53. package/tools/__tests__/_mcp-config.test.ts +0 -67
  54. package/tools/__tests__/pull.test.ts +0 -108
  55. package/tools/_cli-error.ts +0 -17
  56. package/tools/_edits-log.ts +0 -41
  57. package/tools/_git-author.ts +0 -37
  58. package/tools/_last-pushed-commit.ts +0 -28
  59. package/tools/_mcp-config.ts +0 -42
  60. package/tools/_shell.ts +0 -180
  61. package/tools/icons/.gitattributes +0 -1
  62. package/tools/icons/fa6pro-glyphmap.json +0 -4686
  63. package/tools/icons/fa6pro-meta.json +0 -1
  64. package/tools/mcp-env.ts +0 -13
  65. package/tools/mcp-tools/__tests__/data-calc-editing.test.js +0 -516
  66. package/tools/mcp-tools/__tests__/entry-editing.test.js +0 -866
  67. package/tools/mcp-tools/__tests__/huggingface.test.ts +0 -49
  68. package/tools/mcp-tools/__tests__/icons.test.ts +0 -21
  69. package/tools/mcp-tools/__tests__/mcp-env.test.js +0 -19
  70. package/tools/mcp-tools/_editing-helpers.ts +0 -98
  71. package/tools/mcp-tools/_verify.ts +0 -50
  72. package/tools/mcp-tools/compile.ts +0 -104
  73. package/tools/mcp-tools/data-calc-editing.ts +0 -1311
  74. package/tools/mcp-tools/entry-editing.ts +0 -2297
  75. package/tools/mcp-tools/huggingface.ts +0 -772
  76. package/tools/mcp-tools/icons.ts +0 -97
  77. package/tools/mcp-tools/lottie.ts +0 -102
  78. package/tools/mcp-tools/media.ts +0 -113
  79. package/tools/simulator-main.mjs +0 -488
  80. package/tools/simulator-preload.cjs +0 -16
  81. package/types/animation.d.ts +0 -116
  82. package/types/automation.d.ts +0 -231
  83. package/types/brick-base.d.ts +0 -80
  84. package/types/bricks/Camera.d.ts +0 -246
  85. package/types/bricks/Chart.d.ts +0 -372
  86. package/types/bricks/GenerativeMedia.d.ts +0 -290
  87. package/types/bricks/Icon.d.ts +0 -98
  88. package/types/bricks/Image.d.ts +0 -126
  89. package/types/bricks/Items.d.ts +0 -480
  90. package/types/bricks/Lottie.d.ts +0 -168
  91. package/types/bricks/Maps.d.ts +0 -262
  92. package/types/bricks/QrCode.d.ts +0 -117
  93. package/types/bricks/Rect.d.ts +0 -150
  94. package/types/bricks/RichText.d.ts +0 -131
  95. package/types/bricks/Rive.d.ts +0 -220
  96. package/types/bricks/Scene3D.d.ts +0 -676
  97. package/types/bricks/Sketch.d.ts +0 -256
  98. package/types/bricks/Slideshow.d.ts +0 -201
  99. package/types/bricks/Svg.d.ts +0 -99
  100. package/types/bricks/Text.d.ts +0 -148
  101. package/types/bricks/TextInput.d.ts +0 -242
  102. package/types/bricks/Video.d.ts +0 -242
  103. package/types/bricks/VideoStreaming.d.ts +0 -112
  104. package/types/bricks/WebRtcStream.d.ts +0 -65
  105. package/types/bricks/WebView.d.ts +0 -168
  106. package/types/bricks/index.d.ts +0 -23
  107. package/types/canvas.d.ts +0 -82
  108. package/types/common.d.ts +0 -141
  109. package/types/data-calc-command/base.d.ts +0 -57
  110. package/types/data-calc-command/collection.d.ts +0 -418
  111. package/types/data-calc-command/color.d.ts +0 -432
  112. package/types/data-calc-command/constant.d.ts +0 -50
  113. package/types/data-calc-command/datetime.d.ts +0 -147
  114. package/types/data-calc-command/file.d.ts +0 -129
  115. package/types/data-calc-command/index.d.ts +0 -13
  116. package/types/data-calc-command/iteratee.d.ts +0 -23
  117. package/types/data-calc-command/logictype.d.ts +0 -190
  118. package/types/data-calc-command/math.d.ts +0 -275
  119. package/types/data-calc-command/object.d.ts +0 -119
  120. package/types/data-calc-command/sandbox.d.ts +0 -66
  121. package/types/data-calc-command/string.d.ts +0 -407
  122. package/types/data-calc-script.d.ts +0 -21
  123. package/types/data-calc.d.ts +0 -12
  124. package/types/data.d.ts +0 -97
  125. package/types/generators/AlarmClock.d.ts +0 -110
  126. package/types/generators/Assistant.d.ts +0 -640
  127. package/types/generators/BleCentral.d.ts +0 -247
  128. package/types/generators/BlePeripheral.d.ts +0 -208
  129. package/types/generators/CanvasMap.d.ts +0 -74
  130. package/types/generators/CastlesPay.d.ts +0 -87
  131. package/types/generators/DataBank.d.ts +0 -160
  132. package/types/generators/File.d.ts +0 -432
  133. package/types/generators/GraphQl.d.ts +0 -132
  134. package/types/generators/Http.d.ts +0 -222
  135. package/types/generators/HttpServer.d.ts +0 -230
  136. package/types/generators/Information.d.ts +0 -103
  137. package/types/generators/Intent.d.ts +0 -168
  138. package/types/generators/Iterator.d.ts +0 -108
  139. package/types/generators/Keyboard.d.ts +0 -105
  140. package/types/generators/LlmAnthropicCompat.d.ts +0 -212
  141. package/types/generators/LlmAppleBuiltin.d.ts +0 -159
  142. package/types/generators/LlmGgml.d.ts +0 -903
  143. package/types/generators/LlmMediaTekNeuroPilot.d.ts +0 -235
  144. package/types/generators/LlmMlx.d.ts +0 -228
  145. package/types/generators/LlmOnnx.d.ts +0 -213
  146. package/types/generators/LlmOpenAiCompat.d.ts +0 -312
  147. package/types/generators/LlmQualcommAiEngine.d.ts +0 -247
  148. package/types/generators/Mcp.d.ts +0 -637
  149. package/types/generators/McpServer.d.ts +0 -289
  150. package/types/generators/MediaFlow.d.ts +0 -170
  151. package/types/generators/MqttBroker.d.ts +0 -141
  152. package/types/generators/MqttClient.d.ts +0 -141
  153. package/types/generators/Question.d.ts +0 -408
  154. package/types/generators/RealtimeTranscription.d.ts +0 -287
  155. package/types/generators/RerankerGgml.d.ts +0 -195
  156. package/types/generators/SerialPort.d.ts +0 -151
  157. package/types/generators/SoundPlayer.d.ts +0 -94
  158. package/types/generators/SoundRecorder.d.ts +0 -139
  159. package/types/generators/SpeechToTextGgml.d.ts +0 -424
  160. package/types/generators/SpeechToTextOnnx.d.ts +0 -236
  161. package/types/generators/SpeechToTextPlatform.d.ts +0 -85
  162. package/types/generators/SqLite.d.ts +0 -159
  163. package/types/generators/Step.d.ts +0 -107
  164. package/types/generators/SttAppleBuiltin.d.ts +0 -153
  165. package/types/generators/Tcp.d.ts +0 -126
  166. package/types/generators/TcpServer.d.ts +0 -147
  167. package/types/generators/TextToSpeechAppleBuiltin.d.ts +0 -127
  168. package/types/generators/TextToSpeechGgml.d.ts +0 -221
  169. package/types/generators/TextToSpeechOnnx.d.ts +0 -178
  170. package/types/generators/TextToSpeechOpenAiLike.d.ts +0 -121
  171. package/types/generators/ThermalPrinter.d.ts +0 -193
  172. package/types/generators/Tick.d.ts +0 -83
  173. package/types/generators/Udp.d.ts +0 -120
  174. package/types/generators/VadGgml.d.ts +0 -260
  175. package/types/generators/VadOnnx.d.ts +0 -231
  176. package/types/generators/VadTraditional.d.ts +0 -138
  177. package/types/generators/VectorStore.d.ts +0 -257
  178. package/types/generators/Watchdog.d.ts +0 -107
  179. package/types/generators/WebCrawler.d.ts +0 -103
  180. package/types/generators/WebRtc.d.ts +0 -181
  181. package/types/generators/WebSocket.d.ts +0 -148
  182. package/types/generators/index.d.ts +0 -57
  183. package/types/index.d.ts +0 -13
  184. package/types/subspace.d.ts +0 -60
  185. package/types/switch.d.ts +0 -51
  186. package/types/system.d.ts +0 -707
  187. package/utils/__tests__/calc.test.js +0 -25
  188. package/utils/__tests__/id.test.js +0 -154
  189. package/utils/calc.ts +0 -130
  190. package/utils/data.ts +0 -495
  191. package/utils/event-props.ts +0 -912
  192. package/utils/id.ts +0 -133
package/compile/index.ts DELETED
@@ -1,1594 +0,0 @@
1
- /* eslint-disable no-underscore-dangle -- Uses __typename, __actionName, etc. for type system */
2
- import camelCase from 'lodash/camelCase'
3
- import upperFirst from 'lodash/upperFirst'
4
- import snakeCase from 'lodash/snakeCase'
5
- import omit from 'lodash/omit'
6
- import { parse as parseAST } from 'acorn'
7
- import type { BlockStatement, ExportNamedDeclaration, FunctionDeclaration } from 'acorn'
8
- import escodegen from 'escodegen'
9
- import { makeSeededId } from '../utils/id'
10
- import { generateCalulationMap } from './util'
11
- import { templateActionNameMap } from './action-name-map'
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'
17
- import type {
18
- Application,
19
- Data,
20
- DataAssetKind,
21
- Animation,
22
- AnimationDef,
23
- AnimationComposeDef,
24
- ActionWithDataParams,
25
- ActionWithParams,
26
- BrickItems,
27
- SwitchCondData,
28
- SwitchCondInnerStateCurrentCanvas,
29
- SwitchCondPropertyBankByItemKey,
30
- DataCalculationMap,
31
- DataCalculationScript,
32
- DataCalculationData,
33
- DataCommand,
34
- Brick,
35
- Canvas,
36
- Subspace,
37
- AutomationMap,
38
- AutomationTestMap,
39
- AutomationTest,
40
- TestCase,
41
- TestVariable,
42
- } from '../types'
43
-
44
- const uuidPattern = '[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}'
45
-
46
- const entryIdPatterns = {
47
- SUBSPACE: new RegExp(`^SUBSPACE_${uuidPattern}$`),
48
- CANVAS: new RegExp(`^CANVAS_${uuidPattern}$`),
49
- BRICK: new RegExp(`^BRICK_${uuidPattern}$`),
50
- GENERATOR: new RegExp(`^(GENERATOR|AUTO_GENERATOR)_${uuidPattern}$`),
51
- ANIMATION: new RegExp(`^ANIMATION_${uuidPattern}$`),
52
- PROPERTY_BANK_DATA_NODE: new RegExp(`^PROPERTY_BANK_DATA_NODE_${uuidPattern}$`),
53
- PROPERTY_BANK_COMMAND_NODE: new RegExp(`^PROPERTY_BANK_COMMAND_NODE_${uuidPattern}$`),
54
- PROPERTY_BANK_COMMAND_MAP: new RegExp(`^PROPERTY_BANK_COMMAND_MAP_${uuidPattern}$`),
55
- BRICK_STATE_GROUP: new RegExp(`^BRICK_STATE_GROUP_${uuidPattern}$`),
56
- TEST: new RegExp(`^TEST_${uuidPattern}$`),
57
- TEST_CASE: new RegExp(`^TEST_CASE_${uuidPattern}$`),
58
- TEST_VAR: new RegExp(`^TEST_VAR_${uuidPattern}$`),
59
- AUTOMATION_MAP: /^AUTOMATION_MAP_.*/,
60
- } as const
61
-
62
- type EntryIdPatternKey = keyof typeof entryIdPatterns
63
-
64
- const assertEntryId = (
65
- id: unknown,
66
- patternKey: EntryIdPatternKey,
67
- errorReference: string = '',
68
- ): string => {
69
- const pattern = entryIdPatterns[patternKey]
70
- if (typeof id !== 'string' || !pattern.test(id)) {
71
- throw new Error(
72
- `Invalid ${patternKey} id${errorReference ? ` ${errorReference}` : ''}: ${String(id)}`,
73
- )
74
- }
75
- return id
76
- }
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
-
103
- const compileProperty = (property, errorReference: string, result = {}) => {
104
- if (Array.isArray(property)) {
105
- return property.map((p) => compileProperty(p, errorReference))
106
- }
107
- if (property?.__typename === 'DataLink') {
108
- const data = property.data?.()
109
- if (!data?.id) throw new Error(`Invalid DataLink reference ${errorReference}`)
110
- return `PROPERTY_BANK#${data.id}`
111
- }
112
- if (typeof property === 'function') {
113
- const instance = property()
114
- if (!instance?.id) throw new Error(`Invalid ID reference ${errorReference}`)
115
- return instance?.id // defined type instance getter
116
- }
117
- if (property && typeof property === 'object') {
118
- return Object.entries(property).reduce((acc, [key, value]) => {
119
- acc[key] = compileProperty(value, errorReference)
120
- return acc
121
- }, result)
122
- }
123
- return property
124
- }
125
-
126
- const compileScriptCalculationCode = (code = '') => {
127
- try {
128
- const program = parseAST(code, { sourceType: 'module', ecmaVersion: 2020 })
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,
149
- },
150
- )
151
- } catch {
152
- return code || ''
153
- }
154
- }
155
-
156
- const getTemplateName = (key: string) =>
157
- upperFirst(camelCase(key.replace(/^(BRICK|GENERATOR)_/, '')))
158
-
159
- const compileEventActionValue = (templateKey, eventKey, value, errorReference) => {
160
- const tmplEventProperties = templateEventPropsMap[getTemplateName(templateKey)]
161
- const props = tmplEventProperties?.[eventKey]
162
- if (!props) return compileProperty(value, errorReference)
163
- if (typeof value === 'string' && value in props) {
164
- const expectedType = props[value]
165
- if (expectedType) return value
166
- }
167
- if (typeof value === 'string' && /^(BRICK|GENERATOR)_/.test(value)) {
168
- console.warn(
169
- `[Warning] Event property "${value}" is not compatible with event "${eventKey}" of ${templateKey} ${errorReference}`,
170
- )
171
- }
172
- return compileProperty(value, errorReference)
173
- }
174
-
175
- const convertOutletKey = (templateKey: string, key: string) =>
176
- `${templateKey}_${snakeCase(key).toUpperCase()}`
177
-
178
- const compileOutlets = (
179
- templateKey: string,
180
- outlets: { [key: string]: () => Data },
181
- errorReference: string,
182
- ) =>
183
- Object.entries(outlets).reduce((acc, [key, data]) => {
184
- const dataInstance = data?.()
185
- const dataId = assertEntryId(dataInstance?.id, 'PROPERTY_BANK_DATA_NODE', errorReference)
186
- acc[convertOutletKey(templateKey, key)] = dataId
187
- return acc
188
- }, {})
189
-
190
- const convertEventKey = (templateKey: string, key: string) =>
191
- `${templateKey ? `${templateKey}_` : ''}${snakeCase(key).toUpperCase()}`
192
-
193
- const basicAnimationEvents = ['show', 'standby', 'breatheStart']
194
-
195
- const compileAnimations = (
196
- templateKey: string,
197
- animations: { [key: string]: Animation | (() => Animation) },
198
- errorReference: string,
199
- ) =>
200
- Object.entries(animations).reduce((acc, [key, animation]) => {
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)
204
- acc[convertEventKey(basicAnimationEvents.includes(key) ? 'BRICK' : templateKey, key)] =
205
- `ANIMATION#${animationId}`
206
- return acc
207
- }, {})
208
-
209
- const compileActionParam = (templateKey: string, actionName: string, paramName: string) =>
210
- templateActionNameMap[templateKey]?.[actionName]?.[paramName] || paramName
211
-
212
- const compileEvents = (
213
- templateKey: string,
214
- eventMap: { [key: string]: Array<any> },
215
- options = { camelCase: false, errorReference: '' },
216
- ) => {
217
- const { camelCase, errorReference } = options
218
- return Object.entries(eventMap).reduce((acc, [key, events]) => {
219
- acc[convertEventKey(templateKey, key)] = events.map((event) => {
220
- const { handler, action } = event
221
-
222
- let handlerKey
223
- let handlerTemplateKey
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
- }
235
- } else if (typeof handler === 'function') {
236
- let instance = handler()
237
- if (instance?.id) {
238
- instance = instance as { id: string }
239
- handlerKey = instance?.id
240
- } else if (instance?.brickId) {
241
- instance = instance as { brickId: string; templateKey: string }
242
- handlerKey = instance.brickId
243
- }
244
- handlerTemplateKey = instance?.templateKey
245
- }
246
- if (!handlerKey) throw new Error(`Invalid handler: ${handler} ${errorReference}`)
247
-
248
- const parameterList: Array<object> = []
249
- if (Object.prototype.hasOwnProperty.call(action, 'params')) {
250
- const actionDef = action as ActionWithParams
251
- ;(actionDef.params || []).forEach(({ input, value, mapping }) => {
252
- const param = {
253
- [camelCase ? 'inputToReceiver' : 'input_to_receiver']: handlerTemplateKey
254
- ? compileActionParam(handlerTemplateKey, action.__actionName, input)
255
- : input,
256
- [camelCase ? 'resultFromSender' : 'result_from_sender']:
257
- mapping || compileEventActionValue(handlerTemplateKey, key, value, errorReference),
258
- }
259
- if (mapping) param[camelCase ? 'resultDataMapping' : 'result_data_mapping'] = true
260
- parameterList.push(param)
261
- })
262
- } else if (Object.prototype.hasOwnProperty.call(action, 'dataParams')) {
263
- const actionDef = action as ActionWithDataParams
264
- ;(actionDef.dataParams || []).forEach(({ input, value, mapping }) => {
265
- if (!input) return
266
- const param = {
267
- [camelCase ? 'inputToReceiver' : 'input_to_receiver']: input().id,
268
- [camelCase ? 'resultFromSender' : 'result_from_sender']:
269
- mapping || compileEventActionValue(handlerTemplateKey, key, value, errorReference),
270
- }
271
- if (mapping) param[camelCase ? 'resultDataMapping' : 'result_data_mapping'] = true
272
- parameterList.push(param)
273
- })
274
- }
275
- return {
276
- handler: handlerKey,
277
- action: action.__actionName,
278
- [camelCase ? 'parameterList' : 'parameter_list']: parameterList,
279
- [camelCase ? 'waitAsync' : 'wait_async']: event.waitAsync,
280
- }
281
- })
282
- return acc
283
- }, {})
284
- }
285
-
286
- const compileSwitchConds = (templateKey, conds, errorReference) =>
287
- (conds || []).map((item: any, index) => {
288
- const result: any = { method: item.method }
289
- if (!item.cond) return result
290
- if (item.cond.__typename === 'SwitchCondData') {
291
- const cond = item.cond as SwitchCondData
292
- result.type = 'property_bank'
293
- if (!cond.data().id)
294
- throw new Error(`Invalid data reference in conds index: ${index} ${errorReference}`)
295
- result.key = cond.data().id
296
- result.value = cond.value
297
- } else if (item.cond.__typename === 'SwitchCondPropertyBankByItemKey') {
298
- const cond = item.cond as SwitchCondPropertyBankByItemKey
299
- result.type = 'property_bank_by_item_key'
300
- if (!cond.data().id)
301
- throw new Error(`Invalid data reference in conds index: ${index} ${errorReference}`)
302
- result.key = cond.data().id
303
- result.value = cond.value
304
- } else if (item.cond.__typename === 'SwitchCondInnerStateOutlet') {
305
- const { cond } = item
306
- result.type = 'inner_state'
307
- result.key = convertOutletKey(templateKey, cond.outlet)
308
- result.value = cond.value
309
- } else if (item.cond.__typename === 'SwitchCondInnerStateCurrentCanvas') {
310
- const cond = item.cond as SwitchCondInnerStateCurrentCanvas
311
- result.type = 'inner_state'
312
- result.key = 'current_canvas'
313
- if (!cond.value().id)
314
- throw new Error(`Invalid canvas reference in conds index: ${index} ${errorReference}`)
315
- result.value = cond.value().id
316
- }
317
- return result
318
- })
319
-
320
- const compileApplicationSettings = (settings: Application['settings']) => ({
321
- internet_reachability_url: settings?.internetReachabilityUrl,
322
- enable_data_lock: settings?.enableDataLock,
323
- show_deprecated_features: settings?.showDeprecatedFeatures,
324
- enable_unstable_bricks: settings?.enableUnstableFeatures,
325
- runtime_cache_options: settings?.runtimeCacheOptions
326
- ? {
327
- disabled: settings.runtimeCacheOptions.disabled,
328
- behavior: settings.runtimeCacheOptions.behavior,
329
- retry_attempt_for_failure: settings.runtimeCacheOptions.retryAttemptForFailure,
330
- }
331
- : undefined,
332
- tv_options: settings?.tvOptions
333
- ? {
334
- disabled_selectable: settings.tvOptions.disabledSelectable,
335
- selectable_color: settings.tvOptions.selectableColor,
336
- selectable_border: settings.tvOptions.selectableBorder,
337
- }
338
- : undefined,
339
- ai: settings?.ai
340
- ? {
341
- use_anthropic_api_key_system_data: settings.ai.useAnthropicApiKeySystemData,
342
- use_openai_api_key_system_data: settings.ai.useOpenAiApiKeySystemData,
343
- use_gemini_api_key_system_data: settings.ai.useGeminiApiKeySystemData,
344
- }
345
- : undefined,
346
- hide_short_refs: settings?.hideShortRefs,
347
- })
348
-
349
- const animationTypeMap = {
350
- AnimationTimingConfig: 'timing',
351
- AnimationSpringConfig: 'spring',
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
509
- }
510
-
511
- const compileFrame = (frame: Canvas['items'][number]['frame']) => ({
512
- x: frame.x,
513
- y: frame.y,
514
- width: frame.width,
515
- height: frame.height,
516
- standby_mode: frame.standbyMode,
517
- standby_frame: frame.standbyFrame,
518
- standby_opacity: frame.standbyOpacity,
519
- standby_delay: frame.standbyDelay,
520
- standby_delay_random: frame.standbyDelayRandom,
521
- standby_easing: frame.standbyEasing,
522
- showing_delay: frame.showingDelay,
523
- render_out_of_viewport: frame.renderOutOfViewport,
524
- })
525
-
526
- const preloadTypes = [
527
- 'media-resource-image',
528
- 'media-resource-video',
529
- 'media-resource-audio',
530
- 'media-resource-file',
531
- 'lottie-file-uri',
532
- 'rive-file-uri',
533
- 'ggml-model-asset',
534
- 'gguf-model-asset',
535
- 'binary-asset',
536
- 'mlx-model-asset',
537
- 'scene3d-objects',
538
- ]
539
-
540
- const compileKind = (kind: Data['kind']) => {
541
- const { type, ...rest } = kind || {}
542
- if (!type) return {}
543
- if (preloadTypes.includes(type)) {
544
- const { preload, metadata } = kind as DataAssetKind
545
- const result: any = { kind: type, ...rest }
546
- if (preload) {
547
- result.preload = {
548
- type: preload.type,
549
- hashType: preload.hashType,
550
- }
551
- if (preload.hashType) result.preload[preload.hashType] = preload.hash
552
- }
553
- // Handle metadata
554
- if (type === 'gguf-model-asset') {
555
- result._hfRepoInfo = metadata?.hfRepoInfo
556
- }
557
- return result
558
- }
559
-
560
- return { kind: type, ...rest }
561
- }
562
-
563
- const compileRemoteUpdate = (remoteUpdate: Data['remoteUpdate']) => {
564
- if (!remoteUpdate) return { bank_type: 'none' }
565
- if (remoteUpdate.type === 'auto') return { bank_type: 'create', enable_remote_update: true }
566
- return {
567
- bank_type: remoteUpdate.type === 'device-specific' ? 'create-device-specific' : 'global',
568
- enable_remote_update: true,
569
- ...(remoteUpdate.type === 'device-specific' ? { use_remote_id_prefix: true } : {}),
570
- ...(remoteUpdate.type === 'global-data' ? { global_remote_update_prop: remoteUpdate.id } : {}),
571
- }
572
- }
573
-
574
- const compileModule = (subspace: Subspace) => {
575
- if (!subspace.module?.id) return {}
576
- return {
577
- module: {
578
- link: subspace.module.link ?? false,
579
- id: subspace.module.id,
580
- version: subspace.module.version,
581
- },
582
- selected_requirements: subspace.module.selectedRequirements?.reduce((acc, requirement) => {
583
- acc[requirement.subspace] = {
584
- id: requirement.id,
585
- version: requirement.version,
586
- }
587
- return acc
588
- }, {}),
589
- requirements: subspace.module.requirements?.reduce((acc, requirement) => {
590
- acc[requirement.subspace] = {
591
- id: requirement.id,
592
- version: requirement.version,
593
- }
594
- return acc
595
- }, {}),
596
- }
597
- }
598
-
599
- /**
600
- * Compile a run array element - if it's a getter function, call it and extract the id
601
- * Note: All entity ids already include their prefix (e.g., SUBSPACE_xxx, BRICK_xxx, PROPERTY_BANK_DATA_NODE_xxx)
602
- */
603
- function compileRunElement(element: unknown): unknown {
604
- if (typeof element !== 'function') return element
605
-
606
- const result = element()
607
- if (result && typeof result === 'object' && 'id' in result) {
608
- return (result as { id: string }).id
609
- }
610
- return result
611
- }
612
-
613
- /**
614
- * Compile a run array - resolve getter functions and extract ids with prefixes
615
- */
616
- function compileRunArray(run: unknown[]): unknown[] {
617
- return run.map(compileRunElement)
618
- }
619
-
620
- /**
621
- * Compile typed TestCase to raw format (strips __typename)
622
- */
623
- export const compileTestCase = (testCase: TestCase) => ({
624
- id: testCase.id,
625
- name: testCase.name,
626
- hide_short_ref: testCase.hideShortRef,
627
- run: compileRunArray(testCase.run),
628
- exit_on_failed: testCase.exit_on_failed,
629
- commented: testCase.commented,
630
- pre_delay: testCase.pre_delay,
631
- post_delay: testCase.post_delay,
632
- post_delay_rule: testCase.post_delay_rule,
633
- jump_cond: testCase.jump_cond.map((cond) => {
634
- if (cond.jump_to == null) {
635
- console.warn(
636
- `[Warning] jump_cond is missing jump_to in test case "${testCase.name}" (${testCase.id})`,
637
- )
638
- }
639
- return {
640
- type: cond.type,
641
- status: cond.status,
642
- variable: cond.variable,
643
- operator: cond.operator,
644
- value: cond.value,
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),
649
- }
650
- }),
651
- })
652
-
653
- /**
654
- * Compile typed TestVariable to raw format (strips __typename)
655
- */
656
- const compileTestVariable = (variable: TestVariable) => ({
657
- id: variable.id,
658
- name: variable.name,
659
- type: variable.type,
660
- value: variable.value,
661
- })
662
-
663
- /**
664
- * Convert an array of items with id property to an id-keyed record
665
- */
666
- const arrayToIdMap = <T extends { id: string }, R>(
667
- items: T[],
668
- transform: (item: T, index: number) => R,
669
- options: {
670
- patternKey: EntryIdPatternKey
671
- getErrorReference: (item: T, index: number) => string
672
- },
673
- ): Record<string, R> =>
674
- Object.fromEntries(
675
- items.map((item, index) => {
676
- const itemId = assertEntryId(
677
- item.id,
678
- options.patternKey,
679
- options.getErrorReference(item, index),
680
- )
681
- return [itemId, transform(item, index)]
682
- }),
683
- )
684
-
685
- /**
686
- * Compile typed AutomationTest to raw format
687
- */
688
- const compileAutomationTest = (
689
- test: AutomationTest,
690
- options: { mapId: string; testIndex: number },
691
- ) => {
692
- const testId = assertEntryId(
693
- test.id,
694
- 'TEST',
695
- `(automation map: ${options.mapId}, test index: ${options.testIndex})`,
696
- )
697
-
698
- return {
699
- id: testId,
700
- alias: test.alias,
701
- title: test.title,
702
- hide_short_ref: test.hideShortRef,
703
- timeout: test.timeout,
704
- trigger_type: test.trigger_type,
705
- cron: test.cron,
706
- skip_trigger_type_check: test.skip_trigger_type_check,
707
- local_sync: test.local_sync,
708
- meta: test.meta,
709
- case_map: arrayToIdMap(test.cases, compileTestCase, {
710
- patternKey: 'TEST_CASE',
711
- getErrorReference: (_, index) =>
712
- `(automation map: ${options.mapId}, test: ${testId}, case index: ${index})`,
713
- }),
714
- var_map: arrayToIdMap(test.variables, compileTestVariable, {
715
- patternKey: 'TEST_VAR',
716
- getErrorReference: (_, index) =>
717
- `(automation map: ${options.mapId}, test: ${testId}, variable index: ${index})`,
718
- }),
719
- }
720
- }
721
-
722
- /**
723
- * Compile typed AutomationTestMap to raw format
724
- */
725
- const compileAutomationTestMap = (testMap: AutomationTestMap, mapId: string) => {
726
- assertEntryId(testMap.id, 'AUTOMATION_MAP', `(automation map id field: ${mapId})`)
727
-
728
- return {
729
- title: testMap.title,
730
- hide_short_ref: testMap.hideShortRef,
731
- createdAt: testMap.createdAt,
732
- map: arrayToIdMap(
733
- testMap.tests,
734
- (test, index) => compileAutomationTest(test, { mapId, testIndex: index }),
735
- {
736
- patternKey: 'TEST',
737
- getErrorReference: (_, index) => `(automation map: ${mapId}, test index: ${index})`,
738
- },
739
- ),
740
- }
741
- }
742
-
743
- /**
744
- * Compile typed AutomationMap to raw automation_map format
745
- */
746
- const compileAutomation = (automationMap: AutomationMap) =>
747
- Object.fromEntries(
748
- Object.entries(automationMap).map(([mapId, testMap]) => {
749
- const automationMapId = assertEntryId(mapId, 'AUTOMATION_MAP', '(automation map key)')
750
- return [automationMapId, compileAutomationTestMap(testMap, automationMapId)]
751
- }),
752
- )
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
-
792
- export const compile = async (app: Application) => {
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())
800
- const timestamp = Date.now()
801
- // Pre-index subspace ids so the canvas-item validation below stays O(1).
802
- const subspaceIdSet = new Set(app.subspaces.map((s) => s.id))
803
- let compiledAutomationMap: ReturnType<typeof compileAutomation> | null = null
804
- if (app.automationMap) {
805
- const { automationMap } = app
806
- compiledAutomationMap = collect(errors, () => compileAutomation(automationMap), null)
807
- }
808
- const config = {
809
- title: `${app.name || 'Unknown'}(${timestamp})`,
810
- subspace_map: app.subspaces.reduce((subspaceMap, subspace, subspaceIndex) => {
811
- const subspaceId = assertEntryId(
812
- subspace.id,
813
- 'SUBSPACE',
814
- `(subspace index: ${subspaceIndex})`,
815
- )
816
-
817
- // Linked module subspaces reference external content — only include
818
- // module metadata, not local canvas/brick/data compilation.
819
- // Include a placeholder root canvas so the config passes schema
820
- // validation (root_canvas_id is required before the conditional
821
- // schema fix is published).
822
- if (subspace.module?.link) {
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`)
831
- subspaceMap[subspaceId] = {
832
- title: subspace.title,
833
- description: subspace.description,
834
- unused: subspace.unused,
835
- portal: subspace.portal,
836
- layout: {
837
- width: subspace.layout?.width,
838
- height: subspace.layout?.height,
839
- resize_mode: subspace.layout?.resizeMode,
840
- },
841
- root_canvas_id: placeholderCanvasId,
842
- property_bank_map: {},
843
- brick_map: {},
844
- generator_map: {},
845
- animation_map: {},
846
- canvas_map: {
847
- [placeholderCanvasId]: { item_list: [] },
848
- },
849
- ...compileModule(subspace),
850
- }
851
- return subspaceMap
852
- }
853
-
854
- const rootCanvasId = assertEntryId(
855
- subspace.rootCanvas?.id,
856
- 'CANVAS',
857
- `(subspace: ${subspaceId}, root canvas)`,
858
- )
859
-
860
- subspaceMap[subspaceId] = {
861
- title: subspace.title,
862
- description: subspace.description,
863
- hide_short_ref: subspace.hideShortRef,
864
- unused: subspace.unused,
865
- portal: subspace.portal,
866
- _expanded: subspace.unexpanded
867
- ? {
868
- brick: !subspace.unexpanded.brick,
869
- generator: !subspace.unexpanded.generator,
870
- canvas: subspace.unexpanded.canvas?.reduce((acc, canvas, canvasIndex) => {
871
- const unexpandedCanvasId = assertEntryId(
872
- canvas?.id,
873
- 'CANVAS',
874
- `(subspace: ${subspaceId}, unexpanded canvas index: ${canvasIndex})`,
875
- )
876
- acc[unexpandedCanvasId] = !canvas?.id
877
- return acc
878
- }, {}),
879
- property_bank: !subspace.unexpanded.data,
880
- property_bank_calc: !subspace.unexpanded.dataCalculation,
881
- }
882
- : buildDefaultExpandedState(subspace),
883
- layout: {
884
- width: subspace.layout?.width,
885
- height: subspace.layout?.height,
886
- resize_mode: subspace.layout?.resizeMode,
887
- },
888
- local_sync: subspace.localSyncChangeCanvas
889
- ? {
890
- change_canvas: subspace.localSyncChangeCanvas,
891
- }
892
- : undefined,
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
- )
902
-
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})`,
950
- )
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
- )
960
- }
961
- return map
962
- },
963
- ),
964
- brick_map: collectReduce(errors, subspace.bricks, (map, brick, brickIndex) => {
965
- const brickId = assertEntryId(
966
- brick.id,
967
- 'BRICK',
968
- `(brick index: ${brickIndex}, subspace: ${subspaceId})`,
969
- )
970
- const property = compileProperty(
971
- brick.property || {},
972
- `(brick: ${brickId}, subspace ${subspaceId})`,
973
- )
974
- if (brick.templateKey === 'BRICK_ITEMS') {
975
- const brickItems = brick as BrickItems
976
- const buildList = (itemBrick, index, key) => ({
977
- title: itemBrick.title,
978
- brickId: itemBrick.brickId,
979
- brickIdPrefix: itemBrick.brickIdPrefix,
980
- templateKey: itemBrick.templateKey,
981
- frame: itemBrick.frame,
982
- property: compileProperty(
983
- itemBrick.property,
984
- `(brick: ${brickId}, ${key}: ${index}, subspace ${subspaceId})`,
985
- ),
986
- propertyMapping: itemBrick.propertyMapping,
987
- animation: compileAnimations(
988
- itemBrick.templateKey,
989
- itemBrick.animation || {},
990
- `(brick: ${brickId}, ${key}: ${index}, subspace ${subspaceId})`,
991
- ),
992
- outlet: compileOutlets(
993
- itemBrick.templateKey,
994
- itemBrick.outlets || {},
995
- `(brick: ${brickId}, ${key}: ${index}, subspace ${subspaceId})`,
996
- ),
997
- eventMap: compileEvents(itemBrick.templateKey, itemBrick.eventMap || {}, {
998
- camelCase: true,
999
- errorReference: `(brick: ${brickId}, ${key}: ${index}, subspace ${subspaceId})`,
1000
- }),
1001
- stateGroup: itemBrick.stateGroup.reduce((acc, stateGroup, stateGroupIndex) => {
1002
- const stateGroupId = assertEntryId(
1003
- stateGroup.id,
1004
- 'BRICK_STATE_GROUP',
1005
- `(brick: ${brickId}, ${key}: ${index}, switch index: ${stateGroupIndex}, subspace ${subspaceId})`,
1006
- )
1007
-
1008
- acc[stateGroupId] = {
1009
- title: stateGroup.title,
1010
- description: stateGroup.description,
1011
- override: stateGroup.override,
1012
- break: stateGroup.break,
1013
- commented: stateGroup.disabled,
1014
- conds: compileSwitchConds(
1015
- itemBrick.templateKey,
1016
- stateGroup.conds || [],
1017
- `(brick: ${brickId}, ${key}: ${index}, switch: ${stateGroupId}, subspace ${subspaceId})`,
1018
- ),
1019
- property: compileProperty(
1020
- stateGroup.property,
1021
- `(brick: ${brickId}, ${key}: ${index}, switch: ${stateGroupId}, subspace ${subspaceId})`,
1022
- ),
1023
- animation: compileAnimations(
1024
- itemBrick.templateKey,
1025
- stateGroup.animation || {},
1026
- `(brick: ${brickId}, ${key}: ${index}, switch: ${stateGroupId}, subspace ${subspaceId})`,
1027
- ),
1028
- outlet: compileOutlets(
1029
- itemBrick.templateKey,
1030
- stateGroup.outlets || {},
1031
- `(brick: ${brickId}, ${key}: ${index}, switch: ${stateGroupId}, subspace ${subspaceId})`,
1032
- ),
1033
- eventMap: compileEvents(itemBrick.templateKey, stateGroup.eventMap || {}, {
1034
- camelCase: true,
1035
- errorReference: `(brick: ${brickId}, ${key}: ${index}, switch: ${stateGroupId}, subspace ${subspaceId})`,
1036
- }),
1037
- }
1038
- return acc
1039
- }, {}),
1040
- show: itemBrick.show,
1041
- pressToOpenDetail: itemBrick.pressToOpenDetail,
1042
- pressToBackList: itemBrick.pressToBackList,
1043
- })
1044
- if (Array.isArray(brickItems.brickList)) {
1045
- const brickList = (brickItems.brickList || []).map((item, index) =>
1046
- buildList(item, index, 'brickList'),
1047
- )
1048
- property.brickList = brickList
1049
- } else if (!brickItems.brickList) {
1050
- property.brickList = []
1051
- } else {
1052
- // Not supported Data for brickList
1053
- throw new TypeError('Not supported Data for brickList directly')
1054
- }
1055
- if (Array.isArray(brickItems.brickDetails)) {
1056
- const brickDetails = (brickItems.brickDetails || []).map((item, index) =>
1057
- buildList(item, index, 'brickDetails'),
1058
- )
1059
- property.brickDetails = brickDetails
1060
- } else if (!brickItems.brickDetails) {
1061
- property.brickDetails = []
1062
- } else {
1063
- // Not supported Data for brickList
1064
- throw new TypeError('Not supported Data for brickList directly')
1065
- }
1066
- }
1067
- map[brickId] = {
1068
- template_key: brick.templateKey,
1069
- alias: brick.alias,
1070
- title: brick.title,
1071
- description: brick.description,
1072
- hide_short_ref: brick.hideShortRef,
1073
- property,
1074
- animation: compileAnimations(
1075
- brick.templateKey,
1076
- brick.animation || {},
1077
- `(brick: ${brickId}, subspace ${subspaceId})`,
1078
- ),
1079
- event_map: compileEvents(brick.templateKey, brick.events || {}, {
1080
- camelCase: false,
1081
- errorReference: `(brick: ${brickId}, subspace ${subspaceId})`,
1082
- }),
1083
- outlet: compileOutlets(
1084
- brick.templateKey,
1085
- brick.outlets || {},
1086
- `(brick: ${brickId}, subspace ${subspaceId})`,
1087
- ),
1088
- state_group: brick.switches?.reduce((acc, switchCase, switchIndex) => {
1089
- const switchId = assertEntryId(
1090
- switchCase.id,
1091
- 'BRICK_STATE_GROUP',
1092
- `(brick: ${brickId}, switch index: ${switchIndex}, subspace ${subspaceId})`,
1093
- )
1094
-
1095
- acc[switchId] = {
1096
- title: switchCase.title,
1097
- description: switchCase.description,
1098
- break: switchCase.break,
1099
- override: switchCase.override,
1100
- commented: switchCase.disabled,
1101
- conds: compileSwitchConds(
1102
- brick.templateKey,
1103
- switchCase.conds || [],
1104
- `(brick: ${brickId}, switch: ${switchId}, subspace ${subspaceId})`,
1105
- ),
1106
- property: compileProperty(
1107
- switchCase.property,
1108
- `(brick: ${brickId}, switch: ${switchId}, subspace ${subspaceId})`,
1109
- ),
1110
- outlet: compileOutlets(
1111
- brick.templateKey,
1112
- switchCase.outlets || {},
1113
- `(brick: ${brickId}, switch: ${switchId}, subspace ${subspaceId})`,
1114
- ),
1115
- event_map: compileEvents(brick.templateKey, switchCase.events || {}, {
1116
- camelCase: false,
1117
- errorReference: `(brick: ${brickId}, switch: ${switchId}, subspace ${subspaceId})`,
1118
- }),
1119
- animation: compileAnimations(
1120
- brick.templateKey,
1121
- switchCase.animation || {},
1122
- `(brick: ${brickId}, switch: ${switchId}, subspace ${subspaceId})`,
1123
- ),
1124
- }
1125
- return acc
1126
- }, {}),
1127
- }
1128
- return map
1129
- }),
1130
- root_canvas_id: rootCanvasId,
1131
- canvas_map: collectReduce(errors, subspace.canvases, (map, canvas, canvasIndex) => {
1132
- const canvasId = assertEntryId(
1133
- canvas.id,
1134
- 'CANVAS',
1135
- `(canvas index: ${canvasIndex}, subspace: ${subspaceId})`,
1136
- )
1137
-
1138
- map[canvasId] = {
1139
- alias: canvas.alias,
1140
- title: canvas.title,
1141
- description: canvas.description,
1142
- hide_short_ref: canvas.hideShortRef,
1143
- property: compileProperty(
1144
- canvas.property,
1145
- `(canvas: ${canvasId}, subspace ${subspaceId})`,
1146
- ),
1147
- event_map: compileEvents('CANVAS', canvas.events || {}, {
1148
- camelCase: false,
1149
- errorReference: `(canvas: ${canvasId}, subspace ${subspaceId})`,
1150
- }),
1151
- state_group: canvas.switches?.reduce((acc, switchCase, switchIndex) => {
1152
- const switchId = assertEntryId(
1153
- switchCase.id,
1154
- 'BRICK_STATE_GROUP',
1155
- `(canvas: ${canvasId}, switch index: ${switchIndex}, subspace ${subspaceId})`,
1156
- )
1157
-
1158
- acc[switchId] = {
1159
- title: switchCase.title,
1160
- description: switchCase.description,
1161
- break: switchCase.break,
1162
- override: switchCase.override,
1163
- commented: switchCase.disabled,
1164
- conds: compileSwitchConds(
1165
- 'CANVAS',
1166
- switchCase.conds || [],
1167
- `(canvas: ${canvasId}, switch: ${switchId}, subspace ${subspaceId})`,
1168
- ),
1169
- property: compileProperty(
1170
- switchCase.property,
1171
- `(canvas: ${canvasId}, switch: ${switchId}, subspace ${subspaceId})`,
1172
- ),
1173
- event_map: compileEvents('CANVAS', switchCase.events || {}, {
1174
- camelCase: false,
1175
- errorReference: `(canvas: ${canvasId}, switch: ${switchId}, subspace ${subspaceId})`,
1176
- }),
1177
- animation: compileAnimations(
1178
- 'CANVAS',
1179
- switchCase.animation || {},
1180
- `(canvas: ${canvasId}, switch: ${switchId}, subspace ${subspaceId})`,
1181
- ),
1182
- }
1183
- return acc
1184
- }, {}),
1185
- item_list: canvas.items.map((item, index) => {
1186
- let itemPayload = {}
1187
- if (typeof item.item === 'function') {
1188
- const brick = (item.item as () => Brick)()
1189
- if (!brick?.id)
1190
- throw new Error(
1191
- `Invalid canvas item index: ${index}, brick not found (canvas: ${canvasId}, subspace ${subspaceId})`,
1192
- )
1193
- itemPayload = { brick_id: brick.id }
1194
- } else {
1195
- const targetSubspaceId = item.item
1196
- if (!subspaceIdSet.has(targetSubspaceId))
1197
- throw new Error(
1198
- `Invalid canvas item index: ${index}, subspace not found (canvas: ${canvasId}, subspace ${subspaceId})`,
1199
- )
1200
- itemPayload = { subspace_id: targetSubspaceId }
1201
- }
1202
- return {
1203
- type: typeof item.item === 'function' ? 'brick' : 'subspace',
1204
- ...itemPayload,
1205
- frame: compileFrame(item.frame),
1206
- hidden: item.hidden,
1207
- }
1208
- }),
1209
- }
1210
- return map
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
- )
1221
-
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
- )
1252
-
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) => {
1290
- const dataId = assertEntryId(
1291
- data.id,
1292
- 'PROPERTY_BANK_DATA_NODE',
1293
- `(data index: ${dataIndex}, subspace: ${subspaceId})`,
1294
- )
1295
-
1296
- map[dataId] = {
1297
- alias: data.alias,
1298
- title: data.title,
1299
- description: data.description,
1300
- hide_short_ref: data.hideShortRef,
1301
- linked: data.metadata?.linked,
1302
- linkedFrom: data.metadata?.linkedFrom,
1303
- local_sync: data.localSyncUpdateMode
1304
- ? {
1305
- update_mode: data.localSyncUpdateMode,
1306
- }
1307
- : undefined,
1308
- persist_data: data.persistData,
1309
- ...compileRemoteUpdate(data.remoteUpdate),
1310
- routing: data.routing,
1311
- schema: data.schema,
1312
- type: data.type === 'boolean' ? 'bool' : data.type,
1313
- ...compileKind(data.kind),
1314
- value: compileProperty(data.value, `(data: ${dataId}, subspace ${subspaceId})`),
1315
- event_map: compileEvents('PROPERTY_BANK', data.events || {}, {
1316
- camelCase: false,
1317
- errorReference: `(data: ${dataId}, subspace ${subspaceId})`,
1318
- }),
1319
- hit_equal: data.hit_equal,
1320
- hit_regex: data.hit_regex,
1321
- }
1322
- return map
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
- )
1333
-
1334
- const calc: any = {
1335
- alias: dataCalc.alias,
1336
- title: dataCalc.title,
1337
- description: dataCalc.description,
1338
- hide_short_ref: dataCalc.hideShortRef,
1339
- }
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
- )
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
- }
1371
-
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
- }
1464
- return acc
1465
- }, {})
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
- }, {}),
1476
- }
1477
- return acc
1478
- }, {})
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
1498
- }, {}),
1499
- disabled_triggers: scriptCalc.inputs.reduce((acc, input) => {
1500
- const inputId = assertEntryId(
1501
- input.data()?.id,
1502
- 'PROPERTY_BANK_DATA_NODE',
1503
- `(data calc: ${dataCalcId}, script trigger input: ${input.key}, subspace: ${subspaceId})`,
1504
- )
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,
1519
- 'PROPERTY_BANK_DATA_NODE',
1520
- `(data calc: ${dataCalcId}, script outputs key: ${output.key}, subspace: ${subspaceId})`,
1521
- )
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
- }
1533
-
1534
- Object.assign(calc, generateCalulationMap(calc.script_config, dataCalcId))
1535
- }
1536
- map[dataCalcId] = calc
1537
- return map
1538
- },
1539
- ),
1540
- action_map: subspace.actions || undefined,
1541
- event_map: compileEvents('', subspace.events || {}, {
1542
- camelCase: false,
1543
- errorReference: `(subspace ${subspaceId})`,
1544
- }),
1545
- routing: collectReduce(errors, subspace.dataRouting, (acc, data, index) => {
1546
- const dataId = assertEntryId(
1547
- data?.id,
1548
- 'PROPERTY_BANK_DATA_NODE',
1549
- `(data routing index: ${index}, subspace ${subspaceId})`,
1550
- )
1551
- acc[dataId] = { enabled_routing: true }
1552
- return acc
1553
- }),
1554
- ...compileModule(subspace),
1555
- }
1556
- return subspaceMap
1557
- }, {}),
1558
- root_subspace_id: collect(
1559
- errors,
1560
- () => assertEntryId(app.rootSubspace?.id, 'SUBSPACE', '(application root subspace)'),
1561
- '',
1562
- ),
1563
- fonts: app.fonts,
1564
- ...compileApplicationSettings(app.settings),
1565
- // Use typed automationMap if available, otherwise fall back to TEMP metadata
1566
- test_map: compiledAutomationMap
1567
- ? compiledAutomationMap['AUTOMATION_MAP_DEFAULT']?.map || {}
1568
- : app.metadata?.TEMP_test_map || {},
1569
- automation_map: compiledAutomationMap || app.metadata?.TEMP_automation_map || {},
1570
- update_timestamp: timestamp,
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)
1579
- return config
1580
- }
1581
-
1582
- export const checkConfig = async (configPath: string) => {
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
- }
1594
- }