New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

sao

Package Overview
Dependencies
Maintainers
3
Versions
119
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

sao - npm Package Compare versions

Comparing version 1.0.0-alpha.7 to 1.0.0

bin/cli.js

88

lib/GeneratorContext.js
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)
}
}
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&amp;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>
[![NPM version](https://img.shields.io/npm/v/sao.svg?style=flat)](https://npmjs.com/package/sao) [![NPM downloads](https://img.shields.io/npm/dm/sao.svg?style=flat)](https://npmjs.com/package/sao) [![CircleCI](https://circleci.com/gh/saojs/sao/tree/master.svg?style=shield)](https://circleci.com/gh/saojs/sao/tree/master) [![donate](https://img.shields.io/badge/$-donate-ff69b4.svg?maxAge=2592000&style=flat)](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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc