Skip to content

Commit

Permalink
feat: add --version to update command
Browse files Browse the repository at this point in the history
  • Loading branch information
mdonnalley committed Feb 1, 2022
1 parent 0f30505 commit c8e1045
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 17 deletions.
25 changes: 19 additions & 6 deletions src/commands/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,31 @@ export default class UpdateCommand extends Command {

static flags = {
autoupdate: Flags.boolean({hidden: true}),
'from-local': Flags.boolean({description: 'interactively choose an already installed version'}),
'from-local': Flags.boolean({description: 'Interactively choose an already installed version.'}),
version: Flags.string({
description: 'Install a specific version.',
exclusive: ['from-local'],
}),
}

private channel!: string

private readonly clientRoot = this.config.scopedEnvVar('OCLIF_CLIENT_HOME') || path.join(this.config.dataDir, 'client')

private readonly clientBin = path.join(this.clientRoot, 'bin', this.config.windows ? `${this.config.bin}.cmd` : this.config.bin)

async run(): Promise<void> {
const {args, flags} = await this.parse(UpdateCommand)
const updateCli = new UpdateCli({channel: args.channel, autoUpdate: flags.autoupdate, fromLocal: flags['from-local'], config: this.config as Config, exit: this.exit, getPinToVersion: getPinToVersion})

if (args.channel && flags.version) {
this.error('You cannot specifiy both a version and a channel.')
}

const updateCli = new UpdateCli({
channel: args.channel,
autoUpdate: flags.autoupdate,
fromLocal: flags['from-local'],
version: flags.version,
config: this.config as Config,
exit: this.exit,
getPinToVersion: getPinToVersion,
})
return updateCli.runUpdate()
}
}
98 changes: 87 additions & 11 deletions src/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@ export interface UpdateCliOptions {
channel?: string;
autoUpdate: boolean;
fromLocal: boolean;
version: string | undefined;
config: Config;
exit: any;
getPinToVersion: () => Promise<string>;
}

export type VersionIndex = Record<string, string>

