70bf21e064
Deploy (to testing) and Test Playground Preview Worker / Deploy Playground Preview Worker (testing) (push) Has been skipped
Deploy Workers Shared Staging / Deploy Workers Shared Staging (push) Failing after 0s
Prerelease / build (push) Has been skipped
Handle Changesets / Handle Changesets (push) Has been cancelled
Semgrep OSS scan / semgrep-oss (push) Has been cancelled
400 lines
10 KiB
TypeScript
400 lines
10 KiB
TypeScript
import { spawnSync } from "node:child_process";
|
|
import { writeFileSync } from "node:fs";
|
|
import dedent from "ts-dedent";
|
|
import { describe, it, vitest } from "vitest";
|
|
import {
|
|
commitAndPush,
|
|
generateChangesetHeader,
|
|
generateCommitMessage,
|
|
getDependencyChanges,
|
|
getPackageJsonDiff,
|
|
parseDiffForChanges,
|
|
writeChangeSet,
|
|
} from "../generate-dependabot-pr-changesets";
|
|
import type { Mock } from "vitest";
|
|
|
|
vitest.mock("node:child_process", async () => {
|
|
return {
|
|
spawnSync: vitest.fn(),
|
|
};
|
|
});
|
|
|
|
vitest.mock("node:fs", async () => {
|
|
return {
|
|
writeFileSync: vitest.fn(),
|
|
};
|
|
});
|
|
|
|
describe("getPackageJsonDiff()", () => {
|
|
it("should call spawnSync to the appropriate git diff command", ({
|
|
expect,
|
|
}) => {
|
|
(spawnSync as Mock).mockReturnValue({ output: [] });
|
|
getPackageJsonDiff("/path/to/package.json");
|
|
expect(spawnSync).toHaveBeenCalledOnce();
|
|
expect((spawnSync as Mock).mock.lastCall).toMatchInlineSnapshot(`
|
|
[
|
|
"git",
|
|
[
|
|
"diff",
|
|
"HEAD~1",
|
|
"/path/to/package.json",
|
|
],
|
|
{
|
|
"encoding": "utf-8",
|
|
},
|
|
]
|
|
`);
|
|
});
|
|
|
|
it("should split the output into lines", ({ expect }) => {
|
|
(spawnSync as Mock).mockReturnValue({
|
|
output: ["line 1", "line 2\nline 3", "line 4"],
|
|
});
|
|
const diffLines = getPackageJsonDiff("/path/to/package.json");
|
|
expect(diffLines).toMatchInlineSnapshot(`
|
|
[
|
|
"line 1",
|
|
"line 2",
|
|
"line 3",
|
|
"line 4",
|
|
]
|
|
`);
|
|
});
|
|
});
|
|
|
|
describe("parseDiffForChanges()", () => {
|
|
it("should return a map containing the changes for each package", ({
|
|
expect,
|
|
}) => {
|
|
const changes = parseDiffForChanges([
|
|
`- "package": "^0.0.1"`,
|
|
`+ "package": "^0.0.2",`,
|
|
`- "@namespace/package": "1.3.4"`,
|
|
`+ "@namespace/package": "1.4.5",`,
|
|
]);
|
|
expect(changes).toEqual(
|
|
new Map([
|
|
["package", { from: "^0.0.1", to: "^0.0.2" }],
|
|
["@namespace/package", { from: "1.3.4", to: "1.4.5" }],
|
|
])
|
|
);
|
|
});
|
|
|
|
it("should ignore lines that do not match a change", ({ expect }) => {
|
|
const changes = parseDiffForChanges(["", undefined, "random text"]);
|
|
expect(changes.size).toBe(0);
|
|
});
|
|
|
|
it("should capture both quoted and unquoted pnpm catalog entries", ({
|
|
expect,
|
|
}) => {
|
|
const changes = parseDiffForChanges([
|
|
// A literal dependency in a package.json (quoted, JSON key)
|
|
`- "workerd": "1.20260702.1",`,
|
|
`+ "workerd": "1.20260706.1",`,
|
|
// A quoted catalog entry in pnpm-workspace.yaml (caret preserved)
|
|
`- "@cloudflare/workers-types": "^4.20260702.1"`,
|
|
`+ "@cloudflare/workers-types": "^5.20260706.1"`,
|
|
// workerd also appears in pnpm-workspace.yaml, but as an *unquoted*
|
|
// YAML key. The catalog is the source of truth and always carries the
|
|
// same version as the package.json pin, so capturing it here merges
|
|
// into the same map entry rather than conflicting.
|
|
`- workerd: "1.20260702.1"`,
|
|
`+ workerd: "1.20260706.1"`,
|
|
]);
|
|
expect(changes).toEqual(
|
|
new Map([
|
|
[
|
|
"workerd",
|
|
{
|
|
from: "1.20260702.1",
|
|
to: "1.20260706.1",
|
|
},
|
|
],
|
|
[
|
|
"@cloudflare/workers-types",
|
|
{
|
|
from: "^4.20260702.1",
|
|
to: "^5.20260706.1",
|
|
},
|
|
],
|
|
])
|
|
);
|
|
});
|
|
|
|
it("should capture an unquoted catalog key even when no package.json bumps it", ({
|
|
expect,
|
|
}) => {
|
|
// When Dependabot bumps workerd only in the pnpm-workspace.yaml catalog
|
|
// (an unquoted YAML key), leaving the package.json pins untouched, workerd
|
|
// must still make it into the changeset.
|
|
const changes = parseDiffForChanges([
|
|
`- "@cloudflare/workers-types": "^5.20260708.1"`,
|
|
`+ "@cloudflare/workers-types": "^5.20260710.1"`,
|
|
`- workerd: "1.20260708.1"`,
|
|
`+ workerd: "1.20260710.1"`,
|
|
]);
|
|
expect(changes).toEqual(
|
|
new Map([
|
|
[
|
|
"@cloudflare/workers-types",
|
|
{
|
|
from: "^5.20260708.1",
|
|
to: "^5.20260710.1",
|
|
},
|
|
],
|
|
[
|
|
"workerd",
|
|
{
|
|
from: "1.20260708.1",
|
|
to: "1.20260710.1",
|
|
},
|
|
],
|
|
])
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("getDependencyChanges()", () => {
|
|
it("should diff every provided path and merge the changes", ({ expect }) => {
|
|
(spawnSync as Mock).mockClear();
|
|
(spawnSync as Mock).mockImplementation((_command, args) => {
|
|
const path: string = args[2];
|
|
if (path.endsWith("package.json")) {
|
|
return {
|
|
output: [
|
|
`- "workerd": "1.20260702.1",`,
|
|
`+ "workerd": "1.20260706.1",`,
|
|
],
|
|
};
|
|
}
|
|
if (path.endsWith("pnpm-workspace.yaml")) {
|
|
return {
|
|
output: [
|
|
`- "@cloudflare/workers-types": "^4.20260702.1"`,
|
|
`+ "@cloudflare/workers-types": "^5.20260706.1"`,
|
|
],
|
|
};
|
|
}
|
|
|
|
return {
|
|
output: [],
|
|
};
|
|
});
|
|
|
|
const changes = getDependencyChanges([
|
|
"packages/miniflare/package.json",
|
|
"pnpm-workspace.yaml",
|
|
]);
|
|
|
|
expect(spawnSync).toHaveBeenCalledTimes(2);
|
|
expect(changes).toEqual(
|
|
new Map([
|
|
[
|
|
"workerd",
|
|
{
|
|
from: "1.20260702.1",
|
|
to: "1.20260706.1",
|
|
},
|
|
],
|
|
[
|
|
"@cloudflare/workers-types",
|
|
{
|
|
from: "^4.20260702.1",
|
|
to: "^5.20260706.1",
|
|
},
|
|
],
|
|
])
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("generateChangesetHeader()", () => {
|
|
it("should return a header with a single package and 'patch' version bump", ({
|
|
expect,
|
|
}) => {
|
|
const header = generateChangesetHeader(["package-name"]);
|
|
expect(header).toMatchInlineSnapshot(`
|
|
"---
|
|
"package-name": patch
|
|
---"
|
|
`);
|
|
});
|
|
|
|
it("should return a header with multiple packages and 'patch' version bump", ({
|
|
expect,
|
|
}) => {
|
|
const header = generateChangesetHeader(["package-name", "another-package"]);
|
|
expect(header).toMatchInlineSnapshot(`
|
|
"---
|
|
"package-name": patch
|
|
"another-package": patch
|
|
---"
|
|
`);
|
|
});
|
|
});
|
|
|
|
describe("generateCommitMessage()", () => {
|
|
it("should return a commit message about a single changed package", ({
|
|
expect,
|
|
}) => {
|
|
const message = generateCommitMessage(["@namespace/package"], new Map());
|
|
expect(message).toMatchInlineSnapshot(`
|
|
"Update dependencies of "@namespace/package"
|
|
|
|
The following dependency versions have been updated:
|
|
|
|
| Dependency | From | To |
|
|
| ---------- | ---- | -- |"
|
|
`);
|
|
});
|
|
|
|
it("should return a commit message about multiple packages", ({ expect }) => {
|
|
const message = generateCommitMessage(
|
|
["@namespace/package", "another-package"],
|
|
new Map()
|
|
);
|
|
expect(message).toMatchInlineSnapshot(`
|
|
"Update dependencies of "@namespace/package", "another-package"
|
|
|
|
The following dependency versions have been updated:
|
|
|
|
| Dependency | From | To |
|
|
| ---------- | ---- | -- |"
|
|
`);
|
|
});
|
|
|
|
it("should return a commit message containing a row for each change", ({
|
|
expect,
|
|
}) => {
|
|
const message = generateCommitMessage(
|
|
["package-name"],
|
|
new Map([
|
|
["some-package", { from: "^0.0.1", to: "^0.0.2" }],
|
|
["@namespace/some-package", { from: "1.3.4", to: "1.4.5" }],
|
|
])
|
|
);
|
|
|
|
expect(message).toMatchInlineSnapshot(`
|
|
"Update dependencies of "package-name"
|
|
|
|
The following dependency versions have been updated:
|
|
|
|
| Dependency | From | To |
|
|
| ----------------------- | ------ | ------ |
|
|
| some-package | ^0.0.1 | ^0.0.2 |
|
|
| @namespace/some-package | 1.3.4 | 1.4.5 |"
|
|
`);
|
|
});
|
|
});
|
|
|
|
describe("writeChangeSet()", () => {
|
|
it("should call writeFileSync with appropriate changeset path and body", ({
|
|
expect,
|
|
}) => {
|
|
const header = dedent`
|
|
---
|
|
"package-name": patch
|
|
---`;
|
|
const body = dedent`
|
|
chore: update dependencies of "@namespace/package" package
|
|
|
|
The following dependency versions have been updated:
|
|
| Dependency | From | To |
|
|
| ----------------------- | ------ | ------ |
|
|
| some-package | ^0.0.1 | ^0.0.2 |
|
|
| @namespace/some-package | 1.3.4 | 1.4.5 |
|
|
`;
|
|
writeChangeSet("some-prefix", "1234", header, body);
|
|
expect(writeFileSync).toHaveBeenCalledOnce();
|
|
expect((writeFileSync as Mock).mock.lastCall?.[0]).toMatchInlineSnapshot(
|
|
`".changeset/some-prefix-1234.md"`
|
|
);
|
|
expect((writeFileSync as Mock).mock.lastCall?.[1]).toMatchInlineSnapshot(`
|
|
"---
|
|
"package-name": patch
|
|
---
|
|
|
|
chore: update dependencies of "@namespace/package" package
|
|
|
|
The following dependency versions have been updated:
|
|
| Dependency | From | To |
|
|
| ----------------------- | ------ | ------ |
|
|
| some-package | ^0.0.1 | ^0.0.2 |
|
|
| @namespace/some-package | 1.3.4 | 1.4.5 |
|
|
"
|
|
`);
|
|
});
|
|
});
|
|
|
|
describe("commitAndPush()", () => {
|
|
it("should call spawnSync with appropriate git commands", ({ expect }) => {
|
|
(spawnSync as Mock).mockClear();
|
|
(spawnSync as Mock).mockReturnValue({ output: [] });
|
|
const commitMessage = dedent`
|
|
chore: update dependencies of "@namespace/package" package
|
|
|
|
The following dependency versions have been updated:
|
|
| Dependency | From | To |
|
|
| ----------------------- | ------ | ------ |
|
|
| some-package | ^0.0.1 | ^0.0.2 |
|
|
| @namespace/some-package | 1.3.4 | 1.4.5 |`;
|
|
commitAndPush(commitMessage);
|
|
expect(spawnSync).toHaveBeenCalledTimes(3);
|
|
expect((spawnSync as Mock).mock.calls[0]).toMatchInlineSnapshot(`
|
|
[
|
|
"git",
|
|
[
|
|
"add",
|
|
".changeset",
|
|
],
|
|
{
|
|
"encoding": "utf-8",
|
|
},
|
|
]
|
|
`);
|
|
expect((spawnSync as Mock).mock.calls[1]).toMatchInlineSnapshot(`
|
|
[
|
|
"git",
|
|
[
|
|
"commit",
|
|
"-m",
|
|
"chore: update dependencies of "@namespace/package" package
|
|
|
|
The following dependency versions have been updated:
|
|
| Dependency | From | To |
|
|
| ----------------------- | ------ | ------ |
|
|
| some-package | ^0.0.1 | ^0.0.2 |
|
|
| @namespace/some-package | 1.3.4 | 1.4.5 |",
|
|
],
|
|
{
|
|
"encoding": "utf-8",
|
|
},
|
|
]
|
|
`);
|
|
expect((spawnSync as Mock).mock.calls[2]).toMatchInlineSnapshot(`
|
|
[
|
|
"git",
|
|
[
|
|
"push",
|
|
],
|
|
{
|
|
"encoding": "utf-8",
|
|
},
|
|
]
|
|
`);
|
|
});
|
|
|
|
it("should call error if any git command fails", ({ expect }) => {
|
|
(spawnSync as Mock).mockImplementation((git, args) => {
|
|
const error =
|
|
args[0] === "push" ? new Error("Failed to push") : undefined;
|
|
return { output: [], error };
|
|
});
|
|
expect(() =>
|
|
commitAndPush("commit message")
|
|
).toThrowErrorMatchingInlineSnapshot(`[Error: Failed to push]`);
|
|
});
|
|
});
|