Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ updates:
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
33 changes: 25 additions & 8 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,43 @@ name: Node.js Package
on:
release:
types: [released]
workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
environment:
name: "production"
url: "https://www.npmjs.com/package/github-pewpew"
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v7
# Setup .npmrc file to publish to npm
- uses: actions/setup-node@v2
- uses: actions/setup-node@v7
with:
node-version: '14'
node-version: "24.x"
cache: npm
registry-url: 'https://registry.npmjs.org'
- run: npm install
- run: npm ci
# Run tests
- run: npm test
- name: Verify npm OIDC support
run: |
NPM_VERSION=$(npm --version)
node -e "const [major, minor] = '$NPM_VERSION'.split('.').map(Number); process.exit(major > 11 || (major === 11 && minor >= 5) ? 0 : 1)"
# Release tags must match the package version exactly.
- name: Verify release version
if: github.event_name == 'release'
run: |
PACKAGE_VERSION=$(node -p "require('./package.json').version")
test "$GITHUB_REF_NAME" = "v$PACKAGE_VERSION"
# Manual runs validate the complete package without publishing it.
- name: Validate package publication
if: github.event_name == 'workflow_dispatch'
run: npm publish --access public --dry-run
# Publish to npm
- run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Publish package
if: github.event_name == 'release'
run: npm publish --access public
30 changes: 24 additions & 6 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,30 @@
name: Tests
on: [pull_request, workflow_dispatch]

on:
pull_request:
workflow_dispatch:

permissions:
contents: read

jobs:
test:
test_matrix:
name: test (${{ matrix.node-version }})
runs-on: ubuntu-latest
strategy:
matrix:
node-version: ["20.x", "22.x", "24.x"]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 16
- run: npm install
node-version: ${{ matrix.node-version }}
cache: npm
- run: npm ci
- run: npm test

test:
needs: test_matrix
runs-on: ubuntu-latest
steps:
- run: echo "All Node.js matrix tests passed."
41 changes: 28 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<br>
github-pewpew
</h1>
<p align="center">Pew pew needless GitHub repos!<br>Clean up your unused repos via the CLI within seconds.</p>
<p align="center">Pew pew needless GitHub resources!<br>Clean up repositories, Codespaces, and gists from the CLI.</p>
<p align="center">
<a href="https://badge.fury.io/js/github-pewpew"><img src="https://badge.fury.io/js/github-pewpew.svg" alt="npm version" height="18"></a>
<a href="https://justforfunnoreally.dev/"><img src="https://img.shields.io/badge/justforfunnoreally-dev-9ff" alt="Just for fun. No, really." height="18"></a>
Expand Down Expand Up @@ -37,15 +37,34 @@ Available commands:
ghpew repos
ghpew repos --archive
ghpew repos -a
ghpew repos --force
ghpew repos --regex '^adrianmg/demo-'
ghpew repos --list 'adrianmg/one,adrianmg/two'
ghpew repos --list 'adrianmg/one,adrianmg/two' --force
ghpew gists
ghpew codespaces
ghpew help
```

Repository selection is interactive by default. `--regex` uses a case-sensitive JavaScript
regular expression against each full `owner/repository` name; pass the pattern without `/`
delimiters. `--list` accepts exact, comma-separated `owner/repository` names and stops without
processing anything if a name is not available. Every selection mode asks for confirmation
unless `--force` is provided, and either filtering flag can be combined with `--archive`.
Because `--force` processes the selection immediately, review it carefully first.

Codespaces and gists use interactive multi-selection and always ask for confirmation before
deleting anything. Gists are identified by their description or first filename, visibility,
and unique ID. Gist deletion cannot be undone. The first run after upgrading may ask you to
sign in again so the CLI can request permission to manage gists.

## Why?

Have you ever had too much fun with the GitHub API and ended up creating too many dummy repos? Me too 😅!
Have you ever had too much fun with GitHub and ended up creating too many throwaway
repositories, Codespaces, or gists? Me too 😅!

I made this little CLI tool to clean up repositories quickly. I'm planning to add some flags and regexp to delete in bulk in the future. [Let me know](http://twitter.com/adrianmg) if that sounds interesting to you.
I made this little CLI tool to clean up GitHub clutter quickly. [Let me
know](http://twitter.com/adrianmg) what you think.

Do you want to know more? [Visit the official website](https://adrianmato.com/pewpew).

Expand All @@ -58,33 +77,29 @@ The important parts of the project are the following:
├── src
│ ├── commands
│   │   ├── codespaces.js Contains the command to delete codespaces
│   │   └── repos.js Contains the command to delete repos
│   │   ├── gists.js Contains the command to delete gists
│   │   └── repos.js Contains the command to process repositories
│   ├── config.js Contains the configuration manager
│   ├── github.js Business logic: authentication and API calls
│   ├── repo-options.js Parses and resolves repository selection options
│   ├── ui.js CLI interactions
│   └── utils.js Lightweight utility functions
├── test
│   └── test.js Test coverage with Mocha
│   └── test.js Test coverage with the Node.js test runner
├── .prettierrc Code formatting configuration
├── index.js The main thread of execution
├── README.md you're looking at it
```

To **set up your environment** to develop this tool, run:

- Install Node.js 20 or newer
- `npm install`
- `node index`

You can also run `node index DEV=true CLIENT_ID=<YOUR_TESTING_CLIENT_ID>` if you want to use your own client id for development and testing purposes.

All the tests are written with [mocha](https://mochajs.org/) and can be run with `npm test`.

## TODO

- TODO: `--force` flag to avoid confirmation
- TODO: `--regex` flag to delete repos matching a regex
- TODO: `--list` flag to delete repos from a comma-separated list
- TODO: `ghpew gists` command to [delete gists](https://github.com/adrianmg/github-pewpew/issues/36)
The tests use the built-in Node.js test runner and can be run with `npm test`.

## Questions? Ideas? Bugs?

Expand Down
11 changes: 8 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import Config from './src/config.js';
import Github from './src/github.js';
import RepoOptions from './src/repo-options.js';
import UI from './src/ui.js';

import reposCommand from './src/commands/repos.js';
import codespacesCommand from './src/commands/codespaces.js';
import gistsCommand from './src/commands/gists.js';

UI.printWelcome();

Expand All @@ -17,19 +19,21 @@ const main = async () => {
}

const command = process.argv[2];
const archive = process.argv.includes('--archive') || process.argv.includes('-a');

switch (command) {
case 'repos':
case 'repo':
case 'repository':
case 'repositories':
await reposCommand(archive);
await reposCommand(RepoOptions.parse(process.argv.slice(3)));
break;
case 'codespaces':
case 'codespace':
await codespacesCommand();
break;
case 'gists':
case 'gist':
await gistsCommand();
break;
case 'help':
UI.printHelp();
break;
Expand All @@ -49,6 +53,7 @@ const main = async () => {
}

UI.printError(error);
process.exitCode = 1;
return;
}
};
Expand Down
Loading