Skip to content

Commit

Permalink
feat: make cac deno-native
Browse files Browse the repository at this point in the history
  • Loading branch information
egoist committed Dec 11, 2020
1 parent e622b16 commit 4706134
Show file tree
Hide file tree
Showing 16 changed files with 704 additions and 334 deletions.
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,5 @@ node_modules
/types
/api-doc
coverage
/mod.d.ts
/mod.js
/dist
/deno
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ Use CAC as simple argument parser:
const cli = require('cac')()

cli.option('--type <type>', 'Choose a project type', {
default: 'node'
default: 'node',
})

const parsed = cli.parse()
Expand All @@ -91,7 +91,7 @@ console.log(JSON.stringify(parsed, null, 2))
const cli = require('cac')()

cli.option('--type [type]', 'Choose a project type', {
default: 'node'
default: 'node',
})
cli.option('--name <name>', 'Provide your name')

Expand Down Expand Up @@ -141,7 +141,7 @@ Options in kebab-case should be referenced in camelCase in your code:
cli
.command('dev', 'Start dev server')
.option('--clear-screen', 'Clear screen')
.action(options => {
.action((options) => {
console.log(options.clearScreen)
})
```
Expand Down Expand Up @@ -221,7 +221,7 @@ cli
.command('build', 'desc')
.option('--env <env>', 'Set envs')
.example('--env.API_SECRET xxx')
.action(options => {
.action((options) => {
console.log(options)
})

Expand Down Expand Up @@ -301,8 +301,7 @@ import { cac } from 'cac'
### With Deno

```ts
// @deno-types="https://unpkg.com/cac/mod.d.ts"
import { cac } from 'https://unpkg.com/cac/mod.js'
import { cac } from 'https://unpkg.com/cac/mod.ts'

const cli = cac('my-program')
```
Expand Down
2 changes: 2 additions & 0 deletions mod.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Deno users should use mod.ts instead
export * from './deno/index.ts'
2 changes: 2 additions & 0 deletions mod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// For Deno
export * from './deno/index.ts'
3 changes: 1 addition & 2 deletions mod_test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// @deno-types="./mod.d.ts"
import { cac } from './mod.js'
import { cac } from './mod.ts'

const cli = cac('my-program')

Expand Down
47 changes: 27 additions & 20 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
"type": "git"
},
"main": "dist/index.js",
"module": "mod.mjs",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"exports": {
".": {
"import": "./mod.mjs",
"import": "./dist/index.mjs",
"require": "./dist/index.js"
},
"./package.json": "./package.json",
Expand All @@ -20,44 +20,51 @@
"files": [
"dist",
"!**/__test__/**",
"/mod.mjs",
"/mod.js",
"/mod.d.ts"
"/mode.ts",
"/deno"
],
"scripts": {
"test": "jest",
"test:cov": "jest --coverage",
"build": "rollup -c",
"build:deno": "node -r sucrase/register scripts/build-deno.ts",
"build:node": "rollup -c",
"build": "yarn build:deno && yarn build:node",
"toc": "markdown-toc -i README.md",
"prepublishOnly": "npm run build && cp mod.js mod.mjs",
"docs:api": "typedoc --out api-doc --readme none --exclude \"**/__test__/**\" --theme minimal"
},
"author": "egoist <[email protected]>",
"license": "MIT",
"devDependencies": {
"@rollup/plugin-commonjs": "^11.1.0",
"@rollup/plugin-node-resolve": "^7.1.3",
"@types/execa": "^0.9.0",
"@types/jest": "^23.3.9",
"@babel/core": "^7.12.10",
"@babel/plugin-syntax-typescript": "^7.12.1",
"@rollup/plugin-commonjs": "^17.0.0",
"@rollup/plugin-node-resolve": "^11.0.0",
"@types/fs-extra": "^9.0.5",
"@types/jest": "^26.0.19",
"@types/mri": "^1.1.0",
"cz-conventional-changelog": "^2.1.0",
"esbuild": "^0.8.21",
"eslint-config-rem": "^3.0.0",
"events": "^3.0.0",
"execa": "^1.0.0",
"execa": "^5.0.0",
"fs-extra": "^9.0.1",
"globby": "^11.0.1",
"husky": "^1.2.0",
"jest": "^24.9.0",
"lint-staged": "^8.1.0",
"markdown-toc": "^1.2.0",
"mri": "^1.1.1",
"prettier": "^1.15.2",
"rollup": "^2.10.0",
"rollup-plugin-dts": "^1.4.3",
"rollup-plugin-esbuild": "^1.4.1",
"mri": "^1.1.6",
"prettier": "^2.2.1",
"rollup": "^2.34.2",
"rollup-plugin-dts": "^2.0.1",
"rollup-plugin-esbuild": "^2.6.1",
"semantic-release": "^17.3.0",
"ts-jest": "^23.10.5",
"ts-node": "^7.0.1",
"typedoc": "^0.17.6",
"typescript": "^3.9.2"
"sucrase": "^3.16.0",
"ts-jest": "^26.4.4",
"ts-node": "^9.1.1",
"typedoc": "^0.19.2",
"typescript": "^4.1.2"
},
"engines": {
"node": ">=8"
Expand Down
44 changes: 16 additions & 28 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,55 +2,43 @@ import nodeResolvePlugin from '@rollup/plugin-node-resolve'
import esbuildPlugin from 'rollup-plugin-esbuild'
import dtsPlugin from 'rollup-plugin-dts'

function createConfig(target, dts) {
const deno = target === 'deno'
let file = deno ? 'mod.js' : 'dist/index.js'
function createConfig({ dts, esm } = {}) {
let file = 'dist/index.js'
if (dts) {
file = file.replace('.js', '.d.ts')
}
if (esm) {
file = file.replace('.js', '.mjs')
}
return {
input: 'src/index.ts',
output: {
format: deno || dts ? 'esm' : 'cjs',
format: dts || esm ? 'esm' : 'cjs',
file,
exports: 'named'
exports: 'named',
},
plugins: [
nodeResolvePlugin({
preferBuiltins: !deno,
mainFields: dts ? ['types', 'typings'] : ['module', 'main'],
extensions: dts ? ['.d.ts', '.ts'] : ['.js', '.json', '.mjs'],
customResolveOptions: {
moduleDirectory: dts
moduleDirectories: dts
? ['node_modules/@types', 'node_modules']
: 'node_modules'
}
: ['node_modules'],
},
}),
!dts &&
require('@rollup/plugin-commonjs')({
namedExports: {
path: ['basename'],
events: ['EventEmitter']
}
}),
!dts && require('@rollup/plugin-commonjs')(),
!dts &&
esbuildPlugin({
target: 'es2017'
target: 'es2017',
}),
dts && dtsPlugin(),
deno && {
renderChunk(code) {
// Remove triple slashes reference since Deno doesn't support that
return code.replace(/^\/\/\/(.+)/, '')
}
}
].filter(Boolean)
].filter(Boolean),
}
}

export default [
createConfig('node'),
createConfig('deno'),
createConfig('node', true),
createConfig('deno', true)
createConfig(),
createConfig({ dts: true }),
createConfig({ esm: true }),
]
53 changes: 53 additions & 0 deletions scripts/build-deno.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import path from 'path'
import globby from 'globby'
import fs from 'fs-extra'
import { transformAsync, PluginObj, types as Types } from '@babel/core'
import tsSyntax from '@babel/plugin-syntax-typescript'

function node2deno(options: { types: typeof Types }): PluginObj {
const t = options.types
return {
name: 'node2deno',

visitor: {
ImportDeclaration(path) {
const source = path.node.source
if (source.value.startsWith('.')) {
if (source.value.endsWith('/node')) {
source.value = source.value.replace('node', 'deno')
}
source.value += '.ts'
} else if (source.value === 'events') {
source.value = `https://deno.land/[email protected]/node/events.ts`
} else if (source.value === 'mri') {
source.value = `https://cdn.skypack.dev/mri`
}
},

