qiskit-documentation/scripts/js/commands/api/regenerateApiDocs.ts

208 lines
5.7 KiB
TypeScript

// 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 { Pkg } from "../../lib/api/Pkg.js";
import { zxMain } from "../../lib/zx.js";
import { pathExists } from "../../lib/fs.js";
import { generateHistoricalRedirects } from "./generateHistoricalRedirects.js";
interface Arguments {
[x: string]: unknown;
package?: string;
currentApisOnly: boolean;
skipDownload: 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?",
})
.option("skip-download", {
type: "boolean",
default: false,
description: "Don't redownload docs from Box",
})
.parseSync();
};
zxMain(async () => {
const args = readArgs();
await validateGitStatus();
for (const pkgName of Pkg.VALID_NAMES) {
if (args.package && pkgName != args.package) {
continue;
}
const [historicalVersions, currentVersion] = await getReleasedVersions(
pkgName,
args.currentApisOnly,
);
const maybeDevVersion = await getDevVersion(pkgName);
await processVersions(
pkgName,
args.skipDownload,
historicalVersions,
currentVersion,
maybeDevVersion,
);
}
await generateHistoricalRedirects();
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,
skipDownload: boolean,
historicalVersions: string[],
currentVersion: string,
maybeDevVersion: string | undefined,
): Promise<void> {
for (const historicalVersion of historicalVersions) {
await regenerateVersion(
pkgName,
historicalVersion,
skipDownload,
"historical",
);
}
await regenerateVersion(pkgName, currentVersion, skipDownload);
if (maybeDevVersion) {
await regenerateVersion(pkgName, maybeDevVersion, skipDownload, "dev");
}
}
async function regenerateVersion(
pkgName: string,
version: string,
skipDownload: boolean,
typeArgument?: "historical" | "dev",
): Promise<void> {
const command = ["npm", "run", "gen-api", "--", "-p", pkgName, "-v", version];
if (typeArgument) {
command.push(`--${typeArgument}`);
}
if (skipDownload) {
command.push("--skip-download");
}
try {
await $`${command}`;
if ((await gitStatus()) !== "") {
await gitCommit(`Regenerate ${pkgName} ${version}`);
console.log(`🚀 ${pkgName} ${version} regenerated correctly`);
} else {
console.log(`${pkgName} ${version} is already up-to-date`);
}
} catch (_) {
await gitRestore(".");
console.error(`${pkgName} ${version} failed to regenerate`);
}
}
async function getDevVersion(pkgName: string): Promise<string | undefined> {
const devPath = `docs/api/${pkgName}/dev`;
if (await pathExists(devPath)) {
return JSON.parse(await readFile(`${devPath}/_package.json`, "utf-8"))
.version;
}
return undefined;
}
async function getReleasedVersions(
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<void> {
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 <name-branch>' to continue`);
process.exit(1);
}
}
async function gitStatus(): Promise<string> {
const status = await $`git status --porcelain`.quiet();
return status.stdout;
}
async function gitBranch(): Promise<string> {
const status = await $`git branch --show-current`.quiet();
return status.stdout;
}
async function gitCommit(message: string): Promise<void> {
await $`git add .`;
await $`git commit -m ${message}`;
}
async function gitRestore(path: string): Promise<void> {
await $`git restore ${path}`;
}