1- import * as fetch from "node-fetch" ;
21import { pathExists , mkdtemp , createWriteStream , remove } from "fs-extra" ;
32import { tmpdir } from "os" ;
43import { delimiter , dirname , join } from "path" ;
54import * as semver from "semver" ;
6- import { URL } from "url" ;
75import { ExtensionContext , Event } from "vscode" ;
86import { DistributionConfig } from "../config" ;
97import { extLogger } from "../common/logging/vscode" ;
@@ -27,6 +25,8 @@ import {
2725} from "../common/logging" ;
2826import { unzipToDirectoryConcurrently } from "../common/unzip-concurrently" ;
2927import { reportUnzipProgress } from "../common/vscode/unzip-progress" ;
28+ import { Release } from "./distribution/release" ;
29+ import { ReleasesApiConsumer } from "./distribution/releases-api-consumer" ;
3030
3131/**
3232 * distribution.ts
@@ -36,30 +36,14 @@ import { reportUnzipProgress } from "../common/vscode/unzip-progress";
3636 */
3737
3838/**
39- * Default value for the owner name of the extension-managed distribution on GitHub.
40- *
41- * We set the default here rather than as a default config value so that this default is invoked
42- * upon blanking the setting.
43- */
44- const DEFAULT_DISTRIBUTION_OWNER_NAME = "github" ;
45-
46- /**
47- * Default value for the repository name of the extension-managed distribution on GitHub.
48- *
49- * We set the default here rather than as a default config value so that this default is invoked
50- * upon blanking the setting.
39+ * Repository name with owner of the stable version of the extension-managed distribution on GitHub.
5140 */
52- const DEFAULT_DISTRIBUTION_REPOSITORY_NAME = "codeql-cli-binaries" ;
41+ const STABLE_DISTRIBUTION_REPOSITORY_NWO = "github/ codeql-cli-binaries" ;
5342
5443/**
55- * Owner name of the nightly version of the extension-managed distribution on GitHub.
44+ * Repository name with owner of the nightly version of the extension-managed distribution on GitHub.
5645 */
57- const NIGHTLY_DISTRIBUTION_OWNER_NAME = "dsp-testing" ;
58-
59- /**
60- * Repository name of the nightly version of the extension-managed distribution on GitHub.
61- */
62- const NIGHTLY_DISTRIBUTION_REPOSITORY_NAME = "codeql-cli-nightlies" ;
46+ const NIGHTLY_DISTRIBUTION_REPOSITORY_NWO = "dsp-testing/codeql-cli-nightlies" ;
6347
6448/**
6549 * Range of versions of the CLI that are compatible with the extension.
@@ -505,32 +489,22 @@ class ExtensionSpecificDistributionManager {
505489
506490 private createReleasesApiConsumer ( ) : ReleasesApiConsumer {
507491 return new ReleasesApiConsumer (
508- this . distributionOwnerName ,
509- this . distributionRepositoryName ,
492+ this . distributionRepositoryNwo ,
510493 this . config . personalAccessToken ,
511494 ) ;
512495 }
513496
514- private get distributionOwnerName ( ) : string {
497+ private get distributionRepositoryNwo ( ) : string {
515498 if ( this . config . channel === "nightly" ) {
516- return NIGHTLY_DISTRIBUTION_OWNER_NAME ;
499+ return NIGHTLY_DISTRIBUTION_REPOSITORY_NWO ;
517500 } else {
518- return DEFAULT_DISTRIBUTION_OWNER_NAME ;
519- }
520- }
521-
522- private get distributionRepositoryName ( ) : string {
523- if ( this . config . channel === "nightly" ) {
524- return NIGHTLY_DISTRIBUTION_REPOSITORY_NAME ;
525- } else {
526- return DEFAULT_DISTRIBUTION_REPOSITORY_NAME ;
501+ return STABLE_DISTRIBUTION_REPOSITORY_NWO ;
527502 }
528503 }
529504
530505 private get usingNightlyReleases ( ) : boolean {
531506 return (
532- this . distributionOwnerName === NIGHTLY_DISTRIBUTION_OWNER_NAME &&
533- this . distributionRepositoryName === NIGHTLY_DISTRIBUTION_REPOSITORY_NAME
507+ this . distributionRepositoryNwo === NIGHTLY_DISTRIBUTION_REPOSITORY_NWO
534508 ) ;
535509 }
536510
@@ -588,173 +562,6 @@ class ExtensionSpecificDistributionManager {
588562 private static readonly _codeQlExtractedFolderName = "codeql" ;
589563}
590564
591- export class ReleasesApiConsumer {
592- constructor (
593- ownerName : string ,
594- repoName : string ,
595- personalAccessToken ?: string ,
596- ) {
597- // Specify version of the GitHub API
598- this . _defaultHeaders [ "accept" ] = "application/vnd.github.v3+json" ;
599-
600- if ( personalAccessToken ) {
601- this . _defaultHeaders [ "authorization" ] = `token ${ personalAccessToken } ` ;
602- }
603-
604- this . _ownerName = ownerName ;
605- this . _repoName = repoName ;
606- }
607-
608- public async getLatestRelease (
609- versionRange : semver . Range | undefined ,
610- orderBySemver = true ,
611- includePrerelease = false ,
612- additionalCompatibilityCheck ?: ( release : GithubRelease ) => boolean ,
613- ) : Promise < Release > {
614- const apiPath = `/repos/${ this . _ownerName } /${ this . _repoName } /releases` ;
615- const allReleases : GithubRelease [ ] = await (
616- await this . makeApiCall ( apiPath )
617- ) . json ( ) ;
618- const compatibleReleases = allReleases . filter ( ( release ) => {
619- if ( release . prerelease && ! includePrerelease ) {
620- return false ;
621- }
622-
623- if ( versionRange !== undefined ) {
624- const version = semver . parse ( release . tag_name ) ;
625- if (
626- version === null ||
627- ! semver . satisfies ( version , versionRange , { includePrerelease } )
628- ) {
629- return false ;
630- }
631- }
632-
633- return (
634- ! additionalCompatibilityCheck || additionalCompatibilityCheck ( release )
635- ) ;
636- } ) ;
637- // Tag names must all be parsable to semvers due to the previous filtering step.
638- const latestRelease = compatibleReleases . sort ( ( a , b ) => {
639- const versionComparison = orderBySemver
640- ? semver . compare ( semver . parse ( b . tag_name ) ! , semver . parse ( a . tag_name ) ! )
641- : b . id - a . id ;
642- if ( versionComparison !== 0 ) {
643- return versionComparison ;
644- }
645- return b . created_at . localeCompare ( a . created_at , "en-US" ) ;
646- } ) [ 0 ] ;
647- if ( latestRelease === undefined ) {
648- throw new Error (
649- "No compatible CodeQL CLI releases were found. " +
650- "Please check that the CodeQL extension is up to date." ,
651- ) ;
652- }
653- const assets : ReleaseAsset [ ] = latestRelease . assets . map ( ( asset ) => {
654- return {
655- id : asset . id ,
656- name : asset . name ,
657- size : asset . size ,
658- } ;
659- } ) ;
660-
661- return {
662- assets,
663- createdAt : latestRelease . created_at ,
664- id : latestRelease . id ,
665- name : latestRelease . name ,
666- } ;
667- }
668-
669- public async streamBinaryContentOfAsset (
670- asset : ReleaseAsset ,
671- ) : Promise < fetch . Response > {
672- const apiPath = `/repos/${ this . _ownerName } /${ this . _repoName } /releases/assets/${ asset . id } ` ;
673-
674- return await this . makeApiCall ( apiPath , {
675- accept : "application/octet-stream" ,
676- } ) ;
677- }
678-
679- protected async makeApiCall (
680- apiPath : string ,
681- additionalHeaders : { [ key : string ] : string } = { } ,
682- ) : Promise < fetch . Response > {
683- const response = await this . makeRawRequest (
684- ReleasesApiConsumer . _apiBase + apiPath ,
685- Object . assign ( { } , this . _defaultHeaders , additionalHeaders ) ,
686- ) ;
687-
688- if ( ! response . ok ) {
689- // Check for rate limiting
690- const rateLimitResetValue = response . headers . get ( "X-RateLimit-Reset" ) ;
691- if ( response . status === 403 && rateLimitResetValue ) {
692- const secondsToMillisecondsFactor = 1000 ;
693- const rateLimitResetDate = new Date (
694- parseInt ( rateLimitResetValue , 10 ) * secondsToMillisecondsFactor ,
695- ) ;
696- throw new GithubRateLimitedError (
697- response . status ,
698- await response . text ( ) ,
699- rateLimitResetDate ,
700- ) ;
701- }
702- throw new GithubApiError ( response . status , await response . text ( ) ) ;
703- }
704- return response ;
705- }
706-
707- private async makeRawRequest (
708- requestUrl : string ,
709- headers : { [ key : string ] : string } ,
710- redirectCount = 0 ,
711- ) : Promise < fetch . Response > {
712- const response = await fetch . default ( requestUrl , {
713- headers,
714- redirect : "manual" ,
715- } ) ;
716-
717- const redirectUrl = response . headers . get ( "location" ) ;
718- if (
719- isRedirectStatusCode ( response . status ) &&
720- redirectUrl &&
721- redirectCount < ReleasesApiConsumer . _maxRedirects
722- ) {
723- const parsedRedirectUrl = new URL ( redirectUrl ) ;
724- if ( parsedRedirectUrl . protocol !== "https:" ) {
725- throw new Error ( "Encountered a non-https redirect, rejecting" ) ;
726- }
727- if ( parsedRedirectUrl . host !== "api.github.com" ) {
728- // Remove authorization header if we are redirected outside of the GitHub API.
729- //
730- // This is necessary to stream release assets since AWS fails if more than one auth
731- // mechanism is provided.
732- delete headers [ "authorization" ] ;
733- }
734- return await this . makeRawRequest ( redirectUrl , headers , redirectCount + 1 ) ;
735- }
736-
737- return response ;
738- }
739-
740- private readonly _defaultHeaders : { [ key : string ] : string } = { } ;
741- private readonly _ownerName : string ;
742- private readonly _repoName : string ;
743-
744- private static readonly _apiBase = "https://api.github.com" ;
745- private static readonly _maxRedirects = 20 ;
746- }
747-
748- function isRedirectStatusCode ( statusCode : number ) : boolean {
749- return (
750- statusCode === 301 ||
751- statusCode === 302 ||
752- statusCode === 303 ||
753- statusCode === 307 ||
754- statusCode === 308
755- ) ;
756- }
757-
758565/*
759566 * Types and helper functions relating to those types.
760567 */
@@ -905,116 +712,3 @@ function warnDeprecatedLauncher() {
905712 `Please use "${ codeQlLauncherName ( ) } " instead. It is recommended to update to the latest CodeQL binaries.` ,
906713 ) ;
907714}
908-
909- /**
910- * A release on GitHub.
911- */
912- interface Release {
913- assets : ReleaseAsset [ ] ;
914-
915- /**
916- * The creation date of the release on GitHub.
917- */
918- createdAt : string ;
919-
920- /**
921- * The id associated with the release on GitHub.
922- */
923- id : number ;
924-
925- /**
926- * The name associated with the release on GitHub.
927- */
928- name : string ;
929- }
930-
931- /**
932- * An asset corresponding to a release on GitHub.
933- */
934- interface ReleaseAsset {
935- /**
936- * The id associated with the asset on GitHub.
937- */
938- id : number ;
939-
940- /**
941- * The name associated with the asset on GitHub.
942- */
943- name : string ;
944-
945- /**
946- * The size of the asset in bytes.
947- */
948- size : number ;
949- }
950-
951- /**
952- * The json returned from github for a release.
953- */
954- export interface GithubRelease {
955- assets : GithubReleaseAsset [ ] ;
956-
957- /**
958- * The creation date of the release on GitHub, in ISO 8601 format.
959- */
960- created_at : string ;
961-
962- /**
963- * The id associated with the release on GitHub.
964- */
965- id : number ;
966-
967- /**
968- * The name associated with the release on GitHub.
969- */
970- name : string ;
971-
972- /**
973- * Whether the release is a prerelease.
974- */
975- prerelease : boolean ;
976-
977- /**
978- * The tag name. This should be the version.
979- */
980- tag_name : string ;
981- }
982-
983- /**
984- * The json returned by github for an asset in a release.
985- */
986- export interface GithubReleaseAsset {
987- /**
988- * The id associated with the asset on GitHub.
989- */
990- id : number ;
991-
992- /**
993- * The name associated with the asset on GitHub.
994- */
995- name : string ;
996-
997- /**
998- * The size of the asset in bytes.
999- */
1000- size : number ;
1001- }
1002-
1003- export class GithubApiError extends Error {
1004- constructor (
1005- public status : number ,
1006- public body : string ,
1007- ) {
1008- super ( `API call failed with status code ${ status } , body: ${ body } ` ) ;
1009- }
1010- }
1011-
1012- export class GithubRateLimitedError extends GithubApiError {
1013- constructor (
1014- public status : number ,
1015- public body : string ,
1016- public rateLimitResetDate : Date ,
1017- ) {
1018- super ( status , body ) ;
1019- }
1020- }
0 commit comments