Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
00ed45b
feat: added support to delete codespaces
AnishDe12020 Jul 26, 2022
6197f45
update: bump version
adrianmg Jul 29, 2022
21b41b8
fix(codespace): make user re-auth if unable to fetch codespaces
AnishDe12020 Jul 30, 2022
55803a0
refactor: use commander.js
AnishDe12020 Jul 30, 2022
bcfce4a
refactor: remove commander.js
AnishDe12020 Aug 1, 2022
a8eae41
test: added codespace scope to tests
AnishDe12020 Aug 1, 2022
587bb88
docs: update readme to show usage of codespaces command and update fi…
AnishDe12020 Aug 1, 2022
8ed64c2
docs(readme): fix indentation in file tree
AnishDe12020 Aug 3, 2022
5acd3ec
fix: print help if unknown command is passed
AnishDe12020 Aug 4, 2022
865b2b7
Merge branch 'feat/delete-codespaces' of github.com:AnishDe12020/gith…
AnishDe12020 Aug 4, 2022
51ecf43
refactor: use a utility function for repo/codespace label
AnishDe12020 Aug 4, 2022
10b0adf
update: add anish to README
adrianmg Sep 6, 2022
5f582ae
fix: stop printing help message if no repos selected
AnishDe12020 Sep 6, 2022
a0e7a7d
update: refactor arrow function for consistency
adrianmg Sep 6, 2022
ec7014f
update: more descriptive name for labels func
adrianmg Sep 6, 2022
1397859
update: package description
adrianmg Sep 6, 2022
602026f
improve: help command formatting
adrianmg Sep 6, 2022
31de3a6
update: README with more todos
adrianmg Sep 6, 2022
36ef81e
Merge branch 'feat/delete-codespaces' of github.com:AnishDe12020/gith…
adrianmg Sep 6, 2022
efc1a51
fix: missing string
adrianmg Sep 30, 2022
a5e8cda
fix: correct print message on canceling deletion of codespaces
adrianmg Sep 30, 2022
bbb23fd
update: const variables from package
adrianmg Sep 30, 2022
6397aae
update: make help command the default one
adrianmg Sep 30, 2022
79647eb
Bump clipboardy from 2.3.0 to 3.0.0
dependabot[bot] Sep 15, 2022
e794311
update: package description
adrianmg Sep 30, 2022
0f129b0
Bump ora from 5.4.1 to 6.1.2
dependabot[bot] Sep 15, 2022
d271fa9
Migration from CommonJS to ES Modules
adrianmg Sep 30, 2022
e341924
update: test to ES Modules
adrianmg Sep 30, 2022
12aac3a
Bump mocha from 9.2.2 to 10.0.0
adrianmg Sep 30, 2022
7d7bb93
Bump @octokit/auth-oauth-device from 3.1.2 to 4.0.2
adrianmg Sep 30, 2022
5db9794
add: hint how to select
adrianmg Sep 30, 2022
10cffb0
update: less obvious hint to select with space?
adrianmg Sep 30, 2022
9ef7d97
add: backup commands alias for repos
adrianmg Sep 30, 2022
809d683
update: document available commands
adrianmg Oct 1, 2022
f90b2c5
fix: importing assert
adrianmg Oct 1, 2022
5750a92
fix: tests workflow bump version 16
adrianmg Oct 1, 2022
02c9b8e
update: workflow can be dispatched manually
adrianmg Oct 1, 2022
840dd63
fix: add back npm install
adrianmg Oct 1, 2022
9822d5b
update: minor refactor
adrianmg Oct 1, 2022
f6c7e92
update: permissions for release drafter action
adrianmg Oct 1, 2022
1aaf5b3
test if this fixes it
adrianmg Oct 1, 2022
b3f530e
fix
adrianmg Oct 1, 2022
3f09c5d
remove: release draft is not needed anymore. BYE
adrianmg Oct 1, 2022
4058d55
merge: pr #39
jdvr Sep 29, 2022
44b7954
update: directory is recursive and takes unix into account
adrianmg Oct 2, 2022
35e46fe
update: homedir() directly imported from 'os'
adrianmg Oct 2, 2022
45a252d
update: package description since now you can do more than repos!
adrianmg Oct 2, 2022
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
54 changes: 38 additions & 16 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,49 @@ async function main() {
Config.save(token);
}

