@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.
- package/compile/action-name-map.ts +988 -0
- package/compile/index.ts +1245 -0
- package/compile/util.ts +358 -0
- package/index.ts +6 -0
- package/package.json +28 -0
- package/skills/bricks-design/LICENSE.txt +180 -0
- package/skills/bricks-design/SKILL.md +66 -0
- package/skills/bricks-project/SKILL.md +32 -0
- package/skills/bricks-project/rules/animation.md +159 -0
- package/skills/bricks-project/rules/architecture-patterns.md +69 -0
- package/skills/bricks-project/rules/automations.md +221 -0
- package/skills/bricks-project/rules/buttress.md +156 -0
- package/skills/bricks-project/rules/data-calculation.md +208 -0
- package/skills/bricks-project/rules/local-sync.md +129 -0
- package/skills/bricks-project/rules/media-flow.md +158 -0
- package/skills/bricks-project/rules/remote-data-bank.md +196 -0
- package/skills/bricks-project/rules/standby-transition.md +124 -0
- package/skills/rive-marketplace/SKILL.md +99 -0
- package/tools/deploy.ts +151 -0
- package/tools/icons/.gitattributes +1 -0
- package/tools/icons/fa6pro-glyphmap.json +4686 -0
- package/tools/icons/fa6pro-meta.json +3671 -0
- package/tools/mcp-server.ts +28 -0
- package/tools/mcp-tools/compile.ts +91 -0
- package/tools/mcp-tools/huggingface.ts +762 -0
- package/tools/mcp-tools/icons.ts +70 -0
- package/tools/mcp-tools/lottie.ts +102 -0
- package/tools/mcp-tools/media.ts +110 -0
- package/tools/postinstall.ts +229 -0
- package/tools/preview-main.mjs +293 -0
- package/tools/preview.ts +143 -0
- package/tools/pull.ts +116 -0
- package/tsconfig.json +16 -0
- package/types/animation.ts +100 -0
- package/types/automation.ts +235 -0
- package/types/brick-base.ts +80 -0
- package/types/bricks/Camera.ts +246 -0
- package/types/bricks/Chart.ts +372 -0
- package/types/bricks/GenerativeMedia.ts +276 -0
- package/types/bricks/Icon.ts +98 -0
- package/types/bricks/Image.ts +114 -0
- package/types/bricks/Items.ts +476 -0
- package/types/bricks/Lottie.ts +168 -0
- package/types/bricks/Maps.ts +262 -0
- package/types/bricks/QrCode.ts +117 -0
- package/types/bricks/Rect.ts +150 -0
- package/types/bricks/RichText.ts +128 -0
- package/types/bricks/Rive.ts +220 -0
- package/types/bricks/Slideshow.ts +201 -0
- package/types/bricks/Svg.ts +99 -0
- package/types/bricks/Text.ts +148 -0
- package/types/bricks/TextInput.ts +242 -0
- package/types/bricks/Video.ts +175 -0
- package/types/bricks/VideoStreaming.ts +112 -0
- package/types/bricks/WebRtcStream.ts +65 -0
- package/types/bricks/WebView.ts +168 -0
- package/types/bricks/index.ts +21 -0
- package/types/canvas.ts +82 -0
- package/types/common.ts +144 -0
- package/types/data-calc-command.ts +7005 -0
- package/types/data-calc-script.ts +21 -0
- package/types/data-calc.ts +11 -0
- package/types/data.ts +95 -0
- package/types/generators/AlarmClock.ts +110 -0
- package/types/generators/Assistant.ts +621 -0
- package/types/generators/BleCentral.ts +247 -0
- package/types/generators/BlePeripheral.ts +208 -0
- package/types/generators/CanvasMap.ts +74 -0
- package/types/generators/CastlesPay.ts +87 -0
- package/types/generators/DataBank.ts +160 -0
- package/types/generators/File.ts +432 -0
- package/types/generators/GraphQl.ts +132 -0
- package/types/generators/Http.ts +222 -0
- package/types/generators/HttpServer.ts +176 -0
- package/types/generators/Information.ts +103 -0
- package/types/generators/Intent.ts +168 -0
- package/types/generators/Iterator.ts +108 -0
- package/types/generators/Keyboard.ts +105 -0
- package/types/generators/LlmAnthropicCompat.ts +212 -0
- package/types/generators/LlmAppleBuiltin.ts +159 -0
- package/types/generators/LlmGgml.ts +861 -0
- package/types/generators/LlmMediaTekNeuroPilot.ts +235 -0
- package/types/generators/LlmMlx.ts +227 -0
- package/types/generators/LlmOnnx.ts +213 -0
- package/types/generators/LlmOpenAiCompat.ts +244 -0
- package/types/generators/LlmQualcommAiEngine.ts +247 -0
- package/types/generators/Mcp.ts +637 -0
- package/types/generators/McpServer.ts +289 -0
- package/types/generators/MediaFlow.ts +170 -0
- package/types/generators/MqttBroker.ts +141 -0
- package/types/generators/MqttClient.ts +141 -0
- package/types/generators/Question.ts +408 -0
- package/types/generators/RealtimeTranscription.ts +279 -0
- package/types/generators/RerankerGgml.ts +191 -0
- package/types/generators/SerialPort.ts +151 -0
- package/types/generators/SoundPlayer.ts +94 -0
- package/types/generators/SoundRecorder.ts +130 -0
- package/types/generators/SpeechToTextGgml.ts +415 -0
- package/types/generators/SpeechToTextOnnx.ts +236 -0
- package/types/generators/SpeechToTextPlatform.ts +85 -0
- package/types/generators/SqLite.ts +159 -0
- package/types/generators/Step.ts +107 -0
- package/types/generators/SttAppleBuiltin.ts +130 -0
- package/types/generators/Tcp.ts +126 -0
- package/types/generators/TcpServer.ts +147 -0
- package/types/generators/TextToSpeechAppleBuiltin.ts +127 -0
- package/types/generators/TextToSpeechGgml.ts +221 -0
- package/types/generators/TextToSpeechOnnx.ts +178 -0
- package/types/generators/TextToSpeechOpenAiLike.ts +121 -0
- package/types/generators/ThermalPrinter.ts +191 -0
- package/types/generators/Tick.ts +83 -0
- package/types/generators/Udp.ts +120 -0
- package/types/generators/VadGgml.ts +250 -0
- package/types/generators/VadOnnx.ts +231 -0
- package/types/generators/VadTraditional.ts +138 -0
- package/types/generators/VectorStore.ts +257 -0
- package/types/generators/Watchdog.ts +107 -0
- package/types/generators/WebCrawler.ts +103 -0
- package/types/generators/WebRtc.ts +181 -0
- package/types/generators/WebSocket.ts +148 -0
- package/types/generators/index.ts +57 -0
- package/types/index.ts +13 -0
- package/types/subspace.ts +59 -0
- package/types/switch.ts +51 -0
- package/types/system.ts +707 -0
- package/utils/calc.ts +126 -0
- package/utils/data.ts +497 -0
- package/utils/event-props.ts +836 -0
- 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
|
+
})
|
package/tools/preview.ts
ADDED
|
@@ -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
|
+
}
|