@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 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":"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"}
@@ -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
- "name": "@packtory/github-release-gate",
3
- "version": "0.0.1",
4
- "description": "Placeholder claiming the npm package name \"@packtory/github-release-gate\" so a trusted publisher can be configured. See https://github.com/npm/cli/issues/8544.",
5
- "license": "MIT",
6
- "deprecated": "Placeholder published as a workaround so a Trusted Publisher could be configured. See https://github.com/npm/cli/issues/8544."
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
- This version is a placeholder published only to claim the npm name `@packtory/github-release-gate` so a Trusted Publisher
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
- Workaround context: https://github.com/npm/cli/issues/8544
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
+ }