const repositories = await UI.getRepositories();
if (!repositories) {
Config.deleteFile();
return await main();
}
if (process.argv[2] == 'codespaces') {
const codespaces = await UI.getCodespaces();

let res = await UI.promptSelectRepositories(repositories);
if (res.repos.length === 0) {
UI.printNoReposSelected();
let res = await UI.promptSelectCodespaces(codespaces);

return 0;
}
if (res.codespaces.length === 0) {
UI.printNoCodespaceSelected();

return 0;
}

const reposToDelete = res.repos;
const repoCount = reposToDelete.length;
res = await UI.promptConfirmDelete(repoCount);
const codespacesToDelete = res.codespaces;
const codespaceCount = codespacesToDelete.length;
res = await UI.promptConfirmDelete(codespaceCount, 'codespaces');

if (res.confirmDelete === 'Yes') {
await UI.deleteRepositories(reposToDelete);
if (res.confirmDelete === 'Yes') {
await UI.deleteCodespaces(codespacesToDelete);
} else {
UI.printNoReposDeleted();
}
} else {
UI.printNoReposDeleted();
const repositories = await UI.getRepositories();
if (!repositories) {
Config.deleteFile();
return await main();
}

let res = await UI.promptSelectRepositories(repositories);
if (res.repos.length === 0) {
UI.printNoReposSelected();

return 0;
}

const reposToDelete = res.repos;
const repoCount = reposToDelete.length;
res = await UI.promptConfirmDelete(repoCount, 'repos');

if (res.confirmDelete === 'Yes') {
await UI.deleteRepositories(reposToDelete);
} else {
UI.printNoReposDeleted();
}
}
} catch (error) {
if (error instanceof Github.AuthError || error instanceof Github.ScopesError) {
Expand Down
4 changes: 3 additions & 1 deletion src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ function getConfigDir(homeDir) {
homeDir,
process.platform === 'win32'
? path.join('AppData', 'Roaming', PACKAGE_AUTHOR, PACKAGE.name)
: path.join('Library', `com.${PACKAGE_AUTHOR}.${PACKAGE.name}`)
: process.platform == 'darwin'
? path.join('Library', `com.${PACKAGE_AUTHOR}.${PACKAGE.name}`)
: path.join('.config', `com.${PACKAGE_AUTHOR}.${PACKAGE.name}`)
);

return configDir;
Expand Down
30 changes: 29 additions & 1 deletion src/github.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const CLIENT_ID_PROD = 'ed7c193c5b64ee06192a';

const CLIENT_ID = process.env.DEV ? process.env.CLIENT_ID : CLIENT_ID_PROD;
const CLIENT_TYPE = 'oauth-app';
const CLIENT_SCOPES = ['delete_repo', 'repo'];
const CLIENT_SCOPES = ['delete_repo', 'repo', 'codespace'];
const API_PAGINATION = 100;
const API_AFFILIATION = 'owner, collaborator';

Expand Down Expand Up @@ -40,6 +40,24 @@ async function getRepositories() {
return repos;
}

async function getCodespaces() {
let page = 1;

const codespaces = [];

while (true) {
const res = await apiCall('GET', '/user/codespaces', page);
const codespacesCurrentPage = res.data.codespaces;

if (codespacesCurrentPage.length === 0) break;

codespaces.push(...codespacesCurrentPage);
page++;
}

return codespaces;
}

function checkPermissions(authScopes, clientScopes) {
if (authScopes.length < clientScopes.length) {
return false;
Expand All @@ -58,6 +76,14 @@ async function deleteRepository(repository) {
return true;
}

async function deleteCodespace(codespace) {
const res = await apiCall('DELETE', `/user/codespaces/${codespace}`);

if (res.status !== 204) return false;

return true;
}

function getAuthHeader() {
return `token ${process.env.GITHUB_TOKEN}`;
}
Expand Down Expand Up @@ -110,8 +136,10 @@ class ScopesError extends Error {
module.exports = {
auth,
getRepositories,
getCodespaces,
checkPermissions,
deleteRepository,
deleteCodespace,
setToken,
AuthError,
ScopesError,
Expand Down
123 changes: 113 additions & 10 deletions src/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,25 @@ async function promptSelectRepositories(repositories) {
}
}

async function promptSelectCodespaces(codespaces) {
try {
if (codespaces.length === 0) throw error;

return await prompt({
type: 'autocomplete',
name: 'codespaces',
message: 'Select codespaces you want to delete:',
limit: 12,
multiple: true,
footer: '––—————––—————––—————––—————––————————————',
format: (value) => style.green(value),
choices: codespaces.map(({ name }) => name),
});
} catch (error) {
return { codespaces: [] };
}
}

async function getRepositories() {
const strMessage = `Fetching repositories…`;
const spinner = ora(strMessage).start();
Expand All @@ -90,12 +109,39 @@ async function getRepositories() {
}
}

async function getCodespaces() {
const strMessage = `Fetching codespaces…`;
const spinner = ora(strMessage).start();

try {
const codespaces = await Github.getCodespaces();

const count = codespaces.length;
const strSucceed = printCodespacesFound(count);
spinner.succeed(style.dim(strSucceed));

return codespaces;
} catch (error) {
spinner.stop();

if (error instanceof Github.AuthError || error instanceof Github.ScopesError) {
throw error;
}
}
}

function printReposFound(count) {
const strMessage = `${count} ${count > 1 ? 'repositories' : 'repository'} found.`;

return strMessage;
}

function printCodespacesFound(count) {
const strMessage = `${count} ${count > 1 ? 'codespaces' : 'codespace'} found.`;

return strMessage;
}

async function deleteRepositories(repositories) {
const deletedRepos = [];

Expand All @@ -115,13 +161,38 @@ async function deleteRepositories(repositories) {
}

if (deletedRepos.length > 0) {
printConfirmDelete(deletedRepos);
printConfirmDelete(deletedRepos, 'repos');
} else {
printNoReposDeleted();
}
}

async function promptConfirmDelete(repoCount) {
async function deleteCodespaces(codespaces) {
const deletedCodespaces = [];

for (const codespace of codespaces) {
const spinner = ora().start();

try {
await Github.deleteCodespace(codespace);
deletedCodespaces.push(codespace);

spinner.stopAndPersist({ symbol: '', text: style.strikethrough.dim(codespace) });
} catch (error) {
const message = error.response?.data?.message;

spinner.fail(style.dim(`${codespace} (Oops! ${message})`));
}
}

if (deletedCodespaces.length > 0) {
printConfirmDelete(deletedCodespaces, 'codespaces');
} else {
printNoCodespacesDeleted();
}
}

async function promptConfirmDelete(count, type) {
return await prompt({
type: 'select',
name: 'confirmDelete',
Expand All @@ -131,7 +202,15 @@ async function promptConfirmDelete(repoCount) {
{
name: 'Yes',
message: `${style.redBright(
`Yes, delete ${repoCount > 1 ? 'repositories' : 'repository'} (${repoCount})`
`Yes, delete ${
count > 1
? type === 'repos'
? 'repositories'
: 'codespaces'
: type === 'repos'
? 'repository'
: 'codespace'
} (${count})`
)}`,
value: true,
},
Expand All @@ -144,16 +223,23 @@ async function promptConfirmDelete(repoCount) {
});
}

function printConfirmDelete(deletedRepos) {
const count = deletedRepos.length;

const strDeletedRepos = count > 1 ? deletedRepos.join(', ') : deletedRepos;
const strRepos = count > 1 ? 'repositories' : 'repository';
const strConfirm = `🔫 pew pew! ${count} ${strRepos} deleted successfully: ${strDeletedRepos}`;
function printConfirmDelete(deletedItems, type) {
const count = deletedItems.length;

const strDeletedItems = count > 1 ? deletedItems.join(', ') : deletedItems;
const strItems =
count > 1
? type === 'repos'
? 'repositories'
: 'codespaces'
: type === 'repos'
? 'repository'
: 'codespace';
Comment thread
adrianmg marked this conversation as resolved.
Outdated
const strConfirm = `🔫 pew pew! ${count} ${strItems} deleted successfully: ${strDeletedItems}`;
const strRecover = `Recover repositories from github.com/settings/repositories`;

console.log(strConfirm);
console.log(style.dim(strRecover));
type === 'repos' && console.log(style.dim(strRecover));

return true;
}
Expand All @@ -164,12 +250,24 @@ function printNoReposDeleted() {
return console.log(style.dim(strMessage));
}

function printNoCodespacesDeleted() {
const strMessage = `Rest assured, no codespaces were deleted.`;

return console.log(style.dim(strMessage));
}

function printNoReposSelected() {
const strMessage = `No repositories selected.`;

return console.log(style.dim(strMessage));
}

function printNoCodespaceSelected() {
const strMessage = `No codespaces selected.`;

return console.log(style.dim(strMessage));
}

function printError(strError) {
console.log();
return console.log(style.redBright(strError));
Expand All @@ -179,10 +277,15 @@ module.exports = {
printWelcome,
promptAuth,
getRepositories,
getCodespaces,
promptSelectRepositories,
promptSelectCodespaces,
deleteRepositories,
deleteCodespaces,
promptConfirmDelete,
printNoReposDeleted,
printNoReposSelected,
printNoCodespacesDeleted,
printNoCodespaceSelected,
printError,
};