diff --git a/README.md b/README.md index fea0368599f..653ca4594ff 100644 --- a/README.md +++ b/README.md @@ -242,12 +242,26 @@ To check that formatting is valid without actually making changes, run `npm run This is useful when we make improvements to the API generation script. +You can regenerate all API docs versions following these steps: + +1. Create a dedicated branch for the regeneration other than `main` using `git checkout -b `. +2. Ensure there are no pending changes by running `git status` and creating a new commit for them if necessary. +3. Run `npm run regen-apis` to regenerate all API docs versions for `qiskit`, `qiskit-ibm-provider`, and `qiskit-ibm-runtime`. + +Each regenerated version will be saved as a distinct commit. If the changes are too large for one single PR, consider splitting it up into multiple PRs by using `git cherry-pick` or `git rebase -i` so each PR only has the commits it wants to target. + +If you only want to regenerate the latest stable minor release of each package, then add `--current-apis-only` as an argument, and in case you only want to regenerate versions of one package, then you can use the `-p ` argument. + +Alternatively, you can also regenerate one specific version: + 1. Choose which documentation you want to generate (`qiskit`, `qiskit-ibm-provider`, or `qiskit-ibm-runtime`) and its version. 2. Run `npm run gen-api -- -p -v `, e.g. `npm run gen-api -- -p qiskit -v 0.45.0` If the version is not for the latest stable minor release series, then add `--historical` to the arguments. For example, use `--historical` if the latest stable release is 0.45.\* but you're generating docs for the patch release 0.44.3. +In this case, no commit will be automatically created. + ## Generate new API docs This is useful when new docs content is published, usually corresponding to new releases or hotfixes for content issues. If you're generating a patch release, also see the below subsection for additional steps. diff --git a/package.json b/package.json index d44b5b06529..8c91ca30b9e 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "fmt": "prettier --write .", "test": "jest", "typecheck": "tsc", + "regen-apis": "node -r esbuild-register scripts/commands/regenerateApiDocs.ts", "gen-api": "node -r esbuild-register scripts/commands/updateApiDocs.ts", "make-historical": "node -r esbuild-register scripts/commands/convertApiDocsToHistorical.ts" }, diff --git a/scripts/api-html-artifacts.json b/scripts/api-html-artifacts.json index b8e05488ef3..c4195dfce24 100644 --- a/scripts/api-html-artifacts.json +++ b/scripts/api-html-artifacts.json @@ -2,35 +2,36 @@ "qiskit": { "0.46": "https://ibm.box.com/shared/static/74ppfh7b3rx73fntmk9p8pw4asf6pp4a.zip", "0.45": "https://ibm.box.com/shared/static/e2rwbrztml4dg6lkk1w3e0kmevkw9w7s.zip", - "0.44": "https://ibm.box.com/shared/static/yudjd08saugb3z9stciz4zxhpkroqrv4.zip", - "0.43": "https://ibm.box.com/shared/static/odcdrizljwpcz96upbu8umb5xpfvf73h.zip", - "0.42": "https://ibm.box.com/shared/static/muj1v0m4x36fuqyqo22wknexj9cq8yfv.zip", - "0.41": "https://ibm.box.com/shared/static/2j2qw33dgy5swf9vno0tfpq8ahey14uu.zip", - "0.40": "https://ibm.box.com/shared/static/6j9mbwbeqx0kfi20zl22hzp6524kvqz8.zip", - "0.39": "https://ibm.box.com/shared/static/za2u7phhpyd6lrsalv48xsxhz1ud17rd.zip", - "0.38": "https://ibm.box.com/shared/static/rcyb5ugvgjc6gpgb5hkdedz5r5buashu.zip", - "0.37": "https://ibm.box.com/shared/static/4nfrhcnu5ixod1i5ct1fps2qpoyfmgnb.zip", - "0.36": "https://ibm.box.com/shared/static/eekpk9r8kawwxr1bfaswrcc073mien1j.zip", - "0.35": "https://ibm.box.com/shared/static/dkqlxxllnhrq4yyae9fqlifab36fq2av.zip", - "0.33": "https://ibm.box.com/shared/static/kmgdgi0imgam3o7jzumyjhwdz00i9y5w.zip", - "0.32": "https://ibm.box.com/shared/static/tzkb58t2vgzz21i6ydg7r9g4m4995waq.zip", - "0.31": "https://ibm.box.com/shared/static/d4m9d1f4uaq95ovpqo9yot1mnm1jnofw.zip", - "0.30": "https://ibm.box.com/shared/static/loueznt45qyoo925gwt6cb5n1a0lp4o0.zip", - "0.29": "https://ibm.box.com/shared/static/uc8s5lcrmxuy23vkb2bkn8y930m533rk.zip", - "0.28": "https://ibm.box.com/shared/static/656e5bsov004vpnlmc9z40q718eq0ze2.zip", - "0.27": "https://ibm.box.com/shared/static/y2lb96gl2v32si02xgbswmjc06vfvfje.zip", - "0.26": "https://ibm.box.com/shared/static/5wlx2c8wr5xka5buzmlf1mqmz0zl600p.zip", - "0.25": "https://ibm.box.com/shared/static/h9dw4iq19f6nz6vp0egpoyjhxqnx7ot5.zip", - "0.24": "https://ibm.box.com/shared/static/pmcnjymbenwhwa3psxqsptvexs7hwrpd.zip", - "0.19": "https://ibm.box.com/shared/static/ov1hqihl0hlsai31dwyjyjj06k86k2nt.zip" + "0.44": "https://ibm.box.com/shared/static/myfk3g20xsp5xj8f3f1fd81p7ywgl7an.zip", + "0.43": "https://ibm.box.com/shared/static/cgqrwmz0u3ma8bld3xr39ysl0ghp8mfh.zip", + "0.42": "https://ibm.box.com/shared/static/icdn5tbrgy87k3aqxksptjezp98egqh6.zip", + "0.41": "https://ibm.box.com/shared/static/1xsrn1akx7qbr31l0yqc258gficf30cf.zip", + "0.40": "https://ibm.box.com/shared/static/j8rarno6kvv7zag58cv8uc63an3v7z69.zip", + "0.39": "https://ibm.box.com/shared/static/2mhunbme514sg5ewnngsmmtds5a3mpyx.zip", + "0.38": "https://ibm.box.com/shared/static/jl36955v51vezwiyyojs2wsexzu2hhdm.zip", + "0.37": "https://ibm.box.com/shared/static/fvn2x3tvqizvklf7ehnrxd5y149lma25.zip", + "0.36": "https://ibm.box.com/shared/static/p639y3r0xwbfuf5jps3mwdh6xiacdbs3.zip", + "0.35": "https://ibm.box.com/shared/static/2lw6x3tbnbgczigfh1yvr9z165fsauod.zip", + "0.33": "https://ibm.box.com/shared/static/xbmunamrm0yt46xv14q0hv4o104rv6q4.zip", + "0.32": "https://ibm.box.com/shared/static/xjan1nd4x4sviyz9pf43fygppczw1e1q.zip", + "0.31": "https://ibm.box.com/shared/static/wpvvg789qkx4dz0ctnj7su3tj2qfy68s.zip", + "0.30": "https://ibm.box.com/shared/static/gxpg8de5aof9ywar84266onvqfhbiy7t.zip", + "0.29": "https://ibm.box.com/shared/static/eqxzns3bv2yjyprne4s45vom5gzppg0o.zip", + "0.28": "https://ibm.box.com/shared/static/uv4l91bnjrhcqyqdunx1mqcec6fp31xg.zip", + "0.27": "https://ibm.box.com/shared/static/cqud425zvqpog6xd8w2dmfloqy894alb.zip", + "0.26": "https://ibm.box.com/shared/static/el5nseict90pjrs3a4x7gkxhh5203giq.zip", + "0.25": "https://ibm.box.com/shared/static/8qg4lb4outd8opd4a841b7xd18k3b5ke.zip", + "0.24": "https://ibm.box.com/shared/static/ygdswx8s7fj970kuulkzlh8r2mjuzn3k.zip", + "0.19": "https://ibm.box.com/shared/static/wjoea4x5tnxd0l4lgo2v3kxnx6btxvvl.zip" }, "qiskit-ibm-provider": { - "0.7": "https://ibm.box.com/shared/static/o5tjrdefdz72yi35nw09l9zbjjjecips.zip" + "0.7": "https://ibm.box.com/shared/static/t2vuik7bqboata3i34r4a83baabo95xn.zip" }, "qiskit-ibm-runtime": { - "0.17": "https://ibm.box.com/shared/static/5r7r2x65bst3hdtcyuowwfvno5o23xw0.zip", + "0.18": "https://ibm.box.com/shared/static/rbjdfogq8t1a4tquw1bh6gy4qdcv521v.zip", + "0.17": "https://ibm.box.com/shared/static/dnjto1rpvk0qknqb9ir8nr3yv6n0yzhk.zip", "0.16": "https://ibm.box.com/shared/static/xbtjc270jc2uu3s8tp7tqn8o9pckl37i.zip", - "0.15": "https://ibm.box.com/shared/static/j9wiuo9mga3lwihhqy9sdeqtsr4taanm.zip", - "0.14": "https://ibm.box.com/shared/static/bhv1xl2pid74qsanmphx3zm49cup6owv.zip" + "0.15": "https://ibm.box.com/shared/static/u9yqn2ya75cigotxsgl8zqaaqllzuudb.zip", + "0.14": "https://ibm.box.com/shared/static/t37e7jjsi0hii4j3xoorwpwso5m03jqn.zip" } } diff --git a/scripts/commands/regenerateApiDocs.ts b/scripts/commands/regenerateApiDocs.ts new file mode 100644 index 00000000000..09ef3255e61 --- /dev/null +++ b/scripts/commands/regenerateApiDocs.ts @@ -0,0 +1,181 @@ +// 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. + +import { readFile, readdir } from "fs/promises"; + +import yargs from "yargs/yargs"; +import { hideBin } from "yargs/helpers"; +import { $ } from "zx"; +import fs from "fs"; + +import { Pkg } from "../lib/api/Pkg"; +import { zxMain } from "../lib/zx"; + +interface Arguments { + [x: string]: unknown; + package?: string; + currentApisOnly: boolean; +} + +const readArgs = (): Arguments => { + return yargs(hideBin(process.argv)) + .version(false) + .option("package", { + alias: "p", + type: "string", + choices: Pkg.VALID_NAMES, + demandOption: false, + description: "Which package to update", + }) + .option("current-apis-only", { + type: "boolean", + default: false, + description: "Regenerate only the current API docs?", + }) + .parseSync(); +}; + +zxMain(async () => { + const args = readArgs(); + await validateGitStatus(); + + const results = new Map(); + for (const pkgName of Pkg.VALID_NAMES) { + if (args.package && pkgName != args.package) { + continue; + } + + const [historicalVersions, currentVersion] = await getPackageVersions( + pkgName, + args.currentApisOnly, + ); + const result = await processVersions( + pkgName, + historicalVersions, + currentVersion, + ); + results.set(pkgName, result); + } + + console.log(""); + results.forEach((result: string[], pkgName: string) => { + console.log(`Regeneration of ${pkgName}:`); + result.forEach((msg) => console.error(msg)); + console.log(""); + }); + + console.log(`Each regenerated version has been saved as a distinct commit. If the changes are +too large for one single PR, consider splitting it up into multiple PRs by using +git cherry-pick or git rebase -i so each PR only has the commits it wants to target.`); +}); + +async function processVersions( + pkgName: string, + historicalVersions: string[], + currentVersion: string, +): Promise { + const results: string[] = []; + + for (const historicalVersion of historicalVersions) { + results.push(await regenerateVersion(pkgName, historicalVersion)); + } + results.push(await regenerateVersion(pkgName, currentVersion, false)); + return results; +} + +async function regenerateVersion( + pkgName: string, + version: string, + historical: boolean = true, +): Promise { + try { + if (historical) { + await $`npm run gen-api -- -p ${pkgName} -v ${version} --historical`; + } else { + await $`npm run gen-api -- -p ${pkgName} -v ${version}`; + } + + if ((await gitStatus()) !== "") { + await gitCommit(`Regenerate ${pkgName} ${version}`); + return `✅ ${pkgName} ${version} regenerated correctly`; + } else { + return `☑️ ${pkgName} ${version} is up-to-date`; + } + } catch (_) { + await gitRestore("."); + return `❌ ${pkgName} ${version} failed to regenerate`; + } +} + +async function getPackageVersions( + pkgName: string, + currentApisOnly: boolean, +): Promise<[string[], string]> { + const pkgDocsPath = `docs/api/${pkgName}`; + const historicalVersions: string[] = []; + + if (!currentApisOnly) { + const historicalFolders = ( + await readdir(`${pkgDocsPath}`, { withFileTypes: true }) + ).filter((file) => file.isDirectory() && file.name.match(/[0-9].*/)); + + for (const folder of historicalFolders) { + const historicalVersion = JSON.parse( + await readFile(`${pkgDocsPath}/${folder.name}/_package.json`, "utf-8"), + ); + historicalVersions.push(historicalVersion.version); + } + } + + const currentVersion = JSON.parse( + await readFile(`${pkgDocsPath}/_package.json`, "utf-8"), + ); + + return [historicalVersions, currentVersion.version]; +} + +async function validateGitStatus(): Promise { + const initialStatus = await gitStatus(); + if (initialStatus !== "") { + console.error(` + Repository must have clean Git state when calling + \`npm run regenerate-apis\`. 'git status' returned:\n\n${initialStatus}`); + process.exit(1); + } + + const currentBranch = await gitBranch(); + if (currentBranch == "main\n") { + console.error(` + Please create a dedicated branch to regenerate the API docs correctly. + Use 'git checkout -b ' to continue`); + process.exit(1); + } +} + +async function gitStatus(): Promise { + const status = await $`git status --porcelain`.quiet(); + return status.stdout; +} + +async function gitBranch(): Promise { + const status = await $`git branch --show-current`.quiet(); + return status.stdout; +} + +async function gitCommit(message: string): Promise { + await $`git add .`; + await $`git commit -m ${message}`; +} + +async function gitRestore(path: string): Promise { + await $`git restore ${path}`; +}