Sm/index version limits
feat: lets package.json specify indexVersionLimit
MODIFY
src/commands/promote.ts
src/commands/promote.ts
import {Command, flags} from '@oclif/command'
import {cli} from 'cli-ux'
-import * as fs from 'fs-extra'
import * as path from 'path'
import aws from '../aws'
import * as Tarballs from '../tarballs'
import {templateShortKey, commitAWSDir, channelAWSDir, debVersion} from '../upload-util'
-import {sortVersionsObjectByKeysDesc} from '../util'
+import {appendToIndex} from '../version-indexes'
export default class Promote extends Command {
static hidden = true
@@ -37,38 +36,10 @@ export default class Promote extends Command {
const targets = flags.targets.split(',')
const buildConfig = await Tarballs.buildConfig(flags.root, {targets})
const {s3Config, config} = buildConfig
-
- const appendToIndex = async (input: { originalUrl: string; filename: string }) => {
- // these checks are both nice for users AND helpful for TS
- if (!s3Config.bucket) throw new Error('[package.json].oclif.s3.bucket is required for indexes')
- if (!s3Config.host) throw new Error('[package.json].oclif.s3.host is required for indexes')
-
- // json-friendly filenames like sfdx-linux-x64-tar-gz
- const jsonFileName = `${input.filename.replace(/\./g, '-')}.json`
- const key = path.join(s3Config.folder!, 'versions', jsonFileName)
-
- // retrieve existing index file
- let existing = {}
- try {
- existing = JSON.parse((await aws.s3.getObject({
- Bucket: s3Config.bucket,
- Key: key,
- })).Body?.toString() as string)
- this.log('appending to existing index file')
- } catch (error) {
- this.error(`error on ${key}`, error)
- }
- // appends new version from this promotion if not already present (idempotent)
- await fs.writeJSON(jsonFileName, sortVersionsObjectByKeysDesc({...existing, [flags.version]: input.originalUrl.replace(s3Config.bucket, s3Config.host)}), {spaces: 2})
-
- // put the file back in the same place
- await aws.s3.uploadFile(jsonFileName, {
- Bucket: s3Config.bucket,
- Key: key,
- CacheControl: maxAge,
- })
- // cleans up local fs
- await fs.remove(jsonFileName)
+ const indexDefaults = {
+ version: flags.version,
+ s3Config,
+ maxAge,
}
if (!s3Config.bucket) this.error('Cannot determine S3 bucket for promotion')
@@ -123,7 +94,7 @@ export default class Promote extends Command {
)
// eslint-disable-next-line no-await-in-loop
- if (flags.indexes) await appendToIndex({originalUrl: versionedTarGzKey, filename: unversionedTarGzName})
+ if (flags.indexes) await appendToIndex({...indexDefaults, originalUrl: versionedTarGzKey, filename: unversionedTarGzName})
if (flags.xz) {
const versionedTarXzName = templateShortKey('versioned', '.tar.xz', {
@@ -148,7 +119,7 @@ export default class Promote extends Command {
},
)
// eslint-disable-next-line no-await-in-loop
- if (flags.indexes) await appendToIndex({originalUrl: versionedTarXzKey, filename: unversionedTarXzName})
+ if (flags.indexes) await appendToIndex({...indexDefaults, originalUrl: versionedTarXzKey, filename: unversionedTarXzName})
}
}
@@ -169,7 +140,7 @@ export default class Promote extends Command {
MetadataDirective: 'REPLACE',
},
)
- if (flags.indexes) await appendToIndex({originalUrl: darwinCopySource, filename: unversionedPkg})
+ if (flags.indexes) await appendToIndex({...indexDefaults, originalUrl: darwinCopySource, filename: unversionedPkg})
}
// copy win exe
@@ -192,7 +163,7 @@ export default class Promote extends Command {
},
)
// eslint-disable-next-line no-await-in-loop
- if (flags.indexes) await appendToIndex({originalUrl: winCopySource, filename: unversionedExe})
+ if (flags.indexes) await appendToIndex({...indexDefaults, originalUrl: winCopySource, filename: unversionedExe})
cli.action.stop('successfully')
}
}

