Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: singleton octokit instance for shared throttling state #640

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 11 additions & 7 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ jobs:
strategy:
matrix:
node-version:
- '14.17'
- 16
- 18.0.0
- 19
- 20
os:
- ubuntu-latest
runs-on: "${{ matrix.os }}"
Expand All @@ -25,19 +26,22 @@ jobs:
with:
node-version: "${{ matrix.node-version }}"
cache: npm
- run: npm ci
- run: "npm run test:ci"
- run: npm clean-install
- run: npm test
test:
runs-on: ubuntu-latest
needs: test_matrix
steps:
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3
- uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3
with:
node-version: 16
node-version: "lts/*"
cache: npm
- run: npm ci
- run: npm clean-install
- run: npm audit signatures
- name: Ensure dependencies are compatible with the version of node
run: npx ls-engines
- run: npm run lint
- run: npx lockfile-lint --path package-lock.json
# https://github.com/lirantal/lockfile-lint#readme
- name: Scan lockfile for security issues
run: npx lockfile-lint --path package-lock.json
56 changes: 29 additions & 27 deletions README.md

Large diffs are not rendered by default.

101 changes: 73 additions & 28 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,67 +1,112 @@
/* eslint require-atomic-updates: off */

const {defaultTo, castArray} = require('lodash');
const verifyGitHub = require('./lib/verify');
const addChannelGitHub = require('./lib/add-channel');
const publishGitHub = require('./lib/publish');
const successGitHub = require('./lib/success');
const failGitHub = require('./lib/fail');
import { defaultTo, castArray } from "lodash-es";

import verifyGitHub from "./lib/verify.js";
import addChannelGitHub from "./lib/add-channel.js";
import publishGitHub from "./lib/publish.js";
import successGitHub from "./lib/success.js";
import failGitHub from "./lib/fail.js";
import { SemanticReleaseOctokit, getOctokitInstance } from "./lib/octokit.js";

let verified;
let octokit;

