313 lines
9.3 KiB
TypeScript
313 lines
9.3 KiB
TypeScript
// This code is a Qiskit project.
|
|
//
|
|
// (C) Copyright IBM 2023.
|
|
//
|
|
// This code is licensed under the Apache License, Version 2.0. You may
|
|
// obtain a copy of this license in the LICENSE file in the root directory
|
|
// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
|
|
//
|
|
// Any modifications or derivative works of this code must retain this
|
|
// copyright notice, and modified files need to carry a notice indicating
|
|
// that they have been altered from the originals.
|
|
|
|
import { readFile, readdir } from "fs/promises";
|
|
|
|
import { globby } from "globby";
|
|
import yargs from "yargs/yargs";
|
|
import { hideBin } from "yargs/helpers";
|
|
|
|
import { File } from "../lib/links/InternalLink.js";
|
|
import { FileBatch } from "../lib/links/FileBatch.js";
|
|
|
|
// While these files don't exist in this repository, the link
|
|
// checker should assume that they exist in production.
|
|
const SYNTHETIC_FILES: string[] = [
|
|
"docs/errors.mdx",
|
|
"docs/api/runtime/index.mdx",
|
|
"docs/api/runtime/tags/jobs.mdx",
|
|
"docs/announcements/product-updates/2024-04-15-backend-run-deprecation.mdx",
|
|
"docs/api/qiskit-transpiler-service-rest/index.mdx",
|
|
];
|
|
|
|
interface Arguments {
|
|
[x: string]: unknown;
|
|
currentApis: boolean;
|
|
devApis: boolean;
|
|
historicalApis: boolean;
|
|
qiskitReleaseNotes: boolean;
|
|
skipBrokenHistorical: boolean;
|
|
}
|
|
|
|
const readArgs = (): Arguments => {
|
|
return yargs(hideBin(process.argv))
|
|
.version(false)
|
|
.option("current-apis", {
|
|
type: "boolean",
|
|
default: false,
|
|
description: "Check the links in the current API docs.",
|
|
})
|
|
.option("dev-apis", {
|
|
type: "boolean",
|
|
default: false,
|
|
description: "Check the links in the /dev API docs.",
|
|
})
|
|
.option("historical-apis", {
|
|
type: "boolean",
|
|
default: false,
|
|
description:
|
|
"Check the links in the historical API docs, e.g. `api/qiskit/0.44`. " +
|
|
"Warning: this is slow.",
|
|
})
|
|
.option("skip-broken-historical", {
|
|
type: "boolean",
|
|
default: false,
|
|
description:
|
|
"Don't check historical releases that are known to still fail (currently all of Qiskit). " +
|
|
"Intended to be used alongside `--historical-apis`.",
|
|
})
|
|
.option("qiskit-release-notes", {
|
|
type: "boolean",
|
|
default: false,
|
|
description: "Check the links in the `api/qiskit/release-notes` folder.",
|
|
})
|
|
.parseSync();
|
|
};
|
|
|
|
async function main() {
|
|
const args = readArgs();
|
|
|
|
const fileBatches = await determineFileBatches(args);
|
|
const otherFiles = [
|
|
...(await globby("public/{images,videos}/**/*")).map(
|
|
(fp) => new File(fp, new Set()),
|
|
),
|
|
...SYNTHETIC_FILES.map((fp) => new File(fp, new Set(), true)),
|
|
];
|
|
|
|
let allGood = true;
|
|
for (const fileBatch of fileBatches) {
|
|
const allValidLinks = await fileBatch.checkInternalLinks(otherFiles);
|
|
if (!allValidLinks) {
|
|
allGood = false;
|
|
}
|
|
}
|
|
|
|
if (!allGood) {
|
|
console.error("\nSome links appear broken 💔\n");
|
|
process.exit(1);
|
|
}
|
|
console.log("\nNo links appear broken ✅\n");
|
|
}
|
|
|
|
const RUNTIME_GLOBS_TO_LOAD = [
|
|
"docs/api/qiskit/*.mdx",
|
|
"docs/api/qiskit-ibm-runtime/options.mdx",
|
|
"docs/guides/*.{mdx,ipynb}",
|
|
"docs/migration-guides/*.{mdx,ipynb}",
|
|
];
|
|
const TRANSPILER_GLOBS_TO_LOAD = ["docs/api/qiskit/*.mdx"];
|
|
const QISKIT_GLOBS_TO_LOAD = [
|
|
"docs/api/qiskit/release-notes/0.44.mdx",
|
|
"docs/api/qiskit/release-notes/0.45.mdx",
|
|
"docs/api/qiskit/release-notes/0.46.mdx",
|
|
"docs/api/qiskit/release-notes/index.mdx",
|
|
"docs/migration-guides/qiskit-1.0-features.mdx",
|
|
"docs/guides/pulse.ipynb",
|
|
"docs/guides/configure-qiskit-local.mdx",
|
|
];
|
|
|
|
async function determineFileBatches(args: Arguments): Promise<FileBatch[]> {
|
|
const currentBatch = await determineCurrentDocsFileBatch(args);
|
|
const result = [currentBatch];
|
|
|
|
if (args.devApis) {
|
|
const devBatches = await determineDevFileBatches();
|
|
result.push(...devBatches);
|
|
}
|
|
|
|
const transpiler = await determineHistoricalFileBatches(
|
|
"qiskit-ibm-transpiler",
|
|
TRANSPILER_GLOBS_TO_LOAD,
|
|
args.historicalApis,
|
|
);
|
|
const runtime = await determineHistoricalFileBatches(
|
|
"qiskit-ibm-runtime",
|
|
RUNTIME_GLOBS_TO_LOAD,
|
|
args.historicalApis,
|
|
);
|
|
|
|
const qiskit = await determineHistoricalFileBatches(
|
|
"qiskit",
|
|
QISKIT_GLOBS_TO_LOAD,
|
|
args.historicalApis && !args.skipBrokenHistorical,
|
|
args.qiskitReleaseNotes,
|
|
);
|
|
|
|
result.push(...transpiler, ...runtime, ...qiskit);
|
|
|
|
if (args.qiskitReleaseNotes) {
|
|
result.push(await determineQiskitLegacyReleaseNotes());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
async function determineCurrentDocsFileBatch(
|
|
args: Arguments,
|
|
): Promise<FileBatch> {
|
|
const toCheck = [
|
|
"docs/**/*.{ipynb,mdx}",
|
|
"public/api/*/objects.inv",
|
|
// Ignore historical versions
|
|
"!docs/api/*/[0-9]*/*",
|
|
"!public/api/*/[0-9]*/*",
|
|
// Ignore dev version
|
|
"!docs/api/*/dev/*",
|
|
"!public/api/*/dev/*",
|
|
// Ignore Qiskit release notes
|
|
"!docs/api/qiskit/release-notes/*",
|
|
];
|
|
const toLoad = [
|
|
// These docs are used by the migration guides.
|
|
"docs/api/qiskit/0.46/{algorithms,opflow,execute}.mdx",
|
|
"docs/api/qiskit/0.46/qiskit.{algorithms,extensions,opflow}.*",
|
|
"docs/api/qiskit/0.46/qiskit.utils.QuantumInstance.mdx",
|
|
"docs/api/qiskit/0.46/qiskit.primitives.Base{Estimator,Sampler}.mdx",
|
|
"docs/api/qiskit/0.44/qiskit.extensions.{Hamiltonian,Unitary}Gate.mdx",
|
|
"docs/api/qiskit-ibm-runtime/0.26/qiskit_ibm_runtime.{Sampler,Estimator}{,V1}.mdx",
|
|
// Release notes referenced in files.
|
|
"docs/api/qiskit/release-notes/index.mdx",
|
|
"docs/api/qiskit/release-notes/0.45.mdx",
|
|
"docs/api/qiskit/release-notes/1.1.mdx",
|
|
"docs/api/qiskit/release-notes/1.2.mdx",
|
|
// Used by release notes.
|
|
"docs/api/qiskit-ibm-runtime/0.27/qiskit_ibm_runtime.options.ResilienceOptions.mdx",
|
|
];
|
|
|
|
if (!args.currentApis) {
|
|
toCheck.push(`!{public,docs}/api/*/*`);
|
|
toLoad.push(`docs/api/*/*`);
|
|
}
|
|
|
|
if (args.qiskitReleaseNotes) {
|
|
const currentVersion = JSON.parse(
|
|
await readFile(`docs/api/qiskit/_package.json`, "utf-8"),
|
|
)
|
|
.version.split(".")
|
|
.slice(0, -1)
|
|
.join(".");
|
|
|
|
toCheck.push(`docs/api/qiskit/release-notes/${currentVersion}.mdx`);
|
|
// Necessary files for docs/api/qiskit/release-notes/1.0.mdx
|
|
toLoad.push("docs/api/qiskit/release-notes/0.{44,45,46}.mdx");
|
|
}
|
|
|
|
let description: string;
|
|
if (args.currentApis && args.qiskitReleaseNotes) {
|
|
description =
|
|
"non-API docs, current API docs, and latest Qiskit release note";
|
|
} else if (args.currentApis) {
|
|
description = "non-API docs and current API docs";
|
|
} else if (args.qiskitReleaseNotes) {
|
|
description = "non-API docs and latest Qiskit release note";
|
|
} else {
|
|
description = "non-API docs";
|
|
}
|
|
|
|
return FileBatch.fromGlobs(toCheck, toLoad, description);
|
|
}
|
|
|
|
async function determineDevFileBatches(): Promise<FileBatch[]> {
|
|
const projects: [string, string[]][] = [
|
|
["qiskit", QISKIT_GLOBS_TO_LOAD],
|
|
["qiskit-ibm-runtime", RUNTIME_GLOBS_TO_LOAD],
|
|
];
|
|
|
|
const result = [];
|
|
for (const [project, toLoad] of projects) {
|
|
const fileBatch = await FileBatch.fromGlobs(
|
|
[`docs/api/${project}/dev/*`, `public/api/${project}/dev/objects.inv`],
|
|
toLoad,
|
|
`${project} dev docs`,
|
|
);
|
|
result.push(fileBatch);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
async function determineHistoricalFileBatches(
|
|
projectName: string,
|
|
extraGlobsToLoad: string[],
|
|
checkHistoricalApiDocs: boolean,
|
|
checkSeparateReleaseNotes: boolean = false,
|
|
): Promise<FileBatch[]> {
|
|
if (!checkHistoricalApiDocs && !checkSeparateReleaseNotes) {
|
|
return [];
|
|
}
|
|
|
|
const historicalFolders = (
|
|
await readdir(`docs/api/${projectName}`, { withFileTypes: true })
|
|
).filter((file) => file.isDirectory() && file.name.match(/[0-9].*/));
|
|
|
|
const result = [];
|
|
for (const folder of historicalFolders) {
|
|
const toCheck: string[] = [];
|
|
const toLoad = [...extraGlobsToLoad];
|
|
|
|
// Qiskit legacy release notes (< 0.45) have their own FileBatch, and we don't
|
|
// need to check them here.
|
|
const isBeforeQiskit0_45 = projectName === "qiskit" && +folder.name < 0.45;
|
|
if (!checkHistoricalApiDocs && isBeforeQiskit0_45) {
|
|
continue;
|
|
}
|
|
|
|
if (checkHistoricalApiDocs) {
|
|
toCheck.push(
|
|
`docs/api/${projectName}/${folder.name}/*`,
|
|
`public/api/${projectName}/${folder.name}/objects.inv`,
|
|
);
|
|
}
|
|
|
|
if (checkSeparateReleaseNotes && !isBeforeQiskit0_45) {
|
|
toCheck.push(`docs/api/${projectName}/release-notes/${folder.name}.mdx`);
|
|
if (!checkHistoricalApiDocs) {
|
|
toLoad.push(`docs/api/${projectName}/${folder.name}/*`);
|
|
}
|
|
}
|
|
|
|
const fileBatch = await FileBatch.fromGlobs(
|
|
toCheck,
|
|
toLoad,
|
|
`${projectName} v${folder.name}`,
|
|
);
|
|
result.push(fileBatch);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
async function determineQiskitLegacyReleaseNotes(): Promise<FileBatch> {
|
|
const result: FileBatch[] = [];
|
|
|
|
const legacyVersions = (
|
|
await globby("docs/api/qiskit/release-notes/[!index]*")
|
|
)
|
|
.map((releaseNotesPath) =>
|
|
releaseNotesPath.split("/").pop()!.split(".").slice(0, -1).join("."),
|
|
)
|
|
.filter(
|
|
(version) => +version < 1 && version != "0.45" && version != "0.46",
|
|
);
|
|
|
|
const toCheck = legacyVersions.map(
|
|
(legacyVersion) => `docs/api/qiskit/release-notes/${legacyVersion}.mdx`,
|
|
);
|
|
|
|
return await FileBatch.fromGlobs(
|
|
toCheck,
|
|
[`docs/api/qiskit/0.45/*`],
|
|
`qiskit legacy release notes`,
|
|
);
|
|
}
|
|
|
|
main().then(() => process.exit());
|