MODIFY
src/tarballs/config.ts
src/tarballs/config.ts
@@ -22,7 +22,7 @@ export interface BuildConfig {
version: string;
tmp: string;
updateConfig: BuildConfig['config']['pjson']['oclif']['update'];
- s3Config: BuildConfig['updateConfig']['s3'] & {folder?: string};
+ s3Config: BuildConfig['updateConfig']['s3'] & { folder?: string; indexVersionLimit?: number};
xz: boolean;
targets: {platform: Config.PlatformTypes; arch: Config.ArchTypes}[];
workspace(target?: {platform: Config.PlatformTypes; arch: Config.ArchTypes}): string;

ADD
src/version-indexes.ts
+import * as fs from 'fs-extra'
+import * as path from 'path'
+import aws from './aws'
+import {BuildConfig} from './tarballs'
+import {debug as Debug} from './log'
+
+const debug = Debug.new('version-indexes')
+
+interface VersionsObject {
+ [key: string]: string;
+}
+
+const sortVersionsObjectByKeysDesc = (input: VersionsObject, keyLimit?: number): VersionsObject => {
+ const keys = (Reflect.ownKeys(input).sort((a, b) => {
+ const splitA = (a as string).split('.').map(part => parseInt(part, 10))
+ const splitB = (b as string).split('.').map(part => parseInt(part, 10))
+ // sort by major
+ if (splitA[0] < splitB[0]) return 1
+ if (splitA[0] > splitB[0]) return -1
+ // sort by minor
+ if (splitA[1] < splitB[1]) return 1
+ if (splitA[1] > splitB[1]) return -1
+ // sort by patch
+ if (splitA[2] < splitB[2]) return 1
+ if (splitA[2] > splitB[2]) return -1
+ return 0
+ }) as string[]).slice(0, keyLimit) // undefined keyLimit returns the entire array
+ const result: VersionsObject = {}
+ keys.forEach(key => {
+ result[key] = input[key]
+ })
+ return result
+}
+
+// appends to an existing file (or writes a new one) with the versions in descending order, with an optional limit from the pjson file
+export const appendToIndex = async (input: { version: string; originalUrl: string; filename: string; maxAge: string; s3Config: BuildConfig['s3Config'] }) => {
+ const {version, originalUrl, filename, maxAge, s3Config} = input
+ // these checks are both nice for users AND helpful for TS
+ if (!s3Config.bucket) throw new Error('[package.json].oclif.s3.bucket is required for indexes')
+ if (!s3Config.host) throw new Error('[package.json].oclif.s3.host is required for indexes')
+
+ // json-friendly filenames like sfdx-linux-x64-tar-gz
+ const jsonFileName = `${filename.replace(/\./g, '-')}.json`
+ const key = path.join(s3Config.folder!, 'versions', jsonFileName)
+
+ // retrieve existing index file
+ let existing = {}
+ try {
+ existing = JSON.parse((await aws.s3.getObject({
+ Bucket: s3Config.bucket,
+ Key: key,
+ })).Body?.toString() as string)
+ debug('appending to existing index file')
+ } catch (error) {
+ debug(`error on ${key}`, error)
+ }
+ // appends new version from this promotion if not already present (idempotent)
+ await fs.writeJSON(jsonFileName, sortVersionsObjectByKeysDesc(
+ {
+ ...existing,
+ [version]: originalUrl.replace(s3Config.bucket, s3Config.host),
+ }, s3Config.indexVersionLimit,
+ ), {spaces: 2})
+
+ // put the file back in the same place
+ await aws.s3.uploadFile(jsonFileName, {
+ Bucket: s3Config.bucket,
+ Key: key,
+ CacheControl: maxAge,
+ })
+ // cleans up local fs
+ await fs.remove(jsonFileName)
+}
+

Sm/index version limits (#593)

feat: lets package.json specify indexVersionLimit