@practical/create
Version:
Practical CLI
128 lines (127 loc) • 5.28 kB
JavaScript
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);
}
});