Full Code of remorses/actions-cli for AI

master 2e5d17c380e2 cached
20 files
27.7 KB
7.1k tokens
17 symbols
1 requests
Download .txt
Repository: remorses/actions-cli
Branch: master
Commit: 2e5d17c380e2
Files: 20
Total size: 27.7 KB

Directory structure:
gitextract_frtp2xx9/

├── .dockerignore
├── .gitattributes
├── .github/
│   └── workflows/
│       ├── nothing.yml
│       └── package.yml
├── .gitignore
├── .vscode/
│   └── launch.json
├── Dockerfile
├── README.md
├── VERSION
├── package.json
├── src/
│   ├── constants.ts
│   ├── fetch.ts
│   ├── index.ts
│   ├── login.ts
│   ├── logout.ts
│   └── support.ts
├── tests/
│   ├── init.js
│   ├── simple.ts
│   └── spinner.ts
└── tsconfig.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .dockerignore
================================================
node_modules
dist
esm
example


================================================
FILE: .gitattributes
================================================
# Auto detect text files and perform LF normalization
* text=auto


================================================
FILE: .github/workflows/nothing.yml
================================================
name: Nothing

on:
    push:
        branches:
            - master

jobs:
    build:
        runs-on: ubuntu-latest
        steps:
            - uses: actions/checkout@v1
            - uses: actions/setup-node@v1
              with:
                  node-version: 12
                  registry-url: https://registry.npmjs.org/
            - run: yarn
            - run: yarn test
            - run: tsc

================================================
FILE: .github/workflows/package.yml
================================================
name: Npm Package

on:
    push:
        branches:
            - master

jobs:
    build:
        runs-on: ubuntu-latest
        steps:
            - uses: actions/checkout@v1
            - uses: actions/setup-node@v1
              with:
                  node-version: 12
                  registry-url: https://registry.npmjs.org/
            - run: yarn
            - run: yarn test
            - run: tsc
            - run: tsc -m es6 --outDir esm
            - name: Bump version
              uses: remorses/bump-version@js
              with:
                  version_file: VERSION
              env:
                  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
            - run: yarn publish
              env:
                  NODE_AUTH_TOKEN: ${{ secrets.npm_token }}


================================================
FILE: .gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.DS_Store
play*
dist/*
esm

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# TypeScript v1 declaration files
typings/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env

# parcel-bundler cache (https://parceljs.org/)
.cache

# next.js build output
.next

# nuxt.js build output
.nuxt

# vuepress build output
.vuepress/dist

# Serverless directories
.serverless

# FuseBox cache
.fusebox/


================================================
FILE: .vscode/launch.json
================================================
{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "Launch Program",
            "skipFiles": [
                "<node_internals>/**"
            ],
            "program": "${workspaceFolder}/dist/index.js",
            "args": ["-w", "package.yml", "-n"],
            "outFiles": [
                "${workspaceFolder}/**/*.js"
            ]
        }
    ]
}

================================================
FILE: Dockerfile
================================================
FROM node:12-alpine

RUN apk add --no-cache dumb-init # build-base

WORKDIR /workdir

COPY *.json *.lock /workdir/

RUN yarn

COPY . /workdir/

ENTRYPOINT ["dumb-init", "--"]
CMD yarn dev


================================================
FILE: README.md
================================================
<div align='center'>
    <br/>
    <br/>
    <h1>actions-cli</h1>
    <h3>Monitor your GitHub Actions from the command line</h3>
    <br/>
    <img width='500px' src='https://media.giphy.com/media/JUYF1dCf2qQ1T63VFU/giphy.gif'>
    <br/>
    <br/>
</div>

```
npm install -g actions-cli
```

## Usage

```
$ actions-cli login
$ actions-cli ./
```

```
actions-cli

Fetch the current hash job status and logs

Commands:
  actions-cli login  Logins to cli
  actions-cli        Fetch the current hash job status and logs    [predefinito]

Positionals:
  path  The github repo path                         [stringa] [predefinito: ""]

Options:
  --version       Mostra il numero di versione                        [booleano]
  --verbose, -v                                  [booleano] [predefinito: false]
  -h              Mostra la schermata di aiuto                        [booleano]
  --sha           The sha to look for actions, at least 7 characters long
                                         [stringa] [richiesto] [predefinito: ""]
  --workflow, -w  The workflow file name             [stringa] [predefinito: ""]
  --job, -j       The job name, defaults to the first listed job
                                                     [stringa] [predefinito: ""]
```

