@yorha2b-lab/autodev 5.6.1 → 5.6.3

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/bin/autodev.js CHANGED
@@ -5,10 +5,10 @@ const pkg = require('../package.json')
5
5
  const { program } = require('commander')
6
6
  const local = Intl.DateTimeFormat().resolvedOptions().locale.toUpperCase()
7
7
 
8
- const { matrixEffect } = require('../src/utils/ux')
8
+ const { matchDialog, matrixEffect } = require('../src/utils/ux')
9
9
  const recruiter = require('../src/kernel/recruiter')
10
10
  const dialogs = recruiter(path.join(__dirname, '../src/dialogs'))
11
- const dialog = dialogs[local] ?? dialogs['EN-US']
11
+ const dialog = matchDialog(local, dialogs) ?? dialogs['EN-US']
12
12
 
13
13
  program
14
14
  .version(pkg.version)
@@ -45,7 +45,7 @@ program
45
45
  require('../src/commands/watch')()
46
46
  process.on('SIGINT', () => {
47
47
  result.yorha.commander.report(dialog.bunker.systemOffline, 'gray')
48
- matrixEffect(500, dialog)
48
+ matrixEffect(500, dialog, result)
49
49
  })
50
50
  })
51
51
 
package/config.js CHANGED
@@ -7,6 +7,8 @@ module.exports = {
7
7
 
8
8
  useDemo: true,
9
9
  needMock: false,
10
+ //默认拉取克隆数彩蛋
11
+ fetchClone: true,
10
12
  // 是否开启自动联调对齐协议 (42153 拦截塔)
11
13
  enableAutoAlignment: false,
12
14
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yorha2b-lab/autodev",
3
- "version": "5.6.1",
3
+ "version": "5.6.3",
4
4
  "description": "基于视觉大模型的前端(react+Antd)全自动 CRUD 代码生成器",
5
5
  "bin": {
6
6
  "autodev": "bin/autodev.js"
package/src/ai/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  const askAI = async ({ model, yorha, dialog, openAI, messages, response_format = { type: 'json_object' }, retryCount = 0 }) => {
2
2
 
3
- if (retryCount > 3) {
3
+ if (retryCount >= 3) {
4
4
  yorha.commander.report(dialog.bunker.linkSevered, 'red')
5
- throw new Error()
5
+ throw new Error(dialog.bunker.linkSevered)
6
6
  }
7
7
 
8
8
  try {
@@ -20,10 +20,10 @@ const askAI = async ({ model, yorha, dialog, openAI, messages, response_format =
20
20
  return JSON5.parse(match ? match[0] : raw)
21
21
  } catch (err) {
22
22
  const statusCode = err.status || err.response?.status
23
- const isAuthError = err.message.includes('401') || err.message.includes('402') || [401, 402].includes(statusCode)
23
+ const isAuthError = [401, 402].includes(statusCode)
24
24
  if (isAuthError) {
25
25
  yorha.commander.report(dialog.bunker.accessDenied, 'red')
26
- throw new Error()
26
+ throw new Error(dialog.bunker.accessDenied)
27
27
  }
28
28
 
29
29
  yorha.commander.report(dialog.bunker.networkInstability(retryCount + 1))
@@ -5,17 +5,17 @@ module.exports = () => {
5
5
  const chokidar = require('chokidar')
6
6
 
7
7
  const bootstrap = require('../bootstrap')
8
- const queue = require('../kernel/queue')(2)
9
8
 
10
9
  const ctx = bootstrap.get()
11
10
  const routes = Object.values(ctx.handlers)
12
11
  const config = ctx.utils.foundation.getConfig()
12
+ const queue = require('../kernel/queue')(2, ctx)
13
13
 
14
14
  queue.onIdle(() => {
15
15
  ctx.yorha.pod042.report(ctx.dialog.pod042.queueEmpty)
16
16
  const utilsDir = path.join(process.cwd(), config.utilsDir)
17
17
  if (!fs.existsSync(utilsDir)) fs.mkdirSync(utilsDir, { recursive: true })
18
- fs.writeFileSync(path.join(utilsDir, 'menus.js'), `export const menus = ${ctx.utils.generator.contextStringify({ context: ctx.menus, maxLength: 50 })}`)
18
+ fs.writeFileSync(path.join(utilsDir, 'menus.js'), `export const menus = ${ctx.utils.generator.contextStringify({ context: ctx.utils.foundation.getExistingMenus(config.pagesDir), maxLength: 50 })}`)
19
19
  ctx.yorha.commander.report(ctx.dialog.bunker.systemStandby, 'gray')
20
20
  })
21
21
 
@@ -1,10 +1,116 @@
1
1
  module.exports = ({ utils, engine }) => {
2
2
 
3
3
  const { indexTpl, resourceTpl } = engine
4
+ const { wrapCode, cleanCode, contextStringify } = utils.generator
4
5
  const { needMock, responseSuccess } = utils.foundation.getConfig()
5
- const { cleanCode, contextStringify, generateSmartImports, formatFormItemAndColumns } = utils.generator
6
+
7
+ const formatFormItemAndColumns = ({ pageConfig }) => {
8
+
9
+ const codePresets = {
10
+ money: 'text => moneyRender(text)',
11
+ date: 'text => timeRender({time: text})',
12
+ index: '(_, record, index) => index + 1',
13
+ tag: `text => <Tag color='magenta'>{text}</Tag>`,
14
+ badge: `text => <Badge status='success' text={text} />`,
15
+ enum: dataIndex => `text => ${dataIndex}Options.find(item => item.value === text)?.label ?? text`,
16
+ }
17
+
18
+ const columns = pageConfig.table?.columns ?? pageConfig?.columns ?? []
19
+
20
+ const tableDicts = columns?.filter(item => item.type === 'enum')?.map(item => `${item.dataIndex}Options`) ?? []
21
+ const formDicts = pageConfig.formItems?.filter(item => item.type === 'select')?.map(item => `${item.name}Options`) ?? []
22
+ const dictBlocks = Array.from(new Set([...formDicts, ...tableDicts]))
23
+
24
+ const formItems = pageConfig.formItems?.map(item => {
25
+ if (item.type === 'text') {
26
+ delete item.type
27
+ }
28
+ return {
29
+ ...item,
30
+ ...(item.type === 'select' ? { options: wrapCode(`${item.name}Options`) } : {})
31
+ }
32
+ })
33
+
34
+ const processedColumns = columns?.map(col => {
35
+ if (col.type === 'text') {
36
+ delete col.type
37
+ }
38
+ if (['image'].includes(col.type)) {
39
+ return { ...col, renderAction: true }
40
+ }
41
+ if (col.type && codePresets[col.type]) {
42
+ const renderCode = typeof codePresets[col.type] === 'function' ? codePresets[col.type](col.dataIndex) : codePresets[col.type]
43
+ return { ...col, render: wrapCode(renderCode) }
44
+ }
45
+ return col
46
+ })
47
+
48
+ return { formItems, dictBlocks, processedColumns }
49
+ }
50
+
51
+ const generateSmartImports = ({ module, hasTabs, bodyCode, hasFormItems }) => {
52
+
53
+ const hooksLib = ['useTableQuery']
54
+ const utilsLib = ['timeRender', 'moneyRender']
55
+ const reactLib = ['useState', 'useEffect', 'useRef', 'useMemo']
56
+ const componentsLib = ['MyTable', 'MyImage', 'MyModalForm', 'MySearchForm']
57
+ const antdLib = ['Tag', 'Card', 'Badge', 'Space', 'Modal', 'Alert', 'Image', 'Table', 'Input', 'Select', 'Button']
58
+
59
+ const usedAntd = antdLib.filter(name => new RegExp(`\\b${name}\\b`).test(bodyCode))
60
+ const usedUtils = utilsLib.filter(name => new RegExp(`\\b${name}\\b`).test(bodyCode))
61
+ const usedHooks = hooksLib.filter(name => new RegExp(`\\b${name}\\b`).test(bodyCode))
62
+ const usedReact = reactLib.filter(name => new RegExp(`\\b${name}\\b`).test(bodyCode))
63
+ const usedComps = componentsLib.filter(name => new RegExp(`\\b${name}\\b`).test(bodyCode))
64
+
65
+ const imports = [
66
+ usedReact.length && `import { ${usedReact.join(', ')} } from 'react'`,
67
+ usedAntd.length && `import { ${hasFormItems && module === 'index' ? 'Form, ' : ''}${usedAntd.join(', ')} } from 'antd'`,
68
+ ...usedHooks.map(hook => `import { ${hook} } from '../../hooks/${hook}'`),
69
+ ...usedComps.map(comp => `import { ${comp} } from '../../components/${comp}'`),
70
+ ...(module === 'index' ? [
71
+ `import { request } from '../../utils/request'`,
72
+ `import { formatQuery } from '../../utils/utils'`,
73
+ `import { ${hasTabs ? 'tabs, ' : ''}${hasFormItems ? 'formItems, ' : ''}modalItems, tableColumns} from './resource'`
74
+ ] : [
75
+ usedUtils.length && `import { ${usedUtils.join(', ')} } from '../../utils/utils'`
76
+ ]),
77
+ ].sort((a, b) => a.length - b.length)
78
+
79
+ return imports.filter(Boolean).join('\n')
80
+ }
6
81
 
7
82
  return {
83
+ formatFormItemAndColumns,
84
+ resource: ({ pageConfig }) => {
85
+
86
+ const { formItems, processedColumns, dictBlocks } = formatFormItemAndColumns({ pageConfig })
87
+
88
+ const hasTabs = pageConfig.tabs?.length > 0
89
+ const hasFormItems = pageConfig.formItems?.length > 0
90
+ const tabKeys = pageConfig.tabs?.map(tab => tab.key).sort((a, b) => a.length - b.length)
91
+ const optionDict = pageConfig.optionList?.reduce((acc, item) => ({ ...acc, [item.name]: item.options }), {})
92
+
93
+ const columnsData = processedColumns.map(item => {
94
+ delete item.type
95
+ return item
96
+ })
97
+
98
+ const viewData = {
99
+ hasTabs,
100
+ columnsData,
101
+ hasFormItems,
102
+ tabs: pageConfig.tabs,
103
+ dictBlocks: dictBlocks.map(item => ({ name: item, data: optionDict[item] ?? [] })),
104
+ tabColumns: contextStringify({ context: Object.fromEntries(tabKeys.map(tab => [tab, wrapCode('commonColumns')])), maxLength: 100 }),
105
+ formItemsData: !hasTabs ?
106
+ `export const formItems = ${contextStringify({ context: formItems, maxLength: 120 })}` :
107
+ `const searchItems = ${contextStringify({ context: formItems, maxLength: 120 })}\n\nexport const formItems = ${contextStringify({ context: Object.fromEntries(tabKeys.map(tab => [tab, wrapCode('searchItems')])), maxLength: 100 })}`,
108
+ }
109
+
110
+ const bodyCode = resourceTpl(viewData)
111
+ const importsStr = generateSmartImports({ module: 'resource', hasTabs, bodyCode, hasFormItems })
112
+ return cleanCode(`${importsStr}\n\n${bodyCode}`)
113
+ },
8
114
  index: ({ fileName, pageConfig }) => {
9
115
  const hasTabs = pageConfig.tabs?.length > 0
10
116
  const hasPagination = pageConfig.table.pagination
@@ -21,7 +127,7 @@ module.exports = ({ utils, engine }) => {
21
127
  columnsValue = `${columnsValue}.concat(operate)`
22
128
  }
23
129
 
24
- const imgAction = Object.fromEntries(pageConfig.table?.columns?.filter(item => item.type === 'image')?.map(item => [item.dataIndex, `_CODE_(_, record) => <a onClick={() => showImg(record, '${item.dataIndex}')}>查看图片</a>_CODE_`]))
130
+ const imgAction = Object.fromEntries(pageConfig.table?.columns?.filter(item => item.type === 'image')?.map(item => [item.dataIndex, wrapCode(`(_, record) => <a onClick={() => showImg(record, '${item.dataIndex}')}>查看图片</a>`)]))
25
131
  const renderAction = `{${contextStringify({ context: imgAction, maxLength: 120 })}}`.replace(/\n/g, '\n ')
26
132
 
27
133
  const viewData = {
@@ -50,35 +156,5 @@ module.exports = ({ utils, engine }) => {
50
156
  const importsStr = generateSmartImports({ module: 'index', hasTabs, bodyCode, hasFormItems })
51
157
  return cleanCode(`${importsStr}\n\n${bodyCode}`)
52
158
  },
53
- resource: ({ pageConfig }) => {
54
-
55
- const { formItems, processedColumns, dictBlocks } = formatFormItemAndColumns({ pageConfig })
56
-
57
- const hasTabs = pageConfig.tabs?.length > 0
58
- const hasFormItems = pageConfig.formItems?.length > 0
59
- const tabKeys = pageConfig.tabs.map(tab => tab.key).sort((a, b) => a.length - b.length)
60
- const optionDict = pageConfig.optionList?.reduce((acc, item) => ({ ...acc, [item.name]: item.options }), {})
61
-
62
- const columnsData = processedColumns.map(item => {
63
- delete item.type
64
- return item
65
- })
66
-
67
- const viewData = {
68
- hasTabs,
69
- columnsData,
70
- hasFormItems,
71
- tabs: pageConfig.tabs,
72
- dictBlocks: dictBlocks.map(item => ({ name: item, data: optionDict[item] ?? [] })),
73
- tabColumns: contextStringify({ context: Object.fromEntries(tabKeys.map(tab => [tab, '_CODE_commonColumns_CODE_'])), maxLength: 100 }),
74
- formItemsData: !hasTabs ?
75
- `export const formItems = ${contextStringify({ context: formItems, maxLength: 120 })}` :
76
- `const searchItems = ${contextStringify({ context: formItems, maxLength: 120 })}\n\nexport const formItems = ${contextStringify({ context: Object.fromEntries(tabKeys.map(tab => [tab, '_CODE_searchItems_CODE_'])), maxLength: 100 })}`,
77
- }
78
-
79
- const bodyCode = resourceTpl(viewData)
80
- const importsStr = generateSmartImports({ module: 'resource', hasTabs, bodyCode, hasFormItems })
81
- return cleanCode(`${importsStr}\n\n${bodyCode}`)
82
- }
83
159
  }
84
160
  }
@@ -16,7 +16,6 @@ module.exports = {
16
16
 
17
17
 
18
18
  const startTime = Date.now()
19
- const menus = utils.foundation.getExistingMenus(pagesDir)
20
19
  const fileName = path.basename(filePath, path.extname(filePath))
21
20
  const mockDir = path.join(process.cwd(), 'mock')
22
21
  const targetDir = path.join(process.cwd(), pagesDir, fileName)
@@ -44,10 +43,6 @@ module.exports = {
44
43
  fs.writeFileSync(path.join(targetDir, 'resource.js'), resource({ pageConfig }))
45
44
  fs.writeFileSync(path.join(targetDir, 'index.js'), index({ fileName, pageConfig }))
46
45
 
47
- if (!menus.find(m => m.key === fileName)) {
48
- menus.push({ label: fileName, key: fileName })
49
- }
50
-
51
46
  if (needMock) {
52
47
  nineS.update(spinner, dialog.nineS.dataCamouflage(fileName))
53
48
  const rawContent = await generateMock({ columns: pageConfig.table.columns, fileName })
@@ -5,12 +5,13 @@ module.exports = {
5
5
  async handle(filePath) {
6
6
 
7
7
  const chalk = require('chalk')
8
- const { llm, utils, yorha, dialog, prompts } = require('../bootstrap').get()
8
+ const { llm, utils, yorha, dialog, prompts, generator } = require('../bootstrap').get()
9
9
 
10
10
  const { pod042 } = yorha
11
11
  const { recognizePage } = llm
12
12
  const { part: prompt } = prompts
13
- const { cleanCode, contextStringify, formatFormItemAndColumns } = utils.generator
13
+ const { formatFormItemAndColumns } = generator
14
+ const { cleanCode, contextStringify } = utils.generator
14
15
 
15
16
  const startTime = Date.now()
16
17
 
@@ -1,4 +1,4 @@
1
- module.exports = (concurrency = 1) => {
1
+ module.exports = (concurrency = 1, { yorha, dialog }) => {
2
2
 
3
3
  let running = 0
4
4
  let queue = []
@@ -14,7 +14,7 @@ module.exports = (concurrency = 1) => {
14
14
  try {
15
15
  await task()
16
16
  } catch (err) {
17
- console.error('任务执行异常:', err)
17
+ yorha.pod153.report(dialog.pod153.signalLinkFault(err.message), 'red')
18
18
  } finally {
19
19
  running--
20
20
  next()
@@ -1,5 +1,7 @@
1
1
  const stringify = require('json-stringify-pretty-compact')
2
2
 
3
+ const wrapCode = code => `_CODE_${code}_CODE_`
4
+
3
5
  const cleanCode = str => str
4
6
  .replace(/```[a-z]*\n?/gi, '')// 物理超度 Markdown 糖衣(解决```json 报错)
5
7
  .replace(/```/g, '')
@@ -13,86 +15,10 @@ const cleanCode = str => str
13
15
  .replace(/^\s+/, '') // 去掉文件头部的空行
14
16
  .trim() + '\n'
15
17
 
16
- const formatFormItemAndColumns = ({ pageConfig }) => {
17
-
18
- const codePresets = {
19
- money: 'text => moneyRender(text)',
20
- date: 'text => timeRender({time: text})',
21
- index: '(_, record, index) => index + 1',
22
- tag: `text => <Tag color='magenta'>{text}</Tag>`,
23
- badge: `text => <Badge status='success' text={text} />`,
24
- enum: dataIndex => `text => ${dataIndex}Options.find(item => item.value === text)?.label ?? text`,
25
- }
26
-
27
- const columns = pageConfig.table?.columns ?? pageConfig?.columns ?? []
28
-
29
- const tableDicts = columns?.filter(item => item.type === 'enum')?.map(item => `${item.dataIndex}Options`) ?? []
30
- const formDicts = pageConfig.formItems?.filter(item => item.type === 'select')?.map(item => `${item.name}Options`) ?? []
31
- const dictBlocks = Array.from(new Set([...formDicts, ...tableDicts]))
32
-
33
- const formItems = pageConfig.formItems?.map(item => {
34
- if (item.type === 'text') {
35
- delete item.type
36
- }
37
- return {
38
- ...item,
39
- ...(item.type === 'select' ? { options: `_CODE_${item.name}Options_CODE_` } : {})
40
- }
41
- })
42
-
43
- const processedColumns = columns?.map(col => {
44
- if (col.type === 'text') {
45
- delete col.type
46
- }
47
- if (['image'].includes(col.type)) {
48
- return { ...col, renderAction: true }
49
- }
50
- if (col.type && codePresets[col.type]) {
51
- const renderCode = typeof codePresets[col.type] === 'function' ? codePresets[col.type](col.dataIndex) : codePresets[col.type]
52
- return { ...col, render: `_CODE_${renderCode}_CODE_` }
53
- }
54
- return col
55
- })
56
-
57
- return { formItems, dictBlocks, processedColumns }
58
- }
59
-
60
- const generateSmartImports = ({ module, hasTabs, bodyCode, hasFormItems }) => {
61
-
62
- const hooksLib = ['useTableQuery']
63
- const utilsLib = ['timeRender', 'moneyRender']
64
- const reactLib = ['useState', 'useEffect', 'useRef', 'useMemo']
65
- const componentsLib = ['MyTable', 'MyImage', 'MyModalForm', 'MySearchForm']
66
- const antdLib = ['Tag', 'Card', 'Badge', 'Space', 'Modal', 'Alert', 'Image', 'Table', 'Input', 'Select', 'Button']
67
-
68
- const usedAntd = antdLib.filter(name => new RegExp(`\\b${name}\\b`).test(bodyCode))
69
- const usedUtils = utilsLib.filter(name => new RegExp(`\\b${name}\\b`).test(bodyCode))
70
- const usedHooks = hooksLib.filter(name => new RegExp(`\\b${name}\\b`).test(bodyCode))
71
- const usedReact = reactLib.filter(name => new RegExp(`\\b${name}\\b`).test(bodyCode))
72
- const usedComps = componentsLib.filter(name => new RegExp(`\\b${name}\\b`).test(bodyCode))
73
-
74
- const imports = [
75
- usedReact.length && `import { ${usedReact.join(', ')} } from 'react'`,
76
- usedAntd.length && `import { ${hasFormItems && module === 'index' ? 'Form, ' : ''}${usedAntd.join(', ')} } from 'antd'`,
77
- ...usedHooks.map(hook => `import { ${hook} } from '../../hooks/${hook}'`),
78
- ...usedComps.map(comp => `import { ${comp} } from '../../components/${comp}'`),
79
- ...(module === 'index' ? [
80
- `import { request } from '../../utils/request'`,
81
- `import { formatQuery } from '../../utils/utils'`,
82
- `import { ${hasTabs ? 'tabs, ' : ''}${hasFormItems ? 'formItems, ' : ''}modalItems, tableColumns} from './resource'`
83
- ] : [
84
- usedUtils.length && `import { ${usedUtils.join(', ')} } from '../../utils/utils'`
85
- ]),
86
- ].sort((a, b) => a.length - b.length)
87
-
88
- return imports.filter(Boolean).join('\n')
89
- }
90
-
91
18
  const contextStringify = ({ context, indent = 4, maxLength = 200 }) => stringify.default(context, { indent, maxLength })
92
19
 
93
20
  module.exports = {
21
+ wrapCode,
94
22
  cleanCode,
95
23
  contextStringify,
96
- generateSmartImports,
97
- formatFormItemAndColumns,
98
24
  }
package/src/utils/ux.js CHANGED
@@ -38,6 +38,13 @@ const yorha = () => {
38
38
 
39
39
  }
40
40
 
41
+ const matchDialog = (locale, dialogs) => {
42
+ if (dialogs[locale]) return dialogs[locale]
43
+ const lang = locale.split('-')[0] // 取 ZH-HANS-CN → ZH
44
+ const matched = Object.keys(dialogs).find(key => key.startsWith(lang + '-'))
45
+ return matched ? dialogs[matched] : null
46
+ }
47
+
41
48
  const bootSequence = async (version, local) => {
42
49
 
43
50
  const lines = [
@@ -58,18 +65,20 @@ const bootSequence = async (version, local) => {
58
65
  }
59
66
  }
60
67
 
61
- const matrixEffect = async (duration = 1500, dialog) => {
68
+ const matrixEffect = async (duration = 1500, dialog, result) => {
62
69
 
63
70
  let currentTotal = 0
64
71
  const MIRROR_URL = 'https://cdn.jsdelivr.net/gh/yorha2b-lab/auto-crud-copilot@github-repo-stats/bunker-stats.json'
65
72
 
66
73
  try {
67
- const controller = new AbortController()
68
- const timeoutId = setTimeout(() => controller.abort(), 800)
69
- const response = await fetch(MIRROR_URL, { signal: controller.signal })
70
- const stats = await response.json()
71
- currentTotal = stats.total_clones || 0
72
- clearTimeout(timeoutId)
74
+ if (result.utils.foundation.getConfig().fetchClone) {
75
+ const controller = new AbortController()
76
+ const timeoutId = setTimeout(() => controller.abort(), 800)
77
+ const response = await fetch(MIRROR_URL, { signal: controller.signal })
78
+ const stats = await response.json()
79
+ currentTotal = stats.total_clones || 0
80
+ clearTimeout(timeoutId)
81
+ }
73
82
  } catch (e) {
74
83
  currentTotal = 0
75
84
  }
@@ -130,6 +139,7 @@ const matrixEffect = async (duration = 1500, dialog) => {
130
139
 
131
140
  module.exports = {
132
141
  yorha,
142
+ matchDialog,
133
143
  bootSequence,
134
144
  matrixEffect
135
145
  }