async function verifyConditions(pluginConfig, context) {
const {options} = context;
export async function verifyConditions(
pluginConfig,
context,
{ Octokit = SemanticReleaseOctokit } = {}
) {
const { options } = context;
// If the GitHub publish plugin is used and has `assets`, `successComment`, `failComment`, `failTitle`, `labels` or `assignees` configured, validate it now in order to prevent any release if the configuration is wrong
if (options.publish) {
const publishPlugin =
castArray(options.publish).find((config) => config.path && config.path === '@semantic-release/github') || {};
castArray(options.publish).find(
(config) => config.path && config.path === "@semantic-release/github"
) || {};

pluginConfig.assets = defaultTo(pluginConfig.assets, publishPlugin.assets);
pluginConfig.successComment = defaultTo(pluginConfig.successComment, publishPlugin.successComment);
pluginConfig.failComment = defaultTo(pluginConfig.failComment, publishPlugin.failComment);
pluginConfig.failTitle = defaultTo(pluginConfig.failTitle, publishPlugin.failTitle);
pluginConfig.successComment = defaultTo(
pluginConfig.successComment,
publishPlugin.successComment
);
pluginConfig.failComment = defaultTo(
pluginConfig.failComment,
publishPlugin.failComment
);
pluginConfig.failTitle = defaultTo(
pluginConfig.failTitle,
publishPlugin.failTitle
);
pluginConfig.labels = defaultTo(pluginConfig.labels, publishPlugin.labels);
pluginConfig.assignees = defaultTo(pluginConfig.assignees, publishPlugin.assignees);
pluginConfig.assignees = defaultTo(
pluginConfig.assignees,
publishPlugin.assignees
);
}

await verifyGitHub(pluginConfig, context);
octokit = octokit || getOctokitInstance(Octokit, pluginConfig, context);

await verifyGitHub(pluginConfig, context, { octokit });
verified = true;
}

async function publish(pluginConfig, context) {
export async function publish(
pluginConfig,
context,
{ Octokit = SemanticReleaseOctokit } = {}
) {
octokit = octokit || getOctokitInstance(Octokit, pluginConfig, context);

if (!verified) {
await verifyGitHub(pluginConfig, context);
await verifyGitHub(pluginConfig, context, { octokit });
verified = true;
}

return publishGitHub(pluginConfig, context);
return publishGitHub(pluginConfig, context, { octokit });
}

async function addChannel(pluginConfig, context) {
export async function addChannel(
pluginConfig,
context,
{ Octokit = SemanticReleaseOctokit } = {}
) {
octokit = octokit || getOctokitInstance(Octokit, pluginConfig, context);

if (!verified) {
await verifyGitHub(pluginConfig, context);
await verifyGitHub(pluginConfig, context, { octokit });
verified = true;
}

return addChannelGitHub(pluginConfig, context);
return addChannelGitHub(pluginConfig, context, { octokit });
}

async function success(pluginConfig, context) {
export async function success(
pluginConfig,
context,
{ Octokit = SemanticReleaseOctokit } = {}
) {
octokit = octokit || getOctokitInstance(Octokit, pluginConfig, context);

if (!verified) {
await verifyGitHub(pluginConfig, context);
await verifyGitHub(pluginConfig, context, { octokit });
verified = true;
}

await successGitHub(pluginConfig, context);
await successGitHub(pluginConfig, context, { octokit });
}

async function fail(pluginConfig, context) {
export async function fail(
pluginConfig,
context,
{ Octokit = SemanticReleaseOctokit } = {}
) {
octokit = octokit || getOctokitInstance(Octokit, pluginConfig, context);

if (!verified) {
await verifyGitHub(pluginConfig, context);
await verifyGitHub(pluginConfig, context, { octokit });
verified = true;
}

await failGitHub(pluginConfig, context);
await failGitHub(pluginConfig, context, { octokit });
}

module.exports = {verifyConditions, addChannel, publish, success, fail};
71 changes: 43 additions & 28 deletions lib/add-channel.js
Original file line number Diff line number Diff line change
@@ -1,52 +1,67 @@
const debug = require('debug')('semantic-release:github');
const {RELEASE_NAME} = require('./definitions/constants');
const parseGithubUrl = require('./parse-github-url');
const resolveConfig = require('./resolve-config');
const getClient = require('./get-client');
const isPrerelease = require('./is-prerelease');

module.exports = async (pluginConfig, context) => {
import debugFactory from "debug";

import { RELEASE_NAME } from "./definitions/constants.js";
import parseGithubUrl from "./parse-github-url.js";
import isPrerelease from "./is-prerelease.js";

const debug = debugFactory("semantic-release:github");

export default async function addChannel(pluginConfig, context, { octokit }) {
const {
options: {repositoryUrl},
options: { repositoryUrl },
branch,
nextRelease: {name, gitTag, notes},
nextRelease: { name, gitTag, notes },
logger,
} = context;
const {githubToken, githubUrl, githubApiPathPrefix, proxy} = resolveConfig(pluginConfig, context);
const {owner, repo} = parseGithubUrl(repositoryUrl);
const octokit = getClient({githubToken, githubUrl, githubApiPathPrefix, proxy});
const { owner, repo } = parseGithubUrl(repositoryUrl);
let releaseId;

const release = {owner, repo, name, prerelease: isPrerelease(branch), tag_name: gitTag};
const release = {
owner,
repo,
name,
prerelease: isPrerelease(branch),
tag_name: gitTag,
};

debug('release object: %O', release);
debug("release object: %O", release);

try {
({
data: {id: releaseId},
} = await octokit.request('GET /repos/{owner}/{repo}/releases/tags/{tag}', {owner, repo, tag: gitTag}));
data: { id: releaseId },
} = await octokit.request("GET /repos/{owner}/{repo}/releases/tags/{tag}", {
owner,
repo,
tag: gitTag,
}));
} catch (error) {
if (error.status === 404) {
logger.log('There is no release for tag %s, creating a new one', gitTag);
logger.log("There is no release for tag %s, creating a new one", gitTag);

const {
data: {html_url: url},
} = await octokit.request('POST /repos/{owner}/{repo}/releases', {...release, body: notes});
data: { html_url: url },
} = await octokit.request("POST /repos/{owner}/{repo}/releases", {
...release,
body: notes,
});

logger.log('Published GitHub release: %s', url);
return {url, name: RELEASE_NAME};
logger.log("Published GitHub release: %s", url);
return { url, name: RELEASE_NAME };
}

throw error;
}

debug('release release_id: %o', releaseId);
debug("release release_id: %o", releaseId);

const {
data: {html_url: url},
} = await octokit.request('PATCH /repos/{owner}/{repo}/releases/{release_id}', {...release, release_id: releaseId});
data: { html_url: url },
} = await octokit.request(
"PATCH /repos/{owner}/{repo}/releases/{release_id}",
{ ...release, release_id: releaseId }
);

logger.log('Updated GitHub release: %s', url);
logger.log("Updated GitHub release: %s", url);

return {url, name: RELEASE_NAME};
};
return { url, name: RELEASE_NAME };
}
6 changes: 2 additions & 4 deletions lib/definitions/constants.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
const ISSUE_ID = '<!-- semantic-release:github -->';
export const ISSUE_ID = "<!-- semantic-release:github -->";

const RELEASE_NAME = 'GitHub release';

module.exports = {ISSUE_ID, RELEASE_NAME};
export const RELEASE_NAME = "GitHub release";