import path from 'path'; import * as fs from 'fs'; import * as readline from 'readline'; import { slugify } from '../utils'; const { execSync } = require('child_process'); const loadingSpinner = require('loading-spinner'); interface Question { text: string; optional: boolean; answerKey: string; hint?: string; } interface Answers { [key: string]: string; } const workingDir = path.resolve(process.cwd()); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); const ANSWERS: Answers = {}; const QUESTIONS: Array = [ { text: 'Brand name', optional: false, answerKey: 'brandName', hint: 'eg. Project Zero' }, { text: 'Project description', optional: true, answerKey: 'projectDescription' }, { text: 'Commerce URL (SERVICE_BACKEND_URL)', optional: true, answerKey: 'commerceUrl', hint: 'Can be changed later' } ]; const getAnswers = () => { return new Promise((resolve) => { const question = QUESTIONS[Object.keys(ANSWERS).length]; if (!question) { rl.close(); resolve(ANSWERS); return; } let questionText = question.text; if (question.hint) { questionText += ` (${question.hint})`; } if (question.optional) { questionText += ` (optional)`; } rl.question(`${questionText}: `, (answer: string) => { if (!question.optional && !answer.length) { console.log('\x1b[31m%s\x1b[0m', `* This field is required\n`); resolve(getAnswers()); } else { ANSWERS[question.answerKey] = answer; resolve(getAnswers()); } }); }); }; const updateFileContents = ( filePath: string, replacements: { [key: string]: string } ): void => { let content = fs.readFileSync(filePath, { encoding: 'utf8' }); Object.entries(replacements).forEach(([key, value]) => { if (filePath.endsWith('.json')) { content = content.replace( new RegExp(`"${key}":\\s*".*?"`, 'g'), `"${key}": "${value}"` ); } else { content = content.replace( new RegExp(`^${key}=.*$`, 'gm'), `${key}=${value}` ); } }); fs.writeFileSync(filePath, content, { encoding: 'utf8' }); }; function updatePluginsFile(projectDir: string): void { const pluginsFilePath = path.join(projectDir, 'src', 'plugins.js'); const content = 'module.exports = [];'; fs.writeFileSync(pluginsFilePath, content, { encoding: 'utf8' }); } function createEnvironmentFile(projectDir: string) { fs.copyFileSync(`${projectDir}/.env.example`, `${projectDir}/.env`); } function checkGitignore(projectDir: string) { const gitignorePath = path.join(projectDir, '.gitignore'); const npmignorePath = path.join(projectDir, '.npmignore'); if (!fs.existsSync(gitignorePath)) { fs.renameSync(npmignorePath, gitignorePath); } else if (fs.existsSync(npmignorePath)) { fs.rmSync(npmignorePath); } } export default async (): Promise => { const minNodeVersion = 18; const currentNodeVersion = parseInt( process.version.replace('v', '').split('.')[0] ); if (currentNodeVersion < minNodeVersion) { console.log( '\x1b[31m%s\x1b[0m', `Node version must be ${minNodeVersion} or higher.\n`.concat( `Current version is ${currentNodeVersion}. Please update your Node version.` ) ); process.exit(1); } const answers = await getAnswers(); const brandName = answers.brandName === '.' ? path.basename(workingDir) : answers.brandName; const projectDir = answers.brandName === '.' ? workingDir : path.resolve(workingDir, slugify(brandName)); const relativeProjectDir = answers.brandName === '.' ? '.' : slugify(brandName); if (!fs.existsSync(projectDir)) { fs.mkdirSync(projectDir, { recursive: true }); } const templatesDir = path.resolve( __dirname, '../../../projectzero/app-template' ); fs.cpSync(templatesDir, projectDir, { recursive: true }); updatePluginsFile(projectDir); createEnvironmentFile(projectDir); checkGitignore(projectDir); fs.chmodSync(path.join(projectDir, 'build.sh'), 0o711); if (answers.projectDescription) { updateFileContents(path.join(projectDir, 'akinon.json'), { description: answers.projectDescription }); } if (answers.commerceUrl) { updateFileContents(path.join(projectDir, '.env'), { SERVICE_BACKEND_URL: answers.commerceUrl }); } updateFileContents(path.join(projectDir, 'package.json'), { name: slugify(brandName) }); console.log('\x1b[34m%s\x1b[0m', '\n🚀 Installing packages...\n'); execSync(`cd ${relativeProjectDir} && yarn install`, { stdio: 'ignore' }); loadingSpinner.stop(); const successMessage = ` ✨ ${brandName} project is ready at \x1b[4m${projectDir}\x1b[0m Within the directory, the following commands are available: \x1b[35m$ yarn dev\x1b[0m \x1b[32mLaunches the development server.\x1b[0m \x1b[35m$ yarn build\x1b[0m \x1b[32mCompiles the app into static files for production.\x1b[0m \x1b[35m$ yarn start\x1b[0m \x1b[32mRuns the production server.\x1b[0m `; const getStartedMessage = answers.brandName === '.' ? 'To get started, you can type:\n\n \x1b[35m$ yarn dev\x1b[0m\n' : `To get started, you can type:\n\n \x1b[35m$ cd ${relativeProjectDir}\x1b[0m\n \x1b[35m$ yarn dev\x1b[0m\n`; console.log('\x1b[32m%s\x1b[0m', successMessage); console.log('\x1b[36m%s\x1b[0m', getStartedMessage); console.log('\x1b[33m%s\x1b[0m', 'Project setup is complete\n'); console.log('\x1b[33m%s\x1b[0m', 'Project Zero - Akinon\n'); };