fixit-cli
Version:
A cli tool for FixIt theme.
290 lines (289 loc) • 13.7 kB
JavaScript
import { join } from 'node:path';
import process from 'node:process';
import * as p from '@clack/prompts';
import c from 'picocolors';
import shell from 'shelljs';
import { CleanOptions, simpleGit, } from 'simple-git';
import { getLatestRelease, handleTargetDir, modifyFile, removeRemoteOrigin, timer, } from '../lib/utils.js';
/**
* action for create command
* @param {string} projectName project name
* @example fixit create [project-name]
*/
async function createAction(projectName) {
timer.start('Creating a new FixIt project step by step!');
const repositories = {
go: 'https://github.com/hugo-fixit/hugo-fixit-starter.git',
git: 'https://github.com/hugo-fixit/hugo-fixit-starter1.git',
};
const answers = await p.group({
name: () => p.text({
message: 'Please input project name:',
placeholder: 'FixIt project name, e.g. `my-blog`',
initialValue: projectName || '',
validate: (val) => {
if (val === '') {
return 'FixIt project name is required!';
}
},
}),
template: () => p.select({
message: 'Please choose a template:',
options: [
{ value: 'go', label: 'Hugo module based' },
{ value: 'git', label: 'Git submodule based' },
],
}),
blogTitle: () => p.text({
message: 'Please input your blog title:',
placeholder: '[blog title]',
}),
authorName: () => p.text({
message: 'Please input your name:',
placeholder: '[author name]',
}),
authorEmail: () => p.text({
message: 'Please input your email:',
placeholder: '[author email]',
}),
authorLink: () => p.text({
message: 'Please input your link:',
placeholder: '[author link]',
}),
}, {
onCancel: () => {
p.cancel('Operation cancelled.');
process.exit(0);
},
});
let hugoModulePath = '';
if (answers.template === 'go') {
hugoModulePath = await p.text({
message: 'Please input module path:',
placeholder: 'e.g. github.com/your_name/your_project',
initialValue: `github.com/${answers.authorName || 'your_name'}/${answers.name}`,
validate: (val) => {
if (val === '') {
return 'module path is required!';
}
},
});
}
const targetDir = await handleTargetDir(answers.name);
p.log.step(`Initializing FixIt project ${targetDir}, please wait a moment...`);
// 1. download template
const spinnerClone = p.spinner();
spinnerClone.start(`Template downloading from ${c.cyan(repositories[answers.template])}.`);
const progress = ({ method, stage, progress }) => {
spinnerClone.message(c.yellow(`git.${method} ${stage} stage ${progress}% complete${'.'.repeat(Math.floor(Math.random() * 3) + 1)}`));
};
const git = simpleGit({ progress });
git.clean(CleanOptions.FORCE);
const cloneOptions = {
'--depth': 1,
'--branch': 'main',
'--single-branch': null,
};
if (answers.template === 'git') {
cloneOptions['--recurse-submodules'] = undefined;
cloneOptions['--shallow-submodules'] = undefined;
}
git.clone(repositories[answers.template], targetDir, cloneOptions, (err) => {
if (err) {
spinnerClone.stop(err.message, -1);
return;
}
spinnerClone.stop(`${c.green('✔')} Template downloaded from ${c.cyan(repositories[answers.template])}`, 0);
const siteTime = new Date().toISOString();
// 2. initialize FixIt project
const spinnerInit = p.spinner();
spinnerInit.start(`Initializing FixIt project ${targetDir}.`);
// remove remote origin
git.cwd(targetDir);
removeRemoteOrigin(git, spinnerInit);
// initialize Hugo module
if (answers.template === 'go') {
spinnerInit.message('Initializing Hugo module.');
const goMod = join(process.cwd(), targetDir, 'go.mod');
modifyFile(goMod, data => data.replace(/module .*/, `module ${hugoModulePath}`), spinnerInit, 'Modifying module path in go.mod.');
}
// initialize hugo config
const hugoToml = join(process.cwd(), targetDir, 'config/_default/hugo.toml');
modifyFile(hugoToml, (data) => {
let result = data.replace(/baseURL = ".*"/, 'baseURL = "https://example.org/"');
if (answers.blogTitle) {
result = result.replace(/title = ".*"/, `title = "${answers.blogTitle}"`);
}
return result;
}, spinnerInit, 'Modifying baseURL and title in hugo.toml.');
// initialize Fixit params.toml
const paramsToml = join(process.cwd(), targetDir, 'config/_default/params.toml');
modifyFile(paramsToml, (data) => {
let result = data.replace(/value = "" # e.g. "2021-12-18T16:15:22\+08:00"/, `value = "${siteTime}" # e.g. "2021-12-18T16:15:22+08:00"`);
result = result.replace(/since = \d+/, `since = ${new Date().getFullYear()}`);
if (answers.authorName || answers.authorEmail || answers.authorLink) {
result = result.replace(/\[author\]\n.*\n.*\n.*\n/, `[author]\n name = "${answers.authorName || ''}"\n email = "${answers.authorEmail || ''}"\n link = "${answers.authorLink || ''}"\n`);
}
result = result.replace(/logo = ".*"/, 'logo = "/images/fixit.min.svg"');
return result;
}, spinnerInit, 'Modifying site time and author info etc. in params.toml.');
// initialize hello world post create time
const helloMd = join(process.cwd(), targetDir, 'content/posts/hello-world.md');
modifyFile(helloMd, data => data.replace(/date: .*/, `date: ${siteTime}`), spinnerInit, 'Modifying create time in hello-world.md.');
// remove history commits
spinnerInit.message('Removing history commits.');
git.raw(['update-ref', '-d', 'HEAD'], (err) => {
if (err) {
spinnerInit.stop(err.message, -1);
return;
}
spinnerInit.message(`${c.green('✔')} removed history commits.`);
}).then(async () => {
// commit first commit
await git.add('./*');
await git.commit('first commit');
spinnerInit.stop(`${c.green('✔')} FixIt project ${targetDir} initialized!`, 0);
p.log.success('🎉 Congratulations! You have created a new FixIt project.');
const run = await p.confirm({
message: '🚀 Do you want to start the development server now?',
});
if (!run) {
p.log.info(`Run ${c.blue(`cd ${targetDir} && hugo server -O`)} to start the development server.`);
p.outro(`Done in ${timer.stop() / 1000}s`);
process.exit(0);
}
// 3. start development server
p.log.step('Starting the development server...');
if (!shell.which('hugo')) {
p.log.error(`${c.red('Hugo is not installed. You need to install Hugo to start this project!')}`);
p.log.info(`After installing Hugo, run ${c.blue(`cd ${targetDir} && hugo server -O`)} to start the development server.`);
p.outro(`Done in ${timer.stop() / 1000}s`);
// TODO install hugo-bin or hugo-extended automatically
process.exit(1);
}
p.log.info(`> ${c.blue(`cd ${targetDir} && hugo server -O`)}`);
p.outro(`Done in ${timer.stop() / 1000}s`);
shell.cd(targetDir);
shell.exec('hugo server -O');
});
});
}
/**
* action for create component subcommand
* clone https://github.com/hugo-fixit/component-skeleton
* @param {string} componentName component name
* @example fixit create component [component-name]
*/
async function createComponentAction(componentName) {
timer.start('Creating a new FixIt component step by step!');
const repository = 'https://github.com/hugo-fixit/component-skeleton';
const answers = await p.group({
name: () => p.text({
message: 'Please input component name:',
placeholder: 'Component name, e.g. `my-component`',
initialValue: componentName || '',
validate: (val) => {
if (val === '') {
return 'Component name is required!';
}
},
}),
author: () => p.text({
message: 'Please input your GitHub username:',
placeholder: 'GitHub username, e.g. `Lruihao`',
initialValue: 'hugo-fixit',
validate: (val) => {
if (val === '') {
return 'GitHub username is required!';
}
},
}),
}, {
onCancel: () => {
p.cancel('Operation cancelled.');
process.exit(0);
},
});
const targetDir = await handleTargetDir(answers.name);
p.log.step(`Initializing FixIt component ${targetDir}, please wait a moment...`);
// 1. download skeleton
const spinnerClone = p.spinner();
spinnerClone.start(`Skeleton downloading from ${c.cyan(repository)}.`);
const progress = ({ method, stage, progress }) => {
spinnerClone.message(c.yellow(`git.${method} ${stage} stage ${progress}% complete${'.'.repeat(Math.floor(Math.random() * 3) + 1)}`));
};
const git = simpleGit({ progress });
git.clean(CleanOptions.FORCE);
git.clone(repository, targetDir, { '--depth': 1, '--branch': 'main', '--single-branch': null }, (err) => {
if (err) {
spinnerClone.stop(err.message, -1);
return;
}
spinnerClone.stop(`${c.green('✔')} Skeleton downloaded from ${c.cyan(repository)}`, 0);
// 2. initialize FixIt component
const spinnerInit = p.spinner();
spinnerInit.start(`Initializing FixIt component ${targetDir}.`);
// remove remote origin
git.cwd(targetDir);
removeRemoteOrigin(git, spinnerInit);
// initialize Hugo module
spinnerInit.message('Initializing Hugo module.');
const goMod = join(process.cwd(), targetDir, 'go.mod');
modifyFile(goMod, data => data.replace(/module .*/, `module github.com/${answers.author}/${answers.name}`), spinnerInit, 'Modifying module path in go.mod.');
// modify LICENSE
const license = join(process.cwd(), targetDir, 'LICENSE');
modifyFile(license, (data) => {
const currentYear = new Date().getFullYear();
return data.replace(/Copyright \(c\) \d+ .*/, `Copyright (c) ${currentYear} ${answers.author} (https://github.com/${answers.author})`);
}, spinnerInit, 'Modifying author in LICENSE.');
// modify README.md and README.en.md
for (const readmeFile of ['README.md', 'README.en.md']) {
const readme = join(process.cwd(), targetDir, readmeFile);
modifyFile(readme, data => data.replace(/hugo-fixit\/\{component-xxx\}/g, `${answers.author}/${answers.name}`).replace(/\{component-xxx\}/g, answers.name), spinnerInit, `Modifying author in ${readmeFile}.`);
}
// 3. commit first commit and remove history commits
spinnerInit.message('Removing history commits.');
git.raw(['update-ref', '-d', 'HEAD'], (err) => {
if (err) {
spinnerInit.stop(err.message, -1);
return;
}
spinnerInit.message(`${c.green('✔')} removed history commits.`);
}).then(async () => {
await git.add('./*');
await git.commit('first commit');
spinnerInit.stop(`${c.green('✔')} FixIt component ${targetDir} initialized!`, 0);
p.log.success('🎉 Congratulations! You have created a new FixIt component.');
p.outro(`Done in ${timer.stop() / 1000}s`);
});
});
}
/**
* action for check command
* @example fixit check
* @example GITHUB_TOKEN=ghp_ifbeKixxxxxxxxxxxxxxxxxxxxxxxx0gVAgF fixit check
*/
function checkAction() {
timer.start('Checking for updates');
const spinner = p.spinner();
spinner.start('Checking the latest version of FixIt theme.');
getLatestRelease('hugo-fixit', 'FixIt')
.then(({ version, changelog, homeUrl }) => {
p.log.info(`Release Notes: ${c.cyan(homeUrl)}\n\n${changelog.split('\n').map(line => c.gray(line)).join('\n')}`);
spinner.stop(`${c.green('✔')} The latest version of FixIt theme is ${c.blue(version)}.`, 0);
p.log.step(`You can use commands below to update FixIt theme to the latest version.\n`
+ `${c.gray('Hugo module:')}\n`
+ ` ${c.blue(`hugo mod get -u github.com/hugo-fixit/FixIt@${version}`)}\n`
+ ` ${c.blue('hugo mod tidy')}\n`
+ `${c.gray('Git submodule:')}\n`
+ ` ${c.blue('git submodule update --remote --merge themes/FixIt')}`);
p.outro(`Done in ${timer.stop() / 1000}s`);
})
.catch((error) => {
p.log.error(c.red(error.message));
spinner.stop(`${c.red('✘')} failed to check the latest version of FixIt theme.`, -1);
p.log.step(`You can set GITHUB_TOKEN env to avoid GitHub API rate limit.\nRun command ${c.blue('fixit help check')} for more details.\n`);
process.exit(1);
});
}
export { checkAction, createAction, createComponentAction, };