Skip to content
代码片段 群组 项目
generation-engine.js 32.8 KB
更新 更旧
  • 了解如何忽略特定修订
  • /**
     *
     *    Copyright (c) 2020 Silicon Labs
     *
     *    Licensed under the Apache License, Version 2.0 (the "License");
     *    you may not use this file except in compliance with the License.
     *    You may obtain a copy of the License at
     *
     *        http://www.apache.org/licenses/LICENSE-2.0
     *
     *    Unless required by applicable law or agreed to in writing, software
     *    distributed under the License is distributed on an "AS IS" BASIS,
     *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     *    See the License for the specific language governing permissions and
     *    limitations under the License.
     */
    
    /**
     * @module JS API: generator logic
     */
    
    const _ = require('lodash')
    
    const fs = require('fs')
    const fsPromise = fs.promises
    
    const path = require('path')
    
    const util = require('../util/util.js')
    
    const queryPackage = require('../db/query-package.js')
    
    const querySession = require('../db/query-session')
    
    const dbEnum = require('../../src-shared/db-enum.js')
    
    Jing Teng's avatar
    Jing Teng 已提交
    const env = require('../util/env')
    
    const templateEngine = require('./template-engine.js')
    
    const dbApi = require('../db/db-api.js')
    
    const dbCache = require('../db/db-cache.js')
    
    Ethan.Z's avatar
    Ethan.Z 已提交
    const queryNotification = require('../db/query-package-notification.js')
    
    
    /**
     * Given a path, it will read generation template object into memory.
     *
    
     * @param {*} path
     * @returns Object that contains: data, crc, templateData
    
    async function loadGenTemplateFromFile(path) {
      let ret = {}
      ret.data = await fsPromise.readFile(path, 'utf8')
      ret.crc = util.checksum(ret.data)
      ret.templateData = JSON.parse(ret.data)
    
      let requiredFeatureLevel = 0
    
      if ('requiredFeatureLevel' in ret.templateData) {
        requiredFeatureLevel = ret.templateData.requiredFeatureLevel
    
      let status = util.matchFeatureLevel(requiredFeatureLevel, path)
    
      if (status.match) {
    
      } else {
        throw status.message
      }
    
    async function recordPackageIfNonexistent(
    
      version,
      category,
      description
    
      let pkg = await queryPackage.getPackageByPathAndParent(
    
        db,
        packagePath,
        parentId
      )
    
      if (pkg == null) {
        // doesn't exist
        return queryPackage.insertPathCrc(
          db,
          packagePath,
          null,
          packageType,
          parentId,
    
          version,
          category,
          description
    
        )
      } else {
        // Already exists
        return pkg.id
      }
    
    Timotej Ecimovic's avatar
    Timotej Ecimovic 已提交
    async function loadTemplateOptionsFromJsonFile(
      db,
      packageId,
      category,
      externalPath
    ) {
      let content = await fsPromise.readFile(externalPath, 'utf8')
      let jsonData = JSON.parse(content)
      let codeLabels = []
      for (const code of Object.keys(jsonData)) {
        codeLabels.push({ code: code, label: jsonData[code] })
      }
    
      return queryPackage.insertOptionsKeyValues(
        db,
        packageId,
        category,
        codeLabels
      )
    }
    
    
    /**
     * Given a loading context, it records the package into the packages table and adds the packageId field into the resolved context.
     *
     * @param {*} context
     * @returns promise that resolves with the same context passed in, except packageId added to it
     */
    
    async function recordTemplatesPackage(context) {
    
      let topLevel = await queryPackage.registerTopLevelPackage(
    
    Timotej Ecimovic's avatar
    Timotej Ecimovic 已提交
        context.db,
        context.path,
        context.crc,
        dbEnum.packageType.genTemplatesJson,
    
        context.templateData.version,
        context.templateData.category,
    
    Timotej Ecimovic's avatar
    Timotej Ecimovic 已提交
      )
    
      context.packageId = topLevel.id
      if (topLevel.existedPreviously) return context
    
    
    Timotej Ecimovic's avatar
    Timotej Ecimovic 已提交
      let promises = []
    
      let allTemplates = context.templateData.templates
    
      env.logDebug(`Loading ${allTemplates.length} templates.`)
      allTemplates.forEach((template) => {
    
    Timotej Ecimovic's avatar
    Timotej Ecimovic 已提交
        let templatePath = path.resolve(
          path.join(path.dirname(context.path), template.path)
        )
        if (!template.ignore) {
          promises.push(
            recordPackageIfNonexistent(
              context.db,
              templatePath,
              context.packageId,
              dbEnum.packageType.genSingleTemplate,
    
              0,
              template.output,
              template.name
    
            ).then((id) => {
              // We loaded the individual file, now we add options
              if (template.iterator) {
                return queryPackage.insertOptionsKeyValues(
                  context.db,
                  id,
                  dbEnum.packageOptionCategory.outputOptions,
                  [
                    {
                      code: 'iterator',
                      label: template.iterator,
                    },
                  ]
                )
              }
            })
    
    Timotej Ecimovic's avatar
    Timotej Ecimovic 已提交
      // Add options to the list of promises
      if (context.templateData.options != null) {
        for (const category of Object.keys(context.templateData.options)) {
          let data = context.templateData.options[category]
    
    Timotej Ecimovic's avatar
    Timotej Ecimovic 已提交
          if (_.isString(data)) {
            // Data is a string, so we will treat it as a relative path to the JSON file.
            let externalPath = path.resolve(
              path.join(path.dirname(context.path), data)
    
    Timotej Ecimovic's avatar
    Timotej Ecimovic 已提交
              loadTemplateOptionsFromJsonFile(
    
    Timotej Ecimovic's avatar
    Timotej Ecimovic 已提交
                category,
                externalPath
    
    Timotej Ecimovic's avatar
    Timotej Ecimovic 已提交
          } else {
            // Treat this data as an object.
            let codeLabelArray = []
            for (const code of Object.keys(data)) {
    
    Timotej Ecimovic's avatar
    Timotej Ecimovic 已提交
              codeLabelArray.push({
                code: code,
                label: data[code],
              })
    
    Timotej Ecimovic's avatar
    Timotej Ecimovic 已提交
            }
    
    Timotej Ecimovic's avatar
    Timotej Ecimovic 已提交
              queryPackage.insertOptionsKeyValues(
    
    Timotej Ecimovic's avatar
    Timotej Ecimovic 已提交
                category,
                codeLabelArray
    
    Timotej Ecimovic's avatar
    Timotej Ecimovic 已提交
      // Deal with categories
      let helperCategories = []
      if (context.templateData.categories != null) {
        context.templateData.categories.forEach((cat) => {
          helperCategories.push({
            code: cat,
            label: '',
          })
        })
      }
      if (helperCategories.length > 0) {
        promises.push(
          queryPackage.insertOptionsKeyValues(
            context.db,
            context.packageId,
            dbEnum.packageOptionCategory.helperCategories,
            helperCategories
          )
        )
      }
    
    
    Timotej Ecimovic's avatar
    Timotej Ecimovic 已提交
      // Deal with helpers
    
    Timotej Ecimovic's avatar
    Timotej Ecimovic 已提交
      let helperAliases = []
    
    Timotej Ecimovic's avatar
    Timotej Ecimovic 已提交
      if (context.templateData.helpers != null) {
        context.templateData.helpers.forEach((helper) => {
    
    Timotej Ecimovic's avatar
    Timotej Ecimovic 已提交
          let pkg = templateEngine.findHelperPackageByAlias(helper)
    
          if (pkg != null) {
            // The helper listed is an alias to a built-in helper
            // Put it in the array to write into DB later.
            helperAliases.push({
              code: helper,
              label: '',
            })
          } else {
            // We don't have an alias by that name, so we assume it's a path.
            let helperPath = path.join(path.dirname(context.path), helper)
            promises.push(
              recordPackageIfNonexistent(
                context.db,
                helperPath,
                context.packageId,
                dbEnum.packageType.genHelper,
                null,
                null,
                null
              )
    
    Timotej Ecimovic's avatar
    Timotej Ecimovic 已提交
            )
    
    Timotej Ecimovic's avatar
    Timotej Ecimovic 已提交
        })
      }
    
    Timotej Ecimovic's avatar
    Timotej Ecimovic 已提交
      if (helperAliases.length > 0) {
        promises.push(
          queryPackage.insertOptionsKeyValues(
            context.db,
            context.packageId,
            dbEnum.packageOptionCategory.helperAliases,
            helperAliases
          )
        )
      }
    
      // Deal with resource references
      let resources = []
      if (context.templateData.resources != null) {
        for (let key of Object.keys(context.templateData.resources)) {
          let resourcePath = path.join(
            path.dirname(context.path),
            context.templateData.resources[key]
          )
          if (!fs.existsSync(resourcePath))
            throw new Error(`Resource not found: ${resourcePath}`)
          resources.push({
            code: key,
            label: resourcePath,
          })
        }
      }
      if (resources.length > 0) {
        promises.push(
          queryPackage.insertOptionsKeyValues(
            context.db,
            context.packageId,
            dbEnum.packageOptionCategory.resources,
            resources
          )
        )
      }
    
    Timotej Ecimovic's avatar
    Timotej Ecimovic 已提交
    
      // Deal with overrides
      if (context.templateData.override != null) {
        let overridePath = path.join(
          path.dirname(context.path),
          context.templateData.override
        )
        promises.push(
          recordPackageIfNonexistent(
            context.db,
            overridePath,
            context.packageId,
            dbEnum.packageType.genOverride,
    
            null,
            null,
    
    Timotej Ecimovic's avatar
    Timotej Ecimovic 已提交
            null
          )
        )
      }
      // Deal with partials
      if (context.templateData.partials != null) {
        context.templateData.partials.forEach((partial) => {
          let partialPath = path.join(path.dirname(context.path), partial.path)
          promises.push(
            queryPackage.insertPathCrc(
              context.db,
              partialPath,
              null,
              dbEnum.packageType.genPartial,
              context.packageId,
    
              0,
              partial.name,
              ''
    
    Timotej Ecimovic's avatar
    Timotej Ecimovic 已提交
            )
          )
    
    Timotej Ecimovic's avatar
    Timotej Ecimovic 已提交
      }
    
      // Deal with zcl extensions
      if (context.templateData.zcl != null) {
        let zclExtension = context.templateData.zcl
        promises.push(
          loadZclExtensions(
            context.db,
            context.packageId,
            zclExtension,
            context.path
          )
        )
      }
    
    Timotej Ecimovic's avatar
    Timotej Ecimovic 已提交
      await Promise.all(promises)
      return context
    
    /**
     * This method takes extension data in JSON, and converts it into
     * an object that contains:
     *    entityCode, entityQualifier, parentCode, manufacturerCode and value
     * @param {*} entityType
     * @param {*} entity
     * @returns object that can be used for database injection
     */
    
    function decodePackageExtensionEntity(entityType, entity) {
      switch (entityType) {
        case dbEnum.packageExtensionEntity.cluster:
          return {
    
            entityCode: entity.clusterCode,
    
            parentCode: null,
            value: entity.value,
          }
        case dbEnum.packageExtensionEntity.command:
          return {
    
            entityCode: parseInt(entity.commandCode),
    
            entityQualifier: entity.source,
            manufacturerCode: null,
    
            parentCode: parseInt(entity.clusterCode),
    
        case dbEnum.packageExtensionEntity.event:
          return {
            entityCode: parseInt(entity.eventCode),
            manufacturerCode: null,
            parentCode: parseInt(entity.clusterCode),
            value: entity.value,
          }
    
        case dbEnum.packageExtensionEntity.attribute:
          return {
    
            entityCode: parseInt(entity.attributeCode),
    
            entityQualifier: null,
            manufacturerCode: null,
    
            parentCode: parseInt(entity.clusterCode),
    
            value: entity.value,
          }
        case dbEnum.packageExtensionEntity.deviceType:
          return {
            entityCode: entity.device,
    
            entityQualifier: null,
            manufacturerCode: null,
    
        case dbEnum.packageExtensionEntity.attributeType:
          return {
            entityCode: null,
            entityQualifier: entity.type,
            manufacturerCode: null,
            parentCode: null,
            value: entity.value,
          }
    
        default:
          // We don't know how to process defaults otherwise
          return null
      }
    }
    
    
    /**
     * Returns a promise that will load the zcl extensions.
     *
     * @param {*} zclExt
     * @returns Promise of loading the zcl extensions.
     */
    
    async function loadZclExtensions(db, packageId, zclExt, defaultsPath) {
    
      for (const entity of Object.keys(zclExt)) {
    
        let entityExtension = zclExt[entity]
        let propertyArray = []
        let defaultArrayOfArrays = []
    
        for (const property of Object.keys(entityExtension)) {
    
          let prop = entityExtension[property]
    
          propertyArray.push({
            property: property,
            type: prop.type,
            configurability: prop.configurability,
            label: prop.label,
            globalDefault: prop.globalDefault,
          })
    
            if (
              typeof prop.defaults === 'string' ||
              prop.defaults instanceof String
            ) {
              // Data is a string, so we will treat it as a relative path to the JSON file.
              let externalPath = path.resolve(
                path.join(path.dirname(defaultsPath), prop.defaults)
              )
              let data = await fsPromise
                .readFile(externalPath, 'utf8')
                .then((content) => JSON.parse(content))
                .catch((err) => {
    
    Timotej Ecimovic's avatar
    Timotej Ecimovic 已提交
                  env.logWarning(
    
                    `Invalid file! Failed to load defaults from: ${prop.defaults}`
                  )
    
                  queryNotification.setNotification(
                    db,
                    'WARNING',
                    `Invalid file! Failed to load defaults from: ${prop.defaults}`,
                    packageId,
                    2
                  )
    
    Timotej Ecimovic's avatar
    Timotej Ecimovic 已提交
                  env.logWarning(
    
                    `Invalid file format! Failed to load defaults from: ${prop.defaults}`
                  )
    
                  queryNotification.setNotification(
                    db,
                    'WARNING',
                    `Invalid file format! Failed to load defaults from: ${prop.defaults}`,
                    packageId,
                    2
                  )
    
                } else {
                  defaultArrayOfArrays.push(
                    data.map((x) => decodePackageExtensionEntity(entity, x))
                  )
    
              }
            } else {
              defaultArrayOfArrays.push(
                prop.defaults.map((x) => decodePackageExtensionEntity(entity, x))
              )
            }
    
          queryPackage.insertPackageExtension(
            db,
            packageId,
            entity,
            propertyArray,
            defaultArrayOfArrays
          )
    
    /**
     * Api that loads an array of template JSON files or a single file if
     * you just pass in one String.
     *
     * @param {*} db
     * @param {*} genTemplatesJsonArray
     */
    async function loadTemplates(
      db,
      genTemplatesJsonArray,
      options = {
        failOnLoadingError: true,
      }
    ) {
      if (Array.isArray(genTemplatesJsonArray)) {
        let globalCtx = {
          packageIds: [],
          packageId: null,
    
        }
        if (genTemplatesJsonArray != null && genTemplatesJsonArray.length > 0) {
          for (let jsonFile of genTemplatesJsonArray) {
    
            if (jsonFile == null || jsonFile == '') continue
    
            let ctx = await loadGenTemplatesJsonFile(db, jsonFile)
    
            if (ctx.error) {
              if (options.failOnLoadingError) globalCtx.error = ctx.error
            } else {
              if (globalCtx.packageId == null) {
                globalCtx.packageId = ctx.packageId
              }
    
              globalCtx.packageIds.push(ctx.packageId)
            }
          }
        }
        return globalCtx
    
      } else if (genTemplatesJsonArray != null) {
    
        let ctx = await loadGenTemplatesJsonFile(db, genTemplatesJsonArray)
    
        ctx.packageIds = [ctx.packageId]
        return ctx
    
      } else {
        // We didn't load anything, we don't return anything.
        return {
          nop: true,
        }
    
     * Main API async function to load templates from a gen-template.json file.
    
     *
     * @param {*} db Database
    
     * @param {*} genTemplatesJson Path to the JSON file or an array of paths to JSON file
    
     * @returns the loading context, contains: db, path, crc, packageId and templateData, or error
    
    async function loadGenTemplatesJsonFile(db, genTemplatesJson) {
    
      if (genTemplatesJson == null) {
        context.error = 'No templates file specified.'
    
        env.logWarning(context.error)
    
      let isTransactionAlreadyExisting = dbApi.isTransactionActive()
    
      let file = path.resolve(genTemplatesJson)
    
      if (!fs.existsSync(file)) {
        context.error = `Can't locate templates file: ${file}`
    
        env.logWarning(context.error)
    
      if (!isTransactionAlreadyExisting) await dbApi.dbBeginTransaction(db)
    
        Object.assign(context, await loadGenTemplateFromFile(file))
    
        context = await recordTemplatesPackage(context)
        return context
      } catch (err) {
    
        env.logInfo(`Can not read templates from: ${file}`)
    
        if (!isTransactionAlreadyExisting) await dbApi.dbCommit(db)
    
    Timotej Ecimovic's avatar
    Timotej Ecimovic 已提交
    async function retrievePackageMetaInfo(db, genTemplatesPkgId) {
      let metaInfo = {
        aliases: [],
        categories: [],
        resources: {},
      }
    
      let aliases = await queryPackage.selectAllOptionsValues(
        db,
        genTemplatesPkgId,
        dbEnum.packageOptionCategory.helperAliases
      )
      for (let a of aliases) {
        metaInfo.aliases.push(a.optionCode)
      }
    
      let categories = await queryPackage.selectAllOptionsValues(
        db,
        genTemplatesPkgId,
        dbEnum.packageOptionCategory.helperCategories
      )
      for (let c of categories) {
        metaInfo.categories.push(c.optionCode)
      }
    
      let resources = await queryPackage.selectAllOptionsValues(
        db,
        genTemplatesPkgId,
        dbEnum.packageOptionCategory.resources
      )
      for (let c of resources) {
        metaInfo.resources[c.optionCode] = c.optionLabel
      }
    
      return metaInfo
    }
    
    
    /**
     * Generates all the templates inside a toplevel package.
     *
     * @param {*} genResult
    
     * @param {*} genTemplateJsonPkg Package that points to genTemplate.json file
    
     * @param {*} generateOnly if NULL then generate all templates, else only generate template whose out file name matches this.
    
     * @returns Promise that resolves with genResult, that contains all the generated templates, keyed by their 'output'
     */
    
    async function generateAllTemplates(
    
      genResult,
      genTemplateJsonPkg,
    
      options = {
        generateOnly: null,
        disableDeprecationWarnings: false,
    
        generateSequentially: false,
    
      let packages = await queryPackage.getPackageByParent(
        genResult.db,
        genTemplateJsonPkg.id
      )
      let generationTemplates = []
      let helperPromises = []
      let partialPromises = []
      let overridePath = null
    
      let context = {
        db: genResult.db,
        sessionId: genResult.sessionId,
        hb: hb,
      }
    
      for (let pkg of packages) {
        let outputOptions = await queryPackage.selectAllOptionsValues(
          genResult.db,
          pkg.id,
          dbEnum.packageOptionCategory.outputOptions
        )
        outputOptions.forEach((opt) => {
          if (opt.optionCode == 'iterator') {
            pkg.iterator = opt.optionLabel
          }
        })
      }
    
    
      // First extract overridePath if one exists, as we need to
      // pass it to the generation.
      packages.forEach((singlePkg) => {
        if (singlePkg.type == dbEnum.packageType.genOverride) {
          overridePath = singlePkg.path
        }
      })
    
      // Next load the partials
      packages.forEach((singlePkg) => {
        if (singlePkg.type == dbEnum.packageType.genPartial) {
          partialPromises.push(
    
            templateEngine.loadPartial(hb, singlePkg.category, singlePkg.path)
    
    Timotej Ecimovic's avatar
    Timotej Ecimovic 已提交
      // Let's collect the required list of helpers.
      let metaInfo = await retrievePackageMetaInfo(
        genResult.db,
        genTemplateJsonPkg.id
      )
    
      // Initialize helpers package. This is based on the specific
      // list that was calculated above in the `metaInfo`
      templateEngine.initializeBuiltInHelpersForPackage(hb, metaInfo)
    
    Timotej Ecimovic's avatar
    Timotej Ecimovic 已提交
      // Next load the addon helpers which were not yet initialized earlier.
    
      packages.forEach((singlePkg) => {
        if (singlePkg.type == dbEnum.packageType.genHelper) {
    
          helperPromises.push(
            templateEngine.loadHelper(hb, singlePkg.path, context)
          )
    
        }
      })
    
      // Next prepare the templates
      packages.forEach((singlePkg) => {
        if (singlePkg.type == dbEnum.packageType.genSingleTemplate) {
    
          if (options.generateOnly == null) {
            generationTemplates.push(singlePkg)
          } else if (
            Array.isArray(options.generateOnly) &&
            options.generateOnly.includes(singlePkg.category)
    
            // If generateOnly is an array that contains the name
            generationTemplates.push(singlePkg)
          } else if (options.generateOnly == singlePkg.category) {
            // If the generate Only contains the name, then we include it
    
            generationTemplates.push(singlePkg)
          }
        }
      })
    
      // And finally go over the actual templates.
      await Promise.all(helperPromises)
      await Promise.all(partialPromises)
    
    
      if (options.generateSequentially) {
        await util.executePromisesSequentially(generationTemplates, (t) =>
          generateSingleTemplate(hb, metaInfo, genResult, t, genTemplateJsonPkg, {
            overridePath: overridePath,
            disableDeprecationWarnings: options.disableDeprecationWarnings,
          })
        )
      } else {
        let templates = generationTemplates.map((pkg) =>
          generateSingleTemplate(hb, metaInfo, genResult, pkg, genTemplateJsonPkg, {
            overridePath: overridePath,
            disableDeprecationWarnings: options.disableDeprecationWarnings,
          })
        )
        await Promise.all(templates)
      }
    
    
      genResult.partial = false
      return genResult
    
    /**
     * Function that generates a single package and adds it to the generation result.
     *
     * @param {*} genResult
    
     * @param {*} singleTemplatePkg Single template package.
    
     * @returns promise that resolves with the genResult, with newly generated content added.
     */
    
    async function generateSingleTemplate(
    
    Timotej Ecimovic's avatar
    Timotej Ecimovic 已提交
      metaInfo,
    
      genResult,
      singleTemplatePkg,
    
      genTemplateJsonPackage,
    
      options = {
        overridePath: null,
        disableDeprecationWarnings: false,
      }
    
      let genStart = process.hrtime.bigint()
      //console.log(`Start generating from template: ${singleTemplatePkg?.path}`)
      env.logInfo(`Start generating from template: ${singleTemplatePkg?.path}`)
    
      let genFunction
      if (singleTemplatePkg.iterator != null) {
        genFunction = templateEngine.produceIterativeContent
      } else {
        genFunction = templateEngine.produceContent
      }
    
        let resultArray = await genFunction(
    
    Timotej Ecimovic's avatar
    Timotej Ecimovic 已提交
          metaInfo,
    
          genResult.db,
          genResult.sessionId,
          singleTemplatePkg,
    
          genTemplateJsonPackage,
    
        for (let result of resultArray) {
          genResult.content[result.key] = result.content
          genResult.stats[result.key] = result.stats
        }
    
        genResult.partial = true
    
        let nsDuration = process.hrtime.bigint() - genStart
        //console.log(`Finish generating from template: ${singleTemplatePkg?.path}: ${util.duration(nsDuration)}`)
    
          `Finish generating from template: ${
            singleTemplatePkg?.path
          }: ${util.duration(nsDuration)}`
    
        return genResult
      } catch (err) {
    
        genResult.errors[singleTemplatePkg.category] = err
    
        genResult.hasErrors = true
      }
    
     * Main API async function to generate stuff.
    
     *
     * @param {*} db Database
    
     * @param {*} sessionId
     * @param {*} templatePackageId packageId Template package id. It can be either single template or gen template json.
     * @returns Promise that resolves into a generation result.
     * @param {*} templateGeneratorOptions
     * @param {*} options
    
     * @returns Promise that resolves into a generation result.
     */
    
    async function generate(
    
      templateGeneratorOptions = {},
      options = {
        generateOnly: null,
        disableDeprecationWarnings: false,
      }
    
      let pkg = await queryPackage.getPackageByPackageId(db, templatePackageId)
      if (pkg == null) throw new Error(`Invalid packageId: ${templatePackageId}`)
      let genResult = {
        db: db,
        sessionId: sessionId,
        content: {},
        stats: {},
        errors: {},
        hasErrors: false,
        generatorOptions: templateGeneratorOptions,
        templatePath: path.dirname(pkg.path),
      }
      if (pkg.type === dbEnum.packageType.genTemplatesJson) {
        return generateAllTemplates(genResult, pkg, options)
      } else {
        throw new Error(`Invalid package type: ${pkg.type}`)
      }
    
    /**
     * Promise to write out a file, optionally creating a backup.
     *
     * @param {*} fileName
     * @param {*} content
     * @param {*} doBackup
     * @returns promise of a written file.
     */
    
    async function writeFileWithBackup(fileName, content, doBackup) {
    
      if (doBackup && fs.existsSync(fileName)) {
    
        let backupName = fileName.concat('~')
    
        await fsPromise.rename(fileName, backupName)
        return fsPromise.writeFile(fileName, content)
    
        // we need to ensure that directories exist.
        await fsPromise.mkdir(path.dirname(fileName), { recursive: true })
    
        return fsPromise.writeFile(fileName, content)
      }
    }
    
    
    /**
     * Returns a promise that resolves into a content that should be written out to gen result file.
     *
     * @param {*} genResult
     */
    
    async function generateGenerationContent(genResult, timing = {}) {
    
        writeTime: new Date().toString(),
    
        featureLevel: env.zapVersion().featureLevel,
    
        creator: 'zap',
        content: [],
    
        timing: timing,
    
      for (const f of Object.keys(genResult.content).sort()) {
    
      return Promise.resolve(JSON.stringify(out, null, 2))
    
    /**
     * Generate files and write them into the given directory.
     *
     * @param {*} db
     * @param {*} sessionId
     * @param {*} packageId
     * @param {*} outputDirectory
     * @returns a promise which will resolve when all the files are written.
     */
    
    async function generateAndWriteFiles(
    
    Timotej Ecimovic's avatar
    Timotej Ecimovic 已提交
        logger: (msg) => {
          // Empty logger is the default.
        },
    
        genResultFile: false,
    
      // in case user customization has invalidated the cache
      dbCache.clear()
    
    
      let timing = {}
      if (options.fileLoadTime) {
        timing.fileLoad = {
          nsDuration: Number(options.fileLoadTime),
          readableDuration: util.duration(options.fileLoadTime),
        }
      }
    
      let hrstart = process.hrtime.bigint()
    
      let genOptions = await queryPackage.selectAllOptionsValues(
        db,
        templatePackageId,
        dbEnum.packageOptionCategory.generator
      )
    
      // Reduce the long array from query into a single object
      let templateGeneratorOptions = genOptions.reduce((acc, current) => {
        acc[current.optionCode] = current.optionLabel
        return acc
      }, {})
    
      let genResult = await generate(
        db,
        sessionId,
        templatePackageId,
        templateGeneratorOptions
      )
    
    
      // The path we append, assuming you specify the --appendGenerationSubdirectory, and a
      // appendDirectory generator option is present in the genTemplates.json file
      let appendedPath = null
      if (
        templateGeneratorOptions.appendDirectory != null &&
        options.appendGenerationSubdirectory
      ) {
        appendedPath = templateGeneratorOptions.appendDirectory
      }
    
      if (appendedPath != null) {
        outputDirectory = path.join(outputDirectory, appendedPath)
      }
    
    
      if (!fs.existsSync(outputDirectory)) {
        options.logger(`✅ Creating directory: ${outputDirectory}`)
        fs.mkdirSync(outputDirectory, { recursive: true })
      }
    
      options.logger('🤖 Generating files:')
      let promises = []
    
      for (const f of Object.keys(genResult.content)) {
    
        let content = genResult.content[f]
        let fileName = path.join(outputDirectory, f)
        options.logger(`    ✍  ${fileName}`)
        env.logDebug(`Preparing to write file: ${fileName}`)
        promises.push(writeFileWithBackup(fileName, content, options.backup))
      }
      if (genResult.hasErrors) {
        options.logger('⚠️  Errors:')
    
        for (const f of Object.keys(genResult.errors)) {
    
          let err = genResult.errors[f]
          let fileName = path.join(outputDirectory, f)
          options.logger(`    👎  ${fileName}: ⛔ ${err}\nStack trace:\n`)
          options.logger(err)
        }
      }
    
      let nsDuration = process.hrtime.bigint() - hrstart
      options.logger(`🕐 Generation time: ${util.duration(nsDuration)} `)
    
      timing.generation = {
        nsDuration: Number(nsDuration),
        readableDuration: util.duration(nsDuration),
      }
    
        generateGenerationContent(genResult, timing).then((generatedContent) => {
    
            let resultPath = path.join(outputDirectory, 'genResult.json')
            options.logger(`    ✍  Result: ${resultPath}`)
            return writeFileWithBackup(resultPath, generatedContent, options.backup)
    
      if (options.generationLog) {
        let pkg = await queryPackage.getPackageByPackageId(db, templatePackageId)
        let filePath = await querySession.getSessionKeyValue(
          db,
          sessionId,
          dbEnum.sessionKey.filePath