Преглед на файлове

Continues progress on web-enabled build

Consolidates to node-fetch 2.6.7 as Octokit requires v2
main
Eric Amodio преди 2 години
родител
ревизия
ae490b6889
променени са 15 файла, в които са добавени 1157 реда и са изтрити 301 реда
  1. +27
    -0
      .vscode/launch.json
  2. +29
    -0
      .vscode/tasks.json
  3. +1
    -0
      .vscodeignore
  4. +14
    -10
      package.json
  5. +201
    -0
      src/@types/node-fetch.d.ts
  6. +1
    -1
      src/constants.ts
  7. +12
    -9
      src/env/node/git/localGitProvider.ts
  8. +53
    -10
      src/errors.ts
  9. +9
    -2
      src/git/gitProvider.ts
  10. +9
    -8
      src/git/gitProviderService.ts
  11. +2
    -1
      src/git/models/repository.ts
  12. +7
    -22
      src/git/remotes/provider.ts
  13. +135
    -55
      src/github/github.ts
  14. +11
    -8
      webpack.config.js
  15. +646
    -175
      yarn.lock

+ 27
- 0
.vscode/launch.json Целия файл

@ -73,6 +73,33 @@
"trace": true
},
{
"name": "Watch & Run (web)",
"type": "pwa-extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--disable-extension=eamodio.gitlens-insiders",
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionDevelopmentKind=web"
],
"cwd": "${workspaceFolder}",
"debugWebWorkerHost": true,
"rendererDebugOptions": {
"sourceMaps": true,
"webRoot": "${workspaceFolder}/src"
},
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
"preLaunchTask": "${defaultBuildTask}",
"presentation": {
"group": "1_watch",
"order": 1
},
"skipFiles": ["<node_internals>/**", "**/node_modules/**", "**/resources/app/out/vs/**"],
"smartStep": true,
"sourceMaps": true,
"trace": true
},
{
"name": "Watch & Run (sandboxed)",
"type": "pwa-extensionHost",
"request": "launch",

+ 29
- 0
.vscode/tasks.json Целия файл

@ -37,6 +37,35 @@
},
"isBackground": true,
"problemMatcher": ["$ts-checker-webpack-watch", "$ts-checker-eslint-webpack-watch"]
},
{
"label": "Run (vscode.dev)",
"group": "test",
"dependsOn": ["npm: web:serve", "npm: web:tunnel"],
"dependsOrder": "parallel",
"problemMatcher": []
},
{
"type": "npm",
"script": "web:serve",
"group": "test",
"isBackground": true,
"problemMatcher": [],
"presentation": {
"group": "web",
"reveal": "never"
}
},
{
"type": "npm",
"script": "web:tunnel",
"group": "test",
"isBackground": true,
"problemMatcher": [],
"presentation": {
"group": "web",
"reveal": "always"
}
}
]
}

+ 1
- 0
.vscodeignore Целия файл

