From b127b6ed40ef8b0e5d759dea15a91599870046a4 Mon Sep 17 00:00:00 2001 From: Sam Lanning Date: Fri, 23 Aug 2024 23:05:51 +0100 Subject: [PATCH] Prepare docs and release v1.0.0 (#12) * Add individual modules to package exports * Import getOctokit from root of package * Clean up interfaces & arguments * Add initial documentation * Add changeset * Bump version * Additional docs improvements --- CHANGELOG.md | 6 + README.md | 256 ++++++++++++++++++++++++++++++ docs/verified.png | Bin 0 -> 28680 bytes package.json | 22 ++- src/core.ts | 48 +----- src/fs.ts | 20 +-- src/git.ts | 22 +-- src/interface.ts | 93 +++++++++++ src/node.ts | 20 +-- src/test/integration/fs.test.ts | 2 +- src/test/integration/git.test.ts | 2 +- src/test/integration/node.test.ts | 2 +- tsup.config.ts | 1 + 13 files changed, 398 insertions(+), 96 deletions(-) create mode 100644 README.md create mode 100644 docs/verified.png create mode 100644 src/interface.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f3813a..e2b1471 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # @s0/ghcommit +## 1.0.0 + +### Major Changes + +- be55175: First major release + ## 0.1.0 ### Minor Changes diff --git a/README.md b/README.md new file mode 100644 index 0000000..8a6094a --- /dev/null +++ b/README.md @@ -0,0 +1,256 @@ +# `@s0/ghcommit` + +[![View on NPM](https://badgen.net/npm/v/@s0/ghcommit)](https://www.npmjs.com/package/@s0/ghcommit) + +NPM / TypeScript package to commit changes GitHub repositories using the GraphQL API. + +## Why? + +If you or your organisation has strict requirements +around requiring signed commits (i.e. via Branch Protection or Repo Rulesets), then this can make integrating CI workflows or applications that are designed to make changes to your repos quite difficult. This is because you will need to manage your own GPG keys, assign them to machine accounts (which also means it doesn't work with GitHub Apps), and securely manage and rotate them. + +Instead of doing this, if you use the GitHub API to make changes to files (such as what happens when making changes to files directly in the web UI), then GitHub's internal GPG key is used, and commits are all signed and associated with the user of the access token that was used. + +(And this also works with GitHub Apps too, including the GitHub Actions app). + +![](docs/verified.png) + +This library has primarily been designed for use in custom Node GitHub Actions, but can be used in any Node.js or JavaScript project that needs to directly modify files in GitHub repositories. + +## Usage + +### Installation + +Install using your favourite package manager: + +``` +pnpm install @s0/ghcommit +``` + +### Usage in github actions + +All functions in this library that interact with the GitHub API require an octokit client that can execute GraphQL. If you are writing code that is designed to be run from within a GitHub Action, this can be done using the `@actions.github` library: + +```ts +import { getOctokit } from "@actions/github"; + +const octokit = getOctokit(process.env.GITHUB_TOKEN); +``` + +### Importing specific modules + +To allow for you to produce smaller bundle sizes, the functionality exposed in this package is grouped into specific modules that only import the packages required for their use. We recommend that you import from the specific modules rather than the root of the package. + +## API + +All the functions below accept a single object as its argument, and share the following base arguments: + + + +```ts +{ + octokit: GitHubClient; + owner: string; + repository: string; + branch: string; + /** + * Push the commit even if the branch exists and does not match what was + * specified as the base. + */ + force?: boolean; + /** + * The commit message + */ + message: CommitMessage; + log?: Logger; +} +``` + +### `commitChangesFromRepo` + +This function will take an existing repository on your filesystem (defaulting to the current working directory). This function is good to use if you're usually working within the context of a git repository, such as after running `@actions/checkout` in github actions. + +In addition to `CommitFilesBasedArgs`, this function has the following arguments: + +```ts +{ + /** + * The root of the repository. + * + * @default process.cwd() + */ + repoDirectory?: string; +} +``` + +Example: + +```ts +import { getOctokit } from "@actions/github"; +import { commitChangesFromRepo } from "@s0/ghcommit/git"; + +const octokit = getOctokit(process.env.GITHUB_TOKEN); + +// Commit & push the files from the current directory +// e.g. if you're just using @ations/checkout +await commitChangesFromRepo({ + octokit, + owner: "my-org", + repository: "my-repo", + branch: "new-branch-to-create", + message: { + headline: "[chore] do something", + }, +}); + +// Commit & push the files from ta specific directory +// where we've cloned a repo, and made changes to files +await commitChangesFromRepo({ + octokit, + owner: "my-org", + repository: "my-repo", + branch: "another-new-branch-to-create", + message: { + headline: "[chore] do something else", + }, + repoDirectory: "/tmp/some-repo", +}); +``` + +### `commitFilesFromDirectory` + +This function will add or delete specific files from a repository's branch based on files found on the local filesystem. This is good to use when there are specific files that need to be updated on a branch, or if many changes may have been made locally, but only some files need to be pushed. + +In addition to `CommitFilesBasedArgs`, this function has the following arguments: + +```ts +{ + /** + * The current branch, tag or commit that the new branch should be based on. + */ + base: GitBase; + /** + * The directory to consider the root of the repository when calculating + * file paths + */ + workingDirectory?: string; + /** + * The file paths, relative to {@link workingDirectory}, + * to add or delete from the branch on GitHub. + */ + fileChanges: { + /** File paths, relative to {@link workingDirectory}, to remove from the repo. */ + additions?: string[]; + /** File paths, relative to the repository root, to remove from the repo. */ + deletions?: string[]; + }; +} +``` + +Example: + +```ts +import { getOctokit } from "@actions/github"; +import { commitFilesFromDirectory } from "@s0/ghcommit/fs"; + +const octokit = getOctokit(process.env.GITHUB_TOKEN); + +// Commit the changes to package.json and package-lock.json +// based on the main branch +await commitFilesFromDirectory({ + octokit, + owner: "my-org", + repository: "my-repo", + branch: "new-branch-to-create", + message: { + headline: "[chore] do something", + }, + base: { + branch: "main", + }, + workingDirectory: "foo/bar", + fileChanges: { + additions: ["package-lock.json", "package.json"], + }, +}); + +// Push just the index.html file to a new branch called docs, based off the tag v1.0.0 +await commitFilesFromDirectory({ + octokit, + owner: "my-org", + repository: "my-repo", + branch: "docs", + message: { + headline: "[chore] do something", + }, + force: true, // Overwrite any existing branch + base: { + tag: "v1.0.0", + }, + workingDirectory: "some-dir", + fileChanges: { + additions: ["index.html"], + }, +}); +``` + +### `commitFilesFromBuffers` + +This function will add or delete specific files from a repository's branch based on Node.js `Buffers` that can be any binary data in memory. This is useful for when you want to make changes to a repository / branch without cloning a repo or interacting with a filesystem. + +In addition to `CommitFilesBasedArgs`, this function has the following arguments: + +```ts +{ + /** + * The current branch, tag or commit that the new branch should be based on. + */ + base: GitBase; + /** + * The file changes, relative to the repository root, to make to the specified branch. + */ + fileChanges: { + additions?: Array<{ + path: string; + contents: Buffer; + }>; + deletions?: string[]; + }; +} +``` + +Example: + +```ts +import { getOctokit } from "@actions/github"; +import { commitFilesFromBuffers } from "@s0/ghcommit/node"; + +const octokit = getOctokit(process.env.GITHUB_TOKEN); + +// Add a file called hello-world +await commitFilesFromBuffers({ + octokit, + owner: "my-org", + repository: "my-repo", + branch: "new-branch-to-create", + message: { + headline: "[chore] do something", + }, + base: { + branch: "main", + }, + fileChanges: { + additions: [ + { + path: "hello/world.txt", + contents: Buffer.alloc(1024, "Hello, world!"), + }, + ], + }, +}); +``` + +## Other Tools / Alternatives + +- [planetscale/ghcommit](https://github.com/planetscale/ghcommit) - Go library for committing to GitHub using graphql +- [planetscale/ghcommit-action](https://github.com/planetscale/ghcommit-action) - GitHub Action to detect file changes and commit using the above library diff --git a/docs/verified.png b/docs/verified.png new file mode 100644 index 0000000000000000000000000000000000000000..0004353457c4c475db9d3d58d7c9d1fed4eaca20 GIT binary patch literal 28680 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

9vV(=v~n%s&-OTQ#o$F;9LJnLT4OC+>y0WrhU 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 W#lp1w?>n&m6^NRyYPGUm*na`}Gd1-9 literal 0 HcmV?d00001 diff --git a/package.json b/package.json index 551fc7f..13c5e18 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@s0/ghcommit", - "version": "0.1.0", + "version": "1.0.0", "private": false, "description": "Directly change files on github using the github API, to support GPG signing", "keywords": [ @@ -23,6 +23,26 @@ "import": "./dist/index.mjs", "require": "./dist/index.js", "types": "./dist/index.d.ts" + }, + "./core": { + "import": "./dist/core.mjs", + "require": "./dist/core.js", + "types": "./dist/core.d.ts" + }, + "./fs": { + "import": "./dist/fs.mjs", + "require": "./dist/fs.js", + "types": "./dist/fs.d.ts" + }, + "./git": { + "import": "./dist/git.mjs", + "require": "./dist/git.js", + "types": "./dist/git.d.ts" + }, + "./node": { + "import": "./dist/node.mjs", + "require": "./dist/node.js", + "types": "./dist/node.d.ts" } }, "scripts": { diff --git a/src/core.ts b/src/core.ts index 6c1f642..b44bf3c 100644 --- a/src/core.ts +++ b/src/core.ts @@ -1,56 +1,18 @@ -import type { - CommitMessage, - FileChanges, -} from "./github/graphql/generated/types.js"; import { createCommitOnBranchQuery, createRefMutation, getRepositoryMetadata, - GitHubClient, updateRefMutation, } from "./github/graphql/queries.js"; import type { CreateCommitOnBranchMutationVariables, GetRepositoryMetadataQuery, } from "./github/graphql/generated/operations.js"; -import type { Logger } from "./logging.js"; - -export type CommitFilesResult = { - refId: string | null; -}; - -export type GitBase = - | { - branch: string; - } - | { - tag: string; - } - | { - commit: string; - }; - -export type CommitFilesFromBase64Args = { - octokit: GitHubClient; - owner: string; - repository: string; - branch: string; - /** - * The current branch, tag or commit that the new branch should be based on. - */ - base: GitBase; - /** - * Push the commit even if the branch exists and does not match what was - * specified as the base. - */ - force?: boolean; - /** - * The commit message - */ - message: CommitMessage; - fileChanges: FileChanges; - log?: Logger; -}; +import { + CommitFilesFromBase64Args, + CommitFilesResult, + GitBase, +} from "./interface.js"; const getBaseRef = (base: GitBase): string => { if ("branch" in base) { diff --git a/src/fs.ts b/src/fs.ts index 1aa136b..a622a35 100644 --- a/src/fs.ts +++ b/src/fs.ts @@ -1,23 +1,11 @@ import { promises as fs } from "fs"; import * as path from "path"; import type { FileAddition } from "./github/graphql/generated/types.js"; -import { CommitFilesFromBase64Args, CommitFilesResult } from "./core.js"; import { commitFilesFromBuffers } from "./node.js"; - -export type CommitFilesFromDirectoryArgs = Omit< - CommitFilesFromBase64Args, - "fileChanges" -> & { - /** - * The directory to consider the root of the repository when calculating - * file paths - */ - workingDirectory?: string; - fileChanges: { - additions?: string[]; - deletions?: string[]; - }; -}; +import { + CommitFilesFromDirectoryArgs, + CommitFilesResult, +} from "./interface.js"; export const commitFilesFromDirectory = async ({ workingDirectory = process.cwd(), diff --git a/src/git.ts b/src/git.ts index 9d7feed..9014c1b 100644 --- a/src/git.ts +++ b/src/git.ts @@ -1,25 +1,17 @@ import { promises as fs } from "fs"; import git from "isomorphic-git"; -import { CommitFilesFromBase64Args } from "./core"; -import { commitFilesFromBuffers, CommitFilesFromBuffersArgs } from "./node"; - -export type CommitChangesFromRepoArgs = Omit< - CommitFilesFromBase64Args, - "fileChanges" | "base" -> & { - /** - * The root of the repository. - * - * @default process.cwd() - */ - repoDirectory?: string; -}; +import { commitFilesFromBuffers } from "./node"; +import { + CommitChangesFromRepoArgs, + CommitFilesFromBuffersArgs, + CommitFilesResult, +} from "./interface"; export const commitChangesFromRepo = async ({ repoDirectory = process.cwd(), log, ...otherArgs -}: CommitChangesFromRepoArgs) => { +}: CommitChangesFromRepoArgs): Promise => { const gitLog = await git.log({ fs, dir: repoDirectory, diff --git a/src/interface.ts b/src/interface.ts new file mode 100644 index 0000000..91703f4 --- /dev/null +++ b/src/interface.ts @@ -0,0 +1,93 @@ +import type { + CommitMessage, + FileChanges, +} from "./github/graphql/generated/types.js"; +import type { GitHubClient } from "./github/graphql/queries.js"; + +import type { Logger } from "./logging.js"; + +export type CommitFilesResult = { + refId: string | null; +}; + +export type GitBase = + | { + branch: string; + } + | { + tag: string; + } + | { + commit: string; + }; + +export interface CommitFilesBasedArgs { + octokit: GitHubClient; + owner: string; + repository: string; + branch: string; + /** + * Push the commit even if the branch exists and does not match what was + * specified as the base. + */ + force?: boolean; + /** + * The commit message + */ + message: CommitMessage; + log?: Logger; +} + +export interface CommitFilesSharedArgsWithBase extends CommitFilesBasedArgs { + /** + * The current branch, tag or commit that the new branch should be based on. + */ + base: GitBase; +} + +export interface CommitFilesFromBase64Args + extends CommitFilesSharedArgsWithBase { + fileChanges: FileChanges; +} + +export interface CommitFilesFromBuffersArgs + extends CommitFilesSharedArgsWithBase { + /** + * The file changes, relative to the repository root, to make to the specified branch. + */ + fileChanges: { + additions?: Array<{ + path: string; + contents: Buffer; + }>; + deletions?: string[]; + }; +} + +export interface CommitFilesFromDirectoryArgs + extends CommitFilesSharedArgsWithBase { + /** + * The directory to consider the root of the repository when calculating + * file paths + */ + workingDirectory?: string; + /** + * The file paths, relative to {@link workingDirectory}, + * to add or delete from the branch on GitHub. + */ + fileChanges: { + /** File paths, relative to {@link workingDirectory}, to remove from the repo. */ + additions?: string[]; + /** File paths, relative to the repository root, to remove from the repo. */ + deletions?: string[]; + }; +} + +export interface CommitChangesFromRepoArgs extends CommitFilesBasedArgs { + /** + * The root of the repository. + * + * @default process.cwd() + */ + repoDirectory?: string; +} diff --git a/src/node.ts b/src/node.ts index fb07095..565d0ea 100644 --- a/src/node.ts +++ b/src/node.ts @@ -1,21 +1,5 @@ -import { - commitFilesFromBase64, - CommitFilesFromBase64Args, - CommitFilesResult, -} from "./core.js"; - -export type CommitFilesFromBuffersArgs = Omit< - CommitFilesFromBase64Args, - "fileChanges" -> & { - fileChanges: { - additions?: Array<{ - path: string; - contents: Buffer; - }>; - deletions?: string[]; - }; -}; +import { commitFilesFromBase64 } from "./core.js"; +import { CommitFilesFromBuffersArgs, CommitFilesResult } from "./interface.js"; export const commitFilesFromBuffers = async ({ fileChanges, diff --git a/src/test/integration/fs.test.ts b/src/test/integration/fs.test.ts index 34eaef6..a006287 100644 --- a/src/test/integration/fs.test.ts +++ b/src/test/integration/fs.test.ts @@ -1,6 +1,6 @@ import * as path from "path"; import { promises as fs } from "fs"; -import { getOctokit } from "@actions/github/lib/github.js"; +import { getOctokit } from "@actions/github"; import { commitFilesFromDirectory } from "../../fs.js"; import { diff --git a/src/test/integration/git.test.ts b/src/test/integration/git.test.ts index 8bbc160..627706d 100644 --- a/src/test/integration/git.test.ts +++ b/src/test/integration/git.test.ts @@ -8,7 +8,7 @@ import { log, } from "./env"; import { exec } from "child_process"; -import { getOctokit } from "@actions/github/lib/github.js"; +import { getOctokit } from "@actions/github"; import { commitChangesFromRepo } from "../../git"; import { getRefTreeQuery } from "../../github/graphql/queries"; import { deleteBranches } from "./util"; diff --git a/src/test/integration/node.test.ts b/src/test/integration/node.test.ts index e8433be..838d291 100644 --- a/src/test/integration/node.test.ts +++ b/src/test/integration/node.test.ts @@ -1,4 +1,4 @@ -import { getOctokit } from "@actions/github/lib/github.js"; +import { getOctokit } from "@actions/github"; import { ENV, REPO, ROOT_TEST_BRANCH_PREFIX, log } from "./env.js"; import { commitFilesFromBuffers } from "../../node.js"; diff --git a/tsup.config.ts b/tsup.config.ts index 34d51e4..b123c90 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -6,6 +6,7 @@ export default defineConfig({ "src/core.ts", "src/git.ts", "src/fs.ts", + "src/interfaces.ts", "src/node.ts", ], format: ["cjs", "esm"],