Store extension packs in .github/codeql/extensions
This commit is contained in:
@@ -22,7 +22,7 @@ import {
|
||||
} from "./extension-pack-name";
|
||||
import {
|
||||
askForWorkspaceFolder,
|
||||
autoPickWorkspaceFolder,
|
||||
autoPickExtensionsDirectory,
|
||||
} from "./extensions-workspace-folder";
|
||||
|
||||
const maxStep = 3;
|
||||
@@ -319,9 +319,9 @@ async function autoCreateExtensionPack(
|
||||
extensionPacksInfo: QlpacksInfo,
|
||||
logger: NotificationLogger,
|
||||
): Promise<ExtensionPack | undefined> {
|
||||
// Choose a workspace folder to create the extension pack in
|
||||
const workspaceFolder = await autoPickWorkspaceFolder(language);
|
||||
if (!workspaceFolder) {
|
||||
// Get the extensions directory to create the extension pack in
|
||||
const extensionsDirectory = await autoPickExtensionsDirectory();
|
||||
if (!extensionsDirectory) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -380,7 +380,7 @@ async function autoCreateExtensionPack(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const packPath = join(workspaceFolder.uri.fsPath, packName.name);
|
||||
const packPath = join(extensionsDirectory.fsPath, packName.name);
|
||||
|
||||
if (await pathExists(packPath)) {
|
||||
void showAndLogErrorMessage(
|
||||
|
||||
@@ -1,35 +1,123 @@
|
||||
import { window, WorkspaceFolder } from "vscode";
|
||||
import { Uri, window, workspace, WorkspaceFolder } from "vscode";
|
||||
import { getOnDiskWorkspaceFoldersObjects } from "../common/vscode/workspace-folders";
|
||||
import { extLogger } from "../common";
|
||||
|
||||
/**
|
||||
* Returns the ancestors of this path in order from furthest to closest (i.e. root of filesystem to parent directory)
|
||||
*/
|
||||
function getAncestors(uri: Uri): Uri[] {
|
||||
const ancestors: Uri[] = [];
|
||||
let current = uri;
|
||||
while (current.fsPath !== Uri.joinPath(current, "..").fsPath) {
|
||||
ancestors.push(current);
|
||||
current = Uri.joinPath(current, "..");
|
||||
}
|
||||
|
||||
// The ancestors are now in order from closest to furthest, so reverse them
|
||||
ancestors.reverse();
|
||||
|
||||
return ancestors;
|
||||
}
|
||||
|
||||
function getRootWorkspaceDirectory(): Uri | undefined {
|
||||
// If there is a valid workspace file, just use its directory as the directory for the extensions
|
||||
const workspaceFile = workspace.workspaceFile;
|
||||
if (workspaceFile?.scheme === "file") {
|
||||
return Uri.joinPath(workspaceFile, "..");
|
||||
}
|
||||
|
||||
export async function autoPickWorkspaceFolder(
|
||||
language: string,
|
||||
): Promise<WorkspaceFolder | undefined> {
|
||||
const workspaceFolders = getOnDiskWorkspaceFoldersObjects();
|
||||
|
||||
// If there's only 1 workspace folder, use that
|
||||
// Find the common root directory of all workspace folders by finding the longest common prefix
|
||||
const commonRoot = workspaceFolders.reduce((commonRoot, folder) => {
|
||||
const folderUri = folder.uri;
|
||||
const ancestors = getAncestors(folderUri);
|
||||
|
||||
const minLength = Math.min(commonRoot.length, ancestors.length);
|
||||
let commonLength = 0;
|
||||
for (let i = 0; i < minLength; i++) {
|
||||
if (commonRoot[i].fsPath === ancestors[i].fsPath) {
|
||||
commonLength++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return commonRoot.slice(0, commonLength);
|
||||
}, getAncestors(workspaceFolders[0].uri));
|
||||
|
||||
if (commonRoot.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// The path closest to the workspace folders is the last element of the common root
|
||||
const commonRootUri = commonRoot[commonRoot.length - 1];
|
||||
|
||||
// If we are at the root of the filesystem, we can't go up any further and there's something
|
||||
// wrong, so just return undefined
|
||||
if (commonRootUri.fsPath === Uri.joinPath(commonRootUri, "..").fsPath) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return commonRootUri;
|
||||
}
|
||||
|
||||
export async function autoPickExtensionsDirectory(): Promise<Uri | undefined> {
|
||||
const workspaceFolders = getOnDiskWorkspaceFoldersObjects();
|
||||
|
||||
// If there's only 1 workspace folder, use the `.github/codeql/extensions` directory in that folder
|
||||
if (workspaceFolders.length === 1) {
|
||||
return workspaceFolders[0];
|
||||
return Uri.joinPath(
|
||||
workspaceFolders[0].uri,
|
||||
".github",
|
||||
"codeql",
|
||||
"extensions",
|
||||
);
|
||||
}
|
||||
|
||||
// In the vscode-codeql-starter repository, all workspace folders are named "codeql-custom-queries-<language>",
|
||||
// so we can use that to find the workspace folder for the language
|
||||
const starterWorkspaceFolderForLanguage = workspaceFolders.find(
|
||||
(folder) => folder.name === `codeql-custom-queries-${language}`,
|
||||
// Now try to find a workspace folder for which the path ends in `.github/codeql/extensions`
|
||||
const workspaceFolderForExtensions = workspaceFolders.find((folder) =>
|
||||
// Using path instead of fsPath because path always uses forward slashes
|
||||
folder.uri.path.endsWith(".github/codeql/extensions"),
|
||||
);
|
||||
if (starterWorkspaceFolderForLanguage) {
|
||||
return starterWorkspaceFolderForLanguage;
|
||||
if (workspaceFolderForExtensions) {
|
||||
return workspaceFolderForExtensions.uri;
|
||||
}
|
||||
|
||||
// Otherwise, try to find one that ends with "-<language>"
|
||||
const workspaceFolderForLanguage = workspaceFolders.find((folder) =>
|
||||
folder.name.endsWith(`-${language}`),
|
||||
// Get the root workspace directory, i.e. the common root directory of all workspace folders
|
||||
const rootDirectory = getRootWorkspaceDirectory();
|
||||
if (!rootDirectory) {
|
||||
void extLogger.log("Unable to determine root workspace directory");
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// We'll create a new workspace folder for the extensions in the root workspace directory
|
||||
// at `.github/codeql/extensions`
|
||||
const extensionsUri = Uri.joinPath(
|
||||
rootDirectory,
|
||||
".github",
|
||||
"codeql",
|
||||
"extensions",
|
||||
);
|
||||
if (workspaceFolderForLanguage) {
|
||||
return workspaceFolderForLanguage;
|
||||
|
||||
if (
|
||||
!workspace.updateWorkspaceFolders(
|
||||
workspace.workspaceFolders?.length ?? 0,
|
||||
0,
|
||||
{
|
||||
name: "CodeQL Extension Packs",
|
||||
uri: extensionsUri,
|
||||
},
|
||||
)
|
||||
) {
|
||||
void extLogger.log(
|
||||
`Failed to add workspace folder for extensions at ${extensionsUri.fsPath}`,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// If we can't find one, just ask the user
|
||||
return askForWorkspaceFolder();
|
||||
return extensionsUri;
|
||||
}
|
||||
|
||||
export async function askForWorkspaceFolder(): Promise<
|
||||
|
||||
@@ -328,13 +328,25 @@ describe("pickExtensionPackModelFile", () => {
|
||||
index: 1,
|
||||
},
|
||||
{
|
||||
uri: Uri.file(tmpDir.path),
|
||||
uri: Uri.joinPath(Uri.file(tmpDir.path), "codeql-custom-queries-java"),
|
||||
name: "codeql-custom-queries-java",
|
||||
index: 2,
|
||||
},
|
||||
]);
|
||||
jest
|
||||
.spyOn(workspace, "workspaceFile", "get")
|
||||
.mockReturnValue(
|
||||
Uri.joinPath(Uri.file(tmpDir.path), "workspace.code-workspace"),
|
||||
);
|
||||
jest.spyOn(workspace, "updateWorkspaceFolders").mockReturnValue(true);
|
||||
|
||||
const newPackDir = join(Uri.file(tmpDir.path).fsPath, "vscode-codeql-java");
|
||||
const newPackDir = join(
|
||||
Uri.file(tmpDir.path).fsPath,
|
||||
".github",
|
||||
"codeql",
|
||||
"extensions",
|
||||
"vscode-codeql-java",
|
||||
);
|
||||
|
||||
const cliServer = mockCliServer({}, { models: [], data: {} });
|
||||
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
import { Uri, workspace, WorkspaceFolder } from "vscode";
|
||||
import { dir, DirectoryResult } from "tmp-promise";
|
||||
import { autoPickExtensionsDirectory } from "../../../../src/data-extensions-editor/extensions-workspace-folder";
|
||||
|
||||
describe("autoPickExtensionsDirectory", () => {
|
||||
let tmpDir: DirectoryResult;
|
||||
let rootDirectory: Uri;
|
||||
let extensionsDirectory: Uri;
|
||||
|
||||
let workspaceFoldersSpy: jest.SpyInstance<
|
||||
readonly WorkspaceFolder[] | undefined,
|
||||
[]
|
||||
>;
|
||||
let workspaceFileSpy: jest.SpyInstance<Uri | undefined, []>;
|
||||
let updateWorkspaceFoldersSpy: jest.SpiedFunction<
|
||||
typeof workspace.updateWorkspaceFolders
|
||||
>;
|
||||
|
||||
beforeEach(async () => {
|
||||
tmpDir = await dir({
|
||||
unsafeCleanup: true,
|
||||
});
|
||||
|
||||
rootDirectory = Uri.file(tmpDir.path);
|
||||
extensionsDirectory = Uri.joinPath(
|
||||
rootDirectory,
|
||||
".github",
|
||||
"codeql",
|
||||
"extensions",
|
||||
);
|
||||
|
||||
workspaceFoldersSpy = jest
|
||||
.spyOn(workspace, "workspaceFolders", "get")
|
||||
.mockReturnValue([]);
|
||||
workspaceFileSpy = jest
|
||||
.spyOn(workspace, "workspaceFile", "get")
|
||||
.mockReturnValue(undefined);
|
||||
updateWorkspaceFoldersSpy = jest
|
||||
.spyOn(workspace, "updateWorkspaceFolders")
|
||||
.mockReturnValue(true);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await tmpDir.cleanup();
|
||||
});
|
||||
|
||||
it("when a workspace folder with the correct path exists", async () => {
|
||||
workspaceFoldersSpy.mockReturnValue([
|
||||
{
|
||||
uri: Uri.joinPath(rootDirectory, "codeql-custom-queries-java"),
|
||||
name: "codeql-custom-queries-java",
|
||||
index: 0,
|
||||
},
|
||||
{
|
||||
uri: Uri.joinPath(rootDirectory, "codeql-custom-queries-python"),
|
||||
name: "codeql-custom-queries-python",
|
||||
index: 1,
|
||||
},
|
||||
{
|
||||
uri: extensionsDirectory,
|
||||
name: "CodeQL Extension Packs",
|
||||
index: 2,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(await autoPickExtensionsDirectory()).toEqual(extensionsDirectory);
|
||||
expect(updateWorkspaceFoldersSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("when a workspace file exists", async () => {
|
||||
workspaceFoldersSpy.mockReturnValue([
|
||||
{
|
||||
uri: Uri.file("/a/b/c"),
|
||||
name: "codeql-custom-queries-java",
|
||||
index: 0,
|
||||
},
|
||||
{
|
||||
uri: Uri.joinPath(rootDirectory, "codeql-custom-queries-python"),
|
||||
name: "codeql-custom-queries-python",
|
||||
index: 1,
|
||||
},
|
||||
]);
|
||||
|
||||
workspaceFileSpy.mockReturnValue(
|
||||
Uri.joinPath(rootDirectory, "workspace.code-workspace"),
|
||||
);
|
||||
|
||||
expect(await autoPickExtensionsDirectory()).toEqual(extensionsDirectory);
|
||||
expect(updateWorkspaceFoldersSpy).toHaveBeenCalledWith(2, 0, {
|
||||
name: "CodeQL Extension Packs",
|
||||
uri: extensionsDirectory,
|
||||
});
|
||||
});
|
||||
|
||||
it("when updating the workspace folders fails", async () => {
|
||||
updateWorkspaceFoldersSpy.mockReturnValue(false);
|
||||
|
||||
workspaceFoldersSpy.mockReturnValue([
|
||||
{
|
||||
uri: Uri.file("/a/b/c"),
|
||||
name: "codeql-custom-queries-java",
|
||||
index: 0,
|
||||
},
|
||||
{
|
||||
uri: Uri.joinPath(rootDirectory, "codeql-custom-queries-python"),
|
||||
name: "codeql-custom-queries-python",
|
||||
index: 1,
|
||||
},
|
||||
]);
|
||||
|
||||
workspaceFileSpy.mockReturnValue(
|
||||
Uri.joinPath(rootDirectory, "workspace.code-workspace"),
|
||||
);
|
||||
|
||||
expect(await autoPickExtensionsDirectory()).toEqual(undefined);
|
||||
});
|
||||
|
||||
it("when a workspace file does not exist and there is a common root directory", async () => {
|
||||
workspaceFoldersSpy.mockReturnValue([
|
||||
{
|
||||
uri: Uri.joinPath(rootDirectory, "codeql-custom-queries-java"),
|
||||
name: "codeql-custom-queries-java",
|
||||
index: 0,
|
||||
},
|
||||
{
|
||||
uri: Uri.joinPath(rootDirectory, "codeql-custom-queries-python"),
|
||||
name: "codeql-custom-queries-python",
|
||||
index: 1,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(await autoPickExtensionsDirectory()).toEqual(extensionsDirectory);
|
||||
expect(updateWorkspaceFoldersSpy).toHaveBeenCalledWith(2, 0, {
|
||||
name: "CodeQL Extension Packs",
|
||||
uri: extensionsDirectory,
|
||||
});
|
||||
});
|
||||
|
||||
it("when a workspace file does not exist and there is no common root directory", async () => {
|
||||
workspaceFoldersSpy.mockReturnValue([
|
||||
{
|
||||
uri: Uri.joinPath(rootDirectory, "codeql-custom-queries-java"),
|
||||
name: "codeql-custom-queries-java",
|
||||
index: 0,
|
||||
},
|
||||
{
|
||||
uri: Uri.file("/a/b/c"),
|
||||
name: "codeql-custom-queries-python",
|
||||
index: 1,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(await autoPickExtensionsDirectory()).toEqual(undefined);
|
||||
expect(updateWorkspaceFoldersSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user