@fugood/bricks-project 2.24.0 → 2.24.1-beta.0
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/index.ts +2 -2
- package/package.json +3 -2
- package/package.json.bak +3 -2
- package/skills/bricks-ctor/rules/data-calculation.md +2 -1
- package/tools/_git-author.ts +29 -0
- package/tools/_shell.ts +173 -0
- package/tools/deploy.ts +34 -20
- package/tools/mcp-tools/compile.ts +6 -5
- package/tools/mcp-tools/huggingface.ts +1 -1
- package/tools/mcp-tools/media.ts +2 -2
- package/tools/postinstall.ts +10 -4
- package/tools/preview.ts +41 -45
- package/tools/pull.ts +25 -19
package/compile/index.ts
CHANGED
|
@@ -1274,6 +1274,6 @@ export const compile = async (app: Application) => {
|
|
|
1274
1274
|
}
|
|
1275
1275
|
|
|
1276
1276
|
export const checkConfig = async (configPath: string) => {
|
|
1277
|
-
const {
|
|
1278
|
-
await
|
|
1277
|
+
const { sh } = await import('../tools/_shell')
|
|
1278
|
+
await sh`bricks app check-config ${configPath}`
|
|
1279
1279
|
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fugood/bricks-project",
|
|
3
|
-
"version": "2.24.0",
|
|
3
|
+
"version": "2.24.1-beta.0",
|
|
4
4
|
"main": "index.ts",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"typecheck": "tsc --noEmit",
|
|
7
7
|
"build": "bun scripts/build.js"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@fugood/bricks-cli": "^2.24.0",
|
|
10
|
+
"@fugood/bricks-cli": "^2.24.1-beta.0",
|
|
11
11
|
"@huggingface/gguf": "^0.3.2",
|
|
12
12
|
"@iarna/toml": "^3.0.0",
|
|
13
13
|
"@modelcontextprotocol/sdk": "^1.15.0",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"acorn": "^8.13.0",
|
|
19
19
|
"escodegen": "2.1.0",
|
|
20
20
|
"fuse.js": "^7.0.0",
|
|
21
|
+
"json5": "^2.0.1",
|
|
21
22
|
"lodash": "^4.17.4",
|
|
22
23
|
"uuid": "^8.3.1"
|
|
23
24
|
},
|
package/package.json.bak
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fugood/bricks-ctor",
|
|
3
|
-
"version": "2.24.0",
|
|
3
|
+
"version": "2.24.1-beta.0",
|
|
4
4
|
"main": "index.ts",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"typecheck": "tsc --noEmit",
|
|
7
7
|
"build": "bun scripts/build.js"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@fugood/bricks-cli": "^2.24.0",
|
|
10
|
+
"@fugood/bricks-cli": "^2.24.1-beta.0",
|
|
11
11
|
"@huggingface/gguf": "^0.3.2",
|
|
12
12
|
"@iarna/toml": "^3.0.0",
|
|
13
13
|
"@modelcontextprotocol/sdk": "^1.15.0",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"acorn": "^8.13.0",
|
|
19
19
|
"escodegen": "2.1.0",
|
|
20
20
|
"fuse.js": "^7.0.0",
|
|
21
|
+
"json5": "^2.0.1",
|
|
21
22
|
"lodash": "^4.17.4",
|
|
22
23
|
"uuid": "^8.3.1"
|
|
23
24
|
},
|
|
@@ -27,7 +27,8 @@ const calculation: DataCalculationScript = {
|
|
|
27
27
|
}).format(price)
|
|
28
28
|
`,
|
|
29
29
|
// Or load from file (preferred for longer scripts)
|
|
30
|
-
//
|
|
30
|
+
// import { readFile } from 'node:fs/promises'
|
|
31
|
+
// code: await readFile(new URL('./format-price.sandbox.js', import.meta.url), 'utf8'),
|
|
31
32
|
inputs: [
|
|
32
33
|
{ key: 'price', data: () => priceData, trigger: true },
|
|
33
34
|
{ key: 'currency', data: () => currencyData, trigger: false },
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { sh } from './_shell'
|
|
2
|
+
|
|
3
|
+
const CTOR_NAME = 'CTOR'
|
|
4
|
+
const CTOR_EMAIL = 'ctor@bricks.tools'
|
|
5
|
+
const CTOR_COAUTHOR_TRAILER = `Co-authored-by: ${CTOR_NAME} <${CTOR_EMAIL}>`
|
|
6
|
+
|
|
7
|
+
async function hasGitIdentity(cwd: string): Promise<boolean> {
|
|
8
|
+
const out = (await sh`cd ${cwd} && git config user.email`.quiet().nothrow().text()).trim()
|
|
9
|
+
return !!out
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Build `git ...` args for a commit attributed to CTOR.
|
|
13
|
+
// If the machine has a git identity configured (any config level), the user
|
|
14
|
+
// remains the main author and CTOR is added via a Co-authored-by trailer.
|
|
15
|
+
// If no identity is configured (typical for non-tech users), CTOR becomes the
|
|
16
|
+
// main author via per-command `-c user.name/-c user.email` overrides so the
|
|
17
|
+
// user's git config is left untouched.
|
|
18
|
+
export async function buildCommitArgs(cwd: string, messages: string[]): Promise<string[]> {
|
|
19
|
+
const hasIdentity = await hasGitIdentity(cwd)
|
|
20
|
+
const prefix = hasIdentity
|
|
21
|
+
? []
|
|
22
|
+
: ['-c', `user.name=${CTOR_NAME}`, '-c', `user.email=${CTOR_EMAIL}`]
|
|
23
|
+
const msgArgs: string[] = []
|
|
24
|
+
for (const m of messages) msgArgs.push('-m', m)
|
|
25
|
+
if (hasIdentity) msgArgs.push('-m', CTOR_COAUTHOR_TRAILER)
|
|
26
|
+
return [...prefix, 'commit', ...msgArgs]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export { CTOR_NAME, CTOR_EMAIL, CTOR_COAUTHOR_TRAILER }
|
package/tools/_shell.ts
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { spawn, type StdioOptions } from 'node:child_process'
|
|
2
|
+
|
|
3
|
+
// Template-literal shell helper replacing `$` from 'bun'. Only covers the
|
|
4
|
+
// ergonomics actually used in this package (see call sites in tools/*.ts).
|
|
5
|
+
//
|
|
6
|
+
// Usage:
|
|
7
|
+
// await sh`git status` // inherit stdio, throw on nonzero
|
|
8
|
+
// await sh`cd ${cwd} && git status`.quiet().nothrow()
|
|
9
|
+
// const out = await sh`git rev-parse HEAD`.text()
|
|
10
|
+
// await sh`bunx bricks ${args}`.cwd(dir).text()
|
|
11
|
+
//
|
|
12
|
+
// Notes:
|
|
13
|
+
// - Interpolated values are treated as single argv tokens (arrays expand to
|
|
14
|
+
// multiple tokens). Whitespace inside interpolated strings is preserved.
|
|
15
|
+
// - A leading `cd <path> && <cmd...>` in the literal is lifted into the
|
|
16
|
+
// subprocess cwd — no shell is spawned, so no quoting concerns.
|
|
17
|
+
// - Commands that chain with `&&` beyond the `cd` prefix aren't supported;
|
|
18
|
+
// split them into multiple `sh` awaits at the call site.
|
|
19
|
+
|
|
20
|
+
export interface ShResult {
|
|
21
|
+
exitCode: number
|
|
22
|
+
stdout: Buffer
|
|
23
|
+
stderr: Buffer
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type Mode = 'default' | 'text' | 'quiet'
|
|
27
|
+
|
|
28
|
+
class Sh implements PromiseLike<ShResult> {
|
|
29
|
+
private _cwd: string | undefined
|
|
30
|
+
private _mode: Mode = 'default'
|
|
31
|
+
private _throw = true
|
|
32
|
+
private _promise: Promise<ShResult> | null = null
|
|
33
|
+
|
|
34
|
+
constructor(private readonly args: string[]) {}
|
|
35
|
+
|
|
36
|
+
cwd(dir: string): this {
|
|
37
|
+
this._cwd = dir
|
|
38
|
+
return this
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
quiet(): this {
|
|
42
|
+
this._mode = 'quiet'
|
|
43
|
+
return this
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
nothrow(): this {
|
|
47
|
+
this._throw = false
|
|
48
|
+
return this
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async text(): Promise<string> {
|
|
52
|
+
// If no explicit quiet was requested, capture stdout/stderr without
|
|
53
|
+
// forwarding them to the parent's streams (matches Bun's `.text()`).
|
|
54
|
+
if (this._mode === 'default') this._mode = 'text'
|
|
55
|
+
const r = await this.run()
|
|
56
|
+
return r.stdout.toString('utf8')
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
then<TResult1 = ShResult, TResult2 = never>(
|
|
60
|
+
onfulfilled?: ((value: ShResult) => TResult1 | PromiseLike<TResult1>) | null,
|
|
61
|
+
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null,
|
|
62
|
+
): Promise<TResult1 | TResult2> {
|
|
63
|
+
return this.run().then(onfulfilled, onrejected)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private run(): Promise<ShResult> {
|
|
67
|
+
if (!this._promise) this._promise = this.exec()
|
|
68
|
+
return this._promise
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private async exec(): Promise<ShResult> {
|
|
72
|
+
const [cmd, ...rest] = this.args
|
|
73
|
+
if (!cmd) throw new Error('sh: empty command')
|
|
74
|
+
|
|
75
|
+
const stdio: StdioOptions =
|
|
76
|
+
this._mode === 'default'
|
|
77
|
+
? ['inherit', 'pipe', 'pipe']
|
|
78
|
+
: this._mode === 'quiet'
|
|
79
|
+
? ['ignore', 'pipe', 'pipe']
|
|
80
|
+
: ['inherit', 'pipe', 'pipe']
|
|
81
|
+
|
|
82
|
+
const proc = spawn(cmd, rest, { cwd: this._cwd, stdio })
|
|
83
|
+
|
|
84
|
+
const outChunks: Buffer[] = []
|
|
85
|
+
const errChunks: Buffer[] = []
|
|
86
|
+
proc.stdout?.on('data', (c: Buffer) => {
|
|
87
|
+
outChunks.push(c)
|
|
88
|
+
if (this._mode === 'default') process.stdout.write(c)
|
|
89
|
+
})
|
|
90
|
+
proc.stderr?.on('data', (c: Buffer) => {
|
|
91
|
+
errChunks.push(c)
|
|
92
|
+
if (this._mode === 'default') process.stderr.write(c)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
const exitCode: number = await new Promise((resolve, reject) => {
|
|
96
|
+
proc.on('error', reject)
|
|
97
|
+
proc.on('close', (code) => resolve(code ?? 0))
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
const stdout = Buffer.concat(outChunks)
|
|
101
|
+
const stderr = Buffer.concat(errChunks)
|
|
102
|
+
const result: ShResult = { exitCode, stdout, stderr }
|
|
103
|
+
|
|
104
|
+
if (exitCode !== 0 && this._throw) {
|
|
105
|
+
const err: any = new Error(`sh: command failed with exit ${exitCode}: ${this.args.join(' ')}`)
|
|
106
|
+
err.exitCode = exitCode
|
|
107
|
+
err.stdout = stdout
|
|
108
|
+
err.stderr = stderr
|
|
109
|
+
throw err
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return result
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const WHITESPACE = /\s/
|
|
117
|
+
|
|
118
|
+
function tokenize(
|
|
119
|
+
strings: TemplateStringsArray,
|
|
120
|
+
values: unknown[],
|
|
121
|
+
): { tokens: string[]; initialCwd?: string } {
|
|
122
|
+
const tokens: string[] = []
|
|
123
|
+
let current = ''
|
|
124
|
+
let inToken = false
|
|
125
|
+
|
|
126
|
+
const commit = () => {
|
|
127
|
+
if (inToken) {
|
|
128
|
+
tokens.push(current)
|
|
129
|
+
current = ''
|
|
130
|
+
inToken = false
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const append = (s: string) => {
|
|
135
|
+
current += s
|
|
136
|
+
inToken = true
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const appendValue = (val: unknown) => {
|
|
140
|
+
if (val == null) return
|
|
141
|
+
if (Array.isArray(val)) {
|
|
142
|
+
val.forEach((item, i) => {
|
|
143
|
+
if (i > 0) commit()
|
|
144
|
+
append(String(item))
|
|
145
|
+
})
|
|
146
|
+
return
|
|
147
|
+
}
|
|
148
|
+
append(String(val))
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
strings.forEach((literal, i) => {
|
|
152
|
+
for (let c = 0; c < literal.length; c++) {
|
|
153
|
+
const ch = literal[c]
|
|
154
|
+
if (WHITESPACE.test(ch)) commit()
|
|
155
|
+
else append(ch)
|
|
156
|
+
}
|
|
157
|
+
if (i < values.length) appendValue(values[i])
|
|
158
|
+
})
|
|
159
|
+
commit()
|
|
160
|
+
|
|
161
|
+
// Lift `cd <path> && <cmd...>` prefix into subprocess cwd
|
|
162
|
+
if (tokens.length >= 3 && tokens[0] === 'cd' && tokens[2] === '&&') {
|
|
163
|
+
return { tokens: tokens.slice(3), initialCwd: tokens[1] }
|
|
164
|
+
}
|
|
165
|
+
return { tokens }
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function sh(strings: TemplateStringsArray, ...values: unknown[]): Sh {
|
|
169
|
+
const { tokens, initialCwd } = tokenize(strings, values)
|
|
170
|
+
const s = new Sh(tokens)
|
|
171
|
+
if (initialCwd) s.cwd(initialCwd)
|
|
172
|
+
return s
|
|
173
|
+
}
|
package/tools/deploy.ts
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { access, readFile, writeFile } from 'node:fs/promises'
|
|
2
2
|
import { parseArgs } from 'util'
|
|
3
|
+
import { sh } from './_shell'
|
|
4
|
+
import { buildCommitArgs } from './_git-author'
|
|
3
5
|
|
|
4
6
|
const cwd = process.cwd()
|
|
5
7
|
|
|
8
|
+
const exists = async (p: string) => {
|
|
9
|
+
try {
|
|
10
|
+
await access(p)
|
|
11
|
+
return true
|
|
12
|
+
} catch {
|
|
13
|
+
return false
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const readJson = async (p: string) => JSON.parse(await readFile(p, 'utf8'))
|
|
18
|
+
|
|
6
19
|
const {
|
|
7
20
|
values: {
|
|
8
21
|
changelogs: changelogsArg,
|
|
@@ -14,7 +27,7 @@ const {
|
|
|
14
27
|
help,
|
|
15
28
|
},
|
|
16
29
|
} = parseArgs({
|
|
17
|
-
args:
|
|
30
|
+
args: process.argv.slice(2),
|
|
18
31
|
options: {
|
|
19
32
|
changelogs: { type: 'string' },
|
|
20
33
|
'changelogs-file': { type: 'string' },
|
|
@@ -40,7 +53,7 @@ if (help) {
|
|
|
40
53
|
}
|
|
41
54
|
|
|
42
55
|
// Check git status
|
|
43
|
-
const { exitCode } = await
|
|
56
|
+
const { exitCode } = await sh`cd ${cwd} && git status`.quiet().nothrow()
|
|
44
57
|
const isGitRepo = exitCode === 0
|
|
45
58
|
|
|
46
59
|
if (!isGitRepo && !yes) {
|
|
@@ -49,30 +62,30 @@ if (!isGitRepo && !yes) {
|
|
|
49
62
|
}
|
|
50
63
|
|
|
51
64
|
// Read application.json
|
|
52
|
-
const app = await
|
|
53
|
-
const config = await
|
|
65
|
+
const app = await readJson(`${cwd}/application.json`)
|
|
66
|
+
const config = await readJson(`${cwd}/.bricks/build/application-config.json`)
|
|
54
67
|
|
|
55
68
|
// Resolve version: explicit flag > auto-bump > package.json
|
|
56
|
-
const
|
|
57
|
-
const pkgExists = await
|
|
69
|
+
const pkgPath = `${cwd}/package.json`
|
|
70
|
+
const pkgExists = await exists(pkgPath)
|
|
58
71
|
let version: string | undefined
|
|
59
72
|
|
|
60
73
|
if (versionArg) {
|
|
61
74
|
version = versionArg
|
|
62
75
|
if (pkgExists) {
|
|
63
|
-
const pkg = await
|
|
76
|
+
const pkg = await readJson(pkgPath)
|
|
64
77
|
pkg.version = version
|
|
65
|
-
await
|
|
78
|
+
await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n')
|
|
66
79
|
}
|
|
67
80
|
} else if (autoVersion && pkgExists) {
|
|
68
|
-
const pkg = await
|
|
81
|
+
const pkg = await readJson(pkgPath)
|
|
69
82
|
const parts = (pkg.version || '0.0.0').split('.')
|
|
70
83
|
parts[2] = String(Number(parts[2] || 0) + 1)
|
|
71
84
|
version = parts.join('.')
|
|
72
85
|
pkg.version = version
|
|
73
|
-
await
|
|
86
|
+
await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n')
|
|
74
87
|
} else {
|
|
75
|
-
version = pkgExists ? (await
|
|
88
|
+
version = pkgExists ? (await readJson(pkgPath)).version : undefined
|
|
76
89
|
}
|
|
77
90
|
|
|
78
91
|
// Get changelog from flag or file
|
|
@@ -80,11 +93,10 @@ let changelogs = ''
|
|
|
80
93
|
if (changelogsArg) {
|
|
81
94
|
changelogs = changelogsArg
|
|
82
95
|
} else if (changelogsFile) {
|
|
83
|
-
|
|
84
|
-
if (!(await file.exists())) {
|
|
96
|
+
if (!(await exists(changelogsFile))) {
|
|
85
97
|
throw new Error(`Changelogs file not found: ${changelogsFile}`)
|
|
86
98
|
}
|
|
87
|
-
changelogs = await
|
|
99
|
+
changelogs = await readFile(changelogsFile, 'utf8')
|
|
88
100
|
} else if (!yes) {
|
|
89
101
|
changelogs = prompt('Enter changelogs (optional, press Enter to skip):') || ''
|
|
90
102
|
}
|
|
@@ -92,19 +104,21 @@ if (changelogsArg) {
|
|
|
92
104
|
// Handle unstaged changes
|
|
93
105
|
let commitId = ''
|
|
94
106
|
if (isGitRepo) {
|
|
95
|
-
const unstagedChanges = await
|
|
107
|
+
const unstagedChanges = await sh`cd ${cwd} && git diff --name-only --diff-filter=ACMR`.text()
|
|
96
108
|
if (unstagedChanges) {
|
|
97
109
|
if (autoCommit) {
|
|
98
110
|
const commitMsg = `chore: release ${version || 'new version'}`
|
|
99
111
|
const commitBody = changelogs || '[no changelogs]'
|
|
100
|
-
await
|
|
112
|
+
await sh`cd ${cwd} && git add -A`
|
|
113
|
+
const commitArgs = await buildCommitArgs(cwd, [commitMsg, commitBody])
|
|
114
|
+
await sh`cd ${cwd} && git ${commitArgs}`
|
|
101
115
|
} else {
|
|
102
116
|
throw new Error(
|
|
103
117
|
'Unstaged changes found, please commit or stash your changes before deploying',
|
|
104
118
|
)
|
|
105
119
|
}
|
|
106
120
|
}
|
|
107
|
-
commitId = (await
|
|
121
|
+
commitId = (await sh`cd ${cwd} && git rev-parse HEAD`.text()).trim()
|
|
108
122
|
}
|
|
109
123
|
|
|
110
124
|
// Ask for confirmation
|
|
@@ -123,7 +137,7 @@ const releaseConfig = {
|
|
|
123
137
|
bricks_project_last_commit_id: commitId || undefined,
|
|
124
138
|
}
|
|
125
139
|
const configPath = `${cwd}/.bricks/build/release-config.json`
|
|
126
|
-
await
|
|
140
|
+
await writeFile(configPath, JSON.stringify(releaseConfig))
|
|
127
141
|
|
|
128
142
|
const args = ['bricks', command, 'release', app.id, '-c', configPath, '--json']
|
|
129
143
|
|
|
@@ -135,7 +149,7 @@ if (changelogs) {
|
|
|
135
149
|
args.push('--changelogs', changelogs)
|
|
136
150
|
}
|
|
137
151
|
|
|
138
|
-
const result = await
|
|
152
|
+
const result = await sh`${args}`.quiet().nothrow()
|
|
139
153
|
|
|
140
154
|
if (result.exitCode !== 0) {
|
|
141
155
|
const output = result.stderr.toString() || result.stdout.toString()
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
2
|
+
import { readFile } from 'node:fs/promises'
|
|
2
3
|
import { z } from 'zod'
|
|
3
|
-
import {
|
|
4
|
+
import { sh } from '../_shell'
|
|
4
5
|
|
|
5
6
|
export function register(server: McpServer, projectDir: string) {
|
|
6
7
|
const { dirname } = import.meta
|
|
@@ -9,7 +10,7 @@ export function register(server: McpServer, projectDir: string) {
|
|
|
9
10
|
let log = ''
|
|
10
11
|
try {
|
|
11
12
|
log += 'Type checking & Compiling...'
|
|
12
|
-
log += await
|
|
13
|
+
log += await sh`bun compile`.cwd(projectDir).text()
|
|
13
14
|
} catch (err) {
|
|
14
15
|
log += `${err.stdout.toString()}\n${err.stderr.toString()}`
|
|
15
16
|
}
|
|
@@ -60,7 +61,7 @@ export function register(server: McpServer, projectDir: string) {
|
|
|
60
61
|
]
|
|
61
62
|
if (testId) args.push('--test-id', testId)
|
|
62
63
|
if (testTitleLike) args.push('--test-title-like', testTitleLike)
|
|
63
|
-
log = await
|
|
64
|
+
log = await sh`bunx --bun electron ${toolsDir}/preview-main.mjs ${args}`
|
|
64
65
|
.cwd(projectDir)
|
|
65
66
|
.text()
|
|
66
67
|
} catch (err) {
|
|
@@ -70,8 +71,8 @@ export function register(server: McpServer, projectDir: string) {
|
|
|
70
71
|
let screenshotBase64: string | null = null
|
|
71
72
|
if (!error && responseImage) {
|
|
72
73
|
const toolsDir = `${dirname}/..`
|
|
73
|
-
const screenshot = await
|
|
74
|
-
screenshotBase64 =
|
|
74
|
+
const screenshot = await readFile(`${toolsDir}/screenshot.jpg`)
|
|
75
|
+
screenshotBase64 = screenshot.toString('base64')
|
|
75
76
|
}
|
|
76
77
|
const content: Array<
|
|
77
78
|
{ type: 'text'; text: string } | { type: 'image'; data: string; mimeType: string }
|
package/tools/mcp-tools/media.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
2
2
|
import { z } from 'zod'
|
|
3
|
-
import {
|
|
3
|
+
import { sh } from '../_shell'
|
|
4
4
|
|
|
5
5
|
const runBricks = async (projectDir: string, ...args: string[]) => {
|
|
6
6
|
try {
|
|
7
|
-
return await
|
|
7
|
+
return await sh`bunx bricks ${args}`.cwd(projectDir).text()
|
|
8
8
|
} catch (err: any) {
|
|
9
9
|
throw new Error(err.stderr?.toString() || err.message)
|
|
10
10
|
}
|
package/tools/postinstall.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { $ } from 'bun'
|
|
2
1
|
import {
|
|
3
2
|
cp,
|
|
4
3
|
lstat,
|
|
@@ -53,8 +52,15 @@ if (skipCopyProject) {
|
|
|
53
52
|
} else {
|
|
54
53
|
const libFiles = ['types', 'utils', 'index.ts']
|
|
55
54
|
|
|
56
|
-
|
|
57
|
-
await
|
|
55
|
+
const ctorDir = path.join(cwd, 'ctor')
|
|
56
|
+
await mkdir(ctorDir, { recursive: true })
|
|
57
|
+
await Promise.all(
|
|
58
|
+
libFiles.map((file) =>
|
|
59
|
+
cp(path.join(import.meta.dirname, '..', file), path.join(ctorDir, file), {
|
|
60
|
+
recursive: true,
|
|
61
|
+
}),
|
|
62
|
+
),
|
|
63
|
+
)
|
|
58
64
|
console.log('Copied files to ctor/')
|
|
59
65
|
}
|
|
60
66
|
|
|
@@ -175,7 +181,7 @@ const ensureCompatibilitySkillLink = async (linkPath: string, targetDir: string)
|
|
|
175
181
|
}
|
|
176
182
|
|
|
177
183
|
const setupSkills = async () => {
|
|
178
|
-
const packageSkillsDir =
|
|
184
|
+
const packageSkillsDir = path.join(import.meta.dirname, '..', 'skills')
|
|
179
185
|
await mkdir(projectSkillsDir, { recursive: true })
|
|
180
186
|
await copyMissingSkills(packageSkillsDir, projectSkillsDir)
|
|
181
187
|
|
package/tools/preview.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawn } from 'node:child_process'
|
|
2
|
+
import { readFile, writeFile } from 'node:fs/promises'
|
|
2
3
|
import { watch, unlinkSync } from 'fs'
|
|
3
4
|
import type { FSWatcher } from 'fs'
|
|
5
|
+
import { createInterface } from 'node:readline'
|
|
4
6
|
import { parseArgs } from 'util'
|
|
5
7
|
import debounce from 'lodash/debounce'
|
|
8
|
+
import { sh } from './_shell'
|
|
6
9
|
|
|
7
10
|
const { values } = parseArgs({
|
|
8
|
-
args:
|
|
11
|
+
args: process.argv,
|
|
9
12
|
options: {
|
|
10
13
|
'skip-typecheck': { type: 'boolean' },
|
|
11
14
|
'clear-cache': { type: 'boolean' },
|
|
@@ -28,7 +31,7 @@ const { values } = parseArgs({
|
|
|
28
31
|
|
|
29
32
|
const cwd = process.cwd()
|
|
30
33
|
|
|
31
|
-
const app = await
|
|
34
|
+
const app = JSON.parse(await readFile(`${cwd}/application.json`, 'utf8'))
|
|
32
35
|
|
|
33
36
|
const args: string[] = []
|
|
34
37
|
if (values['clear-cache']) args.push('--clear-cache')
|
|
@@ -72,8 +75,8 @@ if (!values['no-cdp']) {
|
|
|
72
75
|
const useTypecheck = !values['skip-typecheck']
|
|
73
76
|
|
|
74
77
|
const compile = async () => {
|
|
75
|
-
if (useTypecheck) await
|
|
76
|
-
await
|
|
78
|
+
if (useTypecheck) await sh`bun typecheck`
|
|
79
|
+
await sh`bun compile.ts`
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
await compile()
|
|
@@ -97,7 +100,7 @@ const cleanupDevtoolsInfo = () => {
|
|
|
97
100
|
|
|
98
101
|
// Kill existing preview process if devtools.json contains a stale pid
|
|
99
102
|
try {
|
|
100
|
-
const devtoolsInfo = await
|
|
103
|
+
const devtoolsInfo = JSON.parse(await readFile(devtoolsInfoPath, 'utf8'))
|
|
101
104
|
if (devtoolsInfo.pid) {
|
|
102
105
|
try {
|
|
103
106
|
process.kill(devtoolsInfo.pid)
|
|
@@ -106,49 +109,42 @@ try {
|
|
|
106
109
|
}
|
|
107
110
|
} catch {}
|
|
108
111
|
|
|
109
|
-
const proc =
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
112
|
+
const proc = spawn(
|
|
113
|
+
'bunx',
|
|
114
|
+
['--bun', 'electron', `${import.meta.dirname}/preview-main.mjs`, ...args],
|
|
115
|
+
{
|
|
116
|
+
env: { ...process.env, BRICKS_STAGE: app.stage || 'production' },
|
|
117
|
+
stdio: ['inherit', 'pipe', 'inherit'],
|
|
118
|
+
},
|
|
119
|
+
)
|
|
114
120
|
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (cdpMatch) {
|
|
134
|
-
const info = {
|
|
135
|
-
port: parseInt(cdpMatch[1], 10),
|
|
136
|
-
pid: proc.pid,
|
|
137
|
-
address: 'localhost',
|
|
138
|
-
name: `${app.name || 'Unnamed'} (CTOR Preview)`,
|
|
139
|
-
startedAt: new Date().toISOString(),
|
|
140
|
-
}
|
|
141
|
-
Bun.write(devtoolsInfoPath, JSON.stringify(info, null, 2))
|
|
142
|
-
}
|
|
143
|
-
console.log(line)
|
|
144
|
-
}
|
|
145
|
-
})
|
|
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 preview-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 Preview)`,
|
|
138
|
+
startedAt: new Date().toISOString(),
|
|
146
139
|
}
|
|
140
|
+
writeFile(devtoolsInfoPath, JSON.stringify(info, null, 2))
|
|
147
141
|
}
|
|
148
|
-
|
|
142
|
+
console.log(line)
|
|
143
|
+
})
|
|
149
144
|
|
|
150
|
-
await
|
|
151
|
-
|
|
145
|
+
await new Promise<void>((resolve) => {
|
|
146
|
+
proc.on('close', () => resolve())
|
|
147
|
+
})
|
|
152
148
|
|
|
153
149
|
cleanupDevtoolsInfo()
|
|
154
150
|
if (watcher) watcher.close()
|
package/tools/pull.ts
CHANGED
|
@@ -1,21 +1,26 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFile, writeFile } from 'node:fs/promises'
|
|
2
2
|
import { format } from 'oxfmt'
|
|
3
|
+
import { sh } from './_shell'
|
|
4
|
+
import { buildCommitArgs } from './_git-author'
|
|
3
5
|
|
|
4
6
|
const cwd = process.cwd()
|
|
5
7
|
const args = process.argv.slice(2)
|
|
6
8
|
const force = args.includes('--force') || args.includes('-f')
|
|
7
9
|
|
|
8
10
|
// Check git status
|
|
9
|
-
const { exitCode } = await
|
|
11
|
+
const { exitCode } = await sh`cd ${cwd} && git status`.nothrow()
|
|
10
12
|
const isGitRepo = exitCode === 0
|
|
11
13
|
|
|
12
14
|
if (isGitRepo) {
|
|
13
|
-
const unstagedChanges = await
|
|
15
|
+
const unstagedChanges = await sh`cd ${cwd} && git diff --name-only --diff-filter=ACMR`.text()
|
|
14
16
|
if (unstagedChanges) {
|
|
15
17
|
if (force) {
|
|
16
18
|
console.log('Force mode: committing unstaged changes before pull...')
|
|
17
|
-
await
|
|
18
|
-
|
|
19
|
+
await sh`cd ${cwd} && git add .`
|
|
20
|
+
const preCommitArgs = await buildCommitArgs(cwd, [
|
|
21
|
+
'chore(force-pull): saved unstaged changes before pull',
|
|
22
|
+
])
|
|
23
|
+
await sh`cd ${cwd} && git ${preCommitArgs}`
|
|
19
24
|
} else {
|
|
20
25
|
throw new Error('Unstaged changes found, please commit or stash your changes before pulling')
|
|
21
26
|
}
|
|
@@ -28,14 +33,14 @@ if (isGitRepo) {
|
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
// Read application.json
|
|
31
|
-
const app = await
|
|
36
|
+
const app = JSON.parse(await readFile(`${cwd}/application.json`, 'utf8'))
|
|
32
37
|
|
|
33
38
|
const isModule = app.type === 'module'
|
|
34
39
|
const command = isModule ? 'module' : 'app'
|
|
35
40
|
|
|
36
41
|
// Fetch project files using CLI
|
|
37
42
|
console.log(`Pulling ${command} project (${app.id})...`)
|
|
38
|
-
const result = await
|
|
43
|
+
const result = await sh`bricks ${command} project-pull ${app.id} --json`.quiet().nothrow()
|
|
39
44
|
|
|
40
45
|
if (result.exitCode !== 0) {
|
|
41
46
|
const output = result.stderr.toString() || result.stdout.toString()
|
|
@@ -52,11 +57,11 @@ const { files, lastCommitId } = JSON.parse(result.stdout.toString())
|
|
|
52
57
|
let useMain = false
|
|
53
58
|
if (isGitRepo && !force) {
|
|
54
59
|
console.log(`Checking commit ${lastCommitId}...`)
|
|
55
|
-
const found = (await
|
|
60
|
+
const found = (await sh`cd ${cwd} && git rev-list -1 ${lastCommitId}`.nothrow().text())
|
|
56
61
|
.trim()
|
|
57
62
|
.match(/^[\da-f]{40}$/)
|
|
58
63
|
|
|
59
|
-
const commitId = (await
|
|
64
|
+
const commitId = (await sh`cd ${cwd} && git rev-parse HEAD`.text()).trim()
|
|
60
65
|
|
|
61
66
|
if (commitId === lastCommitId) throw new Error('Commit not changed')
|
|
62
67
|
|
|
@@ -64,18 +69,18 @@ if (isGitRepo && !force) {
|
|
|
64
69
|
? 'BRICKS_PROJECT_try-pull-module'
|
|
65
70
|
: 'BRICKS_PROJECT_try-pull-application'
|
|
66
71
|
|
|
67
|
-
await
|
|
72
|
+
await sh`cd ${cwd} && git branch -D ${branchName}`.nothrow()
|
|
68
73
|
|
|
69
74
|
if (found) {
|
|
70
|
-
await
|
|
75
|
+
await sh`cd ${cwd} && git checkout -b ${branchName} ${lastCommitId}`.nothrow()
|
|
71
76
|
} else {
|
|
72
|
-
await
|
|
77
|
+
await sh`cd ${cwd} && git checkout -b ${branchName}`
|
|
73
78
|
useMain = true
|
|
74
79
|
}
|
|
75
80
|
}
|
|
76
81
|
|
|
77
|
-
const oxfmtConfig = await
|
|
78
|
-
.
|
|
82
|
+
const oxfmtConfig = await readFile(`${cwd}/.oxfmtrc.json`, 'utf8')
|
|
83
|
+
.then(JSON.parse)
|
|
79
84
|
.catch(() => ({
|
|
80
85
|
trailingComma: 'all',
|
|
81
86
|
tabWidth: 2,
|
|
@@ -91,23 +96,24 @@ await Promise.all(
|
|
|
91
96
|
const result = await format(file.name, file.input, oxfmtConfig)
|
|
92
97
|
content = result.code
|
|
93
98
|
}
|
|
94
|
-
return
|
|
99
|
+
return writeFile(`${cwd}/${file.name}`, content)
|
|
95
100
|
}),
|
|
96
101
|
)
|
|
97
102
|
|
|
98
103
|
if (isGitRepo) {
|
|
99
|
-
await
|
|
100
|
-
const hasChanges = !!(await
|
|
104
|
+
await sh`cd ${cwd} && git add .`
|
|
105
|
+
const hasChanges = !!(await sh`cd ${cwd} && git diff --cached --name-only`.text()).trim()
|
|
101
106
|
if (hasChanges) {
|
|
102
107
|
const commitMsg = force
|
|
103
108
|
? `chore(force-pull): apply force pull-${command}`
|
|
104
109
|
: isModule
|
|
105
110
|
? 'chore(project): apply file changes from BRICKS module'
|
|
106
111
|
: 'chore(project): apply file changes from BRICKS application'
|
|
107
|
-
|
|
112
|
+
const commitArgs = await buildCommitArgs(cwd, [commitMsg])
|
|
113
|
+
await sh`cd ${cwd} && git ${commitArgs}`
|
|
108
114
|
}
|
|
109
115
|
if (!force && !useMain) {
|
|
110
|
-
await
|
|
116
|
+
await sh`cd ${cwd} && git merge main`
|
|
111
117
|
}
|
|
112
118
|
}
|
|
113
119
|
|