Skip to content

Commit 4257756

Browse files
authored
chore: github release (#4)
1 parent cc7656f commit 4257756

22 files changed

Lines changed: 1257 additions & 66 deletions
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { describe, it, expect, beforeEach, vi } from "vitest";
2+
import "@webiny/di";
3+
import { Container } from "@webiny/di";
4+
import { ProjectConfig } from "../../../scripts/features/PublishPackages/abstractions/ProjectConfig.ts";
5+
import { GitRepository } from "../../../scripts/features/PublishPackages/abstractions/GitRepository.ts";
6+
import { GithubToken } from "../../../scripts/features/PublishPackages/abstractions/GithubToken.ts";
7+
import { GithubRelease } from "../../../scripts/features/PublishPackages/abstractions/GithubRelease.ts";
8+
import { GithubRelease as GithubReleaseImpl } from "../../../scripts/features/PublishPackages/GithubRelease.ts";
9+
10+
const { mockCreateRelease } = vi.hoisted(() => ({
11+
mockCreateRelease: vi.fn().mockResolvedValue({})
12+
}));
13+
14+
vi.mock("@octokit/rest", () => {
15+
class MockOctokit {
16+
public readonly rest = { repos: { createRelease: mockCreateRelease } };
17+
}
18+
return { Octokit: MockOctokit };
19+
});
20+
21+
function makeContainer(opts: {
22+
dryRun?: boolean;
23+
remoteUrl?: string;
24+
token?: string;
25+
}): GithubRelease.Interface {
26+
const container = new Container();
27+
container.registerInstance(ProjectConfig, {
28+
rootDir: "/tmp",
29+
packageName: "@test/pkg",
30+
dryRun: opts.dryRun ?? false
31+
});
32+
container.registerInstance(GitRepository, {
33+
tagExists: vi.fn(),
34+
commitsSince: vi.fn(),
35+
createTag: vi.fn(),
36+
getRemoteUrl: vi
37+
.fn()
38+
.mockReturnValue(opts.remoteUrl ?? "https://github.com/acme/my-repo.git")
39+
});
40+
container.registerInstance(GithubToken, {
41+
getToken: () => opts.token ?? "test-token"
42+
});
43+
container.register(GithubReleaseImpl).inSingletonScope();
44+
return container.resolve(GithubRelease);
45+
}
46+
47+
describe("GithubRelease", () => {
48+
beforeEach(() => {
49+
mockCreateRelease.mockClear();
50+
});
51+
52+
describe("constructor", () => {
53+
it("parses HTTPS remote URL without throwing", () => {
54+
expect(() =>
55+
makeContainer({ remoteUrl: "https://github.com/acme/my-repo.git" })
56+
).not.toThrow();
57+
});
58+
59+
it("parses SSH remote URL without throwing", () => {
60+
expect(() =>
61+
makeContainer({ remoteUrl: "git@github.com:acme/my-repo.git" })
62+
).not.toThrow();
63+
});
64+
65+
it("throws for unrecognised remote URL", () => {
66+
expect(() => makeContainer({ remoteUrl: "https://gitlab.com/acme/repo.git" })).toThrow(
67+
"Cannot parse GitHub owner/repo"
68+
);
69+
});
70+
});
71+
72+
describe("createRelease", () => {
73+
it("dry-run: logs and skips API call", async () => {
74+
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
75+
try {
76+
const release = makeContainer({ dryRun: true });
77+
await release.createRelease("v1.2.3", "v1.2.3", "body");
78+
79+
expect(mockCreateRelease).not.toHaveBeenCalled();
80+
const calls = logSpy.mock.calls.map(args => args.join(" "));
81+
expect(
82+
calls.some(
83+
msg => msg.includes("would create GitHub release") && msg.includes("v1.2.3")
84+
)
85+
).toBe(true);
86+
} finally {
87+
logSpy.mockRestore();
88+
}
89+
});
90+
91+
it("real publish: calls Octokit with correct params", async () => {
92+
const release = makeContainer({ dryRun: false });
93+
await release.createRelease("v1.2.3", "v1.2.3", "body text");
94+
95+
expect(mockCreateRelease).toHaveBeenCalledOnce();
96+
expect(mockCreateRelease).toHaveBeenCalledWith({
97+
owner: "acme",
98+
repo: "my-repo",
99+
tag_name: "v1.2.3",
100+
name: "v1.2.3",
101+
body: "body text"
102+
});
103+
});
104+
});
105+
});
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
2+
import "@webiny/di";
3+
import { Container } from "@webiny/di";
4+
import { GithubToken } from "../../../scripts/features/PublishPackages/abstractions/GithubToken.ts";
5+
import { GithubToken as GithubTokenImpl } from "../../../scripts/features/PublishPackages/GithubToken.ts";
6+
7+
function makeToken(): GithubToken.Interface {
8+
const container = new Container();
9+
container.register(GithubTokenImpl).inSingletonScope();
10+
return container.resolve(GithubToken);
11+
}
12+
13+
describe("GithubToken", () => {
14+
let savedToken: string | undefined;
15+
16+
beforeEach(() => {
17+
savedToken = process.env["GITHUB_TOKEN"];
18+
});
19+
20+
afterEach(() => {
21+
if (savedToken === undefined) {
22+
delete process.env["GITHUB_TOKEN"];
23+
} else {
24+
process.env["GITHUB_TOKEN"] = savedToken;
25+
}
26+
});
27+
28+
it("returns the token from GITHUB_TOKEN env var", () => {
29+
process.env["GITHUB_TOKEN"] = "ghp_test123";
30+
const token = makeToken();
31+
expect(token.getToken()).toBe("ghp_test123");
32+
});
33+
34+
it("throws when GITHUB_TOKEN is not set", () => {
35+
delete process.env["GITHUB_TOKEN"];
36+
const token = makeToken();
37+
expect(() => token.getToken()).toThrow("GITHUB_TOKEN env var is required");
38+
});
39+
});

0 commit comments

Comments
 (0)