npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@s0/ghcommit

v1.2.0

Published

Directly change files on github using the github API, to support GPG signing

Downloads

255

Readme

@s0/ghcommit

View on NPM

NPM / TypeScript package to commit changes GitHub repositories using the GraphQL API.

Why?

  • Simplified GPG Signing:

    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).

    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.

  • Simplified Git Config:

    When performing git actions via the GitHub API, all actions are always attributed to the actor whose GITHUB_TOKEN is being used (whether an app, or user), and this information is reflected in the git committer and author. As such, it's no longer necessary (or even possible) to specify the commit author (name and email address).

    This simplifies the process of preparing your workflows for pushing changes, as you no longer need to configure the name and email address in git, and ensure they appropriately match any GPG keys used.

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:

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:

{
  octokit: GitHubClient;
  owner: string;
  repo: 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: string | 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:

{
  /**
   * The base commit to build your changes on-top of
   *
   * @default HEAD
   */
  base?: {
    commit: string;
  };
  /**
   * The root of the repository.
   *
   * @default process.cwd()
   */
  repoDirectory?: string;
}

Example:

import { context, 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,
  ...context.repo,
  branch: "new-branch-to-create",
  message: "[chore] do something",
});

// Commit & push the files from a 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: "[chore] do something else\n\nsome more details",
  repoDirectory: "/tmp/some-repo",
});

// Commit & push the files from the current directory,
// but ensure changes from any locally-made commits are also included
await commitChangesFromRepo({
  octokit,
  ...context.repo,
  branch: "another-new-branch-to-create",
  message: {
    headline: "[chore] do something else",
    body: "some more details",
  },
  base: {
    // This will be the original sha from the workflow run,
    // even if we've made commits locally
    commit: context.sha,
  },
});

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:

{
  /**
   * 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:

import { context, 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,
  ...context.repo,
  branch: "new-branch-to-create",
  message: "[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,
  ...context.repo,
  branch: "docs",
  message: "[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:

{
  /**
   * 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:

import { context, 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,
  ...context.repo,
  branch: "new-branch-to-create",
  message: "[chore] do something",
  base: {
    branch: "main",
  },
  fileChanges: {
    additions: [
      {
        path: "hello/world.txt",
        contents: Buffer.alloc(1024, "Hello, world!"),
      },
    ],
  },
});

Other Tools / Alternatives