Lowcode Demo

Review

  1. 2024-08-17 18:45

[!Summary]

一、Introduction #

if (process.env.IS_PUBLISH_LOWCODE === 'N') process.exit()

const fs = require('fs')
const path = require('path')
const axios = require('axios')
const crypto = require('crypto')
const { execSync } = require('child_process')

const isLocal = !process.env.AWP_DEPLOY_ENV
isLocal && require('./env')

const { env } = process

let reactParser
try {
  reactParser = require('@rome/react-parser')
} catch {
  console.warn('未安装@rome/react-parser,跳过低代码发布')
  process.exit(231)
}

function generateBAString(uri, method, clientId, clientSecret) {
  // 获取http时间
  const timeSpan = new Date().toUTCString()
  const stringToSign = `${method.toUpperCase()} ${uri}\n${timeSpan}`
  const signature = crypto
    .createHmac('sha1', clientSecret)
    .update(stringToSign)
    .digest()
    .toString('base64')
  const Authorization = `${'MWS '}${clientId}:${signature}`
  return {
    Date: timeSpan,
    Authorization,
  }
}

const axPost = (pathname, params, options = {}) => {
  const headers = generateBAString(pathname, 'POST', env.TAM_CLIENT_ID, env.TAM_CLIENT_SECRET)

  console.log('headers ===>', headers)
  console.log('params ===>', JSON.stringify(params))

  return axios.post(pathname, params, {
    baseURL:
      env.TAM_API_ENV === 'test'
        ? 'http://harbour.dzu.test.sankuai.com'
        : 'https://harbour.sankuai.com',
    headers,
    ...options,
  })
}

/**
 * 获取低代码Meta配置对象
 * @param {String} dirPath: 组件目录地址,相对或绝对路径
 * @param {String} pkgName: 组件包名,如 @corn/block-demo1
 */
function getAssetsInfo(dirPath, pkgName) {
  const assetPath = path.resolve(dirPath, './build/assets.json')
  let lowcodePkg
  let assetsObj
  let metaJson = ''
  if (fs.existsSync(assetPath)) {
    try {
      const assetContent = fs.readFileSync(assetPath, 'utf8') || ''
      assetsObj = JSON.parse(assetContent) || {}
      if (assetsObj && assetsObj.packages && assetsObj.packages.length > 0) {
        lowcodePkg = assetsObj.packages.find(d => d.package === pkgName) || {}
      }
    } catch (e) {
      console.log('assetPath', assetPath)
      console.error('[readFileSync error]', e)
      assetsObj = {}
      lowcodePkg = {}
    }
  } else {
    console.log(`组件meta文件不存在, 文件路径:${assetPath}`)
  }
  try {
    metaJson = JSON.stringify((assetsObj.components || [])[0] || {})
  } catch (e) {
    console.error('[assetsObj stringify error]', e)
    console.log('assetsObj', assetsObj)
    console.log('assetsObj.components', assetsObj.components)
  }

  return {
    metaJson,
    packages: assetsObj.packages || [], // 依赖的资源
    umdUrl: lowcodePkg.urls || lowcodePkg.editUrls || [], // 资产包资源链接,js和css各一份 ['xxxxx.umd.js', 'xxxx.umd.css']
  }
}

/**
 * 获取build.lowcode.js配置文件
 * @param {String} dirPath: 组件目录地址,相对或绝对路径
 */
function getLowcodeEntry(dirPath) {
  const filePath = './build.lowcode.js'
  let entryPath = ''
  try {
    const childConfPath = path.resolve(dirPath, filePath)
    const useChild = fs.existsSync(childConfPath)
    const { plugins } = useChild ? require(childConfPath) : require(path.resolve(filePath))
    const confObj =
      ((plugins || []).find(d => d[0] === '@alifd/build-plugin-lowcode') || plugins[0] || [])[1] || {}
    entryPath = confObj.entryPath || ''
    if (useChild && entryPath) entryPath = path.join(dirPath, entryPath)
  } catch (e) {
    console.error('读取 build.lowcode.js 文件失败', e)
  }
  return entryPath
}

/**
 * 发布至Component平台
 * @param {String} dirPath: 组件目录地址,相对或绝对路径
 */
