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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/package.json +4 -28
  2. package/tools/__tests__/legacy-forwarder.test.js +91 -0
  3. package/tools/_forward.ts +26 -0
  4. package/tools/deploy.ts +3 -175
  5. package/tools/mcp-server.ts +3 -35
  6. package/tools/postinstall.ts +3 -291
  7. package/tools/pull.ts +3 -198
  8. package/tools/push-config.ts +3 -113
  9. package/tools/simulator.ts +3 -149
  10. package/compile/__tests__/config-diff.test.js +0 -100
  11. package/compile/__tests__/index.test.js +0 -461
  12. package/compile/__tests__/util.test.js +0 -450
  13. package/compile/action-name-map.ts +0 -1079
  14. package/compile/config-diff.ts +0 -155
  15. package/compile/index.ts +0 -1594
  16. package/compile/util.ts +0 -482
  17. package/index.ts +0 -6
  18. package/skills/bricks-ctor/SKILL.md +0 -38
  19. package/skills/bricks-ctor/references/animation.md +0 -160
  20. package/skills/bricks-ctor/references/architecture-patterns.md +0 -88
  21. package/skills/bricks-ctor/references/automations.md +0 -232
  22. package/skills/bricks-ctor/references/buttress.md +0 -245
  23. package/skills/bricks-ctor/references/data-calculation.md +0 -252
  24. package/skills/bricks-ctor/references/local-sync.md +0 -129
  25. package/skills/bricks-ctor/references/media-flow.md +0 -165
  26. package/skills/bricks-ctor/references/remote-data-bank.md +0 -196
  27. package/skills/bricks-ctor/references/simulator.md +0 -132
  28. package/skills/bricks-ctor/references/source-editing-tools.md +0 -81
  29. package/skills/bricks-ctor/references/standby-transition.md +0 -124
  30. package/skills/bricks-ctor/references/verification-toolchain.md +0 -200
  31. package/skills/bricks-design/SKILL.md +0 -171
  32. package/skills/bricks-design/references/architecture-truths.md +0 -132
  33. package/skills/bricks-design/references/avoiding-complexity.md +0 -91
  34. package/skills/bricks-design/references/design-critique.md +0 -195
  35. package/skills/bricks-design/references/design-languages.md +0 -265
  36. package/skills/bricks-design/references/performance.md +0 -116
  37. package/skills/bricks-design/references/presentation-and-slideshow.md +0 -137
  38. package/skills/bricks-design/references/translating-inputs.md +0 -152
  39. package/skills/bricks-design/references/variations-and-tweaks.md +0 -124
  40. package/skills/bricks-design/references/when-the-brief-is-branded.md +0 -284
  41. package/skills/bricks-design/references/when-the-brief-is-vague.md +0 -85
  42. package/skills/bricks-design/references/workflow.md +0 -134
  43. package/skills/bricks-ux/SKILL.md +0 -114
  44. package/skills/bricks-ux/references/accessibility.md +0 -162
  45. package/skills/bricks-ux/references/flow-states.md +0 -175
  46. package/skills/bricks-ux/references/interaction-archetypes.md +0 -189
  47. package/skills/bricks-ux/references/monitoring-screens.md +0 -153
  48. package/skills/bricks-ux/references/pressable-composition.md +0 -126
  49. package/skills/bricks-ux/references/user-journey.md +0 -168
  50. package/skills/bricks-ux/references/ux-critique.md +0 -256
  51. package/skills/rive-marketplace/SKILL.md +0 -99
  52. package/tools/__tests__/_cli-error.test.ts +0 -35
  53. package/tools/__tests__/_mcp-config.test.ts +0 -67
  54. package/tools/__tests__/pull.test.ts +0 -108
  55. package/tools/_cli-error.ts +0 -17
  56. package/tools/_edits-log.ts +0 -41
  57. package/tools/_git-author.ts +0 -37
  58. package/tools/_last-pushed-commit.ts +0 -28
  59. package/tools/_mcp-config.ts +0 -42
  60. package/tools/_shell.ts +0 -180
  61. package/tools/icons/.gitattributes +0 -1
  62. package/tools/icons/fa6pro-glyphmap.json +0 -4686
  63. package/tools/icons/fa6pro-meta.json +0 -1
  64. package/tools/mcp-env.ts +0 -13
  65. package/tools/mcp-tools/__tests__/data-calc-editing.test.js +0 -516
  66. package/tools/mcp-tools/__tests__/entry-editing.test.js +0 -866
  67. package/tools/mcp-tools/__tests__/huggingface.test.ts +0 -49
  68. package/tools/mcp-tools/__tests__/icons.test.ts +0 -21
  69. package/tools/mcp-tools/__tests__/mcp-env.test.js +0 -19
  70. package/tools/mcp-tools/_editing-helpers.ts +0 -98
  71. package/tools/mcp-tools/_verify.ts +0 -50
  72. package/tools/mcp-tools/compile.ts +0 -104
  73. package/tools/mcp-tools/data-calc-editing.ts +0 -1311
  74. package/tools/mcp-tools/entry-editing.ts +0 -2297
  75. package/tools/mcp-tools/huggingface.ts +0 -772
  76. package/tools/mcp-tools/icons.ts +0 -97
  77. package/tools/mcp-tools/lottie.ts +0 -102
  78. package/tools/mcp-tools/media.ts +0 -113
  79. package/tools/simulator-main.mjs +0 -488
  80. package/tools/simulator-preload.cjs +0 -16
  81. package/types/animation.d.ts +0 -116
  82. package/types/automation.d.ts +0 -231
  83. package/types/brick-base.d.ts +0 -80
  84. package/types/bricks/Camera.d.ts +0 -246
  85. package/types/bricks/Chart.d.ts +0 -372
  86. package/types/bricks/GenerativeMedia.d.ts +0 -290
  87. package/types/bricks/Icon.d.ts +0 -98
  88. package/types/bricks/Image.d.ts +0 -126
  89. package/types/bricks/Items.d.ts +0 -480
  90. package/types/bricks/Lottie.d.ts +0 -168
  91. package/types/bricks/Maps.d.ts +0 -262
  92. package/types/bricks/QrCode.d.ts +0 -117
  93. package/types/bricks/Rect.d.ts +0 -150
  94. package/types/bricks/RichText.d.ts +0 -131
  95. package/types/bricks/Rive.d.ts +0 -220
  96. package/types/bricks/Scene3D.d.ts +0 -676
  97. package/types/bricks/Sketch.d.ts +0 -256
  98. package/types/bricks/Slideshow.d.ts +0 -201
  99. package/types/bricks/Svg.d.ts +0 -99
  100. package/types/bricks/Text.d.ts +0 -148
  101. package/types/bricks/TextInput.d.ts +0 -242
  102. package/types/bricks/Video.d.ts +0 -242
  103. package/types/bricks/VideoStreaming.d.ts +0 -112
  104. package/types/bricks/WebRtcStream.d.ts +0 -65
  105. package/types/bricks/WebView.d.ts +0 -168
  106. package/types/bricks/index.d.ts +0 -23
  107. package/types/canvas.d.ts +0 -82
  108. package/types/common.d.ts +0 -141
  109. package/types/data-calc-command/base.d.ts +0 -57
  110. package/types/data-calc-command/collection.d.ts +0 -418
  111. package/types/data-calc-command/color.d.ts +0 -432
  112. package/types/data-calc-command/constant.d.ts +0 -50
  113. package/types/data-calc-command/datetime.d.ts +0 -147
  114. package/types/data-calc-command/file.d.ts +0 -129
  115. package/types/data-calc-command/index.d.ts +0 -13
  116. package/types/data-calc-command/iteratee.d.ts +0 -23
  117. package/types/data-calc-command/logictype.d.ts +0 -190
  118. package/types/data-calc-command/math.d.ts +0 -275
  119. package/types/data-calc-command/object.d.ts +0 -119
  120. package/types/data-calc-command/sandbox.d.ts +0 -66
  121. package/types/data-calc-command/string.d.ts +0 -407
  122. package/types/data-calc-script.d.ts +0 -21
  123. package/types/data-calc.d.ts +0 -12
  124. package/types/data.d.ts +0 -97
  125. package/types/generators/AlarmClock.d.ts +0 -110
  126. package/types/generators/Assistant.d.ts +0 -640
  127. package/types/generators/BleCentral.d.ts +0 -247
  128. package/types/generators/BlePeripheral.d.ts +0 -208
  129. package/types/generators/CanvasMap.d.ts +0 -74
  130. package/types/generators/CastlesPay.d.ts +0 -87
  131. package/types/generators/DataBank.d.ts +0 -160
  132. package/types/generators/File.d.ts +0 -432
  133. package/types/generators/GraphQl.d.ts +0 -132
  134. package/types/generators/Http.d.ts +0 -222
  135. package/types/generators/HttpServer.d.ts +0 -230
  136. package/types/generators/Information.d.ts +0 -103
  137. package/types/generators/Intent.d.ts +0 -168
  138. package/types/generators/Iterator.d.ts +0 -108
  139. package/types/generators/Keyboard.d.ts +0 -105
  140. package/types/generators/LlmAnthropicCompat.d.ts +0 -212
  141. package/types/generators/LlmAppleBuiltin.d.ts +0 -159
  142. package/types/generators/LlmGgml.d.ts +0 -903
  143. package/types/generators/LlmMediaTekNeuroPilot.d.ts +0 -235
  144. package/types/generators/LlmMlx.d.ts +0 -228
  145. package/types/generators/LlmOnnx.d.ts +0 -213
  146. package/types/generators/LlmOpenAiCompat.d.ts +0 -312
  147. package/types/generators/LlmQualcommAiEngine.d.ts +0 -247
  148. package/types/generators/Mcp.d.ts +0 -637
  149. package/types/generators/McpServer.d.ts +0 -289
  150. package/types/generators/MediaFlow.d.ts +0 -170
  151. package/types/generators/MqttBroker.d.ts +0 -141
  152. package/types/generators/MqttClient.d.ts +0 -141
  153. package/types/generators/Question.d.ts +0 -408
  154. package/types/generators/RealtimeTranscription.d.ts +0 -287
  155. package/types/generators/RerankerGgml.d.ts +0 -195
  156. package/types/generators/SerialPort.d.ts +0 -151
  157. package/types/generators/SoundPlayer.d.ts +0 -94
  158. package/types/generators/SoundRecorder.d.ts +0 -139
  159. package/types/generators/SpeechToTextGgml.d.ts +0 -424
  160. package/types/generators/SpeechToTextOnnx.d.ts +0 -236
  161. package/types/generators/SpeechToTextPlatform.d.ts +0 -85
  162. package/types/generators/SqLite.d.ts +0 -159
  163. package/types/generators/Step.d.ts +0 -107
  164. package/types/generators/SttAppleBuiltin.d.ts +0 -153
  165. package/types/generators/Tcp.d.ts +0 -126
  166. package/types/generators/TcpServer.d.ts +0 -147
  167. package/types/generators/TextToSpeechAppleBuiltin.d.ts +0 -127
  168. package/types/generators/TextToSpeechGgml.d.ts +0 -221
  169. package/types/generators/TextToSpeechOnnx.d.ts +0 -178
  170. package/types/generators/TextToSpeechOpenAiLike.d.ts +0 -121
  171. package/types/generators/ThermalPrinter.d.ts +0 -193
  172. package/types/generators/Tick.d.ts +0 -83
  173. package/types/generators/Udp.d.ts +0 -120
  174. package/types/generators/VadGgml.d.ts +0 -260
  175. package/types/generators/VadOnnx.d.ts +0 -231
  176. package/types/generators/VadTraditional.d.ts +0 -138
  177. package/types/generators/VectorStore.d.ts +0 -257
  178. package/types/generators/Watchdog.d.ts +0 -107
  179. package/types/generators/WebCrawler.d.ts +0 -103
  180. package/types/generators/WebRtc.d.ts +0 -181
  181. package/types/generators/WebSocket.d.ts +0 -148
  182. package/types/generators/index.d.ts +0 -57
  183. package/types/index.d.ts +0 -13
  184. package/types/subspace.d.ts +0 -60
  185. package/types/switch.d.ts +0 -51
  186. package/types/system.d.ts +0 -707
  187. package/utils/__tests__/calc.test.js +0 -25
  188. package/utils/__tests__/id.test.js +0 -154
  189. package/utils/calc.ts +0 -130
  190. package/utils/data.ts +0 -495
  191. package/utils/event-props.ts +0 -912
  192. package/utils/id.ts +0 -133
