Skip to content

Commit

Permalink
feat: init prompts
Browse files Browse the repository at this point in the history
  • Loading branch information
oushihao committed Mar 10, 2022
1 parent 453ea1f commit 474fe01
Show file tree
Hide file tree
Showing 6 changed files with 276 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -0,0 +1 @@
node_modules
111 changes: 111 additions & 0 deletions index.js
@@ -0,0 +1,111 @@
const minimist = require('minimist');
const prompts = require('prompts');
const path = require('path');
const fs = require('fs');
const {
canSafelyOverwrite,
isValidPackageName,
toValidPackageName,
emptyDir,
} = require('./utils/helpers');
const { templateChoices } = require('./utils/templateOptions');

const defaultProjectName = 'ou-app';

const init = async () => {
console.log('init');

const cwd = process.cwd();

const argv = minimist(process.argv.slice(2), { boolean: true });

let targetDir;
let result = {};
try {
// Prompts:
// - Project name:
// - Package name:
// - Should create new directory:
// - Choose a template:
result = await prompts([
{
name: 'projectName',
type: 'text',
message: 'Project name:',
initial: defaultProjectName,
onState: (state) =>
(targetDir = String(state.value).trim() || defaultProjectName),
},
{
name: 'packageName',
type: () => (isValidPackageName(targetDir) ? null : 'text'),
message: 'Package name:',
initial: () => toValidPackageName(targetDir),
validate: (dir) =>
isValidPackageName(dir) || 'Invalid package.json name',
},
{
name: 'shouldCreateNewDir',
type: 'toggle',
message: 'Should create new directory:',
initial: false,
active: 'Yes',
inactive: 'No',
},
{
name: 'shouldOverwrite',
type: (shouldCreateNewDir) =>
!shouldCreateNewDir || canSafelyOverwrite(targetDir)
? null
: 'confirm',
message: () => {
const dirForPrompt =
targetDir === '.'
? 'Current directory'
: `Target directory "${targetDir}"`;

return `${dirForPrompt} is not empty. Remove existing files and continue?`;
},
},
{
name: 'template',
type: 'select',
choices: templateChoices,
message: 'Choose a template:',
},
]);
} catch (cancelled) {
console.log(cancelled.message);
process.exit(1);
}

const {
projectName,
packageName,
shouldCreateNewDir,
shouldOverwrite = false,
template,
} = result;

const root = shouldCreateNewDir ? path.join(cwd, projectName) : cwd;

if (shouldCreateNewDir) {
if (fs.existsSync(root) && shouldOverwrite) {
emptyDir(root);
} else if (!fs.existsSync(root)) {
fs.mkdirSync(root);
}
}

console.log(`\nScaffolding project in ${root}...`);

const pkg = { name: packageName, version: '0.0.0' };
fs.writeFileSync(
path.resolve(root, 'package.json'),
JSON.stringify(pkg, null, 2)
);
};

init().catch((e) => {
console.error(e);
});
25 changes: 25 additions & 0 deletions package.json
@@ -0,0 +1,25 @@
{
"name": "ou",
"version": "1.0.0",
"description": "a cli for doing something.",
"main": "index.js",
"scripts": {
"dev": "node ./index"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ouduidui/ou.git"
},
"keywords": [],
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/ouduidui/ou/issues"
},
"homepage": "https://github.com/ouduidui/ou#readme",
"dependencies": {
"fs-extra": "^10.0.1",
"minimist": "^1.2.5",
"prompts": "^2.4.2"
}
}
60 changes: 60 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

64 changes: 64 additions & 0 deletions utils/helpers.js
@@ -0,0 +1,64 @@
const fs = require('fs');
const path = require('path');

const canSafelyOverwrite = (dir) =>
!fs.existsSync(dir) || fs.readdirSync(dir).length === 0;

const isValidPackageName = (projectName) =>
/^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(
projectName
);

const toValidPackageName = (projectName) =>
projectName
.trim()
.toLowerCase()
.replace(/\s+/g, '-')
.replace(/^[._]/, '')
.replace(/[^a-z0-9-~]+/g, '-');

const emptyDir = (dir) => {
if (!fs.existsSync(dir)) {
return;
}

postOrderDirectoryTraverse(
dir,
(dir) => fs.rmdirSync(dir),
(file) => fs.unlinkSync(file)
);
};

function preOrderDirectoryTraverse(dir, dirCallback, fileCallback) {
for (const filename of fs.readdirSync(dir)) {
const fullpath = path.resolve(dir, filename);
if (fs.lstatSync(fullpath).isDirectory()) {
dirCallback(fullpath);
// in case the dirCallback removes the directory entirely
if (fs.existsSync(fullpath)) {
preOrderDirectoryTraverse(fullpath, dirCallback, fileCallback);
}
continue;
}
fileCallback(fullpath);
}
}

function postOrderDirectoryTraverse(dir, dirCallback, fileCallback) {
for (const filename of fs.readdirSync(dir)) {
const fullpath = path.resolve(dir, filename);
if (fs.lstatSync(fullpath).isDirectory()) {
postOrderDirectoryTraverse(fullpath, dirCallback, fileCallback);
dirCallback(fullpath);
continue;
}
fileCallback(fullpath);
}
}

module.exports = {
canSafelyOverwrite,
isValidPackageName,
toValidPackageName,
emptyDir,
};
15 changes: 15 additions & 0 deletions utils/templateOptions.js
@@ -0,0 +1,15 @@
const templateOptions = [
{ id: 'NODE', label: 'node-ts-template' },
{ id: 'VUE3', label: 'vue3-template' },
{ id: 'VUE3_LITE', label: 'vue3-lite-template (without router and store)' },
{ id: 'REACT', label: 'react-template' },
{ id: 'REACT_LITE', label: 'react-lite-template' },
];

const optionsToChoices = () =>
templateOptions.map((o) => ({ title: o.label, value: o.id }));

module.exports = {
templateOptions,
templateChoices: optionsToChoices(),
};

0 comments on commit 474fe01

Please sign in to comment.