337 lines
10 KiB
TypeScript
337 lines
10 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 { readdir } from "fs/promises";
|
|
|
|
import { globby } from "globby";
|
|
import yargs from "yargs/yargs";
|
|
import { hideBin } from "yargs/helpers";
|
|
|
|
import { readApiMinorVersion } from "../lib/apiVersions.js";
|
|
import { File } from "../lib/links/InternalLink.js";
|
|
import { FileBatch } from "../lib/links/FileBatch.js";
|
|
import { QISKIT_REMOVED_PAGES } from "../lib/links/QiskitRemovedPages.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/administration/quickstart-org.mdx",
|
|
"docs/administration/analytics.mdx",
|
|
"docs/errors.mdx",
|
|
"docs/api/runtime/index.mdx",
|
|
"docs/api/runtime/tags/jobs.mdx",
|
|
"docs/api/qiskit-transpiler-service-rest/index.mdx",
|
|
"docs/api/runtime/tags/usage.mdx",
|
|
"docs/api/runtime/tags/sessions.mdx",
|
|
"docs/api/qiskit-runtime-rest/tags/instances.mdx",
|
|
];
|
|
|
|
interface Arguments {
|
|
[x: string]: unknown;
|
|
currentApis: boolean;
|
|
devApis: boolean;
|
|
historicalApis: boolean;
|
|
qiskitLegacyReleaseNotes: 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.46`. " +
|
|
"Warning: this is slow.",
|
|
})
|
|
.option("qiskit-legacy-release-notes", {
|
|
type: "boolean",
|
|
default: false,
|
|
description: "Check the Qiskit release notes up until 0.45.",
|
|
})
|
|
.parseSync();
|
|
};
|
|
|
|
async function main() {
|
|
const args = readArgs();
|
|
|
|
const fileBatches = await determineFileBatches(args);
|
|
const otherFiles = [
|
|
...(await globby("public/docs/{images,videos,open-source}/**/*")).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 QISKIT_REMOVED_PAGES_TO_LOAD = [
|
|
...QISKIT_REMOVED_PAGES,
|
|
"qiskit.circuit.QuantumCircuit",
|
|
"compiler",
|
|
].map((page) => `docs/api/qiskit/1.4/${page}.mdx`);
|
|
|
|
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}",
|
|
...QISKIT_REMOVED_PAGES_TO_LOAD,
|
|
];
|
|
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/construct-circuits.ipynb",
|
|
"docs/guides/bit-ordering.mdx",
|
|
"docs/guides/pulse.ipynb",
|
|
"docs/guides/primitives.mdx",
|
|
"docs/guides/configure-qiskit-local.mdx",
|
|
"docs/guides/transpiler-stages.ipynb",
|
|
"docs/api/qiskit/providers.mdx",
|
|
"docs/open-source/qiskit-sdk-version-strategy.mdx",
|
|
"docs/migration-guides/qiskit-backendv1-to-v2.mdx",
|
|
];
|
|
// This is reused amongst all the addons to make this config less verbose.
|
|
const ADDON_GLOBS_TO_LOAD = ["docs/api/qiskit/*.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,
|
|
{
|
|
check: args.historicalApis,
|
|
},
|
|
);
|
|
const runtime = await determineHistoricalFileBatches(
|
|
"qiskit-ibm-runtime",
|
|
RUNTIME_GLOBS_TO_LOAD,
|
|
{
|
|
check: args.historicalApis,
|
|
},
|
|
);
|
|
|
|
const qiskit = await determineHistoricalFileBatches(
|
|
"qiskit",
|
|
QISKIT_GLOBS_TO_LOAD,
|
|
{
|
|
check: args.historicalApis,
|
|
hasSeparateReleaseNotes: true,
|
|
},
|
|
);
|
|
|
|
const sqd = await determineHistoricalFileBatches(
|
|
"qiskit-addon-sqd",
|
|
ADDON_GLOBS_TO_LOAD,
|
|
{ check: args.historicalApis },
|
|
);
|
|
const mpf = await determineHistoricalFileBatches(
|
|
"qiskit-addon-mpf",
|
|
ADDON_GLOBS_TO_LOAD,
|
|
{ check: args.historicalApis },
|
|
);
|
|
|
|
// This is intentionally ordered so that the smallest APIs are checked first,
|
|
// since they are much faster to check.
|
|
result.push(...transpiler, ...sqd, ...mpf, ...runtime, ...qiskit);
|
|
|
|
if (args.qiskitLegacyReleaseNotes) {
|
|
result.push(await determineQiskitLegacyReleaseNotes());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
async function determineCurrentDocsFileBatch(
|
|
args: Arguments,
|
|
): Promise<FileBatch> {
|
|
const toCheck = [
|
|
"docs/**/*.{ipynb,mdx}",
|
|
"public/docs/api/*/objects.inv",
|
|
// Ignore historical versions
|
|
"!docs/api/*/[0-9]*/*",
|
|
"!public/docs/api/*/[0-9]*/*",
|
|
// Ignore dev version
|
|
"!docs/api/*/dev/*",
|
|
"!public/docs/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/{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.*.mdx",
|
|
// Used by release notes.
|
|
"docs/api/qiskit-ibm-runtime/0.20/sampler.mdx",
|
|
"docs/api/qiskit-ibm-runtime/0.21/qiskit-runtime-service.mdx",
|
|
"docs/api/qiskit-ibm-runtime/0.25/runtime-options.mdx",
|
|
"docs/api/qiskit-ibm-runtime/0.27/options-resilience-options.mdx",
|
|
"docs/api/qiskit-ibm-runtime/0.29/qiskit-runtime-service.mdx",
|
|
"docs/api/qiskit-ibm-runtime/0.29/session.mdx",
|
|
"docs/api/qiskit-ibm-runtime/0.30/runtime-job.mdx",
|
|
"docs/api/qiskit-ibm-runtime/0.34/ibm-backend.mdx",
|
|
// These pages were removed in Qiskit 2.0.
|
|
...QISKIT_REMOVED_PAGES_TO_LOAD,
|
|
];
|
|
|
|
if (args.currentApis) {
|
|
const currentQiskitVersion = await readApiMinorVersion("docs/api/qiskit");
|
|
toCheck.push(`docs/api/qiskit/release-notes/${currentQiskitVersion}.mdx`);
|
|
} else {
|
|
toCheck.push(`!public/docs/api/*/*`);
|
|
toCheck.push(`!docs/api/*/*`);
|
|
toLoad.push(`docs/api/*/*`);
|
|
}
|
|
|
|
const description = args.currentApis
|
|
? "non-API docs and current API docs"
|
|
: "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/docs/api/${project}/dev/objects.inv`,
|
|
],
|
|
toLoad,
|
|
`${project} dev docs`,
|
|
);
|
|
result.push(fileBatch);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
async function determineHistoricalFileBatches(
|
|
projectName: string,
|
|
extraGlobsToLoad: string[],
|
|
options: {
|
|
check: boolean;
|
|
skipVersions?: (version: string) => boolean;
|
|
hasSeparateReleaseNotes?: boolean;
|
|
},
|
|
): Promise<FileBatch[]> {
|
|
if (!options.check) {
|
|
return [];
|
|
}
|
|
|
|
const historicalFolders = (
|
|
await readdir(`docs/api/${projectName}`, { withFileTypes: true })
|
|
)
|
|
.filter((file) => file.isDirectory() && file.name.match(/[0-9].*/))
|
|
.sort()
|
|
.reverse();
|
|
|
|
const result = [];
|
|
for (const folder of historicalFolders) {
|
|
if (options.skipVersions && options.skipVersions(folder.name)) {
|
|
continue;
|
|
}
|
|
|
|
const toCheck: string[] = [
|
|
`docs/api/${projectName}/${folder.name}/*`,
|
|
`public/docs/api/${projectName}/${folder.name}/objects.inv`,
|
|
];
|
|
const toLoad = [...extraGlobsToLoad];
|
|
|
|
// Also check the release note file for this version, if the package has
|
|
// separate release notes per version.
|
|
//
|
|
// Qiskit legacy release notes (< 0.46) have their own FileBatch, so we don't
|
|
// need to check them here.
|
|
const isBeforeQiskit0_46 = projectName === "qiskit" && +folder.name < 0.46;
|
|
if (options.hasSeparateReleaseNotes && !isBeforeQiskit0_46) {
|
|
toCheck.push(`docs/api/${projectName}/release-notes/${folder.name}.mdx`);
|
|
}
|
|
|
|
const fileBatch = await FileBatch.fromGlobs(
|
|
toCheck,
|
|
toLoad,
|
|
`${projectName} v${folder.name}`,
|
|
);
|
|
result.push(fileBatch);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
async function determineQiskitLegacyReleaseNotes(): Promise<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.46");
|
|
|
|
const toCheck = legacyVersions.map(
|
|
(legacyVersion) => `docs/api/qiskit/release-notes/${legacyVersion}.mdx`,
|
|
);
|
|
|
|
return await FileBatch.fromGlobs(
|
|
toCheck,
|
|
[`docs/api/qiskit/0.45/*`, "docs/api/qiskit/release-notes/index.mdx"],
|
|
`qiskit legacy release notes`,
|
|
);
|
|
}
|
|
|
|
main().then(() => process.exit());
|