package/tools/pull.ts CHANGED
@@ -1,199 +1,4 @@
1
- import { mkdir, readdir, readFile, unlink, writeFile } from 'node:fs/promises'
2
- import { existsSync } from 'node:fs'
3
- import { dirname, join, relative } from 'node:path'
4
- import { format } from 'oxfmt'
5
- import { sh } from './_shell'
6
- import { extractCliErrorMessage } from './_cli-error'
7
- import { buildCommitArgs } from './_git-author'
8
- import { readLastPushedCommit, writeLastPushedCommit } from './_last-pushed-commit'
1
+ #!/usr/bin/env bun
2
+ import { forwardLegacyCtorTool } from './_forward.ts'
9
3
 
10
- // Directories whose .ts contents are entirely owned by the generator.
11
- // Anything under these dirs not present in the freshly pulled file list is an orphan.
12
- const ownedTsDirs = ['subspaces', 'automation-tests']
13
-
14
- async function walkTsFiles(dir: string, baseDir: string): Promise<string[]> {
15
- if (!existsSync(dir)) return []
16
- const result: string[] = []
17
- const entries = await readdir(dir, { withFileTypes: true })
18
- await Promise.all(
19
- entries.map(async (entry) => {
20
- const full = join(dir, entry.name)
21
- if (entry.isDirectory()) {
22
- result.push(...(await walkTsFiles(full, baseDir)))
23
- } else if (entry.isFile() && entry.name.endsWith('.ts')) {
24
- result.push(relative(baseDir, full))
25
- }
26
- }),
27
- )
28
- return result
29
- }
30
-
31
- const cwd = process.cwd()
32
- const args = process.argv.slice(2)
33
- const force = args.includes('--force') || args.includes('-f')
34
-
35
- // Check git status
36
- const { exitCode } = await sh`cd ${cwd} && git status`.nothrow()
37
- const isGitRepo = exitCode === 0
38
-
39
- if (isGitRepo) {
40
- const unstagedChanges = await sh`cd ${cwd} && git diff --name-only --diff-filter=ACMR`.text()
41
- if (unstagedChanges) {
42
- if (force) {
43
- console.log('Force mode: committing unstaged changes before pull...')
44
- await sh`cd ${cwd} && git add .`
45
- const preCommitArgs = await buildCommitArgs(cwd, [
46
- 'chore(force-pull): saved unstaged changes before pull',
47
- ])
48
- await sh`cd ${cwd} && git ${preCommitArgs}`
49
- } else {
50
- throw new Error('Unstaged changes found, please commit or stash your changes before pulling')
51
- }
52
- }
53
- } else {
54
- const confirmContinue = prompt(
55
- 'No git repository found, so it will not be safe to pull, continue? (y/n)',
56
- )
57
- if (confirmContinue !== 'y') throw new Error('Pull cancelled')
58
- }
59
-
60
- // Read application.json
61
- const app = JSON.parse(await readFile(`${cwd}/application.json`, 'utf8'))
62
-
63
- const isModule = app.type === 'module'
64
- const command = isModule ? 'module' : 'app'
65
-
66
- // Fetch project files using CLI
67
- console.log(`Pulling ${command} project (${app.id})...`)
68
- const result = await sh`bricks ${command} project-pull ${app.id} --json`.quiet().nothrow()
69
-
70
- if (result.exitCode !== 0) {
71
- const output = result.stderr.toString() || result.stdout.toString()
72
- throw new Error(extractCliErrorMessage(output, 'Pull failed'))
73
- }
74
-
75
- const { files, lastCommitId: serverLastCommitId } = JSON.parse(result.stdout.toString())
76
-
77
- // The locally-saved commit (recorded by deploy/push-config) is the
78
- // authoritative merge base for THIS clone — the server's value can be an
79
- // opaque nanoid (config-only updates) or point to a commit other clients
80
- // produced. Fall back to the server's value only if we have no local
81
- // record yet.
82
- const savedLocalCommitId = await readLastPushedCommit(cwd)
83
- const baseCommitId = savedLocalCommitId || serverLastCommitId
84
-
85
- const branchName = isModule
86
- ? 'BRICKS_PROJECT_try-pull-module'
87
- : 'BRICKS_PROJECT_try-pull-application'
88
-
89
- let landingBranch = ''
90
- if (isGitRepo && !force) {
91
- landingBranch = (await sh`cd ${cwd} && git branch --show-current`.text()).trim()
92
- if (!landingBranch) throw new Error('Cannot pull from a detached HEAD')
93
-
94
- console.log(`Checking commit ${baseCommitId}...`)
95
- const found = (await sh`cd ${cwd} && git rev-list -1 ${baseCommitId}`.nothrow().text())
96
- .trim()
97
- .match(/^[\da-f]{40}$/)
98
-
99
- const headCommitId = (await sh`cd ${cwd} && git rev-parse HEAD`.text()).trim()
100
-
101
- if (headCommitId === serverLastCommitId) throw new Error('Commit not changed')
102
-
103
- await sh`cd ${cwd} && git branch -D ${branchName}`.nothrow()
104
-
105
- // When the base commit isn't reachable in this clone (server stored a
106
- // nanoid, or the commit was pruned), fall back to forking from current
107
- // HEAD. The downstream merge into the starting branch collapses both paths
108
- // into the same result, just with different merge bases.
109
- if (found) {
110
- await sh`cd ${cwd} && git checkout -b ${branchName} ${baseCommitId}`.nothrow()
111
- } else {
112
- await sh`cd ${cwd} && git checkout -b ${branchName}`
113
- }
114
- }
115
-
116
- const oxfmtConfig = await readFile(`${cwd}/.oxfmtrc.json`, 'utf8')
117
- .then(JSON.parse)
118
- .catch(() => ({
119
- trailingComma: 'all',
120
- tabWidth: 2,
121
- semi: false,
122
- singleQuote: true,
123
- printWidth: 100,
124
- }))
125
-
126
- const expectedFiles = new Set(files.map((file: { name: string }) => file.name))
127
-
128
- // Remove orphan .ts files under generator-owned directories before writing.
129
- // File paths are produced by buildApplicationFiles and use forward slashes,
130
- // so normalise the walked paths the same way for comparison.
131
- const orphans: string[] = []
132
- await Promise.all(
133
- ownedTsDirs.map(async (dir) => {
134
- const existing = await walkTsFiles(join(cwd, dir), cwd)
135
- for (const file of existing) {
136
- const normalized = file.split(/[\\/]/).join('/')
137
- if (!expectedFiles.has(normalized)) orphans.push(normalized)
138
- }
139
- }),
140
- )
141
- await Promise.all(orphans.map((name) => unlink(`${cwd}/${name}`)))
142
-
143
- await Promise.all(
144
- files.map(async (file: { name: string; input: string; formatable?: boolean }) => {
145
- let content = file.input
146
- if (file.formatable) {
147
- const result = await format(file.name, file.input, oxfmtConfig)
148
- content = result.code
149
- }
150
- const target = join(cwd, file.name)
151
- await mkdir(dirname(target), { recursive: true })
152
- return writeFile(target, content)
153
- }),
154
- )
155
-
156
- if (isGitRepo) {
157
- await sh`cd ${cwd} && git add .`
158
- const hasChanges = !!(await sh`cd ${cwd} && git diff --cached --name-only`.text()).trim()
159
- if (hasChanges) {
160
- const commitMsg = force
161
- ? `chore(force-pull): apply force pull-${command}`
162
- : isModule
163
- ? 'chore(project): apply file changes from BRICKS module'
164
- : 'chore(project): apply file changes from BRICKS application'
165
- const commitArgs = await buildCommitArgs(cwd, [commitMsg])
166
- await sh`cd ${cwd} && git ${commitArgs}`
167
- }
168
- if (!force) {
169
- // Land the pulled commits on the starting branch with a single 3-way merge using
170
- // baseCommit as the merge base. The user doesn't have to manage a side
171
- // branch, and conflicts (if any) land in the working tree on the starting branch where
172
- // auto-compile surfaces them as typecheck errors to resolve in-place.
173
- await sh`cd ${cwd} && git checkout ${landingBranch}`
174
- const mergeResult = await sh`cd ${cwd} && git merge ${branchName} --no-edit`.nothrow()
175
- if (mergeResult.exitCode !== 0) {
176
- // Conflict markers are in the working tree — commit them so the tree
177
- // is clean for auto-compile to detect, leaving the resolution to the
178
- // user. Pre-commit hooks would reject markers (lint/format fail on
179
- // invalid syntax), so bypass them for this controlled case.
180
- await sh`cd ${cwd} && git add .`
181
- const conflictArgs = await buildCommitArgs(
182
- cwd,
183
- [`chore(project): merge with conflicts (resolve in ${landingBranch})`],
184
- ['--no-verify'],
185
- )
186
- await sh`cd ${cwd} && git ${conflictArgs}`
187
- }
188
- // The try-pull branch served its purpose; delete it so `git branch`
189
- // stays tidy. The next pull recreates it anyway (line 103).
190
- await sh`cd ${cwd} && git branch -D ${branchName}`.nothrow()
191
- }
192
- // Record the new sync point so a follow-up pull starts from the right base.
193
- const newHead = (await sh`cd ${cwd} && git rev-parse HEAD`.nothrow().text()).trim()
194
- if (newHead) await writeLastPushedCommit(cwd, newHead)
195
- }
196
-
197
- console.log(
198
- `${isModule ? 'Module' : 'App'} project pulled: ${files.length} files${orphans.length ? `, removed ${orphans.length} orphan .ts file${orphans.length === 1 ? '' : 's'}` : ''}${force ? ' (force)' : ''}`,
199
- )
4
+ forwardLegacyCtorTool('pull', import.meta.url)
@@ -1,114 +1,4 @@
1
- import { readFile, writeFile } from 'node:fs/promises'
2
- import { parseArgs } from 'util'
3
- import { sh } from './_shell'
4
- import { extractCliErrorMessage } from './_cli-error'
5
- import { buildCommitArgs } from './_git-author'
6
- import { writeLastPushedCommit } from './_last-pushed-commit'
1
+ #!/usr/bin/env bun
2
+ import { forwardLegacyCtorTool } from './_forward.ts'
7
3
 