### TODOS

-   multiple jobs per workflow
-   multiple workflows
-   ~~use latest commit pushed instead of latest commit~~ use -p
-   ~~don't use the commit pushed from github actions~~ use -n




================================================
FILE: VERSION
================================================
0.0.36

================================================
FILE: package.json
================================================
{
    "name": "actions-cli",
    "_": "[bump]",
    "version": "0.0.36",
    "description": "",
    "main": "dist/index.js",
    "types": "dist/index.d.ts",
    "bin": {
        "actions-cli": "./dist/index.js"
    },
    "mocha": {
        "require": "tests/init.js",
        "spec": "tests/**.ts",
        "timeout": 9999999999
    },
    "files": [
        "/dist/*",
        "/esm/*"
    ],
    "scripts": {
        "test": "NODE_ENV=test mocha --colors --exit",
        "example": "parcel serve --no-autoinstall example/index.html",
        "compile": "sucrase -q ./src -d ./dist --transforms typescript,imports",
        "dev": "yarn compile && node dist",
        "play": "tsc --incremental && node dist"
    },
    "keywords": [],
    "author": "Tommaso De Rossi, morse <beats.by.morse@gmail.com>",
    "license": "ISC",
    "devDependencies": {
        "@types/mocha": "^5.2.7",
        "@types/node": "^12.0.7",
        "dotenv": "^8.2.0",
        "mocha": "^6.1.4",
        "sucrase": "^3.12.1",
        "typescript": "^3.8.3"
    },
    "dependencies": {
        "@octokit/rest": "^17.10.0",
        "@types/chalk": "^2.2.0",
        "@types/lodash": "^4.14.150",
        "@types/memoizee": "^0.4.4",
        "@types/node-fetch": "^2.5.7",
        "@types/yargs": "^15.0.4",
        "appdata-path": "^1.0.0",
        "await-to-js": "^2.1.1",
        "chalk": "^4.0.0",
        "cli-social-login": "^1.0.1",
        "cli-spinners": "^2.3.0",
        "conf": "^6.2.4",
        "encoding": "^0.1.12",
        "git-remote-origin-url": "^3.1.0",
        "lodash": "^4.17.15",
        "log-symbols": "^4.0.0",
        "log-update": "^4.0.0",
        "memoizee": "^0.4.14",
        "multispinner": "^0.2.1",
        "node-fetch": "^2.6.0",
        "ora": "^4.0.4",
        "parse-github-url": "^1.0.2",
        "simple-git": "^2.2.0",
        "winston": "^3.2.1",
        "yargs": "^15.3.1"
    }
}


================================================
FILE: src/constants.ts
================================================
import getAppDataPath from 'appdata-path'
import path from 'path'

const APP_DATA_FOLDER = 'actions-cli'
export const USER_TOKEN_CONFIG_KEY = 'token'
export const APP_DATA_PATH = path.resolve(getAppDataPath(APP_DATA_FOLDER))
export const firebaseConfig = {
    apiKey: 'AIzaSyAdM1fGOP_2bxM72M-Tf91wI76WiSe1ajE',
    authDomain: 'actions-cli-85f6f.firebaseapp.com',
    databaseURL: 'https://actions-cli-85f6f.firebaseio.com',
    projectId: 'actions-cli-85f6f',
    storageBucket: 'actions-cli-85f6f.appspot.com',
    messagingSenderId: '846622072078',
    appId: '1:846622072078:web:d7f258caf4cc93e6090b5f',
    measurementId: 'G-01QPNTKXC1',
}


================================================
FILE: src/fetch.ts
================================================
import memoize from 'memoizee'
import { RestEndpointMethodTypes, Octokit } from '@octokit/rest'
import { flatten } from 'lodash'
import simpleGit from 'simple-git/promise'
import to from 'await-to-js'
import chalk from 'chalk'
import { execSync } from 'child_process'
import { dots as cliSpinner } from 'cli-spinners'
import getRepoUrl from 'git-remote-origin-url'
import * as logSymbols from 'log-symbols'
import Multispinner from 'multispinner'
import Spinners from 'multispinner/lib/spinners'
import ora from 'ora'
import logUpdate from 'log-update'
import path from 'path'
import { Argv } from 'yargs'

