Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
204 lines
6.8 KiB
JavaScript
204 lines
6.8 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import test from "node:test";
|
|
import { dirname, join } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
import YAML from "yaml";
|
|
|
|
const workspaceRoot = join(dirname(fileURLToPath(import.meta.url)), "../..");
|
|
const workflow = YAML.parse(
|
|
readFileSync(join(workspaceRoot, ".github/workflows/pr-checks.yml"), "utf8")
|
|
);
|
|
|
|
const windowsWorkflows = [
|
|
{
|
|
expectedPaths: ["apps/desktop/**", "services/tuttid/builtin-apps/**"],
|
|
path: ".github/workflows/windows-desktop-alpha.yml"
|
|
},
|
|
{
|
|
expectedPaths: ["packages/agent/daemon/**"],
|
|
expectedSetupSteps: ["Setup Go", "Setup Node"],
|
|
path: ".github/workflows/windows-agent-adapters.yml"
|
|
},
|
|
{
|
|
expectedPaths: ["packages/agent/daemon/**", "services/tuttid/**"],
|
|
expectedSetupSteps: ["Setup pnpm", "Setup Node.js", "Setup Go"],
|
|
path: ".github/workflows/windows-daemon-adapters.yml"
|
|
}
|
|
].map((config) => ({
|
|
...config,
|
|
workflow: YAML.parse(readFileSync(join(workspaceRoot, config.path), "utf8"))
|
|
}));
|
|
|
|
test("PR checks route repository groups through shared scripts", () => {
|
|
const changes = workflow.jobs.changes;
|
|
const classificationStep = changes.steps.find(
|
|
(step) => step.name === "Classify changed files"
|
|
);
|
|
const toolingScripts = stepScripts(workflow.jobs["tooling-consistency"]);
|
|
|
|
assert.match(
|
|
classificationStep.run,
|
|
/tools\/scripts\/change-classification\.mjs/u
|
|
);
|
|
assert.match(classificationStep.run, /--base/u);
|
|
assert.match(classificationStep.run, /--head/u);
|
|
for (const group of ["contracts", "generated", "boundaries"]) {
|
|
assert.match(toolingScripts, new RegExp(`--group ${group}`, "u"));
|
|
}
|
|
assert.match(toolingScripts, /--head/u);
|
|
});
|
|
|
|
test("language jobs do not own repository checks", () => {
|
|
for (const jobName of [
|
|
"ts-lint",
|
|
"ts-test-shards",
|
|
"ts-tests",
|
|
"go-tests",
|
|
"go-lint"
|
|
]) {
|
|
assert.doesNotMatch(
|
|
stepScripts(workflow.jobs[jobName]),
|
|
/run-repository-checks|test:tools|pnpm check:/u,
|
|
jobName
|
|
);
|
|
}
|
|
});
|
|
|
|
test("package pack CI receives the classified package subset", () => {
|
|
const changes = workflow.jobs.changes;
|
|
const packJob = workflow.jobs["npm-package-packs"];
|
|
const packStep = packJob.steps.find(
|
|
(step) => step.name === "Validate package tarballs"
|
|
);
|
|
|
|
assert.equal(
|
|
changes.outputs.pack_packages,
|
|
"${{ steps.changed-files.outputs.pack_packages }}"
|
|
);
|
|
assert.equal(
|
|
packStep.env.PACK_PACKAGES,
|
|
"${{ needs.changes.outputs.pack_packages }}"
|
|
);
|
|
assert.match(packStep.run, /--packages-json/u);
|
|
});
|
|
|
|
test("TypeScript Tests preserves its required context across package shards", () => {
|
|
const changes = workflow.jobs.changes;
|
|
const testJob = workflow.jobs["ts-tests"];
|
|
const shardJob = workflow.jobs["ts-test-shards"];
|
|
const testStep = shardJob.steps.find(
|
|
(step) => step.name === "Run TypeScript tests"
|
|
);
|
|
const requireShardsStep = testJob.steps.find(
|
|
(step) => step.name === "Require selected test shards"
|
|
);
|
|
|
|
assert.equal(testJob.name, "TypeScript Tests");
|
|
assert.equal(testJob.if, "always()");
|
|
assert.equal(
|
|
changes.outputs.test_packages,
|
|
"${{ steps.changed-files.outputs.test_packages }}"
|
|
);
|
|
assert.equal(
|
|
changes.outputs.test_shards,
|
|
"${{ steps.changed-files.outputs.test_shards }}"
|
|
);
|
|
assert.equal(
|
|
shardJob.strategy.matrix.shard,
|
|
"${{ fromJSON(needs.changes.outputs.test_shards) }}"
|
|
);
|
|
assert.equal(
|
|
testStep.env.TEST_PACKAGES,
|
|
"${{ needs.changes.outputs.test_packages }}"
|
|
);
|
|
assert.match(testStep.run, /--packages-json/u);
|
|
assert.match(testStep.run, /--shard/u);
|
|
assert.match(testStep.run, /--packages-json[^\n]*--shard/u);
|
|
assert.equal(
|
|
requireShardsStep.env.SHARDS_RESULT,
|
|
"${{ needs.ts-test-shards.result }}"
|
|
);
|
|
});
|
|
|
|
test("Windows workflows route source changes without self-triggering", () => {
|
|
for (const { expectedPaths, path, workflow } of windowsWorkflows) {
|
|
const paths = workflow.on?.pull_request?.paths ?? [];
|
|
assert.ok(paths.length > 0, path);
|
|
assert.ok(!paths.includes(path), `${path} must not trigger itself`);
|
|
for (const expectedPath of expectedPaths) {
|
|
assert.ok(paths.includes(expectedPath), `${path}: ${expectedPath}`);
|
|
}
|
|
}
|
|
});
|
|
|
|
test("Windows Desktop Alpha packages only packaging-relevant changes", () => {
|
|
const { workflow } = windowsWorkflows[0];
|
|
const [job] = Object.values(workflow.jobs);
|
|
const classification = job.steps.find(
|
|
(step) => step.name === "Classify changed files"
|
|
);
|
|
const bundleBuild = job.steps.find(
|
|
(step) => step.name === "Build Windows Desktop bundles"
|
|
);
|
|
const installerBuild = job.steps.find(
|
|
(step) => step.name === "Build unsigned Windows NSIS package"
|
|
);
|
|
const packagedSmoke = job.steps.find(
|
|
(step) =>
|
|
step.name === "Smoke test packaged Workspace App shell and Onboarding"
|
|
);
|
|
const installerUpload = job.steps.find(
|
|
(step) => step.name === "Upload Windows installer"
|
|
);
|
|
const packageCondition =
|
|
"github.event_name == 'workflow_dispatch' || steps.changed-files.outputs.build_windows_installer == 'true'";
|
|
|
|
assert.match(
|
|
classification.run,
|
|
/tools\/scripts\/change-classification\.mjs/u
|
|
);
|
|
assert.equal(
|
|
bundleBuild.if,
|
|
"github.event_name == 'pull_request' && steps.changed-files.outputs.build_windows_installer != 'true'"
|
|
);
|
|
assert.match(bundleBuild.run, /@tutti-os\/desktop build$/u);
|
|
assert.equal(installerBuild.if, packageCondition);
|
|
assert.match(installerBuild.run, /@tutti-os\/desktop build:win:prepared/u);
|
|
assert.equal(packagedSmoke.if, packageCondition);
|
|
assert.equal(installerUpload.if, packageCondition);
|
|
});
|
|
|
|
test("Windows adapter workflows warm default-branch caches", () => {
|
|
for (const { expectedPaths, path, workflow } of windowsWorkflows.filter(
|
|
({ expectedSetupSteps }) => expectedSetupSteps
|
|
)) {
|
|
assert.deepEqual(workflow.on?.push?.branches, ["main"], path);
|
|
assert.deepEqual(workflow.on?.push?.paths, expectedPaths, path);
|
|
}
|
|
});
|
|
|
|
test("Windows adapter workflows keep setup and tests scoped", () => {
|
|
for (const { expectedSetupSteps, path, workflow } of windowsWorkflows.filter(
|
|
({ expectedSetupSteps }) => expectedSetupSteps
|
|
)) {
|
|
const [job] = Object.values(workflow.jobs);
|
|
const checkout = job.steps.find((step) => step.name === "Checkout");
|
|
const setupSteps = job.steps
|
|
.filter((step) => step.name?.startsWith("Setup "))
|
|
.map((step) => step.name);
|
|
const goTestCommands = stepScripts(job)
|
|
.split("\n")
|
|
.filter((line) => line.trimStart().startsWith("go test "));
|
|
|
|
assert.deepEqual(setupSteps, expectedSetupSteps, path);
|
|
assert.equal(checkout.with?.["fetch-depth"], undefined, path);
|
|
assert.equal(goTestCommands.length, 1, path);
|
|
}
|
|
});
|
|
|
|
function stepScripts(job) {
|
|
return job.steps.map((step) => step.run ?? "").join("\n");
|
|
}
|