8
- const cwd = process.cwd()
9
-
10
- const readJson = async (p: string) => JSON.parse(await readFile(p, 'utf8'))
11
-
12
- const {
13
- values: { 'auto-commit': autoCommit, 'no-check': noCheck, 'no-validate': noValidate, yes, help },
14
- } = parseArgs({
15
- args: process.argv.slice(2),
16
- options: {
17
- 'auto-commit': { type: 'boolean' },
18
- 'no-check': { type: 'boolean' },
19
- 'no-validate': { type: 'boolean' },
20
- yes: { type: 'boolean', short: 'y' },
21
- help: { type: 'boolean', short: 'h' },
22
- },
23
- allowPositionals: true,
24
- })
25
-
26
- if (help) {
27
- console.log(`Push compiled config to BRICKS without creating a release.
28
-
29
- Options:
30
- --auto-commit Auto-commit unstaged changes before pushing
31
- --no-check Skip the conflict guard (don't pass --last-commit-id)
32
- --no-validate Skip server-side config schema validation
33
- -y, --yes Skip all prompts
34
- -h, --help Show this help message`)
35
- process.exit(0)
36
- }
37
-
38
- // Detect git repo (mirrors deploy.ts)
39
- const { exitCode } = await sh`cd ${cwd} && git status`.quiet().nothrow()
40
- const isGitRepo = exitCode === 0
41
-
42
- if (!isGitRepo && !yes) {
43
- const confirmContinue = prompt('No git repository found, continue? (y/n)')
44
- if (confirmContinue !== 'y') throw new Error('Update cancelled')
45
- }
46
-
47
- // Read application.json + compiled config
48
- const app = await readJson(`${cwd}/application.json`)
49
- const config = await readJson(`${cwd}/.bricks/build/application-config.json`)
50
-
51
- // Handle unstaged changes the same way deploy.ts does.
52
- let commitId = ''
53
- let parentCommitId = ''
54
- if (isGitRepo) {
55
- const unstagedChanges = await sh`cd ${cwd} && git diff --name-only --diff-filter=ACMR`.text()
56
- if (unstagedChanges) {
57
- if (autoCommit) {
58
- // Capture the pre-commit HEAD so we can use it as the conflict-check
59
- // baseline (the server should still hold it from the prior deploy).
60
- parentCommitId = (await sh`cd ${cwd} && git rev-parse HEAD`.nothrow().text()).trim()
61
- await sh`cd ${cwd} && git add -A`
62
- const commitArgs = await buildCommitArgs(cwd, ['chore: update bricks config'])
63
- await sh`cd ${cwd} && git ${commitArgs}`
64
- } else {
65
- throw new Error('Unstaged changes found, please commit or stash your changes before updating')
66
- }
67
- }
68
- commitId = (await sh`cd ${cwd} && git rev-parse HEAD`.text()).trim()
69
- }
70
-
71
- // Auto-derive --last-commit-id for the server-side conflict guard.
72
- // - parent of the auto-commit (server still holds it from the prior deploy)
73
- // - current HEAD (clean tree — server should be at the same commit too)
74
- let lastCommitId: string | undefined
75
- if (!noCheck) {
76
- if (parentCommitId) lastCommitId = parentCommitId
77
- else if (commitId) lastCommitId = commitId
78
- }
79
-
80
- if (!yes) {
81
- const confirm = prompt('Are you sure you want to push the new config? (y/n)')
82
- if (confirm !== 'y') throw new Error('Update cancelled')
83
- }
84
-
85
- const isModule = app.type === 'module'
86
- const command = isModule ? 'module' : 'app'
87
-
88
- const updateConfig = {
89
- ...config,
90
- bricks_project_last_commit_id: commitId || undefined,
91
- }
92
- const configPath = `${cwd}/.bricks/build/push-config.json`
93
- await writeFile(configPath, JSON.stringify(updateConfig))
94
-
95
- const args = ['bricks', command, 'update', app.id, '-f', configPath, '--json']
96
- if (noValidate) args.push('--no-validate')
97
- if (lastCommitId) args.push('--last-commit-id', lastCommitId)
98
-
99
- const result = await sh`${args}`.quiet().nothrow()
100
-
101
- if (result.exitCode !== 0) {
102
- const output = result.stderr.toString() || result.stdout.toString()
103
- throw new Error(extractCliErrorMessage(output, 'Update failed'))
104
- }
105
-
106
- const output = JSON.parse(result.stdout.toString())
107
-
108
- // Record the commit we just pushed from so a later pull can use it as the
109
- // merge base regardless of what the server stores in
110
- // bricks_project_last_commit_id (which may be an opaque nanoid for
111
- // content-only edits).
112
- if (commitId) await writeLastPushedCommit(cwd, commitId)
113
-
114
- console.log(`${isModule ? 'Module' : 'App'} config updated: ${output.target?.name || app.name}`)
4
+ forwardLegacyCtorTool('push', import.meta.url)
@@ -1,150 +1,4 @@
1
- import { spawn } from 'node:child_process'
2
- import { readFile, writeFile } from 'node:fs/promises'
3
- import { watch, unlinkSync } from 'fs'
4
- import type { FSWatcher } from 'fs'
5
- import { createInterface } from 'node:readline'
6
- import { parseArgs } from 'util'
7
- import debounce from 'lodash/debounce'
8
- import { sh } from './_shell'
1
+ #!/usr/bin/env bun
2
+ import { forwardLegacyCtorTool } from './_forward.ts'
9
3
 
