From d01aa357d498375bfe871437ce51f05180a809d1 Mon Sep 17 00:00:00 2001 From: Christopher Fuhrman Date: Wed, 15 Jul 2026 17:30:04 -0400 Subject: [PATCH] Test isolated VSIX installation and execution (npm run test:vsix) --- .github/workflows/ci.yml | 23 ++++++++ .vscodeignore | 21 ++++++- eslint.config.mjs | 18 +++++- package.json | 8 +-- scripts/run-vsix-tests.js | 116 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 179 insertions(+), 7 deletions(-) create mode 100644 scripts/run-vsix-tests.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c36a139..fa142d9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,3 +31,26 @@ jobs: with: run: npx vscode-test working-directory: client + + test-vsix: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: + - ubuntu-latest + - macos-latest + - windows-latest + node-version: [22.x] + steps: + - uses: actions/checkout@v4 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + - name: Install dependencies + run: npm ci + - name: Package and test the .vsix + uses: coactions/setup-xvfb@v1 + with: + run: npm run test:vsix + diff --git a/.vscodeignore b/.vscodeignore index 95a155d..ea024db 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -1,15 +1,15 @@ .vscode/** .vscode-test/** +.github/** node_modules/** client/node_modules/** server/node_modules/.bin/** server/node_modules/.cache/** -server/node_modules/typescript/** -server/node_modules/ts-morph/** client/src/test/** server/tests/** server/src/** client/src/** +scripts/** **/*.ts **/*.map **/.DS_Store @@ -20,3 +20,20 @@ tsconfig.json README_lidiaa.md test-cases.md install.sh +.vscode-test.mjs +client/.vscode-test.mjs +client/esbuild.js +client/tsconfig.json +client/package-lock.json +client/.vscode-test-dummy/** +client/test-harness/** +server/esbuild.mjs +server/jest.config.mjs +server/tsconfig.json +server/package-lock.json + +**/node_modules/**/test/** +**/node_modules/**/tests/** +**/node_modules/**/*.test.js +**/node_modules/**/.github/** +server/node_modules/typescript/bin/** diff --git a/eslint.config.mjs b/eslint.config.mjs index 222d25f..66f2f2c 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -19,6 +19,22 @@ export default tseslint.config( js.configs.recommended, ...tseslint.configs.recommended, ...tseslint.configs.stylistic, + { + files: ['scripts/**/*.js'], + languageOptions: { + sourceType: 'commonjs', + globals: { + require: 'readonly', + module: 'readonly', + process: 'readonly', + __dirname: 'readonly', + console: 'readonly' + } + }, + rules: { + '@typescript-eslint/no-require-imports': 'off' + } + }, { plugins: { '@stylistic': stylistic @@ -43,4 +59,4 @@ export default tseslint.config( ] } } -); \ No newline at end of file +); diff --git a/package.json b/package.json index e5be1b5..c03061e 100644 --- a/package.json +++ b/package.json @@ -47,17 +47,17 @@ }, "scripts": { "vscode:prepublish": "npm run build:client && npm run build:server", - "build:client": "cd client && node esbuild.js --production", - "build:server": "cd server && node esbuild.mjs --production", "compile": "tsc -b", "watch": "tsc -b -w", "lint": "eslint", "postinstall": "cd client && npm install && cd ../server && npm install && cd ..", "test:client": "npm run compile && npx @vscode/vsce package --allow-missing-repository --baseContentUrl https://github.com/Leoo-code/FamixTypeScriptImporter/blob/dev/vscode-extension && node ./client/dist/test/runTest.js", - "test:vsix": "npm run compile && npx @vscode/vsce package --allow-missing-repository --baseContentUrl https://github.com/Leoo-code/FamixTypeScriptImporter/blob/dev/vscode-extension && node ./client/dist/test/runTestVSIX.js", + "test:vsix": "npm run compile && npx @vscode/vsce package --allow-missing-repository && node ./scripts/run-vsix-tests.js", "test": "npm run compile && node ./client/dist/test/runTest.js && cd server && npm run test", - "build:local": "cd ../FamixTypeScriptImporter && npm run build && npm link && cd ../test-isolated/server && npm link ts2famix && node esbuild.mjs", "test:smoke": "ulimit -n 10240 && cd client && node esbuild.js && npx vscode-test", + "build:client": "cd client && node esbuild.js --production", + "build:server": "cd server && node esbuild.mjs --production", + "build:local-ts2famix": "cd ../FamixTypeScriptImporter && npm run build && npm link && cd ../test-isolated/server && npm link ts2famix && node esbuild.mjs", "build": "cd server && npm install && node esbuild.mjs" }, "devDependencies": { diff --git a/scripts/run-vsix-tests.js b/scripts/run-vsix-tests.js new file mode 100644 index 0000000..612e315 --- /dev/null +++ b/scripts/run-vsix-tests.js @@ -0,0 +1,116 @@ +// scripts/run-vsix-tests.js +// +// Runs the extension's Mocha test suite against the *packaged* .vsix rather +// than the raw source tree. VS Code's own CLI is used to unpack/install the +// .vsix into an isolated, throwaway profile - the extension folder it +// produces is then passed as `extensionDevelopmentPath`, so the "extension +// under test" is byte-identical to what a real user would get after +// installing the .vsix. `--disable-extensions` blocks every other extension, +// so nothing but the packaged code runs. +const path = require('path'); +const fs = require('fs'); +const os = require('os'); +const cp = require('child_process'); +const { + runTests, + downloadAndUnzipVSCode, + resolveCliArgsFromVSCodeExecutablePath, +} = require('@vscode/test-electron'); + +function findVsix(rootDir) { + const pkg = JSON.parse(fs.readFileSync(path.join(rootDir, 'package.json'), 'utf-8')); + const vsixPath = path.resolve(rootDir, `${pkg.name}-${pkg.version}.vsix`); + + if (!fs.existsSync(vsixPath)) { + throw new Error(`Expected VSIX not found: ${vsixPath}\nRun "vsce package" first.`); + } + + return vsixPath; +} + +function installVsix(vscodeExecutablePath, vsixPath, userDataDir, extensionsDir) { + const [cliPath, ...cliArgs] = resolveCliArgsFromVSCodeExecutablePath(vscodeExecutablePath); + + const args = [ + ...cliArgs, + '--user-data-dir', userDataDir, + '--extensions-dir', extensionsDir, + '--install-extension', vsixPath, + '--force', + ]; + + console.log(`[VSIX Test] Installing: ${vsixPath}`); + console.log(`[VSIX Test] Into: ${extensionsDir}`); + + const result = cp.spawnSync(cliPath, args, { + stdio: 'inherit', + encoding: 'utf-8', + shell: process.platform === 'win32', + }); + + if (result.error) { + throw new Error(`VSIX install failed to start: ${result.error.message}\nCLI: ${cliPath}`); + } + + if (result.signal) { + throw new Error(`VSIX install terminated by signal ${result.signal}`); + } + + if (result.status !== 0) { + throw new Error(`VSIX install failed with exit code ${result.status}`); + } +} + +async function main() { + const rootDir = path.resolve(__dirname, '..'); + const vsixPath = findVsix(rootDir); + + const tmpBase = fs.mkdtempSync(path.join(os.tmpdir(), 'ts2famix-vsix-test-')); + const userDataDir = path.join(tmpBase, 'user-data'); + const extensionsDir = path.join(tmpBase, 'extensions'); + fs.mkdirSync(userDataDir, { recursive: true }); + fs.mkdirSync(extensionsDir, { recursive: true }); + + console.log(`[VSIX Test] Isolated profile: ${tmpBase}`); + + const vscodeExecutablePath = await downloadAndUnzipVSCode('stable'); + + installVsix(vscodeExecutablePath, vsixPath, userDataDir, extensionsDir); + + // The extension under test is the exact folder VS Code just extracted - + // no manual unzip, no separate dummy extension. Installing writes + // metadata files (extensions.json, .obsolete, etc.) alongside the + // extension folder, so filter to directories only. + const installedFolder = fs.readdirSync(extensionsDir, { withFileTypes: true }) + .find(entry => entry.isDirectory() && !entry.name.startsWith('.')) + ?.name; + if (!installedFolder) { + throw new Error(`No extension folder found in ${extensionsDir} after install`); + } + const extensionDevelopmentPath = path.join(extensionsDir, installedFolder); + console.log(`[VSIX Test] Extension under test: ${extensionDevelopmentPath}`); + + const extensionTestsPath = path.resolve(rootDir, 'client/dist/test/suite'); + const workspacePath = path.resolve(rootDir, 'client/src/test/fixtures/project-with-tsconfig'); + + console.log('[VSIX Test] Running suite against installed VSIX...'); + + await runTests({ + vscodeExecutablePath, + extensionDevelopmentPath, + extensionTestsPath, + launchArgs: [ + workspacePath, + '--disable-extensions', + '--user-data-dir', userDataDir, + '--extensions-dir', extensionsDir, + ], + }); + + console.log('[VSIX Test] Passed - packaged .vsix works in an isolated profile.'); +} + +main().catch(err => { + console.error(`[VSIX Test] Failed: ${err}`); + process.exit(1); +});