import {
    initOctokit,
    parseGithubUrl,
    printGreen,
    printRed,
    sleep,
    catchAll,
    print,
} from './support'

const DEBUG = process.env.DEBUG

const FetchCommand = {
    command: '$0',
    describe: 'Fetch the current hash job status and logs',
    builder: (argv: Argv) => {
        argv.positional('path', {
            type: 'string',
            default: '',
            required: true,
            description: 'The github repo path',
        })
        argv.option('sha', {
            type: 'string',
            default: '',
            required: true,
            description:
                'The sha to look for actions, at least 7 characters long',
        })
        argv.option('workflow', {
            type: 'string',
            default: '',
            alias: 'w',
            description: 'The workflow file name',
        })
        argv.option('job', {
            type: 'string',
            default: '',
            alias: 'j',
            description: 'The job name, defaults to the first listed job',
        })
        argv.option('non-actions-commit', {
            type: 'boolean',
            default: false,
            alias: 'n',
            description:
                'Double checks that the used sha was not committed by github-actions, only works for recent actions commits',
        })
        argv.option('pushed-commit', {
            type: 'boolean',
            default: false,
            alias: 'p',
            description:
                'Use latest pushed commit instead of using latest commit in HEAD',
        })
    },
    handler: catchAll(async (argv) => {
        const octokit = initOctokit()
        const jobToFetch = argv.job
        const useLatestPushedCommit = argv['pushed-commit']
        const skipActionsCheck = !argv['non-actions-commit']
        const workflowToFetch = argv.workflow
        const currentPath = path.resolve(argv.path || process.cwd())
        const { owner, repo } = await getRepoInfo(currentPath)
        const spinner = ora(`getting last commit sha`).start()
        let sha =
            argv.sha ||
            (await getLastCommit({
                octokit,
                owner,
                repo,
                cwd: currentPath,
                skipActionsCheck,
                useLatestPushedCommit,
            }))
        const prettySha = sha.slice(0, 7)
        changeSpinnerText({
            text: `fetching state for sha '${prettySha}'`,
            spinner,
        })
        while (true) {
            let workflowRuns = []

            if (workflowToFetch) {
                const [err, data] = await to(
                    octokit.actions.listWorkflowRuns({
                        owner,
                        repo,
                        workflow_id: workflowToFetch,
                    }),
                )
                if (err) {
                    spinner.stop()
                    printRed(`Workflow '${workflowToFetch}' not found`)
                    return
                }
                workflowRuns = data.data.workflow_runs
            } else {
                const data = await octokit.actions.listWorkflowRunsForRepo({
                    owner,
                    repo,
                })
                workflowRuns = data.data.workflow_runs
            }
            const lastRun = workflowRuns.find((x) => {
                const { head_sha, status, id, conclusion, workflow_url } = x
                // console.log({ workflow_url })
                if (head_sha.slice(0, 7) === sha.slice(0, 7)) {
                    // console.log('found')

                    return true
                }
                return false
            })
            if (!lastRun) {
                changeSpinnerText({
                    spinner,
                    text: `waiting job handling last commit '${prettySha}'`,
                })
                await sleep(3000)
                if (!argv.sha) {
                    sha = await getLastCommit({
                        octokit,
                        owner,
                        repo,
                        cwd: currentPath,
                        skipActionsCheck,
                        useLatestPushedCommit,
                    })
                }
                continue
            }

            const { head_sha, status, id, conclusion, url, html_url } = lastRun
            // console.log(
            //     'unexpected values',
            //     JSON.stringify({ head_sha, status, id, conclusion }, null, 4)
            // )
            if (status === 'queued') {
                changeSpinnerText({ spinner, text: 'queued' })
                await sleep(3000)
                continue
            }
            if (status === 'in_progress') {
                changeSpinnerText({ spinner, text: 'in progress' })
                spinner.info()
                spinner.stop()
                await pollJobs({ repo, owner, id, jobToFetch })
                return
            }
            if (status === 'completed') {
                changeSpinnerText({ spinner, text: 'completed' })
                spinner.info()
                spinner.stop()
                // if (conclusion === 'success') {
                //     spinner.succeed(chalk.green('Success'))
                // }
                // if (conclusion === 'failure') {
                //     spinner.fail(chalk.red('Failure'))
                // }
                await pollJobs({ repo, owner, id, jobToFetch })
                return
            }
            console.log(
                'unexpected state',
                JSON.stringify({ head_sha, status, id, conclusion }, null, 4),
            )
            spinner.fail('Wtf?')
            return
        }
    }),
} // as CommandModule

