From 5f93acc0a8279380d73b4f9460ec606c790b84f3 Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Thu, 20 Jul 2023 07:41:48 -0700 Subject: [PATCH] Adds a ConfigManager to persist configuration (#115) Stacked on https://github.com/tailscale-dev/vscode-tailscale/pull/116 Signed-off-by: Tyler Smalley --- package.json | 2 +- src/config-manager.test.ts | 46 +++++++++++++++++++++++++++++++++++ src/config-manager.ts | 49 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 src/config-manager.test.ts create mode 100644 src/config-manager.ts diff --git a/package.json b/package.json index a15f500..c34a9a8 100644 --- a/package.json +++ b/package.json @@ -314,7 +314,7 @@ "package": "vsce package --allow-star-activation", "precommit": "lint-staged", "prepare": "husky install", - "test": "vitest --passWithNoTests", + "test": "vitest", "vscode:prepublish": "(yarn run bundle-js & pid1=$!; yarn run bundle-go & pid2=$!; wait $pid1 || exit 1; wait $pid2 || exit 1)", "watch": "webpack serve" }, diff --git a/src/config-manager.test.ts b/src/config-manager.test.ts new file mode 100644 index 0000000..8e7dfbd --- /dev/null +++ b/src/config-manager.test.ts @@ -0,0 +1,46 @@ +import * as vscode from 'vscode'; +import * as fs from 'fs'; +import * as path from 'path'; +import { test, expect, beforeEach } from 'vitest'; +import { ConfigManager } from './config-manager'; + +const extensionContext = { + globalStoragePath: '/tmp/vscode-tailscale', +} as vscode.ExtensionContext; + +const configPath = path.join(extensionContext.globalStoragePath, 'config.json'); + +beforeEach(() => { + if (fs.existsSync(configPath)) { + fs.unlinkSync(configPath); + } +}); + +test('withContext will create directory if it does not exist', () => { + fs.rmSync(extensionContext.globalStoragePath, { recursive: true, force: true }); + expect(fs.existsSync(extensionContext.globalStoragePath)).toBe(false); + + ConfigManager.withContext(extensionContext); + expect(fs.existsSync(extensionContext.globalStoragePath)).toBe(true); +}); + +test('withContext returns an initialized ConfigManager', () => { + const cm = ConfigManager.withContext(extensionContext); + expect(cm.configPath).toBe(configPath); +}); + +test('set persists config to disk', () => { + const cm = new ConfigManager(configPath); + const hosts = { + 'host-1': { + user: 'foo', + rootDir: '/', + }, + }; + + cm.set('hosts', hosts); + expect(cm.get('hosts')).toEqual(hosts); + + const f = fs.readFileSync(configPath, 'utf8'); + expect(JSON.parse(f)).toEqual({ hosts }); +}); diff --git a/src/config-manager.ts b/src/config-manager.ts new file mode 100644 index 0000000..bbced17 --- /dev/null +++ b/src/config-manager.ts @@ -0,0 +1,49 @@ +import * as vscode from 'vscode'; +import * as fs from 'fs'; +import * as path from 'path'; + +interface Host { + user: string; + rootDir: string; +} + +interface Config { + defaultHost?: Host; + hosts?: Record; +} + +export class ConfigManager { + private config: Config; + + constructor(public readonly configPath: string) { + if (fs.existsSync(this.configPath)) { + const rawData = fs.readFileSync(this.configPath, 'utf8'); + this.config = JSON.parse(rawData); + } else { + this.config = {}; + } + } + + static withContext(context: vscode.ExtensionContext) { + const globalStoragePath = context.globalStoragePath; + + if (!fs.existsSync(globalStoragePath)) { + fs.mkdirSync(globalStoragePath); + } + + return new ConfigManager(path.join(globalStoragePath, 'config.json')); + } + + get(key: K): Config[K] { + return this.config[key]; + } + + set(key: K, value: Config[K]) { + this.config[key] = value; + this.saveConfig(); + } + + private saveConfig() { + fs.writeFileSync(this.configPath, JSON.stringify(this.config, null, 2), 'utf8'); + } +}