Skip to content

Commit 76d9e75

Browse files
committed
ci: adopt changesets release flow
1 parent 60494a0 commit 76d9e75

21 files changed

Lines changed: 1566 additions & 338 deletions

File tree

.changeset/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Changesets
2+
3+
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
4+
with multi-package repos, or single-package repos to help you version and publish your code. You can
5+
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
6+
7+
We have a quick list of common questions to get you started engaging with this project in
8+
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)

.changeset/config.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
3+
"changelog": ["@changesets/changelog-github", { "repo": "cloudflare/computer" }],
4+
"commit": false,
5+
"fixed": [],
6+
"linked": [],
7+
"access": "public",
8+
"baseBranch": "main",
9+
"updateInternalDependencies": "patch",
10+
"ignore": ["@example/*", "@cloudflare/example-*"],
11+
"privatePackages": {
12+
"version": true,
13+
"tag": false
14+
}
15+
}

.github/changeset-publish.mjs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
#!/usr/bin/env node
2+
// Called by the changesets action in release.yml as the `publish`
3+
// command. It converges the computerd container image before publishing npm
4+
// packages, so a rerun after npm failure is safe: Docker tags are pushed again
5+
// first, then `changeset publish` publishes anything still missing from npm.
6+
7+
import { execFileSync } from "node:child_process";
8+
import { chmodSync, copyFileSync, mkdirSync, readFileSync } from "node:fs";
9+
10+
function run(cmd, args, options = {}) {
11+
execFileSync(cmd, args, { stdio: "inherit", ...options });
12+
}
13+
14+
function readJson(path) {
15+
return JSON.parse(readFileSync(path, "utf8"));
16+
}
17+
18+
function isStable(version) {
19+
return !version.includes("-");
20+
}
21+
22+
function computerdImageTags(version) {
23+
const tags = [
24+
`ghcr.io/cloudflare/computer-computerd-linux-x64:${version}`,
25+
`registry.cloudflare.com/library/computer-computerd-linux-x64:${version}`,
26+
];
27+
28+
if (isStable(version)) {
29+
tags.push(
30+
"ghcr.io/cloudflare/computer-computerd-linux-x64:latest",
31+
"registry.cloudflare.com/library/computer-computerd-linux-x64:latest",
32+
);
33+
}
34+
35+
return tags;
36+
}
37+
38+
function imageExists(tag) {
39+
try {
40+
execFileSync("docker", ["buildx", "imagetools", "inspect", tag], {
41+
stdio: "ignore",
42+
});
43+
return true;
44+
} catch {
45+
return false;
46+
}
47+
}
48+
49+
function stageComputerdBinary() {
50+
mkdirSync("packages/computer-computerd-linux-x64/bin", { recursive: true });
51+
copyFileSync(
52+
"artifacts/computerd/computerd-linux-x64",
53+
"packages/computer-computerd-linux-x64/bin/computerd",
54+
);
55+
chmodSync("packages/computer-computerd-linux-x64/bin/computerd", 0o755);
56+
}
57+
58+
const { version } = readJson("packages/computerd/package.json");
59+
const tags = computerdImageTags(version);
60+
const missingTags = tags.filter((tag) => !imageExists(tag));
61+
62+
if (missingTags.length === 0) {
63+
console.log(`computerd image ${version} already exists in both registries; skipping image build`);
64+
} else {
65+
console.log(`publishing computerd image for @cloudflare/computerd@${version}`);
66+
console.log(`missing tag(s): ${missingTags.join(", ")}`);
67+
68+
run("npm", ["run", "build:bin", "--workspace", "@cloudflare/computerd"]);
69+
stageComputerdBinary();
70+
71+
const tagArgs = tags.flatMap((tag) => ["--tag", tag]);
72+
run("docker", [
73+
"buildx",
74+
"build",
75+
"--platform",
76+
"linux/amd64",
77+
"--push",
78+
"--provenance=false",
79+
...tagArgs,
80+
"--file",
81+
"packages/computer-computerd-linux-x64/Dockerfile",
82+
"packages/computer-computerd-linux-x64",
83+
]);
84+
}
85+
86+
run("npx", ["changeset", "publish"]);

