@kungfu-tech/buildchain 2.5.1 → 2.5.2-alpha.1
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/docs/MAP.md +4 -2
- package/docs/release-governance.md +55 -0
- package/package.json +1 -1
- package/scripts/buildchain-patrol.mjs +269 -0
- package/scripts/check-inventory.mjs +8 -0
package/docs/MAP.md
CHANGED
|
@@ -25,6 +25,7 @@ running artifact), *use* (consume / extend) - and a **status**:
|
|
|
25
25
|
| How do I initialize a new repository? | [`cli.md`](cli.md) + [`lifecycle-protocol.md`](lifecycle-protocol.md) | use | stable |
|
|
26
26
|
| Why does Buildchain use branch-driven release governance? | [`release-governance.md`](release-governance.md) | why | stable |
|
|
27
27
|
| How do protected dev branches and scheduled ready-PR merging work? | [`release-governance.md`](release-governance.md#protected-dev-branches) | use | stable |
|
|
28
|
+
| How do I run daily, weekly, or monthly repository patrols? | [`release-governance.md`](release-governance.md#buildchain-patrol) | use | stable |
|
|
28
29
|
| How does Buildchain decide patch, minor, and major release lines? | [`versioning.md`](versioning.md) | why | stable |
|
|
29
30
|
| What exact branch/tag state machine runs on alpha, release, and major gate? | [`release-flow.md`](release-flow.md) | verify | stable |
|
|
30
31
|
| What did Buildchain migrate or retire from old action repositories? | [`migration-inventory.md`](migration-inventory.md) | verify | stable |
|
|
@@ -58,8 +59,9 @@ running artifact), *use* (consume / extend) - and a **status**:
|
|
|
58
59
|
[`versioning.md`](versioning.md).
|
|
59
60
|
- **dry-run / what would happen if this channel PR merges** -> [`cli.md`](cli.md)
|
|
60
61
|
and [`release-flow.md`](release-flow.md).
|
|
61
|
-
- **protected dev branches / scheduled ready-PR merge** ->
|
|
62
|
-
[`release-governance.md`](release-governance.md#protected-dev-branches)
|
|
62
|
+
- **protected dev branches / scheduled ready-PR merge / daily-weekly-monthly patrol** ->
|
|
63
|
+
[`release-governance.md`](release-governance.md#protected-dev-branches) and
|
|
64
|
+
[`release-governance.md`](release-governance.md#buildchain-patrol).
|
|
63
65
|
- **pnpm / npm / yarn / package-manager adapters** ->
|
|
64
66
|
[`lifecycle-protocol.md`](lifecycle-protocol.md).
|
|
65
67
|
- **pip / Conan / CMake / custom commands** -> [`lifecycle-protocol.md`](lifecycle-protocol.md)
|
|
@@ -281,6 +281,61 @@ jobs:
|
|
|
281
281
|
dry-run: ${{ inputs.dry-run || false }}
|
|
282
282
|
```
|
|
283
283
|
|
|
284
|
+
## Buildchain Patrol
|
|
285
|
+
|
|
286
|
+
`dev-pr-auto-merge.yml` remains the focused merge primitive. For repositories
|
|
287
|
+
that want a stable day-to-day operations contract, Buildchain also exposes a
|
|
288
|
+
patrol workflow family:
|
|
289
|
+
|
|
290
|
+
| Workflow | Intended cadence | Default intent |
|
|
291
|
+
| --- | --- | --- |
|
|
292
|
+
| `.github/workflows/patrol-daily.yml` | daily | lightweight inspection plus ready dev PR maintenance |
|
|
293
|
+
| `.github/workflows/patrol-weekly.yml` | weekly | release-state, passport, gate, and stale-state health checks as they are added |
|
|
294
|
+
| `.github/workflows/patrol-monthly.yml` | monthly | governance, permission, branch-protection, and workflow drift checks as they are added |
|
|
295
|
+
|
|
296
|
+
The cadence names describe patrol intensity, not release cadence:
|
|
297
|
+
|
|
298
|
+
- daily patrol can run every day without implying a daily release;
|
|
299
|
+
- weekly patrol is for medium-cost maintenance and audit checks;
|
|
300
|
+
- monthly patrol is for structural drift checks that should not block ordinary
|
|
301
|
+
development velocity.
|
|
302
|
+
|
|
303
|
+
Consumers should schedule thin callers and keep their YAML declarative. For
|
|
304
|
+
example:
|
|
305
|
+
|
|
306
|
+
```yaml
|
|
307
|
+
name: Buildchain Daily Patrol
|
|
308
|
+
|
|
309
|
+
on:
|
|
310
|
+
schedule:
|
|
311
|
+
- cron: "17 2 * * *"
|
|
312
|
+
workflow_dispatch:
|
|
313
|
+
|
|
314
|
+
jobs:
|
|
315
|
+
patrol:
|
|
316
|
+
uses: kungfu-systems/buildchain/.github/workflows/patrol-daily.yml@v2
|
|
317
|
+
with:
|
|
318
|
+
target-branch: dev/v2/v2.5
|
|
319
|
+
dry-run: false
|
|
320
|
+
max-actions: 1
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Weekly and monthly callers use the matching wrapper:
|
|
324
|
+
|
|
325
|
+
```yaml
|
|
326
|
+
jobs:
|
|
327
|
+
patrol:
|
|
328
|
+
uses: kungfu-systems/buildchain/.github/workflows/patrol-weekly.yml@v2
|
|
329
|
+
with:
|
|
330
|
+
target-branch: dev/v2/v2.5
|
|
331
|
+
dry-run: true
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
All three wrappers call the same underlying Buildchain patrol protocol, but the
|
|
335
|
+
separate workflow names keep consumer schedules readable and stable. Buildchain
|
|
336
|
+
can add new checks behind the cadence wrappers without forcing every consumer
|
|
337
|
+
repository to rewrite its schedule YAML.
|
|
338
|
+
|
|
284
339
|
## Package-Manager Adapters
|
|
285
340
|
|
|
286
341
|
Old ABV assumed JavaScript repositories with root version state and often
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kungfu-tech/buildchain",
|
|
3
|
-
"version": "2.5.1",
|
|
3
|
+
"version": "2.5.2-alpha.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Buildchain Release Passport, release governance, CLI toolkit, and site facts.",
|
|
6
6
|
"repository": "https://github.com/kungfu-systems/buildchain",
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { runDevPrAutoMerge } from "./dev-pr-auto-merge.mjs";
|
|
5
|
+
|
|
6
|
+
const DEFAULT_TARGET_BRANCH = "dev/v2/v2.5";
|
|
7
|
+
const DEFAULT_OUTPUT_PATH = ".buildchain/patrol/result.json";
|
|
8
|
+
const VALID_CADENCES = new Set(["daily", "weekly", "monthly"]);
|
|
9
|
+
const VALID_MODES = new Set(["cadence-default", "inspect", "merge-ready-dev-prs", "cleanup-safe"]);
|
|
10
|
+
|
|
11
|
+
function splitList(value, fallback = []) {
|
|
12
|
+
if (Array.isArray(value)) return value.map((entry) => String(entry).trim()).filter(Boolean);
|
|
13
|
+
const text = String(value ?? "").trim();
|
|
14
|
+
if (!text) return [...fallback];
|
|
15
|
+
return text
|
|
16
|
+
.split(/[\n,]+/)
|
|
17
|
+
.map((entry) => entry.trim())
|
|
18
|
+
.filter(Boolean);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function boolOption(value, fallback = false) {
|
|
22
|
+
if (value === undefined || value === null || value === "") return fallback;
|
|
23
|
+
if (typeof value === "boolean") return value;
|
|
24
|
+
const text = String(value).trim().toLowerCase();
|
|
25
|
+
return ["1", "true", "yes", "on"].includes(text);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function intOption(value, fallback) {
|
|
29
|
+
const parsed = Number(value);
|
|
30
|
+
return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : fallback;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function normalizeChoice(value, valid, fallback, field) {
|
|
34
|
+
const normalized = String(value || fallback).trim().toLowerCase();
|
|
35
|
+
if (!valid.has(normalized)) {
|
|
36
|
+
throw new Error(`${field} must be one of ${[...valid].join(", ")}, got: ${value || "<empty>"}`);
|
|
37
|
+
}
|
|
38
|
+
return normalized;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function normalizeRepository(value) {
|
|
42
|
+
const repository = String(value || "").trim();
|
|
43
|
+
if (!/^[^/\s]+\/[^/\s]+$/.test(repository)) {
|
|
44
|
+
throw new Error(`repository must be owner/repo, got: ${repository || "<empty>"}`);
|
|
45
|
+
}
|
|
46
|
+
return repository;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function normalizeTargetBranch(value) {
|
|
50
|
+
const branch = String(value || DEFAULT_TARGET_BRANCH).replace(/^refs\/heads\//, "");
|
|
51
|
+
if (!/^dev\/v\d+\/v\d+\.\d+$/.test(branch)) {
|
|
52
|
+
throw new Error(`target-branch must be a semver dev branch such as dev/v2/v2.5, got: ${branch}`);
|
|
53
|
+
}
|
|
54
|
+
return branch;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function defaultCapabilities(cadence) {
|
|
58
|
+
if (cadence === "daily") return ["inspect", "merge-ready-dev-prs"];
|
|
59
|
+
if (cadence === "weekly") return ["inspect", "release-health", "stale-state-health"];
|
|
60
|
+
return ["inspect", "governance-health", "workflow-drift-health"];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function capabilitiesFor({ cadence, mode, capabilities }) {
|
|
64
|
+
const explicit = splitList(capabilities);
|
|
65
|
+
if (explicit.length > 0) return explicit;
|
|
66
|
+
if (mode === "cadence-default") return defaultCapabilities(cadence);
|
|
67
|
+
if (mode === "merge-ready-dev-prs") return ["inspect", "merge-ready-dev-prs"];
|
|
68
|
+
if (mode === "cleanup-safe") return ["inspect", "cleanup-safe"];
|
|
69
|
+
return ["inspect"];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function normalizePatrolOptions(options = {}) {
|
|
73
|
+
const cadence = normalizeChoice(options.cadence || process.env.BUILDCHAIN_PATROL_CADENCE, VALID_CADENCES, "daily", "cadence");
|
|
74
|
+
const mode = normalizeChoice(options.mode || process.env.BUILDCHAIN_PATROL_MODE, VALID_MODES, "cadence-default", "mode");
|
|
75
|
+
const targetBranch = normalizeTargetBranch(options.targetBranch || process.env.BUILDCHAIN_PATROL_TARGET_BRANCH);
|
|
76
|
+
return {
|
|
77
|
+
cadence,
|
|
78
|
+
mode,
|
|
79
|
+
capabilities: capabilitiesFor({
|
|
80
|
+
cadence,
|
|
81
|
+
mode,
|
|
82
|
+
capabilities: options.capabilities || process.env.BUILDCHAIN_PATROL_CAPABILITIES,
|
|
83
|
+
}),
|
|
84
|
+
repository: normalizeRepository(options.repository || process.env.BUILDCHAIN_PATROL_REPOSITORY || process.env.GITHUB_REPOSITORY),
|
|
85
|
+
targetBranch,
|
|
86
|
+
requiredChecks: options.requiredChecks ?? process.env.BUILDCHAIN_PATROL_REQUIRED_CHECKS,
|
|
87
|
+
readyLabel: options.readyLabel ?? process.env.BUILDCHAIN_PATROL_READY_LABEL,
|
|
88
|
+
blockLabels: options.blockLabels ?? process.env.BUILDCHAIN_PATROL_BLOCK_LABELS,
|
|
89
|
+
allowedHeadPrefixes: options.allowedHeadPrefixes ?? process.env.BUILDCHAIN_PATROL_ALLOWED_HEAD_PREFIXES,
|
|
90
|
+
requireApproval: options.requireApproval ?? process.env.BUILDCHAIN_PATROL_REQUIRE_APPROVAL,
|
|
91
|
+
sameRepositoryOnly: options.sameRepositoryOnly ?? process.env.BUILDCHAIN_PATROL_SAME_REPOSITORY_ONLY,
|
|
92
|
+
maxActions: intOption(options.maxActions ?? process.env.BUILDCHAIN_PATROL_MAX_ACTIONS, 1),
|
|
93
|
+
mergeMethod: String(options.mergeMethod || process.env.BUILDCHAIN_PATROL_MERGE_METHOD || "merge").trim(),
|
|
94
|
+
dryRun: boolOption(options.dryRun ?? process.env.BUILDCHAIN_PATROL_DRY_RUN, true),
|
|
95
|
+
outputPath: String(options.outputPath || process.env.BUILDCHAIN_PATROL_OUTPUT_PATH || DEFAULT_OUTPUT_PATH),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function createCheck(id, status, message, details = {}) {
|
|
100
|
+
return { id, status, message, details };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function inspectRepository(options) {
|
|
104
|
+
const checks = [
|
|
105
|
+
createCheck("repository.format", "pass", "repository is owner/repo", { repository: options.repository }),
|
|
106
|
+
createCheck("target_branch.semver_dev", "pass", "target branch is a semver dev channel", {
|
|
107
|
+
targetBranch: options.targetBranch,
|
|
108
|
+
}),
|
|
109
|
+
createCheck("cadence.contract", "pass", "patrol cadence is recognized", {
|
|
110
|
+
cadence: options.cadence,
|
|
111
|
+
mode: options.mode,
|
|
112
|
+
capabilities: options.capabilities,
|
|
113
|
+
}),
|
|
114
|
+
];
|
|
115
|
+
return {
|
|
116
|
+
contract: "kungfu-buildchain-patrol-inspection",
|
|
117
|
+
ok: checks.every((check) => check.status === "pass"),
|
|
118
|
+
checks,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function plannedCapability(capability, reason) {
|
|
123
|
+
return {
|
|
124
|
+
capability,
|
|
125
|
+
status: "planned",
|
|
126
|
+
reason,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export async function runBuildchainPatrol(optionsInput = {}, clientInput) {
|
|
131
|
+
const options = normalizePatrolOptions(optionsInput);
|
|
132
|
+
const result = {
|
|
133
|
+
schemaVersion: 1,
|
|
134
|
+
contract: "kungfu-buildchain-patrol",
|
|
135
|
+
repository: options.repository,
|
|
136
|
+
targetBranch: options.targetBranch,
|
|
137
|
+
cadence: options.cadence,
|
|
138
|
+
mode: options.mode,
|
|
139
|
+
dryRun: options.dryRun,
|
|
140
|
+
maxActions: options.maxActions,
|
|
141
|
+
capabilities: options.capabilities,
|
|
142
|
+
inspections: [],
|
|
143
|
+
actions: [],
|
|
144
|
+
planned: [],
|
|
145
|
+
summary: {
|
|
146
|
+
evaluatedCount: 0,
|
|
147
|
+
actionCount: 0,
|
|
148
|
+
skippedCount: 0,
|
|
149
|
+
plannedCount: 0,
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
if (options.capabilities.includes("inspect")) {
|
|
154
|
+
result.inspections.push(inspectRepository(options));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (options.capabilities.includes("merge-ready-dev-prs")) {
|
|
158
|
+
const mergeResult = await runDevPrAutoMerge(
|
|
159
|
+
{
|
|
160
|
+
repository: options.repository,
|
|
161
|
+
targetBranch: options.targetBranch,
|
|
162
|
+
requiredChecks: options.requiredChecks,
|
|
163
|
+
readyLabel: options.readyLabel,
|
|
164
|
+
blockLabels: options.blockLabels,
|
|
165
|
+
allowedHeadPrefixes: options.allowedHeadPrefixes,
|
|
166
|
+
requireApproval: options.requireApproval,
|
|
167
|
+
sameRepositoryOnly: options.sameRepositoryOnly,
|
|
168
|
+
maxMerges: options.maxActions,
|
|
169
|
+
mergeMethod: options.mergeMethod,
|
|
170
|
+
dryRun: options.dryRun,
|
|
171
|
+
outputPath: path.join(path.dirname(options.outputPath), "dev-pr-auto-merge.json"),
|
|
172
|
+
},
|
|
173
|
+
clientInput,
|
|
174
|
+
);
|
|
175
|
+
result.actions.push({
|
|
176
|
+
capability: "merge-ready-dev-prs",
|
|
177
|
+
status: options.dryRun ? "planned" : "executed",
|
|
178
|
+
result: mergeResult,
|
|
179
|
+
});
|
|
180
|
+
result.summary.evaluatedCount += mergeResult.evaluated.length;
|
|
181
|
+
result.summary.actionCount += mergeResult.merged.length;
|
|
182
|
+
result.summary.skippedCount += mergeResult.skipped.length;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
for (const capability of options.capabilities) {
|
|
186
|
+
if (["inspect", "merge-ready-dev-prs"].includes(capability)) continue;
|
|
187
|
+
const entry = plannedCapability(
|
|
188
|
+
capability,
|
|
189
|
+
`${options.cadence} patrol exposes this stable interface before the checker is implemented`,
|
|
190
|
+
);
|
|
191
|
+
result.planned.push(entry);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
result.summary.plannedCount = result.planned.length;
|
|
195
|
+
result.ok = result.inspections.every((inspection) => inspection.ok !== false);
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function renderPatrolMarkdownSummary(result) {
|
|
200
|
+
const lines = [
|
|
201
|
+
"## Buildchain patrol",
|
|
202
|
+
"",
|
|
203
|
+
`Repository: \`${result.repository}\``,
|
|
204
|
+
`Target branch: \`${result.targetBranch}\``,
|
|
205
|
+
`Cadence: \`${result.cadence}\``,
|
|
206
|
+
`Mode: \`${result.mode}\``,
|
|
207
|
+
`Dry run: \`${result.dryRun ? "true" : "false"}\``,
|
|
208
|
+
`Capabilities: \`${result.capabilities.join(",") || "none"}\``,
|
|
209
|
+
"",
|
|
210
|
+
"| Area | Count |",
|
|
211
|
+
"| --- | ---: |",
|
|
212
|
+
`| Evaluated PRs | ${result.summary.evaluatedCount} |`,
|
|
213
|
+
`| Actions ${result.dryRun ? "planned" : "taken"} | ${result.summary.actionCount} |`,
|
|
214
|
+
`| Skipped PRs | ${result.summary.skippedCount} |`,
|
|
215
|
+
`| Planned future checks | ${result.summary.plannedCount} |`,
|
|
216
|
+
];
|
|
217
|
+
|
|
218
|
+
for (const action of result.actions) {
|
|
219
|
+
if (action.capability !== "merge-ready-dev-prs") continue;
|
|
220
|
+
lines.push("", "### Ready dev PRs", "", "| PR | Action | Reason | Head |", "| --- | --- | --- | --- |");
|
|
221
|
+
const evaluated = action.result.evaluated || [];
|
|
222
|
+
for (const entry of evaluated) {
|
|
223
|
+
lines.push(`| #${entry.number} | ${entry.action} | ${entry.reason} | \`${entry.headRef || ""}\` |`);
|
|
224
|
+
}
|
|
225
|
+
if (evaluated.length === 0) lines.push("| - | skip | no open pull requests | - |");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (result.planned.length > 0) {
|
|
229
|
+
lines.push("", "### Planned checks", "", "| Capability | Reason |", "| --- | --- |");
|
|
230
|
+
for (const entry of result.planned) {
|
|
231
|
+
lines.push(`| \`${entry.capability}\` | ${entry.reason} |`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return `${lines.join("\n")}\n`;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function writeGitHubOutputs(outputs, outputFile = process.env.GITHUB_OUTPUT) {
|
|
239
|
+
if (!outputFile) return;
|
|
240
|
+
const lines = [];
|
|
241
|
+
for (const [key, value] of Object.entries(outputs)) {
|
|
242
|
+
lines.push(`${key}=${String(value).replace(/\n/g, "%0A")}`);
|
|
243
|
+
}
|
|
244
|
+
fs.appendFileSync(outputFile, `${lines.join("\n")}\n`);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async function main() {
|
|
248
|
+
const options = normalizePatrolOptions();
|
|
249
|
+
const result = await runBuildchainPatrol(options);
|
|
250
|
+
fs.mkdirSync(path.dirname(options.outputPath), { recursive: true });
|
|
251
|
+
fs.writeFileSync(options.outputPath, `${JSON.stringify(result, null, 2)}\n`);
|
|
252
|
+
const summary = renderPatrolMarkdownSummary(result);
|
|
253
|
+
if (process.env.GITHUB_STEP_SUMMARY) fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, summary);
|
|
254
|
+
else process.stdout.write(summary);
|
|
255
|
+
writeGitHubOutputs({
|
|
256
|
+
"result-path": options.outputPath,
|
|
257
|
+
"evaluated-count": result.summary.evaluatedCount,
|
|
258
|
+
"action-count": result.summary.actionCount,
|
|
259
|
+
"skipped-count": result.summary.skippedCount,
|
|
260
|
+
"planned-count": result.summary.plannedCount,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
265
|
+
main().catch((error) => {
|
|
266
|
+
console.error(error.stack || error.message);
|
|
267
|
+
process.exit(1);
|
|
268
|
+
});
|
|
269
|
+
}
|
|
@@ -32,6 +32,7 @@ const requiredPaths = [
|
|
|
32
32
|
"scripts/npm-publish-dry-run.mjs",
|
|
33
33
|
"scripts/npm-publish-transaction.mjs",
|
|
34
34
|
"scripts/release-candidate-resolver.mjs",
|
|
35
|
+
"scripts/buildchain-patrol.mjs",
|
|
35
36
|
"scripts/workflow-friction-report.mjs",
|
|
36
37
|
"docs/migration-inventory.md",
|
|
37
38
|
"docs/lifecycle-protocol.md",
|
|
@@ -42,6 +43,13 @@ const requiredPaths = [
|
|
|
42
43
|
".github/workflows/self-hosted-runner-smoke.yml",
|
|
43
44
|
".github/workflows/buildchain-ref-promotion.yml",
|
|
44
45
|
".github/workflows/dev-pr-auto-merge.yml",
|
|
46
|
+
".github/workflows/buildchain-patrol.yml",
|
|
47
|
+
".github/workflows/patrol-daily.yml",
|
|
48
|
+
".github/workflows/patrol-weekly.yml",
|
|
49
|
+
".github/workflows/patrol-monthly.yml",
|
|
50
|
+
".github/workflows/buildchain-patrol-daily.yml",
|
|
51
|
+
".github/workflows/buildchain-patrol-weekly.yml",
|
|
52
|
+
".github/workflows/buildchain-patrol-monthly.yml",
|
|
45
53
|
".github/workflows/release-candidate-promote.yml",
|
|
46
54
|
".github/workflows/npm-publish.yml",
|
|
47
55
|
".github/workflows/binary-distribution.yml",
|