Files
copilotkit__copilotkit/scripts/release/lib/channels-umbrella.test.ts
Benjamin Taylor 1110f18e30 fix(release): exempt first-party publishes from the release-age gate
The packed Channels umbrella (and the runtime-package verify) install
first-party packages straight from the registry, so a just-published
@copilotkit/* version fails pnpm's 24h minimumReleaseAge check — this is
what reddened `unit (20.x)` on channels PRs. The enumerated exclude lists
drift every time an internal dependency is added (main already had to bolt
on @copilotkit/channels-core, then #6012 added @copilotkit/core + shared).

Wildcard the @copilotkit/* scope (org-owned, same publish pipeline — no
outsider can inject a package, and it stops the drift). Keep @ag-ui
enumerated rather than wildcarded: it's a separate upstream org, so we don't
extend immediate-install trust to its entire scope. Third-party deps stay
fully gated. Applied to both the generated consumer workspace and the
repo-root .npmrc.

Verified: `pnpm run verify:channels-umbrella` installs the packed umbrella
with no ERR_PNPM_NO_MATURE_MATCHING_VERSION; umbrella unit tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 13:10:58 -05:00

155 lines
4.7 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
ADAPTERS,
createConsumerWorkspaceYaml,
createConsumerManifest,
FAMILY,
RELEASE_AGE_EXCLUDE,
validatePackedManifests,
} from "./channels-umbrella.js";
import type { PackedManifest } from "./channels-umbrella.js";
function validManifests(): Map<string, PackedManifest> {
const core: PackedManifest = {
name: "@copilotkit/channels-core",
version: "0.1.2",
dependencies: { "@copilotkit/channels-ui": "~0.1.2" },
};
const ui: PackedManifest = {
name: "@copilotkit/channels-ui",
version: "0.1.2",
};
const adapters: PackedManifest[] = ADAPTERS.map((name, index) => ({
name,
version: `0.1.${index + 2}`,
dependencies: {
"@copilotkit/channels-core": "^0.1.2",
"@copilotkit/channels-ui": "^0.1.2",
},
}));
const family = [core, ui, ...adapters];
const umbrella: PackedManifest = {
name: "@copilotkit/channels",
version: "0.2.0",
dependencies: Object.fromEntries(
family.map((manifest) => [manifest.name, manifest.version]),
),
};
return new Map(
[...family, umbrella].map((manifest) => [manifest.name, manifest] as const),
);
}
describe("validatePackedManifests", () => {
it("accepts an exact compatible snapshot", () => {
expect(validatePackedManifests(validManifests())).toEqual([]);
});
it("rejects a non-exact umbrella dependency", () => {
const manifests = validManifests();
manifests.get("@copilotkit/channels")!.dependencies![
"@copilotkit/channels-core"
] = "^0.1.2";
expect(validatePackedManifests(manifests)).toContainEqual(
expect.stringContaining("must pack @copilotkit/channels-core exactly"),
);
});
it("rejects an adapter range that excludes packed core", () => {
const manifests = validManifests();
manifests.get("@copilotkit/channels-slack")!.dependencies![
"@copilotkit/channels-core"
] = "^0.2.0";
expect(validatePackedManifests(manifests)).toContainEqual(
expect.stringContaining("does not accept 0.1.2"),
);
});
it("rejects an adapter back-edge to the umbrella", () => {
const manifests = validManifests();
manifests.get("@copilotkit/channels-slack")!.dependencies![
"@copilotkit/channels"
] = "~0.1.1";
expect(validatePackedManifests(manifests)).toContainEqual(
expect.stringContaining(
"@copilotkit/channels-slack must not depend on @copilotkit/channels",
),
);
});
it("rejects a foundation back-edge to the umbrella", () => {
const manifests = validManifests();
manifests.get("@copilotkit/channels-core")!.dependencies![
"@copilotkit/channels"
] = "^0.2.0";
expect(validatePackedManifests(manifests)).toContainEqual(
expect.stringContaining(
"@copilotkit/channels-core must not depend on @copilotkit/channels",
),
);
});
});
describe("createConsumerManifest", () => {
it("installs only the packed umbrella and overrides its local family", () => {
expect(
createConsumerManifest({
umbrellaTarball: "/tmp/channels.tgz",
packageManager: "pnpm@10.33.4",
typescript: "^5.6.3",
nodeTypes: "^22.10.0",
overrides: new Map([
["@copilotkit/channels-core", "/tmp/channels-core.tgz"],
]),
}),
).toEqual({
name: "channels-umbrella-consumer",
version: "0.0.0",
private: true,
type: "module",
packageManager: "pnpm@10.33.4",
dependencies: {
"@copilotkit/channels": "file:/tmp/channels.tgz",
},
devDependencies: {
"@types/node": "^22.10.0",
typescript: "^5.6.3",
},
pnpm: {
overrides: {
"@copilotkit/channels-core": "file:/tmp/channels-core.tgz",
},
},
});
});
});
describe("createConsumerWorkspaceYaml", () => {
it("exempts first-party packages from the release-age gate", () => {
const workspace = createConsumerWorkspaceYaml();
for (const entry of RELEASE_AGE_EXCLUDE) {
expect(workspace).toContain(` - ${JSON.stringify(entry)}\n`);
}
// Every packed Channels package — and the first-party deps that dragged in
// the age-gate failure (@copilotkit/core, @copilotkit/shared) — is covered
// by the @copilotkit/* wildcard, so none can trip the gate as new deps are
// added.
expect(RELEASE_AGE_EXCLUDE).toContain("@copilotkit/*");
for (const name of [...FAMILY, "@copilotkit/core", "@copilotkit/shared"]) {
expect(name.startsWith("@copilotkit/")).toBe(true);
}
// @ag-ui is a separate upstream org — its packages are enumerated, never
// blanket-wildcarded, so we don't extend immediate-install trust to the
// whole scope.
expect(RELEASE_AGE_EXCLUDE).not.toContain("@ag-ui/*");
});
});