-
Notifications
You must be signed in to change notification settings - Fork 309
/
init.ts
186 lines (158 loc) · 6.02 KB
/
init.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
import {Errors, Flags} from '@oclif/core'
import chalk from 'chalk'
import {readdir, writeFile} from 'node:fs/promises'
import {join, resolve, sep} from 'node:path'
import {FlaggablePrompt, GeneratorCommand, exec, makeFlags, readPJSON} from '../generator'
import {validateBin} from '../util'
const VALID_MODULE_TYPES = ['ESM', 'CommonJS'] as const
type ModuleType = (typeof VALID_MODULE_TYPES)[number]
const VALID_PACKAGE_MANAGERS = ['npm', 'yarn', 'pnpm'] as const
type PackageManager = (typeof VALID_PACKAGE_MANAGERS)[number]
function isPackageManager(d: string): d is PackageManager {
return VALID_PACKAGE_MANAGERS.includes(d as PackageManager)
}
function isModuleType(d: string): d is ModuleType {
return VALID_MODULE_TYPES.includes(d as ModuleType)
}
const FLAGGABLE_PROMPTS = {
bin: {
message: 'Command bin name the CLI will export',
validate: (d) => validateBin(d) || 'Invalid bin name',
},
'module-type': {
message: 'Select a module type',
options: VALID_MODULE_TYPES,
validate: (d) => isModuleType(d) || 'Invalid module type',
},
'package-manager': {
message: 'Select a package manager',
options: VALID_PACKAGE_MANAGERS,
validate: (d) => isPackageManager(d) || 'Invalid package manager',
},
'topic-separator': {
message: 'Select a topic separator',
options: ['colons', 'spaces'],
validate: (d) => d === 'colons' || d === 'spaces' || 'Invalid topic separator',
},
} satisfies Record<string, FlaggablePrompt>
export default class Generate extends GeneratorCommand<typeof Generate> {
static description =
'This will add the necessary oclif bin files, add oclif config to package.json, and install @oclif/core and ts-node.'
static examples = [
{
command: '<%= config.bin %> <%= command.id %>',
description: 'Initialize a new CLI in the current directory',
},
{
command: '<%= config.bin %> <%= command.id %> --output-dir "/path/to/existing/project"',
description: 'Initialize a new CLI in a different directory',
},
{
command: '<%= config.bin %> <%= command.id %> --topic-separator colons --bin mycli',
description: 'Supply answers for specific prompts',
},
]
static flaggablePrompts = FLAGGABLE_PROMPTS
static flags = {
...makeFlags(FLAGGABLE_PROMPTS),
'output-dir': Flags.directory({
char: 'd',
description: 'Directory to initialize the CLI in.',
exists: true,
}),
yes: Flags.boolean({
aliases: ['defaults'],
char: 'y',
description: 'Use defaults for all prompts. Individual flags will override defaults.',
}),
}
static summary = 'Initialize a new oclif CLI'
async run(): Promise<void> {
const outputDir = this.flags['output-dir'] ?? process.cwd()
const location = resolve(outputDir)
this.log(`Initializing oclif in ${chalk.green(location)}`)
const packageJSON = (await readPJSON(location))!
if (!packageJSON) {
throw new Errors.CLIError(`Could not find a package.json file in ${location}`)
}
const bin = await this.getFlagOrPrompt({
defaultValue: location.split(sep).at(-1) || '',
name: 'bin',
type: 'input',
})
const topicSeparator = await this.getFlagOrPrompt({
defaultValue: 'spaces',
name: 'topic-separator',
type: 'select',
})
const moduleType = await this.getFlagOrPrompt({
defaultValue: packageJSON.type === 'module' ? 'ESM' : 'CommonJS',
async maybeOtherValue() {
return packageJSON.type === 'module' ? 'ESM' : packageJSON.type === 'commonjs' ? 'CommonJS' : undefined
},
name: 'module-type',
type: 'select',
})
const packageManager = await this.getFlagOrPrompt({
defaultValue: 'npm',
async maybeOtherValue() {
const rootFiles = await readdir(location)
if (rootFiles.includes('package-lock.json')) {
return 'npm'
}
if (rootFiles.includes('yarn.lock')) {
return 'yarn'
}
if (rootFiles.includes('pnpm-lock.yaml')) {
return 'pnpm'
}
},
name: 'package-manager',
type: 'select',
})
this.log(`Using module type ${chalk.green(moduleType)}`)
this.log(`Using package manager ${chalk.green(packageManager)}`)
const projectBinPath = join(location, 'bin')
const templateBinPath = join(this.templatesDir, 'cli', moduleType.toLowerCase(), 'bin')
await this.template(join(templateBinPath, 'dev.cmd.ejs'), join(projectBinPath, 'dev.cmd'))
await this.template(join(templateBinPath, 'dev.js.ejs'), join(projectBinPath, 'dev.js'))
await this.template(join(templateBinPath, 'run.cmd.ejs'), join(projectBinPath, 'run.cmd'))
await this.template(join(templateBinPath, 'run.js.ejs'), join(projectBinPath, 'run.js'))
if (process.platform !== 'win32') {
await exec(`chmod +x "${join(projectBinPath, 'run.js')}"`)
await exec(`chmod +x "${join(projectBinPath, 'dev.js')}"`)
}
const updatedPackageJSON = {
...packageJSON,
bin: {
...packageJSON.bin,
[bin]: './bin/run.js',
},
oclif: {
bin,
commands: './dist/commands',
dirname: bin,
topicSeparator: topicSeparator === 'colons' ? ':' : ' ',
...packageJSON.oclif,
},
}
await writeFile(join(location, 'package.json'), JSON.stringify(updatedPackageJSON, null, 2))
const installedDeps = Object.keys(packageJSON.dependencies ?? {})
if (!installedDeps.includes('@oclif/core')) {
this.log('Installing @oclif/core')
await exec(`${packageManager} ${packageManager === 'yarn' ? 'add' : 'install'} @oclif/core`, {
cwd: location,
silent: false,
})
}
const allInstalledDeps = [...installedDeps, ...Object.keys(packageJSON.devDependencies ?? {})]
if (!allInstalledDeps.includes('ts-node')) {
this.log('Installing ts-node')
await exec(`${packageManager} ${packageManager === 'yarn' ? 'add --dev' : 'install --save-dev'} ts-node`, {
cwd: location,
silent: false,
})
}
this.log(`\nCreated CLI ${chalk.green(bin)}`)
}
}