@ -2,6 +2,7 @@
.vscode/**
.vscode-clean/**
.vscode-test/**
.vscode-test-web/**
.yarn/**
dist/webviews/*.css
emoji/**

+ 14
- 10
package.json Целия файл

@ -10135,6 +10135,9 @@
"watch": "webpack --watch --mode development",
"watch:extension": "webpack --watch --mode development --config-name extension",
"watch:webviews": "webpack --watch --mode development --config-name webviews",
"web": "vscode-test-web --extensionDevelopmentPath=. --folder-uri=vscode-vfs://github/gitkraken/vscode-gitlens",
"web:serve": "npx serve --cors -l 5000",
"web:tunnel": "npx localtunnel -p 5000",
"update-dts": "pushd \"src/@types\" && npx vscode-dts dev && popd",
"update-dts:master": "pushd \"src/@types\" && npx vscode-dts master && popd",
"update-emoji": "node ./scripts/generateEmojiShortcodeMap.js",
@ -10142,14 +10145,14 @@
"vscode:prepublish": "yarn run bundle"
},
"dependencies": {
"@octokit/graphql": "4.8.0",
"@octokit/core": "3.5.1",
"@vscode/codicons": "0.0.27",
"ansi-regex": "6.0.1",
"chroma-js": "2.1.2",
"iconv-lite": "0.6.3",
"lodash-es": "4.17.21",
"md5.js": "1.3.5",
"node-fetch": "3.0.0",
"node-fetch": "2.6.7",
"path-browserify": "1.0.1",
"sortablejs": "1.14.0"
},
@ -10160,8 +10163,9 @@
"@types/node": "14.17.4",
"@types/sortablejs": "1.10.7",
"@types/vscode": "1.63.1",
"@typescript-eslint/eslint-plugin": "5.9.1",
"@typescript-eslint/parser": "5.9.1",
"@typescript-eslint/eslint-plugin": "5.10.0",
"@typescript-eslint/parser": "5.10.0",
"@vscode/test-web": "0.0.16",
"circular-dependency-plugin": "5.2.2",
"clean-webpack-plugin": "4.0.0",
"copy-webpack-plugin": "10.2.0",
@ -10169,34 +10173,34 @@
"css-loader": "6.5.1",
"esbuild": "0.14.11",
"esbuild-loader": "2.18.0",
"eslint": "8.6.0",
"eslint": "8.7.0",
"eslint-cli": "1.1.1",
"eslint-config-prettier": "8.3.0",
"eslint-import-resolver-typescript": "2.5.0",
"eslint-plugin-anti-trojan-source": "1.1.0",
"eslint-plugin-import": "2.25.4",
"fork-ts-checker-webpack-plugin": "6.5.0",
"html-loader": "3.0.1",
"html-loader": "3.1.0",
"html-webpack-plugin": "5.5.0",
"image-minimizer-webpack-plugin": "3.2.1",
"imagemin": "8.0.1",
"imagemin-webp": "7.0.0",
"json5": "2.2.0",
"license-checker-rseidelsohn": "2.2.0",
"mini-css-extract-plugin": "2.4.6",
"mini-css-extract-plugin": "2.5.2",
"prettier": "2.5.1",
"sass": "1.47.0",
"sass": "1.48.0",
"sass-loader": "12.4.0",
"terser-webpack-plugin": "5.3.0",
"ts-loader": "9.2.6",
"typescript": "4.5.4",
"vsce": "2.6.3",
"webpack": "5.65.0",
"webpack": "5.66.0",
"webpack-bundle-analyzer": "4.5.0",
"webpack-cli": "4.9.1"
},
"resolutions": {
"node-fetch": "2.6.1",
"node-fetch": "2.6.7",
"semver-regex": "3.1.3",
"trim-newlines": "4.0.2"
}

+ 201
- 0
src/@types/node-fetch.d.ts Целия файл

@ -0,0 +1,201 @@
// Type definitions for node-fetch 2.5
// Project: https://github.com/bitinn/node-fetch
// Definitions by: Torsten Werner <https://github.com/torstenwerner>
// Niklas Lindgren <https://github.com/nikcorg>
// Vinay Bedre <https://github.com/vinaybedre>
// Antonio Román <https://github.com/kyranet>
// Andrew Leedham <https://github.com/AndrewLeedham>
// Jason Li <https://github.com/JasonLi914>
// Steve Faulkner <https://github.com/southpolesteve>
// ExE Boss <https://github.com/ExE-Boss>
// Alex Savin <https://github.com/alexandrusavin>
// Alexis Tyler <https://github.com/OmgImAlexis>
// Jakub Kisielewski <https://github.com/kbkk>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
/// <reference types="node" />
declare module 'node-fetch' {
import type { Agent } from 'http';
import type { URLSearchParams, URL } from 'url';
export class Request extends Body {
constructor(input: RequestInfo, init?: RequestInit);
clone(): Request;
context: RequestContext;
headers: Headers;
method: string;
redirect: RequestRedirect;
referrer: string;
url: string;
// node-fetch extensions to the whatwg/fetch spec
agent?: Agent | ((parsedUrl: URL) => Agent) | undefined;
compress: boolean;
counter: number;
follow: number;
hostname: string;
port?: number | undefined;
protocol: string;
size: number;
timeout: number;
}
export interface RequestInit {
// whatwg/fetch standard options
body?: BodyInit | undefined;
headers?: HeadersInit | undefined;
method?: string | undefined;
redirect?: RequestRedirect | undefined;
// node-fetch extensions
agent?: Agent | ((parsedUrl: URL) => Agent) | undefined; // =null http.Agent instance, allows custom proxy, certificate etc.
compress?: boolean | undefined; // =true support gzip/deflate content encoding. false to disable
follow?: number | undefined; // =20 maximum redirect count. 0 to not follow redirect
size?: number | undefined; // =0 maximum response body size in bytes. 0 to disable
timeout?: number | undefined; // =0 req/res timeout in ms, it resets on redirect. 0 to disable (OS limit applies)
// node-fetch does not support mode, cache or credentials options
}
export type RequestContext =
| 'audio'
| 'beacon'
| 'cspreport'
| 'download'
| 'embed'
| 'eventsource'
| 'favicon'
| 'fetch'
| 'font'
| 'form'
| 'frame'
| 'hyperlink'
| 'iframe'
| 'image'
| 'imageset'
| 'import'
| 'internal'
| 'location'
| 'manifest'
| 'object'
| 'ping'
| 'plugin'
| 'prefetch'
| 'script'
| 'serviceworker'
| 'sharedworker'
| 'style'
| 'subresource'
| 'track'
| 'video'
| 'worker'
| 'xmlhttprequest'
| 'xslt';
export type RequestMode = 'cors' | 'no-cors' | 'same-origin';
export type RequestRedirect = 'error' | 'follow' | 'manual';
export type RequestCredentials = 'omit' | 'include' | 'same-origin';
export type RequestCache = 'default' | 'force-cache' | 'no-cache' | 'no-store' | 'only-if-cached' | 'reload';
export class Headers implements Iterable<[string, string]> {
constructor(init?: HeadersInit);
forEach(callback: (value: string, name: string) => void): void;
append(name: string, value: string): void;
delete(name: string): void;
get(name: string): string | null;
has(name: string): boolean;
raw(): { [k: string]: string[] };
set(name: string, value: string): void;
// Iterable methods
entries(): IterableIterator<[string, string]>;
keys(): IterableIterator<string>;
values(): IterableIterator<string>;
[Symbol.iterator](): Iterator<[string, string]>;
}
type BlobPart = ArrayBuffer | ArrayBufferView | Blob | string;
interface BlobOptions {
type?: string | undefined;
endings?: 'transparent' | 'native' | undefined;
}
export class Blob {
constructor(blobParts?: BlobPart[], options?: BlobOptions);
readonly type: string;
readonly size: number;
slice(start?: number, end?: number): Blob;
text(): Promise<string>;
}
export class Body {
constructor(body?: any, opts?: { size?: number | undefined; timeout?: number | undefined });
arrayBuffer(): Promise<ArrayBuffer>;
blob(): Promise<Blob>;
body: NodeJS.ReadableStream;
bodyUsed: boolean;
buffer(): Promise<Buffer>;
json(): Promise<any>;
size: number;
text(): Promise<string>;
textConverted(): Promise<string>;
timeout: number;
}
interface SystemError extends Error {
code?: string | undefined;
}
export class FetchError extends Error {
name: 'FetchError';
constructor(message: string, type: string, systemError?: SystemError);
type: string;
code?: string | undefined;
errno?: string | undefined;
}
export class Response extends Body {
constructor(body?: BodyInit, init?: ResponseInit);
static error(): Response;
static redirect(url: string, status: number): Response;
clone(): Response;
headers: Headers;
ok: boolean;
redirected: boolean;
status: number;
statusText: string;
type: ResponseType;
url: string;
}
export type ResponseType = 'basic' | 'cors' | 'default' | 'error' | 'opaque' | 'opaqueredirect';
export interface ResponseInit {
headers?: HeadersInit | undefined;
size?: number | undefined;
status?: number | undefined;
statusText?: string | undefined;
timeout?: number | undefined;
url?: string | undefined;
}
interface URLLike {
href: string;
}
export type HeadersInit = Headers | string[][] | { [key: string]: string };
// HeaderInit is exported to support backwards compatibility. See PR #34382
export type HeaderInit = HeadersInit;
export type BodyInit = ArrayBuffer | ArrayBufferView | NodeJS.ReadableStream | string | URLSearchParams;
export type RequestInfo = string | URLLike | Request;
declare function fetch(url: RequestInfo, init?: RequestInit): Promise<Response>;
declare namespace fetch {
function isRedirect(code: number): boolean;
}
export default fetch;
}

+ 1
- 1
src/constants.ts Целия файл

@ -97,7 +97,7 @@ export const enum DocumentSchemes {
Output = 'output',
PRs = 'pr',
Vsls = 'vsls',
VirtualFS = 'vscode-vfs',
Virtual = 'vscode-vfs',
}
export function getEditorIfActive(document: TextDocument): TextEditor | undefined {

+ 12
- 9
src/env/node/git/localGitProvider.ts Целия файл

@ -1028,6 +1028,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
async getBranches(
repoPath: string | undefined,
options?: {
cursor?: string;
filter?: (b: GitBranch) => boolean;
sort?: boolean | BranchSortOptions;
},
@ -1083,7 +1084,9 @@ export class LocalGitProvider implements GitProvider, Disposable {
resultsPromise = load.call(this);
if (this.useCaching) {
this._branchesCache.set(repoPath, resultsPromise);
if (options?.cursor == null) {
this._branchesCache.set(repoPath, resultsPromise);
}
queueMicrotask(async () => {
if (!(await this.container.git.getRepository(repoPath))?.supportsChangeEvents) {
@ -1607,6 +1610,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
options?: {
all?: boolean;
authors?: string[];
cursor?: string;
limit?: number;
merges?: boolean;
ordering?: string | null;
@ -1655,6 +1659,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
repoPath: string,
options?: {
authors?: string[];
cursor?: string;
limit?: number;
merges?: boolean;
ordering?: string | null;
@ -1930,6 +1935,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
fileName: string,
options?: {
all?: boolean;
cursor?: string;
limit?: number;
ordering?: string | null;
range?: Range;
@ -2081,6 +2087,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
...options
}: {
all?: boolean;
cursor?: string;
limit?: number;
ordering?: string | null;
range?: Range;
@ -3297,11 +3304,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
}
@log()
async getVersionedUri(
repoPath: string | undefined,
fileName: string,
ref: string | undefined,
): Promise<Uri | undefined> {
async getVersionedUri(repoPath: string, fileName: string, ref: string | undefined): Promise<Uri | undefined> {
if (ref === GitRevision.deletedOrMissing) return undefined;
if (
@ -3310,11 +3313,11 @@ export class LocalGitProvider implements GitProvider, Disposable {
(GitRevision.isUncommitted(ref) && !GitRevision.isUncommittedStaged(ref))
) {
// Make sure the file exists in the repo
let data = await Git.ls_files(repoPath!, fileName);
let data = await Git.ls_files(repoPath, fileName);
if (data != null) return GitUri.file(fileName);
// Check if the file exists untracked
data = await Git.ls_files(repoPath!, fileName, { untracked: true });
data = await Git.ls_files(repoPath, fileName, { untracked: true });
if (data != null) return GitUri.file(fileName);
return undefined;
@ -3324,7 +3327,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
return GitUri.git(fileName, repoPath);
}
return GitUri.toRevisionUri(ref, fileName, repoPath!);
return GitUri.toRevisionUri(ref, fileName, repoPath);
}
@log()

+ 53
- 10
src/errors.ts Целия файл

@ -1,19 +1,62 @@
'use strict';
import { Uri } from 'vscode';
import { GitProviderId } from './git/gitProvider';
import { GitProviderService } from './git/gitProviderService';
export class ProviderNotFoundError extends Error {
readonly id: GitProviderId;
export const enum AuthenticationErrorReason {
UserDidNotConsent = 1,
Unauthorized = 2,
Forbidden = 3,
}
export class AuthenticationError extends Error {
readonly id: string;
readonly original?: Error;
readonly reason: AuthenticationErrorReason | undefined;
constructor(id: GitProviderId);
constructor(uri: Uri);
constructor(idOrUri: GitProviderId | Uri);
constructor(idOrUri: GitProviderId | Uri) {
const id = typeof idOrUri === 'string' ? idOrUri : GitProviderService.getProviderId(idOrUri);
super(`No provider registered with ${id}`);
constructor(id: string, reason?: AuthenticationErrorReason, original?: Error);
constructor(id: string, message?: string, original?: Error);
constructor(id: string, messageOrReason: string | AuthenticationErrorReason | undefined, original?: Error) {
let message;
let reason: AuthenticationErrorReason | undefined;
if (messageOrReason == null) {
message = `Unable to get required authentication session for '${id}`;
} else if (typeof messageOrReason === 'string') {
message = messageOrReason;
reason = undefined;
} else {
reason = messageOrReason;
switch (reason) {
case AuthenticationErrorReason.UserDidNotConsent:
message = `'${id} authentication is required for this operation`;
break;
case AuthenticationErrorReason.Unauthorized:
message = `The provided '${id}' credentials are either invalid or expired`;
break;
case AuthenticationErrorReason.Forbidden:
message = `The provided '${id}' credentials do not have the required access`;
break;
}
}
super(message);
this.id = id;
this.original = original;
this.reason = reason;
Error.captureStackTrace?.(this, AuthenticationError);
}
}
export class ProviderNotFoundError extends Error {
constructor(pathOrUri: string | Uri) {
super(`No provider registered for '${typeof pathOrUri === 'string' ? pathOrUri : pathOrUri.toString(true)}'`);
Error.captureStackTrace?.(this, ProviderNotFoundError);
}
}
export class ProviderRequestError extends Error {
constructor(public readonly original: Error) {
super(original.message);
Error.captureStackTrace?.(this, ProviderRequestError);
}
}

+ 9
- 2
src/git/gitProvider.ts Целия файл

@ -147,7 +147,11 @@ export interface GitProvider {
getBranch(repoPath: string): Promise<GitBranch | undefined>;
getBranches(
repoPath: string,
options?: { filter?: ((b: GitBranch) => boolean) | undefined; sort?: boolean | BranchSortOptions | undefined },
options?: {
cursor?: string;
filter?: ((b: GitBranch) => boolean) | undefined;
sort?: boolean | BranchSortOptions | undefined;
},
): Promise<PagedResult<GitBranch>>;
getChangedFilesCount(repoPath: string, ref?: string): Promise<GitDiffShortStat | undefined>;
getCommit(repoPath: string, ref: string): Promise<GitLogCommit | undefined>;
@ -213,6 +217,7 @@ export interface GitProvider {
options?: {
all?: boolean | undefined;
authors?: string[] | undefined;
cursor?: string | undefined;
limit?: number | undefined;
merges?: boolean | undefined;
ordering?: string | null | undefined;
@ -225,6 +230,7 @@ export interface GitProvider {
repoPath: string,
options?: {
authors?: string[] | undefined;
cursor?: string | undefined;
limit?: number | undefined;
merges?: boolean | undefined;
ordering?: string | null | undefined;
@ -243,6 +249,7 @@ export interface GitProvider {
fileName: string,
options?: {
all?: boolean | undefined;
cursor?: string | undefined;
limit?: number | undefined;
ordering?: string | null | undefined;
range?: Range | undefined;
@ -333,7 +340,7 @@ export interface GitProvider {
getTreeFileForRevision(repoPath: string, fileName: string, ref: string): Promise<GitTree | undefined>;
getTreeForRevision(repoPath: string, ref: string): Promise<GitTree[]>;
getVersionedFileBuffer(repoPath: string, fileName: string, ref: string): Promise<Buffer | undefined>;
getVersionedUri(repoPath: string | undefined, fileName: string, ref: string | undefined): Promise<Uri | undefined>;
getVersionedUri(repoPath: string, fileName: string, ref: string | undefined): Promise<Uri | undefined>;
getWorkingUri(repoPath: string, uri: Uri): Promise<Uri | undefined>;
hasBranchOrTag(

+ 9
- 8
src/git/gitProviderService.ts Целия файл

@ -31,6 +31,7 @@ import { Logger } from '../logger';
import { Arrays, debug, gate, Iterables, log, Promises } from '../system';
import { isDescendent, normalizePath } from '../system/path';
import { PromiseOrValue } from '../system/promise';
import { CharCode } from '../system/string';
import { vslsUriPrefixRegex } from '../vsls/vsls';
import { GitProvider, GitProviderDescriptor, GitProviderId, PagedResult, ScmRepository } from './gitProvider';
import { GitUri } from './gitUri';
@ -580,7 +581,7 @@ export class GitProviderService implements Disposable {
const id = GitProviderService.getProviderId(repoPath);
const provider = this._providers.get(id);
if (provider == null) throw new ProviderNotFoundError(id);
if (provider == null) throw new ProviderNotFoundError(repoPath);
switch (id) {
case GitProviderId.Git:
@ -603,7 +604,7 @@ export class GitProviderService implements Disposable {
throw new Error('Unsupported provider; no repository path');
}
if (typeof repoPath !== 'string' && repoPath.scheme === DocumentSchemes.VirtualFS) {
if (typeof repoPath !== 'string' && repoPath.scheme === DocumentSchemes.Virtual) {
if (repoPath.authority.startsWith('github')) {
return GitProviderId.GitHub;
}
@ -1526,7 +1527,7 @@ export class GitProviderService implements Disposable {
}
async function findRepoPath(this: GitProviderService): Promise<string | null> {
const { provider, path } = this.getProvider(filePath);
const { provider, path } = this.getProvider(repoPathOrUri);
rp = (await provider.getRepoPath(path)) ?? null;
// Store the found repoPath for this filePath, so we can avoid future lookups for the filePath
this._pathToRepoPathCache.set(filePath, rp);
@ -1547,11 +1548,12 @@ export class GitProviderService implements Disposable {
// rp = root.path;
folder = root.folder;
} else {
folder = workspace.getWorkspaceFolder(GitUri.file(rp, isVslsScheme));
const uri = GitUri.file(rp, isVslsScheme);
folder = workspace.getWorkspaceFolder(uri);
if (folder == null) {
const parts = rp.split('/');
folder = {
uri: GitUri.file(rp, isVslsScheme),
uri: uri,
name: parts[parts.length - 1],
index: this.repositoryCount,
};
@ -1594,7 +1596,7 @@ export class GitProviderService implements Disposable {
if (repo == null && isVslsScheme !== false && this.container.vsls.isMaybeGuest) {
if (!vslsUriPrefixRegex.test(path)) {
path = normalizePath(path);
const vslsPath = `/~0${path.startsWith('/') ? path : `/${path}`}`;
const vslsPath = `/~0${path.charCodeAt(0) === CharCode.Slash ? path : `/${path}`}`;
repo = findBySubPath(this._repositories, vslsPath);
}
}
@ -1746,8 +1748,7 @@ export class GitProviderService implements Disposable {
}
isTrackable(uri: Uri): boolean {
const { scheme } = uri;
if (!this._supportedSchemes.has(scheme)) return false;
if (!this._supportedSchemes.has(uri.scheme)) return false;
const { provider } = this.getProvider(uri);
return provider.isTrackable(uri);

+ 2
- 1
src/git/models/repository.ts Целия файл

@ -19,7 +19,7 @@ import { BuiltInGitCommands, BuiltInGitConfiguration, Starred, WorkspaceState }
import { Container } from '../../container';
import { Logger, LogLevel } from '../../logger';
import { Messages } from '../../messages';
import { Arrays, Dates, debug, Functions, gate, Iterables, log, logName } from '../../system';
import { Arrays, Dates, debug, Functions, gate, Iterables, log, logName, memoize } from '../../system';
import { basename, joinPaths, relative } from '../../system/path';
import { runGitCommandInTerminal } from '../../terminal';
import { GitProviderDescriptor } from '../gitProvider';
@ -290,6 +290,7 @@ export class Repository implements Disposable {
this._disposable.dispose();
}
@memoize()
get uri(): Uri {
return Uri.file(this.path);
}

+ 7
- 22
src/git/remotes/provider.ts Целия файл

@ -13,6 +13,7 @@ import { DynamicAutolinkReference } from '../../annotations/autolinks';
import { AutolinkReference } from '../../config';
import { WorkspaceState } from '../../constants';
import { Container } from '../../container';
import { AuthenticationError, ProviderRequestError } from '../../errors';
import { Logger } from '../../logger';
import { debug, Encoding, gate, log, Promises } from '../../system';
import {
@ -259,22 +260,6 @@ export class Authentication {
}
}
export class AuthenticationError extends Error {
constructor(private original: Error) {
super(original.message);
Error.captureStackTrace?.(this, AuthenticationError);
}
}
export class ClientError extends Error {
constructor(private original: Error) {
super(original.message);
Error.captureStackTrace?.(this, ClientError);
}
}
// TODO@eamodio revisit how once authenticated, all remotes are always connected, even after a restart
export abstract class RichRemoteProvider extends RemoteProvider {
@ -396,7 +381,7 @@ export abstract class RichRemoteProvider extends RemoteProvider {
} catch (ex) {
Logger.error(ex, cc);
if (ex instanceof ClientError || ex instanceof AuthenticationError) {
if (ex instanceof ProviderRequestError || ex instanceof AuthenticationError) {
this.handleClientException();
}
return undefined;
@ -431,7 +416,7 @@ export abstract class RichRemoteProvider extends RemoteProvider {
} catch (ex) {
Logger.error(ex, cc);
if (ex instanceof ClientError || ex instanceof AuthenticationError) {
if (ex instanceof ProviderRequestError || ex instanceof AuthenticationError) {
this.handleClientException();
}
return undefined;
@ -461,7 +446,7 @@ export abstract class RichRemoteProvider extends RemoteProvider {
} catch (ex) {
Logger.error(ex, cc);
if (ex instanceof ClientError || ex instanceof AuthenticationError) {
if (ex instanceof ProviderRequestError || ex instanceof AuthenticationError) {
this.handleClientException();
}
return undefined;
@ -487,7 +472,7 @@ export abstract class RichRemoteProvider extends RemoteProvider {
} catch (ex) {
Logger.error(ex, cc);
if (ex instanceof ClientError || ex instanceof AuthenticationError) {
if (ex instanceof ProviderRequestError || ex instanceof AuthenticationError) {
this.handleClientException();
}
return undefined;
@ -520,7 +505,7 @@ export abstract class RichRemoteProvider extends RemoteProvider {
} catch (ex) {
Logger.error(ex, cc);
if (ex instanceof ClientError || ex instanceof AuthenticationError) {
if (ex instanceof ProviderRequestError || ex instanceof AuthenticationError) {
this.handleClientException();
}
return undefined;
@ -567,7 +552,7 @@ export abstract class RichRemoteProvider extends RemoteProvider {
this._prsByCommit.delete(ref);
if (ex instanceof ClientError || ex instanceof AuthenticationError) {
if (ex instanceof ProviderRequestError || ex instanceof AuthenticationError) {
this.handleClientException();
}
return null;

+ 135
- 55
src/github/github.ts Целия файл

@ -1,5 +1,11 @@
'use strict';
import { graphql } from '@octokit/graphql';
import { Octokit } from '@octokit/core';
import { GraphqlResponseError } from '@octokit/graphql';
import { RequestError } from '@octokit/request-error';
import type { Endpoints, OctokitResponse, RequestParameters } from '@octokit/types';
import fetch from '@env/fetch';
import { isWeb } from '@env/platform';
import { AuthenticationError, AuthenticationErrorReason, ProviderRequestError } from '../errors';
import {
type DefaultBranch,
type IssueOrPullRequest,
@ -8,9 +14,9 @@ import {
PullRequestState,
} from '../git/models';
import type { Account } from '../git/models/author';
import { AuthenticationError, ClientError, type RichRemoteProvider } from '../git/remotes/provider';
import { Logger } from '../logger';
import { debug } from '../system';
import type { RichRemoteProvider } from '../git/remotes/provider';
import { LogCorrelationContext, Logger, LogLevel } from '../logger';
import { debug, Stopwatch } from '../system';
export class GitHubApi {
@debug<GitHubApi['getAccountForCommit']>({ args: { 0: p => p.name, 1: '<token>' } })
@ -65,9 +71,8 @@ export class GitHubApi {
}
}`;
const rsp = await graphql<QueryResult>(query, {
const rsp = await this.graphql<QueryResult>(token, query, {
...options,
headers: { authorization: `Bearer ${token}` },
owner: owner,
repo: repo,
ref: ref,
@ -83,13 +88,8 @@ export class GitHubApi {
avatarUrl: author.avatarUrl,
};
} catch (ex) {
Logger.error(ex, cc);
if (ex.status >= 400 && ex.status <= 500) {
if (ex.status === 401) throw new AuthenticationError(ex);
throw new ClientError(ex);
}
throw ex;
debugger;
throw this.handleRequestErrors(ex, cc);
}
}
@ -139,9 +139,8 @@ export class GitHubApi {
}
}`;
const rsp = await graphql<QueryResult>(query, {
const rsp = await this.graphql<QueryResult>(token, query, {
...options,
headers: { authorization: `Bearer ${token}` },
owner: owner,
repo: repo,
emailQuery: `in:email ${email}`,
@ -157,13 +156,8 @@ export class GitHubApi {
avatarUrl: author.avatarUrl,
};
} catch (ex) {
Logger.error(ex, cc);
if (ex.status >= 400 && ex.status <= 500) {
if (ex.status === 401) throw new AuthenticationError(ex);
throw new ClientError(ex);
}
throw ex;
debugger;
throw this.handleRequestErrors(ex, cc);
}
}
@ -199,9 +193,8 @@ export class GitHubApi {
}
}`;
const rsp = await graphql<QueryResult>(query, {
const rsp = await this.graphql<QueryResult>(token, query, {
...options,
headers: { authorization: `Bearer ${token}` },
owner: owner,
repo: repo,
});
@ -214,13 +207,8 @@ export class GitHubApi {
name: defaultBranch,
};
} catch (ex) {
Logger.error(ex, cc);
if (ex.status >= 400 && ex.status <= 500) {
if (ex.status === 401) throw new AuthenticationError(ex);
throw new ClientError(ex);
}
throw ex;
debugger;
throw this.handleRequestErrors(ex, cc);
}
}
@ -268,9 +256,8 @@ export class GitHubApi {
}
}`;
const rsp = await graphql<QueryResult>(query, {
const rsp = await this.graphql<QueryResult>(token, query, {
...options,
headers: { authorization: `Bearer ${token}` },
owner: owner,
repo: repo,
number: number,
@ -290,13 +277,8 @@ export class GitHubApi {
url: issue.url,
};
} catch (ex) {
Logger.error(ex, cc);
if (ex.status >= 400 && ex.status <= 500) {
if (ex.status === 401) throw new AuthenticationError(ex);
throw new ClientError(ex);
}
throw ex;
debugger;
throw this.handleRequestErrors(ex, cc);
}
}
@ -369,9 +351,8 @@ export class GitHubApi {
}
}`;
const rsp = await graphql<QueryResult>(query, {
const rsp = await this.graphql<QueryResult>(token, query, {
...options,
headers: { authorization: `Bearer ${token}` },
owner: owner,
repo: repo,
branch: branch,
@ -396,13 +377,8 @@ export class GitHubApi {
return GitHubPullRequest.from(prs[0], provider);
} catch (ex) {
Logger.error(ex, cc);
if (ex.status >= 400 && ex.status <= 500) {
if (ex.status === 401) throw new AuthenticationError(ex);
throw new ClientError(ex);
}
throw ex;
debugger;
throw this.handleRequestErrors(ex, cc);
}
}
@ -470,9 +446,8 @@ export class GitHubApi {
}
}`;
const rsp = await graphql<QueryResult>(query, {
const rsp = await this.graphql<QueryResult>(token, query, {
...options,
headers: { authorization: `Bearer ${token}` },
owner: owner,
repo: repo,
ref: ref,
@ -495,15 +470,120 @@ export class GitHubApi {
return GitHubPullRequest.from(prs[0], provider);
} catch (ex) {
Logger.error(ex, cc);
debugger;
throw this.handleRequestErrors(ex, cc);
}
}
private _octokits = new Map<string, Octokit>();
private octokit(token: string, options?: ConstructorParameters<typeof Octokit>[0]): Octokit {
let octokit = this._octokits.get(token);
if (octokit == null) {
let defaults;
if (isWeb) {
function fetchCore(url: string, options: { headers?: Record<string, string> }) {
if (options.headers != null) {
// Strip out the user-agent (since it causes warnings in a webworker)
const { 'user-agent': userAgent, ...headers } = options.headers;
if (userAgent) {
options.headers = headers;
}
}
return fetch(url, options);
}
if (ex.status >= 400 && ex.status <= 500) {
if (ex.status === 401) throw new AuthenticationError(ex);
throw new ClientError(ex);
defaults = Octokit.defaults({
auth: `token ${token}`,
request: { fetch: fetchCore },
});
} else {
defaults = Octokit.defaults({ auth: `token ${token}` });
}
octokit = new defaults(options);
this._octokits.set(token, octokit);
if (Logger.logLevel === LogLevel.Debug || Logger.isDebugging) {
octokit.hook.wrap('request', async (request, options) => {
const stopwatch = new Stopwatch(`[GITHUB] ${options.method} ${options.url}`, { log: false });
try {
return await request(options);
} finally {
let message;
try {
if (typeof options.query === 'string') {
const match = /(^[^({\n]+)/.exec(options.query);
message = ` ${match?.[1].trim() ?? options.query}`;
}
} catch {}
stopwatch.stop({ message: message });
}
});
}
}
return octokit;
}
private async graphql<T>(
token: string,
query: string,
variables: { [key: string]: any },
cc?: LogCorrelationContext,
): Promise<T | undefined> {
try {
return await this.octokit(token).graphql<T>(query, variables);
} catch (ex) {
debugger;
throw this.handleRequestErrors(ex, cc);
}
}
private async request<R extends string>(
token: string,
route: keyof Endpoints | R,
options?: R extends keyof Endpoints ? Endpoints[R]['parameters'] & RequestParameters : RequestParameters,
): Promise<R extends keyof Endpoints ? Endpoints[R]['response'] : OctokitResponse<unknown>> {
try {
return (await this.octokit(token).request<R>(route, options)) as any;
} catch (ex) {
this.handleRequestErrors(ex);
throw ex;
}
}
private handleRequestErrors(
ex: unknown | RequestError | GraphqlResponseError<any>,
cc?: LogCorrelationContext,
): unknown {
Logger.error(ex, cc);
debugger;
if (ex instanceof RequestError) {
switch (ex.status) {
case 401:
return new AuthenticationError('github', AuthenticationErrorReason.Unauthorized, ex);
case 403:
return new AuthenticationError('github', AuthenticationErrorReason.Forbidden, ex);
case 500:
if (ex.response != null) {
// TODO@eamodio: Handle GitHub down errors
}
break;
default:
if (ex.status >= 400 && ex.status <= 500) return new ProviderRequestError(ex);
break;
}
return ex;
}
if (ex instanceof GraphqlResponseError && ex.errors?.[0]?.type === 'FORBIDDEN') {
return new AuthenticationError('github', AuthenticationErrorReason.Forbidden, ex);
}
return ex;
}
}
interface GitHubIssueOrPullRequest {

+ 11
- 8
webpack.config.js Целия файл

@ -145,14 +145,17 @@ function getExtensionConfig(target, mode, env) {
},
}),
],
splitChunks: {
// Disable all non-async code splitting
chunks: () => false,
cacheGroups: {
default: false,
vendors: false,
},
},
splitChunks:
target === 'webworker'
? false
: {
// Disable all non-async code splitting
chunks: () => false,
cacheGroups: {
default: false,
vendors: false,
},
},
},
externals: {
vscode: 'commonjs vscode',

+ 646
- 175
yarn.lock
Файловите разлики са ограничени, защото са твърде много
Целия файл


Зареждане…
Отказ
Запис