Skip to content

Commit 4706134

Browse files
committed
feat: make cac deno-native
1 parent e622b16 commit 4706134

File tree

16 files changed

+704
-334
lines changed

16 files changed

+704
-334
lines changed

.gitignore

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,5 @@ node_modules
33
/types
44
/api-doc
55
coverage
6-
/mod.d.ts
7-
/mod.js
86
/dist
7+
/deno

README.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ Use CAC as simple argument parser:
7474
const cli = require('cac')()
7575

7676
cli.option('--type <type>', 'Choose a project type', {
77-
default: 'node'
77+
default: 'node',
7878
})
7979

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

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

@@ -141,7 +141,7 @@ Options in kebab-case should be referenced in camelCase in your code:
141141
cli
142142
.command('dev', 'Start dev server')
143143
.option('--clear-screen', 'Clear screen')
144-
.action(options => {
144+
.action((options) => {
145145
console.log(options.clearScreen)
146146
})
147147
```
@@ -221,7 +221,7 @@ cli
221221
.command('build', 'desc')
222222
.option('--env <env>', 'Set envs')
223223
.example('--env.API_SECRET xxx')
224-
.action(options => {
224+
.action((options) => {
225225
console.log(options)
226226
})
227227

@@ -301,8 +301,7 @@ import { cac } from 'cac'
301301
### With Deno
302302

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

307306
const cli = cac('my-program')
308307
```

mod.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// Deno users should use mod.ts instead
2+
export * from './deno/index.ts'

mod.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// For Deno
2+
export * from './deno/index.ts'

mod_test.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
// @deno-types="./mod.d.ts"
2-
import { cac } from './mod.js'
1+
import { cac } from './mod.ts'
32

43
const cli = cac('my-program')
54

package.json

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
"type": "git"
88
},
99
"main": "dist/index.js",
10-
"module": "mod.mjs",
10+
"module": "dist/index.mjs",
1111
"types": "dist/index.d.ts",
1212
"exports": {
1313
".": {
14-
"import": "./mod.mjs",
14+
"import": "./dist/index.mjs",
1515
"require": "./dist/index.js"
1616
},
1717
"./package.json": "./package.json",
@@ -20,44 +20,51 @@
2020
"files": [
2121
"dist",
2222
"!**/__test__/**",
23-
"/mod.mjs",
2423
"/mod.js",
25-
"/mod.d.ts"
24+
"/mode.ts",
25+
"/deno"
2626
],
2727
"scripts": {
2828
"test": "jest",
2929
"test:cov": "jest --coverage",
30-
"build": "rollup -c",
30+
"build:deno": "node -r sucrase/register scripts/build-deno.ts",
31+
"build:node": "rollup -c",
32+
"build": "yarn build:deno && yarn build:node",
3133
"toc": "markdown-toc -i README.md",
3234
"prepublishOnly": "npm run build && cp mod.js mod.mjs",
3335
"docs:api": "typedoc --out api-doc --readme none --exclude \"**/__test__/**\" --theme minimal"
3436
},
3537
"author": "egoist <0x142857@gmail.com>",
3638
"license": "MIT",
3739
"devDependencies": {
38-
"@rollup/plugin-commonjs": "^11.1.0",
39-
"@rollup/plugin-node-resolve": "^7.1.3",
40-
"@types/execa": "^0.9.0",
41-
"@types/jest": "^23.3.9",
40+
"@babel/core": "^7.12.10",
41+
"@babel/plugin-syntax-typescript": "^7.12.1",
42+
"@rollup/plugin-commonjs": "^17.0.0",
43+
"@rollup/plugin-node-resolve": "^11.0.0",
44+
"@types/fs-extra": "^9.0.5",
45+
"@types/jest": "^26.0.19",
4246
"@types/mri": "^1.1.0",
4347
"cz-conventional-changelog": "^2.1.0",
48+
"esbuild": "^0.8.21",
4449
"eslint-config-rem": "^3.0.0",
45-
"events": "^3.0.0",
46-
"execa": "^1.0.0",
50+
"execa": "^5.0.0",
51+
"fs-extra": "^9.0.1",
52+
"globby": "^11.0.1",
4753
"husky": "^1.2.0",
4854
"jest": "^24.9.0",
4955
"lint-staged": "^8.1.0",
5056
"markdown-toc": "^1.2.0",
51-
"mri": "^1.1.1",
52-
"prettier": "^1.15.2",
53-
"rollup": "^2.10.0",
54-
"rollup-plugin-dts": "^1.4.3",
55-
"rollup-plugin-esbuild": "^1.4.1",
57+
"mri": "^1.1.6",
58+
"prettier": "^2.2.1",
59+
"rollup": "^2.34.2",
60+
"rollup-plugin-dts": "^2.0.1",
61+
"rollup-plugin-esbuild": "^2.6.1",
5662
"semantic-release": "^17.3.0",
57-
"ts-jest": "^23.10.5",
58-
"ts-node": "^7.0.1",
59-
"typedoc": "^0.17.6",
60-
"typescript": "^3.9.2"
63+
"sucrase": "^3.16.0",
64+
"ts-jest": "^26.4.4",
65+
"ts-node": "^9.1.1",
66+
"typedoc": "^0.19.2",
67+
"typescript": "^4.1.2"
6168
},
6269
"engines": {
6370
"node": ">=8"

rollup.config.js

Lines changed: 16 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,55 +2,43 @@ import nodeResolvePlugin from '@rollup/plugin-node-resolve'
22
import esbuildPlugin from 'rollup-plugin-esbuild'
33
import dtsPlugin from 'rollup-plugin-dts'
44

5-
function createConfig(target, dts) {
6-
const deno = target === 'deno'
7-
let file = deno ? 'mod.js' : 'dist/index.js'
5+
function createConfig({ dts, esm } = {}) {
6+
let file = 'dist/index.js'
87
if (dts) {
98
file = file.replace('.js', '.d.ts')
109
}
10+
if (esm) {
11+
file = file.replace('.js', '.mjs')
12+
}
1113
return {
1214
input: 'src/index.ts',
1315
output: {
14-
format: deno || dts ? 'esm' : 'cjs',
16+
format: dts || esm ? 'esm' : 'cjs',
1517
file,
16-
exports: 'named'
18+
exports: 'named',
1719
},
1820
plugins: [
1921
nodeResolvePlugin({
20-
preferBuiltins: !deno,
2122
mainFields: dts ? ['types', 'typings'] : ['module', 'main'],
2223
extensions: dts ? ['.d.ts', '.ts'] : ['.js', '.json', '.mjs'],
2324
customResolveOptions: {
24-
moduleDirectory: dts
25+
moduleDirectories: dts
2526
? ['node_modules/@types', 'node_modules']
26-
: 'node_modules'
27-
}
27+
: ['node_modules'],
28+
},
2829
}),
29-
!dts &&
30-
require('@rollup/plugin-commonjs')({
31-
namedExports: {
32-
path: ['basename'],
33-
events: ['EventEmitter']
34-
}
35-
}),
30+
!dts && require('@rollup/plugin-commonjs')(),
3631
!dts &&
3732
esbuildPlugin({
38-
target: 'es2017'
33+
target: 'es2017',
3934
}),
4035
dts && dtsPlugin(),
41-
deno && {
42-
renderChunk(code) {
43-
// Remove triple slashes reference since Deno doesn't support that
44-
return code.replace(/^\/\/\/(.+)/, '')
45-
}
46-
}
47-
].filter(Boolean)
36+
].filter(Boolean),
4837
}
4938
}
5039

5140
export default [
52-
createConfig('node'),
53-
createConfig('deno'),
54-
createConfig('node', true),
55-
createConfig('deno', true)
41+
createConfig(),
42+
createConfig({ dts: true }),
43+
createConfig({ esm: true }),
5644
]

scripts/build-deno.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import path from 'path'
2+
import globby from 'globby'
3+
import fs from 'fs-extra'
4+
import { transformAsync, PluginObj, types as Types } from '@babel/core'
5+
import tsSyntax from '@babel/plugin-syntax-typescript'
6+
7+
function node2deno(options: { types: typeof Types }): PluginObj {
8+
const t = options.types
9+
return {
10+
name: 'node2deno',
11+
12+
visitor: {
13+
ImportDeclaration(path) {
14+
const source = path.node.source
15+
if (source.value.startsWith('.')) {
16+
if (source.value.endsWith('/node')) {
17+
source.value = source.value.replace('node', 'deno')
18+
}
19+
source.value += '.ts'
20+
} else if (source.value === 'events') {
21+
source.value = `https://deno.land/std@0.80.0/node/events.ts`
22+
} else if (source.value === 'mri') {
23+
source.value = `https://cdn.skypack.dev/mri`
24+
}
25+
},
26+
27+
IfStatement(path) {
28+
if (path.getSource().includes('@remove-for-deno')) {
29+
path.remove()
30+
}
31+
},
32+
},
33+
}
34+
}
35+
36+
async function main() {
37+
const files = await globby(['**/*.ts', '!**/__test__/**'], { cwd: 'src' })
38+
await Promise.all(
39+
files.map(async (file) => {
40+
if (file === 'node.ts') return
41+
const content = await fs.readFile(path.join('src', file), 'utf8')
42+
const transformed = await transformAsync(content, {
43+
plugins: [tsSyntax, node2deno],
44+
})
45+
await fs.outputFile(path.join('deno', file), transformed.code, 'utf8')
46+
})
47+
)
48+
}
49+
50+
main().catch((error) => {
51+
console.error(error)
52+
process.exitCode = 1
53+
})

src/CAC.ts

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ import Command, {
44
GlobalCommand,
55
CommandConfig,
66
HelpCallback,
7-
CommandExample
7+
CommandExample,
88
} from './Command'
99
import { OptionConfig } from './Option'
1010
import {
1111
getMriOptions,
1212
setDotProp,
1313
setByType,
1414
getFileName,
15-
camelcaseOptionName
15+
camelcaseOptionName,
1616
} from './utils'
1717
import { processArgs } from './node'
1818

@@ -43,8 +43,8 @@ class CAC extends EventEmitter {
4343
*/
4444
options: ParsedArgv['options']
4545

46-
showHelpOnExit: boolean
47-
showVersionOnExit: boolean
46+
showHelpOnExit?: boolean
47+
showVersionOnExit?: boolean
4848

4949
/**
5050
* @param name The program name to display in help and version message
@@ -53,6 +53,9 @@ class CAC extends EventEmitter {
5353
super()
5454
this.name = name
5555
this.commands = []
56+
this.rawArgs = []
57+
this.args = []
58+
this.options = {}
5659
this.globalCommand = new GlobalCommand(this)
5760
this.globalCommand.usage('<command> [options]')
5861
}
@@ -155,7 +158,7 @@ class CAC extends EventEmitter {
155158
}
156159
return this
157160
}
158-
161+
159162
unsetMatchedCommand() {
160163
this.matchedCommand = undefined
161164
this.matchedCommandName = undefined
@@ -168,7 +171,7 @@ class CAC extends EventEmitter {
168171
argv = processArgs,
169172
{
170173
/** Whether to run the action for matched command */
171-
run = true
174+
run = true,
172175
} = {}
173176
): ParsedArgv {
174177
this.rawArgs = argv
@@ -187,7 +190,7 @@ class CAC extends EventEmitter {
187190
shouldParse = false
188191
const parsedInfo = {
189192
...parsed,
190-
args: parsed.args.slice(1)
193+
args: parsed.args.slice(1),
191194
}
192195
this.setParsedInfo(parsedInfo, command, commandName)
193196
this.emit(`command:${commandName}`, command)
@@ -243,7 +246,7 @@ class CAC extends EventEmitter {
243246
// All added options
244247
const cliOptions = [
245248
...this.globalCommand.options,
246-
...(command ? command.options : [])
249+
...(command ? command.options : []),
247250
]
248251
const mriOptions = getMriOptions(cliOptions)
249252

@@ -260,17 +263,16 @@ class CAC extends EventEmitter {
260263
(res, name) => {
261264
return {
262265
...res,
263-
[camelcaseOptionName(name)]: parsed[name]
266+
[camelcaseOptionName(name)]: parsed[name],
264267
}
265268
},
266269
{ _: [] }
267270
)
268271

269272
const args = parsed._
270-
delete parsed._
271273

272274
const options: { [k: string]: any } = {
273-
'--': argsAfterDoubleDashes
275+
'--': argsAfterDoubleDashes,
274276
}
275277

276278
// Set option default value
@@ -300,16 +302,18 @@ class CAC extends EventEmitter {
300302
}
301303
}
302304

303-
// Set dot nested option values
305+
// Set option values (support dot-nested property name)
304306
for (const key of Object.keys(parsed)) {
305-
const keys = key.split('.')
306-
setDotProp(options, keys, parsed[key])
307-
setByType(options, transforms)
307+
if (key !== '_') {
308+
const keys = key.split('.')
309+
setDotProp(options, keys, parsed[key])
310+
setByType(options, transforms)
311+
}
308312
}
309313

310314
return {
311315
args,
312-
options
316+
options,
313317
}
314318
}
315319

0 commit comments

Comments
 (0)