UNPKG

@practical/create

Version:

Practical CLI

128 lines (127 loc) 5.28 kB
#!/usr/bin/env node import { input, checkbox } from '@inquirer/prompts'; import { exec } from 'child_process'; import fs from 'fs/promises'; import { promisify } from 'util'; const execAsync = promisify(exec); const isUuid = (data) => /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(data.trim()); const hasPackage = await fs.access('package.json').then(() => true).catch(() => false); const changePackageJsonName = async (name) => { const packageJSON = `${name}/package.json`; try { const data = await fs.readFile(packageJSON, 'utf8'); console.info('Setup package.json'); const result = data.replace('@practical/base', name); await fs.writeFile(packageJSON, result, 'utf8'); console.info('Practical Template'); } catch (err) { console.error(err); } }; const answers = { license: await input({ message: 'Welcome to Practical! - 2.2.0 🎉🥳🙌 \n –> Please enter your @practical License!', validate: (input) => { if (!input) return 'License is required'; if (!isUuid(input)) return 'License format is not correct'; return true; }, }), domain: await input({ message: 'Add your Project Domain (format: example.com)', validate: (input) => !!input || 'Domain name is required', }), presets: await checkbox({ message: 'Choose your CMS Adapters!', choices: [ { name: 'YML - static data', value: 'yml' }, { name: 'Strapi - Headless CMS', value: 'strapi' }, { name: 'Sanity Studio (sanity.io)', value: 'sanity' }, { name: 'Storyblok Headless CMS (storyblok.com)', value: 'storyblok' }, { name: 'Wordpress Headless API', value: 'wordpress' }, ], required: true, validate: (input) => input.length > 0 || 'Please choose an adapter!', // > 0 → boolean }), projectName: await input({ message: 'Enter your Project name', validate: (input) => { if (!input) return 'Project name is required'; const nameRegex = /^(@[a-z0-9-][a-z0-9-_]*\/)?[a-z0-9-][a-z0-9-_]*$/; if (!nameRegex.test(input)) { return 'The name must be lowercase and one word, and may contain hyphens and underscores.'; } return true; }, }), }; const execPath = process.env.npm_execpath || ''; const isYarn = execPath.includes('yarn'); const isBun = execPath.includes('bun'); const configFile = isYarn ? '.yarnrc.yml' : isBun ? 'bunfig.toml' : '.npmrc'; const pkgRegistry = 'pkg.practical.at'; const licenseToken = `${answers.license.trim()}:${answers.domain}`; const yarnFile = `npmScopes: practical-at: npmRegistryServer: https://${pkgRegistry} npmAuthToken: ${licenseToken} `; const npmrcFile = `@practical-at:registry=https://${pkgRegistry}/ //${pkgRegistry}/:_authToken=${licenseToken} `; const bunfigToml = `[install.scopes] "@practical-at" = { token = "${licenseToken}", url = "https://${pkgRegistry}/" }`; const options = answers.presets.join(' '); const projectName = answers.projectName.trim(); const base = answers.presets.length === 1 ? 'base ' : ''; const command = `${!isYarn && !isBun ? 'node' : ''} ${execPath ? `"${execPath}"` : ''} ${isBun ? '' : 'create'} @practical-at${isBun ? '/create' : ''}@latest folder ${projectName} ${base + options} -y`; const fileContents = isYarn ? yarnFile : isBun ? bunfigToml : npmrcFile; try { await fs.mkdir(projectName, { recursive: true }); } catch (err) { console.error(err); } if (!hasPackage) { await fs.writeFile('package.json', '{}'); } await fs.writeFile(configFile, fileContents); console.info('Get @practical Templates: \n', command); try { const { stdout, stderr } = await execAsync(command); await fs.copyFile(configFile, `${projectName}/${configFile}`); if (!hasPackage) await fs.rm('package.json'); await fs.rm(configFile); console.info(stdout || stderr); await changePackageJsonName(projectName); console.info('@practical setup -- done'); } catch (error) { const stderr = error.stderr || ''; if (stderr.includes('E422') || stderr.includes('Unprocessable Entity')) { console.error('❌ Unknown Licence. Please check if Project domain is correct and is paired with correct license.'); } else { console.error('❌ Error:', error.message); } } const gracefulExit = () => { console.log('\n ┌─────────────────────────────────────┐\n │ Thanks for using Practical! │\n │ See you next time! 👋 │\n └─────────────────────────────────────┘\n'); process.exit(0); }; process.on('SIGINT', gracefulExit); process.on('SIGTERM', gracefulExit); process.on('uncaughtException', (error) => { if (error.name === 'ExitPromptError' || error.message?.includes('User force closed')) { gracefulExit(); } else { console.error('An unexpected error occurred:', error.message); process.exit(1); } });