mirror of
https://github.com/github/codeql-action.git
synced 2026-04-03 10:12:17 +00:00
746 lines
21 KiB
TypeScript
Executable File
746 lines
21 KiB
TypeScript
Executable File
#!/usr/bin/env npx tsx
|
|
|
|
import * as fs from "fs";
|
|
import * as path from "path";
|
|
|
|
import * as yaml from "yaml";
|
|
|
|
import { KnownLanguage } from "../src/languages";
|
|
|
|
/** Known workflow input names. */
|
|
enum KnownInputName {
|
|
GoVersion = "go-version",
|
|
JavaVersion = "java-version",
|
|
PythonVersion = "python-version",
|
|
DotnetVersion = "dotnet-version",
|
|
}
|
|
|
|
/**
|
|
* Represents workflow input definitions.
|
|
*/
|
|
interface WorkflowInput {
|
|
type: string;
|
|
description: string;
|
|
required: boolean;
|
|
default: string;
|
|
}
|
|
|
|
/** A partial mapping from known input names to input definitions. */
|
|
type WorkflowInputs = Partial<Record<KnownInputName, WorkflowInput>>;
|
|
|
|
/**
|
|
* Represents PR check specifications.
|
|
*/
|
|
interface Specification extends JobSpecification {
|
|
/** Workflow-level input definitions forwarded to `workflow_dispatch`/`workflow_call`. */
|
|
inputs?: Record<string, WorkflowInput>;
|
|
/** CodeQL bundle versions to test against. Defaults to `DEFAULT_TEST_VERSIONS`. */
|
|
versions?: string[];
|
|
/** Operating system prefixes used to select runner images (e.g. `["ubuntu", "macos"]`). */
|
|
operatingSystems?: string[];
|
|
/** Per-OS version overrides. If specified for an OS, only those versions are tested on that OS. */
|
|
osCodeQlVersions?: Record<string, string[]>;
|
|
/** Whether to use the all-platform CodeQL bundle. */
|
|
useAllPlatformBundle?: string;
|
|
/** Values for the `analysis-kinds` matrix dimension. */
|
|
analysisKinds?: string[];
|
|
|
|
/** Container image configuration for the job. */
|
|
container?: any;
|
|
/** Service containers for the job. */
|
|
services?: any;
|
|
|
|
/** Additional jobs to run after the main PR check job. */
|
|
validationJobs?: Record<string, JobSpecification>;
|
|
|
|
/** If set, this check is part of a named collection that gets its own caller workflow. */
|
|
collection?: string;
|
|
}
|
|
|
|
/** Minimal type to represent steps in Actions workflows. */
|
|
interface Step {
|
|
name?: string;
|
|
[other: string]: any;
|
|
}
|
|
|
|
/** Represents job specifications. */
|
|
interface JobSpecification {
|
|
/** The display name for the check. */
|
|
name: string;
|
|
/** Custom permissions override for the job. */
|
|
permissions?: Record<string, string>;
|
|
/** Extra environment variables for the job. */
|
|
env?: Record<string, any>;
|
|
|
|
/** The workflow steps specific to this check. */
|
|
steps: Step[];
|
|
|
|
installNode?: boolean;
|
|
installGo?: boolean;
|
|
installJava?: boolean;
|
|
installPython?: boolean;
|
|
installDotNet?: boolean;
|
|
installYq?: boolean;
|
|
}
|
|
|
|
/** Describes language/framework-specific steps and inputs. */
|
|
interface LanguageSetup {
|
|
specProperty: keyof JobSpecification;
|
|
/** The names of the known inputs which are required for this setup step. */
|
|
inputs?: KnownInputName[];
|
|
steps: Step[];
|
|
}
|
|
|
|
/** Describes partial mappings from known languages to their specific setup information. */
|
|
type LanguageSetups = Partial<Record<KnownLanguage, LanguageSetup>>;
|
|
|
|
// The default set of CodeQL Bundle versions to use for the PR checks.
|
|
const defaultTestVersions = [
|
|
// The oldest supported CodeQL version. If bumping, update `CODEQL_MINIMUM_VERSION` in `codeql.ts`
|
|
"stable-v2.17.6",
|
|
// The last CodeQL release in the 2.18 series.
|
|
"stable-v2.18.4",
|
|
// The last CodeQL release in the 2.19 series.
|
|
"stable-v2.19.4",
|
|
// The last CodeQL release in the 2.20 series.
|
|
"stable-v2.20.7",
|
|
// The last CodeQL release in the 2.21 series.
|
|
"stable-v2.21.4",
|
|
// The last CodeQL release in the 2.22 series.
|
|
"stable-v2.22.4",
|
|
// The default version of CodeQL for Dotcom, as determined by feature flags.
|
|
"default",
|
|
// The version of CodeQL shipped with the Action in `defaults.json`. During the release process
|
|
// for a new CodeQL release, there will be a period of time during which this will be newer than
|
|
// the default version on Dotcom.
|
|
"linked",
|
|
// A nightly build directly from the our private repo, built in the last 24 hours.
|
|
"nightly-latest",
|
|
];
|
|
|
|
/** The default versions we use for languages / frameworks, if not specified as a workflow input. */
|
|
const defaultLanguageVersions = {
|
|
javascript: "20.x",
|
|
go: ">=1.21.0",
|
|
java: "17",
|
|
python: "3.13",
|
|
csharp: "9.x",
|
|
} as const satisfies Partial<Record<KnownLanguage, string>>;
|
|
|
|
/** A mapping from known input names to their specifications. */
|
|
const inputSpecs: WorkflowInputs = {
|
|
[KnownInputName.GoVersion]: {
|
|
type: "string",
|
|
description: "The version of Go to install",
|
|
required: false,
|
|
default: defaultLanguageVersions.go,
|
|
},
|
|
[KnownInputName.JavaVersion]: {
|
|
type: "string",
|
|
description: "The version of Java to install",
|
|
required: false,
|
|
default: defaultLanguageVersions.java,
|
|
},
|
|
[KnownInputName.PythonVersion]: {
|
|
type: "string",
|
|
description: "The version of Python to install",
|
|
required: false,
|
|
default: defaultLanguageVersions.python,
|
|
},
|
|
[KnownInputName.DotnetVersion]: {
|
|
type: "string",
|
|
description: "The version of .NET to install",
|
|
required: false,
|
|
default: defaultLanguageVersions.csharp,
|
|
},
|
|
};
|
|
|
|
/** Obtains a `WorkflowInputs` object for all the inputs given by `requiredInputs`. */
|
|
function getSetupInputs(requiredInputs: Set<KnownInputName>): WorkflowInputs {
|
|
const inputs: WorkflowInputs = {};
|
|
|
|
// Copy the input specifications for the requested inputs into the output.
|
|
for (const requiredInput of requiredInputs) {
|
|
inputs[requiredInput] = inputSpecs[requiredInput];
|
|
}
|
|
|
|
return inputs;
|
|
}
|
|
|
|
/** A partial mapping from known languages to their specific setup information. */
|
|
const languageSetups: LanguageSetups = {
|
|
javascript: {
|
|
specProperty: "installNode",
|
|
steps: [
|
|
{
|
|
name: "Install Node.js",
|
|
uses: "actions/setup-node@v6",
|
|
with: {
|
|
"node-version": defaultLanguageVersions.javascript,
|
|
cache: "npm",
|
|
},
|
|
},
|
|
{
|
|
name: "Install dependencies",
|
|
run: "npm ci",
|
|
},
|
|
],
|
|
},
|
|
go: {
|
|
specProperty: "installGo",
|
|
inputs: [KnownInputName.GoVersion],
|
|
steps: [
|
|
{
|
|
name: "Install Go",
|
|
uses: "actions/setup-go@v6",
|
|
with: {
|
|
"go-version": `\${{ inputs.go-version || '${defaultLanguageVersions.go}' }}`,
|
|
// to avoid potentially misleading autobuilder results where we expect it to download
|
|
// dependencies successfully, but they actually come from a warm cache
|
|
cache: false,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
java: {
|
|
specProperty: "installJava",
|
|
inputs: [KnownInputName.JavaVersion],
|
|
steps: [
|
|
{
|
|
name: "Install Java",
|
|
uses: "actions/setup-java@v5",
|
|
with: {
|
|
"java-version": `\${{ inputs.java-version || '${defaultLanguageVersions.java}' }}`,
|
|
distribution: "temurin",
|
|
},
|
|
},
|
|
],
|
|
},
|
|
python: {
|
|
specProperty: "installPython",
|
|
inputs: [KnownInputName.PythonVersion],
|
|
steps: [
|
|
{
|
|
name: "Install Python",
|
|
uses: "actions/setup-python@v6",
|
|
with: {
|
|
"python-version": `\${{ inputs.python-version || '${defaultLanguageVersions.python}' }}`,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
csharp: {
|
|
specProperty: "installDotNet",
|
|
inputs: [KnownInputName.DotnetVersion],
|
|
steps: [
|
|
{
|
|
name: "Install .NET",
|
|
uses: "actions/setup-dotnet@v5",
|
|
with: {
|
|
"dotnet-version": `\${{ inputs.dotnet-version || '${defaultLanguageVersions.csharp}' }}`,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
};
|
|
|
|
// This is essentially an arbitrary version of `yq`, which happened to be the one that
|
|
// `choco` fetched when we moved away from using that here.
|
|
// See https://github.com/github/codeql-action/pull/3423
|
|
const YQ_VERSION = "v4.50.1";
|
|
|
|
const THIS_DIR = __dirname;
|
|
const CHECKS_DIR = path.join(THIS_DIR, "checks");
|
|
const OUTPUT_DIR = path.join(THIS_DIR, "..", ".github", "workflows");
|
|
|
|
/**
|
|
* Loads and parses a YAML file.
|
|
*/
|
|
function loadYaml(filePath: string): yaml.Document {
|
|
const content = fs.readFileSync(filePath, "utf8");
|
|
return yaml.parseDocument(content);
|
|
}
|
|
|
|
/** Computes the union of all given `sets`. */
|
|
function unionAll<T>(sets: Array<Set<T>>): Set<T> {
|
|
return sets.reduce((prev, cur) => prev.union(cur), new Set<T>());
|
|
}
|
|
|
|
/**
|
|
* Serialize a value to YAML and write it to a file, prepended with the
|
|
* standard header comment.
|
|
*/
|
|
function writeYaml(filePath: string, workflow: any): void {
|
|
const header = `# Warning: This file is generated automatically, and should not be modified.
|
|
# Instead, please modify the template in the pr-checks directory and run:
|
|
# pr-checks/sync.sh
|
|
# to regenerate this file.
|
|
|
|
`;
|
|
const workflowDoc = new yaml.Document(workflow, {
|
|
aliasDuplicateObjects: false,
|
|
});
|
|
const yamlStr = yaml.stringify(workflowDoc, {
|
|
aliasDuplicateObjects: false,
|
|
singleQuote: true,
|
|
lineWidth: 0,
|
|
});
|
|
fs.writeFileSync(filePath, stripTrailingWhitespace(header + yamlStr), "utf8");
|
|
}
|
|
|
|
/**
|
|
* Strip trailing whitespace from each line.
|
|
*/
|
|
function stripTrailingWhitespace(content: string): string {
|
|
return content
|
|
.split("\n")
|
|
.map((line) => line.trimEnd())
|
|
.join("\n");
|
|
}
|
|
|
|
/** Generates the matrix for a job. */
|
|
function generateJobMatrix(
|
|
checkSpecification: Specification,
|
|
): Array<Record<string, any>> {
|
|
let matrix: Array<Record<string, any>> = [];
|
|
|
|
for (const version of checkSpecification.versions ?? defaultTestVersions) {
|
|
if (version === "latest") {
|
|
throw new Error(
|
|
`Did not recognise "version: ${version}". Did you mean "version: linked"?`,
|
|
);
|
|
}
|
|
|
|
const runnerImages = ["ubuntu-latest", "macos-latest", "windows-latest"];
|
|
const operatingSystems = checkSpecification.operatingSystems ?? ["ubuntu"];
|
|
|
|
for (const operatingSystem of operatingSystems) {
|
|
// If osCodeQlVersions is set for this OS, only include the specified CodeQL versions.
|
|
const allowedVersions =
|
|
checkSpecification.osCodeQlVersions?.[operatingSystem];
|
|
if (allowedVersions && !allowedVersions.includes(version)) {
|
|
continue;
|
|
}
|
|
|
|
const runnerImagesForOs = runnerImages.filter((image) =>
|
|
image.startsWith(operatingSystem),
|
|
);
|
|
|
|
for (const runnerImage of runnerImagesForOs) {
|
|
matrix.push({
|
|
os: runnerImage,
|
|
version,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
if (checkSpecification.analysisKinds) {
|
|
const newMatrix: Array<Record<string, any>> = [];
|
|
for (const matrixInclude of matrix) {
|
|
for (const analysisKind of checkSpecification.analysisKinds) {
|
|
newMatrix.push({
|
|
...matrixInclude,
|
|
"analysis-kinds": analysisKind,
|
|
});
|
|
}
|
|
}
|
|
matrix = newMatrix;
|
|
}
|
|
|
|
return matrix;
|
|
}
|
|
|
|
/**
|
|
* Retrieves setup steps and additional input definitions based on specific languages or frameworks
|
|
* that are requested by the `checkSpecification`.
|
|
*
|
|
* @returns An object containing setup steps and required input names.
|
|
*/
|
|
function getSetupSteps(checkSpecification: JobSpecification): {
|
|
inputs: Set<KnownInputName>;
|
|
steps: Step[];
|
|
} {
|
|
const inputs: Array<Set<KnownInputName>> = [];
|
|
const steps: Step[] = [];
|
|
|
|
for (const language of Object.values(KnownLanguage).sort()) {
|
|
const setupSpec = languageSetups[language];
|
|
|
|
if (
|
|
setupSpec === undefined ||
|
|
checkSpecification[setupSpec.specProperty] !== true
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
steps.push(...setupSpec.steps);
|
|
inputs.push(new Set(setupSpec.inputs));
|
|
}
|
|
|
|
const installYq = checkSpecification.installYq;
|
|
|
|
if (installYq) {
|
|
steps.push({
|
|
name: "Install yq",
|
|
if: "runner.os == 'Windows'",
|
|
env: {
|
|
YQ_PATH: "${{ runner.temp }}/yq",
|
|
YQ_VERSION,
|
|
},
|
|
run:
|
|
'gh release download --repo mikefarah/yq --pattern "yq_windows_amd64.exe" "$YQ_VERSION" -O "$YQ_PATH/yq.exe"\n' +
|
|
'echo "$YQ_PATH" >> "$GITHUB_PATH"',
|
|
});
|
|
}
|
|
|
|
return { inputs: unionAll(inputs), steps };
|
|
}
|
|
|
|
/**
|
|
* Generates an Actions job from the `checkSpecification`.
|
|
*
|
|
* @param specDocument
|
|
* The raw YAML document of the PR check specification.
|
|
* Used to extract `jobs` without losing the original formatting.
|
|
* @param checkSpecification The PR check specification.
|
|
* @returns The job and additional workflow inputs.
|
|
*/
|
|
function generateJob(
|
|
specDocument: yaml.Document,
|
|
checkSpecification: Specification,
|
|
) {
|
|
const matrix: Array<Record<string, any>> =
|
|
generateJobMatrix(checkSpecification);
|
|
|
|
const useAllPlatformBundle = checkSpecification.useAllPlatformBundle
|
|
? checkSpecification.useAllPlatformBundle
|
|
: "false";
|
|
|
|
// Determine which languages or frameworks have to be installed.
|
|
const setupInfo = getSetupSteps(checkSpecification);
|
|
const workflowInputs = setupInfo.inputs;
|
|
|
|
// Construct the workflow steps needed for this check.
|
|
const steps: Step[] = [
|
|
{
|
|
name: "Check out repository",
|
|
uses: "actions/checkout@v6",
|
|
},
|
|
...setupInfo.steps,
|
|
{
|
|
name: "Prepare test",
|
|
id: "prepare-test",
|
|
uses: "./.github/actions/prepare-test",
|
|
with: {
|
|
version: "${{ matrix.version }}",
|
|
"use-all-platform-bundle": useAllPlatformBundle,
|
|
// If the action is being run from a container, then do not setup kotlin.
|
|
// This is because the kotlin binaries cannot be downloaded from the container.
|
|
"setup-kotlin": "container" in checkSpecification ? "false" : "true",
|
|
},
|
|
},
|
|
];
|
|
|
|
// Extract the sequence of steps from the YAML document to persist as much formatting as possible.
|
|
const specSteps = specDocument.get("steps") as yaml.YAMLSeq;
|
|
|
|
// A handful of workflow specifications use double quotes for values, while we generally use single quotes.
|
|
// This replaces double quotes with single quotes for consistency.
|
|
yaml.visit(specSteps, {
|
|
Scalar(_key, node) {
|
|
if (node.type === "QUOTE_DOUBLE") {
|
|
node.type = "QUOTE_SINGLE";
|
|
}
|
|
},
|
|
});
|
|
|
|
// Add the generated steps in front of the ones from the specification.
|
|
specSteps.items.unshift(...steps);
|
|
|
|
const checkJob: Record<string, any> = {
|
|
strategy: {
|
|
"fail-fast": false,
|
|
matrix: {
|
|
include: matrix,
|
|
},
|
|
},
|
|
name: checkSpecification.name,
|
|
if: "github.triggering_actor != 'dependabot[bot]'",
|
|
permissions: {
|
|
contents: "read",
|
|
"security-events": "read",
|
|
},
|
|
"timeout-minutes": 45,
|
|
"runs-on": "${{ matrix.os }}",
|
|
steps: specSteps,
|
|
};
|
|
|
|
if (checkSpecification.permissions) {
|
|
checkJob.permissions = checkSpecification.permissions;
|
|
}
|
|
|
|
for (const key of ["env", "container", "services"] as const) {
|
|
if (checkSpecification[key] !== undefined) {
|
|
checkJob[key] = checkSpecification[key];
|
|
}
|
|
}
|
|
|
|
checkJob.env = checkJob.env ?? {};
|
|
if (!("CODEQL_ACTION_TEST_MODE" in checkJob.env)) {
|
|
checkJob.env.CODEQL_ACTION_TEST_MODE = true;
|
|
}
|
|
|
|
return { checkJob, workflowInputs };
|
|
}
|
|
|
|
/** Generates a validation job. */
|
|
function generateValidationJob(
|
|
specDocument: yaml.Document,
|
|
jobSpecification: JobSpecification,
|
|
checkName: string,
|
|
name: string,
|
|
) {
|
|
// Determine which languages or frameworks have to be installed.
|
|
const { inputs, steps } = getSetupSteps(jobSpecification);
|
|
|
|
// Extract the sequence of steps from the YAML document to persist as much formatting as possible.
|
|
const specSteps = specDocument.getIn([
|
|
"validationJobs",
|
|
name,
|
|
"steps",
|
|
]) as yaml.YAMLSeq;
|
|
|
|
// Add the generated steps in front of the ones from the specification.
|
|
specSteps.items.unshift(...steps);
|
|
|
|
const validationJob: Record<string, any> = {
|
|
name: jobSpecification.name,
|
|
if: "github.triggering_actor != 'dependabot[bot]'",
|
|
needs: [checkName],
|
|
permissions: {
|
|
contents: "read",
|
|
"security-events": "read",
|
|
},
|
|
"timeout-minutes": 5,
|
|
"runs-on": "ubuntu-slim",
|
|
steps: specSteps,
|
|
};
|
|
|
|
if (jobSpecification.permissions) {
|
|
validationJob.permissions = jobSpecification.permissions;
|
|
}
|
|
|
|
for (const key of ["env"] as const) {
|
|
if (jobSpecification[key] !== undefined) {
|
|
validationJob[key] = jobSpecification[key];
|
|
}
|
|
}
|
|
|
|
validationJob.env = validationJob.env ?? {};
|
|
if (!("CODEQL_ACTION_TEST_MODE" in validationJob.env)) {
|
|
validationJob.env.CODEQL_ACTION_TEST_MODE = true;
|
|
}
|
|
|
|
return { validationJob, inputs };
|
|
}
|
|
|
|
/** Generates additional jobs that run after the main check job, based on the `validationJobs` property. */
|
|
function generateValidationJobs(
|
|
specDocument: yaml.Document,
|
|
checkSpecification: Specification,
|
|
checkName: string,
|
|
): {
|
|
validationJobs: Record<string, any>;
|
|
workflowInputs: Set<KnownInputName>;
|
|
} {
|
|
if (checkSpecification.validationJobs === undefined) {
|
|
return { validationJobs: {}, workflowInputs: new Set() };
|
|
}
|
|
|
|
const validationJobs: Record<string, any> = {};
|
|
const workflowInputs: Array<Set<KnownInputName>> = [];
|
|
|
|
for (const [jobName, jobSpec] of Object.entries(
|
|
checkSpecification.validationJobs,
|
|
)) {
|
|
if (checkName === jobName) {
|
|
throw new Error(
|
|
`Validation job '${jobName}' cannot have the same name as the main job.`,
|
|
);
|
|
}
|
|
|
|
const { validationJob, inputs } = generateValidationJob(
|
|
specDocument,
|
|
jobSpec,
|
|
checkName,
|
|
jobName,
|
|
);
|
|
validationJobs[jobName] = validationJob;
|
|
workflowInputs.push(inputs);
|
|
}
|
|
|
|
return {
|
|
validationJobs,
|
|
workflowInputs: unionAll(workflowInputs),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Main entry point for the sync script.
|
|
*/
|
|
function main(): void {
|
|
// Ensure the output directory exists.
|
|
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
|
|
|
// Discover and sort all check specification files.
|
|
const checkFiles = fs
|
|
.readdirSync(CHECKS_DIR)
|
|
.filter((f) => f.endsWith(".yml"))
|
|
.sort()
|
|
.map((f) => path.join(CHECKS_DIR, f));
|
|
|
|
console.log(`Found ${checkFiles.length} check specification(s).`);
|
|
|
|
const collections: Record<
|
|
string,
|
|
Array<{
|
|
specification: Specification;
|
|
checkName: string;
|
|
inputs: Record<string, WorkflowInput>;
|
|
}>
|
|
> = {};
|
|
|
|
for (const file of checkFiles) {
|
|
const checkName = path.basename(file, ".yml");
|
|
const specDocument = loadYaml(file);
|
|
const checkSpecification = specDocument.toJS() as Specification;
|
|
|
|
console.log(`Processing: ${checkName} — "${checkSpecification.name}"`);
|
|
|
|
const { checkJob, workflowInputs } = generateJob(
|
|
specDocument,
|
|
checkSpecification,
|
|
);
|
|
const { validationJobs, workflowInputs: validationJobInputs } =
|
|
generateValidationJobs(specDocument, checkSpecification, checkName);
|
|
const combinedInputs = getSetupInputs(
|
|
workflowInputs.union(validationJobInputs),
|
|
);
|
|
|
|
// If this check belongs to a named collection, record it.
|
|
if (checkSpecification.collection) {
|
|
const collectionName = checkSpecification.collection;
|
|
if (!collections[collectionName]) {
|
|
collections[collectionName] = [];
|
|
}
|
|
collections[collectionName].push({
|
|
specification: checkSpecification,
|
|
checkName,
|
|
inputs: combinedInputs,
|
|
});
|
|
}
|
|
|
|
let extraGroupName = "";
|
|
for (const inputName of Object.keys(combinedInputs)) {
|
|
extraGroupName += `-\${{inputs.${inputName}}}`;
|
|
}
|
|
|
|
const cron = new yaml.Scalar("0 5 * * *");
|
|
cron.type = yaml.Scalar.QUOTE_SINGLE;
|
|
|
|
const workflow = {
|
|
name: `PR Check - ${checkSpecification.name}`,
|
|
env: {
|
|
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}",
|
|
GO111MODULE: "auto",
|
|
},
|
|
on: {
|
|
push: {
|
|
branches: ["main", "releases/v*"],
|
|
},
|
|
pull_request: {
|
|
types: ["opened", "synchronize", "reopened", "ready_for_review"],
|
|
},
|
|
merge_group: {
|
|
types: ["checks_requested"],
|
|
},
|
|
schedule: [{ cron }],
|
|
workflow_dispatch: {
|
|
inputs: combinedInputs,
|
|
},
|
|
workflow_call: {
|
|
inputs: combinedInputs,
|
|
},
|
|
},
|
|
defaults: {
|
|
run: {
|
|
shell: "bash",
|
|
},
|
|
},
|
|
concurrency: {
|
|
"cancel-in-progress":
|
|
"${{ github.event_name == 'pull_request' || false }}",
|
|
group: `${checkName}-\${{github.ref}}${extraGroupName}`,
|
|
},
|
|
jobs: {
|
|
[checkName]: checkJob,
|
|
...validationJobs,
|
|
},
|
|
};
|
|
|
|
const outputPath = path.join(OUTPUT_DIR, `__${checkName}.yml`);
|
|
writeYaml(outputPath, workflow);
|
|
}
|
|
|
|
// Write workflow files for collections.
|
|
for (const collectionName of Object.keys(collections)) {
|
|
const jobs: Record<string, any> = {};
|
|
let combinedInputs: Record<string, WorkflowInput> = {};
|
|
|
|
for (const check of collections[collectionName]) {
|
|
const { checkName, specification, inputs: checkInputs } = check;
|
|
const checkWith: Record<string, string> = {};
|
|
|
|
combinedInputs = { ...combinedInputs, ...checkInputs };
|
|
|
|
for (const inputName of Object.keys(checkInputs)) {
|
|
checkWith[inputName] = `\${{ inputs.${inputName} }}`;
|
|
}
|
|
|
|
jobs[checkName] = {
|
|
name: specification.name,
|
|
permissions: {
|
|
contents: "read",
|
|
"security-events": "read",
|
|
},
|
|
uses: `./.github/workflows/__${checkName}.yml`,
|
|
with: checkWith,
|
|
};
|
|
}
|
|
|
|
const collectionWorkflow = {
|
|
name: `Manual Check - ${collectionName}`,
|
|
env: {
|
|
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}",
|
|
GO111MODULE: "auto",
|
|
},
|
|
on: {
|
|
workflow_dispatch: {
|
|
inputs: combinedInputs,
|
|
},
|
|
},
|
|
jobs,
|
|
};
|
|
|
|
const outputPath = path.join(OUTPUT_DIR, `__${collectionName}.yml`);
|
|
writeYaml(outputPath, collectionWorkflow);
|
|
}
|
|
|
|
console.log(
|
|
`\nDone. Wrote ${checkFiles.length} workflow file(s) to ${OUTPUT_DIR}`,
|
|
);
|
|
}
|
|
|
|
main();
|