Use generated schema to validate qlpack file
This commit is contained in:
@@ -6,6 +6,16 @@ import { format, resolveConfig } from "prettier";
|
||||
const extensionDirectory = resolve(__dirname, "..");
|
||||
|
||||
const schemas = [
|
||||
{
|
||||
path: join(extensionDirectory, "src", "packaging", "qlpack-file.ts"),
|
||||
type: "QlPackFile",
|
||||
schemaPath: join(
|
||||
extensionDirectory,
|
||||
"src",
|
||||
"packaging",
|
||||
"qlpack-file.schema.json",
|
||||
),
|
||||
},
|
||||
{
|
||||
path: join(
|
||||
extensionDirectory,
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { load } from "js-yaml";
|
||||
import { readFile } from "fs-extra";
|
||||
import { QlPackFile } from "../packaging/qlpack-file";
|
||||
import { QueryLanguage } from "./query-language";
|
||||
import { loadQlpackFile } from "../packaging/qlpack-file-loader";
|
||||
|
||||
/**
|
||||
* @param qlpackPath The path to the `qlpack.yml` or `codeql-pack.yml` file.
|
||||
@@ -11,11 +9,9 @@ import { QueryLanguage } from "./query-language";
|
||||
export async function getQlPackLanguage(
|
||||
qlpackPath: string,
|
||||
): Promise<QueryLanguage | undefined> {
|
||||
const qlPack = load(await readFile(qlpackPath, "utf8")) as
|
||||
| QlPackFile
|
||||
| undefined;
|
||||
const qlPack = await loadQlpackFile(qlpackPath);
|
||||
const dependencies = qlPack?.dependencies;
|
||||
if (!dependencies || typeof dependencies !== "object") {
|
||||
if (!dependencies) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
"ExtensionPackMetadata": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"type": "string"
|
||||
},
|
||||
"extensionTargets": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
@@ -24,12 +30,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"type": "string"
|
||||
},
|
||||
"dependencies": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { QlPackFile } from "../packaging/qlpack-file";
|
||||
|
||||
export type ExtensionPackMetadata = QlPackFile & {
|
||||
// Make both extensionTargets and dataExtensions required
|
||||
// Make name, version, extensionTargets, and dataExtensions required
|
||||
name: string;
|
||||
version: string;
|
||||
extensionTargets: Record<string, string>;
|
||||
dataExtensions: string[] | string;
|
||||
};
|
||||
|
||||
28
extensions/ql-vscode/src/packaging/qlpack-file-loader.ts
Normal file
28
extensions/ql-vscode/src/packaging/qlpack-file-loader.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import Ajv from "ajv";
|
||||
import * as qlpackFileSchemaJson from "./qlpack-file.schema.json";
|
||||
import { QlPackFile } from "./qlpack-file";
|
||||
import { load } from "js-yaml";
|
||||
import { readFile } from "fs-extra";
|
||||
|
||||
const ajv = new Ajv({ allErrors: true });
|
||||
const qlpackFileValidate = ajv.compile(qlpackFileSchemaJson);
|
||||
|
||||
export async function loadQlpackFile(path: string): Promise<QlPackFile> {
|
||||
const qlPack = load(await readFile(path, "utf8")) as QlPackFile | undefined;
|
||||
|
||||
qlpackFileValidate(qlPack);
|
||||
|
||||
if (qlpackFileValidate.errors) {
|
||||
throw new Error(
|
||||
`Invalid extension pack YAML: ${qlpackFileValidate.errors
|
||||
.map((error) => `${error.instancePath} ${error.message}`)
|
||||
.join(", ")}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!qlPack) {
|
||||
throw new Error(`Could not parse ${path}`);
|
||||
}
|
||||
|
||||
return qlPack;
|
||||
}
|
||||
97
extensions/ql-vscode/src/packaging/qlpack-file.schema.json
Normal file
97
extensions/ql-vscode/src/packaging/qlpack-file.schema.json
Normal file
@@ -0,0 +1,97 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$ref": "#/definitions/QlPackFile",
|
||||
"definitions": {
|
||||
"QlPackFile": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"type": "string"
|
||||
},
|
||||
"dependencies": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"extensionTargets": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"dbscheme": {
|
||||
"type": "string"
|
||||
},
|
||||
"library": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"defaultSuite": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/SuiteInstruction"
|
||||
}
|
||||
},
|
||||
"defaultSuiteFile": {
|
||||
"type": "string"
|
||||
},
|
||||
"dataExtensions": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"description": "The qlpack pack file, either in qlpack.yml or in codeql-pack.yml."
|
||||
},
|
||||
"SuiteInstruction": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"qlpack": {
|
||||
"type": "string"
|
||||
},
|
||||
"query": {
|
||||
"type": "string"
|
||||
},
|
||||
"queries": {
|
||||
"type": "string"
|
||||
},
|
||||
"include": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"exclude": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"from": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"description": "A single entry in a .qls file."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,8 @@ import { SuiteInstruction } from "./suite-instruction";
|
||||
* The qlpack pack file, either in qlpack.yml or in codeql-pack.yml.
|
||||
*/
|
||||
export interface QlPackFile {
|
||||
name: string;
|
||||
version: string;
|
||||
name?: string;
|
||||
version?: string;
|
||||
dependencies?: Record<string, string>;
|
||||
extensionTargets?: Record<string, string>;
|
||||
dbscheme?: string;
|
||||
|
||||
@@ -59,24 +59,22 @@ describe("getQlPackLanguage", () => {
|
||||
expect(result).toEqual(undefined);
|
||||
});
|
||||
|
||||
it("should find nothing when dependencies is a scalar", async () => {
|
||||
it("should throw when dependencies is a scalar", async () => {
|
||||
await writeYAML(qlpackPath, {
|
||||
name: "test",
|
||||
dependencies: "codeql/java-all",
|
||||
});
|
||||
|
||||
const result = await getQlPackLanguage(qlpackPath);
|
||||
expect(result).toEqual(undefined);
|
||||
await expect(getQlPackLanguage(qlpackPath)).rejects.toBeDefined();
|
||||
});
|
||||
|
||||
it("should find nothing when dependencies is an array", async () => {
|
||||
it("should throw when dependencies is an array", async () => {
|
||||
await writeYAML(qlpackPath, {
|
||||
name: "test",
|
||||
dependencies: ["codeql/java-all"],
|
||||
});
|
||||
|
||||
const result = await getQlPackLanguage(qlpackPath);
|
||||
expect(result).toEqual(undefined);
|
||||
await expect(getQlPackLanguage(qlpackPath)).rejects.toBeDefined();
|
||||
});
|
||||
|
||||
it("should find nothing when there are no matching dependencies", async () => {
|
||||
|
||||
Reference in New Issue
Block a user