export default FetchCommand

export async function pollJobs({ owner, repo, id, jobToFetch }) {
    DEBUG && console.log('pollJobs')
    const octokit = initOctokit()
    let spinners = null
    while (true) {
        const data = await octokit.actions.listJobsForWorkflowRun({
            owner,
            repo,
            run_id: id,
        })
        const job = jobToFetch
            ? data.data.jobs.find((job) => {
                  return job.name === jobToFetch
              })
            : data.data.jobs?.[0]
        if (!job) {
            printRed(
                `Job '${jobToFetch}' not found, make sure your yaml is valid`,
            )
            return
        }
        if (
            spinners === null ||
            // if the steps changed during build
            Object.keys(spinners.spinners).length !== job.steps.length
        ) {
            const obj = Object.assign(
                {},
                ...job.steps.map((x) => ({
                    [x.number]: x.name,
                })),
            )
            if (!spinners) {
                // init spinners
                spinners = new Multispinner(obj, {
                    // clear: false,
                    // update: logUpdate,
                    ...cliSpinner,
                })
            } else {
                // add a new spinner
                const spinnersKeys = Object.keys(spinners.spinners)
                Object.keys(obj).map((k) => {
                    if (!spinnersKeys.includes(k)) {
                        addSpinner({ spinners, key: k, value: obj[k] })
                    }
                })
            }
            DEBUG && console.log(JSON.stringify(job, null, 4))
        }
        displayJobsTree({ spinners, job })
        if (job.status !== 'completed') {
            await sleep(2000)
            continue
        }
        spinners.update.clear() // TODO bug on multispinners
        if (job.conclusion === 'failure') {
            job.steps.forEach((step) => {
                const spinner = spinners.spinners[step.number]
                if (spinner && spinner.state === 'incomplete') {
                    spinners.error(step.number)
                }
            })
        }
        if (job.conclusion === 'failure') {
            printRed(
                `${
                    logSymbols.error
                } Failed, read the logs at ${chalk.whiteBright(job.html_url)}`,
            )
            return
        }
        if (job.conclusion === 'success') {
            printGreen(
                `${
                    logSymbols.success
                } Success, read the logs at ${chalk.whiteBright(job.html_url)}`,
            )
            return
        }
        console.log(JSON.stringify(job, null, 4))
        return
    }
}

export function addSpinner({ spinners, key, value }) {
    spinners.spinners[key] = Spinners.prototype.spinnerObj(value)
}

export function displayJobsTree({
    job = null as RestEndpointMethodTypes['actions']['listJobsForWorkflowRun']['response']['data']['jobs'][0],
    spinners,
}) {
    // console.log(JSON.stringify(job, null, 4))
    for (let step of job.steps) {
        try {
            if (
                !Object.keys(spinners.spinners).includes(step.number.toString())
            ) {
                continue
            }
            if (step.status === 'queued') {
                // spinner.info(step.name)
                // return { ok: true }
            }
            if (step.status === 'in_progress') {
                // spinner.info(step.name)
                // spinners.success(step.number)
                // return { ok: true }
            }
            if (step.status === 'completed') {
                if (step.conclusion === 'success') {
                    // spinner.info(step.name)
                    spinners.success(step.number)
                    // return { ok: true, completed: true }
                }
                if (step.conclusion === 'failure') {
                    spinners.error(step.number)
                    // return { ok: false, completed: true }
                }
            }
        } catch (e) {
            // console.log('wtf', step)
            console.log(JSON.stringify(job, null, 4))
            throw e
            // console.error(step.name, step.number, e)
        }
    }
}

function changeSpinnerText({ spinner, text }) {
    if (spinner.text !== text) {
        spinner.info()
    }
    spinner.start(text)
}

// export async function isCommitFromActions({ sha, repo, owner }) {
//     const octokit = initOctokit()
//     const data = await octokit.repos.getCommit({ owner, repo, ref: sha })
//     console.log(JSON.stringify(data.data, null, 4))
//     return false
// }

