@fugood/bricks-ctor 2.24.0-beta.40

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 (129) hide show
  1. package/compile/action-name-map.ts +988 -0
  2. package/compile/index.ts +1245 -0
  3. package/compile/util.ts +358 -0
  4. package/index.ts +6 -0
  5. package/package.json +28 -0
  6. package/skills/bricks-design/LICENSE.txt +180 -0
  7. package/skills/bricks-design/SKILL.md +66 -0
  8. package/skills/bricks-project/SKILL.md +32 -0
  9. package/skills/bricks-project/rules/animation.md +159 -0
  10. package/skills/bricks-project/rules/architecture-patterns.md +69 -0
  11. package/skills/bricks-project/rules/automations.md +221 -0
  12. package/skills/bricks-project/rules/buttress.md +156 -0
  13. package/skills/bricks-project/rules/data-calculation.md +208 -0
  14. package/skills/bricks-project/rules/local-sync.md +129 -0
  15. package/skills/bricks-project/rules/media-flow.md +158 -0
  16. package/skills/bricks-project/rules/remote-data-bank.md +196 -0
  17. package/skills/bricks-project/rules/standby-transition.md +124 -0
  18. package/skills/rive-marketplace/SKILL.md +99 -0
  19. package/tools/deploy.ts +151 -0
  20. package/tools/icons/.gitattributes +1 -0
  21. package/tools/icons/fa6pro-glyphmap.json +4686 -0
  22. package/tools/icons/fa6pro-meta.json +3671 -0
  23. package/tools/mcp-server.ts +28 -0
  24. package/tools/mcp-tools/compile.ts +91 -0
  25. package/tools/mcp-tools/huggingface.ts +762 -0
  26. package/tools/mcp-tools/icons.ts +70 -0
  27. package/tools/mcp-tools/lottie.ts +102 -0
  28. package/tools/mcp-tools/media.ts +110 -0
  29. package/tools/postinstall.ts +229 -0
  30. package/tools/preview-main.mjs +293 -0
  31. package/tools/preview.ts +143 -0
  32. package/tools/pull.ts +116 -0
  33. package/tsconfig.json +16 -0
  34. package/types/animation.ts +100 -0
  35. package/types/automation.ts +235 -0
  36. package/types/brick-base.ts +80 -0
  37. package/types/bricks/Camera.ts +246 -0
  38. package/types/bricks/Chart.ts +372 -0
  39. package/types/bricks/GenerativeMedia.ts +276 -0
  40. package/types/bricks/Icon.ts +98 -0
  41. package/types/bricks/Image.ts +114 -0
  42. package/types/bricks/Items.ts +476 -0
  43. package/types/bricks/Lottie.ts +168 -0
  44. package/types/bricks/Maps.ts +262 -0
  45. package/types/bricks/QrCode.ts +117 -0
  46. package/types/bricks/Rect.ts +150 -0
  47. package/types/bricks/RichText.ts +128 -0
  48. package/types/bricks/Rive.ts +220 -0
  49. package/types/bricks/Slideshow.ts +201 -0
  50. package/types/bricks/Svg.ts +99 -0
  51. package/types/bricks/Text.ts +148 -0
  52. package/types/bricks/TextInput.ts +242 -0
  53. package/types/bricks/Video.ts +175 -0
  54. package/types/bricks/VideoStreaming.ts +112 -0
  55. package/types/bricks/WebRtcStream.ts +65 -0
  56. package/types/bricks/WebView.ts +168 -0
  57. package/types/bricks/index.ts +21 -0
  58. package/types/canvas.ts +82 -0
  59. package/types/common.ts +144 -0
  60. package/types/data-calc-command.ts +7005 -0
  61. package/types/data-calc-script.ts +21 -0
  62. package/types/data-calc.ts +11 -0
  63. package/types/data.ts +95 -0
  64. package/types/generators/AlarmClock.ts +110 -0
  65. package/types/generators/Assistant.ts +621 -0
  66. package/types/generators/BleCentral.ts +247 -0
  67. package/types/generators/BlePeripheral.ts +208 -0
  68. package/types/generators/CanvasMap.ts +74 -0
  69. package/types/generators/CastlesPay.ts +87 -0
  70. package/types/generators/DataBank.ts +160 -0
  71. package/types/generators/File.ts +432 -0
  72. package/types/generators/GraphQl.ts +132 -0
  73. package/types/generators/Http.ts +222 -0
  74. package/types/generators/HttpServer.ts +176 -0
  75. package/types/generators/Information.ts +103 -0
  76. package/types/generators/Intent.ts +168 -0
  77. package/types/generators/Iterator.ts +108 -0
  78. package/types/generators/Keyboard.ts +105 -0
  79. package/types/generators/LlmAnthropicCompat.ts +212 -0
  80. package/types/generators/LlmAppleBuiltin.ts +159 -0
  81. package/types/generators/LlmGgml.ts +861 -0
  82. package/types/generators/LlmMediaTekNeuroPilot.ts +235 -0
  83. package/types/generators/LlmMlx.ts +227 -0
  84. package/types/generators/LlmOnnx.ts +213 -0
  85. package/types/generators/LlmOpenAiCompat.ts +244 -0
  86. package/types/generators/LlmQualcommAiEngine.ts +247 -0
  87. package/types/generators/Mcp.ts +637 -0
  88. package/types/generators/McpServer.ts +289 -0
  89. package/types/generators/MediaFlow.ts +170 -0
  90. package/types/generators/MqttBroker.ts +141 -0
  91. package/types/generators/MqttClient.ts +141 -0
  92. package/types/generators/Question.ts +408 -0
  93. package/types/generators/RealtimeTranscription.ts +279 -0
  94. package/types/generators/RerankerGgml.ts +191 -0
  95. package/types/generators/SerialPort.ts +151 -0
  96. package/types/generators/SoundPlayer.ts +94 -0
  97. package/types/generators/SoundRecorder.ts +130 -0
  98. package/types/generators/SpeechToTextGgml.ts +415 -0
  99. package/types/generators/SpeechToTextOnnx.ts +236 -0
  100. package/types/generators/SpeechToTextPlatform.ts +85 -0
  101. package/types/generators/SqLite.ts +159 -0
  102. package/types/generators/Step.ts +107 -0
  103. package/types/generators/SttAppleBuiltin.ts +130 -0
  104. package/types/generators/Tcp.ts +126 -0
  105. package/types/generators/TcpServer.ts +147 -0
  106. package/types/generators/TextToSpeechAppleBuiltin.ts +127 -0
  107. package/types/generators/TextToSpeechGgml.ts +221 -0
  108. package/types/generators/TextToSpeechOnnx.ts +178 -0
  109. package/types/generators/TextToSpeechOpenAiLike.ts +121 -0
  110. package/types/generators/ThermalPrinter.ts +191 -0
  111. package/types/generators/Tick.ts +83 -0
  112. package/types/generators/Udp.ts +120 -0
  113. package/types/generators/VadGgml.ts +250 -0
  114. package/types/generators/VadOnnx.ts +231 -0
  115. package/types/generators/VadTraditional.ts +138 -0
  116. package/types/generators/VectorStore.ts +257 -0
  117. package/types/generators/Watchdog.ts +107 -0
  118. package/types/generators/WebCrawler.ts +103 -0
  119. package/types/generators/WebRtc.ts +181 -0
  120. package/types/generators/WebSocket.ts +148 -0
  121. package/types/generators/index.ts +57 -0
  122. package/types/index.ts +13 -0
  123. package/types/subspace.ts +59 -0
  124. package/types/switch.ts +51 -0
  125. package/types/system.ts +707 -0
  126. package/utils/calc.ts +126 -0
  127. package/utils/data.ts +497 -0
  128. package/utils/event-props.ts +836 -0
  129. package/utils/id.ts +80 -0
