import { cancel, intro, isCancel, multiselect, outro, text, spinner, select } from '@clack/prompts' import { dirname, extname, join } from 'node:path' import { chmod, copyFile, mkdir, readdir, readFile, writeFile } from 'node:fs/promises' import color from 'picocolors' import { fileURLToPath } from 'node:url' import { mkdirp } from 'mkdirp' import { execa } from 'execa' import { addPackage } from './pkgjson.js' import ora from 'ora' async function main() { // Some separation from the PNPM previous output that is very noisy. console.log('') intro(color.inverse(' @altipla/create-astro ')) let name = await text({ message: 'What is the name of the project?', placeholder: 'my-astro-app', }) if (isCancel(name)) { cancel('Operation cancelled.') process.exit(0) } if (!name) { name = 'my-astro-app' } let dir = await text({ message: 'Where should we create the project?', placeholder: `./${name}`, }) if (isCancel(dir)) { cancel('Operation cancelled.') process.exit(0) } if (!dir) { dir = `./${name}` } let googleProject = await text({ message: 'What is the Google Cloud project ID?', placeholder: 'precise-truck-89123', validate(value) { if (!value) { return 'Please provide a Google Cloud project ID.' } }, }) if (isCancel(googleProject)) { cancel('Operation cancelled.') process.exit(0) } let cdn = await text({ message: 'What is the CDN domain? (leave empty to not use a CDN)', placeholder: 'https://cdn.domain.com', }) if (isCancel(cdn)) { cancel('Operation cancelled.') process.exit(0) } let sentry = await text({ message: 'What is the Sentry project ID?', placeholder: 'foo-node', validate(value) { if (!value) { return 'Please provide a Sentry project ID.' } }, }) if (isCancel(sentry)) { cancel('Operation cancelled.') process.exit(0) } let components = await multiselect({ message: 'Which components do you want to include?', options: [ { value: 'trpc', label: 'tRPC' }, { value: 'prime', label: 'PrimeVue' }, ], initialValues: ['trpc'], required: false, }) if (isCancel(components)) { cancel('Operation cancelled.') process.exit(0) } let vars = { name, cdn, googleProject, sentry } let s = spinner() s.start('Copy template files...') await mkdir(dir, { recursive: true }) await copyTemplateFiles('base', dir, vars) await emptyFolders(dir) if (components.includes('trpc')) { await templateTRPC(dir, vars) } if (components.includes('prime')) { await templatePrimeVue(dir, vars) } if (cdn) { await copyTemplateFiles('with-cdn', dir, vars) } await chmod(join(dir, 'scripts', 'jenkins', 'after-merge.sh'), 0o755) await chmod(join(dir, 'scripts', 'jenkins', 'check-gerrit.sh'), 0o755) s.stop() let install = await select({ message: 'Do you want to install dependencies with PNPM?', options: [ { value: 'YES', label: 'Yes' }, { value: 'NO', label: 'No' }, ], initialValue: 'YES', }) if (isCancel(install)) { cancel('Operation cancelled.') process.exit(0) } if (install === 'YES') { let s = spinner() s.start('') s.stop() await installPackages(dir) } outro(`Application ${color.italic(color.blue(name))} successfully created!`) } main().catch((err: any) => { console.error(err) process.exit(1) }) interface CopyVars { name: string cdn: string googleProject: string sentry: string } async function emptyFolders(dir: string) { let dirs = ['src/components', 'src/composables', 'src/images'] for (let sub of dirs) { mkdirp.sync(join(dir, sub)) } } async function templateTRPC(dir: string, vars: CopyVars) { await copyTemplateFiles('trpc', dir, vars) addPackage(dir, '@trpc/client') addPackage(dir, '@trpc/server') addPackage(dir, 'zod') addPackage(dir, 'superjson') } async function templatePrimeVue(dir: string, vars: CopyVars) { await copyTemplateFiles('primevue', dir, vars) addPackage(dir, 'primevue') } async function copyTemplateFiles(from: string, to: string, vars: CopyVars) { let root = join(dirname(fileURLToPath(import.meta.url)), '..', 'templates', from) let items = await readdir(root, { withFileTypes: true }) for (let item of items) { if (item.isDirectory()) { await mkdir(join(to, item.name), { recursive: true }) await copyTemplateFiles(join(from, item.name), join(to, item.name), vars) } else if (item.isFile()) { if (canReplaceTemplate(item.name)) { let content = await readFile(join(root, item.name), 'utf-8') content = content.replace(/{{name}}/g, vars.name) content = content.replace(/{{googleProject}}/g, vars.googleProject) content = content.replace(/{{cdn}}/g, vars.cdn) content = content.replace(/{{sentry}}/g, vars.sentry) await writeFile(join(to, destName(item.name)), content, 'utf-8') } else { await copyFile(join(root, item.name), join(to, destName(item.name))) } } } } function canReplaceTemplate(file: string) { let ext = extname(file) return ['.ts', '.vue', '.ce.vue', '.json', '.astro', '.css', '.sh'].includes(ext) } function destName(file: string) { return file.startsWith('_') ? file.slice(1) : file } async function installPackages(dir: string) { let spinner = ora('Running PNPM install...').start() let subprocess = execa('pnpm', ['install'], { cwd: dir }) await new Promise((resolve, reject) => { subprocess.stdout.on('data', (data: any) => { spinner.text = data.toString() }) subprocess.on('error', (e: any) => reject(e)) subprocess.on('close', () => resolve()) }) spinner.succeed(color.green('Successfully installed dependencies.')) }