Search code examples
node.jsgitgithub-actionsactzx

Unable to perform git commands on remote from within script


I have script I want to run upon merging a pull request in GitHub. The script is written in JavaScript using the ZX library.

The purpose of the script is to automatically determine the next version of a package to be published to npm and the actions including fetching the latest git tags from GitHub in order to negotiate what version to release under.

Unfortunately I'm struggling a lot with running a simple git pull --tags action. I managed to install act which enables me to run the script locally.

When the script reaches the step where it pulls from git it times out. The error message is not very clear, but I see something with "Username" so I suspect it is trying to authenticate with GitHub using password instead of personal access token:

| Username for 'https://github.com': /Users/work/projects/aves/node_modules/zx/build/core.cjs:245
|             const output = new ProcessOutput(
|                            ^
| 
| ProcessOutput [Error]: 
|     at /Users/work/projects/aves/scripts/prepublish.js:609:37
|     exit code: null
|     signal: SIGTERM
|     at EventEmitter.end (/Users/work/projects/aves/node_modules/zx/build/core.cjs:245:28)
|     at EventEmitter.emit (node:events:519:28)
|     at ChildProcess.<anonymous> (/Users/work/projects/aves/node_modules/zx/build/vendor.cjs:20283:16)
|     at ChildProcess.emit (node:events:519:28)
|     at maybeClose (node:internal/child_process:1105:16)
|     at Socket.<anonymous> (node:internal/child_process:457:11)
|     at Socket.emit (node:events:519:28)
|     at Pipe.<anonymous> (node:net:338:12) {
|   _code: null,
|   _signal: 'SIGTERM',
|   _stdout: '',
|   _stderr: '',
|   _combined: ''
| }
| 
| Node.js v20.15.0
error Command failed with exit code 1.

This is my workflow:

name: Publish Package to npmjs
on:
  pull_request:
    branches:
      - main
    types: ['closed']
jobs:
  build:
    runs-on: ubuntu-latest
    env:
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    steps:
      - name: Git checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Setup Node.js and copy .npmrc file
        uses: actions/setup-node@v4
        with:
          node-version: '20.x'
          registry-url: 'https://registry.npmjs.org'
      - name: Configure git for HTTPS
        run: |
          git config --global url.https://github.com/.insteadOf [email protected]:
          git config --global url.https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/.insteadOf https://github.com/
      - name: Fetch tags
        run: yarn && yarn zx ./scripts/prepublish.js --ci

Initially, instead of running prepublish.js on the last line I ran:

  run: git pull --tags

And it used to work for a short while until I made some change I don't remember and now it also fails if I pull directly from the workflow.

Inside prepublish.js I tried various things:

  • I've tried printing the GITHUB_TOKEN. It works.

  • I've tried calling git commands that don't require authenticating with the remote repo, such as git status and it works fine.

  • I tried running git config --list and printing the output. Interestingly I notice that the token is not included in the output. I'm not sure if there is some logic in github to remove sensitive tokens before printing, but I tried to remove the x-access-token line from the workflow and instead running it inside prepublish.js like so:

    await $`git config --global url.https://x-access-token:${process.env.GITHUB_TOKEN}@github.com/.insteadOf https://github.com/`;
    

    Subsequently printing git config does include the token in the url, but calls to git push still fails with the same error and I can't think of any other steps to try at this point.

Here is what I currently have in prepublish.js

#!/usr/bin/env zx

const zx = require('zx');

const { $, spinner } = zx;

(async () => {
  await $`git config --global url.https://x-access-token:${process.env.GITHUB_TOKEN}@github.com/.insteadOf https://github.com/`;
  const { stdout: gitConfig } = await $`git config --list`;

  console.log({ gitConfig });

  const { stdout, stderr } = await $`git pull --tags`.timeout('2m');

  if (stderr) {
    console.error('stderr:', stderr);
  }

  console.log('stdout:', stdout);

  return;
})()

Solution

  • I managed to find a solution myself.

    First, I was referencing the environment variable for GITHUB_TOKEN wrong in the run section. Instead of ${{ secrets.GITHUB_TOKEN }} I needed to use ${{ env.GITHUB_TOKEN }}.

    Second, instead of rewriting URLs I needed to re-add the remote with the correct URL format:

    git remote remove origin
    git remote add org https://<user>:${{ env.GITHUB_TOKEN }}@github.com/<organisation>/<repo>.git
    

    So eventually my workflow ended up looking like this:

    name: Publish Package to npmjs
    on:
      pull_request:
        branches:
          - main
        types: ['closed']
    jobs:
      build:
        runs-on: ubuntu-latest
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        steps:
          - name: Git checkout
            uses: actions/checkout@v4
          - name: Setup Node.js and copy .npmrc file
            uses: actions/setup-node@v4
            with:
              node-version: '20.x'
              registry-url: 'https://registry.npmjs.org'
          - name: Configure git for HTTPS
            run: |
              git remote remove origin
              git remote add org https://GITHUB_ACTIONS_BOT:${{ env.GITHUB_TOKEN }}@github.com/<org>/<repo>.git
          - name: Fetch tags
            run: yarn && yarn zx ./scripts/prepublish.js --ci
    
    

    Within prepublish.js I'm now able to execute git commands:

    ...
    const version = ...
    
    await $`npm version ${version}`
    await $`git push --tags`
    ...