@@ -0,0 +1,293 @@
1
+ // eslint-disable-next-line import/no-extraneous-dependencies
2
+ import { app, BrowserWindow } from 'electron'
3
+ import { readFile, writeFile } from 'fs/promises'
4
+ import { watchFile } from 'fs'
5
+ import { createServer } from 'http'
6
+ import { parseArgs } from 'util'
7
+ import * as TOON from '@toon-format/toon'
8
+
9
+ const { values } = parseArgs({
10
+ args: process.argv,
11
+ options: {
12
+ 'clear-cache': { type: 'boolean' },
13
+ 'take-screenshot': { type: 'string' },
14
+ 'show-menu': { type: 'boolean' },
15
+ 'test-id': { type: 'string' },
16
+ 'test-title-like': { type: 'string' },
17
+ 'no-keep-open': { type: 'boolean' },
18
+ 'cdp-port': { type: 'string' },
19
+ },
20
+ strict: true,
21
+ allowPositionals: true,
22
+ })
23
+
24
+ const cwd = process.cwd()
25
+
26
+ let takeScreenshotConfig = null
27
+ try {
28
+ if (values['take-screenshot']) {
29
+ takeScreenshotConfig = JSON.parse(values['take-screenshot'])
30
+ if (!takeScreenshotConfig.path) takeScreenshotConfig.path = `${cwd}/screenshot.jpg`
31
+ if (!takeScreenshotConfig.width) takeScreenshotConfig.width = 1280
32
+ if (!takeScreenshotConfig.height) takeScreenshotConfig.height = 768
33
+ if (!takeScreenshotConfig.delay) takeScreenshotConfig.delay = 1000
34
+ }
35
+ } catch (e) {
36
+ console.error('Invalid take-screenshot config', e)
37
+ // eslint-disable-next-line unicorn/no-process-exit
38
+ process.exit(1)
39
+ }
40
+
41
+ let config = JSON.parse(await readFile(`${cwd}/.bricks/build/application-config.json`))
42
+
43
+ // Resolve testId from testTitleLike
44
+ let testId = values['test-id'] || null
45
+ if (!testId && values['test-title-like']) {
46
+ const titleLike = values['test-title-like'].toLowerCase()
47
+ const automationMap = config.automation_map || {}
48
+ for (const group of Object.values(automationMap)) {
49
+ const found = Object.entries(group.map || {}).find(([, test]) =>
50
+ test.title?.toLowerCase().includes(titleLike),
51
+ )
52
+ if (found) {
53
+ ;[testId] = found
54
+ break
55
+ }
56
+ }
57
+ if (!testId) {
58
+ throw new Error(`No automation found matching title: ${values['test-title-like']}`)
59
+ }
60
+ }
61
+
62
+ const noKeepOpen = values['no-keep-open'] ?? false
63
+
64
+ const stage = process.env.BRICKS_STAGE || 'production'
65
+
66
+ const previewUrlMap = {
67
+ production: 'https://control.bricks.tools/applicationPreview.html',
68
+ beta: 'https://control-beta.bricks.tools/applicationPreview.html',
69
+ development: 'http://localhost:3006/dev-applicationPreview.html',
70
+ }
71
+
72
+ const previewUrl = previewUrlMap[stage]
73
+ if (!previewUrl) throw new Error(`Invalid BRICKS_STAGE: ${stage}`)
74
+
75
+ // --- CDP WebSocket Server ---
76
+ // Bridges external CDP clients to the preview's postMessage-based CDP bridge.
77
+ // Usage: --cdp-port 9222
78
+
79
+ const startCdpServer = async (mainWindow, port) => {
80
+ const { WebSocketServer } = await import('ws')
81
+ const clients = new Set()
82
+
83
+ // Inject a listener in the preview that forwards CDP responses/events via console
84
+ const injectCdpRelay = () =>
85
+ mainWindow.webContents.executeJavaScript(`
86
+ if (!window.__cdpRelayInstalled) {
87
+ window.__cdpRelayInstalled = true
88
+ window.addEventListener('message', (evt) => {
89
+ try {
90
+ const d = typeof evt.data === 'string' ? JSON.parse(evt.data) : evt.data
91
+ if (d && (d.type === 'cdp-response' || d.type === 'cdp-event'))
92
+ console.log('[CDP]' + JSON.stringify(d))
93
+ } catch {}
94
+ })
95
+ }
96
+ `)
97
+
98
+ // Capture CDP messages from the preview's console output
99
+ mainWindow.webContents.on('console-message', (_event, ...rest) => {
100
+ const message = typeof rest[0] === 'object' ? rest[0].message : rest[1]
101
+ if (!message?.startsWith('[CDP]')) return
102
+ try {
103
+ const data = JSON.parse(message.slice(5))
104
+ // Translate to standard CDP wire format (strip internal type field)
105
+ const wireMsg =
106
+ data.type === 'cdp-response'
107
+ ? { id: data.id, result: data.result, error: data.error }
108
+ : { method: data.method, params: data.params }
109
+ if (data.sessionId) wireMsg.sessionId = data.sessionId
110
+ const payload = JSON.stringify(wireMsg)
111
+ for (const c of clients) if (c.readyState === 1) c.send(payload)
112
+ } catch {}
113
+ })
114
+
115
+ // Find an available port before binding
116
+ const findPort = (p) =>
117
+ new Promise((resolve) => {
118
+ const probe = createServer()
119
+ probe.once('error', () => {
120
+ if (p < port + 100) resolve(findPort(p + 1))
121
+ else resolve(null)
122
+ })
123
+ probe.listen(p, () => probe.close(() => resolve(p)))
124
+ })
125
+
126
+ const actualPort = await findPort(port)
127
+ if (!actualPort) {
128
+ console.warn(`CDP server: no available port in range ${port}-${port + 99}`)
129
+ return
130
+ }
131
+
132
+ // HTTP discovery endpoints (chrome://inspect, Playwright, etc.)
133
+ const httpServer = createServer((req, res) => {
134
+ if (req.url === '/json/list' || req.url === '/json') {
135
+ res.writeHead(200, { 'Content-Type': 'application/json' })
136
+ res.end(
137
+ JSON.stringify([
138
+ {
139
+ description: 'BRICKS Preview',
140
+ id: 'bricks-preview',
141
+ title: 'BRICKS Preview',
142
+ type: 'page',
143
+ url: previewUrl,
144
+ webSocketDebuggerUrl: `ws://localhost:${actualPort}/ws`,
145
+ },
146
+ ]),
147
+ )
148
+ return
149
+ }
150
+ if (req.url === '/json/version') {
151
+ res.writeHead(200, { 'Content-Type': 'application/json' })
152
+ res.end(JSON.stringify({ Browser: 'BRICKS Preview', 'Protocol-Version': '1.3' }))
153
+ return
154
+ }
155
+ // bricks-cli discovery endpoint
156
+ if (req.url === '/devtools/info') {
157
+ res.writeHead(200, { 'Content-Type': 'application/json' })
158
+ res.end(
159
+ JSON.stringify({
160
+ name: 'BRICKS Preview',
161
+ port: actualPort,
162
+ protocols: ['cdp'],
163
+ hasPasscode: false,
164
+ }),
165
+ )
166
+ return
167
+ }
168
+ res.writeHead(404)
169
+ res.end()
170
+ })
171
+
172
+ const wss = new WebSocketServer({ server: httpServer, path: '/ws' })
173
+ wss.on('connection', (ws) => {
174
+ clients.add(ws)
175
+ injectCdpRelay()
176
+
177
+ ws.on('message', (raw) => {
178
+ try {
179
+ const req = JSON.parse(raw.toString())
180
+ const cdpReq = JSON.stringify({
181
+ type: 'cdp-request',
182
+ id: req.id,
183
+ method: req.method,
184
+ params: req.params || {},
185
+ sessionId: req.sessionId,
186
+ })
187
+ mainWindow.webContents.executeJavaScript(`window.postMessage(${JSON.stringify(cdpReq)})`)
188
+ } catch {}
189
+ })
190
+
191
+ ws.on('close', () => clients.delete(ws))
192
+ })
193
+
194
+ httpServer.listen(actualPort, () => {
195
+ console.log(`CDP server: ws://localhost:${actualPort}/ws`)
196
+ console.log(
197
+ `Inspect: devtools://devtools/bundled/inspector.html?ws=localhost:${actualPort}/ws`,
198
+ )
199
+ })
200
+ }
201
+
202
+ app.on('ready', () => {
203
+ let show = true
204
+ if (takeScreenshotConfig && !takeScreenshotConfig.noHeadless) show = false
205
+ const mainWindow = new BrowserWindow({
206
+ width: takeScreenshotConfig?.width || 1280,
207
+ height: takeScreenshotConfig?.height || 768,
208
+ frame: !takeScreenshotConfig,
209
+ show,
210
+ })
211
+ mainWindow.setBackgroundColor('#333')
212
+ mainWindow.loadURL(previewUrl)
213
+
214
+ const sendConfig = () => {
215
+ const payload = {
216
+ type: 'config',
217
+ configFile: { _originTitle: 'Test', ...config, title: Math.random().toString() },
218
+ workspace: { billing: { lock: {}, plan: 'free' } },
219
+ showMenu: values['show-menu'] || false,
220
+ testId,
221
+ }
222
+ mainWindow.webContents.executeJavaScript(
223
+ `window.postMessage(JSON.stringify(${JSON.stringify(payload)}))`,
224
+ )
225
+ if (takeScreenshotConfig) {
226
+ const { delay, width, height, path } = takeScreenshotConfig
227
+ setTimeout(() => {
228
+ console.log('Taking screenshot')
229
+ mainWindow.webContents.capturePage().then((image) => {
230
+ console.log('Writing screenshot to', path)
231
+ writeFile(path, image.resize({ width, height }).toJPEG(75))
232
+ if (noKeepOpen) {
233
+ console.log('Closing app')
234
+ app.quit()
235
+ }
236
+ })
237
+ }, delay)
238
+ }
239
+ }
240
+
241
+ mainWindow.webContents.once('dom-ready', () => {
242
+ sendConfig()
243
+ // Listen for test result messages from the preview
244
+ if (testId) {
245
+ mainWindow.webContents.executeJavaScript(`
246
+ window.addEventListener('message', (evt) => {
247
+ try {
248
+ const data = JSON.parse(evt.data)
249
+ if (data.type === 'bricks-preview-test-result') {
250
+ console.log('[TEST_RESULT]' + JSON.stringify(data))
251
+ }
252
+ } catch (e) {}
253
+ })
254
+ `)
255
+ }
256
+ // Start CDP WebSocket server if requested
257
+ const cdpPort = values['cdp-port'] ? parseInt(values['cdp-port'], 10) : null
258
+ if (cdpPort) startCdpServer(mainWindow, cdpPort)
259
+ })
260
+
261
+ // Capture console messages from the preview
262
+ if (testId) {
263
+ mainWindow.webContents.on('console-message', (_event, ...rest) => {
264
+ const message = typeof rest[0] === 'object' ? rest[0].message : rest[1]
265
+ if (message?.startsWith('[TEST_RESULT]')) {
266
+ const data = JSON.parse(message.replace('[TEST_RESULT]', ''))
267
+ console.log(`[TEST_RESULT_TOON]${TOON.encode(data.result)}`)
268
+ if (!takeScreenshotConfig && noKeepOpen) app.quit()
269
+ }
270
+ })
271
+ }
272
+
273
+ if (values['clear-cache']) {
274
+ console.log('Clearing cache')
275
+ mainWindow.webContents.session.clearCache()
276
+ }
277
+
278
+ mainWindow.on('close', () => app.quit())
279
+
280
+ watchFile(
281
+ `${cwd}/.bricks/build/application-config.json`,
282
+ {
283
+ bigint: false,
284
+ persistent: true,
285
+ interval: 1000,
286
+ },
287
+ async () => {
288
+ console.log('Detected config changed')
289
+ config = JSON.parse(await readFile(`${cwd}/.bricks/build/application-config.json`))
290
+ sendConfig()
291
+ },
292
+ )
293
+ })
@@ -0,0 +1,143 @@
1
+ import { $ } from 'bun'
2
+ import { watch, unlinkSync } from 'fs'
3
+ import type { FSWatcher } from 'fs'
4
+ import { parseArgs } from 'util'
5
+ import debounce from 'lodash/debounce'
6
+
7
+ const { values } = parseArgs({
8
+ args: Bun.argv,
9
+ options: {
10
+ 'skip-typecheck': { type: 'boolean' },
11
+ 'clear-cache': { type: 'boolean' },
12
+ screenshot: { type: 'boolean' },
13
+ 'screenshot-delay': { type: 'string' },
14
+ 'screenshot-width': { type: 'string' },
15
+ 'screenshot-height': { type: 'string' },
16
+ 'screenshot-path': { type: 'string' },
17
+ 'screenshot-no-headless': { type: 'boolean' },
18
+ 'show-menu': { type: 'boolean' },
19
+ 'test-id': { type: 'string' },
20
+ 'test-title-like': { type: 'string' },
21
+ 'no-keep-open': { type: 'boolean' },
22
+ 'cdp-port': { type: 'string' },
23
+ 'no-cdp': { type: 'boolean' },
24
+ },
25
+ strict: true,
26
+ allowPositionals: true,
27
+ })
28
+
29
+ const cwd = process.cwd()
30
+
31
+ const app = await Bun.file(`${cwd}/application.json`).json()
32
+
33
+ const args: string[] = []
34
+ if (values['clear-cache']) args.push('--clear-cache')
35
+
36
+ let needWatcher = true
37
+ if (values['screenshot']) {
38
+ args.push(`--take-screenshot`)
39
+ args.push(
40
+ JSON.stringify({
41
+ delay: Number(values['screenshot-delay']) || 1000,
42
+ width: Number(values['screenshot-width']) || 600,
43
+ height: Number(values['screenshot-height']) || 480,
44
+ path: values['screenshot-path'] || `${cwd}/screenshot.jpg`,
45
+ noHeadless: values['screenshot-no-headless'] ?? false,
46
+ }),
47
+ )
48
+ needWatcher = !values['no-keep-open']
49
+ }
50
+
51
+ if (values['show-menu']) {
52
+ args.push('--show-menu')
53
+ }
54
+
55
+ if (values['test-id']) {
56
+ args.push('--test-id', values['test-id'])
57
+ }
58
+
59
+ if (values['test-title-like']) {
60
+ args.push('--test-title-like', values['test-title-like'])
61
+ }
62
+
63
+ if (values['no-keep-open']) {
64
+ args.push('--no-keep-open')
65
+ }
66
+
67
+ if (!values['no-cdp']) {
68
+ const cdpPort = parseInt(values['cdp-port'] || '', 10) || 19852
69
+ args.push('--cdp-port', String(cdpPort))
70
+ }
71
+
72
+ const useTypecheck = !values['skip-typecheck']
73
+
74
+ const compile = async () => {
75
+ if (useTypecheck) await $`bun typecheck`
76
+ await $`bun compile.ts`
77
+ }
78
+
79
+ await compile()
80
+
81
+ let watcher: FSWatcher | null = null
82
+ if (needWatcher) {
83
+ const compileDebounced = debounce(compile, 500)
84
+ watcher = watch(`${cwd}/subspaces`, { recursive: true }, async (event, filename) => {
85
+ console.log(`Detected ${event} in ${filename}`)
86
+ compileDebounced()
87
+ })
88
+ }
89
+
90
+ const devtoolsInfoPath = `${cwd}/.bricks/devtools.json`
91
+
92
+ const cleanupDevtoolsInfo = () => {
93
+ try {
94
+ unlinkSync(devtoolsInfoPath)
95
+ } catch {}
96
+ }
97
+
98
+ const proc = Bun.spawn(['bunx', '--bun', 'electron', `${__dirname}/preview-main.mjs`, ...args], {
99
+ env: { ...process.env, BRICKS_STAGE: app.stage || 'production' },
100
+ stdout: 'pipe',
101
+ stderr: 'inherit',
102
+ })
103
+
104
+ const reader = proc.stdout.getReader()
105
+ const decoder = new TextDecoder()
106
+
107
+ const processOutput = async () => {
108
+ let done = false
109
+ while (!done) {
110
+ // eslint-disable-next-line no-await-in-loop
111
+ const result = await reader.read()
112
+ ;({ done } = result)
113
+ if (!done) {
114
+ const text = decoder.decode(result.value)
115
+ text.split('\n').forEach((line) => {
116
+ if (line.startsWith('[TEST_RESULT_TOON]')) {
117
+ const toonData = line.replace('[TEST_RESULT_TOON]', '')
118
+ console.log(toonData)
119
+ } else if (line) {
120
+ // Detect CDP server startup from preview-main output
121
+ const cdpMatch = line.match(/^CDP server: ws:\/\/localhost:(\d+)/)
122
+ if (cdpMatch) {
123
+ const info = {
124
+ port: parseInt(cdpMatch[1], 10),
125
+ pid: proc.pid,
126
+ address: 'localhost',
127
+ name: app.name || 'BRICKS Preview',
128
+ startedAt: new Date().toISOString(),
129
+ }
130
+ Bun.write(devtoolsInfoPath, JSON.stringify(info, null, 2))
131
+ }
132
+ console.log(line)
133
+ }
134
+ })
135
+ }
136
+ }
137
+ }
138
+
139
+ await processOutput()
140
+ await proc.exited
141
+
142
+ cleanupDevtoolsInfo()
143
+ if (watcher) watcher.close()
package/tools/pull.ts ADDED
@@ -0,0 +1,116 @@
1
+ import { $ } from 'bun'
2
+ import { format } from 'oxfmt'
3
+
4
+ const cwd = process.cwd()
5
+ const args = process.argv.slice(2)
6
+ const force = args.includes('--force') || args.includes('-f')
7
+
8
+ // Check git status
9
+ const { exitCode } = await $`cd ${cwd} && git status`.nothrow()
10
+ const isGitRepo = exitCode === 0
11
+
12
+ if (isGitRepo) {
13
+ const unstagedChanges = await $`cd ${cwd} && git diff --name-only --diff-filter=ACMR`.text()
14
+ if (unstagedChanges) {
15
+ if (force) {
16
+ console.log('Force mode: committing unstaged changes before pull...')
17
+ await $`cd ${cwd} && git add .`
18
+ await $`cd ${cwd} && git commit -m ${'chore(force-pull): saved unstaged changes before pull'}`
19
+ } else {
20
+ throw new Error('Unstaged changes found, please commit or stash your changes before pulling')
21
+ }
22
+ }
23
+ } else {
24
+ const confirmContinue = prompt(
25
+ 'No git repository found, so it will not be safe to pull, continue? (y/n)',
26
+ )
27
+ if (confirmContinue !== 'y') throw new Error('Pull cancelled')
28
+ }
29
+
30
+ // Read application.json
31
+ const app = await Bun.file(`${cwd}/application.json`).json()
32
+
33
+ const isModule = app.type === 'module'
34
+ const command = isModule ? 'module' : 'app'
35
+
36
+ // Fetch project files using CLI
37
+ console.log(`Pulling ${command} project (${app.id})...`)
38
+ const result = await $`bricks ${command} project-pull ${app.id} --json`.quiet().nothrow()
39
+
40
+ if (result.exitCode !== 0) {
41
+ const output = result.stderr.toString() || result.stdout.toString()
42
+ try {
43
+ const json = JSON.parse(output)
44
+ throw new Error(json.error || 'Pull failed')
45
+ } catch {
46
+ throw new Error(output || 'Pull failed')
47
+ }
48
+ }
49
+
50
+ const { files, lastCommitId } = JSON.parse(result.stdout.toString())
51
+
52
+ let useMain = false
53
+ if (isGitRepo && !force) {
54
+ console.log(`Checking commit ${lastCommitId}...`)
55
+ const found = (await $`cd ${cwd} && git rev-list -1 ${lastCommitId}`.nothrow().text())
56
+ .trim()
57
+ .match(/^[\da-f]{40}$/)
58
+
59
+ const commitId = (await $`cd ${cwd} && git rev-parse HEAD`.text()).trim()
60
+
61
+ if (commitId === lastCommitId) throw new Error('Commit not changed')
62
+
63
+ const branchName = isModule
64
+ ? 'BRICKS_PROJECT_try-pull-module'
65
+ : 'BRICKS_PROJECT_try-pull-application'
66
+
67
+ await $`cd ${cwd} && git branch -D ${branchName}`.nothrow()
68
+
69
+ if (found) {
70
+ await $`cd ${cwd} && git checkout -b ${branchName} ${lastCommitId}`.nothrow()
71
+ } else {
72
+ await $`cd ${cwd} && git checkout -b ${branchName}`
73
+ useMain = true
74
+ }
75
+ }
76
+
77
+ const oxfmtConfig = await Bun.file(`${cwd}/.oxfmtrc.json`)
78
+ .json()
79
+ .catch(() => ({
80
+ trailingComma: 'all',
81
+ tabWidth: 2,
82
+ semi: false,
83
+ singleQuote: true,
84
+ printWidth: 100,
85
+ }))
86
+
87
+ await Promise.all(
88
+ files.map(async (file: { name: string; input: string; formatable?: boolean }) => {
89
+ let content = file.input
90
+ if (file.formatable) {
91
+ const result = await format(file.name, file.input, oxfmtConfig)
92
+ content = result.code
93
+ }
94
+ return Bun.write(`${cwd}/${file.name}`, content)
95
+ }),
96
+ )
97
+
98
+ if (isGitRepo) {
99
+ await $`cd ${cwd} && git add .`
100
+ const hasChanges = !!(await $`cd ${cwd} && git diff --cached --name-only`.text()).trim()
101
+ if (hasChanges) {
102
+ const commitMsg = force
103
+ ? `chore(force-pull): apply force pull-${command}`
104
+ : isModule
105
+ ? 'chore(project): apply file changes from BRICKS module'
106
+ : 'chore(project): apply file changes from BRICKS application'
107
+ await $`cd ${cwd} && git commit -m ${commitMsg}`
108
+ }
109
+ if (!force && !useMain) {
110
+ await $`cd ${cwd} && git merge main`
111
+ }
112
+ }
113
+
114
+ console.log(
115
+ `${isModule ? 'Module' : 'App'} project pulled: ${files.length} files${force ? ' (force)' : ''}`,
116
+ )
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "lib": ["ESNext"],
4
+ "target": "ESNext",
5
+ "module": "ESNext",
6
+ "moduleDetection": "force",
7
+ "allowJs": true,
8
+ "resolveJsonModule": true,
9
+ "moduleResolution": "bundler",
10
+ "allowImportingTsExtensions": true,
11
+ "verbatimModuleSyntax": true,
12
+ "noEmit": true,
13
+ "skipLibCheck": true,
14
+ },
15
+ "exclude": ["node_modules"],
16
+ }
@@ -0,0 +1,100 @@
1
+ export type Easing =
2
+ | ''
3
+ | 'easeInSine'
4
+ | 'easeOutSine'
5
+ | 'easeInOutSine'
6
+ | 'easeInQuad'
7
+ | 'easeOutQuad'
8
+ | 'easeInOutQuad'
9
+ | 'easeInCubic'
10
+ | 'easeOutCubic'
11
+ | 'easeInOutCubic'
12
+ | 'easeInQuart'
13
+ | 'easeOutQuart'
14
+ | 'easeInOutQuart'
15
+ | 'easeInQuint'
16
+ | 'easeOutQuint'
17
+ | 'easeInOutQuint'
18
+ | 'easeInExpo'
19
+ | 'easeOutExpo'
20
+ | 'easeInOutExpo'
21
+ | 'easeInCirc'
22
+ | 'easeOutCirc'
23
+ | 'easeInOutCirc'
24
+ | 'easeInBack'
25
+ | 'easeOutBack'
26
+ | 'easeInOutBack'
27
+ | 'easeInElastic'
28
+ | 'easeOutElastic'
29
+ | 'easeInOutElastic'
30
+ | 'easeInBounce'
31
+ | 'easeOutBounce'
32
+ | 'easeInOutBounce'
33
+ | string // Support format like 'cubic-bezier(x1, y1, x2, y2)'
34
+
35
+ export interface AnimationTimingConfig {
36
+ __type: 'AnimationTimingConfig'
37
+ toValue: number // BRICKS Grid unit
38
+ duration: number // ms
39
+ easing: Easing
40
+ delay: number // ms
41
+ isInteraction: boolean
42
+ }
43
+
44
+ export interface AnimationSpringConfig {
45
+ __type: 'AnimationSpringConfig'
46
+ toValue: number // BRICKS Grid unit
47
+ friction: number
48
+ tension: number
49
+ speed: number
50
+ bounciness: number
51
+ }
52
+
53
+ export interface AnimationDecayConfig {
54
+ __type: 'AnimationDecayConfig'
55
+ toValue: number // BRICKS Grid unit
56
+ velocity: number
57
+ deceleration: number
58
+ isInteraction: boolean
59
+ }
60
+
61
+ export interface AnimationDef {
62
+ __typename: 'Animation'
63
+ id: string
64
+ alias?: string
65
+ title: string
66
+ description?: string
67
+ hideShortRef?: boolean
68
+ runType?: 'once' | 'loop'
69
+ property:
70
+ | 'transform.translateX'
71
+ | 'transform.translateY'
72
+ | 'transform.scale'
73
+ | 'transform.scaleX'
74
+ | 'transform.scaleY'
75
+ | 'transform.rotate'
76
+ | 'transform.rotateX'
77
+ | 'transform.rotateY'
78
+ | 'opacity'
79
+ config: AnimationTimingConfig | AnimationSpringConfig | AnimationDecayConfig
80
+ }
81
+
82
+ export interface AnimationComposeDef {
83
+ __typename: 'AnimationCompose'
84
+ id: string
85
+ alias?: string
86
+ title: string
87
+ description?: string
88
+ hideShortRef?: boolean
89
+ runType?: 'once' | 'loop'
90
+ composeType: 'parallel' | 'sequence'
91
+ items: Array<() => Animation>
92
+ }
93
+
94
+ export type Animation = AnimationDef | AnimationComposeDef
95
+
96
+ export interface AnimationBasicEvents {
97
+ showStart?: Animation
98
+ standby?: Animation
99
+ breatheStart?: Animation
100
+ }