{"version":3,"file":"merge-template.mjs","sources":["../../src/utils/merge-template.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-var-requires */\nimport os from 'os';\nimport path from 'path';\nimport fse from 'fs-extra';\nimport _ from 'lodash/fp';\nimport chalk from 'chalk';\nimport { getTemplatePackageInfo, downloadNpmTemplate } from './fetch-npm-template';\nimport type { PackageInfo, Scope, TemplateConfig } from '../types';\n\ninterface ErrorWithCode extends Error {\n  code?: string;\n}\n\n// Specify all the files and directories a template can have\nconst allowFile = Symbol('alloFile');\nconst allowChildren = Symbol('allowChildren');\nconst allowedTemplateContents = {\n  'README.md': allowFile,\n  '.env.example': allowFile,\n  'package.json': allowFile,\n  src: allowChildren,\n  data: allowChildren,\n  database: allowChildren,\n  public: allowChildren,\n  scripts: allowChildren,\n};\n\n// Merge template with new project being created\nexport default async function mergeTemplate(scope: Scope, rootPath: string) {\n  if (!scope.template) {\n    throw new Error('Missing template');\n  }\n\n  let templatePath;\n  let templateParentPath;\n  let templatePackageInfo: PackageInfo | undefined;\n  const isLocalTemplate = ['./', '../', '/'].some((filePrefix) =>\n    scope.template?.startsWith(filePrefix)\n  );\n\n  if (isLocalTemplate) {\n    // Template is a local directory\n    console.log('Installing local template.');\n    templatePath = path.resolve(rootPath, '..', scope.template);\n  } else {\n    // Template should be an npm package. Fetch template info\n    templatePackageInfo = await getTemplatePackageInfo(scope.template);\n    console.log(`Installing ${chalk.yellow(templatePackageInfo.name)} template.`);\n\n    // Download template repository to a temporary directory\n    templateParentPath = await fse.mkdtemp(path.join(os.tmpdir(), 'strapi-'));\n    templatePath = await downloadNpmTemplate(templatePackageInfo, templateParentPath);\n  }\n\n  // Make sure the downloaded template matches the required format\n  const templateConfig = await checkTemplateRootStructure(templatePath);\n  await checkTemplateContentsStructure(path.resolve(templatePath, 'template'));\n\n  // Merge contents of the template in the project\n  await mergePackageJSON({ rootPath, templateConfig, templatePackageInfo });\n  await mergeFilesAndDirectories(rootPath, templatePath);\n\n  // Delete the template directory if it was downloaded\n  if (!isLocalTemplate && templateParentPath) {\n    await fse.remove(templateParentPath);\n  }\n}\n\n// Make sure the template has the required top-level structure\nasync function checkTemplateRootStructure(templatePath: string): Promise<TemplateConfig> {\n  // Make sure the root of the repo has a template.json file\n  const templateJsonPath = path.join(templatePath, 'template.json');\n  const templateJsonExists = await fse.pathExists(templateJsonPath);\n  if (!templateJsonExists) {\n    throw new Error(`A template must have a ${chalk.green('template.json')} root file`);\n  }\n  const templateJsonStat = await fse.stat(templateJsonPath);\n  if (!templateJsonStat.isFile()) {\n    throw new Error(`A template's ${chalk.green('template.json')} must be a file`);\n  }\n\n  const templateConfig = require(templateJsonPath);\n\n  // Make sure the root of the repo has a template folder\n  const templateDirPath = path.join(templatePath, 'template');\n  try {\n    const stat = await fse.stat(templateDirPath);\n    if (!stat.isDirectory()) {\n      throw Error(`A template must have a root ${chalk.green('template/')} directory`);\n    }\n  } catch (error) {\n    if (error instanceof Error && (error as ErrorWithCode).code === 'ENOENT') {\n      throw Error(`A template must have a root ${chalk.green('template/')} directory`);\n    }\n\n    throw error;\n  }\n\n  return templateConfig;\n}\n\n// Traverse template tree to make sure each file and folder is allowed\nasync function checkTemplateContentsStructure(templateContentsPath: string) {\n  // Recursively check if each item in a directory is allowed\n  const checkPathContents = async (pathToCheck: string, parents: string[]) => {\n    const contents = await fse.readdir(pathToCheck);\n    for (const item of contents) {\n      const nextParents = [...parents, item];\n      const matchingTreeValue = _.get(nextParents, allowedTemplateContents);\n\n      // Treat files and directories separately\n      const itemPath = path.resolve(pathToCheck, item);\n      const isDirectory = (await fse.stat(itemPath)).isDirectory();\n\n      if (matchingTreeValue === undefined) {\n        // Unknown paths are forbidden\n        throw Error(\n          `Illegal template structure, unknown path ${chalk.green(nextParents.join('/'))}`\n        );\n      }\n\n      if (matchingTreeValue === allowFile) {\n        if (!isDirectory) {\n          // All good, the file is allowed\n          return;\n        }\n        throw Error(\n          `Illegal template structure, expected a file and got a directory at ${chalk.green(\n            nextParents.join('/')\n          )}`\n        );\n      }\n\n      if (isDirectory) {\n        if (matchingTreeValue === allowChildren) {\n          // All children are allowed\n          return;\n        }\n        // Check if the contents of the directory are allowed\n        await checkPathContents(itemPath, nextParents);\n      } else {\n        throw Error(\n          `Illegal template structure, unknown file ${chalk.green(nextParents.join('/'))}`\n        );\n      }\n    }\n  };\n\n  await checkPathContents(templateContentsPath, []);\n}\n// Merge the template's template.json into the Strapi project's package.json\nasync function mergePackageJSON({\n  rootPath,\n  templateConfig,\n  templatePackageInfo,\n}: {\n  rootPath: string;\n  templateConfig: TemplateConfig;\n  templatePackageInfo: PackageInfo | undefined;\n}) {\n  // Import the package.json as an object\n  const packageJSON = require(path.resolve(rootPath, 'package.json'));\n\n  if (!templateConfig.package) {\n    // Nothing to overwrite\n    return;\n  }\n\n  // Make sure the template.json doesn't overwrite the UUID\n  if (_.has('strapi.uuid', templateConfig.package)) {\n    throw Error('A template cannot overwrite the Strapi UUID');\n  }\n\n  // Use lodash to deeply merge them\n  const mergedConfig = _.merge(templateConfig.package, packageJSON);\n\n  // Add template info to package.json\n  if (templatePackageInfo?.name) {\n    _.set('strapi.template', templatePackageInfo.name, mergedConfig);\n  }\n\n  // Save the merged config as the new package.json\n  const packageJSONPath = path.join(rootPath, 'package.json');\n  await fse.writeJSON(packageJSONPath, mergedConfig, { spaces: 2 });\n}\n\n// Merge all allowed files and directories\nasync function mergeFilesAndDirectories(rootPath: string, templatePath: string) {\n  const templateDir = path.join(templatePath, 'template');\n  await fse.copy(templateDir, rootPath, { overwrite: true, recursive: true });\n}\n"],"names":[],"mappings":"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"}