@packtory/github-release-gate 0.0.64 → 0.0.66
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/github-release-gate/cli-runner.js +4 -87
- package/github-release-gate/cli-runner.js.map +1 -1
- package/github-release-gate/github-api.js +5 -126
- package/github-release-gate/github-api.js.map +1 -1
- package/github-release-gate/release-gate.js +1 -90
- package/github-release-gate/release-gate.js.map +1 -1
- package/github-release-gate/release-policy.js +1 -55
- package/github-release-gate/release-policy.js.map +1 -1
- package/github-release-gate/runner-config.js +1 -58
- package/github-release-gate/runner-config.js.map +1 -1
- package/github-release-gate/validate-api-base-url.js +0 -37
- package/github-release-gate/validate-api-base-url.js.map +1 -1
- package/package.json +3 -3
- package/sbom.cdx.json +15 -15
|
@@ -1,88 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
function formatReleaseAnalysisFailure(error) {
|
|
6
|
-
if (error.type === 'partial') {
|
|
7
|
-
return error
|
|
8
|
-
.failures
|
|
9
|
-
.map(function (failure) {
|
|
10
|
-
return failure.message;
|
|
11
|
-
})
|
|
12
|
-
.join('\n');
|
|
13
|
-
}
|
|
14
|
-
return error.issues.join('\n');
|
|
15
|
-
}
|
|
16
|
-
function writeLogs(write, logs) {
|
|
17
|
-
for (const line of logs) {
|
|
18
|
-
write(line);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
function buildGitHubOutput(mainHeadSha, decision) {
|
|
22
|
-
return [
|
|
23
|
-
`main_head_sha=${mainHeadSha}`,
|
|
24
|
-
`should_publish=${decision.shouldPublish}`,
|
|
25
|
-
`reason=${decision.reason}`
|
|
26
|
-
]
|
|
27
|
-
.join('\n');
|
|
28
|
-
}
|
|
29
|
-
async function writeDecision(output, mainHeadSha, decision) {
|
|
30
|
-
writeLogs(output.stdoutWrite, decision.logs);
|
|
31
|
-
await output.fileManager.writeFile(output.githubOutputPath, `${buildGitHubOutput(mainHeadSha, decision)}\n`);
|
|
32
|
-
}
|
|
33
|
-
async function evaluatePacktoryPolicy(dependencies, config, now, timeGateDecision) {
|
|
34
|
-
const packtoryConfig = await dependencies.loadPacktoryConfig();
|
|
35
|
-
const releaseAnalysis = await dependencies.analyzeReleaseAgainstLatestPublished(packtoryConfig);
|
|
36
|
-
if (releaseAnalysis.result.isErr) {
|
|
37
|
-
throw new Error(formatReleaseAnalysisFailure(releaseAnalysis.result.error));
|
|
38
|
-
}
|
|
39
|
-
return applyPacktoryReleasePolicy({
|
|
40
|
-
baseDecision: timeGateDecision,
|
|
41
|
-
dependencyOnlyMinAgeDays: config.dependencyOnlyMinAgeDays,
|
|
42
|
-
now,
|
|
43
|
-
releaseAnalysis: releaseAnalysis.result.value
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
async function loadGitHubTimeGateDecision(dependencies, config) {
|
|
47
|
-
const githubApi = createGitHubReleaseGateApi(dependencies.fetch, createGitHubRepositoryContext(config));
|
|
48
|
-
const mainHeadSha = await githubApi.getMainBranchHeadSha();
|
|
49
|
-
const mainCiRunStatus = await githubApi.getMainCiRunStatus(config.ciWorkflowFile, mainHeadSha);
|
|
50
|
-
const pullRequestActivities = await githubApi.getOpenPullRequestActivities();
|
|
51
|
-
const now = dependencies.now();
|
|
52
|
-
return {
|
|
53
|
-
mainHeadSha,
|
|
54
|
-
now,
|
|
55
|
-
decision: evaluateGitHubReleaseGate({
|
|
56
|
-
ciWorkflowFile: config.ciWorkflowFile,
|
|
57
|
-
mainBranch: config.defaultBranch,
|
|
58
|
-
mainCiRunStatus,
|
|
59
|
-
mainHeadSha,
|
|
60
|
-
maxLatencyHours: config.maxLatencyHours,
|
|
61
|
-
now,
|
|
62
|
-
pullRequestActivities,
|
|
63
|
-
quietPeriodMinutes: config.quietPeriodMinutes
|
|
64
|
-
})
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
export async function runGitHubReleaseGate(dependencies) {
|
|
68
|
-
const config = readGitHubReleaseGateRunnerConfig(dependencies.getEnvironmentVariable);
|
|
69
|
-
const { decision: timeGateDecision, mainHeadSha, now } = await loadGitHubTimeGateDecision(dependencies, config);
|
|
70
|
-
if (!timeGateDecision.shouldPublish) {
|
|
71
|
-
await writeDecision({
|
|
72
|
-
fileManager: dependencies.fileManager,
|
|
73
|
-
githubOutputPath: config.githubOutputPath,
|
|
74
|
-
stdoutWrite: dependencies.stdoutWrite
|
|
75
|
-
}, mainHeadSha, timeGateDecision);
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
const decision = await evaluatePacktoryPolicy(dependencies, config, now, {
|
|
79
|
-
...timeGateDecision,
|
|
80
|
-
shouldPublish: true
|
|
81
|
-
});
|
|
82
|
-
await writeDecision({
|
|
83
|
-
fileManager: dependencies.fileManager,
|
|
84
|
-
githubOutputPath: config.githubOutputPath,
|
|
85
|
-
stdoutWrite: dependencies.stdoutWrite
|
|
86
|
-
}, mainHeadSha, decision);
|
|
87
|
-
}
|
|
1
|
+
import "./github-api.js";
|
|
2
|
+
import "./release-policy.js";
|
|
3
|
+
import "./release-gate.js";
|
|
4
|
+
import "./runner-config.js";
|
|
88
5
|
//# sourceMappingURL=cli-runner.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli-runner.js","sourceRoot":"","sources":["../../../../source/github-release-gate/cli-runner.ts"],"names":[],"mappings":"AAEA,
|
|
1
|
+
{"version":3,"file":"cli-runner.js","sourceRoot":"","sources":["../../../../source/github-release-gate/cli-runner.ts"],"names":[],"mappings":"AAEA,OAA2C,iBAAiB,CAAC;AAC7D,OAA2C,qBAAqB,CAAC;AACjE,OAA0E,mBAAmB,CAAC;AAC9F,OAIO,oBAAoB,CAAC"}
|
|
@@ -1,127 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
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
|
-
function readReflectedProperty(value, property) {
|
|
14
|
-
return Reflect.get(new Object(value), property);
|
|
15
|
-
}
|
|
16
|
-
function createGitHubRequestError(error) {
|
|
17
|
-
const requestUrl = String(readReflectedProperty(readReflectedProperty(error, 'request'), 'url'));
|
|
18
|
-
const status = String(readReflectedProperty(error, 'status'));
|
|
19
|
-
const parsedUrl = new URL(requestUrl);
|
|
20
|
-
return new Error(`GitHub API request failed (${status}) for ${parsedUrl.pathname}${parsedUrl.search}`, {
|
|
21
|
-
cause: error
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
async function resolveGitHubResponse(request) {
|
|
25
|
-
try {
|
|
26
|
-
return await request;
|
|
27
|
-
}
|
|
28
|
-
catch (error) {
|
|
29
|
-
throw createGitHubRequestError(error);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
function createRequestHeaders(context) {
|
|
33
|
-
return {
|
|
34
|
-
accept: 'application/vnd.github+json',
|
|
35
|
-
authorization: `Bearer ${context.token}`,
|
|
36
|
-
'user-agent': 'packtory-github-release-gate',
|
|
37
|
-
'x-github-api-version': '2022-11-28'
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
export function createGitHubReleaseGateApi(fetchImplementation, context) {
|
|
41
|
-
const GitHubRestClient = Octokit.plugin(restEndpointMethods, paginateRest);
|
|
42
|
-
const requestContext = {
|
|
43
|
-
headers: createRequestHeaders(context),
|
|
44
|
-
owner: context.owner,
|
|
45
|
-
repo: context.repo
|
|
46
|
-
};
|
|
47
|
-
const octokit = new GitHubRestClient({
|
|
48
|
-
baseUrl: context.apiBaseUrl,
|
|
49
|
-
request: {
|
|
50
|
-
fetch: fetchImplementation,
|
|
51
|
-
headers: requestContext.headers
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
return {
|
|
55
|
-
async getMainBranchHeadSha() {
|
|
56
|
-
const branch = await resolveGitHubResponse(octokit.rest.repos.getBranch({
|
|
57
|
-
...requestContext,
|
|
58
|
-
branch: context.defaultBranch
|
|
59
|
-
}));
|
|
60
|
-
return branch.data.commit.sha;
|
|
61
|
-
},
|
|
62
|
-
async getMainCiRunStatus(ciWorkflowFile, headSha) {
|
|
63
|
-
const response = await resolveGitHubResponse(octokit.rest.actions.listWorkflowRuns({
|
|
64
|
-
...requestContext,
|
|
65
|
-
workflow_id: ciWorkflowFile,
|
|
66
|
-
branch: context.defaultBranch,
|
|
67
|
-
event: 'push',
|
|
68
|
-
head_sha: headSha,
|
|
69
|
-
per_page: 100
|
|
70
|
-
}));
|
|
71
|
-
const matchingRuns = response.data.workflow_runs.filter(function (run) {
|
|
72
|
-
return run.head_sha === headSha && run.event === 'push';
|
|
73
|
-
});
|
|
74
|
-
const successfulRun = matchingRuns.find(function (run) {
|
|
75
|
-
return run.conclusion === 'success';
|
|
76
|
-
});
|
|
77
|
-
if (successfulRun !== undefined) {
|
|
78
|
-
return {
|
|
79
|
-
kind: 'success',
|
|
80
|
-
run: {
|
|
81
|
-
htmlUrl: successfulRun.html_url,
|
|
82
|
-
updatedAt: parseTimestamp(successfulRun.updated_at)
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
const inProgressRun = matchingRuns.find(function (run) {
|
|
87
|
-
return run.status !== 'completed';
|
|
88
|
-
});
|
|
89
|
-
if (inProgressRun !== undefined) {
|
|
90
|
-
return { kind: 'in_progress' };
|
|
91
|
-
}
|
|
92
|
-
return { kind: 'missing' };
|
|
93
|
-
},
|
|
94
|
-
async getOpenPullRequestActivities() {
|
|
95
|
-
const openPullRequests = await resolveGitHubResponse(octokit.paginate(octokit.rest.pulls.list, {
|
|
96
|
-
...requestContext,
|
|
97
|
-
state: 'open',
|
|
98
|
-
base: context.defaultBranch,
|
|
99
|
-
per_page: 100
|
|
100
|
-
}));
|
|
101
|
-
return Promise.all(openPullRequests.map(async function (pullRequest) {
|
|
102
|
-
const timeline = await resolveGitHubResponse(octokit.paginate(octokit.rest.issues.listEventsForTimeline, {
|
|
103
|
-
...requestContext,
|
|
104
|
-
issue_number: pullRequest.number,
|
|
105
|
-
per_page: 100
|
|
106
|
-
}));
|
|
107
|
-
const timelineEvents = timeline
|
|
108
|
-
.map(function (event) {
|
|
109
|
-
const timestamp = event.event === 'committed'
|
|
110
|
-
? event.committer?.date
|
|
111
|
-
: event.created_at ?? undefined;
|
|
112
|
-
if (timestamp === undefined) {
|
|
113
|
-
return undefined;
|
|
114
|
-
}
|
|
115
|
-
return { createdAt: parseTimestamp(timestamp), event: event.event };
|
|
116
|
-
})
|
|
117
|
-
.filter(isDefined);
|
|
118
|
-
return {
|
|
119
|
-
number: pullRequest.number,
|
|
120
|
-
htmlUrl: pullRequest.html_url,
|
|
121
|
-
activityAt: selectPullRequestActivityAt(parseTimestamp(pullRequest.created_at), timelineEvents)
|
|
122
|
-
};
|
|
123
|
-
}));
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
}
|
|
1
|
+
import 'remeda';
|
|
2
|
+
import '@octokit/core';
|
|
3
|
+
import '@octokit/plugin-paginate-rest';
|
|
4
|
+
import '@octokit/plugin-rest-endpoint-methods';
|
|
5
|
+
import "./release-gate.js";
|
|
127
6
|
//# sourceMappingURL=github-api.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"github-api.js","sourceRoot":"","sources":["../../../../source/github-release-gate/github-api.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"github-api.js","sourceRoot":"","sources":["../../../../source/github-release-gate/github-api.ts"],"names":[],"mappings":"AAAA,OAA0B,QAAQ,CAAC;AACnC,OAAwB,eAAe,CAAC;AACxC,OAA6B,+BAA+B,CAAC;AAC7D,OAAoC,uCAAuC,CAAC;AAC5E,OAKO,mBAAmB,CAAC"}
|
|
@@ -1,91 +1,2 @@
|
|
|
1
|
-
import
|
|
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 createInProgressCiDecision(input) {
|
|
33
|
-
const inProgressLog = `Skipping publish: a ${input.ciWorkflowFile} push run is still in progress for ${input.mainBranch} ` +
|
|
34
|
-
`HEAD ${input.mainHeadSha}.`;
|
|
35
|
-
return createDecision(false, 'ci_in_progress', [], inProgressLog);
|
|
36
|
-
}
|
|
37
|
-
function hasElapsed(now, since, elapsedMilliseconds) {
|
|
38
|
-
return now.getTime() - since.getTime() >= elapsedMilliseconds;
|
|
39
|
-
}
|
|
40
|
-
function getElapsedFlags(input, mainHeadCiSuccessAt, lastRelevantActivityAt) {
|
|
41
|
-
return {
|
|
42
|
-
quietPeriodElapsed: hasElapsed(input.now, lastRelevantActivityAt, input.quietPeriodMinutes * millisecondsPerMinute),
|
|
43
|
-
maxLatencyElapsed: hasElapsed(input.now, mainHeadCiSuccessAt, input.maxLatencyHours * millisecondsPerHour)
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
function isBranchActivityEvent(eventName) {
|
|
47
|
-
const activityEventName = String(eventName);
|
|
48
|
-
return ['committed', 'head_ref_deleted', 'head_ref_force_pushed', 'head_ref_restored'].includes(activityEventName);
|
|
49
|
-
}
|
|
50
|
-
function lastRelevantActivityAtFor(input, mainHeadCiSuccessAt) {
|
|
51
|
-
const otherActivityDates = Array.from(input.pullRequestActivities, function (pullRequestActivity) {
|
|
52
|
-
return pullRequestActivity.activityAt;
|
|
53
|
-
});
|
|
54
|
-
return maxDate(mainHeadCiSuccessAt, otherActivityDates);
|
|
55
|
-
}
|
|
56
|
-
export function selectPullRequestActivityAt(pullRequestCreatedAt, timelineEvents) {
|
|
57
|
-
const branchActivityDates = [];
|
|
58
|
-
for (const event of timelineEvents) {
|
|
59
|
-
if (isBranchActivityEvent(event.event)) {
|
|
60
|
-
branchActivityDates.push(event.createdAt);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return maxDate(pullRequestCreatedAt, branchActivityDates);
|
|
64
|
-
}
|
|
65
|
-
function evaluateGreenCiGate(input, successfulMainCiRun) {
|
|
66
|
-
const mainHeadCiSuccessAt = successfulMainCiRun.updatedAt;
|
|
67
|
-
const lastRelevantActivityAt = lastRelevantActivityAtFor(input, mainHeadCiSuccessAt);
|
|
68
|
-
const { quietPeriodElapsed, maxLatencyElapsed } = getElapsedFlags(input, mainHeadCiSuccessAt, lastRelevantActivityAt);
|
|
69
|
-
const logs = buildDecisionLogs({
|
|
70
|
-
input,
|
|
71
|
-
mainHeadCiSuccessAt,
|
|
72
|
-
mainHeadCiSuccessHtmlUrl: successfulMainCiRun.htmlUrl,
|
|
73
|
-
lastRelevantActivityAt,
|
|
74
|
-
quietPeriodElapsed,
|
|
75
|
-
maxLatencyElapsed
|
|
76
|
-
});
|
|
77
|
-
if (!quietPeriodElapsed && !maxLatencyElapsed) {
|
|
78
|
-
return createDecision(false, 'activity_not_stale', logs, 'Skipping publish: repository activity is not stale enough yet.');
|
|
79
|
-
}
|
|
80
|
-
return createDecision(true, quietPeriodElapsed ? 'quiet_period_elapsed' : 'max_latency_elapsed', logs, 'Publishing is allowed by the release gate.');
|
|
81
|
-
}
|
|
82
|
-
export function evaluateGitHubReleaseGate(input) {
|
|
83
|
-
if (input.mainCiRunStatus.kind === 'in_progress') {
|
|
84
|
-
return createInProgressCiDecision(input);
|
|
85
|
-
}
|
|
86
|
-
if (input.mainCiRunStatus.kind === 'missing') {
|
|
87
|
-
return createMissingCiDecision(input);
|
|
88
|
-
}
|
|
89
|
-
return evaluateGreenCiGate(input, input.mainCiRunStatus.run);
|
|
90
|
-
}
|
|
1
|
+
import "packtory/common/max-date.js";
|
|
91
2
|
//# sourceMappingURL=release-gate.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"release-gate.js","sourceRoot":"","sources":["../../../../source/github-release-gate/release-gate.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"release-gate.js","sourceRoot":"","sources":["../../../../source/github-release-gate/release-gate.ts"],"names":[],"mappings":"AAAA,OAAwB,6BAAuB,CAAC"}
|
|
@@ -1,56 +1,2 @@
|
|
|
1
|
-
import
|
|
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
|
-
}
|
|
1
|
+
import "packtory/packtory/packtory-results.js";
|
|
56
2
|
//# sourceMappingURL=release-policy.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"release-policy.js","sourceRoot":"","sources":["../../../../source/github-release-gate/release-policy.ts"],"names":[],"mappings":"AACA,
|
|
1
|
+
{"version":3,"file":"release-policy.js","sourceRoot":"","sources":["../../../../source/github-release-gate/release-policy.ts"],"names":[],"mappings":"AACA,OAA8C,uCAAiC,CAAC"}
|
|
@@ -1,59 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
const defaultDependencyOnlyMinAgeDays = 7;
|
|
3
|
-
const defaultMaxLatencyHours = 24;
|
|
4
|
-
const defaultQuietPeriodMinutes = 45;
|
|
5
|
-
function defaultCiWorkflowFile() {
|
|
6
|
-
return 'ci.yml';
|
|
7
|
-
}
|
|
8
|
-
function defaultGitHubApiBaseUrl() {
|
|
9
|
-
return 'https://api.github.com';
|
|
10
|
-
}
|
|
11
|
-
function defaultMainBranch() {
|
|
12
|
-
return 'main';
|
|
13
|
-
}
|
|
14
|
-
function parseInteger(value, fallbackValue) {
|
|
15
|
-
return value === undefined ? fallbackValue : Number.parseInt(value, 10);
|
|
16
|
-
}
|
|
17
|
-
function getRequiredEnvironmentVariable(getEnvironmentVariable, variableName) {
|
|
18
|
-
const value = getEnvironmentVariable(variableName);
|
|
19
|
-
if (value === undefined) {
|
|
20
|
-
throw new Error(`Missing ${variableName} environment variable`);
|
|
21
|
-
}
|
|
22
|
-
return value;
|
|
23
|
-
}
|
|
24
|
-
export function readGitHubReleaseGateRunnerConfig(getEnvironmentVariable) {
|
|
25
|
-
return {
|
|
26
|
-
ciWorkflowFile: getEnvironmentVariable('CI_WORKFLOW_FILE') ?? defaultCiWorkflowFile(),
|
|
27
|
-
dependencyOnlyMinAgeDays: parseInteger(getEnvironmentVariable('DEPENDENCY_ONLY_MIN_AGE_DAYS'), defaultDependencyOnlyMinAgeDays),
|
|
28
|
-
defaultBranch: getEnvironmentVariable('DEFAULT_BRANCH') ?? defaultMainBranch(),
|
|
29
|
-
githubApiBaseUrl: assertGitHubApiBaseUrl(getEnvironmentVariable('GITHUB_API_BASE_URL') ?? defaultGitHubApiBaseUrl()),
|
|
30
|
-
githubOutputPath: getRequiredEnvironmentVariable(getEnvironmentVariable, 'GITHUB_OUTPUT'),
|
|
31
|
-
maxLatencyHours: parseInteger(getEnvironmentVariable('MAX_LATENCY_HOURS'), defaultMaxLatencyHours),
|
|
32
|
-
quietPeriodMinutes: parseInteger(getEnvironmentVariable('QUIET_PERIOD_MINUTES'), defaultQuietPeriodMinutes),
|
|
33
|
-
repository: getRequiredEnvironmentVariable(getEnvironmentVariable, 'GITHUB_REPOSITORY'),
|
|
34
|
-
token: getRequiredEnvironmentVariable(getEnvironmentVariable, 'GITHUB_TOKEN')
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
function splitRepository(repository) {
|
|
38
|
-
const firstSlashIndex = repository.indexOf('/');
|
|
39
|
-
if (firstSlashIndex <= 0 ||
|
|
40
|
-
firstSlashIndex !== repository.lastIndexOf('/') ||
|
|
41
|
-
firstSlashIndex === repository.length - 1) {
|
|
42
|
-
throw new Error(`Invalid GITHUB_REPOSITORY value: ${repository}`);
|
|
43
|
-
}
|
|
44
|
-
return {
|
|
45
|
-
owner: repository.slice(0, firstSlashIndex),
|
|
46
|
-
repo: repository.slice(firstSlashIndex + 1)
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
export function createGitHubRepositoryContext(config) {
|
|
50
|
-
const repository = splitRepository(config.repository);
|
|
51
|
-
return {
|
|
52
|
-
apiBaseUrl: config.githubApiBaseUrl,
|
|
53
|
-
defaultBranch: config.defaultBranch,
|
|
54
|
-
owner: repository.owner,
|
|
55
|
-
repo: repository.repo,
|
|
56
|
-
token: config.token
|
|
57
|
-
};
|
|
58
|
-
}
|
|
1
|
+
import "./validate-api-base-url.js";
|
|
59
2
|
//# sourceMappingURL=runner-config.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner-config.js","sourceRoot":"","sources":["../../../../source/github-release-gate/runner-config.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"runner-config.js","sourceRoot":"","sources":["../../../../source/github-release-gate/runner-config.ts"],"names":[],"mappings":"AAAA,OAAuC,4BAA4B,CAAC"}
|
|
@@ -1,38 +1 @@
|
|
|
1
|
-
function hostnameFromUrl(value) {
|
|
2
|
-
const url = new URL(value);
|
|
3
|
-
return url.hostname;
|
|
4
|
-
}
|
|
5
|
-
function expectedApiHostname() {
|
|
6
|
-
return hostnameFromUrl('https://api.github.com');
|
|
7
|
-
}
|
|
8
|
-
function parseOrThrow(value) {
|
|
9
|
-
try {
|
|
10
|
-
return new URL(value);
|
|
11
|
-
}
|
|
12
|
-
catch {
|
|
13
|
-
throw new Error(`GITHUB_API_BASE_URL is not a valid URL: "${value}"`);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
function isLoopbackHostname(hostname) {
|
|
17
|
-
return (hostname === hostnameFromUrl('http://127.0.0.1') ||
|
|
18
|
-
hostname === hostnameFromUrl('http://[::1]') ||
|
|
19
|
-
hostname === hostnameFromUrl('http://localhost'));
|
|
20
|
-
}
|
|
21
|
-
function buildMismatchMessage(actualHostname) {
|
|
22
|
-
return (`GITHUB_API_BASE_URL hostname must be "${expectedApiHostname()}", got "${actualHostname}". ` +
|
|
23
|
-
'A non-GitHub host would receive the GITHUB_TOKEN.');
|
|
24
|
-
}
|
|
25
|
-
export function assertGitHubApiBaseUrl(value) {
|
|
26
|
-
const parsed = parseOrThrow(value);
|
|
27
|
-
if (isLoopbackHostname(parsed.hostname)) {
|
|
28
|
-
return value;
|
|
29
|
-
}
|
|
30
|
-
if (parsed.protocol !== 'https:') {
|
|
31
|
-
throw new Error(`GITHUB_API_BASE_URL must use https, got: "${value}"`);
|
|
32
|
-
}
|
|
33
|
-
if (parsed.hostname !== expectedApiHostname()) {
|
|
34
|
-
throw new Error(buildMismatchMessage(parsed.hostname));
|
|
35
|
-
}
|
|
36
|
-
return value;
|
|
37
|
-
}
|
|
38
1
|
//# sourceMappingURL=validate-api-base-url.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-api-base-url.js","sourceRoot":"","sources":["../../../../source/github-release-gate/validate-api-base-url.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"validate-api-base-url.js","sourceRoot":"","sources":["../../../../source/github-release-gate/validate-api-base-url.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
"@octokit/core": "7.0.7",
|
|
14
14
|
"@octokit/plugin-paginate-rest": "15.0.0",
|
|
15
15
|
"@octokit/plugin-rest-endpoint-methods": "18.0.0",
|
|
16
|
-
"packtory": "0.0.
|
|
17
|
-
"remeda": "2.
|
|
16
|
+
"packtory": "0.0.93",
|
|
17
|
+
"remeda": "2.45.0"
|
|
18
18
|
},
|
|
19
19
|
"description": "GitHub Actions release gate that batches packtory publishes by waiting for repository activity to settle.",
|
|
20
20
|
"engines": {
|
|
@@ -45,5 +45,5 @@
|
|
|
45
45
|
"./packages/github-release-gate/github-release-gate.entry-point.js"
|
|
46
46
|
],
|
|
47
47
|
"type": "module",
|
|
48
|
-
"version": "0.0.
|
|
48
|
+
"version": "0.0.66"
|
|
49
49
|
}
|
package/sbom.cdx.json
CHANGED
|
@@ -9,16 +9,16 @@
|
|
|
9
9
|
{
|
|
10
10
|
"type": "application",
|
|
11
11
|
"name": "packtory",
|
|
12
|
-
"version": "0.0.
|
|
12
|
+
"version": "0.0.99"
|
|
13
13
|
}
|
|
14
14
|
]
|
|
15
15
|
},
|
|
16
16
|
"component": {
|
|
17
17
|
"type": "library",
|
|
18
18
|
"name": "@packtory/github-release-gate",
|
|
19
|
-
"version": "0.0.
|
|
20
|
-
"bom-ref": "pkg:npm/@packtory/github-release-gate@0.0.
|
|
21
|
-
"purl": "pkg:npm/@packtory/github-release-gate@0.0.
|
|
19
|
+
"version": "0.0.66",
|
|
20
|
+
"bom-ref": "pkg:npm/@packtory/github-release-gate@0.0.66",
|
|
21
|
+
"purl": "pkg:npm/@packtory/github-release-gate@0.0.66"
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
24
|
"components": [
|
|
@@ -64,28 +64,28 @@
|
|
|
64
64
|
{
|
|
65
65
|
"type": "library",
|
|
66
66
|
"name": "packtory",
|
|
67
|
-
"version": "0.0.
|
|
68
|
-
"bom-ref": "pkg:npm/packtory@0.0.
|
|
67
|
+
"version": "0.0.93",
|
|
68
|
+
"bom-ref": "pkg:npm/packtory@0.0.93",
|
|
69
69
|
"scope": "required",
|
|
70
70
|
"licenses": [
|
|
71
71
|
{
|
|
72
72
|
"expression": "MIT"
|
|
73
73
|
}
|
|
74
74
|
],
|
|
75
|
-
"purl": "pkg:npm/packtory@0.0.
|
|
75
|
+
"purl": "pkg:npm/packtory@0.0.93"
|
|
76
76
|
},
|
|
77
77
|
{
|
|
78
78
|
"type": "library",
|
|
79
79
|
"name": "remeda",
|
|
80
|
-
"version": "2.
|
|
81
|
-
"bom-ref": "pkg:npm/remeda@2.
|
|
80
|
+
"version": "2.45.0",
|
|
81
|
+
"bom-ref": "pkg:npm/remeda@2.45.0",
|
|
82
82
|
"scope": "required",
|
|
83
83
|
"licenses": [
|
|
84
84
|
{
|
|
85
85
|
"expression": "MIT"
|
|
86
86
|
}
|
|
87
87
|
],
|
|
88
|
-
"purl": "pkg:npm/remeda@2.
|
|
88
|
+
"purl": "pkg:npm/remeda@2.45.0"
|
|
89
89
|
}
|
|
90
90
|
],
|
|
91
91
|
"dependencies": [
|
|
@@ -99,20 +99,20 @@
|
|
|
99
99
|
"ref": "pkg:npm/@octokit/plugin-rest-endpoint-methods@18.0.0"
|
|
100
100
|
},
|
|
101
101
|
{
|
|
102
|
-
"ref": "pkg:npm/@packtory/github-release-gate@0.0.
|
|
102
|
+
"ref": "pkg:npm/@packtory/github-release-gate@0.0.66",
|
|
103
103
|
"dependsOn": [
|
|
104
104
|
"pkg:npm/@octokit/core@7.0.7",
|
|
105
105
|
"pkg:npm/@octokit/plugin-paginate-rest@15.0.0",
|
|
106
106
|
"pkg:npm/@octokit/plugin-rest-endpoint-methods@18.0.0",
|
|
107
|
-
"pkg:npm/packtory@0.0.
|
|
108
|
-
"pkg:npm/remeda@2.
|
|
107
|
+
"pkg:npm/packtory@0.0.93",
|
|
108
|
+
"pkg:npm/remeda@2.45.0"
|
|
109
109
|
]
|
|
110
110
|
},
|
|
111
111
|
{
|
|
112
|
-
"ref": "pkg:npm/packtory@0.0.
|
|
112
|
+
"ref": "pkg:npm/packtory@0.0.93"
|
|
113
113
|
},
|
|
114
114
|
{
|
|
115
|
-
"ref": "pkg:npm/remeda@2.
|
|
115
|
+
"ref": "pkg:npm/remeda@2.45.0"
|
|
116
116
|
}
|
|
117
117
|
]
|
|
118
118
|
}
|