diff --git a/scripts/commands/checkLinks.ts b/scripts/commands/checkLinks.ts index fd8e3d0955f..e4adc78fc09 100644 --- a/scripts/commands/checkLinks.ts +++ b/scripts/commands/checkLinks.ts @@ -10,7 +10,7 @@ // 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 { readFile, readdir } from "fs/promises"; import { globby } from "globby"; import yargs from "yargs/yargs"; @@ -19,6 +19,7 @@ import { hideBin } from "yargs/helpers"; import { pathExists } from "../lib/fs"; import { File } from "../lib/links/LinkChecker"; import { FileBatch } from "../lib/links/FileBatch"; +import { QISKIT_MISSING_VERSION_MAPPING } from "../lib/qiskitMetapackage"; // While these files don't exist in this repository, the link // checker should assume that they exist in production. @@ -111,20 +112,28 @@ async function determineFileBatches(args: Arguments): Promise { result.push(...devBatches); } - if (args.historicalApis) { - const provider = await determineHistoricalFileBatches( - "qiskit-ibm-provider", - ["docs/api/qiskit/*.md"], - ); - const runtime = await determineHistoricalFileBatches("qiskit-ibm-runtime", [ - "docs/api/qiskit/providers_models.md", - ]); - let qiskit: FileBatch[] = []; - if (!args.skipBrokenHistorical) { - qiskit = await determineHistoricalFileBatches("qiskit"); - } - result.push(...provider, ...runtime, ...qiskit); - } + const provider = await determineHistoricalFileBatches( + "qiskit-ibm-provider", + ["docs/api/qiskit/*.md"], + args.historicalApis, + ); + const runtime = await determineHistoricalFileBatches( + "qiskit-ibm-runtime", + ["docs/api/qiskit/providers_models.md"], + args.historicalApis, + ); + + const qiskit = await determineHistoricalFileBatches( + "qiskit", + [ + "docs/api/qiskit/release-notes/0.44.md", + "docs/api/qiskit-ibm-provider/index.md", + ], + args.historicalApis && !args.skipBrokenHistorical, + args.qiskitReleaseNotes, + ); + + result.push(...provider, ...runtime, ...qiskit); return result; } @@ -141,6 +150,8 @@ async function determineCurrentDocsFileBatch( // Ignore dev version "!docs/api/{qiskit,qiskit-ibm-provider,qiskit-ibm-runtime}/dev/*", "!public/api/{qiskit,qiskit-ibm-provider,qiskit-ibm-runtime}/dev/*", + // Ignore Qiskit release notes + "!docs/api/qiskit/release-notes/*", ]; const toLoad = [ // The 0.46 docs are used by release notes for APIs that were removed in 1.0. @@ -149,6 +160,7 @@ async function determineCurrentDocsFileBatch( "docs/api/qiskit/0.45/qiskit.quantum_info.{OneQubitEuler,TwoQubitBasis,XX}Decomposer.md", "docs/api/qiskit/0.45/qiskit.transpiler.synthesis.aqc.AQC.md", "docs/api/qiskit/0.45/{tools,quantum_info,synthesis_aqc}.md", + "docs/api/qiskit/release-notes/index.md", ]; if (!args.currentApis) { @@ -158,9 +170,15 @@ async function determineCurrentDocsFileBatch( toLoad.push("docs/api/{qiskit,qiskit-ibm-provider,qiskit-ibm-runtime}/*"); } - if (!args.qiskitReleaseNotes) { - toCheck.push("!docs/api/qiskit/release-notes/*"); - toLoad.push("docs/api/qiskit/release-notes/index.md"); + 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}.md`); } let description: string; @@ -203,19 +221,59 @@ async function determineDevFileBatches(): Promise { async function determineHistoricalFileBatches( projectName: string, - toLoad: string[] = [], + extraGlobsToLoad: string[], + checkHistoricalApiDocs: boolean, + checkSeparateReleaseNotes: boolean = false, ): Promise { + 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 fileBatch = await FileBatch.fromGlobs( - [ + const toCheck: string[] = []; + const toLoad = [...extraGlobsToLoad]; + + if (checkHistoricalApiDocs) { + toCheck.push( `docs/api/${projectName}/${folder.name}/*`, `public/api/${projectName}/${folder.name}/objects.inv`, - ], + ); + } else { + toLoad.push(`docs/api/${projectName}/${folder.name}/*`); + } + + if (checkSeparateReleaseNotes) { + toCheck.push(`docs/api/${projectName}/release-notes/${folder.name}.md`); + + // Some legacy release notes don't have docs, and their links point to the closest + // next version present in the repo. QISKIT_MISSING_VERSION_MAPPING contains what + // versions point to which other version + const extraVersionsToCheck = QISKIT_MISSING_VERSION_MAPPING.get( + folder.name, + ); + extraVersionsToCheck?.forEach((version) => { + toCheck.push(`docs/api/${projectName}/release-notes/${version}.md`); + }); + + // Temporary - remove after https://github.com/Qiskit/documentation/pull/865 is merged + toLoad.push( + "docs/api/qiskit/*.{ipynb,md,mdx}", + "docs/api/qiskit/0.46/*.md", + "docs/api/qiskit/0.44/qiskit.extensions.{Hamiltonian,Unitary}Gate.md", + "docs/api/qiskit/0.45/qiskit.quantum_info.{OneQubitEuler,TwoQubitBasis,XX}Decomposer.md", + "docs/api/qiskit/0.45/qiskit.transpiler.synthesis.aqc.AQC.md", + "docs/api/qiskit/0.45/{tools,quantum_info,synthesis_aqc}.md", + "docs/api/qiskit/release-notes/index.md", + ); + } + + const fileBatch = await FileBatch.fromGlobs( + toCheck, toLoad, `${projectName} v${folder.name}`, ); diff --git a/scripts/lib/api/Pkg.ts b/scripts/lib/api/Pkg.ts index 08b38fccced..aa586b87a48 100644 --- a/scripts/lib/api/Pkg.ts +++ b/scripts/lib/api/Pkg.ts @@ -14,6 +14,7 @@ import { join } from "path/posix"; import { findLegacyReleaseNotes } from "./releaseNotes"; import { getRoot } from "../fs"; +import { determineHistoricalQiskitGithubUrl } from "../qiskitMetapackage"; export interface ReleaseNoteEntry { title: string; @@ -207,162 +208,3 @@ export class Pkg { ); } } - -const QISKIT_METAPACKAGE_TO_TERRA = new Map([ - ["0.44", "0.25"], - ["0.43", "0.24"], - ["0.42", "0.23"], - ["0.41", "0.23"], - ["0.40", "0.23"], - ["0.39", "0.22"], - ["0.38", "0.21"], - ["0.37", "0.21"], - ["0.36", "0.20"], - ["0.35", "0.20"], - ["0.34", "0.19"], - ["0.33", "0.19"], - ["0.32", "0.18"], - ["0.31", "0.18"], - ["0.30", "0.18"], - ["0.29", "0.18"], - ["0.28", "0.18"], - ["0.27", "0.17"], - ["0.26", "0.17"], - ["0.25", "0.17"], - ["0.24", "0.16"], - ["0.19", "0.14"], -]); -const QISKIT_METAPACKAGE_TO_AER = new Map([ - ["0.43", "0.12"], - ["0.42", "0.12"], - ["0.41", "0.11"], - ["0.40", "0.11"], - ["0.39", "0.11"], - ["0.38", "0.11"], - ["0.37", "0.10"], - ["0.36", "0.10"], - ["0.35", "0.10"], - ["0.34", "0.10"], - ["0.33", "0.9"], - ["0.32", "0.9"], - ["0.31", "0.9"], - ["0.30", "0.9"], - ["0.29", "0.8"], - ["0.28", "0.8"], - ["0.27", "0.8"], - ["0.26", "0.8"], - ["0.25", "0.8"], - ["0.24", "0.7"], - ["0.19", "0.5"], -]); -const QISKIT_METAPACKAGE_TO_IGNIS = new Map([ - ["0.36", "0.7"], - ["0.35", "0.7"], - ["0.34", "0.7"], - ["0.33", "0.7"], - ["0.32", "0.6"], - ["0.31", "0.6"], - ["0.30", "0.6"], - ["0.29", "0.6"], - ["0.28", "0.6"], - ["0.27", "0.6"], - ["0.26", "0.6"], - ["0.25", "0.6"], - ["0.24", "0.5"], - ["0.19", "0.3"], -]); -const QISKIT_METAPACKAGE_TO_AQUA = new Map([ - ["0.32", "0.9"], - ["0.31", "0.9"], - ["0.30", "0.9"], - ["0.29", "0.9"], - ["0.28", "0.9"], - ["0.27", "0.9"], - ["0.26", "0.9"], - ["0.25", "0.9"], - ["0.24", "0.8"], - ["0.19", "0.7"], -]); -const QISKIT_METAPACKAGE_TO_IBMQ_PROVIDER = new Map([ - ["0.43", "0.20"], - ["0.42", "0.20"], - ["0.41", "0.20"], - ["0.40", "0.19"], - ["0.39", "0.19"], - ["0.38", "0.19"], - ["0.37", "0.19"], - ["0.36", "0.19"], - ["0.35", "0.18"], - ["0.34", "0.18"], - ["0.33", "0.18"], - ["0.32", "0.18"], - ["0.31", "0.17"], - ["0.30", "0.16"], - ["0.29", "0.16"], - ["0.28", "0.15"], - ["0.27", "0.14"], - ["0.26", "0.13"], - ["0.25", "0.12"], - ["0.24", "0.12"], - ["0.19", "0.7"], -]); - -function determineHistoricalQiskitGithubUrl( - metapackageVersion: string, - fileName: string, -): string { - const getOrThrow = (mapping: Map): string => { - const result = mapping.get(metapackageVersion); - if (result === undefined) { - throw new Error( - `No compatible version found for the file ${fileName} with qiskit-metapackage ${metapackageVersion}}`, - ); - } - return result; - }; - - /** - * Special case: - * The file `qiskit/optimization/converters/quadratic_program_to_negative_value_oracle` existed in qiskit-aqua - * patch 0.7.3, but was removed in patch 0.7.4. Thus, the branch stable/0.7 doesn't contain the file, and - * we can only access it through the tag 0.7.3 - */ - if ( - metapackageVersion == "0.19" && - fileName == - "qiskit/optimization/converters/quadratic_program_to_negative_value_oracle" - ) { - return `https://github.com/qiskit-community/qiskit-aqua/tree/0.7.3/${fileName}.py`; - } - - let slug: string; - let version: string; - if ( - fileName.includes("qiskit_aer") || - fileName.includes("qiskit/aer") || - fileName.includes("qiskit/providers/aer") - ) { - slug = "qiskit/qiskit-aer"; - version = getOrThrow(QISKIT_METAPACKAGE_TO_AER); - } else if (fileName.includes("qiskit/ignis")) { - slug = "qiskit-community/qiskit-ignis"; - version = getOrThrow(QISKIT_METAPACKAGE_TO_IGNIS); - } else if ( - fileName.includes("qiskit/aqua") || - fileName.includes("qiskit/chemistry") || - fileName.includes("qiskit/finance") || - fileName.includes("qiskit/ml") || - fileName.includes("qiskit/optimization") - ) { - slug = "qiskit-community/qiskit-aqua"; - version = getOrThrow(QISKIT_METAPACKAGE_TO_AQUA); - } else if (fileName.includes("qiskit/providers/ibmq")) { - slug = "qiskit/qiskit-ibmq-provider"; - version = getOrThrow(QISKIT_METAPACKAGE_TO_IBMQ_PROVIDER); - } else { - slug = "qiskit/qiskit"; - version = getOrThrow(QISKIT_METAPACKAGE_TO_TERRA); - } - - return `https://github.com/${slug}/tree/stable/${version}/${fileName}.py`; -} diff --git a/scripts/lib/qiskitMetapackage.ts b/scripts/lib/qiskitMetapackage.ts new file mode 100644 index 00000000000..4148599f876 --- /dev/null +++ b/scripts/lib/qiskitMetapackage.ts @@ -0,0 +1,197 @@ +// This code is a Qiskit project. +// +// (C) Copyright IBM 2024. +// +// 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. + +// Some Qiskit release notes don't have docs in the repo, and we need to change +// their links to point to the next closest version available. This map stores +// the next closest version with docs for some qiskit legacy versions. +export const QISKIT_MISSING_VERSION_MAPPING = new Map([ + [ + "0.19", + [ + "0.5", + "0.6", + "0.7", + "0.8", + "0.9", + "0.10", + "0.11", + "0.12", + "0.13", + "0.14", + "0.15", + "0.16", + "0.17", + "0.18", + ], + ], + ["0.24", ["0.20", "0.21", "0.22", "0.23"]], + ["0.35", ["0.34"]], +]); + +const QISKIT_METAPACKAGE_TO_TERRA = new Map([ + ["0.44", "0.25"], + ["0.43", "0.24"], + ["0.42", "0.23"], + ["0.41", "0.23"], + ["0.40", "0.23"], + ["0.39", "0.22"], + ["0.38", "0.21"], + ["0.37", "0.21"], + ["0.36", "0.20"], + ["0.35", "0.20"], + ["0.34", "0.19"], + ["0.33", "0.19"], + ["0.32", "0.18"], + ["0.31", "0.18"], + ["0.30", "0.18"], + ["0.29", "0.18"], + ["0.28", "0.18"], + ["0.27", "0.17"], + ["0.26", "0.17"], + ["0.25", "0.17"], + ["0.24", "0.16"], + ["0.19", "0.14"], +]); +const QISKIT_METAPACKAGE_TO_AER = new Map([ + ["0.43", "0.12"], + ["0.42", "0.12"], + ["0.41", "0.11"], + ["0.40", "0.11"], + ["0.39", "0.11"], + ["0.38", "0.11"], + ["0.37", "0.10"], + ["0.36", "0.10"], + ["0.35", "0.10"], + ["0.34", "0.10"], + ["0.33", "0.9"], + ["0.32", "0.9"], + ["0.31", "0.9"], + ["0.30", "0.9"], + ["0.29", "0.8"], + ["0.28", "0.8"], + ["0.27", "0.8"], + ["0.26", "0.8"], + ["0.25", "0.8"], + ["0.24", "0.7"], + ["0.19", "0.5"], +]); +const QISKIT_METAPACKAGE_TO_IGNIS = new Map([ + ["0.36", "0.7"], + ["0.35", "0.7"], + ["0.34", "0.7"], + ["0.33", "0.7"], + ["0.32", "0.6"], + ["0.31", "0.6"], + ["0.30", "0.6"], + ["0.29", "0.6"], + ["0.28", "0.6"], + ["0.27", "0.6"], + ["0.26", "0.6"], + ["0.25", "0.6"], + ["0.24", "0.5"], + ["0.19", "0.3"], +]); +const QISKIT_METAPACKAGE_TO_AQUA = new Map([ + ["0.32", "0.9"], + ["0.31", "0.9"], + ["0.30", "0.9"], + ["0.29", "0.9"], + ["0.28", "0.9"], + ["0.27", "0.9"], + ["0.26", "0.9"], + ["0.25", "0.9"], + ["0.24", "0.8"], + ["0.19", "0.7"], +]); +const QISKIT_METAPACKAGE_TO_IBMQ_PROVIDER = new Map([ + ["0.43", "0.20"], + ["0.42", "0.20"], + ["0.41", "0.20"], + ["0.40", "0.19"], + ["0.39", "0.19"], + ["0.38", "0.19"], + ["0.37", "0.19"], + ["0.36", "0.19"], + ["0.35", "0.18"], + ["0.34", "0.18"], + ["0.33", "0.18"], + ["0.32", "0.18"], + ["0.31", "0.17"], + ["0.30", "0.16"], + ["0.29", "0.16"], + ["0.28", "0.15"], + ["0.27", "0.14"], + ["0.26", "0.13"], + ["0.25", "0.12"], + ["0.24", "0.12"], + ["0.19", "0.7"], +]); + +export function determineHistoricalQiskitGithubUrl( + metapackageVersion: string, + fileName: string, +): string { + const getOrThrow = (mapping: Map): string => { + const result = mapping.get(metapackageVersion); + if (result === undefined) { + throw new Error( + `No compatible version found for the file ${fileName} with qiskit-metapackage ${metapackageVersion}}`, + ); + } + return result; + }; + + /** + * Special case: + * The file `qiskit/optimization/converters/quadratic_program_to_negative_value_oracle` existed in qiskit-aqua + * patch 0.7.3, but was removed in patch 0.7.4. Thus, the branch stable/0.7 doesn't contain the file, and + * we can only access it through the tag 0.7.3 + */ + if ( + metapackageVersion == "0.19" && + fileName == + "qiskit/optimization/converters/quadratic_program_to_negative_value_oracle" + ) { + return `https://github.com/qiskit-community/qiskit-aqua/tree/0.7.3/${fileName}.py`; + } + + let slug: string; + let version: string; + if ( + fileName.includes("qiskit_aer") || + fileName.includes("qiskit/aer") || + fileName.includes("qiskit/providers/aer") + ) { + slug = "qiskit/qiskit-aer"; + version = getOrThrow(QISKIT_METAPACKAGE_TO_AER); + } else if (fileName.includes("qiskit/ignis")) { + slug = "qiskit-community/qiskit-ignis"; + version = getOrThrow(QISKIT_METAPACKAGE_TO_IGNIS); + } else if ( + fileName.includes("qiskit/aqua") || + fileName.includes("qiskit/chemistry") || + fileName.includes("qiskit/finance") || + fileName.includes("qiskit/ml") || + fileName.includes("qiskit/optimization") + ) { + slug = "qiskit-community/qiskit-aqua"; + version = getOrThrow(QISKIT_METAPACKAGE_TO_AQUA); + } else if (fileName.includes("qiskit/providers/ibmq")) { + slug = "qiskit/qiskit-ibmq-provider"; + version = getOrThrow(QISKIT_METAPACKAGE_TO_IBMQ_PROVIDER); + } else { + slug = "qiskit/qiskit"; + version = getOrThrow(QISKIT_METAPACKAGE_TO_TERRA); + } + + return `https://github.com/${slug}/tree/stable/${version}/${fileName}.py`; +}