10
- const { values } = parseArgs({
11
- args: process.argv,
12
- options: {
13
- 'skip-typecheck': { type: 'boolean' },
14
- 'clear-cache': { type: 'boolean' },
15
- screenshot: { type: 'boolean' },
16
- 'screenshot-delay': { type: 'string' },
17
- 'screenshot-width': { type: 'string' },
18
- 'screenshot-height': { type: 'string' },
19
- 'screenshot-path': { type: 'string' },
20
- 'screenshot-no-headless': { type: 'boolean' },
21
- 'show-menu': { type: 'boolean' },
22
- 'test-id': { type: 'string' },
23
- 'test-title-like': { type: 'string' },
24
- 'no-keep-open': { type: 'boolean' },
25
- 'cdp-port': { type: 'string' },
26
- 'no-cdp': { type: 'boolean' },
27
- },
28
- strict: true,
29
- allowPositionals: true,
30
- })
31
-
32
- const cwd = process.cwd()
33
-
34
- const app = JSON.parse(await readFile(`${cwd}/application.json`, 'utf8'))
35
-
36
- const args: string[] = []
37
- if (values['clear-cache']) args.push('--clear-cache')
38
-
39
- let needWatcher = true
40
- if (values['screenshot']) {
41
- args.push(`--take-screenshot`)
42
- args.push(
43
- JSON.stringify({
44
- delay: Number(values['screenshot-delay']) || 1000,
45
- width: Number(values['screenshot-width']) || 600,
46
- height: Number(values['screenshot-height']) || 480,
47
- path: values['screenshot-path'] || `${cwd}/screenshot.jpg`,
48
- noHeadless: values['screenshot-no-headless'] ?? false,
49
- }),
50
- )
51
- needWatcher = !values['no-keep-open']
52
- }
53
-
54
- if (values['show-menu']) {
55
- args.push('--show-menu')
56
- }
57
-
58
- if (values['test-id']) {
59
- args.push('--test-id', values['test-id'])
60
- }
61
-
62
- if (values['test-title-like']) {
63
- args.push('--test-title-like', values['test-title-like'])
64
- }
65
-
66
- if (values['no-keep-open']) {
67
- args.push('--no-keep-open')
68
- }
69
-
70
- if (!values['no-cdp']) {
71
- const cdpPort = parseInt(values['cdp-port'] || '', 10) || 19852
72
- args.push('--cdp-port', String(cdpPort))
73
- }
74
-
75
- const useTypecheck = !values['skip-typecheck']
76
-
77
- const compile = async () => {
78
- if (useTypecheck) await sh`bun typecheck`
79
- await sh`bun compile.ts`
80
- }
81
-
82
- await compile()
83
-
84
- let watcher: FSWatcher | null = null
85
- if (needWatcher) {
86
- const compileDebounced = debounce(compile, 500)
87
- watcher = watch(`${cwd}/subspaces`, { recursive: true }, async (event, filename) => {
88
- console.log(`Detected ${event} in ${filename}`)
89
- compileDebounced()
90
- })
91
- }
92
-
93
- const devtoolsInfoPath = `${cwd}/.bricks/devtools.json`
94
-
95
- const cleanupDevtoolsInfo = () => {
96
- try {
97
- unlinkSync(devtoolsInfoPath)
98
- } catch {}
99
- }
100
-
101
- // Kill existing simulator process if devtools.json contains a stale pid
102
- try {
103
- const devtoolsInfo = JSON.parse(await readFile(devtoolsInfoPath, 'utf8'))
104
- if (devtoolsInfo.pid) {
105
- try {
106
- process.kill(devtoolsInfo.pid)
107
- } catch {}
108
- cleanupDevtoolsInfo()
109
- }
110
- } catch {}
111
-
112
- const proc = spawn(
113
- 'bunx',
114
- ['--bun', 'electron', `${import.meta.dirname}/simulator-main.mjs`, ...args],
115
- {
116
- env: { ...process.env, BRICKS_STAGE: app.stage || 'production' },
117
- stdio: ['inherit', 'pipe', 'inherit'],
118
- },
119
- )
120
-
121
- const rl = createInterface({ input: proc.stdout! })
122
-
123
- rl.on('line', (line) => {
124
- if (line.startsWith('[TEST_RESULT_TOON]')) {
125
- const toonData = line.replace('[TEST_RESULT_TOON]', '')
126
- console.log(toonData)
127
- return
128
- }
129
- if (!line) return
130
- // Detect CDP server startup from simulator-main output
131
- const cdpMatch = line.match(/^CDP server: ws:\/\/localhost:(\d+)/)
132
- if (cdpMatch) {
133
- const info = {
134
- port: parseInt(cdpMatch[1], 10),
135
- pid: proc.pid,
136
- address: 'localhost',
137
- name: `${app.name || 'Unnamed'} (CTOR Simulator)`,
138
- startedAt: new Date().toISOString(),
139
- }
140
- writeFile(devtoolsInfoPath, JSON.stringify(info, null, 2))
141
- }
142
- console.log(line)
143
- })
144
-
145
- await new Promise<void>((resolve) => {
146
- proc.on('close', () => resolve())
147
- })
148
-
149
- cleanupDevtoolsInfo()
150
- if (watcher) watcher.close()
4
+ forwardLegacyCtorTool('simulator', import.meta.url)
@@ -1,100 +0,0 @@
1
- import { computeConfigChange } from '../config-diff'
2
-
3
- describe('computeConfigChange', () => {
4
- test('ignores the volatile top-level title and update_timestamp', () => {
5
- const before = { title: 'App(1)', update_timestamp: 1, subspace_map: {} }
6
- const after = { title: 'App(2)', update_timestamp: 2, subspace_map: {} }
7
- expect(computeConfigChange(before, after)).toEqual({ status: 'ok', ops: [], opCount: 0 })
8
- })
9
-
10
- test('still diffs nested (subspace/brick) titles', () => {
11
- const before = { title: 'App(1)', subspace_map: { S: { title: 'Home' } } }
12
- const after = { title: 'App(2)', subspace_map: { S: { title: 'Start' } } }
13
- expect(computeConfigChange(before, after).ops).toEqual([
14
- { op: 'set', path: ['subspace_map', 'S', 'title'], value: 'Start' },
15
- ])
16
- })
17
-
18
- test('emits a leaf set for a scalar property change', () => {
19
- const wrap = (url) => ({ subspace_map: { S: { brick_map: { B: { property: { url } } } } } })
20
- expect(computeConfigChange(wrap('a'), wrap('b')).ops).toEqual([
21
- { op: 'set', path: ['subspace_map', 'S', 'brick_map', 'B', 'property', 'url'], value: 'b' },
22
- ])
23
- })
24
-
25
- test('added key → set, removed key → unset', () => {
26
- const change = computeConfigChange({ m: { a: 1 } }, { m: { b: 2 } })
27
- expect(change.opCount).toBe(2)
28
- expect(change.ops).toContainEqual({ op: 'unset', path: ['m', 'a'] })
29
- expect(change.ops).toContainEqual({ op: 'set', path: ['m', 'b'], value: 2 })
30
- })
31
-
32
- test('equal-length arrays diff element-wise', () => {
33
- const before = { items: [{ x: 1 }, { x: 2 }] }
34
- const after = { items: [{ x: 1 }, { x: 9 }] }
35
- expect(computeConfigChange(before, after).ops).toEqual([
36
- { op: 'set', path: ['items', 1, 'x'], value: 9 },
37
- ])
38
- })
39
-
40
- test('length-changed arrays emit a single whole-array set', () => {
41
- const before = { items: [1, 2] }
42
- const after = { items: [1, 2, 3] }
43
- expect(computeConfigChange(before, after).ops).toEqual([
44
- { op: 'set', path: ['items'], value: [1, 2, 3] },
45
- ])
46
- })
47
-
48
- test('identical configs produce no ops', () => {
49
- const config = { subspace_map: { S: { brick_map: { B: { property: { a: 1, b: [1, 2] } } } } } }
50
- expect(computeConfigChange(config, structuredClone(config))).toEqual({
51
- status: 'ok',
52
- ops: [],
53
- opCount: 0,
54
- })
55
- })
56
-
57
- test('ignores object fields omitted by JSON.stringify', () => {
58
- const before = { subspace_map: { S: { title: 'Home' } } }
59
- const after = {
60
- subspace_map: {
61
- S: {
62
- title: 'Home',
63
- description: undefined,
64
- },
65
- },
66
- }
67
- expect(computeConfigChange(before, after)).toEqual({ status: 'ok', ops: [], opCount: 0 })
68
- })
69
-
70
- test('emits added values in JSON-compatible shape', () => {
71
- const before = { m: {} }
72
- const after = {
73
- m: {
74
- nested: {
75
- keep: 1,
76
- drop: undefined,
77
- },
78
- items: [undefined, 2],
79
- },
80
- }
81
- const change = computeConfigChange(before, after)
82
- expect(change.ops).toEqual([
83
- { op: 'set', path: ['m', 'nested'], value: { keep: 1 } },
84
- { op: 'set', path: ['m', 'items'], value: [null, 2] },
85
- ])
86
- })
87
-
88
- test('compares array undefined entries like JSON nulls', () => {
89
- expect(computeConfigChange({ items: [null] }, { items: [undefined] })).toEqual({
90
- status: 'ok',
91
- ops: [],
92
- opCount: 0,
93
- })
94
- })
95
-
96
- test('reports no_baseline / unavailable for missing sides', () => {
97
- expect(computeConfigChange(null, { a: 1 })).toEqual({ status: 'no_baseline' })
98
- expect(computeConfigChange({ a: 1 }, null)).toEqual({ status: 'unavailable' })
99
- })
100
- })