diff --git a/CHANGELOG.md b/CHANGELOG.md index be19c28f..49bc3bdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log +## 0.6.6 (PENDING) + +- Auto-renew license upon expiration. +- Update bazel and stackb activity panel keybindings + ## 0.6.5 (Sun Nov 1 2020) - Update bzl to include codesearch fixes/improvements. diff --git a/package-lock.json b/package-lock.json index 170822d9..7023a11d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "bazel-stack-vscode", - "version": "0.6.5", + "version": "0.6.6", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -5975,6 +5975,11 @@ "punycode": "^2.1.1" } }, + "tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==" + }, "ts-loader": { "version": "8.0.4", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.0.4.tgz", diff --git a/package.json b/package.json index 04284ece..283f38f8 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "bazel-stack-vscode", "displayName": "bazel-stack-vscode", "description": "Bazel Support for Visual Studio Code", - "version": "0.6.5", + "version": "0.6.6", "publisher": "StackBuild", "license": "Apache-2.0", "icon": "stackb-full.png", @@ -572,12 +572,12 @@ }, { "command": "workbench.view.extension.bazel-explorer", - "key": "shift+cmd+z", + "key": "shift+cmd+t", "title": "Reveal Bazel Explorer" }, { "command": "workbench.view.extension.stackb-explorer", - "key": "shift+cmd+t", + "key": "shift+ctrl+t", "title": "Reveal Stack.Build Explorer" } ], @@ -1151,13 +1151,14 @@ "protobufjs": "6.10.1", "request": "2.88.2", "sha256-file": "1.0.0", - "shiki": "^0.2.6", "shiki-themes": "^0.2.6", + "shiki": "^0.2.6", "simple-lightbox": "2.1.0", "slash": "3.0.0", "strip-ansi": "^6.0.0", "tail": "2.0.4", "tmp": "0.2.1", + "tree-kill": "^1.2.2", "uuid": "8.3.0", "vscode-common": "1.50.0", "vscode-extension-telemetry": "^0.1.6", diff --git a/src/bzl/bzlclient.ts b/src/bzl/bzlclient.ts deleted file mode 100644 index 0b7d19f0..00000000 --- a/src/bzl/bzlclient.ts +++ /dev/null @@ -1,406 +0,0 @@ -import * as grpc from '@grpc/grpc-js'; -import * as vscode from 'vscode'; -import { ApplicationServiceClient } from '../proto/build/stack/bezel/v1beta1/ApplicationService'; -import { CancelRequest } from '../proto/build/stack/bezel/v1beta1/CancelRequest'; -import { CancelResponse } from '../proto/build/stack/bezel/v1beta1/CancelResponse'; -import { CommandHistory } from '../proto/build/stack/bezel/v1beta1/CommandHistory'; -import { CommandServiceClient } from '../proto/build/stack/bezel/v1beta1/CommandService'; -import { DeleteCommandHistoryResponse } from '../proto/build/stack/bezel/v1beta1/DeleteCommandHistoryResponse'; -import { ExternalListWorkspacesResponse } from '../proto/build/stack/bezel/v1beta1/ExternalListWorkspacesResponse'; -import { ExternalWorkspace } from '../proto/build/stack/bezel/v1beta1/ExternalWorkspace'; -import { ExternalWorkspaceServiceClient } from '../proto/build/stack/bezel/v1beta1/ExternalWorkspaceService'; -import { FileDownloadResponse } from '../proto/build/stack/bezel/v1beta1/FileDownloadResponse'; -import { FileKind } from '../proto/build/stack/bezel/v1beta1/FileKind'; -import { FileServiceClient } from '../proto/build/stack/bezel/v1beta1/FileService'; -import { HistoryClient } from '../proto/build/stack/bezel/v1beta1/History'; -import { LabelKind } from '../proto/build/stack/bezel/v1beta1/LabelKind'; -import { ListCommandHistoryResponse } from '../proto/build/stack/bezel/v1beta1/ListCommandHistoryResponse'; -import { ListPackagesResponse } from '../proto/build/stack/bezel/v1beta1/ListPackagesResponse'; -import { ListRulesResponse } from '../proto/build/stack/bezel/v1beta1/ListRulesResponse'; -import { ListWorkspacesResponse } from '../proto/build/stack/bezel/v1beta1/ListWorkspacesResponse'; -import { Metadata } from '../proto/build/stack/bezel/v1beta1/Metadata'; -import { Package } from '../proto/build/stack/bezel/v1beta1/Package'; -import { PackageServiceClient } from '../proto/build/stack/bezel/v1beta1/PackageService'; -import { ShutdownResponse } from '../proto/build/stack/bezel/v1beta1/ShutdownResponse'; -import { Workspace } from '../proto/build/stack/bezel/v1beta1/Workspace'; -import { WorkspaceServiceClient } from '../proto/build/stack/bezel/v1beta1/WorkspaceService'; -import { CodeSearchClient } from '../proto/build/stack/codesearch/v1beta1/CodeSearch'; -import { CreateScopeRequest } from '../proto/build/stack/codesearch/v1beta1/CreateScopeRequest'; -import { CreateScopeResponse } from '../proto/build/stack/codesearch/v1beta1/CreateScopeResponse'; -import { GetScopeRequest } from '../proto/build/stack/codesearch/v1beta1/GetScopeRequest'; -import { ListScopesRequest } from '../proto/build/stack/codesearch/v1beta1/ListScopesRequest'; -import { ListScopesResponse } from '../proto/build/stack/codesearch/v1beta1/ListScopesResponse'; -import { Scope } from '../proto/build/stack/codesearch/v1beta1/Scope'; -import { ScopedQuery } from '../proto/build/stack/codesearch/v1beta1/ScopedQuery'; -import { ScopesClient } from '../proto/build/stack/codesearch/v1beta1/Scopes'; -import { ProtoGrpcType as BzlProtoGrpcType } from '../proto/bzl'; -import { ProtoGrpcType as CodesearchProtoGrpcType } from '../proto/codesearch'; -import { CodeSearchResult } from '../proto/livegrep/CodeSearchResult'; -import { ButtonName } from './constants'; - -export interface Closeable { - close(): void; -} - -export class GRPCClient implements vscode.Disposable { - private disposables: vscode.Disposable[] = []; - private closeables: Closeable[] = []; - - constructor( - readonly address: string, - protected defaultDeadlineSeconds = 30, - ) { - } - - protected getCredentials(address: string): grpc.ChannelCredentials { - if (address.endsWith(':443')) { - return grpc.credentials.createSsl(); - } - return grpc.credentials.createInsecure(); - } - - protected getDeadline(seconds?: number): grpc.Deadline { - const deadline = new Date(); - deadline.setSeconds(deadline.getSeconds() - + (seconds || this.defaultDeadlineSeconds)); - return deadline; - } - - protected handleError(err: grpc.ServiceError): grpc.ServiceError { - if (err.code === grpc.status.UNAVAILABLE) { - return this.handleErrorUnavailable(err); - } - return err; - } - - protected handleErrorUnavailable(err: grpc.ServiceError): grpc.ServiceError { - return err; - } - - protected add(client: T): T { - this.closeables.push(client); - return client; - } - - public dispose() { - for (const closeable of this.closeables) { - closeable.close(); - } - this.closeables.length = 0; - for (const disposable of this.disposables) { - disposable.dispose(); - } - this.disposables.length = 0; - } - -} - -export interface BzlCodesearch { - createScope(request: CreateScopeRequest, callback: (response: CreateScopeResponse) => void): Promise; - searchScope(request: ScopedQuery): Promise; - listScopes(request: ListScopesRequest): Promise; - getScope(request: GetScopeRequest): Promise; -} - -export class BzlClient extends GRPCClient implements BzlCodesearch { - private readonly app: ApplicationServiceClient; - private readonly externals: ExternalWorkspaceServiceClient; - private readonly workspaces: WorkspaceServiceClient; - private readonly packages: PackageServiceClient; - public readonly commands: CommandServiceClient; // server-streaming - private readonly history: HistoryClient; - private readonly files: FileServiceClient; - public readonly scopes: ScopesClient; // server-streaming - private readonly codesearch: CodeSearchClient; - public metadata: Metadata | undefined; - public isRemoteClient: boolean = false; - - constructor( - public readonly executable: string, - readonly bzlProtos: BzlProtoGrpcType, - readonly codesearchProtos: CodesearchProtoGrpcType, - readonly address: string, - private onDidRequestRestart?: vscode.EventEmitter, - ) { - super(address); - - const v1beta1 = bzlProtos.build.stack.bezel.v1beta1; - const creds = this.getCredentials(address); - this.app = this.add(new v1beta1.ApplicationService(address, creds, { - 'grpc.initial_reconnect_backoff_ms': 200, - })); - this.externals = this.add(new v1beta1.ExternalWorkspaceService(address, creds)); - this.workspaces = this.add(new v1beta1.WorkspaceService(address, creds)); - this.packages = this.add(new v1beta1.PackageService(address, creds)); - this.commands = this.add(new v1beta1.CommandService(address, creds)); - this.history = this.add(new v1beta1.History(address, creds)); - this.files = this.add(new v1beta1.FileService(address, creds)); - this.scopes = this.add(new codesearchProtos.build.stack.codesearch.v1beta1.Scopes(address, creds)); - this.codesearch = this.add(new codesearchProtos.build.stack.codesearch.v1beta1.CodeSearch(address, creds)); - } - - httpURL(): string { - const address = this.address; - const scheme = address.endsWith(':443') ? 'https' : 'http'; - return `${scheme}://${address}`; - } - - async waitForReady(seconds: number = 3): Promise { - return this.getMetadata(true, seconds); - } - - protected handleErrorUnavailable(err: grpc.ServiceError): grpc.ServiceError { - // if metadata object not exists we might still be in the "starting the - // bzl server" phase. - if (!this.metadata) { - return err; - } - if (this.onDidRequestRestart) { - vscode.window.showWarningMessage( - `The server at ${this.address} is unavailable. Would you like to restart?`, - ButtonName.Yes, ButtonName.NoThanks, - ).then(response => { - if (response === ButtonName.Yes) { - this.onDidRequestRestart!.fire(); - } - }); - } else { - vscode.window.showWarningMessage( - `The server at ${this.address} is unavailable. Please check that the tcp connection is still valid.`, - ); - } - return err; - } - - async getMetadata(waitForReady = false, deadlineSeconds = 30): Promise { - return new Promise((resolve, reject) => { - this.app.GetMetadata( - {}, - new grpc.Metadata({ waitForReady: waitForReady }), - { deadline: this.getDeadline(deadlineSeconds) }, - (err?: grpc.ServiceError, resp?: Metadata) => { - if (err) { - reject(this.handleError(err)); - } else { - this.metadata = resp; - resolve(resp); - } - }); - }); - } - - async restart(): Promise { - return this.shutdown(true); - } - - async shutdown(restart: boolean = false): Promise { - return new Promise((resolve, reject) => { - this.app.Shutdown( - { restart: restart }, - new grpc.Metadata(), - { deadline: this.getDeadline() }, - (err?: grpc.ServiceError, resp?: ShutdownResponse) => { - if (err) { - reject(this.handleError(err)); - } else { - resolve(resp); - } - }); - }); - } - - async listHistory(cwd: string): Promise { - return new Promise((resolve, reject) => { - this.history.List( - { cwd }, - new grpc.Metadata(), - { deadline: this.getDeadline() }, - async (err?: grpc.ServiceError, resp?: ListCommandHistoryResponse) => { - if (err) { - reject(this.handleError(err)); - } else { - resolve(resp?.history); - } - }); - }); - } - - async deleteCommandHistoryById(id: string): Promise { - return new Promise((resolve, reject) => { - this.history.Delete( - { id: id }, - new grpc.Metadata(), - { deadline: this.getDeadline() }, - async (err?: grpc.ServiceError, resp?: DeleteCommandHistoryResponse) => { - if (err) { - reject(this.handleError(err)); - } else { - resolve(resp); - } - }); - }); - } - - async cancelCommand( - request: CancelRequest, - md: grpc.Metadata = new grpc.Metadata(), - ): Promise { - return new Promise((resolve, reject) => { - this.commands.cancel(request, md, (err: grpc.ServiceError | undefined, response: CancelResponse | undefined) => { - if (err) { - reject(this.handleError(err)); - } else { - resolve(response!); - } - }); - }); - } - - async getWorkspace(cwd: string): Promise { - return new Promise((resolve, reject) => { - this.workspaces.Get({ - cwd: cwd, - }, new grpc.Metadata(), async (err?: grpc.ServiceError, resp?: Workspace) => { - if (err) { - reject(this.handleError(err)); - } else { - resolve(resp!); - } - }); - }); - } - - async listWorkspaces(refresh: boolean = false): Promise { - return new Promise((resolve, reject) => { - this.workspaces.List({ - refresh: refresh, - }, new grpc.Metadata(), async (err?: grpc.ServiceError, resp?: ListWorkspacesResponse) => { - if (err) { - reject(this.handleError(err)); - } else { - resolve(resp?.workspace); - } - }); - }); - } - - async listExternalWorkspaces(workspace: Workspace): Promise { - return new Promise((resolve, reject) => { - this.externals.ListExternal( - { workspace: workspace }, - new grpc.Metadata(), - { deadline: this.getDeadline() }, - async (err?: grpc.ServiceError, resp?: ExternalListWorkspacesResponse) => { - if (err) { - reject(this.handleError(err)); - } else { - resolve(resp?.workspace); - } - }); - }); - } - - async listPackages(workspace: Workspace, external?: ExternalWorkspace): Promise { - return new Promise((resolve, reject) => { - this.packages.ListPackages({ - workspace: workspace, - externalWorkspace: external, - }, - new grpc.Metadata(), - { deadline: this.getDeadline() }, - async (err?: grpc.ServiceError, resp?: ListPackagesResponse) => { - if (err) { - reject(this.handleError(err)); - } else { - resolve(resp?.package); - } - }); - }); - } - - async listRules(workspace: Workspace, external?: ExternalWorkspace, pkg?: Package): Promise { - return new Promise((resolve, reject) => { - this.packages.ListRules({ - workspace: workspace, - externalWorkspace: external, - package: pkg, - }, new grpc.Metadata(), async (err?: grpc.ServiceError, resp?: ListRulesResponse) => { - if (err) { - reject(this.handleError(err)); - } else { - resolve(resp?.rule); - } - }); - }); - } - - async downloadFile(workspace: Workspace, kind: FileKind, uri: string): Promise { - return new Promise((resolve, reject) => { - this.files.Download({ - label: uri, - kind: kind, - workspace: workspace, - }, new grpc.Metadata(), async (err?: grpc.ServiceError, resp?: FileDownloadResponse) => { - if (err) { - reject(this.handleError(err)); - } else { - resolve(resp); - } - }); - }); - } - - async searchScope(request: ScopedQuery): Promise { - return new Promise((resolve, reject) => { - this.codesearch.Search(request, new grpc.Metadata(), async (err?: grpc.ServiceError, resp?: CodeSearchResult) => { - if (err) { - reject(this.handleError(err)); - } else { - resolve(resp); - } - }); - }); - } - - async createScope(request: CreateScopeRequest, callback: (response: CreateScopeResponse) => void): Promise { - return new Promise((resolve, reject) => { - const stream = this.scopes.Create(request, new grpc.Metadata()); - stream.on('data', (response: CreateScopeResponse) => { - callback(response); - }); - stream.on('metadata', (md: grpc.Metadata) => { - }); - stream.on('error', (err: Error) => { - reject(err.message); - }); - stream.on('end', () => { - resolve(); - }); - }); - } - - async getScope(request: GetScopeRequest): Promise { - return new Promise((resolve, reject) => { - this.scopes.Get(request, new grpc.Metadata(), async (err?: grpc.ServiceError, resp?: Scope) => { - if (err) { - reject(this.handleError(err)); - } else { - resolve(resp); - } - }); - }); - } - - async listScopes(request: ListScopesRequest): Promise { - return new Promise((resolve, reject) => { - this.scopes.List(request, new grpc.Metadata(), async (err?: grpc.ServiceError, resp?: ListScopesResponse) => { - if (err) { - reject(this.handleError(err)); - } else { - resolve(resp); - } - }); - }); - } - -} \ No newline at end of file diff --git a/src/bzl/client.ts b/src/bzl/client.ts index 9378804a..d9f394c8 100644 --- a/src/bzl/client.ts +++ b/src/bzl/client.ts @@ -1,59 +1,350 @@ +import * as grpc from '@grpc/grpc-js'; import * as vscode from 'vscode'; -import { - LanguageClient, - LanguageClientOptions, - ServerOptions -} from 'vscode-languageclient'; -import { Server } from './constants'; +import { ApplicationServiceClient } from '../proto/build/stack/bezel/v1beta1/ApplicationService'; +import { CancelRequest } from '../proto/build/stack/bezel/v1beta1/CancelRequest'; +import { CancelResponse } from '../proto/build/stack/bezel/v1beta1/CancelResponse'; +import { CommandHistory } from '../proto/build/stack/bezel/v1beta1/CommandHistory'; +import { CommandServiceClient } from '../proto/build/stack/bezel/v1beta1/CommandService'; +import { DeleteCommandHistoryResponse } from '../proto/build/stack/bezel/v1beta1/DeleteCommandHistoryResponse'; +import { ExternalListWorkspacesResponse } from '../proto/build/stack/bezel/v1beta1/ExternalListWorkspacesResponse'; +import { ExternalWorkspace } from '../proto/build/stack/bezel/v1beta1/ExternalWorkspace'; +import { ExternalWorkspaceServiceClient } from '../proto/build/stack/bezel/v1beta1/ExternalWorkspaceService'; +import { FileDownloadResponse } from '../proto/build/stack/bezel/v1beta1/FileDownloadResponse'; +import { FileKind } from '../proto/build/stack/bezel/v1beta1/FileKind'; +import { FileServiceClient } from '../proto/build/stack/bezel/v1beta1/FileService'; +import { HistoryClient } from '../proto/build/stack/bezel/v1beta1/History'; +import { LabelKind } from '../proto/build/stack/bezel/v1beta1/LabelKind'; +import { ListCommandHistoryResponse } from '../proto/build/stack/bezel/v1beta1/ListCommandHistoryResponse'; +import { ListPackagesResponse } from '../proto/build/stack/bezel/v1beta1/ListPackagesResponse'; +import { ListRulesResponse } from '../proto/build/stack/bezel/v1beta1/ListRulesResponse'; +import { ListWorkspacesResponse } from '../proto/build/stack/bezel/v1beta1/ListWorkspacesResponse'; +import { Metadata } from '../proto/build/stack/bezel/v1beta1/Metadata'; +import { Package } from '../proto/build/stack/bezel/v1beta1/Package'; +import { PackageServiceClient } from '../proto/build/stack/bezel/v1beta1/PackageService'; +import { ShutdownResponse } from '../proto/build/stack/bezel/v1beta1/ShutdownResponse'; +import { Workspace } from '../proto/build/stack/bezel/v1beta1/Workspace'; +import { WorkspaceServiceClient } from '../proto/build/stack/bezel/v1beta1/WorkspaceService'; +import { CodeSearchClient } from '../proto/build/stack/codesearch/v1beta1/CodeSearch'; +import { CreateScopeRequest } from '../proto/build/stack/codesearch/v1beta1/CreateScopeRequest'; +import { CreateScopeResponse } from '../proto/build/stack/codesearch/v1beta1/CreateScopeResponse'; +import { GetScopeRequest } from '../proto/build/stack/codesearch/v1beta1/GetScopeRequest'; +import { ListScopesRequest } from '../proto/build/stack/codesearch/v1beta1/ListScopesRequest'; +import { ListScopesResponse } from '../proto/build/stack/codesearch/v1beta1/ListScopesResponse'; +import { Scope } from '../proto/build/stack/codesearch/v1beta1/Scope'; +import { ScopedQuery } from '../proto/build/stack/codesearch/v1beta1/ScopedQuery'; +import { ScopesClient } from '../proto/build/stack/codesearch/v1beta1/Scopes'; +import { ProtoGrpcType as BzlProtoGrpcType } from '../proto/bzl'; +import { ProtoGrpcType as CodesearchProtoGrpcType } from '../proto/codesearch'; +import { CodeSearchResult } from '../proto/livegrep/CodeSearchResult'; +import { ButtonName } from './constants'; +import { GRPCClient } from './grpcclient'; +export interface BzlCodesearch { + createScope(request: CreateScopeRequest, callback: (response: CreateScopeResponse) => void): Promise; + searchScope(request: ScopedQuery): Promise; + listScopes(request: ListScopesRequest): Promise; + getScope(request: GetScopeRequest): Promise; +} -/** - * Client implementation to the Bzl Server Process. - */ -export class BzlServerProcess implements vscode.Disposable { +export class BzlClient extends GRPCClient implements BzlCodesearch { + private readonly app: ApplicationServiceClient; + private readonly externals: ExternalWorkspaceServiceClient; + private readonly workspaces: WorkspaceServiceClient; + private readonly packages: PackageServiceClient; + public readonly commands: CommandServiceClient; // server-streaming + private readonly history: HistoryClient; + private readonly files: FileServiceClient; + public readonly scopes: ScopesClient; // server-streaming + private readonly codesearch: CodeSearchClient; + public metadata: Metadata | undefined; + public isRemoteClient: boolean = false; - private disposables: vscode.Disposable[] = []; - private client: LanguageClient; + constructor( + public readonly executable: string, + readonly bzlProtos: BzlProtoGrpcType, + readonly codesearchProtos: CodesearchProtoGrpcType, + readonly address: string, + private onDidRequestRestart?: vscode.EventEmitter, + ) { + super(address); - constructor(executable: string, command: string[]) { - let serverOptions: ServerOptions = { - command: executable, - args: command, - }; + const v1beta1 = bzlProtos.build.stack.bezel.v1beta1; + const creds = this.getCredentials(address); + this.app = this.add(new v1beta1.ApplicationService(address, creds, { + 'grpc.initial_reconnect_backoff_ms': 200, + })); + this.externals = this.add(new v1beta1.ExternalWorkspaceService(address, creds)); + this.workspaces = this.add(new v1beta1.WorkspaceService(address, creds)); + this.packages = this.add(new v1beta1.PackageService(address, creds)); + this.commands = this.add(new v1beta1.CommandService(address, creds)); + this.history = this.add(new v1beta1.History(address, creds)); + this.files = this.add(new v1beta1.FileService(address, creds)); + this.scopes = this.add(new codesearchProtos.build.stack.codesearch.v1beta1.Scopes(address, creds)); + this.codesearch = this.add(new codesearchProtos.build.stack.codesearch.v1beta1.CodeSearch(address, creds)); + } - let clientOptions: LanguageClientOptions = { - // Register the server for all documents to keep it running - documentSelector: [{ pattern: '**/*' }] - }; + httpURL(): string { + const address = this.address; + const scheme = address.endsWith(':443') ? 'https' : 'http'; + return `${scheme}://${address}`; + } - // Create the language client and start the client. - this.client = new LanguageClient( - Server.BinaryName, - Server.Description, - serverOptions, - clientOptions - ); + async waitForReady(seconds: number = 3): Promise { + return this.getMetadata(true, seconds); } - public start() { - this.disposables.push(this.client.start()); + protected handleErrorUnavailable(err: grpc.ServiceError): grpc.ServiceError { + // if metadata object not exists we might still be in the "starting the + // bzl server" phase. + if (!this.metadata) { + return err; + } + if (this.onDidRequestRestart) { + vscode.window.showWarningMessage( + `The server at ${this.address} is unavailable. Would you like to restart?`, + ButtonName.Yes, ButtonName.NoThanks, + ).then(response => { + if (response === ButtonName.Yes) { + this.onDidRequestRestart!.fire(); + } + }); + } else { + vscode.window.showWarningMessage( + `The server at ${this.address} is unavailable. Please check that the tcp connection is still valid.`, + ); + } + return err; } - public async onReady(): Promise { - return this.client.onReady(); + async getMetadata(waitForReady = false, deadlineSeconds = 30): Promise { + return new Promise((resolve, reject) => { + this.app.GetMetadata( + {}, + new grpc.Metadata({ waitForReady: waitForReady }), + { deadline: this.getDeadline(deadlineSeconds) }, + (err?: grpc.ServiceError, resp?: Metadata) => { + if (err) { + reject(this.handleError(err)); + } else { + this.metadata = resp; + resolve(resp); + } + }); + }); } - public getLanguageClientForTesting(): LanguageClient { - return this.client; + async restart(): Promise { + return this.shutdown(true); } - public dispose() { - if (this.client) { - this.client.stop(); - } - for (const disposable of this.disposables) { - disposable.dispose(); - } + async shutdown(restart: boolean = false): Promise { + return new Promise((resolve, reject) => { + this.app.Shutdown( + { restart: restart }, + new grpc.Metadata(), + { deadline: this.getDeadline() }, + (err?: grpc.ServiceError, resp?: ShutdownResponse) => { + if (err) { + reject(this.handleError(err)); + } else { + resolve(resp); + } + }); + }); } -} + async listHistory(cwd: string): Promise { + return new Promise((resolve, reject) => { + this.history.List( + { cwd }, + new grpc.Metadata(), + { deadline: this.getDeadline() }, + async (err?: grpc.ServiceError, resp?: ListCommandHistoryResponse) => { + if (err) { + reject(this.handleError(err)); + } else { + resolve(resp?.history); + } + }); + }); + } + + async deleteCommandHistoryById(id: string): Promise { + return new Promise((resolve, reject) => { + this.history.Delete( + { id: id }, + new grpc.Metadata(), + { deadline: this.getDeadline() }, + async (err?: grpc.ServiceError, resp?: DeleteCommandHistoryResponse) => { + if (err) { + reject(this.handleError(err)); + } else { + resolve(resp); + } + }); + }); + } + + async cancelCommand( + request: CancelRequest, + md: grpc.Metadata = new grpc.Metadata(), + ): Promise { + return new Promise((resolve, reject) => { + this.commands.cancel(request, md, (err: grpc.ServiceError | undefined, response: CancelResponse | undefined) => { + if (err) { + reject(this.handleError(err)); + } else { + resolve(response!); + } + }); + }); + } + + async getWorkspace(cwd: string): Promise { + return new Promise((resolve, reject) => { + this.workspaces.Get({ + cwd: cwd, + }, new grpc.Metadata(), async (err?: grpc.ServiceError, resp?: Workspace) => { + if (err) { + reject(this.handleError(err)); + } else { + resolve(resp!); + } + }); + }); + } + + async listWorkspaces(refresh: boolean = false): Promise { + return new Promise((resolve, reject) => { + this.workspaces.List({ + refresh: refresh, + }, new grpc.Metadata(), async (err?: grpc.ServiceError, resp?: ListWorkspacesResponse) => { + if (err) { + reject(this.handleError(err)); + } else { + resolve(resp?.workspace); + } + }); + }); + } + + async listExternalWorkspaces(workspace: Workspace): Promise { + return new Promise((resolve, reject) => { + this.externals.ListExternal( + { workspace: workspace }, + new grpc.Metadata(), + { deadline: this.getDeadline() }, + async (err?: grpc.ServiceError, resp?: ExternalListWorkspacesResponse) => { + if (err) { + reject(this.handleError(err)); + } else { + resolve(resp?.workspace); + } + }); + }); + } + + async listPackages(workspace: Workspace, external?: ExternalWorkspace): Promise { + return new Promise((resolve, reject) => { + this.packages.ListPackages({ + workspace: workspace, + externalWorkspace: external, + }, + new grpc.Metadata(), + { deadline: this.getDeadline() }, + async (err?: grpc.ServiceError, resp?: ListPackagesResponse) => { + if (err) { + reject(this.handleError(err)); + } else { + resolve(resp?.package); + } + }); + }); + } + + async listRules(workspace: Workspace, external?: ExternalWorkspace, pkg?: Package): Promise { + return new Promise((resolve, reject) => { + this.packages.ListRules({ + workspace: workspace, + externalWorkspace: external, + package: pkg, + }, new grpc.Metadata(), async (err?: grpc.ServiceError, resp?: ListRulesResponse) => { + if (err) { + reject(this.handleError(err)); + } else { + resolve(resp?.rule); + } + }); + }); + } + + async downloadFile(workspace: Workspace, kind: FileKind, uri: string): Promise { + return new Promise((resolve, reject) => { + this.files.Download({ + label: uri, + kind: kind, + workspace: workspace, + }, new grpc.Metadata(), async (err?: grpc.ServiceError, resp?: FileDownloadResponse) => { + if (err) { + reject(this.handleError(err)); + } else { + resolve(resp); + } + }); + }); + } + + async searchScope(request: ScopedQuery): Promise { + return new Promise((resolve, reject) => { + this.codesearch.Search(request, new grpc.Metadata(), async (err?: grpc.ServiceError, resp?: CodeSearchResult) => { + if (err) { + reject(this.handleError(err)); + } else { + resolve(resp); + } + }); + }); + } + + async createScope(request: CreateScopeRequest, callback: (response: CreateScopeResponse) => void): Promise { + return new Promise((resolve, reject) => { + const stream = this.scopes.Create(request, new grpc.Metadata()); + stream.on('data', (response: CreateScopeResponse) => { + callback(response); + }); + stream.on('metadata', (md: grpc.Metadata) => { + }); + stream.on('error', (err: Error) => { + reject(err.message); + }); + stream.on('end', () => { + resolve(); + }); + }); + } + + async getScope(request: GetScopeRequest): Promise { + return new Promise((resolve, reject) => { + this.scopes.Get(request, new grpc.Metadata(), async (err?: grpc.ServiceError, resp?: Scope) => { + if (err) { + reject(this.handleError(err)); + } else { + resolve(resp); + } + }); + }); + } + + async listScopes(request: ListScopesRequest): Promise { + return new Promise((resolve, reject) => { + this.scopes.List(request, new grpc.Metadata(), async (err?: grpc.ServiceError, resp?: ListScopesResponse) => { + if (err) { + reject(this.handleError(err)); + } else { + resolve(resp); + } + }); + }); + } + +} \ No newline at end of file diff --git a/src/bzl/codesearch/codelens.ts b/src/bzl/codesearch/codelens.ts index 26854089..98209634 100644 --- a/src/bzl/codesearch/codelens.ts +++ b/src/bzl/codesearch/codelens.ts @@ -11,7 +11,7 @@ import { CreateScopeRequest } from '../../proto/build/stack/codesearch/v1beta1/C import { CreateScopeResponse } from '../../proto/build/stack/codesearch/v1beta1/CreateScopeResponse'; import { Scope } from '../../proto/build/stack/codesearch/v1beta1/Scope'; import { Query } from '../../proto/livegrep/Query'; -import { BzlCodesearch } from '../bzlclient'; +import { BzlCodesearch } from '../client'; import { CodesearchConfiguration } from '../configuration'; import { CommandName } from '../constants'; import { OutputChannelName, PanelTitle, QueryOptions } from './constants'; diff --git a/src/bzl/codesearch/codesearch.ts b/src/bzl/codesearch/codesearch.ts index 62aa2fd5..baf71c70 100644 --- a/src/bzl/codesearch/codesearch.ts +++ b/src/bzl/codesearch/codesearch.ts @@ -1,7 +1,7 @@ import * as vscode from 'vscode'; import { ICommandCodeLensProviderRegistry } from '../../api'; import { Workspace } from '../../proto/build/stack/bezel/v1beta1/Workspace'; -import { BzlClient } from '../bzlclient'; +import { BzlClient } from '../client'; import { CodesearchConfiguration } from '../configuration'; import { CodeSearchCodeLens } from './codelens'; diff --git a/src/bzl/commandrunner.ts b/src/bzl/commandrunner.ts index 8a505e84..c2a57b3c 100644 --- a/src/bzl/commandrunner.ts +++ b/src/bzl/commandrunner.ts @@ -11,7 +11,7 @@ import { RunResponse } from '../proto/build/stack/bezel/v1beta1/RunResponse'; import { BuildEvent as BuildEventStreamEvent } from '../proto/build_event_stream/BuildEvent'; import { BuildEvent } from '../proto/google/devtools/build/v1/BuildEvent'; import { OrderedBuildEvent } from '../proto/google/devtools/build/v1/OrderedBuildEvent'; -import { BzlClient } from './bzlclient'; +import { BzlClient } from './client'; import { CommandTaskConfiguration } from './configuration'; import { MatcherName } from './constants'; import path = require('path'); diff --git a/src/bzl/feature.ts b/src/bzl/feature.ts index c014107d..06f98e84 100644 --- a/src/bzl/feature.ts +++ b/src/bzl/feature.ts @@ -1,8 +1,7 @@ import * as vscode from 'vscode'; import { API } from '../api'; import { IExtensionFeature } from '../common'; -import { BzlClient, Closeable } from './bzlclient'; -import { BzlServerProcess } from './client'; +import { BzlClient } from './client'; import { CodeSearch } from './codesearch/codesearch'; import { BzlServerCommandRunner } from './commandrunner'; import { @@ -20,6 +19,9 @@ import { loadNucleateProtos } from './configuration'; import { ConfigSection, Server, ViewName } from './constants'; +import { Closeable } from './grpcclient'; +import { BzlLicenseRenewer } from './renewer'; +import { BzlServer } from './server'; import { EmptyView } from './view/emptyview'; import { BuildEventProtocolView } from './view/events'; import { BzlCommandHistoryView } from './view/history'; @@ -38,13 +40,22 @@ export class BzlFeature implements IExtensionFeature, vscode.Disposable { private disposables: vscode.Disposable[] = []; private closeables: Closeable[] = []; private client: BzlClient | undefined; - private server: BzlServerProcess | undefined; + private server: BzlServer | undefined; private onDidBzlClientChange = new vscode.EventEmitter(); + private onDidServerDoNotRestart = new vscode.EventEmitter(); + private onDidBzlLicenseExpire = new vscode.EventEmitter(); + private onDidBzlLicenseTokenChange = new vscode.EventEmitter(); constructor(private api: API) { this.add(this.onDidBzlClientChange); + this.add(this.onDidServerDoNotRestart); + this.add(this.onDidBzlLicenseExpire); + this.add(this.onDidBzlLicenseTokenChange); } + /** + * @override + */ async activate(ctx: vscode.ExtensionContext, config: vscode.WorkspaceConfiguration): Promise { const cfg = await createBzlConfiguration(ctx.asAbsolutePath.bind(ctx), ctx.globalStoragePath, config); this.setupStackBuildActivity(ctx, cfg); @@ -55,10 +66,18 @@ export class BzlFeature implements IExtensionFeature, vscode.Disposable { new EmptyView(ViewName.Workspace, this.disposables); new EmptyView(ViewName.Package, this.disposables); new EmptyView(ViewName.History, this.disposables); - new EmptyView(ViewName.BEP, this.disposables); + new EmptyView(ViewName.BEP, this.disposables); return; } + this.onDidBzlLicenseTokenChange.event(async (newToken) => { + // This only occurs when the license is renewed. The update will + // trigger re-activation of the feature + if (token !== newToken) { + await config.update(ConfigSection.LicenseToken, newToken, vscode.ConfigurationTarget.Global); + } + }); + cfg.server.command.push(Server.LicenseTokenFlag); cfg.server.command.push(token); @@ -118,26 +137,33 @@ export class BzlFeature implements IExtensionFeature, vscode.Disposable { )); this.add(new CodeSearch( - this.api, + this.api, cfg.codesearch, repositoryListView.onDidChangeCurrentRepository.event, this.onDidBzlClientChange.event, )); + this.add(this.onDidServerDoNotRestart.event(msg => { + // Expect this string if the server dies three times and is not + // restarted. + if (msg.indexOf('Please obtain a new license') !== -1) { + this.onDidBzlLicenseExpire.fire(); + } + })); + return this.tryConnectServer(cfg.server, 0); } async tryConnectServer(cfg: BzlServerConfiguration, attempts: number): Promise { if (attempts > 3) { - return Promise.reject(`could not connect to bzl: too many failed attempts to ${cfg.address}, giving up.`); + return Promise.reject(`could not connect to bzl: too many failed attempts to ${cfg.address}. Server will not be restarted.`); } - try { const metadata = await this.client!.waitForReady(); this.onDidBzlClientChange.fire(this.client!); console.debug(`Connected to bzl ${metadata.version} at ${cfg.address}`); } catch (e) { - console.log('connect error', e); + console.log('bzl server connect error', e); return this.restartServer(cfg, ++attempts); } } @@ -147,9 +173,14 @@ export class BzlFeature implements IExtensionFeature, vscode.Disposable { this.server.dispose(); this.server = undefined; } - const server = this.server = this.add(new BzlServerProcess(cfg.executable, cfg.command)); + + const server = this.server = this.add( + new BzlServer( + this.onDidServerDoNotRestart, cfg.executable, cfg.command)); + server.start(); await server.onReady(); + console.debug(`Started bzl (${cfg.executable})`); return this.tryConnectServer(cfg, attempts); @@ -174,7 +205,10 @@ export class BzlFeature implements IExtensionFeature, vscode.Disposable { this.closeables.push(licenseClient); this.disposables.push(new BzlSignup(ctx.extensionPath, cfg.license, authClient, licenseClient, plansClient, subscriptionsClient)); - this.disposables.push(new BzlAccountView(cfg.license.token, licenseClient)); + this.disposables.push(new BzlAccountView(this.onDidBzlLicenseTokenChange, licenseClient)); + this.disposables.push(new BzlLicenseRenewer(this.onDidBzlLicenseExpire, this.onDidBzlLicenseTokenChange, licenseClient)); + + this.onDidBzlLicenseTokenChange.fire(cfg.license.token); } public deactivate() { @@ -196,6 +230,9 @@ export class BzlFeature implements IExtensionFeature, vscode.Disposable { return disposable; } + /** + * @override + */ public dispose() { for (const closeable of this.closeables) { closeable.close(); diff --git a/src/bzl/grpcclient.ts b/src/bzl/grpcclient.ts new file mode 100644 index 00000000..433a5a4e --- /dev/null +++ b/src/bzl/grpcclient.ts @@ -0,0 +1,59 @@ +import * as grpc from '@grpc/grpc-js'; +import * as vscode from 'vscode'; + +export interface Closeable { + close(): void; +} + +export class GRPCClient implements vscode.Disposable { + private disposables: vscode.Disposable[] = []; + private closeables: Closeable[] = []; + + constructor( + readonly address: string, + protected defaultDeadlineSeconds = 30, + ) { + } + + protected getCredentials(address: string): grpc.ChannelCredentials { + if (address.endsWith(':443')) { + return grpc.credentials.createSsl(); + } + return grpc.credentials.createInsecure(); + } + + protected getDeadline(seconds?: number): grpc.Deadline { + const deadline = new Date(); + deadline.setSeconds(deadline.getSeconds() + + (seconds || this.defaultDeadlineSeconds)); + return deadline; + } + + protected handleError(err: grpc.ServiceError): grpc.ServiceError { + if (err.code === grpc.status.UNAVAILABLE) { + return this.handleErrorUnavailable(err); + } + return err; + } + + protected handleErrorUnavailable(err: grpc.ServiceError): grpc.ServiceError { + return err; + } + + protected add(client: T): T { + this.closeables.push(client); + return client; + } + + public dispose() { + for (const closeable of this.closeables) { + closeable.close(); + } + this.closeables.length = 0; + for (const disposable of this.disposables) { + disposable.dispose(); + } + this.disposables.length = 0; + } + +} diff --git a/src/bzl/renewer.ts b/src/bzl/renewer.ts new file mode 100644 index 00000000..2df4b522 --- /dev/null +++ b/src/bzl/renewer.ts @@ -0,0 +1,74 @@ +import * as grpc from '@grpc/grpc-js'; +import * as vscode from 'vscode'; +import { Telemetry } from '../constants'; +import { Container } from '../container'; +import { License } from '../proto/build/stack/license/v1beta1/License'; +import { LicensesClient } from '../proto/build/stack/license/v1beta1/Licenses'; +import { RenewLicenseResponse } from '../proto/build/stack/license/v1beta1/RenewLicenseResponse'; + +/** + * BzlLicenseRenewer listens for expiration events, attempts to renew the + * license, and emits the updated license token if successful. + */ +export class BzlLicenseRenewer implements vscode.Disposable { + private disposables: vscode.Disposable[] = []; + private currentToken: string = ''; + + constructor( + // input signal when the license has expired + onDidBzlLicenseExpire: vscode.EventEmitter, + // output signal to call when license has been renewed/changed. + // string argument is the license token. + private onDidBzlLicenseTokenChange: vscode.EventEmitter, + private client: LicensesClient, + ) { + this.disposables.push(onDidBzlLicenseExpire.event(() => this.renew())); + this.disposables.push(onDidBzlLicenseTokenChange.event(token => { + this.currentToken = token; + })); + } + + private async renew(): Promise { + const btnRenew = 'Renew License'; + const input = await vscode.window.showWarningMessage( + 'The bzl license has expired. Confirm to renew.', + btnRenew + ); + if (input === btnRenew) { + return this.doRenew(); + } + } + + private async doRenew(): Promise { + if (!this.currentToken) { + return Promise.reject('license token must be defined'); + } + + return new Promise((resolve, reject) => { + const req = { + currentToken: this.currentToken, + }; + this.client.Renew(req, new grpc.Metadata(), async (err?: grpc.ServiceError, resp?: RenewLicenseResponse) => { + if (err) { + reject(err); + vscode.window.showErrorMessage( + `Bzl license renewal failed: ${err.message} (${err.code})`); + Container.telemetry.sendTelemetryEvent(Telemetry.LicenseRenewFailed); + return; + } + this.onDidBzlLicenseTokenChange.fire(resp?.newToken!); + Container.telemetry.sendTelemetryEvent(Telemetry.LicenseRenewSuccess); + vscode.window.showInformationMessage( + `License successfully renewed for ${resp?.license?.name} (${resp?.license?.subscriptionName})`); + resolve(resp?.license); + }); + }); + } + + public dispose() { + for (const disposable of this.disposables) { + disposable.dispose(); + } + } + +} diff --git a/src/bzl/server.ts b/src/bzl/server.ts new file mode 100644 index 00000000..fcdf09ab --- /dev/null +++ b/src/bzl/server.ts @@ -0,0 +1,156 @@ +import * as vscode from 'vscode'; +import * as vlc from 'vscode-languageclient'; +import { Server } from './constants'; + + +/** + * Manager of the Bzl Server Process. Implements vscode.OutputChannel to + * intercept the process output which is needed to detect the failure condition + * by string matching. This is suboptimal, but the LanguageClient does not seem + * to provide a way to inspect the exit code from a failing child process. + */ +export class BzlServer implements vscode.Disposable, vscode.OutputChannel { + /** + * The human-readable name of this output channel. + * @override + */ + readonly name: string = 'Bzl Server'; + + // List of disposables. + private disposables: vscode.Disposable[] = []; + // LanguageClient. This does the child process management. + private client: vlc.LanguageClient; + // Number of times the server has been restarted + private restarts: number = 0; + // the backing output channel + private output: vscode.OutputChannel; + // a string buffer that holds output content while the server is failing. + private lastOutputBuffer = ''; + + constructor( + private onDidServerDoNotRestart: vscode.EventEmitter, + executable: string, + command: string[], + ) { + this.output = vscode.window.createOutputChannel(this.name); + this.disposables.push(this.output); + + let serverOptions: vlc.ServerOptions = { + command: executable, + args: command, + }; + + let clientOptions: vlc.LanguageClientOptions = { + // Register the server for all documents to always keep it running + documentSelector: [{ pattern: '**/*' }], + errorHandler: this, + outputChannel: this, + }; + + this.client = new vlc.LanguageClient( + Server.BinaryName, + Server.Description, + serverOptions, + clientOptions + ); + } + + public start() { + this.disposables.push(this.client.start()); + } + + public async onReady(): Promise { + return this.client.onReady(); + } + + /** + * An error has occurred while writing or reading from the connection. + * @override + * + * @param error - the error received + * @param message - the message to be delivered to the server if know. + * @param count - a count indicating how often an error is received. Will + * be reset if a message got successfully send or received. + */ + error(error: Error, message: vlc.Message, count: number): vlc.ErrorAction { + // NOTE: for some reason the 'error' method is not being called. + console.log(`vlc error: ${error.message} (count=${count})`, error, message); + if (count < 5) { + return vlc.ErrorAction.Continue; + } + return vlc.ErrorAction.Shutdown; + } + + /** + * Callback when the connection to the server got closed. + * @override + */ + closed(): vlc.CloseAction { + if (this.restarts++ < 3) { + this.lastOutputBuffer = ''; + return vlc.CloseAction.Restart; + } + this.onDidServerDoNotRestart.fire(this.lastOutputBuffer.replace('\n', '')); + return vlc.CloseAction.DoNotRestart; + } + + /** + * @override + */ + public dispose() { + if (this.client) { + this.client.stop(); + } + for (const disposable of this.disposables) { + disposable.dispose(); + } + } + + /** + * Append the given value to the channel. + * + * @param value A string, falsy values will not be printed. + * @override + */ + append(value: string): void { + if (this.restarts) { + this.lastOutputBuffer += value; + } + this.output.append(value); + } + + /** + * Append the given value and a line feed character + * to the channel. + * + * @param value A string, falsy values will be printed. + * @override + */ + appendLine(value: string): void { + this.output.appendLine(value); + } + + /** + * Removes all output from the channel. + * @override + */ + clear(): void { + this.output.clear(); + } + + show(columnOrPreserveFocus?: vscode.ViewColumn | boolean, preserveFocus?: boolean): void { + this.output.show(preserveFocus); + } + + /** + * Hide this channel from the UI. + * @override + */ + hide(): void { + this.output.hide(); + } + + public getLanguageClientForTesting(): vlc.LanguageClient { + return this.client; + } +} diff --git a/src/bzl/view/bzlclienttreedataprovider.ts b/src/bzl/view/bzlclienttreedataprovider.ts index 853ba958..19e98452 100644 --- a/src/bzl/view/bzlclienttreedataprovider.ts +++ b/src/bzl/view/bzlclienttreedataprovider.ts @@ -1,5 +1,5 @@ import * as vscode from 'vscode'; -import { BzlClient } from '../bzlclient'; +import { BzlClient } from '../client'; import { GrpcTreeDataProvider } from './grpctreedataprovider'; /** diff --git a/src/bzl/view/events.ts b/src/bzl/view/events.ts index 21ce3f8a..7af11c87 100644 --- a/src/bzl/view/events.ts +++ b/src/bzl/view/events.ts @@ -20,7 +20,7 @@ import { TargetConfigured } from '../../proto/build_event_stream/TargetConfigure import { TestResult } from '../../proto/build_event_stream/TestResult'; import { WorkspaceConfig } from '../../proto/build_event_stream/WorkspaceConfig'; import { FailureDetail } from '../../proto/failure_details/FailureDetail'; -import { BzlClient } from '../bzlclient'; +import { BzlClient } from '../client'; import { BazelBuildEvent } from '../commandrunner'; import { ButtonName, CommandName, ContextValue, DiagnosticCollectionName, ruleClassIconUri, ThemeIconCloudDownload, ThemeIconDebugStackframe, ThemeIconDebugStackframeFocused, ThemeIconReport, ThemeIconSymbolEvent, ThemeIconSymbolInterface, ViewName } from '../constants'; import { BzlClientTreeDataProvider } from './bzlclienttreedataprovider'; diff --git a/src/bzl/view/history.ts b/src/bzl/view/history.ts index a73f42d1..ad9b625a 100644 --- a/src/bzl/view/history.ts +++ b/src/bzl/view/history.ts @@ -10,7 +10,7 @@ import { RunRequest } from '../../proto/build/stack/bezel/v1beta1/RunRequest'; import { RunResponse } from '../../proto/build/stack/bezel/v1beta1/RunResponse'; import { Workspace } from '../../proto/build/stack/bezel/v1beta1/Workspace'; import { Timestamp } from '../../proto/google/protobuf/Timestamp'; -import { BzlClient } from '../bzlclient'; +import { BzlClient } from '../client'; import { CommandTaskRunner } from '../commandrunner'; import { CommandName, ContextValue, FileName, ThemeIconCircleOutline, ThemeIconDebugContinue, ThemeIconDebugStackframe, ThemeIconDebugStart, ThemeIconQuestion, ViewName } from '../constants'; import { BzlClientTreeDataProvider } from './bzlclienttreedataprovider'; diff --git a/src/bzl/view/license.ts b/src/bzl/view/license.ts index aeb95bae..b1ab82f8 100644 --- a/src/bzl/view/license.ts +++ b/src/bzl/view/license.ts @@ -16,12 +16,16 @@ import { GrpcTreeDataProvider } from './grpctreedataprovider'; */ export class BzlAccountView extends GrpcTreeDataProvider { private license: License | undefined; + private currentToken: string = ''; constructor( - private token: string, + onDidBzlLicenseTokenChange: vscode.EventEmitter, private client: LicensesClient, ) { super(ViewName.Account); + this.disposables.push(onDidBzlLicenseTokenChange.event(token => { + this.currentToken = token; + })); } async getRootItems(): Promise { @@ -36,7 +40,7 @@ export class BzlAccountView extends GrpcTreeDataProvider { if (this.license) { return Promise.resolve(this.license); } - if (!this.token) { + if (!this.currentToken) { await setContextGrpcStatusValue(ExtensionName, ViewName.Account, { name: 'Invalid token configuration', code: grpc.status.FAILED_PRECONDITION, @@ -51,7 +55,7 @@ export class BzlAccountView extends GrpcTreeDataProvider { return new Promise((resolve, reject) => { const req = { - currentToken: this.token, + currentToken: this.currentToken, }; this.client.Renew(req, new grpc.Metadata(), async (err?: grpc.ServiceError, resp?: RenewLicenseResponse) => { await setContextGrpcStatusValue(ExtensionName, ViewName.Account, err); diff --git a/src/bzl/view/packages.ts b/src/bzl/view/packages.ts index f378b803..00991cf6 100644 --- a/src/bzl/view/packages.ts +++ b/src/bzl/view/packages.ts @@ -11,7 +11,7 @@ import { Package } from '../../proto/build/stack/bezel/v1beta1/Package'; import { RunRequest } from '../../proto/build/stack/bezel/v1beta1/RunRequest'; import { RunResponse } from '../../proto/build/stack/bezel/v1beta1/RunResponse'; import { Workspace } from '../../proto/build/stack/bezel/v1beta1/Workspace'; -import { BzlClient } from '../bzlclient'; +import { BzlClient } from '../client'; import { CommandTaskRunner } from '../commandrunner'; import { getLabelAbsolutePath, LabelParts, splitLabel } from '../configuration'; import { diff --git a/src/bzl/view/repositories.ts b/src/bzl/view/repositories.ts index d5412046..4830365e 100644 --- a/src/bzl/view/repositories.ts +++ b/src/bzl/view/repositories.ts @@ -4,7 +4,7 @@ import * as vscode from 'vscode'; import { BuiltInCommands, Telemetry } from '../../constants'; import { Container, MediaIconName } from '../../container'; import { Workspace } from '../../proto/build/stack/bezel/v1beta1/Workspace'; -import { BzlClient } from '../bzlclient'; +import { BzlClient } from '../client'; import { CommandName, ContextValue, FileName, ViewName } from '../constants'; import { BzlClientTreeDataProvider } from './bzlclienttreedataprovider'; const slash = require('slash'); diff --git a/src/bzl/view/server.ts b/src/bzl/view/server.ts index da9b6ca5..e6ecd729 100644 --- a/src/bzl/view/server.ts +++ b/src/bzl/view/server.ts @@ -6,7 +6,7 @@ import { BuiltInCommands } from '../../constants'; import { MultiStepInput } from '../../multiStepInput'; import { ProtoGrpcType } from '../../proto/bzl'; import { ProtoGrpcType as CodesearchProtoGrpcType } from '../../proto/codesearch'; -import { BzlClient } from '../bzlclient'; +import { BzlClient } from '../client'; import { CommandName, ContextValue, ThemeIconDebugStackframeActive, ThemeIconDebugStackframeFocused, ViewName } from '../constants'; import { BzlClientTreeDataProvider } from './bzlclienttreedataprovider'; import Long = require('long'); diff --git a/src/bzl/view/signup.ts b/src/bzl/view/signup.ts index bb4b5f82..248af04b 100644 --- a/src/bzl/view/signup.ts +++ b/src/bzl/view/signup.ts @@ -450,7 +450,9 @@ export class BzlGetStarted implements vscode.Disposable { lead: '

