Skip to content

Commit aadb23f

Browse files
committed
Adds a ConfigManager and introduces vitest
Signed-off-by: Tyler Smalley <tyler@tailscale.com>
1 parent 5c36e64 commit aadb23f

File tree

7 files changed

+121
-1
lines changed

7 files changed

+121
-1
lines changed

.github/workflows/test.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ jobs:
2626
key: yarn-cache
2727
- name: Install dependencies
2828
run: tool/yarn install --frozen-lockfile --prefer-offline
29+
- name: Unit tests
30+
run: tool/yarn test
2931
- name: Lint
3032
run: tool/yarn lint
3133
- name: Typescript

.vscode/launch.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,17 @@
1515
},
1616
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
1717
"preLaunchTask": "${defaultBuildTask}"
18+
},
19+
{
20+
"type": "node",
21+
"request": "launch",
22+
"name": "Debug Current Test File",
23+
"autoAttachChildProcesses": true,
24+
"skipFiles": ["<node_internals>/**", "**/node_modules/**"],
25+
"program": "${workspaceRoot}/node_modules/vitest/vitest.mjs",
26+
"args": ["run", "${relativeFile}"],
27+
"smartStep": true,
28+
"console": "integratedTerminal"
1829
}
1930
]
2031
}

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@
278278
"package": "vsce package --allow-star-activation",
279279
"precommit": "lint-staged",
280280
"prepare": "husky install",
281+
"test": "vitest",
281282
"vscode:prepublish": "(yarn run bundle-js & pid1=$!; yarn run bundle-go & pid2=$!; wait $pid1 || exit 1; wait $pid2 || exit 1)",
282283
"watch": "webpack serve"
283284
},
@@ -315,6 +316,7 @@
315316
"ts-loader": "^9.4.4",
316317
"typescript": "^5.1.6",
317318
"utf-8-validate": "^6.0.3",
319+
"vitest": "^.33.0",
318320
"vscode-jsonrpc": "^8.1.0",
319321
"webpack": "^5.88.2",
320322
"webpack-cli": "^5.1.4",

src/config-manager.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import * as vscode from 'vscode';
2+
import * as fs from 'fs';
3+
import * as path from 'path';
4+
import { test, expect, beforeEach } from 'vitest';
5+
import { ConfigManager } from './config-manager';
6+
7+
const extensionContext = {
8+
globalStoragePath: '/tmp/vscode-tailscale',
9+
} as vscode.ExtensionContext;
10+
11+
const configPath = path.join(extensionContext.globalStoragePath, 'config.json');
12+
13+
beforeEach(() => {
14+
if (fs.existsSync(configPath)) {
15+
fs.unlinkSync(configPath);
16+
}
17+
});
18+
19+
test('withContext will create directory if it does not exist', () => {
20+
fs.rmSync(extensionContext.globalStoragePath, { recursive: true, force: true });
21+
expect(fs.existsSync(extensionContext.globalStoragePath)).toBe(false);
22+
23+
ConfigManager.withContext(extensionContext);
24+
expect(fs.existsSync(extensionContext.globalStoragePath)).toBe(true);
25+
});
26+
27+
test('withContext returns an initialized ConfigManager', () => {
28+
const cm = ConfigManager.withContext(extensionContext);
29+
expect(cm.configPath).toBe(configPath);
30+
});
31+
32+
test('set persists config to disk', () => {
33+
const cm = new ConfigManager(configPath);
34+
const hosts = {
35+
'host-1': {
36+
user: 'foo',
37+
rootDir: '/',
38+
},
39+
};
40+
41+
cm.set('hosts', hosts);
42+
expect(cm.get('hosts')).toEqual(hosts);
43+
44+
const f = fs.readFileSync(configPath, 'utf8');
45+
expect(JSON.parse(f)).toEqual({ hosts });
46+
});

src/config-manager.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import * as vscode from 'vscode';
2+
import * as fs from 'fs';
3+
import * as path from 'path';
4+
5+
interface Host {
6+
user: string;
7+
rootDir: string;
8+
}
9+
10+
interface Config {
11+
defaultHost?: Host;
12+
hosts?: Record<string, Host>;
13+
}
14+
15+
export class ConfigManager {
16+
private config: Config;
17+
18+
constructor(public readonly configPath: string) {
19+
if (fs.existsSync(this.configPath)) {
20+
const rawData = fs.readFileSync(this.configPath, 'utf8');
21+
this.config = JSON.parse(rawData);
22+
} else {
23+
this.config = {};
24+
}
25+
}
26+
27+
static withContext(context: vscode.ExtensionContext) {
28+
const globalStoragePath = context.globalStoragePath;
29+
30+
if (!fs.existsSync(globalStoragePath)) {
31+
fs.mkdirSync(globalStoragePath);
32+
}
33+
34+
return new ConfigManager(path.join(globalStoragePath, 'config.json'));
35+
}
36+
37+
get<K extends keyof Config>(key: K): Config[K] {
38+
return this.config[key];
39+
}
40+
41+
set<K extends keyof Config>(key: K, value: Config[K]) {
42+
this.config[key] = value;
43+
this.saveConfig();
44+
}
45+
46+
private saveConfig() {
47+
fs.writeFileSync(this.configPath, JSON.stringify(this.config, null, 2), 'utf8');
48+
}
49+
}

src/extension.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as vscode from 'vscode';
2-
2+
import * as fs from 'fs';
33
import { ServePanelProvider } from './serve-panel-provider';
44
import { ADMIN_CONSOLE } from './utils/url';
55
import { Tailscale } from './tailscale';
@@ -13,6 +13,7 @@ import {
1313
} from './node-explorer-provider';
1414

1515
import { TSFileSystemProvider } from './ts-file-system-provider';
16+
import { ConfigManager } from './config-manager';
1617

1718
let tailscaleInstance: Tailscale;
1819

@@ -21,6 +22,8 @@ export async function activate(context: vscode.ExtensionContext) {
2122

2223
tailscaleInstance = await Tailscale.withInit(vscode);
2324

25+
const configManager = ConfigManager.withContext(context);
26+
2427
// walkthrough completion
2528
tailscaleInstance.serveStatus().then((status) => {
2629
// assume if we have any BackendState we are installed

vitest.config.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { configDefaults, defineConfig } from 'vitest/config';
2+
3+
export default defineConfig({
4+
test: {
5+
exclude: [...configDefaults.exclude, '**/out/**'],
6+
},
7+
});

0 commit comments

Comments
 (0)