.github/changeset-version.mjs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
#!/usr/bin/env node
2+
// Called by the changesets action in release.yml as the `version`
3+
// command. It consumes pending changesets, then applies release-time
4+
// references that must be committed with the Version Packages PR.
5+
//
6+
// Changesets owns package versions and changelogs, including private
7+
// packages (`privatePackages.version` is enabled in .changeset/config.json).
8+
// The linux-x64 image context is derivative of @cloudflare/computerd, not a
9+
// changeset target, so this script copies computerd's version into that
10+
// package.json and updates Dockerfile/docs image pins to the same version.
11+
12+
import { execFileSync } from "node:child_process";
13+
import { readdirSync, readFileSync, writeFileSync } from "node:fs";
14+
import { join } from "node:path";
15+
16+
const IMAGE_TAG_RE =
17+
/(ghcr\.io\/cloudflare\/computer-computerd-linux-x64:)[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?/g;
18+
const TEXT_FILE_EXTENSIONS = new Set([
19+
"",
20+
".Dockerfile",
21+
".js",
22+
".json",
23+
".md",
24+
".mjs",
25+
".ts",
26+
".yaml",
27+
".yml",
28+
]);
29+
const IGNORED_DIRS = new Set([".git", ".devbox", ".venv", "artifacts", "dist", "node_modules"]);
30+
const IGNORED_FILES = new Set([
31+
"package-lock.json",
32+
"CHANGELOG.md",
33+
".github/changeset-version.mjs",
34+
]);
35+
36+
function run(cmd, args) {
37+
execFileSync(cmd, args, { stdio: "inherit" });
38+
}
39+
40+
function readJson(path) {
41+
return JSON.parse(readFileSync(path, "utf8"));
42+
}
43+
44+
function writeJson(path, value) {
45+
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`);
46+
}
47+
48+
function* walk(dir) {
49+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
50+
if (entry.name.startsWith(".") && entry.name !== ".github") continue;
51+
52+
const path = join(dir, entry.name);
53+
if (entry.isDirectory()) {
54+
if (!IGNORED_DIRS.has(entry.name)) yield* walk(path);
55+
continue;
56+
}
57+
58+
if (!entry.isFile()) continue;
59+
if (IGNORED_FILES.has(path) || IGNORED_FILES.has(entry.name)) continue;
60+
if (entry.name === "Dockerfile" || entry.name.startsWith("Dockerfile.")) {
61+
yield path;
62+
continue;
63+
}
64+
65+
const suffix = entry.name.includes(".") ? entry.name.slice(entry.name.lastIndexOf(".")) : "";
66+
if (TEXT_FILE_EXTENSIONS.has(suffix)) yield path;
67+
}
68+
}
69+
70+
function syncDerivedImagePackage(computerdVersion) {
71+
const path = "packages/computer-computerd-linux-x64/package.json";
72+
const pkg = readJson(path);
73+
pkg.version = computerdVersion;
74+
pkg.private = true;
75+
writeJson(path, pkg);
76+
console.log(`${path}: derivative image package version → ${computerdVersion}`);
77+
}
78+
79+
function updateImageReferences(computerdVersion) {
80+
let updatedFiles = 0;
81+
let replacements = 0;
82+
83+
for (const file of walk(".")) {
84+
const before = readFileSync(file, "utf8");
85+
if (!IMAGE_TAG_RE.test(before)) {
86+
IMAGE_TAG_RE.lastIndex = 0;
87+
continue;
88+
}
89+
90+
IMAGE_TAG_RE.lastIndex = 0;
91+
const after = before.replace(IMAGE_TAG_RE, `$1${computerdVersion}`);
92+
if (after !== before) {
93+
const count = before.match(IMAGE_TAG_RE)?.length ?? 0;
94+
writeFileSync(file, after);
95+
updatedFiles += 1;
96+
replacements += count;
97+
console.log(`${file}: computerd image tag → ${computerdVersion}`);
98+
}
99+
IMAGE_TAG_RE.lastIndex = 0;
100+
}
101+
102+
console.log(
103+
`updated ${replacements} computerd image tag reference(s) across ${updatedFiles} file(s)`,
104+
);
105+
}
106+
107+
run("npx", ["changeset", "version"]);
108+
109+
const { version: computerdVersion } = readJson("packages/computerd/package.json");
110+
syncDerivedImagePackage(computerdVersion);
111+
updateImageReferences(computerdVersion);
112+
113+
// changeset version doesn't update package-lock.json (changesets/changesets#421).
114+
run("npm", ["install", "--package-lock-only"]);

.github/workflows/close-unrequested-prs.yml

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,19 @@ jobs:
1717
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
1818
with:
1919
script: |
20-
const pullRequest = context.payload.pull_request;
20+
const { owner, repo } = context.repo;
21+
const pull_number = context.payload.pull_request.number;
22+
const issue_number = pull_number;
2123
const allowedAssociations = new Set(['OWNER', 'MEMBER', 'COLLABORATOR']);
22-
const allowedBots = new Set(['dependabot[bot]', 'renovate[bot]']);
24+
const allowedBots = new Set(['dependabot[bot]', 'renovate[bot]', 'github-actions[bot]']);
2325
const allowedLabels = new Set(['allow-pr']);
2426
27+
const { data: pullRequest } = await github.rest.pulls.get({
28+
owner,
29+
repo,
30+
pull_number,
31+
});
32+
2533
if (allowedAssociations.has(pullRequest.author_association)) {
2634
return;
2735
}
@@ -35,8 +43,6 @@ jobs:
3543
return;
3644
}
3745
38-
const { owner, repo } = context.repo;
39-
const issue_number = pullRequest.number;
4046
const body = [
4147
'Thanks for your interest in Cloudflare Computer.',
4248
'',
@@ -49,4 +55,4 @@ jobs:
4955
].join('\n');
5056
5157
await github.rest.issues.createComment({ owner, repo, issue_number, body });
52-
await github.rest.pulls.update({ owner, repo, pull_number: issue_number, state: 'closed' });
58+
await github.rest.pulls.update({ owner, repo, pull_number, state: 'closed' });

0 commit comments

Comments
 (0)