Merge remote queries webview outline into main (#1027)

This commit is contained in:
Shati Patel
2021-12-03 10:48:54 +00:00
committed by GitHub
parent 749565828d
commit 7e6483490a
14 changed files with 562 additions and 20 deletions

View File

@@ -37,6 +37,6 @@ export function watchTypeScript() {
/** Copy CSS files for the results view into the output directory. */
export function copyViewCss() {
return gulp.src('src/view/*.css')
return gulp.src('src/**/view/*.css')
.pipe(gulp.dest('out'));
}

View File

@@ -6,6 +6,7 @@ export const config: webpack.Configuration = {
entry: {
resultsView: './src/view/results.tsx',
compareView: './src/compare/view/Compare.tsx',
remoteQueriesView: './src/remote-queries/view/RemoteQueries.tsx',
},
output: {
path: path.resolve(__dirname, '..', 'out'),

View File

@@ -284,6 +284,10 @@
"command": "codeQL.runRemoteQuery",
"title": "CodeQL: Run Remote Query"
},
{
"command": "codeQL.openRemoteQueriesView",
"title": "CodeQL: Open Remote Queries View"
},
{
"command": "codeQL.runQueries",
"title": "CodeQL: Run Queries in Selected Files"
@@ -746,6 +750,10 @@
"command": "codeQL.runRemoteQuery",
"when": "config.codeQL.canary && editorLangId == ql && resourceExtname == .ql"
},
{
"command": "codeQL.openRemoteQueriesView",
"when": "config.codeQL.canary"
},
{
"command": "codeQL.runQueries",
"when": "false"

View File

@@ -74,7 +74,8 @@ import {
import { CodeQlStatusBarHandler } from './status-bar';
import { Credentials } from './authentication';
import { runRemoteQuery } from './run-remote-query';
import { runRemoteQuery } from './remote-queries/run-remote-query';
import { RemoteQueriesInterfaceManager } from './remote-queries/remote-queries-interface';
/**
* extension.ts
@@ -501,23 +502,23 @@ async function activateWithInstalledDistribution(
selectedQuery: Uri
): Promise<void> {
// selectedQuery is unpopulated when executing through the command palette
const pathToQhelp = selectedQuery ? selectedQuery.fsPath : window.activeTextEditor?.document.uri.fsPath;
if(pathToQhelp) {
const pathToQhelp = selectedQuery ? selectedQuery.fsPath : window.activeTextEditor?.document.uri.fsPath;
if (pathToQhelp) {
// Create temporary directory
const relativePathToMd = path.basename(pathToQhelp, '.qhelp') + '.md';
const absolutePathToMd = path.join(qhelpTmpDir.name, relativePathToMd);
const uri = Uri.file(absolutePathToMd);
try {
await cliServer.generateQueryHelp(pathToQhelp , absolutePathToMd);
await cliServer.generateQueryHelp(pathToQhelp, absolutePathToMd);
await commands.executeCommand('markdown.showPreviewToSide', uri);
} catch (err) {
const errorMessage = err.message.includes('Generating qhelp in markdown') ? (
const errorMessage = err.message.includes('Generating qhelp in markdown') ? (
`Could not generate markdown from ${pathToQhelp}: Bad formatting in .qhelp file.`
) : `Could not open a preview of the generated file (${absolutePathToMd}).`;
void helpers.showAndLogErrorMessage(errorMessage, { fullMessage: `${errorMessage}\n${err}` });
}
}
}
async function openReferencedFile(
@@ -743,6 +744,14 @@ async function activateWithInstalledDistribution(
}
)
);
void logger.log('Initializing remote queries panel interface.');
const rmpm = new RemoteQueriesInterfaceManager(
ctx,
logger
);
ctx.subscriptions.push(rmpm);
// The "runRemoteQuery" command is internal-only.
ctx.subscriptions.push(
commandRunnerWithProgress('codeQL.runRemoteQuery', async (

View File

@@ -364,3 +364,23 @@ export interface ParsedResultSets {
resultSetNames: string[];
resultSet: ResultSet;
}
export type FromRemoteQueriesMessage =
| RemoteQueryLoadedMessage
| RemoteQueryErrorMessage;
export type ToRemoteQueriesMessage =
| OpenRemoteQueriesViewMessage;
export interface RemoteQueryLoadedMessage {
t: 'remoteQueryLoaded';
}
export interface OpenRemoteQueriesViewMessage {
t: 'openRemoteQueriesView';
}
export interface RemoteQueryErrorMessage {
t: 'remoteQueryError';
error: string;
}

View File

@@ -0,0 +1,134 @@
import { DisposableObject } from '../pure/disposable-object';
import {
WebviewPanel,
ExtensionContext,
window as Window,
ViewColumn,
Uri,
} from 'vscode';
import * as path from 'path';
import { tmpDir } from '../run-queries';
import {
ToRemoteQueriesMessage,
FromRemoteQueriesMessage,
} from '../pure/interface-types';
import { Logger } from '../logging';
import { getHtmlForWebview } from '../interface-utils';
import { assertNever } from '../pure/helpers-pure';
import { commandRunner } from '../commandRunner';
export class RemoteQueriesInterfaceManager extends DisposableObject {
private panel: WebviewPanel | undefined;
private panelLoaded = false;
private panelLoadedCallBacks: (() => void)[] = [];
constructor(
private ctx: ExtensionContext,
private logger: Logger,
) {
super();
commandRunner('codeQL.openRemoteQueriesView', () => this.handleOpenRemoteQueriesView());
this.panelLoadedCallBacks.push(() => {
void logger.log('Remote queries view loaded');
});
}
async showResults() {
this.getPanel().reveal(undefined, true);
await this.waitForPanelLoaded();
await this.postMessage({
t: 'openRemoteQueriesView',
});
}
getPanel(): WebviewPanel {
if (this.panel == undefined) {
const { ctx } = this;
const panel = (this.panel = Window.createWebviewPanel(
'remoteQueriesView',
'Remote Query Results',
{ viewColumn: ViewColumn.Active, preserveFocus: true },
{
enableScripts: true,
enableFindWidget: true,
retainContextWhenHidden: true,
localResourceRoots: [
Uri.file(tmpDir.name),
Uri.file(path.join(this.ctx.extensionPath, 'out')),
],
}
));
this.panel.onDidDispose(
() => {
this.panel = undefined;
},
null,
ctx.subscriptions
);
const scriptPathOnDisk = Uri.file(
ctx.asAbsolutePath('out/remoteQueriesView.js')
);
const stylesheetPathOnDisk = Uri.file(
ctx.asAbsolutePath('out/remote-queries/view/remoteQueries.css')
);
panel.webview.html = getHtmlForWebview(
panel.webview,
scriptPathOnDisk,
stylesheetPathOnDisk
);
panel.webview.onDidReceiveMessage(
async (e) => this.handleMsgFromView(e),
undefined,
ctx.subscriptions
);
}
return this.panel;
}
private waitForPanelLoaded(): Promise<void> {
return new Promise((resolve) => {
if (this.panelLoaded) {
resolve();
} else {
this.panelLoadedCallBacks.push(resolve);
}
});
}
private async handleMsgFromView(
msg: FromRemoteQueriesMessage
): Promise<void> {
switch (msg.t) {
case 'remoteQueryLoaded':
this.panelLoaded = true;
this.panelLoadedCallBacks.forEach((cb) => cb());
this.panelLoadedCallBacks = [];
break;
case 'remoteQueryError':
void this.logger.log(
`Remote query error: ${msg.error}`
);
break;
default:
assertNever(msg);
}
}
private postMessage(msg: ToRemoteQueriesMessage): Thenable<boolean> {
return this.getPanel().webview.postMessage(msg);
}
async handleOpenRemoteQueriesView() {
this.getPanel().reveal(undefined, true);
await this.waitForPanelLoaded();
}
}

View File

@@ -3,14 +3,16 @@ import * as path from 'path';
import * as yaml from 'js-yaml';
import * as fs from 'fs-extra';
import * as tmp from 'tmp-promise';
import { askForLanguage, findLanguage, getOnDiskWorkspaceFolders, showAndLogErrorMessage, showAndLogInformationMessage, showInformationMessageWithAction } from './helpers';
import { Credentials } from './authentication';
import * as cli from './cli';
import { logger } from './logging';
import { getRemoteControllerRepo, getRemoteRepositoryLists, setRemoteControllerRepo } from './config';
import { tmpDir } from './run-queries';
import { ProgressCallback, UserCancellationException } from './commandRunner';
import { askForLanguage, findLanguage, getOnDiskWorkspaceFolders, showAndLogErrorMessage, showAndLogInformationMessage, showAndLogWarningMessage, showInformationMessageWithAction } from '../helpers';
import { Credentials } from '../authentication';
import * as cli from '../cli';
import { logger } from '../logging';
import { getRemoteControllerRepo, getRemoteRepositoryLists, setRemoteControllerRepo } from '../config';
import { tmpDir } from '../run-queries';
import { ProgressCallback, UserCancellationException } from '../commandRunner';
import { OctokitResponse } from '@octokit/types/dist-types';
import * as unzipper from 'unzipper';
interface Config {
repositories: string[];
ref?: string;
@@ -347,7 +349,6 @@ async function runRemoteQueriesApiRequest(
queryPackBase64: string,
dryRun = false
): Promise<void> {
if (dryRun) {
void showAndLogInformationMessage('[DRY RUN] Would have sent request. See extension log for the payload.');
void logger.log(JSON.stringify({ ref, language, repositories, owner, repo, queryPackBase64: queryPackBase64.substring(0, 100) + '... ' + queryPackBase64.length + ' bytes' }));
@@ -369,8 +370,8 @@ async function runRemoteQueriesApiRequest(
}
}
);
void showAndLogInformationMessage(`Successfully scheduled runs. [Click here to see the progress](https://github.com/${owner}/${repo}/actions/runs/${response.data.workflow_run_id}).`);
const workflowRunId = response.data.workflow_run_id;
void showAndLogInformationMessage(`Successfully scheduled runs. [Click here to see the progress](https://github.com/${owner}/${repo}/actions/runs/${workflowRunId}).`);
} catch (error) {
await attemptRerun(error, credentials, ref, language, repositories, owner, repo, queryPackBase64, dryRun);
}
@@ -440,3 +441,118 @@ async function ensureNameAndSuite(queryPackDir: string, packRelativePath: string
}];
await fs.writeFile(packPath, yaml.safeDump(qlpack));
}
/**
* Lists the workflow run artifacts for the given workflow run ID.
* @param credentials Credentials for authenticating to the GitHub API.
* @param owner
* @param repo
* @param workflowRunId The ID of the workflow run to list artifacts for.
* @returns An array of artifact details (including artifact name and ID).
*/
async function listWorkflowRunArtifacts(
credentials: Credentials,
owner: string,
repo: string,
workflowRunId: number
) {
const octokit = await credentials.getOctokit();
const response = await octokit.rest.actions.listWorkflowRunArtifacts({
owner,
repo,
run_id: workflowRunId,
});
return response.data.artifacts;
}
/**
* @param artifactName The artifact name, as a string.
* @param artifacts An array of artifact details (from the "list workflow run artifacts" API response).
* @returns The artifact ID corresponding to the given artifact name.
*/
function getArtifactIDfromName(artifactName: string, artifacts: Array<{ id: number, name: string }>): number | undefined {
const artifact = artifacts.find(a => a.name === artifactName);
return artifact?.id;
}
/**
* Downloads an artifact from a workflow run.
* @param credentials Credentials for authenticating to the GitHub API.
* @param owner
* @param repo
* @param artifactId The ID of the artifact to download.
* @returns The path to the enclosing directory of the unzipped artifact.
*/
async function downloadArtifact(
credentials: Credentials,
owner: string,
repo: string,
artifactId: number
): Promise<string> {
const octokit = await credentials.getOctokit();
const response = await octokit.rest.actions.downloadArtifact({
owner,
repo,
artifact_id: artifactId,
archive_format: 'zip',
});
const artifactPath = path.join(tmpDir.name, `${artifactId}`);
void logger.log(`Downloading artifact to ${artifactPath}.zip`);
await fs.writeFile(
`${artifactPath}.zip`,
Buffer.from(response.data as ArrayBuffer)
);
void logger.log(`Extracting artifact to ${artifactPath}`);
await (
await unzipper.Open.file(`${artifactPath}.zip`)
).extract({ path: artifactPath });
return artifactPath;
}
interface ResultIndexItem {
nwo: string;
id: string;
results_count: number;
bqrs_file_size: number;
sarif_file_size?: number;
}
/**
* Gets the result index file for a given remote queries run.
* @param credentials Credentials for authenticating to the GitHub API.
* @param owner
* @param repo
* @param workflowRunId The ID of the workflow run to get the result index for.
* @returns An object containing the result index.
*/
export async function getResultIndex(
credentials: Credentials,
owner: string,
repo: string,
workflowRunId: number
): Promise<ResultIndexItem[]> {
const artifactList = await listWorkflowRunArtifacts(credentials, owner, repo, workflowRunId);
const artifactId = getArtifactIDfromName('result-index', artifactList);
if (!artifactId) {
void showAndLogWarningMessage(
`Could not find a result index for the [specified workflow](https://github.com/${owner}/${repo}/actions/runs/${workflowRunId}).
Please check whether the workflow run has successfully completed.`
);
return [];
}
const artifactPath = await downloadArtifact(credentials, owner, repo, artifactId);
const indexFilePath = path.join(artifactPath, 'index.json');
if (!(await fs.pathExists(indexFilePath))) {
void showAndLogWarningMessage('Could not find an `index.json` file in the result artifact.');
return [];
}
const resultIndex = await fs.readFile(path.join(artifactPath, 'index.json'), 'utf8');
try {
return JSON.parse(resultIndex);
} catch (error) {
throw new Error(`Invalid result index file: ${error}`);
}
}

View File

@@ -0,0 +1,13 @@
module.exports = {
env: {
browser: true
},
extends: [
"plugin:react/recommended"
],
settings: {
react: {
version: 'detect'
}
}
}

View File

@@ -0,0 +1,113 @@
import * as React from 'react';
import * as Rdom from 'react-dom';
import * as octicons from '../../view/octicons';
import { vscode } from '../../view/vscode-api';
interface AnalysisResult {
nwo: string,
resultCount: number,
downloadLink: string,
fileSize: string,
}
interface Props {
queryTitle: string;
queryFile: string;
totalRepositoryCount: number;
totalResultCount: number;
executionTimestamp: string;
executionDuration: string;
downloadLink: string;
results: AnalysisResult[]
}
const AnalysisResult = (props: AnalysisResult) => (
<span>
<span className="vscode-codeql__analysis-item">{octicons.repo}</span>
<span className="vscode-codeql__analysis-item">{props.nwo}</span>
<span className="vscode-codeql__analysis-item vscode-codeql__badge-container">
<span className="vscode-codeql__badge">{props.resultCount}</span>
</span>
<span className="vscode-codeql__analysis-item">
<a
className="vscode-codeql__download-link"
href={props.downloadLink}>
{octicons.download}{props.fileSize}
</a>
</span>
</span>
);
export function RemoteQueries(props: Props): JSX.Element {
return <div className="vscode-codeql__remote-queries-view">
<h1 className="vscode-codeql__query-title">{props.queryTitle}</h1>
<p className="vscode-codeql__paragraph">
{props.totalResultCount} results in {props.totalRepositoryCount} repositories
({props.executionDuration}), {props.executionTimestamp}
</p>
<p className="vscode-codeql__paragraph">
<span className="vscode-codeql__query-file">{octicons.file} <span>{props.queryFile}</span></span>
<span>{octicons.codeSquare} <span>query</span></span>
</p>
<div className="vscode-codeql__query-summary-container">
<h2 className="vscode-codeql__query-summary-title">Repositories with results ({props.totalRepositoryCount}):</h2>
<a className="vscode-codeql__summary-download-link vscode-codeql__download-link" href={props.downloadLink}>
{octicons.download}Download all
</a>
</div>
<ul className="vscode-codeql__results-list">
{props.results.map(result =>
<li key={result.nwo} className="vscode-codeql__results-list-item">
<AnalysisResult {...result} />
</li>
)}
</ul>
</div>;
}
const formatDate = (d: Date): string => {
const datePart = d.toLocaleDateString(undefined, { day: 'numeric', month: 'short' });
const timePart = d.toLocaleTimeString(undefined, { hour: 'numeric', minute: 'numeric', hour12: true });
return `${datePart} at ${timePart}`;
};
const data: Props = {
queryTitle: 'Empty block',
queryFile: 'example.ql',
totalRepositoryCount: 13,
totalResultCount: 72,
executionTimestamp: formatDate(new Date()),
executionDuration: '0.6 seconds',
downloadLink: 'www.example.com',
results: [
{
nwo: 'github/foo',
resultCount: 35,
downloadLink: 'www.example.com',
fileSize: '12.3mb'
},
{
nwo: 'github/bar',
resultCount: 9,
downloadLink: 'www.example.com',
fileSize: '10.1mb'
},
{
nwo: 'github/baz',
resultCount: 80,
downloadLink: 'www.example.com',
fileSize: '11.2mb'
}
]
};
Rdom.render(
<RemoteQueries {...data} />,
document.getElementById('root'),
// Post a message to the extension when fully loaded.
() => vscode.postMessage({ t: 'remoteQueryLoaded' })
);

View File

@@ -0,0 +1,90 @@
.vscode-codeql__remote-queries-view {
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial,
sans-serif, Apple Color Emoji, Segoe UI Emoji;
}
.vscode-codeql__paragraph {
margin: 0.5em 0 0.5em 0;
}
.vscode-codeql__download-link {
display: inline-block;
font-size: x-small;
text-decoration: none;
}
.vscode-codeql__download-link svg {
fill: var(--vscode-textLink-foreground);
}
.vscode-codeql__query-title {
font-size: large;
margin-bottom: 0.5em;
font-weight: 500;
}
.octicon {
fill: var(--vscode-editor-foreground);
height: 1.2em;
width: 1.2em;
vertical-align: middle;
display: inline-block;
}
.octicon-light {
opacity: 0.6;
}
.vscode-codeql__query-file {
padding-right: 1em;
}
.vscode-codeql__query-summary-container {
padding-top: 1.5em;
}
.vscode-codeql__query-summary-title {
font-size: medium;
font-weight: 500;
padding: 0 0.5em 0 0;
margin: 0;
display: inline-block;
vertical-align: middle;
}
.vscode-codeql__summary-download-link {
vertical-align: middle;
}
.vscode-codeql__results-list {
list-style-type: none;
margin: 0;
padding: 0.5em 0 0 0;
}
.vscode-codeql__results-list-item {
margin-top: 0.5em;
}
.vscode-codeql__analysis-item {
padding-right: 0.1em;
}
.vscode-codeql__badge-container {
justify-content: center;
align-items: center;
min-height: 100vh;
padding-left: 0.2em;
}
.vscode-codeql__badge {
display: inline-block;
min-width: 1.5em;
padding: 0.3em;
border-radius: 35%;
font-size: x-small;
text-align: center;
background: var(--vscode-badge-background);
color: var(--vscode-badge-foreground);
border-color: var(--vscode-badge-background);
}

View File

@@ -0,0 +1,18 @@
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "node",
"target": "es6",
"outDir": "out",
"lib": ["es6", "dom"],
"jsx": "react",
"sourceMap": true,
"rootDir": "..",
"strict": true,
"noUnusedLocals": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"experimentalDecorators": true
},
"exclude": ["node_modules"]
}

View File

@@ -20,3 +20,23 @@ export const listUnordered = <svg className="octicon octicon-light" width="16" h
export const info = <svg className="octicon octicon-light" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" >
<path fillRule="evenodd" clipRule="evenodd" d="M8.568 1.03a6.8 6.8 0 0 1 4.192 2.02 7.06 7.06 0 0 1 .46 9.39 6.85 6.85 0 0 1-8.58 1.74 7 7 0 0 1-3.12-3.5 7.12 7.12 0 0 1-.23-4.71 7 7 0 0 1 2.77-3.79 6.8 6.8 0 0 1 4.508-1.15zm.472 12.85a5.89 5.89 0 0 0 3.41-2.07 6.07 6.07 0 0 0-.4-8.06 5.82 5.82 0 0 0-7.43-.74 6.06 6.06 0 0 0 .5 10.29 5.81 5.81 0 0 0 3.92.58zM8.51 7h-1v4h1V7zm0-2h-1v1h1V5z" />
</svg>;
/**
* The icons below come from https://primer.style/octicons/
*/
export const file = <svg className="octicon octicon-light" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fillRule="evenodd" d="M3.75 1.5a.25.25 0 00-.25.25v11.5c0 .138.112.25.25.25h8.5a.25.25 0 00.25-.25V6H9.75A1.75 1.75 0 018 4.25V1.5H3.75zm5.75.56v2.19c0 .138.112.25.25.25h2.19L9.5 2.06zM2 1.75C2 .784 2.784 0 3.75 0h5.086c.464 0 .909.184 1.237.513l3.414 3.414c.329.328.513.773.513 1.237v8.086A1.75 1.75 0 0112.25 15h-8.5A1.75 1.75 0 012 13.25V1.75z"></path>
</svg>;
export const codeSquare = <svg className="octicon octicon-light" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fillRule="evenodd" d="M1.75 1.5a.25.25 0 00-.25.25v12.5c0 .138.112.25.25.25h12.5a.25.25 0 00.25-.25V1.75a.25.25 0 00-.25-.25H1.75zM0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v12.5A1.75 1.75 0 0114.25 16H1.75A1.75 1.75 0 010 14.25V1.75zm9.22 3.72a.75.75 0 000 1.06L10.69 8 9.22 9.47a.75.75 0 101.06 1.06l2-2a.75.75 0 000-1.06l-2-2a.75.75 0 00-1.06 0zM6.78 6.53a.75.75 0 00-1.06-1.06l-2 2a.75.75 0 000 1.06l2 2a.75.75 0 101.06-1.06L5.31 8l1.47-1.47z"></path>
</svg>;
export const repo = <svg className="octicon octicon-light" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fillRule="evenodd" d="M2 2.5A2.5 2.5 0 014.5 0h8.75a.75.75 0 01.75.75v12.5a.75.75 0 01-.75.75h-2.5a.75.75 0 110-1.5h1.75v-2h-8a1 1 0 00-.714 1.7.75.75 0 01-1.072 1.05A2.495 2.495 0 012 11.5v-9zm10.5-1V9h-8c-.356 0-.694.074-1 .208V2.5a1 1 0 011-1h8zM5 12.25v3.25a.25.25 0 00.4.2l1.45-1.087a.25.25 0 01.3 0L8.6 15.7a.25.25 0 00.4-.2v-3.25a.25.25 0 00-.25-.25h-3.5a.25.25 0 00-.25.25z"></path>
</svg>;
export const download = <svg className="octicon octicon-light" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fillRule="evenodd" d="M7.47 10.78a.75.75 0 001.06 0l3.75-3.75a.75.75 0 00-1.06-1.06L8.75 8.44V1.75a.75.75 0 00-1.5 0v6.69L4.78 5.97a.75.75 0 00-1.06 1.06l3.75 3.75zM3.75 13a.75.75 0 000 1.5h8.5a.75.75 0 000-1.5h-8.5z"></path>
</svg>;

View File

@@ -1,10 +1,10 @@
import { FromCompareViewMessage, FromResultsViewMsg } from '../pure/interface-types';
import { FromCompareViewMessage, FromRemoteQueriesMessage, FromResultsViewMsg } from '../pure/interface-types';
export interface VsCodeApi {
/**
* Post message back to vscode extension.
*/
postMessage(msg: FromResultsViewMsg | FromCompareViewMessage): void;
postMessage(msg: FromResultsViewMsg | FromCompareViewMessage | FromRemoteQueriesMessage): void;
}
declare const acquireVsCodeApi: () => VsCodeApi;

View File

@@ -7,7 +7,7 @@ import * as fs from 'fs-extra';
import * as os from 'os';
import * as yaml from 'js-yaml';
import { QlPack, runRemoteQuery } from '../../run-remote-query';
import { QlPack, runRemoteQuery } from '../../remote-queries/run-remote-query';
import { Credentials } from '../../authentication';
import { CliVersionConstraint, CodeQLCliServer } from '../../cli';
import { CodeQLExtensionInterface } from '../../extension';