IfStatement(path) {
if (path.getSource().includes('@remove-for-deno')) {
path.remove()
}
},
},
}
}

async function main() {
const files = await globby(['**/*.ts', '!**/__test__/**'], { cwd: 'src' })
await Promise.all(
files.map(async (file) => {
if (file === 'node.ts') return
const content = await fs.readFile(path.join('src', file), 'utf8')
const transformed = await transformAsync(content, {
plugins: [tsSyntax, node2deno],
})
await fs.outputFile(path.join('deno', file), transformed.code, 'utf8')
})
)
}

main().catch((error) => {
console.error(error)
process.exitCode = 1
})
36 changes: 20 additions & 16 deletions src/CAC.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import Command, {
GlobalCommand,
CommandConfig,
HelpCallback,
CommandExample
CommandExample,
} from './Command'
import { OptionConfig } from './Option'
import {
getMriOptions,
setDotProp,
setByType,
getFileName,
camelcaseOptionName
camelcaseOptionName,
} from './utils'
import { processArgs } from './node'

Expand Down Expand Up @@ -43,8 +43,8 @@ class CAC extends EventEmitter {
*/
options: ParsedArgv['options']

showHelpOnExit: boolean
showVersionOnExit: boolean
showHelpOnExit?: boolean
showVersionOnExit?: boolean

/**
* @param name The program name to display in help and version message
Expand All @@ -53,6 +53,9 @@ class CAC extends EventEmitter {
super()
this.name = name
this.commands = []
this.rawArgs = []
this.args = []
this.options = {}
this.globalCommand = new GlobalCommand(this)
this.globalCommand.usage('<command> [options]')
}
Expand Down Expand Up @@ -155,7 +158,7 @@ class CAC extends EventEmitter {
}
return this
}

unsetMatchedCommand() {
this.matchedCommand = undefined
this.matchedCommandName = undefined
Expand All @@ -168,7 +171,7 @@ class CAC extends EventEmitter {
argv = processArgs,
{
/** Whether to run the action for matched command */
run = true
run = true,
} = {}
): ParsedArgv {
this.rawArgs = argv
Expand All @@ -187,7 +190,7 @@ class CAC extends EventEmitter {
shouldParse = false
const parsedInfo = {
...parsed,
args: parsed.args.slice(1)
args: parsed.args.slice(1),
}
this.setParsedInfo(parsedInfo, command, commandName)
this.emit(`command:${commandName}`, command)
Expand Down Expand Up @@ -243,7 +246,7 @@ class CAC extends EventEmitter {
// All added options
const cliOptions = [
...this.globalCommand.options,
...(command ? command.options : [])
...(command ? command.options : []),
]
const mriOptions = getMriOptions(cliOptions)

Expand All @@ -260,17 +263,16 @@ class CAC extends EventEmitter {
(res, name) => {
return {
...res,
[camelcaseOptionName(name)]: parsed[name]
[camelcaseOptionName(name)]: parsed[name],
}
},
{ _: [] }
)

const args = parsed._
delete parsed._

const options: { [k: string]: any } = {
'--': argsAfterDoubleDashes
'--': argsAfterDoubleDashes,
}

// Set option default value
Expand Down Expand Up @@ -300,16 +302,18 @@ class CAC extends EventEmitter {
}
}

// Set dot nested option values
// Set option values (support dot-nested property name)
for (const key of Object.keys(parsed)) {
const keys = key.split('.')
setDotProp(options, keys, parsed[key])
setByType(options, transforms)
if (key !== '_') {
const keys = key.split('.')
setDotProp(options, keys, parsed[key])
setByType(options, transforms)
}
}

return {
args,
options
options,
}
}

Expand Down
Loading

0 comments on commit 4706134

Please sign in to comment.