Comparing version 1.0.0-alpha.7 to 1.0.0
const path = require('path') | ||
const fs = require('fs-extra') | ||
const chalk = require('chalk') | ||
const installPackages = require('install-packages') | ||
const spawn = require('cross-spawn') | ||
const spinner = require('./spinner') | ||
const logger = require('./logger') | ||
const SAOError = require('./SAOError') | ||
module.exports = class GeneratorContext { | ||
constructor() { | ||
this.color = chalk | ||
constructor(sao, generator) { | ||
this.sao = sao | ||
this.generator = generator | ||
this.spinner = spinner | ||
this.chalk = chalk | ||
this.logger = logger | ||
this.fs = fs | ||
} | ||
async npmInstall({ packages, packageManager } = {}) { | ||
const spinner = require('./spinner').start( | ||
`Installing ${packages ? packages.join(', ') : 'packages'}...` | ||
) | ||
await installPackages({ | ||
packages, | ||
logTitle: false, | ||
cwd: this.options.outDir, | ||
packageManager: packageManager || this.options.packageManager | ||
}) | ||
spinner.stop() | ||
get pkg() { | ||
try { | ||
return require(path.join(this.outDir, 'package.json')) | ||
} catch (err) { | ||
return {} | ||
} | ||
} | ||
determinePackageManager(cwd = this.options.outDir) { | ||
return installPackages.determinePackageManager(cwd) | ||
get answers() { | ||
return this._answers | ||
} | ||
gitInit() { | ||
return require('./utils/gitInit')(this.options.outDir) | ||
get gitUser() { | ||
return require('./gitInfo')(this.sao.opts.mock) | ||
} | ||
showCompleteTips() { | ||
logger.success( | ||
chalk.bold( | ||
`Successfully generating into ${chalk.cyan( | ||
'./' + path.relative(process.cwd(), this.options.outDir) | ||
)}` | ||
) | ||
) | ||
if (!this.options.inPlace) { | ||
logger.tip( | ||
chalk.bold( | ||
`To get started, run ${chalk.cyan(`cd ${this.options.outDirName}`)}` | ||
) | ||
) | ||
get outFolder() { | ||
return path.basename(this.sao.opts.outDir) | ||
} | ||
get outDir() { | ||
return this.sao.opts.outDir | ||
} | ||
get npmClient() { | ||
return this.sao.opts.npmClient | ||
} | ||
gitInit() { | ||
const ps = spawn.sync('git', ['init'], { | ||
stdio: 'ignore', | ||
cwd: this.outDir | ||
}) | ||
if (ps.status === 0) { | ||
logger.success('Initialized empty Git repository') | ||
} else { | ||
logger.debug(`git init failed in ${this.outDir}`) | ||
} | ||
} | ||
npmInstall() { | ||
return require('./installPackages')({ | ||
cwd: this.outDir | ||
}) | ||
} | ||
showProjectTips() { | ||
spinner.stop() // Stop when necessary | ||
logger.success(`Generated into ${chalk.underline(this.outDir)}`) | ||
} | ||
createError(message) { | ||
return new SAOError(message) | ||
} | ||
} |
498
lib/index.js
const path = require('path') | ||
const fs = require('fs-extra') | ||
const chalk = require('chalk') | ||
const { fs } = require('majo') | ||
const invokeActions = require('./invokeActions') | ||
const parseGenerator = require('./utils/parseGenerator') | ||
const downloadGitRepo = require('download-git-repo') | ||
const resolveFrom = require('resolve-from') | ||
const loadConfig = require('./loadConfig') | ||
const paths = require('./paths') | ||
const spinner = require('./spinner') | ||
const BaseGeneratorContext = require('./GeneratorContext') | ||
const logger = require('./logger') | ||
const evaluate = require('./utils/evaluate') | ||
const { escapeDots } = require('./utils') | ||
const GeneratorContext = require('./GeneratorContext') | ||
const isLocalPath = require('./utils/isLocalPath') | ||
const SAOError = require('./SAOError') | ||
const logDescription = desc => { | ||
if (desc) { | ||
logger.info(chalk.magenta(`Description: ${desc}`)) | ||
class SAO { | ||
/** | ||
* Create an instance of SAO | ||
* @param {Object} opts | ||
*/ | ||
constructor(opts) { | ||
this.opts = Object.assign({}, opts) | ||
this.opts.outDir = path.resolve(this.opts.outDir) | ||
this.opts.npmClient = | ||
this.opts.npmClient || require('./installPackages').getNpmClient() | ||
this.logger = logger | ||
logger.setOptions({ | ||
logLevel: | ||
typeof this.opts.logLevel === 'number' | ||
? this.opts.logLevel | ||
: this.opts.debug | ||
? 4 | ||
: this.opts.quiet | ||
? 1 | ||
: 3 | ||
}) | ||
} | ||
} | ||
module.exports = class SAO extends GeneratorContext { | ||
constructor(options) { | ||
super() | ||
this.options = this.normalizeOptions(options) | ||
} | ||
normalizeOptions(options = {}) { | ||
const outDir = path.resolve(options.outDir || '.') | ||
const logLevel = options.logLevel | ||
if (typeof logLevel === 'number') { | ||
logger.setOptions({ logLevel }) | ||
/** | ||
* Run the generator. | ||
*/ | ||
async run(name) { | ||
const generator = require('./parseGenerator')(name || this.opts.generator) | ||
if (generator.type === 'repo') { | ||
await ensureRepo(generator, this.opts.npmClient, this.opts.update) | ||
} else if (generator.type === 'npm') { | ||
await ensurePackage(generator, this.opts.npmClient, this.opts.update) | ||
} else if (generator.type === 'local') { | ||
await ensureLocal(generator) | ||
} | ||
return { | ||
baseDir: options.baseDir || process.cwd(), | ||
from: options.from, | ||
outDir, | ||
outDirName: path.basename(outDir), | ||
inPlace: outDir === process.cwd(), | ||
// Update cached generator first | ||
update: options.update, | ||
packageManager: options.npm ? 'npm' : null, | ||
mock: options.mock, | ||
skipInstall: options.skipInstall, | ||
skipCache: | ||
typeof options.skipCache === 'boolean' ? | ||
options.skipCache : | ||
process.env.NODE_ENV === 'test', | ||
logLevel | ||
} | ||
} | ||
static async mockPrompt(from, answers, subAnswers) { | ||
const options = typeof from === 'string' ? { from } : from | ||
const sao = new SAO( | ||
Object.assign( | ||
{ | ||
outDir: path.join( | ||
require('os').tmpdir(), | ||
require('crypto') | ||
.randomBytes(20) | ||
.toString('hex'), | ||
`sao-temp` | ||
), | ||
logLevel: 1, | ||
mock: { | ||
answers, | ||
subAnswers | ||
}, | ||
skipCache: true | ||
}, | ||
options | ||
) | ||
) | ||
const loaded = await loadConfig(generator.path) | ||
const config = loaded.path | ||
? loaded.data | ||
: require(path.join(__dirname, 'saofile.fallback.js')) | ||
const TestHelper = require('./TestHelper') | ||
return new TestHelper(sao).run() | ||
} | ||
async getDefaultValue(template) { | ||
return require('./utils/renderTemplate')(template, { | ||
folderName: this.options.outDirName, | ||
folderPath: this.options.outDir, | ||
async gitUser() { | ||
const gitInfo = await require('./utils/gitInfo')() | ||
return gitInfo.user | ||
}, | ||
async gitEmail() { | ||
const gitInfo = await require('./utils/gitInfo')() | ||
return gitInfo.email | ||
}, | ||
pkg: () => { | ||
return require(path.join(this.options.outDir, 'package.json')) | ||
if (generator.subGenerator) { | ||
const subGenerator = | ||
config.generators && | ||
config.generators.find(g => g.name === generator.subGenerator) | ||
if (subGenerator) { | ||
const generatorPath = isLocalPath(subGenerator.from) | ||
? path.resolve(generator.path, subGenerator.from) | ||
: resolveFrom(generator.path, subGenerator.from) | ||
return this.run(generatorPath) | ||
} | ||
}) | ||
} | ||
async generate({ from = this.options.from, baseDir, parentName, name } = {}) { | ||
const actualGenerator = await this.getGenerator( | ||
from, | ||
baseDir, | ||
name, | ||
parentName | ||
) | ||
if (!actualGenerator) { | ||
return logger.error('No generator was found!') | ||
throw new SAOError(`No such sub generator in generator ${generator.path}`) | ||
} | ||
const { | ||
generatorPath, | ||
config, | ||
generatorName, | ||
storeKey | ||
} = actualGenerator | ||
await this.runGenerator(generator, config) | ||
} | ||
const beforeGenerators = [] | ||
const afterGenerators = [] | ||
if (config.generators) { | ||
for (const g of config.generators) { | ||
if (g.invoke) { | ||
if (g.invoke === 'after') { | ||
afterGenerators.push(g) | ||
} else { | ||
beforeGenerators.push(g) | ||
} | ||
} | ||
} | ||
async runGenerator(generator, config) { | ||
if (config.description) { | ||
logger.status('green', 'Generator', config.description) | ||
} | ||
if (beforeGenerators.length > 0) { | ||
await this.runGenerators( | ||
beforeGenerators, | ||
generatorPath, | ||
name || generatorName | ||
) | ||
} | ||
const GeneratorContext = this.opts.getContext | ||
? this.opts.getContext(BaseGeneratorContext) | ||
: BaseGeneratorContext | ||
const generatorContext = new GeneratorContext(this, generator) | ||
this.generatorContext = generatorContext | ||
const callWithContext = target => { | ||
return typeof target === 'function' ? target.call(this, this) : target | ||
if (typeof config.prepare === 'function') { | ||
await config.prepare.call(generatorContext, generatorContext) | ||
} | ||
if (config.prompts) { | ||
let prompts = config.prompts | ||
if (!Array.isArray(prompts) && typeof prompts === 'object') { | ||
prompts = Object.keys(prompts).map(name => | ||
Object.assign({ name }, prompts[name]) | ||
) | ||
} | ||
prompts = await Promise.all( | ||
callWithContext(config.prompts).map(async prompt => { | ||
if (typeof prompt.when === 'string') { | ||
const when = prompt.when | ||
prompt.when = answers => evaluate(when, answers) | ||
} | ||
if (prompt.store && !this.options.skipCache) { | ||
const stored = require('./promptStore').get( | ||
`${storeKey}.${prompt.name}` | ||
) | ||
if (stored !== undefined) { | ||
prompt.default = stored | ||
} | ||
} | ||
if (typeof prompt.default === 'string') { | ||
prompt.default = await this.getDefaultValue(prompt.default) | ||
} | ||
return prompt | ||
}) | ||
) | ||
if (this.options.mock) { | ||
this.answers = {} | ||
for (const prompt of prompts) { | ||
this.answers[prompt.name] = | ||
typeof prompt.default === 'function' ? | ||
prompt.default(this.answers) : | ||
prompt.default | ||
} | ||
await require('./runPrompts')(config, generatorContext) | ||
} | ||
if (parentName) { | ||
for (const name of Object.keys(this.options.mock.subAnswers)) { | ||
const g = await this.getGenerator(name) | ||
if (g.generatorPath === generatorPath) { | ||
Object.assign(this.answers, this.options.mock.subAnswers[name]) | ||
break | ||
} | ||
} | ||
} else { | ||
Object.assign(this.answers, this.options.mock.answers) | ||
} | ||
} else { | ||
this.answers = await require('inquirer').prompt(prompts) | ||
// Prevent `variable is undefined` error in ejs template | ||
for (const prompt of prompts) { | ||
if ( | ||
!Object.prototype.hasOwnProperty.call(this.answers, prompt.name) | ||
) { | ||
this.answers[prompt.name] = undefined | ||
} | ||
} | ||
} | ||
if (!this.options.skipCache) { | ||
Object.keys(this.answers).forEach(key => { | ||
const prompt = prompts.find(p => p.name === key) | ||
if (prompt && prompt.store) { | ||
require('./promptStore').set( | ||
`${storeKey}.${key}`, | ||
this.answers[key] | ||
) | ||
} | ||
}) | ||
} | ||
} | ||
if (config.actions) { | ||
const actions = callWithContext(config.actions) | ||
.map(action => { | ||
if (typeof action.when === 'string') { | ||
action.when = evaluate(action.when, this.answers) | ||
} else if (typeof action.when === 'function') { | ||
action.when = action.when(this.answers) | ||
} else { | ||
action.when = action.when === undefined ? true : action.when | ||
} | ||
return action | ||
}) | ||
.filter(action => action.when) | ||
await invokeActions(actions, { | ||
generatorPath, | ||
templateDir: config.templateDir, | ||
outDir: this.options.outDir, | ||
context: this, | ||
config | ||
}) | ||
await require('./runActions')(config, generatorContext) | ||
} | ||
if (afterGenerators.length > 0) { | ||
await this.runGenerators( | ||
afterGenerators, | ||
generatorPath, | ||
name || generatorName | ||
) | ||
if (!this.opts.mock && config.completed) { | ||
await config.completed.call(generatorContext, generatorContext) | ||
} | ||
if (config.completed && !this.options.mock) { | ||
try { | ||
await callWithContext(config.completed) | ||
} catch (err) { | ||
// In case spinner is not stopped | ||
require('./spinner').stop() | ||
throw err | ||
} | ||
} | ||
} | ||
} | ||
async runGenerators(generators, generatorPath, parentName) { | ||
for (const generator of generators) { | ||
await this.generate({ | ||
from: generator.from, | ||
baseDir: generatorPath, | ||
name: generator.name, | ||
parentName | ||
}) | ||
} | ||
} | ||
/** | ||
* Create an instance of SAO | ||
* @param {Object} opts | ||
*/ | ||
module.exports = opts => new SAO(opts) | ||
async getGenerator(from, baseDir, name, parentName) { | ||
const generator = parseGenerator(from) | ||
module.exports.mock = require('./mock') | ||
if (parentName && (name || from)) { | ||
logger.info( | ||
`${chalk.bold('Using sub generator:')} ${`${chalk.bold( | ||
parentName | ||
)} ${chalk.dim(':')} `}${chalk.bold(`${name || from}`)}` | ||
) | ||
} | ||
module.exports.handleError = require('./handleError') | ||
let generatorPath | ||
let storeKey | ||
if (generator.type === 'local') { | ||
generatorPath = path.resolve(baseDir || '.', generator.path) | ||
storeKey = generatorPath | ||
} else if (generator.type === 'npm') { | ||
generatorPath = await require('./ensurePackage')(generator.module, { | ||
forceInstall: this.options.update, | ||
packageManager: this.options.packageManager | ||
/** | ||
* Download git repo | ||
* @param {string} repo | ||
* @param {string} target | ||
* @param {Object=} opts | ||
*/ | ||
function downloadRepo(repo, target, opts) { | ||
return fs.remove(target).then( | ||
() => | ||
new Promise((resolve, reject) => { | ||
downloadGitRepo(repo, target, opts, err => { | ||
if (err) return reject(err) | ||
resolve() | ||
}) | ||
}) | ||
storeKey = generatorPath + ':' + (generator.version || '') | ||
} else if (generator.type === 'git') { | ||
generatorPath = await require('./ensureRepo')({ | ||
repo: generator.repo, | ||
user: generator.user, | ||
version: generator.version, | ||
clone: this.options.clone, | ||
forceDownload: this.options.update, | ||
packageManager: this.options.packageManager | ||
}) | ||
storeKey = generatorPath + ':' + (generator.version || '') | ||
} | ||
) | ||
} | ||
let config | ||
const configFilePath = path.join(generatorPath, 'saofile.js') | ||
const hasConfigFile = await fs.pathExists(configFilePath) | ||
if (hasConfigFile) { | ||
config = require(configFilePath) | ||
} else { | ||
config = require('./defaultSAOFile') | ||
} | ||
/** | ||
* Ensure packages are installed in a generator | ||
* In most cases this is used for `repo` generators | ||
* @param {Object} generator | ||
* @param {string=} npmClient | ||
* @param {boolean=} update | ||
*/ | ||
async function ensureRepo(generator, npmClient, update) { | ||
if (!update && (await fs.pathExists(generator.path))) { | ||
return | ||
} | ||
let pkg = {} | ||
const pkgPath = path.join(generatorPath, 'package.json') | ||
if (await fs.pathExists(pkgPath)) { | ||
pkg = require(pkgPath) | ||
// Download repo | ||
spinner.start('Downloading repo') | ||
try { | ||
await downloadRepo(generator.slug, generator.path) | ||
logger.success('Downloaded repo') | ||
} catch (err) { | ||
let message = err.message | ||
if (err.host && err.path) { | ||
message += '\n' + err.host + err.path | ||
} | ||
throw new SAOError(message) | ||
} | ||
// Only try to install dependencies for real generator | ||
const [hasConfig, hasPackageJson] = await Promise.all([ | ||
loadConfig.hasConfig(generator.path), | ||
fs.pathExists(path.join(generator.path, 'package.json')) | ||
]) | ||
if (!this.options.mock && generator.type === 'npm') { | ||
const notifier = require('update-notifier')({ | ||
pkg, | ||
updateCheckInterval: 10 | ||
}) | ||
if (hasConfig && hasPackageJson) { | ||
await require('./installPackages')({ | ||
cwd: generator.path, | ||
npmClient, | ||
installArgs: ['--production'] | ||
}) | ||
} | ||
} | ||
if (notifier.update) { | ||
logger.warn( | ||
`Generator update available! ${chalk.dim( | ||
notifier.update.current | ||
)} -> ${chalk.green(notifier.update.latest)}` | ||
) | ||
logger.warn( | ||
`Run this generator with ${chalk.cyan('--update')} or ${chalk.cyan( | ||
'-u' | ||
)} flag to update.` | ||
) | ||
} | ||
} | ||
async function ensureLocal(generator) { | ||
const exists = await fs.pathExists(generator.path) | ||
if (generator.generator) { | ||
let actualGenerator | ||
for (const subGeneratorName of generator.generator) { | ||
const subGenerator = config.generators.find( | ||
g => g.name === subGeneratorName | ||
) | ||
if (subGenerator) { | ||
actualGenerator = this.getGenerator( | ||
subGenerator.from, | ||
generatorPath, | ||
subGenerator.name, | ||
generator.name | ||
) | ||
} | ||
} | ||
if (!exists) { | ||
throw new SAOError( | ||
`Directory ${chalk.underline(generator.path)} does not exist` | ||
) | ||
} | ||
} | ||
return actualGenerator | ||
} | ||
async function ensurePackage(generator, npmClient, update) { | ||
const installPath = path.join(paths.packagePath, generator.hash) | ||
if (config.description !== false) { | ||
logDescription(config.description || pkg.description) | ||
} | ||
if (update || !(await fs.pathExists(generator.path))) { | ||
await fs.ensureDir(installPath) | ||
await fs.writeFile( | ||
path.join(installPath, 'package.json'), | ||
JSON.stringify({ | ||
private: true | ||
}), | ||
'utf8' | ||
) | ||
logger.debug('Installing generator at', installPath) | ||
await require('./installPackages')({ | ||
cwd: installPath, | ||
npmClient, | ||
packages: [`${generator.name}@${generator.version || 'latest'}`] | ||
}) | ||
} | ||
} | ||
config.templateDir = config.templateDir || 'template' | ||
// async function ensureOutDir(outDir, force) { | ||
// if (force) return | ||
// if (!(await fs.pathExists(outDir))) return | ||
return { | ||
pkg, | ||
generatorPath, | ||
config, | ||
generatorName: generator.name, | ||
storeKey: escapeDots(storeKey) | ||
} | ||
} | ||
} | ||
// const files = await fs.readdir(outDir) | ||
// if (files.length === 0) return | ||
// const answers = await require('inquirer').prompt([{ | ||
// name: 'force', | ||
// message: `Directory ${chalk.underline(outDir)} already exists, do you want to continue`, | ||
// type: 'confirm', | ||
// default: false | ||
// }]) | ||
// if (!answers.force) { | ||
// throw new SAOError(`Aborted`) | ||
// } | ||
// } |
@@ -6,12 +6,23 @@ const path = require('path') | ||
constructor(options) { | ||
this.options = {} | ||
this.setOptions(options) | ||
this.options = Object.assign( | ||
{ | ||
logLevel: 2 | ||
}, | ||
options | ||
) | ||
} | ||
setOptions(options) { | ||
this.options = Object.assign({ | ||
logLevel: 3 | ||
}, this.options, options) | ||
Object.assign(this.options, options) | ||
} | ||
// level: 4 | ||
debug(...args) { | ||
if (this.options.logLevel < 4) { | ||
return | ||
} | ||
this.status('magenta', 'debug', ...args) | ||
} | ||
// level: 2 | ||
@@ -60,5 +71,3 @@ warn(...args) { | ||
this.info( | ||
`${chalk[color](type)}${chalk.dim(':')} ${chalk.green( | ||
path.relative(process.cwd(), fp) | ||
)}` | ||
`${chalk[color](type)} ${chalk.green(path.relative(process.cwd(), fp))}` | ||
) | ||
@@ -72,5 +81,5 @@ } | ||
this.info( | ||
`${chalk.blue('Moved')}${chalk.dim(':')} ${chalk.green( | ||
`${chalk.blue('Moved')} ${chalk.green( | ||
path.relative(process.cwd(), from) | ||
)} -> ${chalk.green(path.relative(process.cwd(), to))}` | ||
)} ${chalk.dim('->')} ${chalk.green(path.relative(process.cwd(), to))}` | ||
) | ||
@@ -77,0 +86,0 @@ } |
@@ -1,1 +0,3 @@ | ||
module.exports = require('ora')({ spinner: 'dots10' }) | ||
const ora = require('ora') | ||
module.exports = ora() |
@@ -1,2 +0,2 @@ | ||
module.exports = function (exp, data) { | ||
module.exports = function(exp, data) { | ||
/* eslint-disable no-new-func */ | ||
@@ -3,0 +3,0 @@ const fn = new Function('data', `with (data) { return ${exp} }`) |
@@ -1,5 +0,5 @@ | ||
module.exports = async (tpl, data) => { | ||
if (typeof tpl !== 'string') { | ||
module.exports = (template, data) => { | ||
if (typeof template !== 'string') { | ||
throw new TypeError( | ||
`Expected a string in the first argument, got ${typeof tpl}` | ||
`Expected a string in the first argument, got ${typeof template}` | ||
) | ||
@@ -14,5 +14,7 @@ } | ||
const re = /\[(.*?)\]/g | ||
const regex = /(\\)?{(.*?)}/g | ||
return asyncReplace(tpl, re, async (_, key) => { | ||
return template.replace(regex, (_, disabled, key) => { | ||
if (disabled) return `{${key}}` | ||
let ret = data | ||
@@ -22,5 +24,2 @@ | ||
ret = ret ? ret[prop] : '' | ||
if (typeof ret === 'function') { | ||
ret = await ret() | ||
} | ||
} | ||
@@ -31,11 +30,1 @@ | ||
} | ||
async function asyncReplace(str, re, replacer) { | ||
const fns = [] | ||
str.replace(re, (_, ...args) => { | ||
fns.push(replacer(_, ...args)) | ||
return _ | ||
}) | ||
const replacements = await Promise.all(fns) | ||
return str.replace(re, () => replacements.shift()) | ||
} |
{ | ||
"name": "sao", | ||
"version": "1.0.0-alpha.7", | ||
"description": "Project scaffolding for humans.", | ||
"version": "1.0.0", | ||
"description": "Futuristic scaffolding tool.", | ||
"repository": { | ||
"url": "EGOIST/sao", | ||
"url": "https://github.com/saojs/sao.git", | ||
"type": "git" | ||
}, | ||
"bin": "bin/sao.js", | ||
"main": "lib/index.js", | ||
"bin": "bin/cli.js", | ||
"files": [ | ||
"lib", | ||
"bin" | ||
"bin", | ||
"!**/__test__/**" | ||
], | ||
"publishConfig": { | ||
"tag": "next" | ||
}, | ||
"scripts": { | ||
"test": "npm run lint && echo 'no tests!'", | ||
"lint": "xo", | ||
"docs": "vuepress dev docs" | ||
"test": "npm run lint && ava", | ||
"lint": "xo" | ||
}, | ||
"engines": { | ||
"node": ">=8" | ||
}, | ||
"author": "EGOIST <0x142857@gmail.com>", | ||
"author": "egoist <0x142857@gmail.com>", | ||
"license": "MIT", | ||
"dependencies": { | ||
"cac": "^5.0.10", | ||
"chalk": "^2.4.0", | ||
"conf": "^1.4.0", | ||
"download-git-repo": "^1.0.2", | ||
"fast-glob": "^2.2.0", | ||
"git-config-path": "^1.0.1", | ||
"inquirer": "^5.2.0", | ||
"install-packages": "^0.2.1", | ||
"cac": "^5.0.15", | ||
"chalk": "^2.4.1", | ||
"conf": "^2.0.0", | ||
"cross-spawn": "^6.0.5", | ||
"download-git-repo": "^1.1.0", | ||
"fs-extra": "^7.0.0", | ||
"hash-sum": "^1.0.2", | ||
"ini": "^1.3.5", | ||
"inquirer": "^6.2.0", | ||
"joycon": "^2.1.2", | ||
"jstransformer": "^1.0.0", | ||
"jstransformer-ejs": "^0.2.0", | ||
"jstransformer-handlebars": "^1.1.0", | ||
"majo": "^0.6.2", | ||
"micromatch": "^3.1.10", | ||
"ora": "^2.0.0", | ||
"parse-git-config": "^2.0.2", | ||
"ora": "^3.0.0", | ||
"parse-package-name": "^0.1.0", | ||
"resolve": "^1.7.1", | ||
"update-notifier": "^2.5.0" | ||
"resolve-from": "^4.0.0", | ||
"strip-ansi": "^5.0.0", | ||
"wcwidth": "^1.0.1" | ||
}, | ||
"devDependencies": { | ||
"@types/inquirer": "^0.0.41", | ||
"@types/jest": "^22.2.3", | ||
"@types/node": "^9.6.6", | ||
"ava": "^0.25.0", | ||
"cz-conventional-changelog": "2.1.0", | ||
"eslint-config-prettier": "^3.1.0", | ||
"eslint-config-rem": "^3.0.0", | ||
"jest": "^22.4.3", | ||
"vuepress": "^0.13.0", | ||
"eslint-plugin-prettier": "^3.0.0", | ||
"husky": "^1.1.3", | ||
"lint-staged": "^8.0.4", | ||
"prettier": "^1.14.3", | ||
"semantic-release": "^15.10.7", | ||
"xo": "^0.18.0" | ||
}, | ||
"xo": { | ||
"extends": "rem", | ||
"envs": [ | ||
"jest" | ||
"extends": [ | ||
"rem", | ||
"plugin:prettier/recommended" | ||
], | ||
"rules": { | ||
"unicorn/filename-case": "off", | ||
"no-await-in-loop": "off" | ||
"no-await-in-loop": "off", | ||
"unicorn/filename-case": "off" | ||
} | ||
}, | ||
"husky": { | ||
"hooks": { | ||
"pre-commit": "lint-staged" | ||
} | ||
}, | ||
"lint-staged": { | ||
"*.js": [ | ||
"yarn lint --fix", | ||
"git add" | ||
] | ||
}, | ||
"release": { | ||
"branch": "master" | ||
}, | ||
"config": { | ||
"commitizen": { | ||
"path": "./node_modules/cz-conventional-changelog" | ||
} | ||
} | ||
} |
@@ -0,16 +1,42 @@ | ||
<p align="center"> | ||
<img src="https://user-images.githubusercontent.com/8784712/47992262-650b1780-e127-11e8-9e58-6c75e22ad99f.png" width="150" /> | ||
</p> | ||
# sao | ||
<p align="center"> | ||
<br> | ||
<a href="https://npmjs.com/package/sao"><img src="https://img.shields.io/npm/v/sao.svg?style=flat" alt="NPM version"></a> <a href="https://npmjs.com/package/sao"><img src="https://img.shields.io/npm/dm/sao.svg?style=flat" alt="NPM downloads"></a> <a href="https://circleci.com/gh/saojs/sao"><img src="https://img.shields.io/circleci/project/saojs/sao/master.svg?style=flat" alt="Build Status"></a> <a href="https://packagephobia.now.sh/result?p=sao"><img src="https://packagephobia.now.sh/badge?p=sao" alt="install size"></a> <a href="https://github.com/egoist/donate"><img src="https://img.shields.io/badge/$-donate-ff69b4.svg?maxAge=2592000&style=flat" alt="donate"></a> <a href="https://chat.egoist.moe"><img src="https://img.shields.io/badge/chat-on%20discord-7289DA.svg?style=flat" alt="chat"></a> | ||
</p> | ||
[](https://npmjs.com/package/sao) [](https://npmjs.com/package/sao) [](https://circleci.com/gh/saojs/sao/tree/master) [](https://github.com/egoist/donate) | ||
## Motivation | ||
**SAO** was made because **yeoman**, while powerful, is too complex. **vue-cli**, on the other hand, is more than a scaffolding tool and lacks some important features like *unit testing*. **SAO** combines the powerful core features of **yeoman** with the simplicity of **vue-cli** into a single application. | ||
**SAO** is compatible with: | ||
- Regular git repo (simply download it) | ||
- SAO generator as git repo | ||
- SAO generator as npm package | ||
- SAO generator in local folder | ||
⚡ ️**Both repo and npm package can be used offline.** | ||
## Quick Start | ||
```bash | ||
npm i -g sao | ||
# Create a node module from `sao-nm` generator | ||
sao nm my-node-module | ||
yarn global add sao | ||
# An official generator for creating a Node.js project | ||
# Generate from git repo | ||
sao egoist/sao-nm my-module | ||
# Or from npm package (npm.im/sao-nm) | ||
sao nm my-module | ||
``` | ||
__Documentation: https://saojs.org__ | ||
For detailed usage please head to https://saojs.org | ||
## Related | ||
- [Awesome SAO](https://github.com/saojs/awesome-sao) - A curated list of delightful SAO resources. | ||
- [SAO articles on medium.com](https://medium.com/saojs). | ||
## Contributing | ||
@@ -24,8 +50,9 @@ | ||
## Author | ||
**sao** © [EGOIST](https://github.com/egoist), Released under the [MIT](./LICENSE) License.<br> | ||
Authored and maintained by EGOIST with help from contributors ([list](https://github.com/EGOIST/sao/contributors)). | ||
**SAO** © [EGOIST](https://github.com/egoist), Released under the [MIT](https://egoist.mit-license.org/) License.<br> | ||
Authored and maintained by EGOIST with help from contributors ([list](https://github.com/saojs/sao/contributors)). | ||
> [github.com/egoist](https://github.com/EGOIST) · GitHub [@EGOIST](https://github.com/EGOIST) · Twitter [@_egoistlily](https://twitter.com/_egoistlily) | ||
> [egoist.sh](https://egoist.sh) · GitHub [@EGOIST](https://github.com/egoist) · Twitter [@_egoistlily](https://twitter.com/_egoistlily) | ||
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
No tests
QualityPackage does not have any tests. This is a strong signal of a poorly maintained or low quality package.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
32433
25
942
1
58
19
10
8
1
+ Addedcross-spawn@^6.0.5
+ Addedfs-extra@^7.0.0
+ Addedhash-sum@^1.0.2
+ Addedini@^1.3.5
+ Addedjoycon@^2.1.2
+ Addedresolve-from@^4.0.0
+ Addedstrip-ansi@^5.0.0
+ Addedwcwidth@^1.0.1
+ Addedansi-regex@4.1.1(transitive)
+ Addedchardet@0.7.0(transitive)
+ Addedcli-spinners@2.9.2(transitive)
+ Addedconf@2.2.0(transitive)
+ Addedcross-spawn@6.0.6(transitive)
+ Addedexternal-editor@3.1.0(transitive)
+ Addedhash-sum@1.0.2(transitive)
+ Addedinquirer@6.5.2(transitive)
+ Addednice-try@1.0.5(transitive)
+ Addedora@3.4.0(transitive)
+ Addedresolve-from@4.0.0(transitive)
+ Addedrxjs@6.6.7(transitive)
+ Addedstrip-ansi@5.2.0(transitive)
+ Addedtslib@1.14.1(transitive)
- Removedfast-glob@^2.2.0
- Removedgit-config-path@^1.0.1
- Removedinstall-packages@^0.2.1
- Removedjstransformer-handlebars@^1.1.0
- Removedparse-git-config@^2.0.2
- Removedresolve@^1.7.1
- Removedupdate-notifier@^2.5.0
- Removedansi-align@2.0.0(transitive)
- Removedboxen@1.3.0(transitive)
- Removedcamelcase@4.1.0(transitive)
- Removedchardet@0.4.2(transitive)
- Removedci-info@1.6.0(transitive)
- Removedcli-boxes@1.0.0(transitive)
- Removedcli-spinners@1.3.1(transitive)
- Removedcommand-exists@1.2.9(transitive)
- Removedconf@1.4.0(transitive)
- Removedconfigstore@3.1.5(transitive)
- Removedcross-spawn@4.0.25.1.0(transitive)
- Removedcrypto-random-string@1.0.0(transitive)
- Removeddeep-extend@0.6.0(transitive)
- Removedexeca@0.7.0(transitive)
- Removedexpand-tilde@2.0.2(transitive)
- Removedexternal-editor@2.2.0(transitive)
- Removedfs-exists-sync@0.1.0(transitive)
- Removedgit-config-path@1.0.1(transitive)
- Removedglobal-dirs@0.1.1(transitive)
- Removedhandlebars@4.7.8(transitive)
- Removedhomedir-polyfill@1.0.3(transitive)
- Removedimport-lazy@2.1.0(transitive)
- Removedinquirer@5.2.0(transitive)
- Removedinstall-packages@0.2.5(transitive)
- Removedis-ci@1.2.1(transitive)
- Removedis-core-module@2.16.1(transitive)
- Removedis-installed-globally@0.1.0(transitive)
- Removedis-npm@1.0.0(transitive)
- Removedis-path-inside@1.0.1(transitive)
- Removedjstransformer-handlebars@1.2.0(transitive)
- Removedlatest-version@3.1.0(transitive)
- Removedlru-cache@4.1.5(transitive)
- Removedneo-async@2.6.2(transitive)
- Removednpm-run-path@2.0.2(transitive)
- Removedora@2.1.0(transitive)
- Removedp-finally@1.0.0(transitive)
- Removedpackage-json@4.0.1(transitive)
- Removedparse-git-config@2.0.3(transitive)
- Removedparse-passwd@1.0.0(transitive)
- Removedpath-is-inside@1.0.2(transitive)
- Removedpath-parse@1.0.7(transitive)
- Removedpseudomap@1.0.2(transitive)
- Removedrc@1.2.8(transitive)
- Removedregistry-auth-token@3.4.0(transitive)
- Removedregistry-url@3.1.0(transitive)
- Removedresolve@1.22.10(transitive)
- Removedrxjs@5.5.12(transitive)
- Removedsemver-diff@2.1.0(transitive)
- Removedsource-map@0.6.1(transitive)
- Removedstrip-eof@1.0.0(transitive)
- Removedstrip-json-comments@2.0.1(transitive)
- Removedsupports-preserve-symlinks-flag@1.0.0(transitive)
- Removedsymbol-observable@1.0.1(transitive)
- Removedterm-size@1.2.0(transitive)
- Removeduglify-js@3.19.3(transitive)
- Removedunique-string@1.0.0(transitive)
- Removedupdate-notifier@2.5.0(transitive)
- Removedwidest-line@2.0.1(transitive)
- Removedwordwrap@1.0.0(transitive)
- Removedxdg-basedir@3.0.0(transitive)
- Removedyallist@2.1.2(transitive)
Updatedcac@^5.0.15
Updatedchalk@^2.4.1
Updatedconf@^2.0.0
Updateddownload-git-repo@^1.1.0
Updatedinquirer@^6.2.0
Updatedora@^3.0.0