semantic-release-hackage
Version:
A semantic-release plugin to publish Haskell packages to Hackage
96 lines (77 loc) • 3.33 kB
text/typescript
import axios from "axios";
import { PublishContext } from "semantic-release";
import { PluginConfig } from "./types/pluginConfig";
import { runExecCommand } from "./utils/exec";
import fs from "fs";
export const HACKAGE_CANDIDATES_URL = "https://hackage.haskell.org/packages/candidates";
const V2_HADDOCK_COMMAND = "cabal v2-haddock --haddock-for-hackage --enable-documentation";
export const postReleaseCandidate = async (sdistPath: string, hackageToken?: string): Promise<number | undefined> => {
try {
const headers = {
Accept: "text/plain",
Authorization: `X-ApiKey ${hackageToken}`,
"Content-Type": "multipart/form-data",
};
const req = await axios.post(HACKAGE_CANDIDATES_URL, { package: fs.createReadStream(sdistPath) }, { headers });
return req.status;
} catch (e: unknown) {
throw e instanceof Error
? new Error(`You do not have access to POST a file to ${HACKAGE_CANDIDATES_URL} , ${e.message}`)
: e;
}
};
export const publishRCDocumentation = async (
docsSdistPath: string,
url: string,
hackageToken?: string,
): Promise<number | undefined> => {
try {
const headers = {
Authorization: `X-ApiKey ${hackageToken}`,
"Content-Encoding": "gzip",
"Content-Type": "application/x-tar",
};
const req = await axios.put(url, fs.createReadStream(docsSdistPath), { headers });
return req.status;
} catch (e: unknown) {
throw e instanceof Error
? new Error(`You do not have access to POST a documentation file to ${url} , ${e.message}`)
: e;
}
};
export const publish = async (
{ packageName, versionPrefix, publishDocumentation }: PluginConfig,
{ logger, nextRelease, cwd }: PublishContext,
): Promise<void> => {
const realCwd = cwd ?? process.cwd();
logger.log("Current working directory: ", realCwd);
const { version } = nextRelease;
logger.log("Getting sdist path with version: ", version);
const filename = `${packageName}-${versionPrefix}${version}.tar.gz`;
const sdistPath = `${realCwd}/dist-newstyle/sdist/${filename}`;
logger.log("Uploading sdist: ", sdistPath);
logger.log("Post release candidate in hackage");
const status = await postReleaseCandidate(sdistPath, process.env.HACKAGE_TOKEN);
if (status !== 200) {
throw new Error(`Cannot post release candidate now, status: ${status}`);
}
logger.log("Checking publishDocumentation plugin configuration: ", publishDocumentation);
if (publishDocumentation) {
logger.log("Generating documentation");
const { warn, output } = await runExecCommand(V2_HADDOCK_COMMAND);
if (warn) {
logger.warn(warn);
}
logger.log(output);
logger.log("Publishing documentation");
const docsFilename = `${packageName}-${versionPrefix}${version}-docs.tar.gz`;
const docsSdistPath = `${realCwd}/dist-newstyle/${docsFilename}`;
const docsUrl = `https://hackage.haskell.org/package/${packageName}-${versionPrefix}${version}/candidate/docs`;
logger.log("Publishing file: ", docsFilename, " from: ", docsSdistPath);
const docStatus = await publishRCDocumentation(docsSdistPath, docsUrl, process.env.HACKAGE_TOKEN);
if (docStatus !== 200) {
throw new Error(`Cannot post release candidate documentation now, status: ${status}`);
}
}
logger.success("Publish done!");
};