export async function getRepoInfo(currentPath) {
    const gitRepoUrl = await getRepoUrl(currentPath)
    const { name: repo, owner } = parseGithubUrl(gitRepoUrl)
    return { repo, owner }
}

const GITHUB_ACTIONS_BOT_LOGIN = 'github-actions[bot]'

const getRepoCommits = memoize(
    async ({
        octokit,
        owner,
        repo,
    }): Promise<{ actor: string; refs: string[] }[]> => {
        const data = await octokit.activity.listRepoEvents({
            owner,
            repo,
            per_page: 50,
        })
        const commits = data.data
            .filter((event) => {
                return event.type === 'PushEvent'
            })
            .map((event) => {
                return {
                    actor: event.actor.login,
                    refs: event.payload.commits.map((x) => x.sha),
                }
            })
        return commits
    },
)

export async function getLastCommit(args: {
    octokit: Octokit
    owner
    repo
    cwd
    skipActionsCheck?: boolean
    useLatestPushedCommit: boolean
}): Promise<string> {
    if (args.useLatestPushedCommit) {
        return execSync(`git rev-parse origin/${getCurrentBranch()}`)
            .toString()
            .trim()
    }
    const { owner, repo, octokit } = args
    const git = simpleGit(args.cwd)
    const lastLocalCommits = await git.log()

    if (args.skipActionsCheck) {
        lastLocalCommits.all.find(
            (x) =>
                !['[no ci]', '[skip ci]', '[ci skip]'].some((m) =>
                    x.message.toLocaleLowerCase().includes(m),
                ),
        )
    }
    // console.log(JSON.stringify(data.data, null, 4))
    const commits = await getRepoCommits({ owner, repo, octokit })
    const githubActionsCommits: string[] = flatten(
        commits
            .filter((x) => {
                if (process.env.DEBUG) {
                    console.log('commit actor is ' + x.actor)
                }
                return x.actor === GITHUB_ACTIONS_BOT_LOGIN
            })
            .map((x) => x.refs),
    ).map((x) => x.slice(0, 7))

    const lastNonActionsCommit = lastLocalCommits.all.find((commit) => {
        return !githubActionsCommits.includes(commit.hash.slice(0, 7))
    })
    // console.log(
    //     '\n' +
    //         cliSpinner.frames[0] +
    //         ` found a non actions commit made from ` +
    //         lastNonActionsCommit.author_name ||
    //         lastNonActionsCommit.author_email,
    // )
    return lastNonActionsCommit.hash
}

function getCurrentBranch() {
    return execSync('git branch --show-current').toString().trim()
}


================================================
FILE: src/index.ts
================================================
#!/usr/bin/env node
import yargs from 'yargs'
import winston from 'winston'
import loginCommand from './login'
import logoutCommand from './logout'
import { winstonConf } from './support'
import FetchCommand from './fetch'

yargs
    .option('verbose', {
        alias: 'v',
        type: 'boolean',
        default: false,
    })
    .middleware([
        (argv) => {
            if (argv.verbose) {
                winston.configure({
                    ...winstonConf,
                    level: 'debug',
                })
                return
            }
            winston.configure({ ...winstonConf, silent: true, level: 'error' })
        },
    ])
    .command(loginCommand as any)
    .command(logoutCommand as any)
    .command(FetchCommand as any)
    // .demandCommand()
    .help('help').argv


================================================
FILE: src/login.ts
================================================
import yargs, { CommandModule, Argv } from 'yargs'
import { loginOnLocalhost } from 'cli-social-login'
import fs from 'fs'
import { USER_TOKEN_CONFIG_KEY, firebaseConfig } from './constants'
import { initStore, printRed } from './support'

const welcomeMessage =
    'Run `actions-cli` to see the actions status for the current commit'

