@packtory/github-release-gate 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/github-release-gate/cli-runner.js +86 -0
- package/github-release-gate/cli-runner.js.map +1 -0
- package/github-release-gate/github-api.js +105 -0
- package/github-release-gate/github-api.js.map +1 -0
- package/github-release-gate/release-gate.js +84 -0
- package/github-release-gate/release-gate.js.map +1 -0
- package/github-release-gate/release-policy.js +56 -0
- package/github-release-gate/release-policy.js.map +1 -0
- package/github-release-gate/runner-config.js +58 -0
- package/github-release-gate/runner-config.js.map +1 -0
- package/package.json +45 -6
- package/packages/github-release-gate/github-release-gate.entry-point.js +71 -0
- package/packages/github-release-gate/github-release-gate.entry-point.js.map +1 -0
- package/readme.md +116 -4
- package/sbom.cdx.json +118 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) [2023]
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { createGitHubReleaseGateApi } from "./github-api.js";
|
|
2
|
+
import { applyPacktoryReleasePolicy } from "./release-policy.js";
|
|
3
|
+
import { evaluateGitHubReleaseGate } from "./release-gate.js";
|
|
4
|
+
import { createGitHubRepositoryContext, readGitHubReleaseGateRunnerConfig } from "./runner-config.js";
|
|
5
|
+
function formatReleaseAnalysisFailure(error) {
|
|
6
|
+
if (error.type === 'partial') {
|
|
7
|
+
return error.failures
|
|
8
|
+
.map((failure) => {
|
|
9
|
+
return failure.message;
|
|
10
|
+
})
|
|
11
|
+
.join('\n');
|
|
12
|
+
}
|
|
13
|
+
return error.issues.join('\n');
|
|
14
|
+
}
|
|
15
|
+
function writeLogs(write, logs) {
|
|
16
|
+
for (const line of logs) {
|
|
17
|
+
write(line);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function buildGitHubOutput(mainHeadSha, decision) {
|
|
21
|
+
return [
|
|
22
|
+
`main_head_sha=${mainHeadSha}`,
|
|
23
|
+
`should_publish=${decision.shouldPublish}`,
|
|
24
|
+
`reason=${decision.reason}`
|
|
25
|
+
].join('\n');
|
|
26
|
+
}
|
|
27
|
+
async function writeDecision(output, mainHeadSha, decision) {
|
|
28
|
+
writeLogs(output.stdoutWrite, decision.logs);
|
|
29
|
+
await output.fileManager.writeFile(output.githubOutputPath, `${buildGitHubOutput(mainHeadSha, decision)}\n`);
|
|
30
|
+
}
|
|
31
|
+
async function evaluatePacktoryPolicy(dependencies, config, now, timeGateDecision) {
|
|
32
|
+
const packtoryConfig = await dependencies.loadPacktoryConfig();
|
|
33
|
+
const releaseAnalysis = await dependencies.analyzeReleaseAgainstLatestPublished(packtoryConfig);
|
|
34
|
+
if (releaseAnalysis.result.isErr) {
|
|
35
|
+
throw new Error(formatReleaseAnalysisFailure(releaseAnalysis.result.error));
|
|
36
|
+
}
|
|
37
|
+
return applyPacktoryReleasePolicy({
|
|
38
|
+
baseDecision: timeGateDecision,
|
|
39
|
+
dependencyOnlyMinAgeDays: config.dependencyOnlyMinAgeDays,
|
|
40
|
+
now,
|
|
41
|
+
releaseAnalysis: releaseAnalysis.result.value
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
async function loadGitHubTimeGateDecision(dependencies, config) {
|
|
45
|
+
const githubApi = createGitHubReleaseGateApi(dependencies.fetch, createGitHubRepositoryContext(config));
|
|
46
|
+
const mainHeadSha = await githubApi.getMainBranchHeadSha();
|
|
47
|
+
const successfulMainCiRun = await githubApi.getLatestSuccessfulMainCiRun(config.ciWorkflowFile, mainHeadSha);
|
|
48
|
+
const pullRequestActivities = await githubApi.getOpenPullRequestActivities();
|
|
49
|
+
const now = dependencies.now();
|
|
50
|
+
return {
|
|
51
|
+
mainHeadSha,
|
|
52
|
+
now,
|
|
53
|
+
decision: evaluateGitHubReleaseGate({
|
|
54
|
+
ciWorkflowFile: config.ciWorkflowFile,
|
|
55
|
+
mainBranch: config.defaultBranch,
|
|
56
|
+
mainHeadSha,
|
|
57
|
+
maxLatencyHours: config.maxLatencyHours,
|
|
58
|
+
now,
|
|
59
|
+
pullRequestActivities,
|
|
60
|
+
quietPeriodMinutes: config.quietPeriodMinutes,
|
|
61
|
+
successfulMainCiRun
|
|
62
|
+
})
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export async function runGitHubReleaseGate(dependencies) {
|
|
66
|
+
const config = readGitHubReleaseGateRunnerConfig(dependencies.getEnvironmentVariable);
|
|
67
|
+
const { decision: timeGateDecision, mainHeadSha, now } = await loadGitHubTimeGateDecision(dependencies, config);
|
|
68
|
+
if (!timeGateDecision.shouldPublish) {
|
|
69
|
+
await writeDecision({
|
|
70
|
+
fileManager: dependencies.fileManager,
|
|
71
|
+
githubOutputPath: config.githubOutputPath,
|
|
72
|
+
stdoutWrite: dependencies.stdoutWrite
|
|
73
|
+
}, mainHeadSha, timeGateDecision);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const decision = await evaluatePacktoryPolicy(dependencies, config, now, {
|
|
77
|
+
...timeGateDecision,
|
|
78
|
+
shouldPublish: true
|
|
79
|
+
});
|
|
80
|
+
await writeDecision({
|
|
81
|
+
fileManager: dependencies.fileManager,
|
|
82
|
+
githubOutputPath: config.githubOutputPath,
|
|
83
|
+
stdoutWrite: dependencies.stdoutWrite
|
|
84
|
+
}, mainHeadSha, decision);
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=cli-runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-runner.js","sourceRoot":"","sources":["../../../../source/github-release-gate/cli-runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { isDefined } from 'remeda';
|
|
2
|
+
import { Octokit } from '@octokit/core';
|
|
3
|
+
import { paginateRest } from '@octokit/plugin-paginate-rest';
|
|
4
|
+
import { restEndpointMethods } from '@octokit/plugin-rest-endpoint-methods';
|
|
5
|
+
import { selectPullRequestActivityAt } from "./release-gate.js";
|
|
6
|
+
function parseTimestamp(timestamp) {
|
|
7
|
+
const date = new Date(timestamp);
|
|
8
|
+
if (Number.isNaN(date.getTime())) {
|
|
9
|
+
throw new TypeError(`Invalid timestamp: ${timestamp}`);
|
|
10
|
+
}
|
|
11
|
+
return date;
|
|
12
|
+
}
|
|
13
|
+
async function requestGitHub(request) {
|
|
14
|
+
try {
|
|
15
|
+
return await request;
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
const requestUrl = String(Reflect.get(new Object(Reflect.get(new Object(error), 'request')), 'url'));
|
|
19
|
+
const status = String(Reflect.get(new Object(error), 'status'));
|
|
20
|
+
const parsedUrl = new URL(requestUrl);
|
|
21
|
+
throw new Error(`GitHub API request failed (${status}) for ${parsedUrl.pathname}${parsedUrl.search}`, {
|
|
22
|
+
cause: error
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function createGitHubReleaseGateApi(fetchImplementation, context) {
|
|
27
|
+
const GitHubRestClient = Octokit.plugin(restEndpointMethods, paginateRest);
|
|
28
|
+
const requestContext = {
|
|
29
|
+
headers: {
|
|
30
|
+
accept: 'application/vnd.github+json',
|
|
31
|
+
authorization: `Bearer ${context.token}`,
|
|
32
|
+
'user-agent': 'packtory-github-release-gate',
|
|
33
|
+
'x-github-api-version': '2022-11-28'
|
|
34
|
+
},
|
|
35
|
+
owner: context.owner,
|
|
36
|
+
repo: context.repo
|
|
37
|
+
};
|
|
38
|
+
const octokit = new GitHubRestClient({
|
|
39
|
+
baseUrl: context.apiBaseUrl,
|
|
40
|
+
request: {
|
|
41
|
+
fetch: fetchImplementation,
|
|
42
|
+
headers: requestContext.headers
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
return {
|
|
46
|
+
async getMainBranchHeadSha() {
|
|
47
|
+
const branch = await requestGitHub(octokit.rest.repos.getBranch({
|
|
48
|
+
...requestContext,
|
|
49
|
+
branch: context.defaultBranch
|
|
50
|
+
}));
|
|
51
|
+
return branch.data.commit.sha;
|
|
52
|
+
},
|
|
53
|
+
async getLatestSuccessfulMainCiRun(ciWorkflowFile, headSha) {
|
|
54
|
+
const response = await requestGitHub(octokit.rest.actions.listWorkflowRuns({
|
|
55
|
+
...requestContext,
|
|
56
|
+
workflow_id: ciWorkflowFile,
|
|
57
|
+
branch: context.defaultBranch,
|
|
58
|
+
event: 'push',
|
|
59
|
+
head_sha: headSha,
|
|
60
|
+
status: 'completed',
|
|
61
|
+
per_page: 100
|
|
62
|
+
}));
|
|
63
|
+
const matchingRun = response.data.workflow_runs.find((run) => {
|
|
64
|
+
return run.head_sha === headSha && run.conclusion === 'success' && run.event === 'push';
|
|
65
|
+
});
|
|
66
|
+
if (matchingRun === undefined) {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
htmlUrl: matchingRun.html_url,
|
|
71
|
+
updatedAt: parseTimestamp(matchingRun.updated_at)
|
|
72
|
+
};
|
|
73
|
+
},
|
|
74
|
+
async getOpenPullRequestActivities() {
|
|
75
|
+
const openPullRequests = await requestGitHub(octokit.paginate(octokit.rest.pulls.list, {
|
|
76
|
+
...requestContext,
|
|
77
|
+
state: 'open',
|
|
78
|
+
base: context.defaultBranch,
|
|
79
|
+
per_page: 100
|
|
80
|
+
}));
|
|
81
|
+
return Promise.all(openPullRequests.map(async (pullRequest) => {
|
|
82
|
+
const timeline = await requestGitHub(octokit.paginate(octokit.rest.issues.listEventsForTimeline, {
|
|
83
|
+
...requestContext,
|
|
84
|
+
issue_number: pullRequest.number,
|
|
85
|
+
per_page: 100
|
|
86
|
+
}));
|
|
87
|
+
const timelineEvents = timeline
|
|
88
|
+
.map((event) => {
|
|
89
|
+
const timestamp = event.event === 'committed' ? event.committer?.date : (event.created_at ?? undefined);
|
|
90
|
+
if (timestamp === undefined) {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
return { createdAt: parseTimestamp(timestamp), event: event.event };
|
|
94
|
+
})
|
|
95
|
+
.filter(isDefined);
|
|
96
|
+
return {
|
|
97
|
+
number: pullRequest.number,
|
|
98
|
+
htmlUrl: pullRequest.html_url,
|
|
99
|
+
activityAt: selectPullRequestActivityAt(parseTimestamp(pullRequest.created_at), timelineEvents)
|
|
100
|
+
};
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=github-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-api.js","sourceRoot":"","sources":["../../../../source/github-release-gate/github-api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { maxDate } from "packtory/common/max-date.js";
|
|
2
|
+
const millisecondsPerMinute = 60_000;
|
|
3
|
+
const millisecondsPerHour = 3_600_000;
|
|
4
|
+
function createDecision(shouldPublish, reason, logs, decisionLog) {
|
|
5
|
+
return {
|
|
6
|
+
shouldPublish,
|
|
7
|
+
reason,
|
|
8
|
+
logs: [...logs, decisionLog]
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
function createMainCiSuccessLog(context) {
|
|
12
|
+
return `main CI success: ${context.mainHeadCiSuccessAt.toISOString()} (${context.mainHeadCiSuccessHtmlUrl})`;
|
|
13
|
+
}
|
|
14
|
+
function buildDecisionLogs(context) {
|
|
15
|
+
const logs = [
|
|
16
|
+
`main HEAD: ${context.input.mainHeadSha}`,
|
|
17
|
+
createMainCiSuccessLog(context),
|
|
18
|
+
`open PRs targeting ${context.input.mainBranch}: ${context.input.pullRequestActivities.length}`
|
|
19
|
+
];
|
|
20
|
+
for (const pullRequestActivity of context.input.pullRequestActivities) {
|
|
21
|
+
const activityAt = pullRequestActivity.activityAt.toISOString();
|
|
22
|
+
logs.push(`PR #${pullRequestActivity.number} activity: ${activityAt} ${pullRequestActivity.htmlUrl}`);
|
|
23
|
+
}
|
|
24
|
+
logs.push(`last relevant activity: ${context.lastRelevantActivityAt.toISOString()}`, `quiet period elapsed: ${context.quietPeriodElapsed}`, `max latency elapsed: ${context.maxLatencyElapsed}`);
|
|
25
|
+
return logs;
|
|
26
|
+
}
|
|
27
|
+
function createMissingCiDecision(input) {
|
|
28
|
+
const missingCiLog = `Skipping publish: no successful ${input.ciWorkflowFile} push run found for ${input.mainBranch} ` +
|
|
29
|
+
`HEAD ${input.mainHeadSha}.`;
|
|
30
|
+
return createDecision(false, 'ci_not_green', [], missingCiLog);
|
|
31
|
+
}
|
|
32
|
+
function hasElapsed(now, since, elapsedMilliseconds) {
|
|
33
|
+
return now.getTime() - since.getTime() >= elapsedMilliseconds;
|
|
34
|
+
}
|
|
35
|
+
function getElapsedFlags(input, mainHeadCiSuccessAt, lastRelevantActivityAt) {
|
|
36
|
+
return {
|
|
37
|
+
quietPeriodElapsed: hasElapsed(input.now, lastRelevantActivityAt, input.quietPeriodMinutes * millisecondsPerMinute),
|
|
38
|
+
maxLatencyElapsed: hasElapsed(input.now, mainHeadCiSuccessAt, input.maxLatencyHours * millisecondsPerHour)
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function isBranchActivityEvent(eventName) {
|
|
42
|
+
const activityEventName = String(eventName);
|
|
43
|
+
return (activityEventName === 'committed' ||
|
|
44
|
+
activityEventName === 'head_ref_deleted' ||
|
|
45
|
+
activityEventName === 'head_ref_force_pushed' ||
|
|
46
|
+
activityEventName === 'head_ref_restored');
|
|
47
|
+
}
|
|
48
|
+
function lastRelevantActivityAtFor(input, mainHeadCiSuccessAt) {
|
|
49
|
+
const otherActivityDates = [];
|
|
50
|
+
for (const pullRequestActivity of input.pullRequestActivities) {
|
|
51
|
+
otherActivityDates.push(pullRequestActivity.activityAt);
|
|
52
|
+
}
|
|
53
|
+
return maxDate(mainHeadCiSuccessAt, otherActivityDates);
|
|
54
|
+
}
|
|
55
|
+
export function selectPullRequestActivityAt(pullRequestCreatedAt, timelineEvents) {
|
|
56
|
+
const branchActivityDates = [];
|
|
57
|
+
for (const event of timelineEvents) {
|
|
58
|
+
if (isBranchActivityEvent(event.event)) {
|
|
59
|
+
branchActivityDates.push(event.createdAt);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return maxDate(pullRequestCreatedAt, branchActivityDates);
|
|
63
|
+
}
|
|
64
|
+
export function evaluateGitHubReleaseGate(input) {
|
|
65
|
+
if (input.successfulMainCiRun === undefined) {
|
|
66
|
+
return createMissingCiDecision(input);
|
|
67
|
+
}
|
|
68
|
+
const mainHeadCiSuccessAt = input.successfulMainCiRun.updatedAt;
|
|
69
|
+
const lastRelevantActivityAt = lastRelevantActivityAtFor(input, mainHeadCiSuccessAt);
|
|
70
|
+
const { quietPeriodElapsed, maxLatencyElapsed } = getElapsedFlags(input, mainHeadCiSuccessAt, lastRelevantActivityAt);
|
|
71
|
+
const logs = buildDecisionLogs({
|
|
72
|
+
input,
|
|
73
|
+
mainHeadCiSuccessAt,
|
|
74
|
+
mainHeadCiSuccessHtmlUrl: input.successfulMainCiRun.htmlUrl,
|
|
75
|
+
lastRelevantActivityAt,
|
|
76
|
+
quietPeriodElapsed,
|
|
77
|
+
maxLatencyElapsed
|
|
78
|
+
});
|
|
79
|
+
if (!quietPeriodElapsed && !maxLatencyElapsed) {
|
|
80
|
+
return createDecision(false, 'activity_not_stale', logs, 'Skipping publish: repository activity is not stale enough yet.');
|
|
81
|
+
}
|
|
82
|
+
return createDecision(true, quietPeriodElapsed ? 'quiet_period_elapsed' : 'max_latency_elapsed', logs, 'Publishing is allowed by the release gate.');
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=release-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"release-gate.js","sourceRoot":"","sources":["../../../../source/github-release-gate/release-gate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AA2ChD,MAAM,qBAAqB,GAAG,MAAM,CAAC;AACrC,MAAM,mBAAmB,GAAG,SAAS,CAAC;AAWtC,SAAS,cAAc,CACnB,aAAsB,EACtB,MAA2C,EAC3C,IAAuB,EACvB,WAAmB;IAEnB,OAAO;QACH,aAAa;QACb,MAAM;QACN,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,WAAW,CAAC;KAC/B,CAAC;AACN,CAAC;AAED,SAAS,sBAAsB,CAAC,OAA2B;IACvD,OAAO,oBAAoB,OAAO,CAAC,mBAAmB,CAAC,WAAW,EAAE,KAAK,OAAO,CAAC,wBAAwB,GAAG,CAAC;AACjH,CAAC;AAED,SAAS,iBAAiB,CAAC,OAA2B;IAClD,MAAM,IAAI,GAAG;QACT,cAAc,OAAO,CAAC,KAAK,CAAC,WAAW,EAAE;QACzC,sBAAsB,CAAC,OAAO,CAAC;QAC/B,sBAAsB,OAAO,CAAC,KAAK,CAAC,UAAU,KAAK,OAAO,CAAC,KAAK,CAAC,qBAAqB,CAAC,MAAM,EAAE;KAClG,CAAC;IAEF,KAAK,MAAM,mBAAmB,IAAI,OAAO,CAAC,KAAK,CAAC,qBAAqB,EAAE,CAAC;QACpE,MAAM,UAAU,GAAG,mBAAmB,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC;QAChE,IAAI,CAAC,IAAI,CAAC,OAAO,mBAAmB,CAAC,MAAM,cAAc,UAAU,IAAI,mBAAmB,CAAC,OAAO,EAAE,CAAC,CAAC;IAC1G,CAAC;IAED,IAAI,CAAC,IAAI,CACL,2BAA2B,OAAO,CAAC,sBAAsB,CAAC,WAAW,EAAE,EAAE,EACzE,yBAAyB,OAAO,CAAC,kBAAkB,EAAE,EACrD,wBAAwB,OAAO,CAAC,iBAAiB,EAAE,CACtD,CAAC;IAEF,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,SAAS,uBAAuB,CAAC,KAA6B;IAC1D,MAAM,YAAY,GACd,mCAAmC,KAAK,CAAC,cAAc,uBAAuB,KAAK,CAAC,UAAU,GAAG;QACjG,QAAQ,KAAK,CAAC,WAAW,GAAG,CAAC;IAEjC,OAAO,cAAc,CAAC,KAAK,EAAE,cAAc,EAAE,EAAE,EAAE,YAAY,CAAC,CAAC;AACnE,CAAC;AAED,SAAS,UAAU,CAAC,GAAS,EAAE,KAAW,EAAE,mBAA2B;IACnE,OAAO,GAAG,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,OAAO,EAAE,IAAI,mBAAmB,CAAC;AAClE,CAAC;AAED,SAAS,eAAe,CACpB,KAA6B,EAC7B,mBAAyB,EACzB,sBAA4B;IAK5B,OAAO;QACH,kBAAkB,EAAE,UAAU,CAC1B,KAAK,CAAC,GAAG,EACT,sBAAsB,EACtB,KAAK,CAAC,kBAAkB,GAAG,qBAAqB,CACnD;QACD,iBAAiB,EAAE,UAAU,CAAC,KAAK,CAAC,GAAG,EAAE,mBAAmB,EAAE,KAAK,CAAC,eAAe,GAAG,mBAAmB,CAAC;KAC7G,CAAC;AACN,CAAC;AAED,SAAS,qBAAqB,CAAC,SAA6B;IACxD,MAAM,iBAAiB,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;IAC5C,OAAO,CACH,iBAAiB,KAAK,WAAW;QACjC,iBAAiB,KAAK,kBAAkB;QACxC,iBAAiB,KAAK,uBAAuB;QAC7C,iBAAiB,KAAK,mBAAmB,CAC5C,CAAC;AACN,CAAC;AAED,SAAS,yBAAyB,CAAC,KAA6B,EAAE,mBAAyB;IACvF,MAAM,kBAAkB,GAAW,EAAE,CAAC;IAEtC,KAAK,MAAM,mBAAmB,IAAI,KAAK,CAAC,qBAAqB,EAAE,CAAC;QAC5D,kBAAkB,CAAC,IAAI,CAAC,mBAAmB,CAAC,UAAU,CAAC,CAAC;IAC5D,CAAC;IAED,OAAO,OAAO,CAAC,mBAAmB,EAAE,kBAAkB,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,2BAA2B,CACvC,oBAA0B,EAC1B,cAAmD;IAEnD,MAAM,mBAAmB,GAAW,EAAE,CAAC;IACvC,KAAK,MAAM,KAAK,IAAI,cAAc,EAAE,CAAC;QACjC,IAAI,qBAAqB,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;YACrC,mBAAmB,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAC9C,CAAC;IACL,CAAC;IAED,OAAO,OAAO,CAAC,oBAAoB,EAAE,mBAAmB,CAAC,CAAC;AAC9D,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,KAA6B;IACnE,IAAI,KAAK,CAAC,mBAAmB,KAAK,SAAS,EAAE,CAAC;QAC1C,OAAO,uBAAuB,CAAC,KAAK,CAAC,CAAC;IAC1C,CAAC;IAED,MAAM,mBAAmB,GAAG,KAAK,CAAC,mBAAmB,CAAC,SAAS,CAAC;IAChE,MAAM,sBAAsB,GAAG,yBAAyB,CAAC,KAAK,EAAE,mBAAmB,CAAC,CAAC;IACrF,MAAM,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,GAAG,eAAe,CAC7D,KAAK,EACL,mBAAmB,EACnB,sBAAsB,CACzB,CAAC;IACF,MAAM,IAAI,GAAG,iBAAiB,CAAC;QAC3B,KAAK;QACL,mBAAmB;QACnB,wBAAwB,EAAE,KAAK,CAAC,mBAAmB,CAAC,OAAO;QAC3D,sBAAsB;QACtB,kBAAkB;QAClB,iBAAiB;KACpB,CAAC,CAAC;IAEH,IAAI,CAAC,kBAAkB,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAC5C,OAAO,cAAc,CACjB,KAAK,EACL,oBAAoB,EACpB,IAAI,EACJ,gEAAgE,CACnE,CAAC;IACN,CAAC;IAED,OAAO,cAAc,CACjB,IAAI,EACJ,kBAAkB,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,qBAAqB,EACnE,IAAI,EACJ,4CAA4C,CAC/C,CAAC;AACN,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { releaseAnalysisClassification } from "packtory/packtory/packtory-results.js";
|
|
2
|
+
const millisecondsPerDay = 86_400_000;
|
|
3
|
+
function formatPublishedAt(date) {
|
|
4
|
+
return date === undefined ? '(unknown)' : date.toISOString();
|
|
5
|
+
}
|
|
6
|
+
function buildReleaseAnalysisLogs(releaseAnalysis) {
|
|
7
|
+
const logs = [
|
|
8
|
+
`release classification: ${releaseAnalysis.classification}`,
|
|
9
|
+
`most recent published package timestamp: ${formatPublishedAt(releaseAnalysis.mostRecentPublishedAt)}`
|
|
10
|
+
];
|
|
11
|
+
for (const analysis of releaseAnalysis.packageAnalyses) {
|
|
12
|
+
const packageLog = `package ${analysis.name}: ${analysis.classification}` +
|
|
13
|
+
` latest=${analysis.latestPublishedVersion ?? '(unpublished)'}` +
|
|
14
|
+
` publishedAt=${formatPublishedAt(analysis.latestPublishedAt)}`;
|
|
15
|
+
logs.push(packageLog);
|
|
16
|
+
}
|
|
17
|
+
return logs;
|
|
18
|
+
}
|
|
19
|
+
function minAgeElapsed(now, publishedAt, dependencyOnlyMinAgeDays) {
|
|
20
|
+
return now.getTime() - publishedAt.getTime() >= dependencyOnlyMinAgeDays * millisecondsPerDay;
|
|
21
|
+
}
|
|
22
|
+
function formatMinimumAgePendingLog(dependencyOnlyMinAgeDays) {
|
|
23
|
+
const intro = 'Skipping publish: dependency-only releases must age for at least';
|
|
24
|
+
return `${intro} ${dependencyOnlyMinAgeDays} day(s).`;
|
|
25
|
+
}
|
|
26
|
+
function formatMinimumAgeElapsedLog(dependencyOnlyMinAgeDays) {
|
|
27
|
+
const intro = 'Publishing is allowed because the dependency-only minimum age of';
|
|
28
|
+
return `${intro} ${dependencyOnlyMinAgeDays} day(s) has elapsed.`;
|
|
29
|
+
}
|
|
30
|
+
function createPolicyDecision(shouldPublish, reason, logs, policyLog) {
|
|
31
|
+
return {
|
|
32
|
+
shouldPublish,
|
|
33
|
+
reason,
|
|
34
|
+
logs: [...logs, policyLog]
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export function applyPacktoryReleasePolicy(input) {
|
|
38
|
+
const logs = [...input.baseDecision.logs, ...buildReleaseAnalysisLogs(input.releaseAnalysis)];
|
|
39
|
+
if (input.releaseAnalysis.classification === releaseAnalysisClassification.unchanged) {
|
|
40
|
+
return createPolicyDecision(false, 'release_unchanged', logs, 'Skipping publish: the next Packtory release would be unchanged versus npm latest.');
|
|
41
|
+
}
|
|
42
|
+
if (input.releaseAnalysis.classification !== releaseAnalysisClassification.dependencyOnly) {
|
|
43
|
+
return {
|
|
44
|
+
...input.baseDecision,
|
|
45
|
+
logs: [...logs, 'Publishing is allowed by the Packtory release policy.']
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
if (input.releaseAnalysis.mostRecentPublishedAt === undefined) {
|
|
49
|
+
return createPolicyDecision(true, 'dependency_only_published_at_unknown', logs, 'Publishing is allowed because this dependency-only release has no publishedAt baseline to delay from.');
|
|
50
|
+
}
|
|
51
|
+
if (!minAgeElapsed(input.now, input.releaseAnalysis.mostRecentPublishedAt, input.dependencyOnlyMinAgeDays)) {
|
|
52
|
+
return createPolicyDecision(false, 'dependency_only_min_age_not_elapsed', logs, formatMinimumAgePendingLog(input.dependencyOnlyMinAgeDays));
|
|
53
|
+
}
|
|
54
|
+
return createPolicyDecision(true, 'dependency_only_min_age_elapsed', logs, formatMinimumAgeElapsedLog(input.dependencyOnlyMinAgeDays));
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=release-policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"release-policy.js","sourceRoot":"","sources":["../../../../source/github-release-gate/release-policy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const defaultDependencyOnlyMinAgeDays = 7;
|
|
2
|
+
const defaultMaxLatencyHours = 24;
|
|
3
|
+
const defaultQuietPeriodMinutes = 45;
|
|
4
|
+
function defaultCiWorkflowFile() {
|
|
5
|
+
return 'ci.yml';
|
|
6
|
+
}
|
|
7
|
+
function defaultGitHubApiBaseUrl() {
|
|
8
|
+
return 'https://api.github.com';
|
|
9
|
+
}
|
|
10
|
+
function defaultMainBranch() {
|
|
11
|
+
return 'main';
|
|
12
|
+
}
|
|
13
|
+
function parseInteger(value, fallbackValue) {
|
|
14
|
+
return value === undefined ? fallbackValue : Number.parseInt(value, 10);
|
|
15
|
+
}
|
|
16
|
+
function getRequiredEnvironmentVariable(getEnvironmentVariable, variableName) {
|
|
17
|
+
const value = getEnvironmentVariable(variableName);
|
|
18
|
+
if (value === undefined) {
|
|
19
|
+
throw new Error(`Missing ${variableName} environment variable`);
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
export function readGitHubReleaseGateRunnerConfig(getEnvironmentVariable) {
|
|
24
|
+
return {
|
|
25
|
+
ciWorkflowFile: getEnvironmentVariable('CI_WORKFLOW_FILE') ?? defaultCiWorkflowFile(),
|
|
26
|
+
dependencyOnlyMinAgeDays: parseInteger(getEnvironmentVariable('DEPENDENCY_ONLY_MIN_AGE_DAYS'), defaultDependencyOnlyMinAgeDays),
|
|
27
|
+
defaultBranch: getEnvironmentVariable('DEFAULT_BRANCH') ?? defaultMainBranch(),
|
|
28
|
+
githubApiBaseUrl: getEnvironmentVariable('GITHUB_API_BASE_URL') ?? defaultGitHubApiBaseUrl(),
|
|
29
|
+
githubOutputPath: getRequiredEnvironmentVariable(getEnvironmentVariable, 'GITHUB_OUTPUT'),
|
|
30
|
+
maxLatencyHours: parseInteger(getEnvironmentVariable('MAX_LATENCY_HOURS'), defaultMaxLatencyHours),
|
|
31
|
+
quietPeriodMinutes: parseInteger(getEnvironmentVariable('QUIET_PERIOD_MINUTES'), defaultQuietPeriodMinutes),
|
|
32
|
+
repository: getRequiredEnvironmentVariable(getEnvironmentVariable, 'GITHUB_REPOSITORY'),
|
|
33
|
+
token: getRequiredEnvironmentVariable(getEnvironmentVariable, 'GITHUB_TOKEN')
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function splitRepository(repository) {
|
|
37
|
+
const firstSlashIndex = repository.indexOf('/');
|
|
38
|
+
if (firstSlashIndex <= 0 ||
|
|
39
|
+
firstSlashIndex !== repository.lastIndexOf('/') ||
|
|
40
|
+
firstSlashIndex === repository.length - 1) {
|
|
41
|
+
throw new Error(`Invalid GITHUB_REPOSITORY value: ${repository}`);
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
owner: repository.slice(0, firstSlashIndex),
|
|
45
|
+
repo: repository.slice(firstSlashIndex + 1)
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export function createGitHubRepositoryContext(config) {
|
|
49
|
+
const repository = splitRepository(config.repository);
|
|
50
|
+
return {
|
|
51
|
+
apiBaseUrl: config.githubApiBaseUrl,
|
|
52
|
+
defaultBranch: config.defaultBranch,
|
|
53
|
+
owner: repository.owner,
|
|
54
|
+
repo: repository.repo,
|
|
55
|
+
token: config.token
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=runner-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner-config.js","sourceRoot":"","sources":["../../../../source/github-release-gate/runner-config.ts"],"names":[],"mappings":"AAoBA,MAAM,+BAA+B,GAAG,CAAC,CAAC;AAC1C,MAAM,sBAAsB,GAAG,EAAE,CAAC;AAClC,MAAM,yBAAyB,GAAG,EAAE,CAAC;AAErC,SAAS,qBAAqB;IAC1B,OAAO,QAAQ,CAAC;AACpB,CAAC;AAED,SAAS,uBAAuB;IAC5B,OAAO,wBAAwB,CAAC;AACpC,CAAC;AAED,SAAS,iBAAiB;IACtB,OAAO,MAAM,CAAC;AAClB,CAAC;AAED,SAAS,YAAY,CAAC,KAAyB,EAAE,aAAqB;IAClE,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAC5E,CAAC;AAED,SAAS,8BAA8B,CACnC,sBAAoE,EACpE,YAAoB;IAEpB,MAAM,KAAK,GAAG,sBAAsB,CAAC,YAAY,CAAC,CAAC;IAEnD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,WAAW,YAAY,uBAAuB,CAAC,CAAC;IACpE,CAAC;IAED,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,iCAAiC,CAC7C,sBAAoE;IAEpE,OAAO;QACH,cAAc,EAAE,sBAAsB,CAAC,kBAAkB,CAAC,IAAI,qBAAqB,EAAE;QACrF,wBAAwB,EAAE,YAAY,CAClC,sBAAsB,CAAC,8BAA8B,CAAC,EACtD,+BAA+B,CAClC;QACD,aAAa,EAAE,sBAAsB,CAAC,gBAAgB,CAAC,IAAI,iBAAiB,EAAE;QAC9E,gBAAgB,EAAE,sBAAsB,CAAC,qBAAqB,CAAC,IAAI,uBAAuB,EAAE;QAC5F,gBAAgB,EAAE,8BAA8B,CAAC,sBAAsB,EAAE,eAAe,CAAC;QACzF,eAAe,EAAE,YAAY,CAAC,sBAAsB,CAAC,mBAAmB,CAAC,EAAE,sBAAsB,CAAC;QAClG,kBAAkB,EAAE,YAAY,CAAC,sBAAsB,CAAC,sBAAsB,CAAC,EAAE,yBAAyB,CAAC;QAC3G,UAAU,EAAE,8BAA8B,CAAC,sBAAsB,EAAE,mBAAmB,CAAC;QACvF,KAAK,EAAE,8BAA8B,CAAC,sBAAsB,EAAE,cAAc,CAAC;KAChF,CAAC;AACN,CAAC;AAED,SAAS,eAAe,CAAC,UAAkB;IACvC,MAAM,eAAe,GAAG,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAEhD,IACI,eAAe,IAAI,CAAC;QACpB,eAAe,KAAK,UAAU,CAAC,WAAW,CAAC,GAAG,CAAC;QAC/C,eAAe,KAAK,UAAU,CAAC,MAAM,GAAG,CAAC,EAC3C,CAAC;QACC,MAAM,IAAI,KAAK,CAAC,oCAAoC,UAAU,EAAE,CAAC,CAAC;IACtE,CAAC;IAED,OAAO;QACH,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,eAAe,CAAC;QAC3C,IAAI,EAAE,UAAU,CAAC,KAAK,CAAC,eAAe,GAAG,CAAC,CAAC;KAC9C,CAAC;AACN,CAAC;AAED,MAAM,UAAU,6BAA6B,CACzC,MAA+C;IAE/C,MAAM,UAAU,GAAG,eAAe,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAEtD,OAAO;QACH,UAAU,EAAE,MAAM,CAAC,gBAAgB;QACnC,aAAa,EAAE,MAAM,CAAC,aAAa;QACnC,KAAK,EAAE,UAAU,CAAC,KAAK;QACvB,IAAI,EAAE,UAAU,CAAC,IAAI;QACrB,KAAK,EAAE,MAAM,CAAC,KAAK;KACtB,CAAC;AACN,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,46 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
"author": "Mathias Schreck <schreck.mathias@gmail.com>",
|
|
3
|
+
"bin": {
|
|
4
|
+
"github-release-gate": "./packages/github-release-gate/github-release-gate.entry-point.js"
|
|
5
|
+
},
|
|
6
|
+
"contributors": [
|
|
7
|
+
"Christian Rackerseder <github@echooff.de>"
|
|
8
|
+
],
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@octokit/core": "7.0.6",
|
|
11
|
+
"@octokit/plugin-paginate-rest": "14.0.0",
|
|
12
|
+
"@octokit/plugin-rest-endpoint-methods": "17.0.0",
|
|
13
|
+
"packtory": "0.0.30",
|
|
14
|
+
"remeda": "2.37.0"
|
|
15
|
+
},
|
|
16
|
+
"description": "GitHub Actions release gate that batches packtory publishes by waiting for repository activity to settle.",
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": "^24 || ^26"
|
|
19
|
+
},
|
|
20
|
+
"exports": {
|
|
21
|
+
"./package.json": "./package.json"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"bundler",
|
|
25
|
+
"bundling",
|
|
26
|
+
"modules",
|
|
27
|
+
"monorepo",
|
|
28
|
+
"npm",
|
|
29
|
+
"package",
|
|
30
|
+
"packaging",
|
|
31
|
+
"publish",
|
|
32
|
+
"publishing",
|
|
33
|
+
"versioning"
|
|
34
|
+
],
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"name": "@packtory/github-release-gate",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "git+ssh://git@github.com/enormora/packtory.git"
|
|
40
|
+
},
|
|
41
|
+
"sideEffects": [
|
|
42
|
+
"./packages/github-release-gate/github-release-gate.entry-point.js"
|
|
43
|
+
],
|
|
44
|
+
"type": "module",
|
|
45
|
+
"version": "0.0.3"
|
|
46
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
3
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
4
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
5
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
return path;
|
|
9
|
+
};
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { hasProp, isPlainObject } from 'remeda';
|
|
13
|
+
import { createFileManager } from "packtory/file-manager/file-manager.js";
|
|
14
|
+
import { runGitHubReleaseGate } from "../../github-release-gate/cli-runner.js";
|
|
15
|
+
function getEnvironmentVariable(variableName) {
|
|
16
|
+
const value = process.env[variableName];
|
|
17
|
+
return value === undefined || value.length === 0 ? undefined : value;
|
|
18
|
+
}
|
|
19
|
+
function isFunction(value) {
|
|
20
|
+
return typeof value === 'function';
|
|
21
|
+
}
|
|
22
|
+
function unwrapConfigModule(module) {
|
|
23
|
+
if (hasProp(module, 'config')) {
|
|
24
|
+
return module.config;
|
|
25
|
+
}
|
|
26
|
+
if (hasProp(module, 'buildConfig')) {
|
|
27
|
+
const { buildConfig } = module;
|
|
28
|
+
if (isFunction(buildConfig)) {
|
|
29
|
+
return buildConfig();
|
|
30
|
+
}
|
|
31
|
+
throw new Error('Named export of "buildConfig" config file is not a function');
|
|
32
|
+
}
|
|
33
|
+
throw new Error('Config file doesn’t have a named export "config" nor "buildConfig"');
|
|
34
|
+
}
|
|
35
|
+
async function loadPacktoryConfigFromCwd() {
|
|
36
|
+
const configFilePath = path.join(process.cwd(), 'packtory.config.js');
|
|
37
|
+
const module = await import(__rewriteRelativeImportExtension(configFilePath));
|
|
38
|
+
if (!isPlainObject(module)) {
|
|
39
|
+
throw new Error('Invalid config file');
|
|
40
|
+
}
|
|
41
|
+
return unwrapConfigModule(module);
|
|
42
|
+
}
|
|
43
|
+
function createDependencies() {
|
|
44
|
+
return {
|
|
45
|
+
analyzeReleaseAgainstLatestPublished: async (config) => {
|
|
46
|
+
const packtory = await import("packtory");
|
|
47
|
+
return packtory.analyzeReleaseAgainstLatestPublished(config);
|
|
48
|
+
},
|
|
49
|
+
fetch: globalThis.fetch,
|
|
50
|
+
fileManager: createFileManager({ hostFileSystem: fs.promises }),
|
|
51
|
+
getEnvironmentVariable,
|
|
52
|
+
loadPacktoryConfig: loadPacktoryConfigFromCwd,
|
|
53
|
+
now: () => {
|
|
54
|
+
return new Date();
|
|
55
|
+
},
|
|
56
|
+
stdoutWrite: (message) => {
|
|
57
|
+
process.stdout.write(`${message}\n`);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
process.exitCode = await (async () => {
|
|
62
|
+
try {
|
|
63
|
+
await runGitHubReleaseGate(createDependencies());
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
68
|
+
return 1;
|
|
69
|
+
}
|
|
70
|
+
})();
|
|
71
|
+
//# sourceMappingURL=github-release-gate.entry-point.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-release-gate.entry-point.js","sourceRoot":"","sources":["../../../../../source/packages/github-release-gate/github-release-gate.entry-point.ts"],"names":[],"mappings":"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"}
|
package/readme.md
CHANGED
|
@@ -1,7 +1,119 @@
|
|
|
1
1
|
# @packtory/github-release-gate
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
can subsequently be configured for it. It contains no real package content and is published already
|
|
5
|
-
deprecated.
|
|
3
|
+
**GitHub Actions release gate for batching publishes without manual approval**
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
This package evaluates whether a repository is quiet enough to publish from `main`. It is designed for GitHub Actions workflows that want to batch human and bot changes without blocking forever on draft PRs, failing PRs, or long-lived PRs.
|
|
6
|
+
|
|
7
|
+
## Motivation
|
|
8
|
+
|
|
9
|
+
A publish workflow usually wants two things that pull in opposite directions:
|
|
10
|
+
|
|
11
|
+
- avoid spamming releases when several changes land close together
|
|
12
|
+
- avoid making a human change wait for a coarse scheduled release window
|
|
13
|
+
|
|
14
|
+
Publishing on every successful merge solves the second problem, but creates too many releases during bursts of activity. That is especially visible with bot-driven update trains such as Renovate, but it can also happen with a sequence of human PRs landing within a short period.
|
|
15
|
+
|
|
16
|
+
On the other hand, a simple scheduled release, such as "publish every hour" or "publish every night", solves the release-spam problem by batching everything together, but it also delays legitimate changes even when the repository has already become stable enough to publish.
|
|
17
|
+
|
|
18
|
+
This tool aims to sit between those extremes. It tries to find a practical balance:
|
|
19
|
+
|
|
20
|
+
- wait a little while after relevant repository activity so nearby changes can naturally batch together
|
|
21
|
+
- do not wait forever if activity keeps happening
|
|
22
|
+
- only allow publishing from a `main` commit that has already passed CI
|
|
23
|
+
- slow-roll low-signal dependency-only releases without delaying substantive changes the same way
|
|
24
|
+
|
|
25
|
+
The result is a release gate that is more selective than "publish on every merge", but much more responsive than a coarse scheduled release cadence.
|
|
26
|
+
|
|
27
|
+
## Detailed Concept
|
|
28
|
+
|
|
29
|
+
The gate has two layers:
|
|
30
|
+
|
|
31
|
+
- a GitHub activity gate
|
|
32
|
+
- a Packtory release-content policy
|
|
33
|
+
|
|
34
|
+
The GitHub activity gate is based on two timing signals:
|
|
35
|
+
|
|
36
|
+
- `quiet period`: how long the repository should stay quiet before a publish is allowed
|
|
37
|
+
- `max latency`: how long a green `main` commit may wait before a publish is forced
|
|
38
|
+
|
|
39
|
+
It then evaluates the current repository state using the following rules:
|
|
40
|
+
|
|
41
|
+
- Require a successful CI run for the current `main` HEAD.
|
|
42
|
+
- Treat pushes to open PR branches as release-relevant activity, including PRs from forks.
|
|
43
|
+
- Delay publishing until activity has been stale for a configurable quiet period.
|
|
44
|
+
- Force publication once a configurable maximum latency has elapsed since the current `main` HEAD first went green.
|
|
45
|
+
- Once the GitHub gate opens, analyze the pending Packtory release against npm `latest`.
|
|
46
|
+
- If the release is unchanged, skip publishing.
|
|
47
|
+
- If the release is substantive or a first publish, publish immediately.
|
|
48
|
+
- If the release is dependency-only, require an additional minimum age since the most recent published package version.
|
|
49
|
+
|
|
50
|
+
More concretely:
|
|
51
|
+
|
|
52
|
+
1. Resolve the current `main` HEAD SHA.
|
|
53
|
+
2. Look up the latest successful CI run for that exact SHA.
|
|
54
|
+
3. Inspect all open PRs targeting the default branch.
|
|
55
|
+
4. For each PR, derive its latest relevant branch activity from the PR timeline.
|
|
56
|
+
5. Compute the latest relevant activity timestamp across:
|
|
57
|
+
- the successful CI completion time for `main` HEAD
|
|
58
|
+
- all open PR branch activity timestamps
|
|
59
|
+
6. Open the GitHub activity gate if either condition is true:
|
|
60
|
+
- the quiet period has elapsed since the latest relevant activity
|
|
61
|
+
- the max latency has elapsed since `main` HEAD first went green
|
|
62
|
+
7. Run Packtory release analysis against npm `latest`.
|
|
63
|
+
8. Apply the release-content policy:
|
|
64
|
+
- `unchanged`: skip
|
|
65
|
+
- `substantive`: publish
|
|
66
|
+
- `first-publish`: publish
|
|
67
|
+
- `dependency-only`: publish only after `DEPENDENCY_ONLY_MIN_AGE_DAYS`
|
|
68
|
+
|
|
69
|
+
This means:
|
|
70
|
+
|
|
71
|
+
- a burst of nearby merges tends to collapse into a single later publish
|
|
72
|
+
- active PR pushes keep the gate closed for a while, so more changes can batch together
|
|
73
|
+
- a repository that never fully settles will still publish eventually once max latency is reached
|
|
74
|
+
- stale draft PRs, failing PRs, or intentionally long-lived PRs do not need special-case state handling, because the signal is branch activity, not PR labels or mergeability
|
|
75
|
+
- dependency-only churn can be intentionally delayed without delaying substantive source releases by the same amount
|
|
76
|
+
|
|
77
|
+
## Decision Outputs
|
|
78
|
+
|
|
79
|
+
- `should_publish=true|false`
|
|
80
|
+
- `reason=ci_not_green|activity_not_stale|quiet_period_elapsed|max_latency_elapsed|release_unchanged|dependency_only_min_age_not_elapsed|dependency_only_min_age_elapsed|dependency_only_published_at_unknown`
|
|
81
|
+
- `main_head_sha=<sha>`
|
|
82
|
+
|
|
83
|
+
When run inside GitHub Actions, the tool writes these values to `$GITHUB_OUTPUT`.
|
|
84
|
+
|
|
85
|
+
## Installation
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
npm install -D @packtory/github-release-gate
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The package ships a `github-release-gate` executable that writes its decision to `$GITHUB_OUTPUT` when invoked from a GitHub Actions step.
|
|
92
|
+
|
|
93
|
+
## Usage in a Workflow
|
|
94
|
+
|
|
95
|
+
```yaml
|
|
96
|
+
- name: Install release gate
|
|
97
|
+
run: npm install -D --ignore-scripts @packtory/github-release-gate
|
|
98
|
+
- name: Evaluate GitHub release gate
|
|
99
|
+
id: release-gate
|
|
100
|
+
run: npx github-release-gate
|
|
101
|
+
env:
|
|
102
|
+
CI_WORKFLOW_FILE: ci.yml
|
|
103
|
+
DEFAULT_BRANCH: main
|
|
104
|
+
DEPENDENCY_ONLY_MIN_AGE_DAYS: '7'
|
|
105
|
+
GITHUB_TOKEN: ${{ github.token }}
|
|
106
|
+
MAX_LATENCY_HOURS: '24'
|
|
107
|
+
QUIET_PERIOD_MINUTES: '45'
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Environment Variables
|
|
111
|
+
|
|
112
|
+
- `GITHUB_TOKEN` and `GITHUB_REPOSITORY` are required.
|
|
113
|
+
- `GITHUB_OUTPUT` is required when used as a GitHub Actions step.
|
|
114
|
+
- `CI_WORKFLOW_FILE` defaults to `ci.yml`.
|
|
115
|
+
- `DEFAULT_BRANCH` defaults to `main`.
|
|
116
|
+
- `GITHUB_API_BASE_URL` defaults to `https://api.github.com`.
|
|
117
|
+
- `DEPENDENCY_ONLY_MIN_AGE_DAYS` defaults to `7`.
|
|
118
|
+
- `QUIET_PERIOD_MINUTES` defaults to `45`.
|
|
119
|
+
- `MAX_LATENCY_HOURS` defaults to `24`.
|
package/sbom.cdx.json
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json",
|
|
3
|
+
"bomFormat": "CycloneDX",
|
|
4
|
+
"specVersion": "1.6",
|
|
5
|
+
"version": 1,
|
|
6
|
+
"metadata": {
|
|
7
|
+
"tools": {
|
|
8
|
+
"components": [
|
|
9
|
+
{
|
|
10
|
+
"type": "application",
|
|
11
|
+
"name": "packtory",
|
|
12
|
+
"version": "0.0.29"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
"component": {
|
|
17
|
+
"type": "library",
|
|
18
|
+
"name": "@packtory/github-release-gate",
|
|
19
|
+
"version": "0.0.3",
|
|
20
|
+
"bom-ref": "pkg:npm/@packtory/github-release-gate@0.0.3",
|
|
21
|
+
"purl": "pkg:npm/@packtory/github-release-gate@0.0.3"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"components": [
|
|
25
|
+
{
|
|
26
|
+
"type": "library",
|
|
27
|
+
"name": "@octokit/core",
|
|
28
|
+
"version": "7.0.6",
|
|
29
|
+
"bom-ref": "pkg:npm/@octokit/core@7.0.6",
|
|
30
|
+
"scope": "required",
|
|
31
|
+
"licenses": [
|
|
32
|
+
{
|
|
33
|
+
"expression": "MIT"
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"purl": "pkg:npm/@octokit/core@7.0.6"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"type": "library",
|
|
40
|
+
"name": "@octokit/plugin-paginate-rest",
|
|
41
|
+
"version": "14.0.0",
|
|
42
|
+
"bom-ref": "pkg:npm/@octokit/plugin-paginate-rest@14.0.0",
|
|
43
|
+
"scope": "required",
|
|
44
|
+
"licenses": [
|
|
45
|
+
{
|
|
46
|
+
"expression": "MIT"
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
"purl": "pkg:npm/@octokit/plugin-paginate-rest@14.0.0"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"type": "library",
|
|
53
|
+
"name": "@octokit/plugin-rest-endpoint-methods",
|
|
54
|
+
"version": "17.0.0",
|
|
55
|
+
"bom-ref": "pkg:npm/@octokit/plugin-rest-endpoint-methods@17.0.0",
|
|
56
|
+
"scope": "required",
|
|
57
|
+
"licenses": [
|
|
58
|
+
{
|
|
59
|
+
"expression": "MIT"
|
|
60
|
+
}
|
|
61
|
+
],
|
|
62
|
+
"purl": "pkg:npm/@octokit/plugin-rest-endpoint-methods@17.0.0"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"type": "library",
|
|
66
|
+
"name": "packtory",
|
|
67
|
+
"version": "0.0.30",
|
|
68
|
+
"bom-ref": "pkg:npm/packtory@0.0.30",
|
|
69
|
+
"scope": "required",
|
|
70
|
+
"licenses": [
|
|
71
|
+
{
|
|
72
|
+
"expression": "MIT"
|
|
73
|
+
}
|
|
74
|
+
],
|
|
75
|
+
"purl": "pkg:npm/packtory@0.0.30"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"type": "library",
|
|
79
|
+
"name": "remeda",
|
|
80
|
+
"version": "2.37.0",
|
|
81
|
+
"bom-ref": "pkg:npm/remeda@2.37.0",
|
|
82
|
+
"scope": "required",
|
|
83
|
+
"licenses": [
|
|
84
|
+
{
|
|
85
|
+
"expression": "MIT"
|
|
86
|
+
}
|
|
87
|
+
],
|
|
88
|
+
"purl": "pkg:npm/remeda@2.37.0"
|
|
89
|
+
}
|
|
90
|
+
],
|
|
91
|
+
"dependencies": [
|
|
92
|
+
{
|
|
93
|
+
"ref": "pkg:npm/@octokit/core@7.0.6"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"ref": "pkg:npm/@octokit/plugin-paginate-rest@14.0.0"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"ref": "pkg:npm/@octokit/plugin-rest-endpoint-methods@17.0.0"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"ref": "pkg:npm/@packtory/github-release-gate@0.0.3",
|
|
103
|
+
"dependsOn": [
|
|
104
|
+
"pkg:npm/@octokit/core@7.0.6",
|
|
105
|
+
"pkg:npm/@octokit/plugin-paginate-rest@14.0.0",
|
|
106
|
+
"pkg:npm/@octokit/plugin-rest-endpoint-methods@17.0.0",
|
|
107
|
+
"pkg:npm/packtory@0.0.30",
|
|
108
|
+
"pkg:npm/remeda@2.37.0"
|
|
109
|
+
]
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"ref": "pkg:npm/packtory@0.0.30"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"ref": "pkg:npm/remeda@2.37.0"
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
}
|