import { Logger } from "../util/logging" ; import { assertDefined , best , intOfString } from "../util/util" ; import { readDataFile } from "./common" ; import { CachedNpmInfoClient } from "./npm-client" ; import { AllPackages , NotNeededPackage , PackageId , TypingsData } from "./packages" ; export const versionsFilename = "versions.json" ; export interface ChangedTyping { readonly pkg : TypingsData ; /** This is the version to be published, meaning it's the version that doesn't exist yet. */ readonly version : string ; /** For a non-latest version, this is the latest version; publishing an old version updates the 'latest' tag and we want to change it back. */ readonly latestVersion : string | undefined ; } export interface ChangedPackagesJson { readonly changedTypings : ReadonlyArray < ChangedTypingJson > ; readonly changedNotNeededPackages : ReadonlyArray < string > ; } export interface ChangedTypingJson { readonly id : PackageId ; readonly version : string ; readonly latestVersion ? : string ; } export interface ChangedPackages { readonly changedTypings : ReadonlyArray < ChangedTyping > ; readonly changedNotNeededPackages : ReadonlyArray < NotNeededPackage > ; } export async function readChangedPackages ( allPackages ) { const json = await readDataFile ( "calculate-versions" , versionsFilename ) as ChangedPackagesJson ; return { changedTypings : json . changedTypings . map ( ( { id , version , latestVersion } ) : ChangedTyping => ( { pkg : allPackages . getTypingsData ( id ) , version , latestVersion } ) ) , changedNotNeededPackages : json . changedNotNeededPackages . map ( id => assertDefined ( allPackages . getNotNeededPackage ( id ) ) ) , } ; } /**
 * When we fail to publish a deprecated package, it leaves behind an entry in the time property.
 * So the keys of 'time' give the actual 'latest'.
 * If that's not equal to the expected latest, try again by bumping the patch version of the last attempt by 1.
 */ export function skipBadPublishes ( pkg , client , log ) { const info = assertDefined ( client . getNpmInfoFromCache ( pkg . fullEscapedNpmName ) ) ; const notNeeded = pkg . version ; const latest = Semver . parse ( findActualLatest ( info . time ) ) ; if ( latest . equals ( notNeeded ) || latest . greaterThan ( notNeeded ) || info . versions . has ( notNeeded . versionString ) && ! assertDefined ( info . versions . get ( notNeeded . versionString ) ) . deprecated ) { const plusOne = new Semver ( latest . major , latest . minor , latest . patch + 1 ) ; log ( `Deprecation of ${ notNeeded . versionString } failed, instead using ${ plusOne . versionString }.` ) ; return new NotNeededPackage ( { asOfVersion : plusOne . versionString , libraryName : pkg . libraryName , sourceRepoURL : pkg . sourceRepoURL , typingsPackageName : pkg . name , } ) ; } return pkg ; } function findActualLatest ( times ) { const actual = best ( times , ( [ k , v ] , [ bestK , bestV ] ) => ( bestK === "modified" || bestK === "created" ) ? true : ( k === "modified" || k === "created" ) ? false : new Date ( v ) . getTime ( ) > new Date ( bestV ) . getTime ( ) ) ; if ( ! actual ) { throw new Error ( "failed to find actual latest" ) ; } return actual [ 0 ] ; } /** Version of a package published to NPM. */ export class Semver { static parse ( semver , coerce ? ) { const result = Semver . tryParse ( semver , coerce ) ; if ( ! result ) { throw new Error ( `Unexpected semver: ${ semver }` ) ; } return result ; } static fromRaw ( { major , minor , patch } ) { return new Semver ( major , minor , patch ) ; } note param semver param coerce static tryParse ( semver , coerce ? ) { const rgx = /^(\d+)(\.(\d+))?(\.(\d+))?$/ ; const match = rgx . exec ( semver ) ; if ( match ) { const { 1 : major , 3 : minor , 5 : patch } = match ; if ( ( minor !== undefined && patch !== undefined ) || coerce ) { return new Semver ( intOfString ( major ) , intOfString ( minor || "0" ) , intOfString ( patch || "0" ) ) ; } } return undefined ; } constructor ( readonly major , readonly minor , readonly patch ) { } get versionString ( ) : string { const { major , minor , patch } = this ; return `${ major }.${ minor }.${ patch }` ; } equals ( other ) { return compare ( this , other ) === 0 ; } greaterThan ( other ) { return compare ( this , other ) === 1 ; } } /**
 * Returns 0 if equal, 1 if x > y, -1 if x < y
 */ export function compare ( x , y ) { const versions = [ [ x . major , y . major ] , [ x . minor , y . minor ] , [ x . patch , y . patch ] ] ; for ( const [ componentX , componentY ] of versions ) { if ( componentX > componentY ) { return 1 ; } if ( componentX < componentY ) { return - 1 ; } } return 0 ; }