export default {
    command: 'login',
    describe: 'Logins to cli',
    builder: (argv: Argv) => {
        argv.option('token', {
            type: 'string',
            default: '',
            description:
                "Pass the token directly, necessary if you can't login via localhost and browser",
        })
    },
    handler: async (argv) => {
        const store = initStore()
        if (argv.token) {
            store.set(USER_TOKEN_CONFIG_KEY, argv.token)
            console.log(`Token Saved`)
            console.log(welcomeMessage)
            return
        }
        // starts a server on localhost to login the user
        const { credentials, user } = await loginOnLocalhost({
            firebaseConfig,
            providers: ['github'],
            scopes: {
                github: ['notifications', 'repo'], // TODO organizations dont work
            },
        })
        const githubToken = credentials.oauthAccessToken
        if (!githubToken) {
            printRed('cannot get token')
            return
        }
        store.set(USER_TOKEN_CONFIG_KEY, githubToken)
        console.log(`Token Saved`)
        console.log(welcomeMessage)
    },
} // as CommandModule


================================================
FILE: src/logout.ts
================================================
import yargs, { CommandModule, Argv } from 'yargs'
import { loginOnLocalhost } from 'cli-social-login'
import fs from 'fs'
import { USER_TOKEN_CONFIG_KEY, firebaseConfig } from './constants'
import { initStore, printRed } from './support'

export default {
    command: 'logout',
    describe: 'Deletes the saved token',
    builder: (argv: Argv) => {},
    handler: async (argv) => {
        const store = initStore()
        store.delete(USER_TOKEN_CONFIG_KEY)
        console.log(`Deleted Token`)
    },
} // as CommandModule


================================================
FILE: src/support.ts
================================================
import Conf from 'conf'
import _parseGithubUrl from 'parse-github-url'

import chalk from 'chalk'
import path from 'path'
import { APP_DATA_PATH, USER_TOKEN_CONFIG_KEY } from './constants'
import winston from 'winston'
import { LoggerOptions } from 'winston'
import { Octokit } from '@octokit/rest'

let conf
export function initStore(): Conf {
    if (!conf) {
        conf = new Conf({ cwd: APP_DATA_PATH })
    }
    return conf
}

const { format, transports } = winston

const logFormat = format.printf((info) => {
    const msg =
        typeof info.message === 'object'
            ? JSON.stringify(info.message, null, 4)
            : info.message
    return `${info.timestamp} ${msg}`
})

export const winstonConf: LoggerOptions = {
    format: format.combine(
        format.label({
            label: path.basename(
                (process.mainModule && process.mainModule.filename) || '',
            ),
        }),
        format.timestamp({ format: 'YYYY-MM-DD HH' }),
        // Format the metadata object
        format.metadata({
            fillExcept: ['message', 'level', 'timestamp', 'label'],
        }),
    ),
    transports: [
        new transports.Console({
            format: format.combine(format.colorize({}), logFormat),
        }),
    ],
}

export const print = console.log
export const printRed = (x) => console.log(chalk.red(x))
export const printGreen = (x) => console.log(chalk.green(x))

export function getGithubToken() {
    const store = initStore()
    const token = store.get(USER_TOKEN_CONFIG_KEY)
    // console.log(token)
    if (!token) {
        printRed('cannot find github token, run `actions-cli login` first')
        process.exit(1)
    }
    return token
}

export function initOctokit(): Octokit {
    const token = getGithubToken()
    const octokit = new Octokit({ auth: token })
    return octokit
}

export function parseGithubUrl(githubUrl): { name; owner } {
    if (!githubUrl) {
        throw new Error(`cannot parse null github url `)
    }
    const parsedUrl = _parseGithubUrl(githubUrl)
    if (!parsedUrl) {
        throw new Error('cannot parse github url ' + githubUrl)
    }
    const { owner, name: repo } = parsedUrl
    if (!owner || !repo) {
        throw new Error('cannot parse github url ' + githubUrl)
    }
    return parsedUrl
}

export const sleep = (ms) => new Promise((r) => setTimeout(r, ms))

export function catchAll(fun) {
    return async (...args) => {
        try {
            return await fun(...args)
        } catch (e) {
            console.log()
            printRed(e)
            if (process.env.DEBUG) console.log(e)
            process.exit(1)
        }
    }
}


================================================
FILE: tests/init.js
================================================
const { config } = require('dotenv')

config({ path: 'test.env', })
// require('ts-node/register')
require('sucrase/register')


================================================
FILE: tests/simple.ts
================================================
import { getRepoInfo, getLastCommit } from '../src/fetch'
import { strict as assert } from 'assert'
import { Octokit } from '@octokit/rest'

