Skip to content

Commit d16b162

Browse files
refactor: migrate autocomplete commands esm (#3433)
* initial commit for migration of autocomplete commands to esm * removal of autocomplete from old commands and fix of helpers * update of doctor and related tests * converting to modern patterns of node:url and non blocking async patterns used in repo * resolving linting errors and fixing esm imports * fixing linting errors * removing linting errors * converting to use normalizePathForShell for handling paths for windows as in the rest of the project * updating path getters for a mixed format * removing comments and ignore errors * removing ts-expect-error as unused * removing unused, newly added path normalization * converting ambient module declaration to explicit exports as with other types * updating to use error_ to match the repo standards * converting and reverting to catch error usage
1 parent 9117245 commit d16b162

File tree

15 files changed

+398
-380
lines changed

15 files changed

+398
-380
lines changed

packages/cli/src/oldCommands/autocomplete/create.ts renamed to packages/cli/src/commands/autocomplete/create.ts

Lines changed: 36 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
/*
21
import {Command} from '@oclif/core'
3-
import * as fs from 'fs-extra'
2+
import fs from 'fs-extra'
43
import * as path from 'path'
4+
import {fileURLToPath} from 'node:url'
5+
import debug from 'debug'
56

6-
import {AutocompleteBase} from '../../lib/autocomplete/base'
7+
import {AutocompleteBase} from '../../lib/autocomplete/base.js'
78

8-
const debug = require('debug')('autocomplete:create')
9+
const __filename = fileURLToPath(import.meta.url)
10+
const __dirname = path.dirname(__filename)
11+
12+
const debugLog = debug('autocomplete:create')
913

1014
const AC_LIB_PATH = path.resolve(__dirname, '..', '..', '..', 'autocomplete-scripts')
1115

@@ -65,7 +69,7 @@ export default class Create extends AutocompleteBase {
6569
private get commands(): Command.Loadable[] {
6670
if (this._commands) return this._commands
6771

68-
const plugins = this.config.plugins
72+
const {plugins} = this.config
6973
const commands: Command.Loadable[] = []
7074

7175
plugins.forEach(p => {
@@ -74,9 +78,11 @@ export default class Create extends AutocompleteBase {
7478
try {
7579
commands.push(c)
7680
} catch (error: any) {
77-
debug(`Error creating completions for command ${c.id}`)
78-
debug(error.message)
79-
this.writeLogFile(error.message)
81+
debugLog(`Error creating completions for command ${c.id}`)
82+
debugLog(error.message)
83+
this.writeLogFile(error.message).catch(error => {
84+
debugLog(`Failed to write log file: ${error.message}`)
85+
})
8086
}
8187
})
8288
})
@@ -91,9 +97,11 @@ export default class Create extends AutocompleteBase {
9197
const publicFlags = this.genCmdPublicFlags(c).trim()
9298
return `${c.id} ${publicFlags}`
9399
} catch (error: any) {
94-
debug(`Error creating bash completion for command ${c.id}, moving on...`)
95-
debug(error.message)
96-
this.writeLogFile(error.message)
100+
debugLog(`Error creating bash completion for command ${c.id}, moving on...`)
101+
debugLog(error.message)
102+
this.writeLogFile(error.message).catch(error => {
103+
debugLog(`Failed to write log file: ${error.message}`)
104+
})
97105
return ''
98106
}
99107
}).join('\n')
@@ -110,9 +118,11 @@ export default class Create extends AutocompleteBase {
110118
try {
111119
return this.genCmdWithDescription(c)
112120
} catch (error: any) {
113-
debug(`Error creating zsh autocomplete for command ${c.id}, moving on...`)
114-
debug(error.message)
115-
this.writeLogFile(error.message)
121+
debugLog(`Error creating zsh autocomplete for command ${c.id}, moving on...`)
122+
debugLog(error.message)
123+
this.writeLogFile(error.message).catch(error => {
124+
debugLog(`Failed to write log file: ${error.message}`)
125+
})
116126
return ''
117127
}
118128
})
@@ -125,9 +135,11 @@ export default class Create extends AutocompleteBase {
125135
try {
126136
return this.genZshCmdFlagsSetter(c)
127137
} catch (error: any) {
128-
debug(`Error creating zsh autocomplete for command ${c.id}, moving on...`)
129-
debug(error.message)
130-
this.writeLogFile(error.message)
138+
debugLog(`Error creating zsh autocomplete for command ${c.id}, moving on...`)
139+
debugLog(error.message)
140+
this.writeLogFile(error.message).catch(error => {
141+
debugLog(`Failed to write log file: ${error.message}`)
142+
})
131143
return ''
132144
}
133145
}).join('\n')
@@ -152,11 +164,11 @@ export default class Create extends AutocompleteBase {
152164
}
153165

154166
private genZshCmdFlagsSetter(command: Command.Loadable): string {
155-
const id = command.id
156-
const flagscompletions = Object.keys(command.flags || {})
157-
.filter(flag => command.flags && !command.flags[flag].hidden)
167+
const {id, flags: commandFlags = {}} = command
168+
const flagscompletions = Object.keys(commandFlags)
169+
.filter(flag => !commandFlags[flag].hidden)
158170
.map(flag => {
159-
const f = (command.flags && command.flags[flag]) || {description: ''}
171+
const f = commandFlags[flag] || {description: ''}
160172
const isBoolean = f.type === 'boolean'
161173
const hasCompletion = 'completion' in f || this.findCompletion(id, flag, f.description)
162174
const name = isBoolean ? flag : `${flag}=-`
@@ -198,10 +210,7 @@ ${cmdsWithDesc.join('\n')}
198210
}
199211

200212
private get envAnalyticsDir(): string {
201-
return `HEROKU_AC_ANALYTICS_DIR=${path.join(
202-
this.autocompleteCacheDir,
203-
'completion_analytics',
204-
)};`
213+
return `HEROKU_AC_ANALYTICS_DIR=${path.join(this.autocompleteCacheDir, 'completion_analytics')};`
205214
}
206215

207216
private get envCommandsPath(): string {
@@ -211,11 +220,7 @@ ${cmdsWithDesc.join('\n')}
211220
private get bashSetupScript(): string {
212221
return `${this.envAnalyticsDir}
213222
${this.envCommandsPath}
214-
HEROKU_AC_BASH_COMPFUNC_PATH=${path.join(
215-
AC_LIB_PATH,
216-
'bash',
217-
'heroku.bash',
218-
)} && test -f $HEROKU_AC_BASH_COMPFUNC_PATH && source $HEROKU_AC_BASH_COMPFUNC_PATH;
223+
HEROKU_AC_BASH_COMPFUNC_PATH=${AC_LIB_PATH}/bash/heroku.bash && test -f $HEROKU_AC_BASH_COMPFUNC_PATH && source $HEROKU_AC_BASH_COMPFUNC_PATH;
219224
`
220225
}
221226

@@ -225,7 +230,7 @@ ${this.envAnalyticsDir}
225230
${this.envCommandsPath}
226231
HEROKU_AC_ZSH_SETTERS_PATH=\${HEROKU_AC_COMMANDS_PATH}_setters && test -f $HEROKU_AC_ZSH_SETTERS_PATH && source $HEROKU_AC_ZSH_SETTERS_PATH;
227232
fpath=(
228-
${path.join(AC_LIB_PATH, 'zsh')}
233+
${AC_LIB_PATH}/zsh
229234
$fpath
230235
);
231236
autoload -Uz compinit;
@@ -250,4 +255,3 @@ bindkey "^I" expand-or-complete-with-dots`
250255
].includes(flag)
251256
}
252257
}
253-
*/

packages/cli/src/oldCommands/autocomplete/doctor.ts renamed to packages/cli/src/commands/autocomplete/doctor.ts

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
/*
21
import {flags} from '@heroku-cli/command'
3-
import {Args} from '@oclif/core'
2+
import {Args, Interfaces} from '@oclif/core'
43
import {hux} from '@heroku/heroku-cli-util'
5-
import {FlagInput} from '@oclif/core/lib/interfaces/parser'
6-
import * as fs from 'fs-extra'
4+
import fs from 'fs-extra'
75
import * as path from 'path'
6+
import {fileURLToPath} from 'node:url'
87

9-
import {AutocompleteBase} from '../../lib/autocomplete/base'
8+
import {AutocompleteBase} from '../../lib/autocomplete/base.js'
9+
10+
const __filename = fileURLToPath(import.meta.url)
11+
const __dirname = path.dirname(__filename)
1012

1113
export default class Doctor extends AutocompleteBase {
1214
static hidden = true
@@ -17,7 +19,7 @@ export default class Doctor extends AutocompleteBase {
1719
shell: Args.string({description: 'shell type', required: false}),
1820
}
1921

20-
static flags: FlagInput = {
22+
static flags: Interfaces.FlagInput = {
2123
verbose: flags.boolean({description: 'list completable commands'}),
2224
}
2325

@@ -33,32 +35,41 @@ export default class Doctor extends AutocompleteBase {
3335
data.push({name: 'cli version', value: this.config.version})
3436

3537
// plugin version
36-
const pjson = require(path.resolve(__dirname, '..', '..', '..', 'package.json'))
38+
const pjsonPath = path.resolve(__dirname, '..', '..', '..', 'package.json')
39+
const pjson = await fs.readJSON(pjsonPath)
3740
data.push({name: 'plugin version', value: pjson.version})
3841

3942
// check shell shim source env var
4043
// i.e. HEROKU_AC_<shell>_SETUP_PATH
4144
const shellProfilePath = path.join(process.env.HOME || '', shell === 'zsh' ? '.zshrc' : '.bashrc')
42-
const shellProfile = fs.readFileSync(shellProfilePath)
43-
const regex = /AC_\w+_SETUP_PATH/
44-
const shimValue = regex.exec(shellProfile.toString()) ? 'present' : 'missing'
45+
let shimValue = 'missing'
46+
try {
47+
const shellProfile = await fs.readFile(shellProfilePath)
48+
const regex = /AC_\w+_SETUP_PATH/
49+
shimValue = regex.exec(shellProfile.toString()) ? 'present' : 'missing'
50+
} catch {
51+
// File doesn't exist or can't be read
52+
shimValue = 'missing'
53+
}
54+
4555
data.push({name: `~/${shell === 'zsh' ? '.zshrc' : '.bashrc'} shimmed`, value: shimValue})
4656

4757
// check shell shim
4858
const shellCompletion = path.join(this.autocompleteCacheDir, `${shell}_setup`)
49-
const shellCompletionValue = fs.existsSync(shellCompletion) ? 'present' : 'missing'
59+
const shellCompletionValue = await fs.pathExists(shellCompletion) ? 'present' : 'missing'
5060
data.push({name: `${shell} shim file`, value: shellCompletionValue})
5161

5262
// check shell command cache
5363
const shellCmdCache = path.join(this.autocompleteCacheDir, shell === 'zsh' ? 'commands_setters' : 'commands')
54-
const shellCmdCacheValue = fs.existsSync(shellCmdCache) ? 'present' : 'missing'
64+
const shellCmdCacheValue = await fs.pathExists(shellCmdCache) ? 'present' : 'missing'
5565
data.push({name: `${shell} commands cache`, value: shellCmdCacheValue})
5666

5767
// check app completion cache
5868
const appsCache = path.join(this.completionsCacheDir, 'app')
5969
let appsCacheValue
60-
if (fs.existsSync(appsCache)) {
61-
const length = fs.readJSONSync(appsCache).length
70+
if (await fs.pathExists(appsCache)) {
71+
const cacheData = await fs.readJSON(appsCache)
72+
const {length} = cacheData
6273
appsCacheValue = length || 'empty'
6374
} else {
6475
appsCacheValue = 'missing'
@@ -69,7 +80,11 @@ export default class Doctor extends AutocompleteBase {
6980
hux.table(data, {
7081
name: {},
7182
value: {},
72-
}, {'no-header': true, printLine})
83+
}, {
84+
// @ts-expect-error - no-header option exists but may not be in type definition
85+
'no-header': true,
86+
printLine,
87+
})
7388

7489
if (flags.verbose) this.printList()
7590
}
@@ -85,7 +100,7 @@ export default class Doctor extends AutocompleteBase {
85100
if (c.hidden) {
86101
this.log(`${c.id} (hidden)`)
87102
} else {
88-
const results = Object.keys(c.flags).map((f: string) => {
103+
const results = Object.keys(c.flags || {}).map((f: string) => {
89104
let out = `--${f}`
90105
const flag = c.flags[f]
91106
if (flag.type === 'option') out += '='
@@ -106,4 +121,3 @@ export default class Doctor extends AutocompleteBase {
106121
})
107122
}
108123
}
109-
*/
Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
1-
/*
21
import {flags} from '@heroku-cli/command'
3-
import {AppCompletion, PipelineCompletion, SpaceCompletion, TeamCompletion} from '@heroku-cli/command/lib/completions'
4-
import chalk from 'chalk'
5-
import {Args, ux} from '@oclif/core'
6-
import {FlagInput} from '@oclif/core/lib/interfaces/parser'
2+
import {AppCompletion, PipelineCompletion, SpaceCompletion, TeamCompletion} from '@heroku-cli/command/lib/completions.js'
3+
import {color} from '@heroku-cli/color'
4+
import {Args, Interfaces, ux} from '@oclif/core'
75

86
import * as path from 'path'
97

10-
import {AutocompleteBase} from '../../lib/autocomplete/base'
11-
import {updateCache} from '../../lib/autocomplete/cache'
8+
import {AutocompleteBase} from '../../lib/autocomplete/base.js'
9+
import {updateCache} from '../../lib/autocomplete/cache.js'
1210

13-
import Create from './create'
11+
import Create from './create.js'
1412

1513
export default class Index extends AutocompleteBase {
1614
static description = 'display autocomplete installation instructions'
@@ -19,7 +17,7 @@ export default class Index extends AutocompleteBase {
1917
shell: Args.string({description: 'shell type', required: false}),
2018
}
2119

22-
static flags: FlagInput = {
20+
static flags: Interfaces.FlagInput = {
2321
'refresh-cache': flags.boolean({description: 'refresh cache only (ignores displaying instructions)', char: 'r'}),
2422
}
2523

@@ -35,33 +33,34 @@ export default class Index extends AutocompleteBase {
3533
const shell = args.shell || this.config.shell
3634
this.errorIfNotSupportedShell(shell)
3735

38-
ux.action.start(`${chalk.bold('Building the autocomplete cache')}`)
39-
await Create.run([], this.config)
36+
ux.action.start(`${color.bold('Building the autocomplete cache')}`)
37+
const create = new Create([], this.config)
38+
await create.run()
4039
await this.updateCache(AppCompletion, 'app')
4140
await this.updateCache(PipelineCompletion, 'pipeline')
4241
await this.updateCache(SpaceCompletion, 'space')
4342
await this.updateCache(TeamCompletion, 'team')
4443
ux.action.stop()
4544

4645
if (!flags['refresh-cache']) {
47-
const bin = this.config.bin
46+
const {bin} = this.config
4847
const bashNote = 'If your terminal starts as a login shell you may need to print the init script into ~/.bash_profile or ~/.profile.'
49-
const zshNote = `After sourcing, you can run \`${chalk.cyan('$ compaudit -D')}\` to ensure no permissions conflicts are present`
48+
const zshNote = `After sourcing, you can run \`${color.cyan('$ compaudit -D')}\` to ensure no permissions conflicts are present`
5049
const note = shell === 'zsh' ? zshNote : bashNote
5150
const tabStr = shell === 'bash' ? '<TAB><TAB>' : '<TAB>'
5251

5352
this.log(`
54-
${chalk.bold(`Setup Instructions for ${bin.toUpperCase()} CLI Autocomplete ---`)}
53+
${color.bold(`Setup Instructions for ${bin.toUpperCase()} CLI Autocomplete ---`)}
5554
5655
1) Add the autocomplete env var to your ${shell} profile and source it
57-
${chalk.cyan(`$ printf "$(${bin} autocomplete:script ${shell})" >> ~/.${shell}rc; source ~/.${shell}rc`)}
56+
${color.cyan(`$ printf "$(${bin} autocomplete:script ${shell})" >> ~/.${shell}rc; source ~/.${shell}rc`)}
5857
5958
NOTE: ${note}
6059
6160
2) Test it out, e.g.:
62-
${chalk.cyan(`$ ${bin} ${tabStr}`)} # Command completion
63-
${chalk.cyan(`$ ${bin} apps:info --${tabStr}`)} # Flag completion
64-
${chalk.cyan(`$ ${bin} apps:info --app=${tabStr}`)} # Flag option completion
61+
${color.cyan(`$ ${bin} ${tabStr}`)} # Command completion
62+
${color.cyan(`$ ${bin} apps:info --${tabStr}`)} # Flag completion
63+
${color.cyan(`$ ${bin} apps:info --app=${tabStr}`)} # Flag option completion
6564
6665
Visit the autocomplete Dev Center doc at https://devcenter.heroku.com/articles/heroku-cli-autocomplete
6766
@@ -76,4 +75,4 @@ Enjoy!
7675
await updateCache(cachePath, options)
7776
}
7877
}
79-
*/
78+

packages/cli/src/oldCommands/autocomplete/options.ts renamed to packages/cli/src/commands/autocomplete/options.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
/*
21
import {flags} from '@heroku-cli/command'
32
import {Args, Command} from '@oclif/core'
43
import * as path from 'path'
54

6-
import {AutocompleteBase} from '../../lib/autocomplete/base'
7-
import {fetchCache} from '../../lib/autocomplete/cache'
5+
import {AutocompleteBase} from '../../lib/autocomplete/base.js'
6+
import {fetchCache} from '../../lib/autocomplete/cache.js'
87

98
export default class Options extends AutocompleteBase {
109
static hidden = true
@@ -43,7 +42,7 @@ export default class Options extends AutocompleteBase {
4342
if (options) this.log(options)
4443
} catch (error: any) {
4544
// write to ac log
46-
this.writeLogFile(error.message)
45+
await this.writeLogFile(error.message)
4746
}
4847
}
4948

@@ -236,4 +235,3 @@ export default class Options extends AutocompleteBase {
236235
return [argsIndex, argIsFlag, argIsFlagValue]
237236
}
238237
}
239-
*/

packages/cli/src/oldCommands/autocomplete/script.ts renamed to packages/cli/src/commands/autocomplete/script.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
/*
21
import {Args} from '@oclif/core'
32
import * as path from 'path'
43

5-
import {AutocompleteBase} from '../../lib/autocomplete/base'
4+
import {AutocompleteBase} from '../../lib/autocomplete/base.js'
65

76
export default class Script extends AutocompleteBase {
87
static description = 'display autocomplete setup script for shell'
@@ -31,4 +30,4 @@ export default class Script extends AutocompleteBase {
3130
return `\n# ${this.config.bin} autocomplete setup\n`
3231
}
3332
}
34-
*/
33+

0 commit comments

Comments
 (0)