Skip to content

Commit 7ba8473

Browse files
authored
Split fs / node-specific functionality into modules, and add more tests for different file sizes. (#7)
* Split core and fs functionality into separate files * Add some tests for various file sizes * Avoid relying on `actions` type in built type definitions * switch package to use modules * Always delete test directory after finishing
1 parent b6e43ae commit 7ba8473

File tree

13 files changed

+337
-181
lines changed

13 files changed

+337
-181
lines changed

jest.integration.config.cjs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
const { randomBytes } = require("crypto");
2+
const path = require("path");
3+
const os = require("os");
4+
5+
const ROOT_TEST_BRANCH_PREFIX = `test-${randomBytes(4).toString("hex")}`;
6+
const ROOT_TEMP_DIRECTORY = path.join(os.tmpdir(), ROOT_TEST_BRANCH_PREFIX);
7+
18
module.exports = {
29
preset: "ts-jest",
310
testEnvironment: "node",
@@ -17,4 +24,9 @@ module.exports = {
1724
"^(.+).js$": "$1",
1825
},
1926
testMatch: ["<rootDir>/src/test/integration/**/*.test.ts"],
27+
globals: {
28+
ROOT_TEST_BRANCH_PREFIX,
29+
ROOT_TEMP_DIRECTORY,
30+
},
31+
globalTeardown: "<rootDir>/src/test/integration/jest.globalTeardown.ts",
2032
};

package.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,11 @@
2626
}
2727
},
2828
"scripts": {
29-
"build": "pnpm codegen:github && tsc --noEmit && tsup",
29+
"build": "rm -rf dist && pnpm codegen:github && tsc --noEmit && tsup",
3030
"codegen:github": "graphql-codegen --config src/github/codegen.ts",
3131
"format:check": "prettier --check \"**/*.{ts,tsx,md}\"",
3232
"format:fix": "prettier --write \"**/*.{ts,tsx,md}\"",
3333
"lint": "eslint . --max-warnings 0",
34-
"test": "jest",
35-
"test:watch": "jest --watch",
3634
"test:integration": "jest --config jest.integration.config.cjs"
3735
},
3836
"devDependencies": {

src/core.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import type {
2+
CommitMessage,
3+
FileChanges,
4+
} from "./github/graphql/generated/types.js";
5+
import {
6+
createCommitOnBranchQuery,
7+
createRefMutation,
8+
getRepositoryMetadata,
9+
GitHubClient,
10+
} from "./github/graphql/queries.js";
11+
import type { CreateCommitOnBranchMutationVariables } from "./github/graphql/generated/operations.js";
12+
import type { Logger } from "./logging.js";
13+
14+
export type CommitFilesResult = {
15+
refId: string | null;
16+
};
17+
18+
export type CommitFilesFromBase64Args = {
19+
octokit: GitHubClient;
20+
owner: string;
21+
repository: string;
22+
branch: string;
23+
/**
24+
* The current commit that the target branch is at
25+
*/
26+
baseBranch: string;
27+
/**
28+
* The commit message
29+
*/
30+
message: CommitMessage;
31+
fileChanges: FileChanges;
32+
log?: Logger;
33+
};
34+
35+
export const commitFilesFromBase64 = async ({
36+
octokit,
37+
owner,
38+
repository,
39+
branch,
40+
baseBranch,
41+
message,
42+
fileChanges,
43+
log,
44+
}: CommitFilesFromBase64Args): Promise<CommitFilesResult> => {
45+
const repositoryNameWithOwner = `${owner}/${repository}`;
46+
const baseRef = `refs/heads/${baseBranch}`;
47+
48+
log?.debug(`Getting repo info ${repositoryNameWithOwner}`);
49+
const info = await getRepositoryMetadata(octokit, {
50+
owner,
51+
name: repository,
52+
ref: baseRef,
53+
});
54+
log?.debug(`Repo info: ${JSON.stringify(info, null, 2)}`);
55+
56+
if (!info) {
57+
throw new Error(`Repository ${repositoryNameWithOwner} not found`);
58+
}
59+
60+
const oid = info.ref?.target?.oid;
61+
62+
if (!info) {
63+
throw new Error(`Ref ${baseRef} not found`);
64+
}
65+
66+
log?.debug(`Creating branch ${branch} from commit ${oid}}`);
67+
const refId = await createRefMutation(octokit, {
68+
input: {
69+
repositoryId: info.id,
70+
name: `refs/heads/${branch}`,
71+
oid,
72+
},
73+
});
74+
75+
log?.debug(`Created branch with refId ${JSON.stringify(refId, null, 2)}`);
76+
77+
const refIdStr = refId.createRef?.ref?.id;
78+
79+
if (!refIdStr) {
80+
throw new Error(`Failed to create branch ${branch}`);
81+
}
82+
83+
await log?.debug(`Creating commit on branch ${branch}`);
84+
const createCommitMutation: CreateCommitOnBranchMutationVariables = {
85+
input: {
86+
branch: {
87+
id: refIdStr,
88+
},
89+
expectedHeadOid: oid,
90+
message,
91+
fileChanges,
92+
},
93+
};
94+
log?.debug(JSON.stringify(createCommitMutation, null, 2));
95+
96+
const result = await createCommitOnBranchQuery(octokit, createCommitMutation);
97+
return {
98+
refId: result.createCommitOnBranch?.ref?.id ?? null,
99+
};
100+
};

src/fs.ts

Lines changed: 22 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,126 +1,43 @@
11
import { promises as fs } from "fs";
22
import * as path from "path";
3-
import type {
4-
CommitMessage,
5-
FileAddition,
6-
FileDeletion,
7-
} from "./github/graphql/generated/types";
8-
import {
9-
createCommitOnBranchQuery,
10-
createRefMutation,
11-
getRepositoryMetadata,
12-
GitHubClient,
13-
} from "./github/graphql/queries";
14-
import type { CreateCommitOnBranchMutationVariables } from "./github/graphql/generated/operations";
15-
import type { Logger } from "./logging";
16-
17-
export const commitFilesFromDirectory = async (args: {
18-
octokit: GitHubClient;
3+
import type { FileAddition } from "./github/graphql/generated/types.js";
4+
import { CommitFilesFromBase64Args, CommitFilesResult } from "./core.js";
5+
import { commitFilesFromBuffers } from "./node.js";
6+
7+
export type CommitFilesFromDirectoryArgs = Omit<
8+
CommitFilesFromBase64Args,
9+
"fileChanges"
10+
> & {
1911
/**
20-
* The root of the github repository.
12+
* The directory to consider the root of the repository when calculating
13+
* file paths
2114
*/
2215
workingDirectory?: string;
23-
owner: string;
24-
repository: string;
25-
branch: string;
26-
/**
27-
* The current commit that the target branch is at
28-
*/
29-
baseBranch: string;
30-
/**
31-
* The commit message
32-
*/
33-
message: CommitMessage;
3416
fileChanges: {
35-
/**
36-
* File paths (relative to the repository root)
37-
*/
3817
additions?: string[];
3918
deletions?: string[];
4019
};
41-
log?: Logger;
42-
}) => {
43-
const {
44-
octokit,
45-
workingDirectory = process.cwd(),
46-
owner,
47-
repository,
48-
branch,
49-
baseBranch,
50-
message,
51-
fileChanges,
52-
log,
53-
} = args;
54-
const repositoryNameWithOwner = `${owner}/${repository}`;
55-
const baseRef = `refs/heads/${baseBranch}`;
20+
};
5621

22+
export const commitFilesFromDirectory = async ({
23+
workingDirectory = process.cwd(),
24+
fileChanges,
25+
...otherArgs
26+
}: CommitFilesFromDirectoryArgs): Promise<CommitFilesResult> => {
5727
const additions: FileAddition[] = await Promise.all(
5828
(fileChanges.additions || []).map(async (p) => {
59-
const fileContents = await fs.readFile(path.join(workingDirectory, p));
60-
const base64Contents = Buffer.from(fileContents).toString("base64");
6129
return {
6230
path: p,
63-
contents: base64Contents,
31+
contents: await fs.readFile(path.join(workingDirectory, p)),
6432
};
6533
}),
6634
);
6735

68-
const deletions: FileDeletion[] =
69-
fileChanges.deletions?.map((p) => ({
70-
path: p,
71-
})) ?? [];
72-
73-
log?.debug(`Getting repo info ${repositoryNameWithOwner}`);
74-
const info = await getRepositoryMetadata(octokit, {
75-
owner: args.owner,
76-
name: args.repository,
77-
ref: baseRef,
78-
});
79-
log?.debug(`Repo info: ${JSON.stringify(info, null, 2)}`);
80-
81-
if (!info) {
82-
throw new Error(`Repository ${repositoryNameWithOwner} not found`);
83-
}
84-
85-
const oid = info.ref?.target?.oid;
86-
87-
if (!info) {
88-
throw new Error(`Ref ${baseRef} not found`);
89-
}
90-
91-
log?.debug(`Creating branch ${branch} from commit ${oid}}`);
92-
const refId = await createRefMutation(octokit, {
93-
input: {
94-
repositoryId: info.id,
95-
name: `refs/heads/${branch}`,
96-
oid,
36+
return commitFilesFromBuffers({
37+
...otherArgs,
38+
fileChanges: {
39+
additions,
40+
deletions: fileChanges.deletions,
9741
},
9842
});
99-
100-
log?.debug(`Created branch with refId ${JSON.stringify(refId, null, 2)}`);
101-
102-
const refIdStr = refId.createRef?.ref?.id;
103-
104-
if (!refIdStr) {
105-
throw new Error(`Failed to create branch ${branch}`);
106-
}
107-
108-
await log?.debug(`Creating commit on branch ${args.branch}`);
109-
const createCommitMutation: CreateCommitOnBranchMutationVariables = {
110-
input: {
111-
branch: {
112-
id: refIdStr,
113-
},
114-
expectedHeadOid: oid,
115-
message,
116-
fileChanges: {
117-
additions,
118-
deletions,
119-
},
120-
},
121-
};
122-
log?.debug(JSON.stringify(createCommitMutation, null, 2));
123-
124-
const result = await createCommitOnBranchQuery(octokit, createCommitMutation);
125-
return result.createCommitOnBranch?.ref?.id ?? null;
12643
};

src/github/graphql/queries.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import type { GitHub } from "@actions/github/lib/utils";
2-
3-
export type GitHubClient = InstanceType<typeof GitHub>;
1+
export type GitHubClient = {
2+
graphql: <T>(query: string, variables: any) => Promise<T>;
3+
};
44

55
import type {
66
CreateCommitOnBranchMutation,
@@ -11,7 +11,7 @@ import type {
1111
DeleteRefMutationVariables,
1212
GetRepositoryMetadataQuery,
1313
GetRepositoryMetadataQueryVariables,
14-
} from "./generated/operations";
14+
} from "./generated/operations.js";
1515

1616
const GET_REPOSITORY_METADATA = /* GraphQL */ `
1717
query getRepositoryMetadata($owner: String!, $name: String!, $ref: String!) {

src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
export * as queries from "./github/graphql/queries";
2-
export { commitFilesFromDirectory } from "./fs";
1+
export * as queries from "./github/graphql/queries.js";
2+
export { commitFilesFromDirectory } from "./fs.js";

src/node.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import {
2+
commitFilesFromBase64,
3+
CommitFilesFromBase64Args,
4+
CommitFilesResult,
5+
} from "./core.js";
6+
7+
export type CommitFilesFromBuffersArgs = Omit<
8+
CommitFilesFromBase64Args,
9+
"fileChanges"
10+
> & {
11+
fileChanges: {
12+
additions?: Array<{
13+
path: string;
14+
contents: Buffer;
15+
}>;
16+
deletions?: string[];
17+
};
18+
};
19+
20+
export const commitFilesFromBuffers = async ({
21+
fileChanges,
22+
...otherArgs
23+
}: CommitFilesFromBuffersArgs): Promise<CommitFilesResult> => {
24+
return commitFilesFromBase64({
25+
...otherArgs,
26+
fileChanges: {
27+
additions: fileChanges.additions?.map(({ path, contents }) => ({
28+
path,
29+
contents: contents.toString("base64"),
30+
})),
31+
deletions: fileChanges.deletions?.map((path) => ({ path })),
32+
},
33+
});
34+
};

src/test/integration/env.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { pino } from "pino";
2+
import { configDotenv } from "dotenv";
3+
4+
declare namespace global {
5+
const ROOT_TEST_BRANCH_PREFIX: string;
6+
const ROOT_TEMP_DIRECTORY: string;
7+
}
8+
9+
export const ROOT_TEST_BRANCH_PREFIX = global.ROOT_TEST_BRANCH_PREFIX;
10+
export const ROOT_TEMP_DIRECTORY = global.ROOT_TEMP_DIRECTORY;
11+
12+
configDotenv();
13+
14+
const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
15+
if (!GITHUB_TOKEN) {
16+
throw new Error("GITHUB_TOKEN must be set");
17+
}
18+
19+
const GITHUB_REPOSITORY = process.env.GITHUB_REPOSITORY;
20+
21+
const [owner, repository] = GITHUB_REPOSITORY?.split("/") || [];
22+
if (!owner || !repository) {
23+
throw new Error("GITHUB_REPOSITORY must be set");
24+
}
25+
26+
export const ENV = {
27+
GITHUB_TOKEN,
28+
};
29+
30+
export const REPO = { owner, repository };
31+
32+
export const log = pino({
33+
level: process.env.RUNNER_DEBUG === "1" ? "debug" : "info",
34+
transport: {
35+
target: "pino-pretty",
36+
},
37+
});

0 commit comments

Comments
 (0)