it('ready', () => {
    assert.ok(true)
})
it('getLastCommit', async () => {
    const cwd = process.cwd()
    const res = await getLastCommit({
        ...await getRepoInfo(cwd),
        cwd,
        octokit: new Octokit(),
    })
    console.log(res)
})


================================================
FILE: tests/spinner.ts
================================================
import ora from 'ora'
import Multispinner from 'multispinner'
import { sleep, printRed } from '../src/support'
import { addSpinner } from '../src/fetch'

it('spinner', async () => {
    let spinner = ora('xxx').start()
    await sleep(1000)
    spinner.info()
    spinner.start('ciao')
    await sleep(1000)
    spinner.info()
    spinner.start('bye')
})
it('multispinner', async () => {
    let xs = new Multispinner({ a: 'x', b: 'y' }, {})
    await sleep(1000)
    xs.success('a')
    await sleep(1000)
})
it('addSpinner', async () => {
    let xs = new Multispinner({ a: 'x', b: 'y' }, {})
    await sleep(1000)
    xs.success('a')
    addSpinner({ spinners: xs, key: 'z', value: 'z' })
    await sleep(1000)
    printRed('ciao')
})


================================================
FILE: tsconfig.json
================================================
{
    "compilerOptions": {
        "target": "es5",
        "module": "commonjs",
        "moduleResolution": "Node",
        "lib": [
            "es2017",
            "es7",
            "es6"
            // "dom"
        ],
        "declaration": true,
        "outDir": "dist",
        "strict": false,
        "esModuleInterop": true,
        "noImplicitAny": false,
        "jsx": "react",
        "sourceMap": true,
        "skipLibCheck": true
    },
    "exclude": ["node_modules", "dist", "esm", "example", "tests"]
}
Download .txt
gitextract_frtp2xx9/

├── .dockerignore
├── .gitattributes
├── .github/
│   └── workflows/
│       ├── nothing.yml
│       └── package.yml
├── .gitignore
├── .vscode/
│   └── launch.json
├── Dockerfile
├── README.md
├── VERSION
├── package.json
├── src/
│   ├── constants.ts
│   ├── fetch.ts
│   ├── index.ts
│   ├── login.ts
│   ├── logout.ts
│   └── support.ts
├── tests/
│   ├── init.js
│   ├── simple.ts
│   └── spinner.ts
└── tsconfig.json
Download .txt
SYMBOL INDEX (17 symbols across 3 files)

FILE: src/constants.ts
  constant APP_DATA_FOLDER (line 4) | const APP_DATA_FOLDER = 'actions-cli'
  constant USER_TOKEN_CONFIG_KEY (line 5) | const USER_TOKEN_CONFIG_KEY = 'token'
  constant APP_DATA_PATH (line 6) | const APP_DATA_PATH = path.resolve(getAppDataPath(APP_DATA_FOLDER))