export default class UpdateCli {
private channel!: string

Expand Down Expand Up @@ -65,12 +68,32 @@ export default class UpdateCli {

CliUx.ux.log()
CliUx.ux.log(`Updating to an already installed version will not update the channel. If autoupdate is enabled, the CLI will eventually be updated back to ${this.channel}.`)
} else if (this.options.version) {
CliUx.ux.action.start(`${this.options.config.name}: Updating CLI`)
await this.options.config.runHook('preupdate', {channel: this.channel})

const index = await this.fetchVersionIndex()
const url = index[this.options.version]
if (!url) {
throw new Error(`${this.options.version} not found in index:\n${Object.keys(index).join(', ')}`)
}

const manifest = await this.fetchVersionManifest(this.options.version, url)
this.currentVersion = await this.determineCurrentVersion()
this.updatedVersion = manifest.sha ? `${manifest.version}-${manifest.sha}` : manifest.version
const reason = await this.skipUpdate()
if (reason) CliUx.ux.action.stop(reason || 'done')
else await this.update(manifest)

CliUx.ux.debug('tidy')
await this.tidy()
await this.options.config.runHook('update', {channel: this.channel})
} else {
CliUx.ux.action.start(`${this.options.config.name}: Updating CLI`)
await this.options.config.runHook('preupdate', {channel: this.channel})
const manifest = await this.fetchManifest()
const manifest = await this.fetchChannelManifest()
this.currentVersion = await this.determineCurrentVersion()
this.updatedVersion = (manifest as any).sha ? `${manifest.version}-${(manifest as any).sha}` : manifest.version
this.updatedVersion = manifest.sha ? `${manifest.version}-${manifest.sha}` : manifest.version
const reason = await this.skipUpdate()
if (reason) CliUx.ux.action.stop(reason || 'done')
else await this.update(manifest)
Expand All @@ -83,21 +106,39 @@ export default class UpdateCli {
CliUx.ux.action.stop()
}

private async fetchManifest(): Promise<IManifest> {
private async fetchChannelManifest(): Promise<IManifest> {
const s3Key = this.s3ChannelManifestKey(
this.options.config.bin,
this.options.config.platform,
this.options.config.arch,
(this.options.config.pjson.oclif.update.s3 as any).folder,
)
return this.fetchManifest(s3Key)
}

private async fetchVersionManifest(version: string, url: string): Promise<IManifest> {
const parts = url.split('/')
const hashIndex = parts.indexOf(version) + 1
const hash = parts[hashIndex]
const s3Key = this.s3VersionManifestKey(
this.options.config.bin,
version,
hash,
this.options.config.platform,
this.options.config.arch,
(this.options.config.pjson.oclif.update.s3 as any).folder,
)
return this.fetchManifest(s3Key)
}

private async fetchManifest(s3Key: string): Promise<IManifest> {
const http: typeof HTTP = require('http-call').HTTP

CliUx.ux.action.status = 'fetching manifest'

try {
const url = this.options.config.s3Url(this.options.config.s3Key('manifest', {
channel: this.channel,
platform: this.options.config.platform,
arch: this.options.config.arch,
}))
const url = this.options.config.s3Url(s3Key)
const {body} = await http.get<IManifest | string>(url)

// in case the content-type is not set, parse as a string
// this will happen if uploading without `oclif-dev publish`
if (typeof body === 'string') {
return JSON.parse(body)
}
Expand All @@ -109,6 +150,28 @@ export default class UpdateCli {
}
}

private async fetchVersionIndex(): Promise<VersionIndex> {
const http: typeof HTTP = require('http-call').HTTP

CliUx.ux.action.status = 'fetching version index'

const newIndexUrl = this.options.config.s3Url(
this.s3VersionIndexKey(
this.options.config.bin,
this.options.config.platform,
this.options.config.arch,
(this.options.config.pjson.oclif.update.s3 as any).folder,
),
)

const {body} = await http.get<VersionIndex>(newIndexUrl)
if (typeof body === 'string') {
return JSON.parse(body)
}

return body
}

private async downloadAndExtract(output: string, manifest: IManifest, channel: string) {
const {version, gz, sha256gz} = manifest

Expand Down Expand Up @@ -224,6 +287,19 @@ export default class UpdateCli {
return path.join(s3SubDir, 'channels', this.channel, `${bin}-${platform}-${arch}-buildmanifest`)
}

// eslint-disable-next-line max-params
private s3VersionManifestKey(bin: string, version: string, hash: string, platform: string, arch: string, folder = ''): string {
let s3SubDir = folder || ''
if (s3SubDir !== '' && s3SubDir.slice(-1) !== '/') s3SubDir = `${s3SubDir}/`
return path.join(s3SubDir, 'versions', version, hash, `${bin}-v${version}-${hash}-${platform}-${arch}-buildmanifest`)
}

private s3VersionIndexKey(bin: string, platform: string, arch: string, folder = ''): string {
let s3SubDir = folder || ''
if (s3SubDir !== '' && s3SubDir.slice(-1) !== '/') s3SubDir = `${s3SubDir}/`
return path.join(s3SubDir, 'versions', `${bin}-${platform}-${arch}-tar-gz.json`)
}

private async setChannel() {
const channelPath = path.join(this.options.config.dataDir, 'channel')
fs.writeFile(channelPath, this.channel, 'utf8')
Expand Down
1 change: 1 addition & 0 deletions test/update.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ function initUpdateCli(options: Partial<UpdateCliOptions>): UpdateCli {
fromLocal: options.fromLocal || false,
autoUpdate: options.autoUpdate || false,
config: options.config!,
version: undefined,
exit: undefined,
getPinToVersion: async () => '2.0.0',
})
Expand Down

0 comments on commit c8e1045

Please sign in to comment.