diff --git a/extensions/github/package.json b/extensions/github/package.json index 81e7646d464..e8213756e9f 100644 --- a/extensions/github/package.json +++ b/extensions/github/package.json @@ -18,6 +18,21 @@ "vscode.git" ], "main": "./out/extension.js", + "contributes": { + "commands": [ + { + "command": "github.publish", + "title": "Publish to GitHub" + } + ] + }, + "viewsWelcome": [ + { + "view": "scm", + "contents": "%welcome.publishFolder%", + "when": "config.git.enabled && git.state == initialized && workbenchState == folder" + } + ], "scripts": { "vscode:prepublish": "npm run compile", "compile": "gulp compile-extension:github", diff --git a/extensions/github/package.nls.json b/extensions/github/package.nls.json index 9132f0a2bf0..66c6b04ab12 100644 --- a/extensions/github/package.nls.json +++ b/extensions/github/package.nls.json @@ -1,4 +1,5 @@ { "displayName": "GitHub", - "description": "GitHub" + "description": "GitHub", + "welcome.publishFolder": "You can also directly publish this folder to a GitHub repository.\n[$(github) Publish to GitHub](command:github.publish)" } diff --git a/extensions/github/src/commands.ts b/extensions/github/src/commands.ts new file mode 100644 index 00000000000..6ca4c6ac819 --- /dev/null +++ b/extensions/github/src/commands.ts @@ -0,0 +1,125 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { API as GitAPI } from './typings/git'; +import { getOctokit } from './octokit'; + +function sanitizeRepositoryName(value: string): string { + return value.trim().replace(/[^a-z0-9_.]/ig, '-'); +} + +export function registerGlobalCommands(context: vscode.ExtensionContext, gitAPI: GitAPI) { + async function publish(): Promise { + if (!vscode.workspace.workspaceFolders?.length) { + return; + } + + const folder = vscode.workspace.workspaceFolders[0]; // TODO + + const octokit = await getOctokit(); + const user = await octokit.users.getAuthenticated({}); + const owner = user.data.login; + + const quickpick = vscode.window.createQuickPick(); + quickpick.ignoreFocusOut = true; + + quickpick.placeholder = 'Repository Name'; + quickpick.show(); + + let repo: string | undefined; + + const onDidChangeValue = async () => { + const sanitizedRepo = sanitizeRepositoryName(quickpick.value); + + if (!sanitizedRepo) { + quickpick.items = []; + } else { + quickpick.items = [{ label: `$(repo) Create private repository`, description: `$(github) ${owner}/${sanitizedRepo}`, alwaysShow: true, repo: sanitizedRepo }]; + } + }; + + quickpick.value = folder.name; + onDidChangeValue(); + + while (true) { + const listener = quickpick.onDidChangeValue(onDidChangeValue); + const pick = await getPick(quickpick); + listener.dispose(); + + repo = pick?.repo; + + if (repo) { + try { + quickpick.busy = true; + await octokit.repos.get({ owner, repo: repo }); + quickpick.items = [{ label: `$(error) Repository already exists`, description: `$(github) ${owner}/${repo}`, alwaysShow: true }]; + } catch { + break; + } finally { + quickpick.busy = false; + } + } + } + + quickpick.dispose(); + + if (!repo) { + return; + } + + const githubRepository = await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, cancellable: false, title: 'Publish to GitHub' }, async progress => { + progress.report({ message: 'Creating private repository in GitHub', increment: 25 }); + + const res = await octokit.repos.createForAuthenticatedUser({ + name: repo!, + private: true + }); + + const createdGithubRepository = res.data; + + progress.report({ message: 'Creating first commit', increment: 25 }); + const repository = await gitAPI.init(folder.uri); + + if (!repository) { + return; + } + + await repository.commit('first commit', { all: true }); + + progress.report({ message: 'Uploading files', increment: 25 }); + await repository.addRemote('origin', createdGithubRepository.clone_url); + await repository.push('origin', 'master', true); + + return createdGithubRepository; + }); + + if (!githubRepository) { + return; + } + + const openInGitHub = 'Open In GitHub'; + const action = await vscode.window.showInformationMessage(`Successfully published the '${owner}/${repo}' repository on GitHub.`, openInGitHub); + + if (action === openInGitHub) { + vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(githubRepository.html_url)); + } + } + + context.subscriptions.push(vscode.commands.registerCommand('github.publish', async () => { + try { + publish(); + } catch (err) { + vscode.window.showErrorMessage(err.message); + } + })); +} + +function getPick(quickpick: vscode.QuickPick): Promise { + return Promise.race([ + new Promise(c => quickpick.onDidAccept(() => quickpick.selectedItems.length > 0 && c(quickpick.selectedItems[0]))), + new Promise(c => quickpick.onDidHide(() => c(undefined))) + ]); +}