Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Changesets

Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)

We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
15 changes: 15 additions & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
"changelog": ["@changesets/changelog-github", { "repo": "cloudflare/computer" }],
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": ["@example/*", "@cloudflare/example-*"],
"privatePackages": {
"version": true,
"tag": false
}
}
86 changes: 86 additions & 0 deletions .github/changeset-publish.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#!/usr/bin/env node
// Called by the changesets action in release.yml as the `publish`
// command. It converges the computerd container image before publishing npm
// packages, so a rerun after npm failure is safe: Docker tags are pushed again
// first, then `changeset publish` publishes anything still missing from npm.

import { execFileSync } from "node:child_process";
import { chmodSync, copyFileSync, mkdirSync, readFileSync } from "node:fs";

function run(cmd, args, options = {}) {
execFileSync(cmd, args, { stdio: "inherit", ...options });
}

function readJson(path) {
return JSON.parse(readFileSync(path, "utf8"));
}

function isStable(version) {
return !version.includes("-");
}

function computerdImageTags(version) {
const tags = [
`ghcr.io/cloudflare/computer-computerd-linux-x64:${version}`,
`registry.cloudflare.com/library/computer-computerd-linux-x64:${version}`,
];

if (isStable(version)) {
tags.push(
"ghcr.io/cloudflare/computer-computerd-linux-x64:latest",
"registry.cloudflare.com/library/computer-computerd-linux-x64:latest",
);
}

return tags;
}

function imageExists(tag) {
try {
execFileSync("docker", ["buildx", "imagetools", "inspect", tag], {
stdio: "ignore",
});
return true;
} catch {
return false;
}
}

function stageComputerdBinary() {
mkdirSync("packages/computer-computerd-linux-x64/bin", { recursive: true });
copyFileSync(
"artifacts/computerd/computerd-linux-x64",
"packages/computer-computerd-linux-x64/bin/computerd",
);
chmodSync("packages/computer-computerd-linux-x64/bin/computerd", 0o755);
}

const { version } = readJson("packages/computerd/package.json");
const tags = computerdImageTags(version);
const missingTags = tags.filter((tag) => !imageExists(tag));

if (missingTags.length === 0) {
console.log(`computerd image ${version} already exists in both registries; skipping image build`);
} else {
console.log(`publishing computerd image for @cloudflare/computerd@${version}`);
console.log(`missing tag(s): ${missingTags.join(", ")}`);

run("npm", ["run", "build:bin", "--workspace", "@cloudflare/computerd"]);
stageComputerdBinary();

const tagArgs = tags.flatMap((tag) => ["--tag", tag]);
run("docker", [
"buildx",
"build",
"--platform",
"linux/amd64",
"--push",
"--provenance=false",
...tagArgs,
"--file",
"packages/computer-computerd-linux-x64/Dockerfile",
"packages/computer-computerd-linux-x64",
]);
}

run("npx", ["changeset", "publish"]);
114 changes: 114 additions & 0 deletions .github/changeset-version.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#!/usr/bin/env node
// Called by the changesets action in release.yml as the `version`
// command. It consumes pending changesets, then applies release-time
// references that must be committed with the Version Packages PR.
//
// Changesets owns package versions and changelogs, including private
// packages (`privatePackages.version` is enabled in .changeset/config.json).
// The linux-x64 image context is derivative of @cloudflare/computerd, not a
// changeset target, so this script copies computerd's version into that
// package.json and updates Dockerfile/docs image pins to the same version.

import { execFileSync } from "node:child_process";
import { readdirSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";

const IMAGE_TAG_RE =
/(ghcr\.io\/cloudflare\/computer-computerd-linux-x64:)[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?/g;
const TEXT_FILE_EXTENSIONS = new Set([
"",
".Dockerfile",
".js",
".json",
".md",
".mjs",
".ts",
".yaml",
".yml",
]);
const IGNORED_DIRS = new Set([".git", ".devbox", ".venv", "artifacts", "dist", "node_modules"]);
const IGNORED_FILES = new Set([
"package-lock.json",
"CHANGELOG.md",
".github/changeset-version.mjs",
]);

function run(cmd, args) {
execFileSync(cmd, args, { stdio: "inherit" });
}

function readJson(path) {
return JSON.parse(readFileSync(path, "utf8"));
}

function writeJson(path, value) {
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`);
}

function* walk(dir) {
for (const entry of readdirSync(dir, { withFileTypes: true })) {
if (entry.name.startsWith(".") && entry.name !== ".github") continue;

const path = join(dir, entry.name);
if (entry.isDirectory()) {
if (!IGNORED_DIRS.has(entry.name)) yield* walk(path);
continue;
}

if (!entry.isFile()) continue;
if (IGNORED_FILES.has(path) || IGNORED_FILES.has(entry.name)) continue;
if (entry.name === "Dockerfile" || entry.name.startsWith("Dockerfile.")) {
yield path;
continue;
}

const suffix = entry.name.includes(".") ? entry.name.slice(entry.name.lastIndexOf(".")) : "";
if (TEXT_FILE_EXTENSIONS.has(suffix)) yield path;
}
}

function syncDerivedImagePackage(computerdVersion) {
const path = "packages/computer-computerd-linux-x64/package.json";
const pkg = readJson(path);
pkg.version = computerdVersion;
pkg.private = true;
writeJson(path, pkg);
console.log(`${path}: derivative image package version → ${computerdVersion}`);
}

function updateImageReferences(computerdVersion) {
let updatedFiles = 0;
let replacements = 0;

for (const file of walk(".")) {
const before = readFileSync(file, "utf8");
if (!IMAGE_TAG_RE.test(before)) {
IMAGE_TAG_RE.lastIndex = 0;
continue;
}

IMAGE_TAG_RE.lastIndex = 0;
const after = before.replace(IMAGE_TAG_RE, `$1${computerdVersion}`);
if (after !== before) {
const count = before.match(IMAGE_TAG_RE)?.length ?? 0;
writeFileSync(file, after);
updatedFiles += 1;
replacements += count;
console.log(`${file}: computerd image tag → ${computerdVersion}`);
}
IMAGE_TAG_RE.lastIndex = 0;
}

console.log(
`updated ${replacements} computerd image tag reference(s) across ${updatedFiles} file(s)`,
);
}

run("npx", ["changeset", "version"]);

const { version: computerdVersion } = readJson("packages/computerd/package.json");
syncDerivedImagePackage(computerdVersion);
updateImageReferences(computerdVersion);

// changeset version doesn't update package-lock.json (changesets/changesets#421).
run("npm", ["install", "--package-lock-only"]);
16 changes: 11 additions & 5 deletions .github/workflows/close-unrequested-prs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,19 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const pullRequest = context.payload.pull_request;
const { owner, repo } = context.repo;
const pull_number = context.payload.pull_request.number;
const issue_number = pull_number;
const allowedAssociations = new Set(['OWNER', 'MEMBER', 'COLLABORATOR']);
const allowedBots = new Set(['dependabot[bot]', 'renovate[bot]']);
const allowedBots = new Set(['dependabot[bot]', 'renovate[bot]', 'github-actions[bot]']);
const allowedLabels = new Set(['allow-pr']);

const { data: pullRequest } = await github.rest.pulls.get({
owner,
repo,
pull_number,
});

if (allowedAssociations.has(pullRequest.author_association)) {
return;
}
Expand All @@ -35,8 +43,6 @@ jobs:
return;
}

const { owner, repo } = context.repo;
const issue_number = pullRequest.number;
const body = [
'Thanks for your interest in Cloudflare Computer.',
'',
Expand All @@ -49,4 +55,4 @@ jobs:
].join('\n');

await github.rest.issues.createComment({ owner, repo, issue_number, body });
await github.rest.pulls.update({ owner, repo, pull_number: issue_number, state: 'closed' });
await github.rest.pulls.update({ owner, repo, pull_number, state: 'closed' });
Loading
Loading