UNPKG

@strapi/generate-new

Version:

Generate a new Strapi application.

1 lines 9.97 kB
{"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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"}