@fugood/bricks-project 2.21.0 → 2.21.1

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.
@@ -62,6 +62,26 @@ export const templateActionNameMap = {
62
62
  height: 'TAKE_SCREENSHOT_HEIGHT',
63
63
  saveProperty: 'TAKE_SCREENSHOT_SAVE_PROPERTY',
64
64
  },
65
+ STORAGE_SET: {
66
+ storageType: 'STORAGE_TYPE',
67
+ storageScope: 'STORAGE_SCOPE',
68
+ storageKey: 'STORAGE_KEY',
69
+ storageValue: 'STORAGE_VALUE',
70
+ },
71
+ STORAGE_RETRIEVE: {
72
+ storageType: 'STORAGE_TYPE',
73
+ storageScope: 'STORAGE_SCOPE',
74
+ storageKey: 'STORAGE_KEY',
75
+ result: 'STORAGE_RETRIEVE_RESULT',
76
+ defaultValue: 'STORAGE_RETRIEVE_DEFAULT_VALUE',
77
+ skipIfNotFound: 'STORAGE_RETRIEVE_SKIP_IF_NOT_FOUND',
78
+ },
79
+ STORAGE_DELETE: {
80
+ storageType: 'STORAGE_TYPE',
81
+ storageScope: 'STORAGE_SCOPE',
82
+ storageKey: 'STORAGE_KEY',
83
+ all: 'STORAGE_DELETE_ALL',
84
+ },
65
85
  CHANNEL_SUBSCRIBE: {
66
86
  key: 'CHANNEL_SUBSCRIBE_KEY',
67
87
  type: 'CHANNEL_SUBSCRIBE_TYPE',
package/compile/index.ts CHANGED
@@ -4,6 +4,7 @@ import type { ExportNamedDeclaration, FunctionDeclaration } from 'acorn'
4
4
  import escodegen from 'escodegen'
5
5
  import { generateCalulationMap } from './util'
6
6
  import { templateActionNameMap } from './action-name-map'
7
+ import { templateEventPropsMap } from '../utils/event-props'
7
8
  import type {
8
9
  Application,
9
10
  Data,
@@ -52,6 +53,17 @@ const compileProperty = (property, errorReference: string, result = {}) => {
52
53
  return property
53
54
  }
54
55
 
56
+ const compileEventActionValue = (templateKey, eventKey, value, errorReference) => {
57
+ const tmplEventProperties = templateEventPropsMap[templateKey]
58
+ const props = tmplEventProperties?.[eventKey]
59
+ if (!props) return compileProperty(value, errorReference)
60
+ if (props.includes(value)) return value
61
+ if (value?.startsWith(templateKey)) {
62
+ console.warn(`[Warning] Value start with template key but there is no event property: ${value} ${errorReference}`)
63
+ }
64
+ return compileProperty(value, errorReference)
65
+ }
66
+
55
67
  const convertOutletKey = (templateKey: string, key: string) => {
56
68
  return `${templateKey}_${_.snakeCase(key).toUpperCase()}`
57
69
  }
@@ -128,7 +140,7 @@ const compileEvents = (
128
140
  ? compileActionParam(handlerTemplateKey, action.__actionName, input)
129
141
  : input,
130
142
  [camelCase ? 'resultFromSender' : 'result_from_sender']:
131
- mapping || compileProperty(value, errorReference),
143
+ mapping || compileEventActionValue(handlerTemplateKey, key, value, errorReference),
132
144
  }
133
145
  if (mapping) param[camelCase ? 'resultDataMapping' : 'result_data_mapping'] = true
134
146
  parameterList.push(param)
@@ -140,7 +152,7 @@ const compileEvents = (
140
152
  const param = {
141
153
  [camelCase ? 'inputToReceiver' : 'input_to_receiver']: input().id,
142
154
  [camelCase ? 'resultFromSender' : 'result_from_sender']:
143
- mapping || compileProperty(value, errorReference),
155
+ mapping || compileEventActionValue(handlerTemplateKey, key, value, errorReference),
144
156
  }
145
157
  if (mapping) param[camelCase ? 'resultDataMapping' : 'result_data_mapping'] = true
146
158
  parameterList.push(param)
package/index.ts CHANGED
@@ -3,3 +3,4 @@ export type * from './types'
3
3
  export { makeId } from './utils/id'
4
4
  export { generateDataCalculationMapEditorInfo } from './utils/calc'
5
5
  export { linkData, useSystemData, createCanvasIdRef } from './utils/data'
6
+ export { templateEventPropsMap } from './utils/event-props'
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@fugood/bricks-project",
3
- "version": "2.21.0",
3
+ "version": "2.21.1",
4
4
  "main": "index.ts",
5
5
  "scripts": {
6
6
  "build": "node scripts/build.js"
7
7
  },
8
8
  "dependencies": {
9
+ "@modelcontextprotocol/sdk": "^1.7.0",
9
10
  "@types/escodegen": "^0.0.10",
10
11
  "@types/lodash": "^4.17.12",
11
12
  "acorn": "^8.13.0",
12
13
  "escodegen": "^2.1.0",
13
14
  "lodash": "^4.17.4",
14
15
  "uuid": "^8.3.1"
15
- },
16
- "gitHead": "7198652cb0d8217f36d88a755542300321dd2d74"
16
+ }
17
17
  }
@@ -0,0 +1,86 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
3
+ import { z } from 'zod'
4
+ import { $ } from 'bun'
5
+
6
+ const server = new McpServer({
7
+ name: 'bricks-project',
8
+ version: '1.0.0',
9
+ })
10
+
11
+ const dirname = import.meta.dirname
12
+ const projectDir = String(dirname).split('/node_modules/')[0]
13
+
14
+ server.tool('compile', {}, async () => {
15
+ let log
16
+ try {
17
+ log = await $`bun compile`.cwd(projectDir).text()
18
+ } catch (err) {
19
+ log = err.stdout.toString() + '\n' + err.stderr.toString()
20
+ }
21
+ return {
22
+ content: [{ type: 'text', text: log || 'Compiled successfully' }],
23
+ }
24
+ })
25
+
26
+ // NOTE: Cursor (Or VSCode) seems set ELECTRON_RUN_AS_NODE to 1, so we need to unset it
27
+ process.env.ELECTRON_RUN_AS_NODE = ''
28
+
29
+ server.tool(
30
+ 'take-preview-screenshot',
31
+ {
32
+ delay: z
33
+ .number()
34
+ .describe('Delay in milliseconds before taking screenshot')
35
+ .optional()
36
+ .default(3000),
37
+ width: z.number().describe('Width of the screenshot').optional().default(600),
38
+ height: z.number().optional().default(480),
39
+ responseImage: z
40
+ .boolean()
41
+ .describe(
42
+ 'Whether to response image content (base64 encoded jpeg). If false, only saved path will be responded as text.',
43
+ )
44
+ .optional()
45
+ .default(false),
46
+ } as any,
47
+ async ({ delay, width, height, responseImage }: any) => {
48
+ let log = ''
49
+ let error = false
50
+ try {
51
+ const args = [
52
+ '--take-screenshot',
53
+ JSON.stringify({
54
+ delay,
55
+ width,
56
+ height,
57
+ path: `${dirname}/screenshot.jpg`,
58
+ closeAfter: true,
59
+ headless: true,
60
+ }),
61
+ ]
62
+ log = await $`bunx --bun electron ${dirname}/preview-main.mjs ${args}`.cwd(projectDir).text()
63
+ } catch (err) {
64
+ log = err.stdout.toString() + '\n' + err.stderr.toString()
65
+ error = true
66
+ }
67
+ let screenshotBase64: any = null
68
+ if (!error && responseImage) {
69
+ const screenshot = await Bun.file(`${dirname}/screenshot.jpg`).arrayBuffer()
70
+ screenshotBase64 = Buffer.from(screenshot).toString('base64')
71
+ }
72
+ return {
73
+ content: [
74
+ { type: 'text', text: log },
75
+ screenshotBase64 && {
76
+ type: 'image',
77
+ data: screenshotBase64,
78
+ mimeType: 'image/jpeg',
79
+ },
80
+ ].filter(Boolean),
81
+ }
82
+ },
83
+ )
84
+
85
+ const transport = new StdioServerTransport()
86
+ await server.connect(transport)
@@ -1,5 +1,5 @@
1
1
  import { $ } from 'bun'
2
-
2
+ import { stat, readFile, writeFile } from 'fs/promises'
3
3
  const cwd = process.cwd()
4
4
 
5
5
  const libFiles = ['types', 'utils', 'index.ts']
@@ -9,4 +9,56 @@ for (const file of libFiles) {
9
9
  await $`cp -r ${__dirname}/../${file} ${cwd}/project`
10
10
  }
11
11
 
12
- console.log('Copied files to project/')
12
+ console.log('Copied files to project/')
13
+
14
+ async function exists(f: string) {
15
+ try {
16
+ await stat(f)
17
+ return true
18
+ } catch {
19
+ return false
20
+ }
21
+ }
22
+
23
+ const projectMcpServer = {
24
+ command: 'bun',
25
+ args: [`${cwd}/node_modules/@fugood/bricks-project/tools/mcp-server.ts`],
26
+ }
27
+
28
+ const defaultMcpConfig = {
29
+ mcpServers: {
30
+ 'bricks-project': projectMcpServer,
31
+ },
32
+ }
33
+
34
+ const handleMcpConfigOverride = async (mcpConfigPath: string) => {
35
+ let mcpConfig: { mcpServers: Record<string, typeof projectMcpServer> } | null = null
36
+ if (await exists(mcpConfigPath)) {
37
+ const configStr = await readFile(mcpConfigPath, 'utf-8')
38
+ try {
39
+ mcpConfig = JSON.parse(configStr)
40
+ if (!mcpConfig?.mcpServers) throw new Error('mcpServers is not defined')
41
+ mcpConfig.mcpServers['bricks-project'] = projectMcpServer
42
+ } catch (e) {
43
+ mcpConfig = defaultMcpConfig
44
+ }
45
+ } else {
46
+ mcpConfig = defaultMcpConfig
47
+ }
48
+
49
+ await writeFile(mcpConfigPath, `${JSON.stringify(mcpConfig, null, 2)}\n`)
50
+
51
+ console.log(`Updated ${mcpConfigPath}`)
52
+ }
53
+
54
+ if (await exists(`${cwd}/.cursorrules`)) {
55
+ await $`mkdir -p ${cwd}/.cursor`
56
+ const cursorMcpConfigPath = `${cwd}/.cursor/mcp.json`
57
+ await handleMcpConfigOverride(cursorMcpConfigPath)
58
+ }
59
+
60
+ if (await exists(`${cwd}/CLAUDE.md`)) {
61
+ await $`mkdir -p ${cwd}/.cursor`
62
+ const claudeCodeMcpConfigPath = `${cwd}/.mcp.json`
63
+ await handleMcpConfigOverride(claudeCodeMcpConfigPath)
64
+ }
@@ -1,6 +1,6 @@
1
1
  // eslint-disable-next-line import/no-extraneous-dependencies
2
2
  import { app, BrowserWindow } from 'electron'
3
- import { readFile } from 'fs/promises'
3
+ import { readFile, writeFile } from 'fs/promises'
4
4
  import { watchFile } from 'fs'
5
5
  import { parseArgs } from 'util'
6
6
 
@@ -8,6 +8,7 @@ const { values } = parseArgs({
8
8
  args: process.argv,
9
9
  options: {
10
10
  'clear-cache': { type: 'boolean' },
11
+ 'take-screenshot': { type: 'string' },
11
12
  },
12
13
  strict: true,
13
14
  allowPositionals: true,
@@ -15,6 +16,21 @@ const { values } = parseArgs({
15
16
 
16
17
  const cwd = process.cwd()
17
18
 
19
+ let takeScreenshotConfig = null
20
+ try {
21
+ if (values['take-screenshot']) {
22
+ takeScreenshotConfig = JSON.parse(values['take-screenshot'])
23
+ if (!takeScreenshotConfig.path) takeScreenshotConfig.path = `${cwd}/screenshot.jpg`
24
+ if (!takeScreenshotConfig.width) takeScreenshotConfig.width = 1280
25
+ if (!takeScreenshotConfig.height) takeScreenshotConfig.height = 768
26
+ if (!takeScreenshotConfig.delay) takeScreenshotConfig.delay = 1000
27
+ }
28
+ } catch (e) {
29
+ console.error('Invalid take-screenshot config', e)
30
+ // eslint-disable-next-line unicorn/no-process-exit
31
+ process.exit(1)
32
+ }
33
+
18
34
  let config = JSON.parse(await readFile(`${cwd}/.bricks/build/application-config.json`))
19
35
 
20
36
  const stage = process.env.BRICKS_STAGE || 'production'
@@ -29,7 +45,14 @@ const previewUrl = previewUrlMap[stage]
29
45
  if (!previewUrl) throw new Error(`Invalid BRICKS_STAGE: ${stage}`)
30
46
 
31
47
  app.on('ready', () => {
32
- const mainWindow = new BrowserWindow({ width: 1280, height: 768 })
48
+ let show = true
49
+ if (takeScreenshotConfig && !takeScreenshotConfig.noHeadless) show = false
50
+ const mainWindow = new BrowserWindow({
51
+ width: takeScreenshotConfig?.width || 1280,
52
+ height: takeScreenshotConfig?.height || 768,
53
+ frame: !takeScreenshotConfig,
54
+ show,
55
+ })
33
56
  mainWindow.setBackgroundColor('#333')
34
57
  mainWindow.loadURL(previewUrl)
35
58
 
@@ -38,10 +61,25 @@ app.on('ready', () => {
38
61
  type: 'config',
39
62
  configFile: { _originTitle: 'Test', ...config, title: Math.random().toString() },
40
63
  workspace: { billing: { lock: {}, plan: 'free' } },
64
+ showMenu: false,
41
65
  }
42
66
  mainWindow.webContents.executeJavaScript(
43
67
  `window.postMessage(JSON.stringify(${JSON.stringify(payload)}))`,
44
68
  )
69
+ if (takeScreenshotConfig) {
70
+ const { delay, width, height, path, keepOpen = false } = takeScreenshotConfig
71
+ setTimeout(() => {
72
+ console.log('Taking screenshot')
73
+ mainWindow.webContents.capturePage().then((image) => {
74
+ console.log('Writing screenshot to', path)
75
+ writeFile(path, image.resize({ width, height }).toJPEG(75))
76
+ if (!keepOpen) {
77
+ console.log('Closing app')
78
+ app.quit()
79
+ }
80
+ })
81
+ }, delay)
82
+ }
45
83
  }
46
84
 
47
85
  mainWindow.webContents.once('dom-ready', sendConfig)
package/tools/preview.ts CHANGED
@@ -1,22 +1,50 @@
1
1
  import { $ } from 'bun'
2
2
  import { watch } from 'fs'
3
+ import type { FSWatcher } from 'fs'
3
4
  import { parseArgs } from 'util'
4
5
  import { debounce } from 'lodash'
5
6
 
6
7
  const { values } = parseArgs({
7
8
  args: Bun.argv,
8
9
  options: {
9
- 'skip-typecheck': {
10
- type: 'boolean',
11
- },
12
- 'clear-cache': {
13
- type: 'boolean',
14
- },
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-keep-open': { type: 'boolean' },
18
+ 'screenshot-no-headless': { type: 'boolean' },
15
19
  },
16
20
  strict: true,
17
21
  allowPositionals: true,
18
22
  })
19
23
 
24
+ const cwd = process.cwd()
25
+
26
+ const app = await Bun.file(`${cwd}/application.json`).json()
27
+
28
+ let args: string[] = []
29
+ if (values['clear-cache']) args.push('--clear-cache')
30
+
31
+ let needWatcher = true
32
+ if (values['screenshot']) {
33
+ args.push(`--take-screenshot`)
34
+ const keepOpen = values['screenshot-keep-open'] ?? false
35
+ args.push(
36
+ JSON.stringify({
37
+ delay: Number(values['screenshot-delay']) || 1000,
38
+ width: Number(values['screenshot-width']) || 600,
39
+ height: Number(values['screenshot-height']) || 480,
40
+ path: values['screenshot-path'] || `${cwd}/screenshot.jpg`,
41
+ keepOpen,
42
+ noHeadless: values['screenshot-no-headless'] ?? false,
43
+ }),
44
+ )
45
+ needWatcher = keepOpen
46
+ }
47
+
20
48
  const useTypecheck = !values['skip-typecheck']
21
49
 
22
50
  const compile = async () => {
@@ -26,22 +54,15 @@ const compile = async () => {
26
54
 
27
55
  await compile()
28
56
 
29
- const compileDebounced = debounce(compile, 500)
30
-
31
- const cwd = process.cwd()
32
-
33
- const watcher = watch(`${cwd}/subspaces`, { recursive: true }, async (event, filename) => {
34
- console.log(`Detected ${event} in ${filename}`)
35
- compileDebounced()
36
- })
37
-
38
- const app = await Bun.file(`${cwd}/application.json`).json()
39
-
40
-
41
- if (values['clear-cache']) {
42
- await $`BRICKS_STAGE=${app.stage || 'production'} bunx --bun electron ${__dirname}/preview-main.mjs --clear-cache`
43
- } else {
44
- await $`BRICKS_STAGE=${app.stage || 'production'} bunx --bun electron ${__dirname}/preview-main.mjs`
57
+ let watcher: FSWatcher | null = null
58
+ if (needWatcher) {
59
+ const compileDebounced = debounce(compile, 500)
60
+ watcher = watch(`${cwd}/subspaces`, { recursive: true }, async (event, filename) => {
61
+ console.log(`Detected ${event} in ${filename}`)
62
+ compileDebounced()
63
+ })
45
64
  }
46
65
 
47
- watcher.close()
66
+ await $`BRICKS_STAGE=${app.stage || 'production'} bunx --bun electron ${__dirname}/preview-main.mjs ${args}`
67
+
68
+ if (watcher) watcher.close()