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

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 (190) hide show
  1. package/package.json +4 -28
  2. package/tools/deploy.ts +19 -176
  3. package/tools/mcp-server.ts +16 -33
  4. package/tools/postinstall.ts +21 -292
  5. package/tools/pull.ts +15 -195
  6. package/tools/push-config.ts +18 -113
  7. package/tools/simulator.ts +19 -148
  8. package/compile/__tests__/config-diff.test.js +0 -100
  9. package/compile/__tests__/index.test.js +0 -461
  10. package/compile/__tests__/util.test.js +0 -450
  11. package/compile/action-name-map.ts +0 -1079
  12. package/compile/config-diff.ts +0 -155
  13. package/compile/index.ts +0 -1594
  14. package/compile/util.ts +0 -482
  15. package/index.ts +0 -6
  16. package/skills/bricks-ctor/SKILL.md +0 -38
  17. package/skills/bricks-ctor/references/animation.md +0 -160
  18. package/skills/bricks-ctor/references/architecture-patterns.md +0 -88
  19. package/skills/bricks-ctor/references/automations.md +0 -232
  20. package/skills/bricks-ctor/references/buttress.md +0 -245
  21. package/skills/bricks-ctor/references/data-calculation.md +0 -252
  22. package/skills/bricks-ctor/references/local-sync.md +0 -129
  23. package/skills/bricks-ctor/references/media-flow.md +0 -165
  24. package/skills/bricks-ctor/references/remote-data-bank.md +0 -196
  25. package/skills/bricks-ctor/references/simulator.md +0 -132
  26. package/skills/bricks-ctor/references/source-editing-tools.md +0 -81
  27. package/skills/bricks-ctor/references/standby-transition.md +0 -124
  28. package/skills/bricks-ctor/references/verification-toolchain.md +0 -200
  29. package/skills/bricks-design/SKILL.md +0 -171
  30. package/skills/bricks-design/references/architecture-truths.md +0 -132
  31. package/skills/bricks-design/references/avoiding-complexity.md +0 -91
  32. package/skills/bricks-design/references/design-critique.md +0 -195
  33. package/skills/bricks-design/references/design-languages.md +0 -265
  34. package/skills/bricks-design/references/performance.md +0 -116
  35. package/skills/bricks-design/references/presentation-and-slideshow.md +0 -137
  36. package/skills/bricks-design/references/translating-inputs.md +0 -152
  37. package/skills/bricks-design/references/variations-and-tweaks.md +0 -124
  38. package/skills/bricks-design/references/when-the-brief-is-branded.md +0 -284
  39. package/skills/bricks-design/references/when-the-brief-is-vague.md +0 -85
  40. package/skills/bricks-design/references/workflow.md +0 -134
  41. package/skills/bricks-ux/SKILL.md +0 -114
  42. package/skills/bricks-ux/references/accessibility.md +0 -162
  43. package/skills/bricks-ux/references/flow-states.md +0 -175
  44. package/skills/bricks-ux/references/interaction-archetypes.md +0 -189
  45. package/skills/bricks-ux/references/monitoring-screens.md +0 -153
  46. package/skills/bricks-ux/references/pressable-composition.md +0 -126
  47. package/skills/bricks-ux/references/user-journey.md +0 -168
  48. package/skills/bricks-ux/references/ux-critique.md +0 -256
  49. package/skills/rive-marketplace/SKILL.md +0 -99
  50. package/tools/__tests__/_cli-error.test.ts +0 -35
  51. package/tools/__tests__/_mcp-config.test.ts +0 -67
  52. package/tools/__tests__/pull.test.ts +0 -108
  53. package/tools/_cli-error.ts +0 -17
  54. package/tools/_edits-log.ts +0 -41
  55. package/tools/_git-author.ts +0 -37
  56. package/tools/_last-pushed-commit.ts +0 -28
  57. package/tools/_mcp-config.ts +0 -42
  58. package/tools/_shell.ts +0 -180
  59. package/tools/icons/.gitattributes +0 -1
  60. package/tools/icons/fa6pro-glyphmap.json +0 -4686
  61. package/tools/icons/fa6pro-meta.json +0 -1
  62. package/tools/mcp-env.ts +0 -13
  63. package/tools/mcp-tools/__tests__/data-calc-editing.test.js +0 -516
  64. package/tools/mcp-tools/__tests__/entry-editing.test.js +0 -866
  65. package/tools/mcp-tools/__tests__/huggingface.test.ts +0 -49
  66. package/tools/mcp-tools/__tests__/icons.test.ts +0 -21
  67. package/tools/mcp-tools/__tests__/mcp-env.test.js +0 -19
  68. package/tools/mcp-tools/_editing-helpers.ts +0 -98
  69. package/tools/mcp-tools/_verify.ts +0 -50
  70. package/tools/mcp-tools/compile.ts +0 -104
  71. package/tools/mcp-tools/data-calc-editing.ts +0 -1311
  72. package/tools/mcp-tools/entry-editing.ts +0 -2297
  73. package/tools/mcp-tools/huggingface.ts +0 -772
  74. package/tools/mcp-tools/icons.ts +0 -97
  75. package/tools/mcp-tools/lottie.ts +0 -102
  76. package/tools/mcp-tools/media.ts +0 -113
  77. package/tools/simulator-main.mjs +0 -488
  78. package/tools/simulator-preload.cjs +0 -16
  79. package/types/animation.d.ts +0 -116
  80. package/types/automation.d.ts +0 -231
  81. package/types/brick-base.d.ts +0 -80
  82. package/types/bricks/Camera.d.ts +0 -246
  83. package/types/bricks/Chart.d.ts +0 -372
  84. package/types/bricks/GenerativeMedia.d.ts +0 -290
  85. package/types/bricks/Icon.d.ts +0 -98
  86. package/types/bricks/Image.d.ts +0 -126
  87. package/types/bricks/Items.d.ts +0 -480
  88. package/types/bricks/Lottie.d.ts +0 -168
  89. package/types/bricks/Maps.d.ts +0 -262
  90. package/types/bricks/QrCode.d.ts +0 -117
  91. package/types/bricks/Rect.d.ts +0 -150
  92. package/types/bricks/RichText.d.ts +0 -131
  93. package/types/bricks/Rive.d.ts +0 -220
  94. package/types/bricks/Scene3D.d.ts +0 -676
  95. package/types/bricks/Sketch.d.ts +0 -256
  96. package/types/bricks/Slideshow.d.ts +0 -201
  97. package/types/bricks/Svg.d.ts +0 -99
  98. package/types/bricks/Text.d.ts +0 -148
  99. package/types/bricks/TextInput.d.ts +0 -242
  100. package/types/bricks/Video.d.ts +0 -242
  101. package/types/bricks/VideoStreaming.d.ts +0 -112
  102. package/types/bricks/WebRtcStream.d.ts +0 -65
  103. package/types/bricks/WebView.d.ts +0 -168
  104. package/types/bricks/index.d.ts +0 -23
  105. package/types/canvas.d.ts +0 -82
  106. package/types/common.d.ts +0 -141
  107. package/types/data-calc-command/base.d.ts +0 -57
  108. package/types/data-calc-command/collection.d.ts +0 -418
  109. package/types/data-calc-command/color.d.ts +0 -432
  110. package/types/data-calc-command/constant.d.ts +0 -50
  111. package/types/data-calc-command/datetime.d.ts +0 -147
  112. package/types/data-calc-command/file.d.ts +0 -129
  113. package/types/data-calc-command/index.d.ts +0 -13
  114. package/types/data-calc-command/iteratee.d.ts +0 -23
  115. package/types/data-calc-command/logictype.d.ts +0 -190
  116. package/types/data-calc-command/math.d.ts +0 -275
  117. package/types/data-calc-command/object.d.ts +0 -119
  118. package/types/data-calc-command/sandbox.d.ts +0 -66
  119. package/types/data-calc-command/string.d.ts +0 -407
  120. package/types/data-calc-script.d.ts +0 -21
  121. package/types/data-calc.d.ts +0 -12
  122. package/types/data.d.ts +0 -97
  123. package/types/generators/AlarmClock.d.ts +0 -110
  124. package/types/generators/Assistant.d.ts +0 -640
  125. package/types/generators/BleCentral.d.ts +0 -247
  126. package/types/generators/BlePeripheral.d.ts +0 -208
  127. package/types/generators/CanvasMap.d.ts +0 -74
  128. package/types/generators/CastlesPay.d.ts +0 -87
  129. package/types/generators/DataBank.d.ts +0 -160
  130. package/types/generators/File.d.ts +0 -432
  131. package/types/generators/GraphQl.d.ts +0 -132
  132. package/types/generators/Http.d.ts +0 -222
  133. package/types/generators/HttpServer.d.ts +0 -230
  134. package/types/generators/Information.d.ts +0 -103
  135. package/types/generators/Intent.d.ts +0 -168
  136. package/types/generators/Iterator.d.ts +0 -108
  137. package/types/generators/Keyboard.d.ts +0 -105
  138. package/types/generators/LlmAnthropicCompat.d.ts +0 -212
  139. package/types/generators/LlmAppleBuiltin.d.ts +0 -159
  140. package/types/generators/LlmGgml.d.ts +0 -903
  141. package/types/generators/LlmMediaTekNeuroPilot.d.ts +0 -235
  142. package/types/generators/LlmMlx.d.ts +0 -228
  143. package/types/generators/LlmOnnx.d.ts +0 -213
  144. package/types/generators/LlmOpenAiCompat.d.ts +0 -312
  145. package/types/generators/LlmQualcommAiEngine.d.ts +0 -247
  146. package/types/generators/Mcp.d.ts +0 -637
  147. package/types/generators/McpServer.d.ts +0 -289
  148. package/types/generators/MediaFlow.d.ts +0 -170
  149. package/types/generators/MqttBroker.d.ts +0 -141
  150. package/types/generators/MqttClient.d.ts +0 -141
  151. package/types/generators/Question.d.ts +0 -408
  152. package/types/generators/RealtimeTranscription.d.ts +0 -287
  153. package/types/generators/RerankerGgml.d.ts +0 -195
  154. package/types/generators/SerialPort.d.ts +0 -151
  155. package/types/generators/SoundPlayer.d.ts +0 -94
  156. package/types/generators/SoundRecorder.d.ts +0 -139
  157. package/types/generators/SpeechToTextGgml.d.ts +0 -424
  158. package/types/generators/SpeechToTextOnnx.d.ts +0 -236
  159. package/types/generators/SpeechToTextPlatform.d.ts +0 -85
  160. package/types/generators/SqLite.d.ts +0 -159
  161. package/types/generators/Step.d.ts +0 -107
  162. package/types/generators/SttAppleBuiltin.d.ts +0 -153
  163. package/types/generators/Tcp.d.ts +0 -126
  164. package/types/generators/TcpServer.d.ts +0 -147
  165. package/types/generators/TextToSpeechAppleBuiltin.d.ts +0 -127
  166. package/types/generators/TextToSpeechGgml.d.ts +0 -221
  167. package/types/generators/TextToSpeechOnnx.d.ts +0 -178
  168. package/types/generators/TextToSpeechOpenAiLike.d.ts +0 -121
  169. package/types/generators/ThermalPrinter.d.ts +0 -193
  170. package/types/generators/Tick.d.ts +0 -83
  171. package/types/generators/Udp.d.ts +0 -120
  172. package/types/generators/VadGgml.d.ts +0 -260
  173. package/types/generators/VadOnnx.d.ts +0 -231
  174. package/types/generators/VadTraditional.d.ts +0 -138
  175. package/types/generators/VectorStore.d.ts +0 -257
  176. package/types/generators/Watchdog.d.ts +0 -107
  177. package/types/generators/WebCrawler.d.ts +0 -103
  178. package/types/generators/WebRtc.d.ts +0 -181
  179. package/types/generators/WebSocket.d.ts +0 -148
  180. package/types/generators/index.d.ts +0 -57
  181. package/types/index.d.ts +0 -13
  182. package/types/subspace.d.ts +0 -60
  183. package/types/switch.d.ts +0 -51
  184. package/types/system.d.ts +0 -707
  185. package/utils/__tests__/calc.test.js +0 -25
  186. package/utils/__tests__/id.test.js +0 -154
  187. package/utils/calc.ts +0 -130
  188. package/utils/data.ts +0 -495
  189. package/utils/event-props.ts +0 -912
  190. 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
- }