function publishToComponent(dirPath) {
  if (!env.CMP_PKG_ID || !env.CMP_USER_TOKEN) return
  let ssMetaTool
  try {
    ssMetaTool = require('@ss/meta-tool/dist/index')
  } catch {
    console.warn('未安装@ss/meta-tool/dist/index,跳过发布至Component平台')
    process.exitCode = 231
    return
  }
  try {
    const assetPath = path.resolve(dirPath, './build/assets.json')
    const assetContent = fs.readFileSync(assetPath, 'utf8') || ''
    if (fs.existsSync(assetPath)) {
      const assetsObj = JSON.parse(assetContent) || {}
      assetsObj.packages && assetsObj.packages.unshift(...[
        {
          "package": "lodash",
          "library": "_",
          "urls": [
            "https://g.alicdn.com/platform/c/lodash/4.6.1/lodash.min.js"
          ]
        },
        {
          "title": "react",
          "package": "react",
          "type": "procode",
          "version": "16.13.1",
          "library": "react",
          "urls": [
            "https://unpkg.com/react@16.13.1/umd/react.production.min.js"
          ]
        },
        {
          "title": "react-dom",
          "package": "react-dom",
          "type": "procode",
          "version": "16.13.1",
          "library": "react-dom",
          "urls": [
            "https://unpkg.com/react-dom@16.13.1/umd/react-dom.production.min.js"
          ]
        }
      ])
      const newAssetPath = path.resolve(assetPath, '../assets_cmp.json')
      fs.writeFileSync(newAssetPath, JSON.stringify(assetsObj, null, 2))
      ssMetaTool.uploadAndUpdateAsset({
        token: env.CMP_USER_TOKEN,
        path: path.relative('./', newAssetPath),
        id: env.CMP_PKG_ID,
        // PS: Component平台正式环境值为 prod
        env: env.TAM_API_ENV === 'production' ? 'prod' : env.TAM_API_ENV,
      })
    }
  } catch (e) {
    console.warn('发布至Component平台失败', e)
    process.exitCode = 231
  }
}

async function start() {
  if (!env.projectRoot) {
    console.log('未配置环境变量 projectRoot,使用缺省值 ./\n')
    Object.assign(env, {
      projectRoot: './',
    })
  }
  if (!fs.existsSync(path.resolve(env.projectRoot, 'lowcode'))) {
    console.log('不存在 lowcode 文件夹,不发布低代码物料')
    process.exit(231)
  }

  try {
    const cornConfPath = env.CORN_CONF_PATH
    execSync(
      `node_modules/.bin/corn-lowcode build${cornConfPath ? ` -c ${cornConfPath}` : ''}`,
      { stdio: 'inherit' }
    )
  } catch (e) {
    console.error('[execSync error]', e)
    process.exit(1)
  }

  try {
    const compsRelDir = env.projectRoot || './'
    const packObj = require(path.resolve(compsRelDir, './package.json')) || {}
    const compName = (packObj.name || '').replace(/^[^/]+\//, '')
    const { packages, metaJson, umdUrl } = getAssetsInfo(compsRelDir, packObj.name)

    await axPost('/api/tam/paas/setPackages', {
      projectGit: env.AWP_GIT_REPO,
      packages: [
        {
          dirPath: compsRelDir, // 资产包入口路径,以 './' 开头
          name: compName,
          npm: packObj.name,
          version: packObj.version, // 资产包版本号
          versionType: env.AWP_DEPLOY_ENV === 'production' ? 'prod' : 'beta', // 版本类型,beta、prod
          assetType: 'react-component', // 资产类型,枚举:'vue-component', 'react-component', 'setter'等
          umdUrl,
          packages, // 依赖的资源
        },
      ],
    }).then(resp => {
      console.log('----------------------------')
      console.log('setPackages ===>', JSON.stringify(resp.data))
      console.log('----------------------------')
    })

    if (env.TAM_PKG_TAG) {
      await axPost(`/api/tam/paas/ba/setPackageTag`, {
        projectGit: env.AWP_GIT_REPO,
        npm: packObj.name,
        tagIds: env.TAM_PKG_TAG,
        isCover: env.IS_TAG_OVERWRITE || '0',
      })
        .then(resp => {
          console.log('----------------------------')
          console.log('setPackageTag ===>', JSON.stringify(resp.data))
          console.log('----------------------------')
        })
        .catch(e => {
          console.warn('setPackageTag失败 ===>', e)
          process.exitCode = 231
        })
    }

    const entryPath = getLowcodeEntry(compsRelDir)
    const tmpResult = reactParser({
      cwd: process.cwd(),
      projectType: 'Rome-Component',
      projectRoot: compsRelDir,
      ...(entryPath ? { entryPath } : {}),
    })
    // if (isLocal) {
    //   const tmpFilePath = path.resolve(__dirname, './tmpResult.json')
    //   fs.writeFileSync(tmpFilePath, JSON.stringify(tmpResult))
    // }

    const tmpPkg = (tmpResult.packages || [])[0] || {}
    const tamCmps = (tmpPkg.assets && tmpPkg.assets.components) || []
    // metaJson检测是否存在
    if (tamCmps[0] && !tamCmps[0].metaJson) {
      console.log('metaJson不存在, 需要赋值')
      tamCmps[0].metaJson = metaJson
    }

    await axPost('/api/tam/paas/setComponents', {
      projectGit: env.AWP_GIT_REPO,
      packageDirPath: compsRelDir,
      version: packObj.version || '',
      assetType: 'react-component',
      tag: env.AWP_DEPLOY_ENV === 'production' ? 'latest' : 'beta',
      components: tmpResult.packages,
    }).then(resp => {
      console.log('----------------------------')
      console.log('setComponents ===>', JSON.stringify(resp.data))
      console.log('----------------------------')
    })

    publishToComponent(compsRelDir)
  } catch (e) {
    console.error(e)
    process.exit(1)
  }
}

start()

Reference #