import fs from 'fs' import path from 'path' import shell from 'shelljs' import { render } from 'ejs' import { templatesPath } from './config.js' const currentDirectory = process.cwd() const SKIP_FILES = [ 'node_modules', 'dist', '.angular', '.cache', '.DS_Store', '.template.json', ] const ASSETS_EXT = ['.jpg', '.jpeg', '.png', '.gif', '.svg', '.webp', '.ico', '.bmp'] const TEMPLATE_CONFIG_FILE = '.template.json' const resolveDestinationName = (file: string) => { if (file === 'gitignore') return '.gitignore' if (file === 'github') return '.github' if (file === 'cursor') return '.cursor' if (file === 'vscode') return '.vscode' return file } export interface TemplateRenderContext { projectName: string features: string[] hasPwa: boolean hasNotifications: boolean } interface TemplateConfig { removeBasePaths?: string[] } const readTemplateConfig = (templatePath: string): TemplateConfig => { const configPath = path.join(templatePath, TEMPLATE_CONFIG_FILE) if (!fs.existsSync(configPath)) { return {} } const contents = fs.readFileSync(configPath, 'utf8') return JSON.parse(contents) as TemplateConfig } export const createProject = (projectName: string) => { const targetPath = path.join(currentDirectory, projectName) if (fs.existsSync(targetPath)) { throw new Error(`Folder ${targetPath} already exists. Delete or use another name`) } fs.mkdirSync(targetPath) return { targetPath } } export const renderTemplateContents = ( projectName: string, template: string, renderContext: TemplateRenderContext ) => { const templatePath = path.join(templatesPath, template) if (template !== 'base') { createDirectoryContents(projectName, path.join(templatesPath, 'base'), renderContext) } createDirectoryContents(projectName, templatePath, renderContext) const templateConfig = readTemplateConfig(templatePath) templateConfig.removeBasePaths?.forEach((relativePath) => { deletePath(path.join(currentDirectory, projectName), relativePath) }) } export const createDirectoryContents = ( projectName: string, templatePath: string, renderContext: TemplateRenderContext = { projectName: path.basename(projectName), features: [], hasPwa: false, hasNotifications: false, } ) => { // read all files/folders (1 level) from template folder const filesToCreate = fs.readdirSync(templatePath) // loop each file/folder filesToCreate.forEach((file) => { const origFilePath = path.join(templatePath, file) const destinationFile = resolveDestinationName(file) const destinationPath = path.join(currentDirectory, projectName, destinationFile) // get stats about the current file const stats = fs.statSync(origFilePath) // skip files that should not be copied if (SKIP_FILES.indexOf(file) > -1) return if (stats.isFile()) { const ext = path.extname(destinationFile) if (ASSETS_EXT.includes(ext)) { // copy file to destination folder fs.copyFileSync(origFilePath, destinationPath) } else { // read file content and transform it using template engine let contents = fs.readFileSync(origFilePath, 'utf8') contents = render(contents, renderContext) // write file to destination folder fs.writeFileSync(destinationPath, contents, 'utf8') } } else if (stats.isDirectory()) { // create folder in destination folder if (!fs.existsSync(destinationPath)) { fs.mkdirSync(destinationPath) } // copy files/folder inside current folder recursively createDirectoryContents( path.join(projectName, destinationFile), path.join(templatePath, file), renderContext ) } }) } export const deletePath = (targetPath: string, relativePath: string) => { const fullPath = path.join(targetPath, relativePath) if (!fs.existsSync(fullPath)) { return } fs.rmSync(fullPath, { recursive: true, force: true }) } export const installPackages = (targetPath: string, command = 'pnpm install') => { const isNode = fs.existsSync(path.join(targetPath, 'package.json')) if (!isNode) { return } shell.cd(targetPath) const result = shell.exec(command) if (result.code !== 0) { throw new Error(`An error ocurred running ${command}`) } }