forked from opentiny/tiny-engine
607 lines
16 KiB
JavaScript
607 lines
16 KiB
JavaScript
import axios from 'axios'
|
|
import markdownit from 'markdown-it'
|
|
import fs from 'fs'
|
|
import LocalCache from './cache.mjs';
|
|
|
|
|
|
function checkIsStandardTable(params, standard) {
|
|
const { col, content } = params
|
|
for (let i = 0; i < col; i++) {
|
|
const item = standard[i];
|
|
const ct = content[i]
|
|
if (Array.isArray(item)) {
|
|
if (!item.includes(ct)) {
|
|
return false
|
|
}
|
|
} else {
|
|
if (item !== ct && !!item !== !!ct) {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
function capitalizeFirstLetter(word) {
|
|
if (typeof word !== 'string' || word.length === 0) {
|
|
throw new Error('Input must be a non-empty string');
|
|
}
|
|
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
}
|
|
|
|
class GenerateMaterial {
|
|
_owner = undefined
|
|
_repo = undefined
|
|
_path = undefined
|
|
_host = undefined
|
|
_version = undefined
|
|
_projectName = undefined
|
|
_framework = undefined
|
|
_requiredComponents = []
|
|
_unnecessary = []
|
|
_temporary_omit = []
|
|
_script = undefined
|
|
_css = undefined
|
|
_exportNamePrefix = undefined
|
|
_md = markdownit()
|
|
_lc = new LocalCache()
|
|
|
|
_components = []
|
|
_blocks = []
|
|
_snippets = []
|
|
_cached = []
|
|
_default_actions = ["copy", "remove", "insert", "updateAttr", "bindEevent", "createBlock"]
|
|
_need_cache_key = ['_components', '_blocks', '_snippets', '_framework', '_cached']
|
|
_mdParams = []
|
|
get url() {
|
|
return `${this._host}/repos/${this._owner}/${this._repo}/contents/${this._path}`
|
|
}
|
|
|
|
constructor(params) {
|
|
this._checkParams(params)
|
|
this._owner = params.owner
|
|
this._repo = params.repo
|
|
this._path = params.path
|
|
this._host = params.host
|
|
this._version = params.version
|
|
this._projectName = params.projectName
|
|
this._framework = params.framework
|
|
this._script = params.script
|
|
this._css = params.css
|
|
this._exportNamePrefix = params.exportNamePrefix
|
|
if (!params.token) {
|
|
throw new Error("请在系统中内置环境变量 GITHUB_TOKEN_PAT")
|
|
}
|
|
this._token = params.token
|
|
if (params.temporary_omit) {
|
|
this._temporary_omit = params.temporary_omit
|
|
}
|
|
if (params.isAttributeTable) {
|
|
this._isAttributeTable = params.isAttributeTable
|
|
}
|
|
if (params.isEvent) {
|
|
this._isEvent = params.isEvent
|
|
}
|
|
this._init()
|
|
}
|
|
|
|
async start() {
|
|
try {
|
|
const files = await this._requestGithub(this.url)
|
|
if (files && Array.isArray(files)) {
|
|
files.forEach((item) => {
|
|
const { name = "", type = "" } = item
|
|
if (
|
|
!name.startsWith("_")
|
|
&& !name.startsWith("vc-")
|
|
&& !this._unnecessary.includes(name)
|
|
&& type === "dir"
|
|
) {
|
|
if (this._temporary_omit.length) {
|
|
if (this._temporary_omit.includes(name)) {
|
|
this._requiredComponents.push(item)
|
|
}
|
|
} else {
|
|
this._requiredComponents.push(item)
|
|
}
|
|
}
|
|
})
|
|
this._requiredComponents = this._requiredComponents.filter(({ name }) => !this._cached.includes(name))
|
|
console.log(`files lenght: ${this._requiredComponents.length}`)
|
|
if (this._requiredComponents.length) {
|
|
for (const item of this._requiredComponents) {
|
|
const { git_url } = item
|
|
const data = await this._requestGithub(git_url)
|
|
if (data && Array.isArray(data.tree)) {
|
|
const mdObj = data.tree.find(({ path }) => path === "index.zh-CN.md")
|
|
console.log(item.name, mdObj)
|
|
if (!mdObj) {
|
|
console.log(`${item.name} 不存在文档已跳过`)
|
|
continue
|
|
}
|
|
const { content } = await this._requestGithub(mdObj.url)
|
|
let mdParam = this._parseMd(content)
|
|
this._mdParams.push(mdParam)
|
|
await this._generateMaterial(mdParam)
|
|
this._cached.push(item.name)
|
|
}
|
|
}
|
|
this._generateJSONFile({ data: this.data }, "bundle.json", "./packages/design-core/public/mock")
|
|
this._generateJSONFile({ data: this._mdParams }, "mdParams.json", "./packages/design-core/public/mock")
|
|
}
|
|
}
|
|
} catch (error) {
|
|
this._to_save()
|
|
throw new Error(error)
|
|
}
|
|
}
|
|
|
|
_init() {
|
|
const caches = this._need_cache_key
|
|
for (let i = 0; i < caches.length; i++) {
|
|
const key = caches[i];
|
|
const saveValue = this._lc.get(key)
|
|
if (saveValue) {
|
|
this[key] = saveValue
|
|
}
|
|
}
|
|
}
|
|
|
|
_to_save() {
|
|
const caches = this._need_cache_key
|
|
for (let i = 0; i < caches.length; i++) {
|
|
const key = caches[i];
|
|
this._lc.set(key, this[key])
|
|
}
|
|
}
|
|
|
|
get data() {
|
|
return {
|
|
framework: this._framework,
|
|
materials: {
|
|
components: this._components,
|
|
blocks: this._blocks,
|
|
snippets: this._snippets,
|
|
},
|
|
}
|
|
}
|
|
|
|
async _requestGithub(url) {
|
|
try {
|
|
const res = await axios({
|
|
url,
|
|
headers: { 'Authorization': "Bearer " + this._token }
|
|
})
|
|
return res?.data || {}
|
|
} catch (error) {
|
|
console.log(error)
|
|
}
|
|
}
|
|
|
|
get baseNpm() {
|
|
return {
|
|
package: this._repo,
|
|
version: this._version,
|
|
script: this._script,
|
|
css: this._css,
|
|
dependencies: null,
|
|
}
|
|
}
|
|
|
|
_generateProperties(tables) {
|
|
const properties = {
|
|
label: {
|
|
zh_CN: "基础信息"
|
|
},
|
|
description: {
|
|
zh_CN: "基础信息"
|
|
},
|
|
collapse: {
|
|
number: 6,
|
|
text: {
|
|
zh_CN: "显示更多"
|
|
}
|
|
},
|
|
}
|
|
const slots = {}
|
|
const attrs = tables.find(this._isAttributeTable)
|
|
if (attrs) {
|
|
const res = this._chunk(attrs.content, attrs.col)
|
|
for (let i = 1; i < res.length; i++) {
|
|
const rows = res[i];
|
|
if (!Array.isArray(properties.content)) {
|
|
properties.content = []
|
|
}
|
|
const params = {
|
|
property: rows[0],
|
|
description: rows[1],
|
|
type: rows[2],
|
|
defaultValue: rows[3]
|
|
}
|
|
if (params.property.includes('(')) {
|
|
let newproperty = params.property.split("(").shift()
|
|
params.property = newproperty.trim()
|
|
}
|
|
const content_item = this._createPropertieContent(params)
|
|
const types = params.type.split('|').map((str) => str.trim())
|
|
const isSlot = types.some((item) => item.includes('slot'))
|
|
if (isSlot) {
|
|
slots[params.property] = {
|
|
label: { zh_CN: params.property },
|
|
description: { zh_CN: params.description },
|
|
}
|
|
}
|
|
properties.content.push(content_item)
|
|
}
|
|
}
|
|
return { properties, slots }
|
|
}
|
|
|
|
_generateEvents(tables) {
|
|
const events = {}
|
|
const event = tables.find(this._isEvent)
|
|
if (event) {
|
|
const res = this._chunk(event.content, event.col)
|
|
for (let i = 1; i < res.length; i++) {
|
|
const rows = res[i];
|
|
const content_item = this._createEventContent(rows)
|
|
Object.assign(events, content_item)
|
|
}
|
|
}
|
|
return events
|
|
}
|
|
|
|
_generateComponent(params) {
|
|
const { tables, heading = {}, description } = params
|
|
const npm = {
|
|
...this.baseNpm,
|
|
exportName: `${this._exportNamePrefix}${heading?.title}`
|
|
}
|
|
|
|
const { properties, slots } = this._generateProperties(tables)
|
|
const configure = {
|
|
loop: true,
|
|
condition: true,
|
|
styles: true,
|
|
isContainer: false,
|
|
isModal: false,
|
|
isPopper: false,
|
|
nestingRule: {
|
|
childWhitelist: "",
|
|
parentWhitelist: "",
|
|
descendantBlacklist: "",
|
|
ancestorWhitelist: ""
|
|
},
|
|
isNullNode: false,
|
|
isLayout: false,
|
|
rootSelector: "",
|
|
shortcuts: {
|
|
properties: properties.content?.map(({ property }) => property) || []
|
|
},
|
|
contextMenu: {
|
|
actions: this._default_actions,
|
|
disable: []
|
|
},
|
|
invalidity: [""],
|
|
clickCapture: true,
|
|
framework: this._framework
|
|
}
|
|
const events = this._generateEvents(tables)
|
|
const shortcuts = {}
|
|
const contentMenu = {}
|
|
const schema = { properties: [properties], events, shortcuts, slots, contentMenu }
|
|
const component = {
|
|
id: this._components.length + 1,
|
|
version: this._version,
|
|
name: { "zh_CN": heading?.subtitle },
|
|
component: npm.exportName,
|
|
icon: heading?.title,
|
|
description,
|
|
doc_url: "",
|
|
screenshot: "",
|
|
tags: "",
|
|
keywords: "",
|
|
dev_mode: "proCode",
|
|
npm,
|
|
group: heading.type,
|
|
category: this._projectName,
|
|
configure,
|
|
schema,
|
|
}
|
|
|
|
this._components.push(component)
|
|
}
|
|
|
|
_generateSnippets(params) {
|
|
const { heading = {}, description } = params
|
|
let snippet = this._snippets.find(({ group }) => group === heading.type)
|
|
if (!snippet) {
|
|
snippet = { group: heading.type, children: [] }
|
|
this._snippets.push(snippet)
|
|
}
|
|
const nowComponent = {
|
|
name: {
|
|
zh_CN: heading?.subtitle
|
|
},
|
|
icon: heading?.title,
|
|
screenshot: "",
|
|
snippetName: `${this._exportNamePrefix}${heading?.title}`,
|
|
schema: {}
|
|
}
|
|
snippet.children.push(nowComponent)
|
|
}
|
|
|
|
async _generateMaterial(params) {
|
|
this._generateComponent(params)
|
|
this._generateSnippets(params)
|
|
}
|
|
|
|
_createPropertieContent(params) {
|
|
let { property, description, type, defaultValue } = params;
|
|
const widgetContent = this._createWidget({ type, defaultValue })
|
|
return {
|
|
property,
|
|
label: {
|
|
text: {
|
|
zh_CN: property
|
|
}
|
|
},
|
|
description: {
|
|
zh_CN: description,
|
|
},
|
|
required: true,
|
|
readOnly: false,
|
|
disabled: false,
|
|
cols: 12,
|
|
labelPosition: "top",
|
|
type: widgetContent.type,
|
|
defaultValue: widgetContent.defaultValue,
|
|
widget: widgetContent.widget
|
|
}
|
|
}
|
|
|
|
_createEventContent(params) {
|
|
let [property, description] = params;
|
|
if (property.includes("(")) {
|
|
property = property.split("(").shift()
|
|
property = property.trim()
|
|
}
|
|
return {
|
|
[`on${capitalizeFirstLetter(property)}`]: {
|
|
label: {
|
|
zh_CN: description
|
|
},
|
|
description: {
|
|
zh_CN: description
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
_createWidget(params) {
|
|
let { type, defaultValue } = params
|
|
const result = {}
|
|
if (type.includes('|')) {
|
|
const types = type.split('|').map((str) => str.trim())
|
|
const isString = types.every((str) => str.startsWith('`') && str.endsWith('`'))
|
|
if (isString) {
|
|
type = 'string'
|
|
result.widget = {
|
|
component: 'MetaSelect',
|
|
props: {
|
|
options: types.map((item) => (
|
|
{
|
|
label: item.slice(1, item.length - 1),
|
|
value: item.slice(1, item.length - 1)
|
|
}
|
|
))
|
|
}
|
|
}
|
|
}
|
|
const isBoolean = types.some((str) => str === 'boolean')
|
|
if (isBoolean) {
|
|
result.widget = { component: 'MetaSwitch', props: {} }
|
|
result.defaultValue = String(defaultValue).includes("false") ? false : true
|
|
}
|
|
}
|
|
result.type = type
|
|
switch (type) {
|
|
case 'boolean':
|
|
result.widget = { component: 'MetaSwitch', props: {} }
|
|
result.defaultValue = String(defaultValue).includes("false") ? false : true
|
|
break;
|
|
case 'string':
|
|
!result.widget && (result.widget = { component: 'MetaInput', props: {} })
|
|
result.defaultValue = defaultValue
|
|
break;
|
|
case 'v-slot':
|
|
!result.widget && (result.widget = { component: 'MetaJsSlot', props: { slots: ['default'] } })
|
|
result.defaultValue = defaultValue
|
|
break;
|
|
case 'slot':
|
|
!result.widget && (result.widget = { component: 'MetaJsSlot', props: { slots: ['default'] } })
|
|
result.defaultValue = defaultValue
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return result
|
|
}
|
|
|
|
_chunk(array, size = 1) {
|
|
size = Math.max(Number(size), 0)
|
|
const length = array == null ? 0 : array.length
|
|
if (!length || size < 1) {
|
|
return []
|
|
}
|
|
let index = 0
|
|
let resIndex = 0
|
|
const result = new Array(Math.ceil(length / size))
|
|
|
|
while (index < length) {
|
|
result[resIndex++] = array.slice(index, (index += size))
|
|
}
|
|
return result
|
|
}
|
|
|
|
_isAttributeTable(params) {
|
|
throw new Error("需要传入 isAttributeTable")
|
|
}
|
|
|
|
_isEvent(params) {
|
|
throw new Error("需要传入 isEvent")
|
|
}
|
|
|
|
_generateJSONFile(jsonData, fileName = 'output.json', filePath = './') {
|
|
// 将 JSON 数据转换为字符串
|
|
const jsonString = JSON.stringify(jsonData, null, 2);
|
|
|
|
// 拼接文件路径
|
|
const fullFilePath = filePath.endsWith('/') ? filePath + fileName : filePath + '/' + fileName;
|
|
|
|
// 写入文件
|
|
fs.writeFile(fullFilePath, jsonString, 'utf8', (err) => {
|
|
if (err) {
|
|
console.error('写入文件时出错:', err);
|
|
} else {
|
|
console.log(`JSON 数据已成功写入到文件 ${fullFilePath}`);
|
|
}
|
|
});
|
|
}
|
|
|
|
_atob_cn(base64String) {
|
|
const bytes = Uint8Array.from(atob(base64String), c => c.charCodeAt(0))
|
|
const decodedString = new TextDecoder('utf-8').decode(bytes)
|
|
return decodedString
|
|
}
|
|
|
|
_getBaseInfo(tokens) {
|
|
let description = undefined
|
|
let heading = undefined
|
|
// let name = undefined
|
|
// 寻找表格内容
|
|
for (let i = 0; i < tokens.length; i++) {
|
|
const token = tokens[i]
|
|
if (!description && token.type === "paragraph_open") {
|
|
description = tokens[i + 1].content
|
|
}
|
|
if (!heading && token.type === "heading_open") {
|
|
const content = tokens[i + 1].content
|
|
const heads = content.split("\n")
|
|
heading = {}
|
|
heads.forEach((item) => {
|
|
const kv = item.split(": ")
|
|
heading[kv[0]] = kv[1]
|
|
})
|
|
}
|
|
}
|
|
return { description, heading }
|
|
}
|
|
|
|
_getMdTableInfo(tokens) {
|
|
let tableContent = null
|
|
const tableContents = []
|
|
const titles = this._parseTitle(tokens)
|
|
let col = 0 // 列数
|
|
// 寻找表格内容
|
|
for (let i = 0; i < tokens.length; i++) {
|
|
const token = tokens[i]
|
|
if (token.type === 'table_open') {
|
|
col = 0
|
|
tableContent = {
|
|
content: [],
|
|
row: 0,
|
|
col,
|
|
parents: [],
|
|
parentMap: {}
|
|
}
|
|
let endIndex = undefined
|
|
for (let t = 0; t < titles.length; t++) {
|
|
const { index } = titles[t];
|
|
if (index > i && endIndex === undefined) {
|
|
endIndex = t
|
|
break;
|
|
}
|
|
}
|
|
const newTitles = titles.slice(0, endIndex).reverse()
|
|
let first_title_index = (newTitles[0] || {}).index
|
|
if (first_title_index) {
|
|
tableContent.parents.push(newTitles[0])
|
|
}
|
|
for (let j = 0; j < newTitles.length; j++) {
|
|
const title = newTitles[j];
|
|
if (title.index > first_title_index) {
|
|
tableContent.parents.push(title)
|
|
first_title_index = title.index
|
|
}
|
|
}
|
|
} else if (token.type === 'thead_close') {
|
|
tableContent.col = col
|
|
} else if (token.type === 'table_close') {
|
|
tableContent.row = tableContent.content.length / tableContent.col
|
|
tableContents.push(tableContent)
|
|
tableContent = null
|
|
} else if (tableContent && token.type === 'inline') {
|
|
const content = token.content
|
|
col++
|
|
tableContent.content.push(content)
|
|
}
|
|
}
|
|
return { tables: tableContents }
|
|
}
|
|
|
|
_parseTitle(tokens) {
|
|
const titles = []
|
|
for (let i = 0; i < tokens.length; i++) {
|
|
const token = tokens[i]
|
|
if (token.type === 'heading_open' && token.markup && token.markup.includes("#")) {
|
|
const content = tokens[i + 1].content
|
|
const title = {
|
|
level: String(token.markup).length,
|
|
content,
|
|
index: i
|
|
}
|
|
titles.push(title)
|
|
}
|
|
}
|
|
return titles
|
|
}
|
|
|
|
_parseMd(mdStrBase64) {
|
|
const mdStr = this._atob_cn(mdStrBase64)
|
|
let result = undefined
|
|
try {
|
|
result = this._md.parse(mdStr, {})
|
|
} catch (error) {
|
|
console.log(mdStr)
|
|
throw new Error(error)
|
|
}
|
|
const tablesInfo = this._getMdTableInfo(result)
|
|
const info = this._getBaseInfo(result)
|
|
return { ...tablesInfo, ...info }
|
|
}
|
|
|
|
_checkParams(params) {
|
|
if (!params) {
|
|
throw new Error('Initialization requires passing in parameters');
|
|
}
|
|
const requiredProperties = [
|
|
'owner',
|
|
'repo',
|
|
'path',
|
|
'host',
|
|
'version',
|
|
'projectName',
|
|
'framework',
|
|
'script',
|
|
'css',
|
|
'exportNamePrefix',
|
|
]
|
|
for (let prop of requiredProperties) {
|
|
if (!(prop in params)) {
|
|
throw new Error(`Property '${prop}' is missing from the params.`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
export default GenerateMaterial |