diff --git a/package.json b/package.json index 149fcff..ca2623b 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,14 @@ "light": "images/book-light.svg", "dark": "images/book-light.svg" } + }, + { + "command": "Docx Github Add Token", + "title": "Docx Github - Add token" + }, + { + "command": "Docx Gitlab Add Token", + "title": "Docx Gitlab - Add token" } ], "menus": { diff --git a/src/api/repository.controller.ts b/src/api/repository.controller.ts index 6d3793c..09493c0 100644 --- a/src/api/repository.controller.ts +++ b/src/api/repository.controller.ts @@ -1,6 +1,7 @@ import { Documentation } from '../association.manager' import { AssociationsValidator, DocAssociationsConfig } from '../association.validator' import { StructuralValidator } from '../structural.validator' +import { Token } from '../utils/credentials.utils' import { ErrorManager } from '../utils/error.utils' import { FileSystemManager } from '../utils/fileSystem.utils' import { WorkspaceManager } from '../utils/workspace.utils' @@ -46,18 +47,20 @@ export class RepositoryController { public static async create( json: string, providerStrategies: ProviderStrategy[], + tokens: Token[] = [], fileSystem = new FileSystemManager() ): Promise { const instance = new RepositoryController(json, fileSystem, providerStrategies) - await instance.initialize(json) + await instance.initialize(json, tokens) return instance } - private async initialize(json: string): Promise { + private async initialize(json: string, tokens: Token[]): Promise { const config = this.fileSystem.processFileContent(json) await this.validateConfig(config) - const providerConfigs = await this.configMapper.mapConfigToProviders(config) + const providerConfigs = await this.configMapper.mapConfigToProviders(config, tokens) + this.repository = new RepositoryFactory(providerConfigs) } @@ -85,7 +88,10 @@ export class RepositoryController { class ProviderConfigMapper { constructor(private providerStrategies: ProviderStrategy[]) {} - public async mapConfigToProviders(config: DocAssociationsConfig): Promise { + public async mapConfigToProviders( + config: DocAssociationsConfig, + tokens: Token[] + ): Promise { const providerConfigsMap = new Map() for (const docLocations of Object.values(config.associations)) { @@ -94,7 +100,7 @@ class ProviderConfigMapper { strategy.isMatch(docLocation) ) if (matchingStrategy) { - const providerConfig = matchingStrategy.getProviderConfig(docLocation) + const providerConfig = matchingStrategy.getProviderConfig(docLocation, tokens) const key = JSON.stringify(providerConfig) // Create a unique key for each config providerConfigsMap.set(key, providerConfig) } diff --git a/src/api/repository.strategy.ts b/src/api/repository.strategy.ts index 861886e..1608a0c 100644 --- a/src/api/repository.strategy.ts +++ b/src/api/repository.strategy.ts @@ -1,11 +1,12 @@ import { ErrorManager } from '../utils/error.utils' import { ProviderConfig } from './repository.controller' +import { Token } from '../utils/credentials.utils' const knownRepositories = ['github.com'] as const export interface ProviderStrategy { isMatch(docLocation: string): boolean - getProviderConfig(docLocation: string): ProviderConfig + getProviderConfig(docLocation: string, tokens: Token[]): ProviderConfig } export class LocalProviderStrategy implements ProviderStrategy { @@ -22,13 +23,19 @@ export class RepositoryProviderStrategy implements ProviderStrategy { return knownRepositories.some((domain) => docLocation.includes(domain)) } - getProviderConfig(docLocation: string): ProviderConfig { + getProviderConfig(docLocation: string, tokens: Token[]): ProviderConfig { const domain = knownRepositories.find((domain) => docLocation.includes(domain)) if (!domain) { ErrorManager.outputError(`Unrecognized repository domain in URL: ${docLocation}`) throw new Error(`Unrecognized repository domain in URL: ${docLocation}`) } - return { type: this.extractRepositoryName(domain), repositories: [docLocation] } + const repositoryName = this.extractRepositoryName(domain) + const token = tokens.find((token) => token.provider === repositoryName)!.key + return { + type: repositoryName, + repositories: [docLocation], + token: token, + } } private extractRepositoryName(domain: string): 'github' | never { diff --git a/src/extension.ts b/src/extension.ts index 72145cf..cd3dbbb 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -8,8 +8,16 @@ import { SchemaManager } from './config/schema.manager' import { RepositoryController } from './api/repository.controller' import { RepositoryProviderStrategy, LocalProviderStrategy } from './api/repository.strategy' import * as MarkdownIt from 'markdown-it' +import { CredentialManager } from './utils/credentials.utils' export async function activate(context: vscode.ExtensionContext) { + const credentialManager = new CredentialManager(context.secrets) + let tokens = await credentialManager.getTokens() + + credentialManager.onTokenChange(async () => { + tokens = await credentialManager.getTokens() + }) + ErrorManager.initialize() SchemaManager.initialize( '/.docx.json', @@ -19,8 +27,11 @@ export async function activate(context: vscode.ExtensionContext) { const workspaceFolder = WorkspaceManager.getWorkspaceFolder() const providerStrategies = [new LocalProviderStrategy(), new RepositoryProviderStrategy()] const jsonConfig = await fileSystem.readFile(`${workspaceFolder}/.docx.json`) - - const repositoryController = await RepositoryController.create(jsonConfig, providerStrategies) + const repositoryController = await RepositoryController.create( + jsonConfig, + providerStrategies, + tokens + ) const documentations = await repositoryController.getDocumentations() const disposable = vscode.commands.registerCommand('extension.openDropdown', async () => { @@ -62,6 +73,18 @@ export async function activate(context: vscode.ExtensionContext) { }) }) + const commandGithubAddToken = vscode.commands.registerCommand( + 'Docx Github Add Token', + async () => await credentialManager.openTokenInputBox('github') + ) + + const commandGitlabAddToken = vscode.commands.registerCommand( + 'Docx Gitlab Add Token', + async () => await credentialManager.openTokenInputBox('gitlab') + ) + + context.subscriptions.push(commandGithubAddToken) + context.subscriptions.push(commandGitlabAddToken) context.subscriptions.push(disposable) } export function deactivate() {} diff --git a/src/utils/credentials.utils.ts b/src/utils/credentials.utils.ts new file mode 100644 index 0000000..ffda59d --- /dev/null +++ b/src/utils/credentials.utils.ts @@ -0,0 +1,66 @@ +import * as vscode from 'vscode' + +export interface Token { + provider: 'github' | 'gitlab' + key: string +} + +export class CredentialManager { + private secretStorage: vscode.SecretStorage + private providers: Token['provider'][] + constructor(secretStorage: vscode.SecretStorage) { + this.secretStorage = secretStorage + this.providers = ['github', 'gitlab'] + } + + private saveToken = async (token: Token) => { + await this.secretStorage.store(token.provider, token.key) + } + + public openTokenInputBox = async (provider: Token['provider']) => { + const inputValue = await vscode.window.showInputBox({ + placeHolder: `${provider.charAt(0).toUpperCase()}${provider.slice(1)} Personnal Access Token`, + }) + if (inputValue) { + this.saveToken({ + provider, + key: inputValue, + }) + vscode.window.showInformationMessage( + `Docx ${provider} Personnal Access Token has been saved successfully.`, + 'Close' + ) + } + } + + private getToken = async (provider: Token['provider']): Promise => { + const tokenKey = await this.secretStorage.get(provider) + if (tokenKey) { + return { + provider, + key: tokenKey, + } + } + } + + public getTokens = async (): Promise => { + const tokens: Token[] = [] + + for (const provider of this.providers) { + const token = await this.getToken(provider) + if (token) tokens.push(token) + } + + return tokens + } + + public deleteToken = (provider: Token['provider']) => { + this.secretStorage.delete(provider) + } + + public onTokenChange = (callback: () => void) => { + this.secretStorage.onDidChange(() => { + callback() + }) + } +}