FILE: src/fetch.ts
  constant DEBUG (line 28) | const DEBUG = process.env.DEBUG
  function pollJobs (line 193) | async function pollJobs({ owner, repo, id, jobToFetch }) {
  function addSpinner (line 278) | function addSpinner({ spinners, key, value }) {
  function displayJobsTree (line 282) | function displayJobsTree({
  function changeSpinnerText (line 323) | function changeSpinnerText({ spinner, text }) {
  function getRepoInfo (line 337) | async function getRepoInfo(currentPath) {
  constant GITHUB_ACTIONS_BOT_LOGIN (line 343) | const GITHUB_ACTIONS_BOT_LOGIN = 'github-actions[bot]'
  function getLastCommit (line 370) | async function getLastCommit(args: {
  function getCurrentBranch (line 421) | function getCurrentBranch() {

FILE: src/support.ts
  function initStore (line 12) | function initStore(): Conf {
  function getGithubToken (line 53) | function getGithubToken() {
  function initOctokit (line 64) | function initOctokit(): Octokit {
  function parseGithubUrl (line 70) | function parseGithubUrl(githubUrl): { name; owner } {
  function catchAll (line 87) | function catchAll(fun) {
Condensed preview — 20 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (31K chars).
[
  {
    "path": ".dockerignore",
    "chars": 30,
    "preview": "node_modules\ndist\nesm\nexample\n"
  },
  {
    "path": ".gitattributes",
    "chars": 66,
    "preview": "# Auto detect text files and perform LF normalization\n* text=auto\n"
  },
  {
    "path": ".github/workflows/nothing.yml",
    "chars": 404,
    "preview": "name: Nothing\n\non:\n    push:\n        branches:\n            - master\n\njobs:\n    build:\n        runs-on: ubuntu-latest\n   "
  },
  {
    "path": ".github/workflows/package.yml",
    "chars": 780,
    "preview": "name: Npm Package\n\non:\n    push:\n        branches:\n            - master\n\njobs:\n    build:\n        runs-on: ubuntu-latest"
  },
  {
    "path": ".gitignore",
    "chars": 1132,
    "preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n.DS_Store\nplay*\ndist/*\nesm\n\n# Runtime data\npids\n*.pid\n*"
  },
  {
    "path": ".vscode/launch.json",
    "chars": 650,
    "preview": "{\n    // Use IntelliSense to learn about possible attributes.\n    // Hover to view descriptions of existing attributes.\n"
  },
  {
    "path": "Dockerfile",
    "chars": 188,
    "preview": "FROM node:12-alpine\n\nRUN apk add --no-cache dumb-init # build-base\n\nWORKDIR /workdir\n\nCOPY *.json *.lock /workdir/\n\nRUN "
  },
  {
    "path": "README.md",
    "chars": 1465,
    "preview": "<div align='center'>\n    <br/>\n    <br/>\n    <h1>actions-cli</h1>\n    <h3>Monitor your GitHub Actions from the command l"
  },
  {
    "path": "VERSION",
    "chars": 6,
    "preview": "0.0.36"
  },
  {
    "path": "package.json",
    "chars": 1904,
    "preview": "{\n    \"name\": \"actions-cli\",\n    \"_\": \"[bump]\",\n    \"version\": \"0.0.36\",\n    \"description\": \"\",\n    \"main\": \"dist/index."
  },
  {
    "path": "src/constants.ts",
    "chars": 646,
    "preview": "import getAppDataPath from 'appdata-path'\nimport path from 'path'\n\nconst APP_DATA_FOLDER = 'actions-cli'\nexport const US"
  },
  {
    "path": "src/fetch.ts",
    "chars": 13782,
    "preview": "import memoize from 'memoizee'\nimport { RestEndpointMethodTypes, Octokit } from '@octokit/rest'\nimport { flatten } from "
  },
  {
    "path": "src/index.ts",
    "chars": 813,
    "preview": "#!/usr/bin/env node\nimport yargs from 'yargs'\nimport winston from 'winston'\nimport loginCommand from './login'\nimport lo"
  },
  {
    "path": "src/login.ts",
    "chars": 1544,
    "preview": "import yargs, { CommandModule, Argv } from 'yargs'\nimport { loginOnLocalhost } from 'cli-social-login'\nimport fs from 'f"
  },
  {
    "path": "src/logout.ts",
    "chars": 529,
    "preview": "import yargs, { CommandModule, Argv } from 'yargs'\nimport { loginOnLocalhost } from 'cli-social-login'\nimport fs from 'f"
  },
  {
    "path": "src/support.ts",
    "chars": 2664,
    "preview": "import Conf from 'conf'\nimport _parseGithubUrl from 'parse-github-url'\n\nimport chalk from 'chalk'\nimport path from 'path"
  },
  {
    "path": "tests/init.js",
    "chars": 127,
    "preview": "const { config } = require('dotenv')\n\nconfig({ path: 'test.env', })\n// require('ts-node/register')\nrequire('sucrase/regi"
  },
  {
    "path": "tests/simple.ts",
    "chars": 397,
    "preview": "import { getRepoInfo, getLastCommit } from '../src/fetch'\nimport { strict as assert } from 'assert'\nimport { Octokit } f"
  },
  {
    "path": "tests/spinner.ts",
    "chars": 737,
    "preview": "import ora from 'ora'\nimport Multispinner from 'multispinner'\nimport { sleep, printRed } from '../src/support'\nimport { "
  },
  {
    "path": "tsconfig.json",
    "chars": 527,
    "preview": "{\n    \"compilerOptions\": {\n        \"target\": \"es5\",\n        \"module\": \"commonjs\",\n        \"moduleResolution\": \"Node\",\n  "
  }
]

About this extraction

This page contains the full source code of the remorses/actions-cli GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 20 files (27.7 KB), approximately 7.1k tokens, and a symbol index with 17 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!