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"]
}
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
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.