@norskvideo/ctl-dev-kit 0.2.25 → 0.2.27
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/conventions/bot-prs.yml +167 -0
- package/conventions/check-drift.ts +12 -0
- package/conventions/sync-dev-kit.yml +18 -1
- package/conventions/sync-drift.ts +8 -0
- package/conventions/upgrade-latest.yml +18 -1
- package/create-product/canon.ts +2 -0
- package/create-product/create-product.ts +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Land the pin bots' green PRs; put the red ones on the product dashboard.
|
|
2
|
+
#
|
|
3
|
+
# Three bots keep a product fresh: upgrade-latest (the Norsk nightly line),
|
|
4
|
+
# sync-dev-kit (this package) and sync-ctl-packages (the ctl-* / moq-* npm
|
|
5
|
+
# family). The first two land on main when their gate is green and open a PR
|
|
6
|
+
# when it is not; sync-ctl-packages ALWAYS opens a PR, because its gate cannot
|
|
7
|
+
# run the smoke tier its packages drive. Nothing then acted on those PRs: green
|
|
8
|
+
# ones sat unmerged (studio's ctl-sdk was twenty releases behind on 2026-10-03,
|
|
9
|
+
# its PR open since 09-03) and red ones were invisible, because the bot that
|
|
10
|
+
# opened them reports success. With a product count that only goes up, "someone
|
|
11
|
+
# will notice" is not a process.
|
|
12
|
+
#
|
|
13
|
+
# So, for every open PR whose branch a bot owns:
|
|
14
|
+
# - every check on its head has passed and it merges cleanly -> squash-merge it
|
|
15
|
+
# (the PR's checks + smoke ARE the verdict; integration then runs on main);
|
|
16
|
+
# - a check failed, it conflicts with main, or it is a draft (the bot's own
|
|
17
|
+
# gate was red) -> list it, and report this repo's `bot-prs` cell RED on the
|
|
18
|
+
# product dashboard, which is the Discord message people actually read;
|
|
19
|
+
# - checks still running -> leave it for the next trigger.
|
|
20
|
+
# The cell goes green again once nothing is red.
|
|
21
|
+
#
|
|
22
|
+
# Runs when a PR's checks or smoke finish (the prompt path), hourly as a sweep,
|
|
23
|
+
# and on demand. No checkout: it only talks to the GitHub API, so it needs none
|
|
24
|
+
# of the runner-workspace steps the building workflows carry.
|
|
25
|
+
#
|
|
26
|
+
# Single-sourced in @norskvideo/ctl-dev-kit (conventions/bot-prs.yml) and copied
|
|
27
|
+
# verbatim into each product repo -- it reads its dashboard key from this repo's
|
|
28
|
+
# upgrade-latest.yml rather than carrying one -- so sync-drift creates it where
|
|
29
|
+
# absent and check:drift fails a copy that diverges. Never hand-edit the copy.
|
|
30
|
+
name: bot-prs
|
|
31
|
+
|
|
32
|
+
on:
|
|
33
|
+
workflow_run:
|
|
34
|
+
workflows: [checks, smoke]
|
|
35
|
+
types: [completed]
|
|
36
|
+
schedule:
|
|
37
|
+
- cron: "23 * * * *"
|
|
38
|
+
workflow_dispatch:
|
|
39
|
+
|
|
40
|
+
permissions:
|
|
41
|
+
contents: read
|
|
42
|
+
checks: read
|
|
43
|
+
pull-requests: read
|
|
44
|
+
|
|
45
|
+
concurrency:
|
|
46
|
+
# One at a time per repo, and never cancelled: a merge half-done by a run that
|
|
47
|
+
# was cancelled is worse than a second run finding nothing left to do.
|
|
48
|
+
group: bot-prs-${{ github.repository }}
|
|
49
|
+
cancel-in-progress: false
|
|
50
|
+
|
|
51
|
+
jobs:
|
|
52
|
+
triage:
|
|
53
|
+
runs-on: x64
|
|
54
|
+
steps:
|
|
55
|
+
- name: Merge green bot PRs, report red ones
|
|
56
|
+
id: triage
|
|
57
|
+
env:
|
|
58
|
+
# Reads go through the job's own token, which the permissions above
|
|
59
|
+
# scope to exactly what is read; CI_DISPATCH_TOKEN (a PAT) has no
|
|
60
|
+
# Checks access, and the check-runs call returned an error object.
|
|
61
|
+
# Merges and branch deletes go through the PAT, because a push made
|
|
62
|
+
# with GITHUB_TOKEN does not trigger main's own workflows.
|
|
63
|
+
READ_TOKEN: ${{ github.token }}
|
|
64
|
+
WRITE_TOKEN: ${{ secrets.CI_DISPATCH_TOKEN }}
|
|
65
|
+
REPO: ${{ github.repository }}
|
|
66
|
+
run: |
|
|
67
|
+
set -euo pipefail
|
|
68
|
+
api="https://api.github.com/repos/$REPO"
|
|
69
|
+
curl_as() { local tok="$1"; shift; curl -sS --retry 3 --retry-all-errors -H "Authorization: Bearer $tok" -H "Accept: application/vnd.github+json" "$@"; }
|
|
70
|
+
gh_api() { curl_as "$READ_TOKEN" "$@"; }
|
|
71
|
+
gh_write() { curl_as "$WRITE_TOKEN" "$@"; }
|
|
72
|
+
# An API error is a JSON object with a message; say what it was rather
|
|
73
|
+
# than letting jq fail later on a missing field.
|
|
74
|
+
expect() {
|
|
75
|
+
local what="$1" filter="$2" body="$3"
|
|
76
|
+
jq -e "$filter" >/dev/null 2>&1 <<<"$body" && return 0
|
|
77
|
+
echo "::error::$what: $(jq -r '.message // .' <<<"$body" 2>/dev/null || echo "$body")"
|
|
78
|
+
exit 1
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# The bots own exactly these branches (see each workflow's PR step).
|
|
82
|
+
pulls="$(gh_api "$api/pulls?state=open&per_page=100")"
|
|
83
|
+
expect "listing open PRs" 'type == "array"' "$pulls"
|
|
84
|
+
prs="$(jq -c --arg repo "$REPO" '
|
|
85
|
+
[.[] | select(.head.repo.full_name == $repo)
|
|
86
|
+
| select(.head.ref | test("^(upgrade-latest/|sync-dev-kit/|chore/sync-ctl-packages$)"))
|
|
87
|
+
| {number, ref: .head.ref, sha: .head.sha, draft, url: .html_url}]' <<<"$pulls")"
|
|
88
|
+
|
|
89
|
+
red=()
|
|
90
|
+
merged=()
|
|
91
|
+
while IFS= read -r pr; do
|
|
92
|
+
[ -n "$pr" ] || continue
|
|
93
|
+
n="$(jq -r .number <<<"$pr")"; ref="$(jq -r .ref <<<"$pr")"; sha="$(jq -r .sha <<<"$pr")"
|
|
94
|
+
runs="$(gh_api "$api/commits/$sha/check-runs?per_page=100")"
|
|
95
|
+
expect "reading checks for #$n" '.check_runs | type == "array"' "$runs"
|
|
96
|
+
failed="$(jq -r '[.check_runs[] | select(.conclusion | IN("failure","cancelled","timed_out","action_required","startup_failure")) | .name] | unique | join(", ")' <<<"$runs")"
|
|
97
|
+
pending="$(jq -r '[.check_runs[] | select(.status != "completed")] | length' <<<"$runs")"
|
|
98
|
+
passed="$(jq -r '[.check_runs[] | select(.conclusion == "success")] | length' <<<"$runs")"
|
|
99
|
+
if [ -n "$failed" ]; then red+=("#$n $ref: failing $failed"); continue; fi
|
|
100
|
+
if [ "$(jq -r .draft <<<"$pr")" = "true" ]; then red+=("#$n $ref: draft (its bot's gate was red)"); continue; fi
|
|
101
|
+
if [ "$pending" -gt 0 ] || [ "$passed" -eq 0 ]; then echo "#$n $ref: checks not finished; next time"; continue; fi
|
|
102
|
+
# GitHub computes mergeability lazily; ask until it has an answer.
|
|
103
|
+
mergeable=null
|
|
104
|
+
for _ in 1 2 3 4 5; do
|
|
105
|
+
mergeable="$(gh_api "$api/pulls/$n" | jq -r .mergeable)"
|
|
106
|
+
[ "$mergeable" != "null" ] && break
|
|
107
|
+
sleep 3
|
|
108
|
+
done
|
|
109
|
+
if [ "$mergeable" != "true" ]; then red+=("#$n $ref: does not merge cleanly with main"); continue; fi
|
|
110
|
+
# sha pins the merge to what was checked: a bot force-pushing the
|
|
111
|
+
# branch meanwhile makes this refuse rather than land unchecked code.
|
|
112
|
+
res="$(gh_write -X PUT "$api/pulls/$n/merge" -d "$(jq -nc --arg sha "$sha" '{merge_method:"squash", sha:$sha}')")"
|
|
113
|
+
if [ "$(jq -r '.merged // false' <<<"$res")" = "true" ]; then
|
|
114
|
+
merged+=("#$n")
|
|
115
|
+
echo "merged #$n ($ref)"
|
|
116
|
+
# The bots recreate their branch on the next bump.
|
|
117
|
+
gh_write -X DELETE "$api/git/refs/heads/$ref" -o /dev/null || true
|
|
118
|
+
else
|
|
119
|
+
red+=("#$n $ref: merge refused: $(jq -r '.message // "unknown"' <<<"$res")")
|
|
120
|
+
fi
|
|
121
|
+
done < <(jq -c '.[]' <<<"$prs")
|
|
122
|
+
|
|
123
|
+
if [ "${#red[@]}" -gt 0 ]; then
|
|
124
|
+
state=failure
|
|
125
|
+
detail="$(printf '%s; ' "${red[@]}")"
|
|
126
|
+
for r in "${red[@]}"; do echo "::warning::bot PR needs a human: $r"; done
|
|
127
|
+
else
|
|
128
|
+
state=success
|
|
129
|
+
detail="${merged[*]:+merged ${merged[*]}}"
|
|
130
|
+
fi
|
|
131
|
+
echo "state=$state" >> "$GITHUB_OUTPUT"
|
|
132
|
+
echo "detail=${detail%; }" >> "$GITHUB_OUTPUT"
|
|
133
|
+
|
|
134
|
+
- name: Report to the product dashboard
|
|
135
|
+
# Always: a triage that broke is exactly what nobody would otherwise see.
|
|
136
|
+
if: always()
|
|
137
|
+
env:
|
|
138
|
+
TOKEN: ${{ secrets.CI_DISPATCH_TOKEN }}
|
|
139
|
+
READ_TOKEN: ${{ github.token }}
|
|
140
|
+
STATUS: ${{ steps.triage.outputs.state || 'failure' }}
|
|
141
|
+
DETAIL: ${{ steps.triage.outputs.detail || 'the bot-PR triage itself failed; see the run' }}
|
|
142
|
+
SHA: ${{ github.sha }}
|
|
143
|
+
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
144
|
+
REPO: ${{ github.repository }}
|
|
145
|
+
run: |
|
|
146
|
+
set -euo pipefail
|
|
147
|
+
# The dashboard key is the one upgrade-latest.yml reports under, read
|
|
148
|
+
# from it so this file can stay byte-identical across products.
|
|
149
|
+
product="$(curl -sS -H "Authorization: Bearer $READ_TOKEN" -H "Accept: application/vnd.github.raw" \
|
|
150
|
+
"https://api.github.com/repos/$REPO/contents/.github/workflows/upgrade-latest.yml" \
|
|
151
|
+
| sed -nE 's/^[[:space:]]*product:[[:space:]]*([^[:space:]]+).*/\1/p' | head -1)"
|
|
152
|
+
if [ -z "$product" ] || [ "$product" = "__PRODUCT__" ]; then
|
|
153
|
+
echo "::warning::no product key in upgrade-latest.yml; not reporting to the dashboard"
|
|
154
|
+
exit 0
|
|
155
|
+
fi
|
|
156
|
+
ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
157
|
+
payload="$(jq -cn \
|
|
158
|
+
--arg product "$product" --arg pipeline bot-prs --arg conclusion "$STATUS" \
|
|
159
|
+
--arg sha "$SHA" --arg runUrl "$RUN_URL" --arg ts "$ts" --arg detail "$DETAIL" \
|
|
160
|
+
'{event_type: "ci-status", client_payload: (
|
|
161
|
+
{product: $product, pipeline: $pipeline, conclusion: $conclusion, sha: $sha, runUrl: $runUrl, ts: $ts}
|
|
162
|
+
+ (if $detail == "" then {} else {detail: $detail} end)
|
|
163
|
+
)}')"
|
|
164
|
+
code="$(curl -sS -o /dev/null -w '%{http_code}' -X POST \
|
|
165
|
+
-H "Authorization: Bearer $TOKEN" -H "Accept: application/vnd.github+json" \
|
|
166
|
+
https://api.github.com/repos/id3as/ci-workflows/dispatches -d "$payload")"
|
|
167
|
+
[ "$code" = "204" ] || echo "::warning::dashboard dispatch returned HTTP $code"
|
|
@@ -256,6 +256,10 @@ export interface CanonicalBytes {
|
|
|
256
256
|
/** conventions/sync-ctl-packages.yml — the ctl-* pin freshness PR. No per-repo
|
|
257
257
|
* line at all, so it is verbatim and sync-drift creates it where absent. */
|
|
258
258
|
syncCtlPackages: string;
|
|
259
|
+
/** conventions/bot-prs.yml -- merges the pin bots' green PRs and reports red
|
|
260
|
+
* ones. Verbatim (it reads its dashboard key at run time), so sync-drift
|
|
261
|
+
* creates it where absent, like sync-ctl-packages. */
|
|
262
|
+
botPrs: string;
|
|
259
263
|
publishDocs: string;
|
|
260
264
|
/** conventions/build-docs.yml — the embedded docs variant (bundle -> image, no upload). */
|
|
261
265
|
buildDocs: string;
|
|
@@ -683,6 +687,13 @@ export function checkDrift(repoRoot: string, canonical: CanonicalBytes, opts: Ch
|
|
|
683
687
|
push(`.github/workflows/sync-ctl-packages.yml differs from conventions/sync-ctl-packages.yml. ${RESYNC}`);
|
|
684
688
|
}
|
|
685
689
|
|
|
690
|
+
// bot-prs.yml: the same shape -- optional until a repo's first sync creates
|
|
691
|
+
// it, verbatim once present.
|
|
692
|
+
const botPrsPath = join(repoRoot, ".github", "workflows", "bot-prs.yml");
|
|
693
|
+
if (existsSync(botPrsPath) && readFileSync(botPrsPath, "utf8") !== canonical.botPrs) {
|
|
694
|
+
push(`.github/workflows/bot-prs.yml differs from conventions/bot-prs.yml. ${RESYNC}`);
|
|
695
|
+
}
|
|
696
|
+
|
|
686
697
|
// smoke.yml is OPTIONAL until every product carries a tests/smoke.spec.ts
|
|
687
698
|
// (04b s7.2 sequences the required-file rule last). When present it is the
|
|
688
699
|
// shared, single-sourced workflow; only its `product:` dispatch key and the
|
|
@@ -793,6 +804,7 @@ if (import.meta.main) {
|
|
|
793
804
|
upgradeLatest: readFileSync(join(import.meta.dir, "upgrade-latest.yml"), "utf8"),
|
|
794
805
|
syncDevKit: readFileSync(join(import.meta.dir, "sync-dev-kit.yml"), "utf8"),
|
|
795
806
|
syncCtlPackages: readFileSync(join(import.meta.dir, "sync-ctl-packages.yml"), "utf8"),
|
|
807
|
+
botPrs: readFileSync(join(import.meta.dir, "bot-prs.yml"), "utf8"),
|
|
796
808
|
publishDocs: readFileSync(join(import.meta.dir, "publish-docs.yml"), "utf8"),
|
|
797
809
|
buildDocs: readFileSync(join(import.meta.dir, "build-docs.yml"), "utf8"),
|
|
798
810
|
withDisplay: readFileSync(join(import.meta.dir, "..", "doc-guide", "with-display.sh"), "utf8"),
|
|
@@ -167,8 +167,25 @@ jobs:
|
|
|
167
167
|
|
|
168
168
|
- name: Land the freshness bump on main (gate green)
|
|
169
169
|
if: steps.sync.outputs.changed == '1' && steps.gate.outcome == 'success'
|
|
170
|
+
env:
|
|
171
|
+
API_TOKEN: ${{ secrets.CI_DISPATCH_TOKEN }}
|
|
170
172
|
run: |
|
|
171
173
|
set -euo pipefail
|
|
174
|
+
# This bump is about to land, which makes any sync-dev-kit/* PR an earlier
|
|
175
|
+
# red run opened obsolete. Left open they piled up -- nine were closed
|
|
176
|
+
# by hand on 2026-10-03 -- so close them once the push has landed.
|
|
177
|
+
# Best-effort: a failure here must not fail the landing.
|
|
178
|
+
close_superseded() {
|
|
179
|
+
api="https://api.github.com/repos/$GITHUB_REPOSITORY"
|
|
180
|
+
auth=(-H "Authorization: Bearer $API_TOKEN" -H "Accept: application/vnd.github+json")
|
|
181
|
+
for n in $(curl -sS "${auth[@]}" "$api/pulls?state=open&per_page=100" \
|
|
182
|
+
| jq -r --arg repo "$GITHUB_REPOSITORY" '.[] | select(.head.repo.full_name == $repo and (.head.ref | startswith("sync-dev-kit/"))) | .number'); do
|
|
183
|
+
curl -sS -o /dev/null "${auth[@]}" -X POST "$api/issues/$n/comments" \
|
|
184
|
+
-d '{"body":"Superseded: a later sync-dev-kit run landed its bump on main."}'
|
|
185
|
+
curl -sS -o /dev/null "${auth[@]}" -X PATCH "$api/pulls/$n" -d '{"state":"closed"}'
|
|
186
|
+
echo "closed superseded PR #$n"
|
|
187
|
+
done
|
|
188
|
+
}
|
|
172
189
|
git config user.name "github-actions[bot]"
|
|
173
190
|
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
174
191
|
git add -A
|
|
@@ -184,7 +201,7 @@ jobs:
|
|
|
184
201
|
# is schedule/dispatch-only, so no loop). Rebase-and-retry so a commit
|
|
185
202
|
# landing mid-run does not red the sync; a real conflict aborts.
|
|
186
203
|
for attempt in 1 2 3 4 5; do
|
|
187
|
-
if git push origin HEAD:main; then exit 0; fi
|
|
204
|
+
if git push origin HEAD:main; then close_superseded || true; exit 0; fi
|
|
188
205
|
echo "push rejected (attempt $attempt) - rebasing onto latest origin/main"
|
|
189
206
|
# --unshallow, not a plain fetch: these runners keep their .git
|
|
190
207
|
# between jobs, and a commit an earlier job fetched at depth 1 stays
|
|
@@ -307,6 +307,13 @@ export function syncDrift(repoRoot: string, canonical: CanonicalBytes, shape: Pr
|
|
|
307
307
|
".github/workflows/sync-ctl-packages.yml",
|
|
308
308
|
r.written,
|
|
309
309
|
);
|
|
310
|
+
// bot-prs.yml likewise: verbatim, and created where absent.
|
|
311
|
+
writeIfChanged(
|
|
312
|
+
join(repoRoot, ".github/workflows/bot-prs.yml"),
|
|
313
|
+
canonical.botPrs,
|
|
314
|
+
".github/workflows/bot-prs.yml",
|
|
315
|
+
r.written,
|
|
316
|
+
);
|
|
310
317
|
// build-image.yml is the Studio bundle-image workflow; an sdk-app ships its
|
|
311
318
|
// own container, so it is not synced for that shape.
|
|
312
319
|
if (shape === "studio") syncBuildImage(repoRoot, canonical.buildImage, r);
|
|
@@ -346,6 +353,7 @@ if (import.meta.main) {
|
|
|
346
353
|
upgradeLatest: readFileSync(join(dir, "upgrade-latest.yml"), "utf8"),
|
|
347
354
|
syncDevKit: readFileSync(join(dir, "sync-dev-kit.yml"), "utf8"),
|
|
348
355
|
syncCtlPackages: readFileSync(join(dir, "sync-ctl-packages.yml"), "utf8"),
|
|
356
|
+
botPrs: readFileSync(join(dir, "bot-prs.yml"), "utf8"),
|
|
349
357
|
publishDocs: readFileSync(join(dir, "publish-docs.yml"), "utf8"),
|
|
350
358
|
buildDocs: readFileSync(join(dir, "build-docs.yml"), "utf8"),
|
|
351
359
|
withDisplay: readFileSync(join(dir, "..", "doc-guide", "with-display.sh"), "utf8"),
|
|
@@ -297,8 +297,25 @@ jobs:
|
|
|
297
297
|
|
|
298
298
|
- name: Land the bump on main (gate green)
|
|
299
299
|
if: steps.bump.outputs.changed == '1' && steps.gate.outcome == 'success'
|
|
300
|
+
env:
|
|
301
|
+
API_TOKEN: ${{ secrets.CI_DISPATCH_TOKEN }}
|
|
300
302
|
run: |
|
|
301
303
|
set -euo pipefail
|
|
304
|
+
# This bump is about to land, which makes any upgrade-latest/* PR an earlier
|
|
305
|
+
# red run opened obsolete. Left open they piled up -- nine were closed
|
|
306
|
+
# by hand on 2026-10-03 -- so close them once the push has landed.
|
|
307
|
+
# Best-effort: a failure here must not fail the landing.
|
|
308
|
+
close_superseded() {
|
|
309
|
+
api="https://api.github.com/repos/$GITHUB_REPOSITORY"
|
|
310
|
+
auth=(-H "Authorization: Bearer $API_TOKEN" -H "Accept: application/vnd.github+json")
|
|
311
|
+
for n in $(curl -sS "${auth[@]}" "$api/pulls?state=open&per_page=100" \
|
|
312
|
+
| jq -r --arg repo "$GITHUB_REPOSITORY" '.[] | select(.head.repo.full_name == $repo and (.head.ref | startswith("upgrade-latest/"))) | .number'); do
|
|
313
|
+
curl -sS -o /dev/null "${auth[@]}" -X POST "$api/issues/$n/comments" \
|
|
314
|
+
-d '{"body":"Superseded: a later upgrade-latest run landed its bump on main."}'
|
|
315
|
+
curl -sS -o /dev/null "${auth[@]}" -X PATCH "$api/pulls/$n" -d '{"state":"closed"}'
|
|
316
|
+
echo "closed superseded PR #$n"
|
|
317
|
+
done
|
|
318
|
+
}
|
|
302
319
|
git config user.name "github-actions[bot]"
|
|
303
320
|
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
304
321
|
git add -A
|
|
@@ -316,7 +333,7 @@ jobs:
|
|
|
316
333
|
# landing mid-run does not fail the bump; a genuine content conflict
|
|
317
334
|
# still aborts loudly rather than force-pushing over other work.
|
|
318
335
|
for attempt in 1 2 3 4 5; do
|
|
319
|
-
if git push origin HEAD:main; then exit 0; fi
|
|
336
|
+
if git push origin HEAD:main; then close_superseded || true; exit 0; fi
|
|
320
337
|
echo "push rejected (attempt $attempt) - rebasing onto latest origin/main"
|
|
321
338
|
# --unshallow, not a plain fetch: these runners keep their .git
|
|
322
339
|
# between jobs, and a commit an earlier job fetched at depth 1 stays
|
package/create-product/canon.ts
CHANGED
|
@@ -19,6 +19,7 @@ export interface Canon {
|
|
|
19
19
|
upgradeLatest: string;
|
|
20
20
|
syncDevKit: string;
|
|
21
21
|
syncCtlPackages: string;
|
|
22
|
+
botPrs: string;
|
|
22
23
|
checks: string;
|
|
23
24
|
biome: string;
|
|
24
25
|
tsconfigBase: string;
|
|
@@ -44,6 +45,7 @@ export function loadCanon(): Canon {
|
|
|
44
45
|
upgradeLatest: readFileSync(join(conventionsDir, "upgrade-latest.yml"), "utf8"),
|
|
45
46
|
syncDevKit: readFileSync(join(conventionsDir, "sync-dev-kit.yml"), "utf8"),
|
|
46
47
|
syncCtlPackages: readFileSync(join(conventionsDir, "sync-ctl-packages.yml"), "utf8"),
|
|
48
|
+
botPrs: readFileSync(join(conventionsDir, "bot-prs.yml"), "utf8"),
|
|
47
49
|
checks: readFileSync(join(conventionsDir, "checks.yml"), "utf8"),
|
|
48
50
|
buildDocs: readFileSync(join(conventionsDir, "build-docs.yml"), "utf8"),
|
|
49
51
|
withDisplay: readFileSync(join(conventionsDir, "..", "doc-guide", "with-display.sh"), "utf8"),
|
|
@@ -222,6 +222,7 @@ function conventionFiles(ctx: ShapeContext, shape: ShapeModule): GeneratedFile[]
|
|
|
222
222
|
{ path: ".github/workflows/upgrade-latest.yml", content: fillProduct(canon.upgradeLatest) },
|
|
223
223
|
{ path: ".github/workflows/sync-dev-kit.yml", content: fillProduct(canon.syncDevKit) },
|
|
224
224
|
{ path: ".github/workflows/sync-ctl-packages.yml", content: canon.syncCtlPackages },
|
|
225
|
+
{ path: ".github/workflows/bot-prs.yml", content: canon.botPrs },
|
|
225
226
|
{ path: ".github/workflows/build-image.yml", content: fillProduct(canon.buildImage) },
|
|
226
227
|
{ path: ".github/actions/ci-status-dispatch/action.yml", content: loadAsset("ci-status-dispatch.yml") },
|
|
227
228
|
{ path: "manifest.seed.json", content: seedJson(ctx) },
|