diff --git a/src/commands/update.ts b/src/commands/update.ts index f67a9b37..23a982ef 100644 --- a/src/commands/update.ts +++ b/src/commands/update.ts @@ -1,5 +1,6 @@ import select from '@inquirer/select' -import {Args, Command, Flags, ux} from '@oclif/core' +import {Args, Command, Flags, Interfaces, ux} from '@oclif/core' +import {got} from 'got' import {basename} from 'node:path' import {sort} from 'semver' import TtyTable from 'tty-table' @@ -37,9 +38,11 @@ export default class UpdateCommand extends Command { available: Flags.boolean({ char: 'a', description: 'See available versions.', + exclusive: ['version', 'interactive'], }), force: Flags.boolean({ description: 'Force a re-download of the requested version.', + exclusive: ['interactive', 'available'], }), interactive: Flags.boolean({ char: 'i', @@ -49,7 +52,7 @@ export default class UpdateCommand extends Command { version: Flags.string({ char: 'v', description: 'Install a specific version.', - exclusive: ['interactive'], + exclusive: ['interactive', 'force'], }), } @@ -57,18 +60,28 @@ export default class UpdateCommand extends Command { const {args, flags} = await this.parse(UpdateCommand) const updater = new Updater(this.config) if (flags.available) { - const [index, localVersions] = await Promise.all([updater.fetchVersionIndex(), updater.findLocalVersions()]) + const {distTags, index, localVersions} = await lookupVersions(updater, this.config) + + const headers = [ + {align: 'left', value: 'Location'}, + {align: 'left', value: 'Version'}, + ] + + if (distTags) { + headers.push({align: 'left', value: 'Channel'}) + } // eslint-disable-next-line new-cap const t = TtyTable( - [ - {align: 'left', value: 'Location'}, - {align: 'left', value: 'Version'}, - ], + headers, sort(Object.keys(index)) .reverse() .map((version) => { const location = localVersions.find((l) => basename(l).startsWith(version)) || index[version] + if (distTags) { + return [location, version, distTags[version] ?? ''] + } + return [location, version] }), {compact: true}, @@ -86,16 +99,52 @@ export default class UpdateCommand extends Command { autoUpdate: flags.autoupdate, channel: args.channel, force: flags.force, - version: flags.interactive ? await promptForVersion(updater) : flags.version, + version: flags.interactive ? await promptForVersion(updater, this.config) : flags.version, }) } } -const promptForVersion = async (updater: Updater): Promise => - select({ - choices: sort(Object.keys(await updater.fetchVersionIndex())) +const lookupVersions = async (updater: Updater, config: Interfaces.Config) => { + ux.action.start('Looking up versions') + const [index, localVersions, distTags] = await Promise.all([ + updater.fetchVersionIndex(), + updater.findLocalVersions(), + fetchDistTags(config), + ]) + + ux.action.stop(`Found ${Object.keys(index).length} versions`) + return { + distTags, + index, + localVersions, + } +} + +const fetchDistTags = async (config: Interfaces.Config) => { + const distTags = config.pjson.oclif.update?.disableNpmLookup + ? {} + : await got + .get(`${config.npmRegistry ?? 'https://registry.npmjs.org'}/${config.pjson.name}`) + .json<{ + 'dist-tags': Record + }>() + .then((r) => r['dist-tags']) + + // Invert the distTags object so we can look up the channel by version + return Object.fromEntries(Object.entries(distTags ?? {}).map(([k, v]) => [v, k])) +} + +const displayName = (value: string, distTags: Record) => + `${value} ${distTags[value] ? `(${distTags[value]})` : ''}` + +const promptForVersion = async (updater: Updater, config: Interfaces.Config): Promise => { + const {distTags, index} = await lookupVersions(updater, config) + return select({ + choices: sort(Object.keys(index)) .reverse() - .map((v) => ({value: v})), + .map((v) => ({name: displayName(v, distTags), value: v})), loop: false, message: 'Select a version to update to', + pageSize: 10, }) +} diff --git a/src/update.ts b/src/update.ts index 1faff979..4343e16f 100644 --- a/src/update.ts +++ b/src/update.ts @@ -36,7 +36,6 @@ export class Updater { } public async fetchVersionIndex(): Promise { - ux.action.status = 'fetching version index' const newIndexUrl = this.config.s3Url(s3VersionIndexKey(this.config)) try { const {body} = await got.get(newIndexUrl) @@ -83,6 +82,7 @@ export class Updater { if (localVersion) { await this.updateToExistingVersion(current, localVersion) } else { + ux.action.status = 'fetching version index' const index = await this.fetchVersionIndex() const url = index[version] if (!url) {