Skip to content

Commit

Permalink
feat: copy template
Browse files Browse the repository at this point in the history
  • Loading branch information
oushihao committed Mar 10, 2022
1 parent 3c08ba6 commit ba84e97
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 133 deletions.
90 changes: 35 additions & 55 deletions index.js
@@ -1,26 +1,22 @@
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 path = require('path')
const fs = require('fs')
const minimist = require('minimist')
const prompts = require('prompts')
const { canSafelyOverwrite, isValidPackageName, toValidPackageName, emptyDir } = require('./utils/helpers')
const { templateChoices } = require('./utils/templateOptions')
const { renderTemplate } = require('./utils/renderTemplate')

const defaultProjectName = 'ou-app';
const defaultProjectName = 'ou-app'

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

const cwd = process.cwd();
const cwd = process.cwd()

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

let targetDir;
let result = {};
let targetDir
let result = {}
try {
// Prompts:
// - Project name:
Expand All @@ -33,16 +29,14 @@ const init = async () => {
type: 'text',
message: 'Project name:',
initial: defaultProjectName,
onState: (state) =>
(targetDir = String(state.value).trim() || 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',
validate: (dir) => isValidPackageName(dir) || 'Invalid package.json name',
},
{
name: 'shouldCreateNewDir',
Expand All @@ -54,17 +48,11 @@ const init = async () => {
},
{
name: 'shouldOverwrite',
type: (shouldCreateNewDir) =>
!shouldCreateNewDir || canSafelyOverwrite(targetDir)
? null
: 'confirm',
type: (shouldCreateNewDir) => (!shouldCreateNewDir || canSafelyOverwrite(targetDir) ? null : 'confirm'),
message: () => {
const dirForPrompt =
targetDir === '.'
? 'Current directory'
: `Target directory "${targetDir}"`;
const dirForPrompt = targetDir === '.' ? 'Current directory' : `Target directory "${targetDir}"`

return `${dirForPrompt} is not empty. Remove existing files and continue?`;
return `${dirForPrompt} is not empty. Remove existing files and continue?`
},
},
{
Expand All @@ -73,39 +61,31 @@ const init = async () => {
choices: templateChoices,
message: 'Choose a template:',
},
]);
])
} catch (cancelled) {
console.log(cancelled.message);
process.exit(1);
console.log(cancelled.message)
process.exit(1)
}

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

const root = shouldCreateNewDir ? path.join(cwd, projectName) : cwd;
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);
}
if (fs.existsSync(root) && shouldOverwrite) emptyDir(root)
else if (fs.existsSync(root)) process.exit(1)
else fs.mkdirSync(root)
}

console.log(`\nScaffolding project in ${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)
);
};
const pkg = { name: packageName, version: '0.0.0' }
fs.writeFileSync(path.resolve(root, 'package.json'), JSON.stringify(pkg, null, 2))

const templateDir = path.join(cwd, `./templates/${template}`)
renderTemplate(templateDir, root)
}

init().catch((e) => {
console.error(e);
});
console.error(e)
})
13 changes: 6 additions & 7 deletions package.json
Expand Up @@ -2,23 +2,22 @@
"name": "ou",
"version": "1.0.0",
"description": "a cli for doing something.",
"main": "index.js",
"scripts": {
"dev": "node ./index"
},
"keywords": [],
"license": "ISC",
"repository": {
"type": "git",
"url": "git+https://github.com/ouduidui/ou.git"
},
"keywords": [],
"author": "",
"license": "ISC",
"main": "index.js",
"scripts": {
"dev": "node ./index"
},
"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"
}
Expand Down
28 changes: 0 additions & 28 deletions pnpm-lock.yaml

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

49 changes: 14 additions & 35 deletions utils/helpers.js
@@ -1,58 +1,37 @@
const fs = require('fs');
const path = require('path');
const fs = require('fs')
const path = require('path')

const canSafelyOverwrite = (dir) =>
!fs.existsSync(dir) || fs.readdirSync(dir).length === 0;
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 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, '-');
.replace(/[^a-z0-9-~]+/g, '-')

const emptyDir = (dir) => {
if (!fs.existsSync(dir)) {
return;
}
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;
const fullPath = path.resolve(dir, filename)
if (fs.lstatSync(fullPath).isDirectory()) {
postOrderDirectoryTraverse(fullPath, dirCallback, fileCallback)
dirCallback(fullPath)
continue
}
fileCallback(fullpath);
fileCallback(fullPath)
}
}

Expand All @@ -61,4 +40,4 @@ module.exports = {
isValidPackageName,
toValidPackageName,
emptyDir,
};
}
36 changes: 36 additions & 0 deletions utils/renderTemplate.js
@@ -0,0 +1,36 @@
const path = require('path')
const fs = require('fs')

const renderTemplate = (src, dest) => {
const stat = fs.statSync(src)

if (stat.isDirectory()) {
if (path.basename(src) === 'node_modules') return

fs.mkdirSync(dest, { recursive: true })
for (const file of fs.readdirSync(src)) {
renderTemplate(path.resolve(src, file), path.resolve(dest, file))
}
return
}

const filename = path.basename(src)

if (filename === 'package.json' && fs.existsSync(dest)) {
// TODO merge

return
}

if (/^\./.test(filename)) return

if (/^\_/.test(filename)) {
dest = path.resolve(path.dirname(dest), filename.replace(/^_/, '.'))
}

fs.copyFileSync(src, dest)
}

module.exports = {
renderTemplate,
}
16 changes: 8 additions & 8 deletions utils/templateOptions.js
@@ -1,15 +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' },
];
// { 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 }));
templateOptions.map((o) => ({ title: o.label, value: o.id }))

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

0 comments on commit ba84e97

Please sign in to comment.