Getting your subscription details...

', }); - const flow = new RenewLicenseFlow(this.licensesClient, jwt, + const flow = new RenewLicenseFlow( + this.licensesClient, + jwt, () => this.tryListPlans(jwt), () => this.tryListPlans(jwt), (license, token) => this.saveToken(license, token), diff --git a/src/bzl/view/workspaces.ts b/src/bzl/view/workspaces.ts index f9d12519..7dab3de7 100644 --- a/src/bzl/view/workspaces.ts +++ b/src/bzl/view/workspaces.ts @@ -5,7 +5,7 @@ import { BuiltInCommands, Telemetry } from '../../constants'; import { Container, MediaIconName } from '../../container'; import { ExternalWorkspace } from '../../proto/build/stack/bezel/v1beta1/ExternalWorkspace'; import { Workspace } from '../../proto/build/stack/bezel/v1beta1/Workspace'; -import { BzlClient } from '../bzlclient'; +import { BzlClient } from '../client'; import { CommandName, ContextValue, ViewName } from '../constants'; import { BzlClientTreeDataProvider } from './bzlclienttreedataprovider'; diff --git a/src/constants.ts b/src/constants.ts index 1f336ded..a89a4a25 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -25,6 +25,8 @@ export enum Telemetry { SignupSuccess = 'signup.success', SignupError = 'signup.error', BzlRunTask = 'bzl.runTask', + LicenseRenewSuccess = 'license.renew.success', + LicenseRenewFailed = 'license.renew.failed', BzlWorkspaceList = 'bzl.workspace.list', BzlRepositoryList = 'bzl.workspace.list', diff --git a/src/test/integration/feature.bzl.codesearch.test.ts b/src/test/integration/feature.bzl.codesearch.test.ts index ec27cf58..2440e0ba 100644 --- a/src/test/integration/feature.bzl.codesearch.test.ts +++ b/src/test/integration/feature.bzl.codesearch.test.ts @@ -2,7 +2,7 @@ import { expect, use as chaiUse } from 'chai'; import { afterEach, beforeEach, describe, it } from 'mocha'; -import { BzlCodesearch } from '../../bzl/bzlclient'; +import { BzlCodesearch } from '../../bzl/client'; import { CodeSearchCodeLens, CodesearchIndexOptions, OutputChannel } from '../../bzl/codesearch/codelens'; import { CodesearchRenderer } from '../../bzl/codesearch/renderer'; import { CommandName } from '../../bzl/constants'; diff --git a/src/test/integration/feature.bzl.test.ts b/src/test/integration/feature.bzl.test.ts index 58e4bd59..3cbe2876 100644 --- a/src/test/integration/feature.bzl.test.ts +++ b/src/test/integration/feature.bzl.test.ts @@ -6,9 +6,10 @@ import * as grpc from '@grpc/grpc-js'; import { expect } from 'chai'; import { after, before, describe, it } from 'mocha'; -import { BzlServerProcess } from '../../bzl/client'; +import * as vscode from 'vscode'; import { BzlServerConfiguration, loadBzlProtos, loadLicenseProtos, setServerAddresses, setServerExecutable } from '../../bzl/configuration'; import { BzlFeatureName } from '../../bzl/feature'; +import { BzlServer } from '../../bzl/server'; import { License } from '../../proto/build/stack/license/v1beta1/License'; import { ProtoGrpcType } from '../../proto/bzl'; import { ProtoGrpcType as LicenseProtoGrpcType } from '../../proto/license'; @@ -28,7 +29,7 @@ describe(BzlFeatureName, function () { this.timeout(120 * 1000); let downloadDir: string; - let server: BzlServerProcess; + let server: BzlServer; let serverConfig: BzlServerConfiguration; let proto: ProtoGrpcType; @@ -51,8 +52,9 @@ describe(BzlFeatureName, function () { await setServerAddresses(serverConfig); proto = loadBzlProtos(serverConfig.protofile); - - server = new BzlServerProcess( + const onDidServerDoNotRestart = new vscode.EventEmitter(); + server = new BzlServer( + onDidServerDoNotRestart, serverConfig.executable, serverConfig.command.concat(['--base_dir', downloadDir])); server.start();