├── .yarnrc
├── sample
├── package.nls.json
├── test-workspace
│ ├── folder
│ │ ├── x.txt
│ │ └── .bar
│ │ │ └── .foo
│ ├── hello.txt
│ ├── world.txt
│ └── folder_with_utf_8_🧿
│ │ └── !#$%&'()+,-.0123456789;=@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{}~
├── .vscode
│ ├── extensions.json
│ ├── settings.json
│ ├── tasks.json
│ └── launch.json
├── tsconfig.runTest.json
├── src
│ └── web
│ │ ├── test
│ │ ├── suite
│ │ │ ├── extension.test.ts
│ │ │ ├── index.ts
│ │ │ ├── search.test.ts
│ │ │ └── fs.test.ts
│ │ └── runTest.ts
│ │ └── extension.ts
├── README.md
├── tsconfig.json
├── package.json
└── webpack.config.js
├── fs-provider
├── package.nls.json
├── .vscode
│ ├── extensions.json
│ ├── settings.json
│ └── tasks.json
├── tsconfig.json
├── package.json
├── webpack.config.js
├── vscode.proposed.fileSearchProvider.d.ts
├── src
│ ├── fsExtensionMain.ts
│ └── fsProvider.ts
└── vscode.proposed.textSearchProvider.d.ts
├── .prettierrc
├── tsconfig.tsbuildinfo
├── .gitignore
├── .npmignore
├── .vscode
├── tasks.json
├── settings.json
└── launch.json
├── tsconfig.json
├── .editorconfig
├── src
├── server
│ ├── tsconfig.json
│ ├── main.ts
│ ├── mounts.ts
│ ├── extensions.ts
│ ├── app.ts
│ ├── download.ts
│ ├── workbench.ts
│ └── index.ts
└── browser
│ ├── tsconfig-amd.json
│ ├── tsconfig-esm.json
│ ├── main.ts
│ └── workbench.api.d.ts
├── .github
└── workflows
│ └── tests.yml
├── LICENSE
├── views
├── workbench-esm.html
└── workbench.html
├── CHANGELOG.md
├── eslint.config.mjs
├── package.json
├── SECURITY.md
└── README.md
/.yarnrc:
--------------------------------------------------------------------------------
1 | --ignore-engines true
--------------------------------------------------------------------------------
/sample/package.nls.json:
--------------------------------------------------------------------------------
1 | {
2 | }
--------------------------------------------------------------------------------
/fs-provider/package.nls.json:
--------------------------------------------------------------------------------
1 | {
2 | }
--------------------------------------------------------------------------------
/sample/test-workspace/folder/x.txt:
--------------------------------------------------------------------------------
1 | // x
--------------------------------------------------------------------------------
/sample/test-workspace/folder/.bar/.foo:
--------------------------------------------------------------------------------
1 | foo
--------------------------------------------------------------------------------
/sample/test-workspace/hello.txt:
--------------------------------------------------------------------------------
1 | // hello
--------------------------------------------------------------------------------
/sample/test-workspace/world.txt:
--------------------------------------------------------------------------------
1 | // world
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": true,
3 | "printWidth": 120,
4 | "singleQuote": true
5 | }
6 |
--------------------------------------------------------------------------------
/tsconfig.tsbuildinfo:
--------------------------------------------------------------------------------
1 | {"fileNames":[],"fileInfos":[],"root":[],"options":{"composite":true},"version":"5.9.3"}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode-test
2 | .vscode-test-web/
3 | node_modules
4 | out
5 | sample/dist
6 | fs-provider/dist
7 | tsconfig.tsbuildinfo
8 |
--------------------------------------------------------------------------------
/sample/test-workspace/folder_with_utf_8_🧿/!#$%&'()+,-.0123456789;=@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{}~:
--------------------------------------------------------------------------------
1 | test_utf_8_🧿
--------------------------------------------------------------------------------
/sample/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // See http://go.microsoft.com/fwlink/?LinkId=827846
3 | // for the documentation about the extensions.json format
4 | "recommendations": [
5 | "dbaeumer.vscode-eslint",
6 | "eamodio.tsl-problem-matcher"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/fs-provider/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // See http://go.microsoft.com/fwlink/?LinkId=827846
3 | // for the documentation about the extensions.json format
4 | "recommendations": [
5 | "dbaeumer.vscode-eslint",
6 | "eamodio.tsl-problem-matcher"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/sample/tsconfig.runTest.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "ES2022",
5 | "outDir": "dist",
6 | "lib": [
7 | "ES2022"
8 | ],
9 | "rootDir": "src",
10 | "strict": true
11 | },
12 | "include": [
13 | "./src/web/test/runTest.ts"
14 | ]
15 | }
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .vscode
2 | .npmignore
3 | .editorconfig
4 | .eslint.config.js
5 | .prettierrc
6 | .github/
7 | build/
8 | src/
9 | sample/
10 | fs-provider/src/
11 | fs-provider/node_modules/
12 |
13 | .vscode-test-web/
14 | tsconfig.json
15 | tslint.json
16 | webpack.config.js
17 |
18 | **/*.js.map
19 | **/*.d.ts
20 | !out/server/index.d.ts
21 | *.tgz
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "npm",
6 | "type": "shell",
7 | "command": "npm",
8 | "args": [
9 | "run",
10 | "watch"
11 | ],
12 | "isBackground": true,
13 | "problemMatcher": "$tsc-watch",
14 | "group": "build"
15 | }
16 | ]
17 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "incremental": true,
4 | "composite": true
5 | },
6 | "files": [],
7 | "references": [
8 | {
9 | "path": "./src/server/tsconfig.json"
10 | },
11 | {
12 | "path": "./src/browser/tsconfig-esm.json"
13 | },
14 | {
15 | "path": "./src/browser/tsconfig-amd.json"
16 | }
17 | ]
18 | }
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: https://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Tab indentation
7 | [*]
8 | indent_style = tab
9 | trim_trailing_whitespace = true
10 |
11 | # The indent size used in the `package.json` file cannot be changed
12 | # https://github.com/npm/npm/pull/3180#issuecomment-16336516
13 | [{*.yml,*.yaml,package.json}]
14 | indent_style = space
15 | indent_size = 2
16 |
--------------------------------------------------------------------------------
/sample/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | "files.exclude": {
4 | "out": false // set this to true to hide the "out" folder with the compiled JS files
5 | },
6 | "search.exclude": {
7 | "out": true // set this to false to include "out" folder in search results
8 | },
9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts
10 | "typescript.tsc.autoDetect": "off"
11 | }
--------------------------------------------------------------------------------
/fs-provider/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | "files.exclude": {
4 | "out": false // set this to true to hide the "out" folder with the compiled JS files
5 | },
6 | "search.exclude": {
7 | "out": true // set this to false to include "out" folder in search results
8 | },
9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts
10 | "typescript.tsc.autoDetect": "off"
11 | }
--------------------------------------------------------------------------------
/src/server/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "module": "commonjs",
5 | "lib": [
6 | "ES2022"
7 | ],
8 | "outDir": "../../out/server",
9 | "declaration": true,
10 | "strict": true,
11 | "noImplicitAny": false,
12 | "noImplicitThis": true,
13 | "noUnusedLocals": true,
14 | "alwaysStrict": true,
15 | "skipLibCheck": true,
16 | "forceConsistentCasingInFileNames": true,
17 | "sourceMap": false,
18 | "newLine": "lf"
19 | }
20 | }
--------------------------------------------------------------------------------
/src/browser/tsconfig-amd.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "module": "AMD",
5 | "lib": [
6 | "ES2022",
7 | "DOM",
8 | ],
9 | "outDir": "../../out/browser/amd",
10 | "declaration": true,
11 | "strict": true,
12 | "noImplicitAny": false,
13 | "noImplicitThis": true,
14 | "noUnusedLocals": true,
15 | "alwaysStrict": true,
16 | "skipLibCheck": true,
17 | "forceConsistentCasingInFileNames": true,
18 | "sourceMap": false,
19 | "newLine": "lf",
20 | "removeComments": true
21 | }
22 | }
--------------------------------------------------------------------------------
/src/browser/tsconfig-esm.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "module": "ES2022",
5 | "lib": [
6 | "ES2022",
7 | "DOM",
8 | ],
9 | "outDir": "../../out/browser/esm",
10 | "declaration": true,
11 | "strict": true,
12 | "noImplicitAny": false,
13 | "noImplicitThis": true,
14 | "noUnusedLocals": true,
15 | "alwaysStrict": true,
16 | "skipLibCheck": true,
17 | "forceConsistentCasingInFileNames": true,
18 | "sourceMap": false,
19 | "newLine": "lf",
20 | "removeComments": true
21 | }
22 | }
--------------------------------------------------------------------------------
/sample/src/web/test/suite/extension.test.ts:
--------------------------------------------------------------------------------
1 | import * as assert from 'assert';
2 |
3 | // You can import and use all API from the 'vscode' module
4 | // as well as import your extension to test it
5 | import * as vscode from 'vscode';
6 | // import * as myExtension from '../../extension';
7 |
8 | suite('Web Extension Test Suite', () => {
9 | vscode.window.showInformationMessage('Start all tests.');
10 |
11 | test('Sample test', () => {
12 | assert.strictEqual(-1, [1, 2, 3].indexOf(5));
13 | assert.strictEqual(-1, [1, 2, 3].indexOf(0));
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/sample/README.md:
--------------------------------------------------------------------------------
1 |
2 |
vscode-test-web-sample
3 |
4 |
5 | Sample for using https://github.com/microsoft/vscode-test-web.
6 |
7 | Continuously tested with latest changes:
8 |
9 | - [Azure DevOps](https://dev.azure.com/vscode/vscode-test-web/_build?definitionId=15)
10 | - [Travis](https://travis-ci.org/github/microsoft/vscode-test-web)
11 |
12 | When making changes to `vscode-test-web` library, you should compile and run the tests in this sample project locally to make sure the tests can still run successfully.
13 |
14 | ```bash
15 | npm run install
16 | npm run compile
17 | npm run test
18 | ```
--------------------------------------------------------------------------------
/sample/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "ES2022",
5 | "lib": [
6 | "ES2022", "WebWorker"
7 | ],
8 | "rootDir": "src",
9 | "strict": true /* enable all strict type-checking options */
10 | /* Additional Checks */
11 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
12 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
13 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
14 | },
15 | "exclude": [
16 | "node_modules",
17 | ".vscode-test"
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/sample/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | // See https://go.microsoft.com/fwlink/?LinkId=733558
2 | // for the documentation about the tasks.json format
3 | {
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "type": "npm",
8 | "script": "compile-web",
9 | "group": {
10 | "kind": "build",
11 | "isDefault": true
12 | },
13 | "problemMatcher": [
14 | "$ts-webpack",
15 | "$tslint-webpack"
16 | ]
17 | },
18 | {
19 | "type": "npm",
20 | "script": "watch-web",
21 | "group": "build",
22 | "isBackground": true,
23 | "problemMatcher": [
24 | "$ts-webpack-watch",
25 | "$tslint-webpack-watch"
26 | ]
27 | }
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/fs-provider/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | // See https://go.microsoft.com/fwlink/?LinkId=733558
2 | // for the documentation about the tasks.json format
3 | {
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "type": "npm",
8 | "script": "compile-web",
9 | "group": {
10 | "kind": "build",
11 | "isDefault": true
12 | },
13 | "problemMatcher": [
14 | "$ts-webpack",
15 | "$tslint-webpack"
16 | ]
17 | },
18 | {
19 | "type": "npm",
20 | "script": "watch-web",
21 | "group": "build",
22 | "isBackground": true,
23 | "problemMatcher": [
24 | "$ts-webpack-watch",
25 | "$tslint-webpack-watch"
26 | ]
27 | }
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | "editor.insertSpaces": true,
4 | "files.eol": "\n",
5 | "files.trimTrailingWhitespace": true,
6 | "files.exclude": {
7 | "**/.git": true,
8 | "**/.DS_Store": true,
9 | "**/*.js": {
10 | "when": "$(basename).ts"
11 | }
12 | },
13 | "prettier.semi": true,
14 | "git.branchProtection": [
15 | "main"
16 | ],
17 | "git.branchProtectionPrompt": "alwaysCommitToNewBranch",
18 | "git.branchRandomName.enable": true,
19 | "githubPullRequests.assignCreated": "${user}",
20 | "githubPullRequests.defaultMergeMethod": "squash"
21 | }
--------------------------------------------------------------------------------
/fs-provider/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "ES2022",
5 | "outDir": "dist",
6 | "lib": [
7 | "ES2022", "WebWorker"
8 | ],
9 | "sourceMap": true,
10 | "rootDir": "src",
11 | "strict": true /* enable all strict type-checking options */
12 | /* Additional Checks */
13 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
14 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
15 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
16 | },
17 | "exclude": [
18 | "node_modules",
19 | ".vscode-test"
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/sample/src/web/test/suite/index.ts:
--------------------------------------------------------------------------------
1 | // imports mocha for the browser, defining the `mocha` global.
2 | require('mocha/mocha');
3 |
4 | export function run(): Promise {
5 |
6 | return new Promise((c, e) => {
7 | mocha.setup({
8 | ui: 'tdd',
9 | reporter: undefined
10 | });
11 |
12 | // bundles all files in the current directory matching `*.test`
13 | const importAll = (r: __WebpackModuleApi.RequireContext) => r.keys().forEach(r);
14 | importAll(require.context('.', true, /\.test$/));
15 |
16 | try {
17 | // Run the mocha test
18 | mocha.run(failures => {
19 | if (failures > 0) {
20 | e(new Error(`${failures} tests failed.`));
21 | } else {
22 | c();
23 | }
24 | });
25 | } catch (err) {
26 | console.error(err);
27 | e(err);
28 | }
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | on: [push]
2 |
3 | name: Tests
4 |
5 | permissions: {}
6 |
7 | jobs:
8 | build:
9 | strategy:
10 | matrix:
11 | os: [macos-latest, ubuntu-latest, windows-latest]
12 | runs-on: ${{ matrix.os }}
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v4
16 | with:
17 | persist-credentials: false
18 | - name: Install Node.js
19 | uses: actions/setup-node@v4
20 | with:
21 | node-version: 22.x
22 | - name: Install root project dependencies
23 | run: npm ci
24 | - name: Install extensions dependencies
25 | run: npm run install-extensions
26 | - name: Run tests
27 | uses: GabrielBB/xvfb-action@b706e4e27b14669b486812790492dc50ca16b465 # v1.7
28 | with:
29 | run: npm run sample-tests
30 |
--------------------------------------------------------------------------------
/sample/src/web/test/runTest.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path';
2 |
3 | import { runTests } from '../../../..';
4 |
5 | async function main() {
6 | try {
7 | // The folder containing the Extension Manifest package.json
8 | const extensionDevelopmentPath = path.resolve(__dirname, '../../../');
9 |
10 | // The path to module with the test runner and tests
11 | const extensionTestsPath = path.resolve(__dirname, './suite/index');
12 |
13 | const folderPath = path.resolve(__dirname, '../../../test-workspace');
14 |
15 | const attachArgName = '--waitForDebugger=';
16 | const waitForDebugger = process.argv.find(arg => arg.startsWith(attachArgName));
17 |
18 | // Start a web server that serves VSCode in a browser, run the tests
19 | await runTests({
20 | browserType: 'chromium',
21 | extensionDevelopmentPath,
22 | extensionTestsPath,
23 | folderPath,
24 | waitForDebugger: waitForDebugger ? Number(waitForDebugger.slice(attachArgName.length)) : undefined,
25 | });
26 | } catch (err) {
27 | console.error('Failed to run tests');
28 | process.exit(1);
29 | }
30 | }
31 |
32 | main();
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Microsoft Corporation. All rights reserved.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE
--------------------------------------------------------------------------------
/sample/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Web Extension in VS Code",
6 | "type": "pwa-extensionHost",
7 | "debugWebWorkerHost": true,
8 | "request": "launch",
9 | "args": [
10 | "--extensionDevelopmentPath=${workspaceFolder}",
11 | "--extensionDevelopmentKind=web ",
12 | "${workspaceFolder}/test-workspace"
13 | ],
14 | "outFiles": [
15 | "${workspaceFolder}/dist/web/**/*.js"
16 | ],
17 | "preLaunchTask": "npm: watch-web"
18 | },
19 | {
20 | "name": "Web Extension Tests in VS Code",
21 | "type": "extensionHost",
22 | "debugWebWorkerHost": true,
23 | "request": "launch",
24 | "args": [
25 | "--extensionDevelopmentPath=${workspaceFolder}",
26 | "--extensionDevelopmentKind=web",
27 | "--extensionTestsPath=${workspaceFolder}/dist/web/test/suite/index",
28 | "${workspaceFolder}/test-workspace"
29 | ],
30 | "outFiles": [
31 | "${workspaceFolder}/dist/web/**/*.js"
32 | ],
33 | "preLaunchTask": "npm: watch-web"
34 | }
35 | ]
36 | }
--------------------------------------------------------------------------------
/sample/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vscode-test-web-sample",
3 | "displayName": "vscode-test-web-sample",
4 | "description": "",
5 | "version": "0.0.1",
6 | "license": "MIT",
7 | "engines": {
8 | "vscode": "^1.72.0"
9 | },
10 | "categories": [
11 | "Other"
12 | ],
13 | "activationEvents": [
14 | "*"
15 | ],
16 | "browser": "./dist/web/extension.js",
17 | "contributes": {
18 | "commands": [
19 | {
20 | "command": "vscode-test-web-sample.helloWorld",
21 | "title": "Hello World"
22 | },
23 | {
24 | "command": "vscode-test-web-sample.findFiles",
25 | "title": "Find files"
26 | }
27 | ]
28 | },
29 | "scripts": {
30 | "test": "node ./dist/web/test/runTest.js",
31 | "pretest": "npm run compile-web && tsc -p tsconfig.runTest.json",
32 | "vscode:prepublish": "npm run package-web",
33 | "compile-web": "webpack",
34 | "watch-web": "webpack --watch",
35 | "package-web": "webpack --mode production --devtool hidden-source-map"
36 | },
37 | "devDependencies": {
38 | "@types/mocha": "10.0.10",
39 | "@types/vscode": "^1.94.0",
40 | "@types/webpack-env": "^1.18.8",
41 | "assert": "^2.1.0",
42 | "mocha": "^11.7.5",
43 | "process": "^0.11.10",
44 | "ts-loader": "^9.5.4",
45 | "typescript": "^5.9.3",
46 | "webpack": "^5.103.0",
47 | "webpack-cli": "^6.0.1"
48 | },
49 | "dependencies": {
50 | "@vscode/test-web": "file:.."
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/fs-provider/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vscode-test-web-fs",
3 | "private": true,
4 | "displayName": "vscode-test-web file system provider",
5 | "description": "Provides a file provider for web tests to access local files and folders.",
6 | "publisher": "vscode",
7 | "version": "0.0.1",
8 | "license": "MIT",
9 | "engines": {
10 | "vscode": "^1.72.0"
11 | },
12 | "categories": [
13 | "Other"
14 | ],
15 | "activationEvents": [
16 | "onFileSystem:vscode-test-web",
17 | "onSearch:vscode-test-web"
18 | ],
19 | "enabledApiProposals": [
20 | "fileSearchProvider",
21 | "textSearchProvider"
22 | ],
23 | "contributes": {
24 | "resourceLabelFormatters": [
25 | {
26 | "authority": "mount",
27 | "scheme": "vscode-test-web",
28 | "formatting": {
29 | "workspaceSuffix": "Test Files",
30 | "label": "${path}"
31 | }
32 | }
33 | ]
34 | },
35 | "browser": "./dist/fsExtensionMain.js",
36 | "scripts": {
37 | "vscode:prepublish": "npm run package-web",
38 | "compile-web": "webpack",
39 | "watch-web": "webpack --watch",
40 | "package-web": "webpack --mode production --devtool hidden-source-map"
41 | },
42 | "devDependencies": {
43 | "@types/vscode": "^1.94.0",
44 | "minimatch": "10.1.1",
45 | "path-browserify": "^1.0.1",
46 | "process": "^0.11.10",
47 | "request-light": "^0.8.0",
48 | "ts-loader": "^9.5.4",
49 | "vscode-uri": "^3.1.0",
50 | "webpack": "^5.103.0",
51 | "webpack-cli": "^6.0.1"
52 | },
53 | "dependencies": {
54 | "@vscode/test-web": "file:.."
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/server/main.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import createApp from './app';
7 |
8 | export interface IConfig {
9 | readonly extensionPaths: string[] | undefined;
10 | readonly extensionIds: GalleryExtensionInfo[] | undefined;
11 | readonly extensionDevelopmentPath: string | undefined;
12 | readonly extensionTestsPath: string | undefined;
13 | readonly build: Sources | Static | CDN;
14 | readonly folderUri: string | undefined;
15 | readonly folderMountPath: string | undefined;
16 | readonly printServerLog: boolean;
17 | readonly coi: boolean;
18 | readonly esm: boolean;
19 | }
20 |
21 | export interface GalleryExtensionInfo {
22 | readonly id: string;
23 | readonly preRelease?: boolean;
24 | }
25 |
26 | export interface Sources {
27 | readonly type: 'sources';
28 | readonly location: string;
29 | }
30 |
31 | export interface Static {
32 | readonly type: 'static';
33 | readonly location: string;
34 | readonly quality: 'stable' | 'insider';
35 | readonly version: string;
36 | }
37 |
38 | export interface CDN {
39 | readonly type: 'cdn';
40 | readonly uri: string;
41 | }
42 |
43 | export interface IServer {
44 | close(): void;
45 | }
46 |
47 | export async function runServer(host: string, port: number | undefined, config: IConfig): Promise {
48 | const app = await createApp(config);
49 | try {
50 | const server = app.listen(port, host);
51 | console.log(`Listening on http://${host}:${port}`);
52 | return server;
53 | } catch (e) {
54 | console.error(`Failed to listen to port ${port} on host ${host}`, e);
55 | throw e;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/views/workbench-esm.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
42 |
45 | {{WORKBENCH_MAIN}}
46 |
47 |
--------------------------------------------------------------------------------
/sample/webpack.config.js:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | //@ts-check
7 | 'use strict';
8 |
9 | //@ts-check
10 | /** @typedef {import('webpack').Configuration} WebpackConfig **/
11 |
12 | const path = require('path');
13 | const webpack = require('webpack');
14 |
15 | /** @type WebpackConfig */
16 | const webExtensionConfig = {
17 | mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production')
18 | target: 'webworker', // extensions run in a webworker context
19 | entry: {
20 | 'extension': './src/web/extension.ts',
21 | 'test/suite/index': './src/web/test/suite/index.ts'
22 | },
23 | output: {
24 | filename: '[name].js',
25 | path: path.join(__dirname, './dist/web'),
26 | libraryTarget: 'commonjs',
27 | devtoolModuleFilenameTemplate: "../../[resource-path]",
28 | },
29 | resolve: {
30 | mainFields: ['module', 'main'],
31 | extensions: ['.ts', '.js'], // support ts-files and js-files
32 | alias: {
33 | },
34 | fallback: {
35 | 'assert': require.resolve('assert')
36 | }
37 | },
38 | module: {
39 | rules: [{
40 | test: /\.ts$/,
41 | exclude: /node_modules/,
42 | use: [
43 | {
44 | loader: 'ts-loader'
45 | }
46 | ]
47 | }]
48 | },
49 | plugins: [
50 | new webpack.ProvidePlugin({
51 | process: 'process/browser',
52 | }),
53 | ],
54 | externals: {
55 | 'vscode': 'commonjs vscode', // ignored because it doesn't exist
56 | },
57 | performance: {
58 | hints: false
59 | },
60 | devtool: 'nosources-source-map'
61 | };
62 |
63 | module.exports = [ webExtensionConfig ];
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | ## 0.0.58
3 | * new option `--commit` to specify the build of VS Code to use. By default the latest build is used.
4 |
5 | ## 0.0.37
6 | * new option `--testRunnerDataDir` to set the temporary folder for storing the VS Code builds used for running the tests
7 |
8 | ## 0.0.28
9 | * new option `--coi` to enable cross origin isolation.
10 |
11 | ## 0.0.22
12 | * new option `--printServerLog` replacing `--hideServerLog`.
13 | * new option `--browser` replacing `--browserType`.
14 |
15 | ## 0.0.20
16 | * new option `--extensionId publisher.name[@prerelease]` to include one or more extensions.
17 |
18 | ## 0.0.18
19 | * new option `--browserType none` to start the server without opening a browser.
20 |
21 | ## 0.0.17
22 | * new options `--host` and `--port`: If provided runs the server from the given host and port.
23 | * new option `--verbose` to print out the browser console log.
24 |
25 | ## 0.0.16
26 | * new option `--sourcesPath`: If provided, runs the server from VS Code sources at the given location.
27 | * option `--version` is deprecated and replaced with `quality`. Supported values: `stable`, `insiders`. Instead of `sources` use `--insiders`.
28 |
29 | ## 0.0.14
30 | * new option `--extensionPath` : A path pointing to a folder containing additional extensions to include. Argument can be provided multiple times.
31 | * new option `--permission`: Permission granted to the opened browser: e.g. clipboard-read, clipboard-write. See full list of options [here](https://playwright.dev/docs/1.14/emulation#permissions). Argument can be provided multiple times.
32 | * new option `--hideServerLog`: If set, hides the server log. Defaults to true when an extensionTestsPath is provided, otherwise false.
33 | * close server when browser is closed
34 |
35 | ## 0.0.9
36 |
37 | * new option `folderPath`: A local folder to open VS Code on. The folder content will be available as a virtual file system and opened as workspace.
38 |
39 |
40 | ### 0.0.1 |
41 |
42 | - Initial version
43 |
44 |
45 |
--------------------------------------------------------------------------------
/sample/src/web/extension.ts:
--------------------------------------------------------------------------------
1 | // The module 'vscode' contains the VS Code extensibility API
2 | // Import the module and reference it with the alias vscode in your code below
3 | import * as vscode from 'vscode';
4 |
5 | // this method is called when your extension is activated
6 | // your extension is activated the very first time the command is executed
7 | export function activate(context: vscode.ExtensionContext) {
8 |
9 | // Use the console to output diagnostic information (console.log) and errors (console.error)
10 | // This line of code will only be executed once when your extension is activated
11 | console.log('Congratulations, your extension "vscode-test-web-sample" is now active in the web extension host!');
12 |
13 | // The command has been defined in the package.json file
14 | // Now provide the implementation of the command with registerCommand
15 | // The commandId parameter must match the command field in package.json
16 | let disposable = vscode.commands.registerCommand('vscode-test-web-sample.helloWorld', () => {
17 | // The code you place here will be executed every time your command is executed
18 |
19 | // Display a message box to the user
20 | vscode.window.showInformationMessage('Hello World from vscode-test-web-sample in a web extension host!');
21 | });
22 |
23 | context.subscriptions.push(disposable);
24 |
25 | let findFilesDisposable = vscode.commands.registerCommand('vscode-test-web-sample.findFiles', () => {
26 | vscode.window.showInputBox({ title: 'Enter a pattern', placeHolder: '**/*.md' })
27 | .then((pattern) => {
28 | return pattern ? vscode.workspace.findFiles(pattern) : undefined;
29 | })
30 | .then((results) => {
31 | if (!results) {
32 | return vscode.window.showErrorMessage('Find files returned undefined');
33 | }
34 | let summary = `Found:\n${results.map(uri => ` - ${uri.path}`).join('\n')}`;
35 | return vscode.window.showInformationMessage(summary);
36 | });
37 | });
38 |
39 | context.subscriptions.push(findFilesDisposable);
40 |
41 | }
42 |
43 | // this method is called when your extension is deactivated
44 | export function deactivate() { }
45 |
--------------------------------------------------------------------------------
/sample/src/web/test/suite/search.test.ts:
--------------------------------------------------------------------------------
1 | import * as assert from 'assert';
2 | import * as vscode from 'vscode';
3 |
4 | suite('Workspace search', () => {
5 | // tests findFiles operation against the current workspace folder
6 | // when running with `@vscode/test-web`, this will be a virtual file system, powered
7 | // by the vscoe-web-test file system provider
8 |
9 | const workspaceFolder = vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders[0];
10 | assert.ok(workspaceFolder, 'Expecting an open folder');
11 |
12 | const workspaceFolderUri = workspaceFolder.uri;
13 |
14 | function getUri(path: string): vscode.Uri {
15 | return vscode.Uri.joinPath(workspaceFolderUri, path);
16 | }
17 |
18 | async function assertEntries(path: string, expectedFiles: string[], expectedFolders: string[]) {
19 | const entrySorter = (e1: [string, vscode.FileType], e2: [string, vscode.FileType]) => {
20 | const d = e1[1] - e2[1];
21 | if (d === 0) {
22 | return e1[0].localeCompare(e2[0]);
23 | }
24 | return d;
25 | };
26 |
27 | let entries = await vscode.workspace.fs.readDirectory(getUri(path));
28 | entries = entries.sort(entrySorter);
29 |
30 | let expected = expectedFolders
31 | .map<[string, vscode.FileType]>((name) => [name, vscode.FileType.Directory])
32 | .concat(expectedFiles.map((name) => [name, vscode.FileType.File]))
33 | .sort(entrySorter);
34 |
35 | assert.deepStrictEqual(entries, expected);
36 | }
37 |
38 | async function assertFindsFiles(pattern: string, expectedFiles: string[]) {
39 | let entries = await vscode.workspace.findFiles(pattern);
40 | let foundFiles = entries.map((uri) => uri.path.substring(uri.path.lastIndexOf('/') + 1));
41 |
42 | assert.deepStrictEqual(foundFiles, expectedFiles);
43 | }
44 |
45 | // commented out because of https://github.com/microsoft/vscode/issues/227248
46 | test('Find files', async () => {
47 | debugger;
48 | await assertEntries('/folder', ['x.txt'], ['.bar']);
49 | await assertEntries('/folder/', ['x.txt'], ['.bar']);
50 | await assertEntries('/', ['hello.txt', 'world.txt'], ['folder', 'folder_with_utf_8_🧿']);
51 |
52 | await assertFindsFiles('**/*.txt', ['x.txt', 'hello.txt', 'world.txt']);
53 | });
54 | });
55 |
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import header from "@tony.ganchev/eslint-plugin-header";
7 | import tsParser from "@typescript-eslint/parser";
8 | import path from "node:path";
9 | import { fileURLToPath } from "node:url";
10 | import js from "@eslint/js";
11 | import { FlatCompat } from "@eslint/eslintrc";
12 |
13 | const __filename = fileURLToPath(import.meta.url);
14 | const __dirname = path.dirname(__filename);
15 | const compat = new FlatCompat({
16 | baseDirectory: __dirname,
17 | recommendedConfig: js.configs.recommended,
18 | allConfig: js.configs.all
19 | });
20 |
21 | export default [{
22 | ignores: ["**/*.d.ts", "**/*.test.ts", "**/*.js", "sample/**/*.*"],
23 | }, ...compat.extends("plugin:@typescript-eslint/recommended"), {
24 | plugins: {
25 | header,
26 | },
27 |
28 | languageOptions: {
29 | parser: tsParser,
30 | ecmaVersion: 2018,
31 | sourceType: "module",
32 | },
33 |
34 | rules: {
35 | "@typescript-eslint/no-use-before-define": "off",
36 | "@typescript-eslint/explicit-function-return-type": "off",
37 | "@typescript-eslint/no-explicit-any": "off",
38 | "@typescript-eslint/no-non-null-assertion": "off",
39 | "@typescript-eslint/explicit-module-boundary-types": "off",
40 | "@typescript-eslint/no-unused-vars": "off",
41 | "@typescript-eslint/no-var-requires": "error",
42 |
43 | "header/header": [
44 | 2,
45 | "block",
46 | [
47 | "---------------------------------------------------------------------------------------------",
48 | " * Copyright (c) Microsoft Corporation. All rights reserved.",
49 | " * Licensed under the MIT License. See License.txt in the project root for license information.",
50 | " *--------------------------------------------------------------------------------------------"],
51 | ],
52 | },
53 | }];
--------------------------------------------------------------------------------
/fs-provider/webpack.config.js:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | /** @typedef {import('webpack').Configuration} WebpackConfig **/
7 |
8 | const path = require('path');
9 | const webpack = require('webpack');
10 |
11 | /** @type WebpackConfig */
12 | const webConfig = {
13 | context: __dirname,
14 | mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production')
15 | target: 'webworker', // web extensions run in a webworker context
16 | entry: {
17 | 'fsExtensionMain': './src/fsExtensionMain.ts', // source of the web extension main file
18 | },
19 | output: {
20 | filename: '[name].js',
21 | path: path.join(__dirname, './dist'),
22 | libraryTarget: 'commonjs',
23 | devtoolModuleFilenameTemplate: '../[resource-path]'
24 | },
25 | resolve: {
26 | mainFields: ['browser', 'module', 'main'], // look for `browser` entry point in imported node modules
27 | extensions: ['.ts', '.js'], // support ts-files and js-files
28 | alias: {
29 | // provides alternate implementation for node module and source files
30 | },
31 | fallback: {
32 | // Webpack 5 no longer polyfills Node.js core modules automatically.
33 | // see https://webpack.js.org/configuration/resolve/#resolvefallback
34 | // for the list of Node.js core module polyfills.
35 | },
36 | },
37 | module: {
38 | rules: [
39 | {
40 | test: /\.ts$/,
41 | exclude: /node_modules/,
42 | use: [
43 | {
44 | loader: 'ts-loader',
45 | },
46 | ],
47 | },
48 | ],
49 | },
50 | plugins: [
51 | new webpack.ProvidePlugin({
52 | process: 'process/browser', // provide a shim for the global `process` variable
53 | }),
54 | ],
55 | externals: {
56 | vscode: 'commonjs vscode', // ignored because it doesn't exist
57 | },
58 | performance: {
59 | hints: false,
60 | },
61 | devtool: 'nosources-source-map', // create a source map that points to the original source file
62 | };
63 |
64 | module.exports = [webConfig];
65 |
--------------------------------------------------------------------------------
/views/workbench.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
60 |
63 | {{WORKBENCH_MAIN}}
64 |
65 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vscode/test-web",
3 | "version": "0.0.77",
4 | "scripts": {
5 | "install-extensions": "npm i --prefix=fs-provider && npm i --prefix=sample",
6 | "compile": "tsc -b ./ && npm run compile-fs-provider",
7 | "watch": "tsc -b -w ./",
8 | "prepack": "npm run compile",
9 | "test": "eslint src && tsc --noEmit",
10 | "preversion": "npm test",
11 | "postversion": "git push && git push --tags",
12 | "compile-fs-provider": "npm run --prefix=fs-provider compile-web",
13 | "compile-sample": "npm run --prefix=sample compile-web",
14 | "sample": "npm run compile && npm run compile-sample && node . --extensionDevelopmentPath=sample sample/test-workspace",
15 | "sample-tests": "npm run compile && npm run compile-sample && node . --extensionDevelopmentPath=sample --extensionTestsPath=sample/dist/web/test/suite/index.js --headless=true sample/test-workspace",
16 | "empty": "npm run compile && node ."
17 | },
18 | "main": "./out/server/index.js",
19 | "bin": {
20 | "vscode-test-web": "./out/server/index.js"
21 | },
22 | "engines": {
23 | "node": ">=20"
24 | },
25 | "dependencies": {
26 | "@koa/cors": "^5.0.0",
27 | "@koa/router": "^15.0.0",
28 | "@playwright/browser-chromium": "^1.57.0",
29 | "tinyglobby": "^0.2.15",
30 | "gunzip-maybe": "^1.4.2",
31 | "http-proxy-agent": "^7.0.2",
32 | "https-proxy-agent": "^7.0.6",
33 | "koa": "^3.1.1",
34 | "koa-morgan": "^1.0.1",
35 | "koa-mount": "^4.2.0",
36 | "koa-static": "^5.0.0",
37 | "minimist": "^1.2.8",
38 | "playwright": "^1.57.0",
39 | "tar-fs": "^3.1.1",
40 | "vscode-uri": "^3.1.0"
41 | },
42 | "devDependencies": {
43 | "@eslint/eslintrc": "^3.3.3",
44 | "@eslint/js": "^9.39.1",
45 | "@types/gunzip-maybe": "^1.4.3",
46 | "@types/koa": "^3.0.1",
47 | "@types/koa__router": "^12.0.5",
48 | "@types/koa-morgan": "^1.0.9",
49 | "@types/koa-mount": "^4.0.5",
50 | "@types/koa-static": "^4.0.4",
51 | "@types/minimist": "^1.2.5",
52 | "@types/node": "^20.16.13",
53 | "@types/tar-fs": "^2.0.4",
54 | "@typescript-eslint/eslint-plugin": "^8.48.1",
55 | "@typescript-eslint/parser": "^8.48.1",
56 | "eslint": "^9.39.1",
57 | "@tony.ganchev/eslint-plugin-header": "^3.1.11",
58 | "typescript": "^5.9.3"
59 | },
60 | "license": "MIT",
61 | "author": "Visual Studio Code Team",
62 | "repository": {
63 | "type": "git",
64 | "url": "https://github.com/microsoft/vscode-test-web.git"
65 | },
66 | "bugs": {
67 | "url": "https://github.com/microsoft/vscode-test-web/issues"
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "pwa-node",
9 | "request": "launch",
10 | "name": "Launch sample test",
11 | "outputCapture": "std",
12 | "program": "${workspaceFolder}/sample/dist/web/test/runTest.js",
13 | "args": ["--waitForDebugger=9229"],
14 | "cascadeTerminateToConfigurations": ["Launch sample test"],
15 | "presentation": {
16 | "hidden": true,
17 | }
18 | },
19 | {
20 | "type": "pwa-chrome",
21 | "request": "attach",
22 | "name": "Attach sample test",
23 | "skipFiles": [
24 | "/**"
25 | ],
26 | "port": 9229,
27 | "timeout": 30000, // give it time to download vscode if needed
28 | "resolveSourceMapLocations": [
29 | "!**/vs/**", // exclude core vscode sources
30 | "!**/static/build/extensions/**", // exclude built-in extensions
31 | ],
32 | "webRoot": "${workspaceFolder}/sample", // only needed since sample is in a subdir
33 | "presentation": {
34 | "hidden": true,
35 | }
36 | },
37 | {
38 | "type": "pwa-node",
39 | "request": "launch",
40 | "name": "Run in Chromium",
41 | "skipFiles": [
42 | "/**"
43 | ],
44 | "program": "${workspaceFolder}/out/server/index.js",
45 | "args": [
46 | "--browserType=chromium",
47 | "--extensionDevelopmentPath=${workspaceFolder}/sample",
48 | "sample/test-workspace"
49 | ],
50 | "outFiles": [
51 | "${workspaceFolder}/out/**/*.js"
52 | ]
53 | },
54 | {
55 | "type": "pwa-node",
56 | "request": "launch",
57 | "name": "Run Test in Chromium",
58 | "skipFiles": [
59 | "/**"
60 | ],
61 | "program": "${workspaceFolder}/out/server/index.js",
62 | "args": [
63 | "--browserType=chromium",
64 | "--extensionDevelopmentPath=${workspaceFolder}/sample",
65 | "--extensionTestsPath=${workspaceFolder}/sample/dist/web/test/suite/index.js"
66 | ],
67 | "outFiles": [
68 | "${workspaceFolder}/out/**/*.js"
69 | ]
70 | }
71 | ],
72 | "compounds": [
73 | {
74 | "name": "Debug Sample Test",
75 | "configurations": [
76 | "Launch sample test",
77 | "Attach sample test"
78 | ]
79 | }
80 | ]
81 | }
82 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Security
4 |
5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
6 |
7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below.
8 |
9 | ## Reporting Security Issues
10 |
11 | **Please do not report security vulnerabilities through public GitHub issues.**
12 |
13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).
14 |
15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).
16 |
17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc).
18 |
19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
20 |
21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
22 | * Full paths of source file(s) related to the manifestation of the issue
23 | * The location of the affected source code (tag/branch/commit or direct URL)
24 | * Any special configuration required to reproduce the issue
25 | * Step-by-step instructions to reproduce the issue
26 | * Proof-of-concept or exploit code (if possible)
27 | * Impact of the issue, including how an attacker might exploit the issue
28 |
29 | This information will help us triage your report more quickly.
30 |
31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs.
32 |
33 | ## Preferred Languages
34 |
35 | We prefer all communications to be in English.
36 |
37 | ## Policy
38 |
39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).
40 |
41 |
42 |
--------------------------------------------------------------------------------
/src/server/mounts.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { IConfig } from './main';
7 |
8 | import * as Koa from 'koa';
9 | import * as kstatic from 'koa-static';
10 | import * as kmount from 'koa-mount';
11 | import Router, { RouterMiddleware } from '@koa/router';
12 |
13 | import { Dirent, promises as fs, Stats } from 'fs';
14 | import * as path from 'path';
15 |
16 | const mountPrefix = '/static/mount';
17 | export const fsProviderExtensionPrefix = '/static/extensions/fs';
18 | export const fsProviderFolderUri = 'vscode-test-web://mount/';
19 |
20 | export function configureMounts(config: IConfig, app: Koa): void {
21 | const folderMountPath = config.folderMountPath;
22 | if (folderMountPath) {
23 | console.log(`Serving local content ${folderMountPath} at ${mountPrefix}`);
24 | app.use(fileOps(mountPrefix, folderMountPath));
25 | app.use(kmount(mountPrefix, kstatic(folderMountPath, { hidden: true })));
26 |
27 | app.use(kmount(fsProviderExtensionPrefix, kstatic(path.join(__dirname, '../../fs-provider'), { hidden: true })));
28 | }
29 | }
30 |
31 | function fileOps(mountPrefix: string, folderMountPath: string): RouterMiddleware {
32 | const router = new Router();
33 | router.get(`${mountPrefix}{/*path}`, async (ctx, next) => {
34 | if (ctx.query.stat !== undefined) {
35 | const p = path.join(folderMountPath, decodeURIComponent(ctx.path.substring(mountPrefix.length)));
36 | try {
37 | const stats = await fs.stat(p);
38 | ctx.body = {
39 | type: getFileType(stats),
40 | ctime: stats.ctime.getTime(),
41 | mtime: stats.mtime.getTime(),
42 | size: stats.size,
43 | };
44 | } catch (e) {
45 | ctx.body = { error: (e as NodeJS.ErrnoException).code };
46 | }
47 | } else if (ctx.query.readdir !== undefined) {
48 | const p = path.join(folderMountPath, decodeURIComponent(ctx.path.substring(mountPrefix.length)));
49 | try {
50 | const entries = await fs.readdir(p, { withFileTypes: true });
51 | ctx.body = entries.map((d) => ({ name: d.name, type: getFileType(d) }));
52 | } catch (e) {
53 | ctx.body = { error: (e as NodeJS.ErrnoException).code };
54 | }
55 | } else {
56 | return next();
57 | }
58 | });
59 | return router.routes();
60 | }
61 |
62 | enum FileType {
63 | Unknown = 0,
64 | File = 1,
65 | Directory = 2,
66 | SymbolicLink = 64,
67 | }
68 |
69 | function getFileType(stats: Stats | Dirent) {
70 | if (stats.isFile()) {
71 | return FileType.File;
72 | } else if (stats.isDirectory()) {
73 | return FileType.Directory;
74 | } else if (stats.isSymbolicLink()) {
75 | return FileType.SymbolicLink;
76 | }
77 | return FileType.Unknown;
78 | }
79 |
--------------------------------------------------------------------------------
/fs-provider/vscode.proposed.fileSearchProvider.d.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | declare module 'vscode' {
7 |
8 | // https://github.com/microsoft/vscode/issues/73524
9 |
10 | /**
11 | * The parameters of a query for file search.
12 | */
13 | export interface FileSearchQuery {
14 | /**
15 | * The search pattern to match against file paths.
16 | * To be correctly interpreted by Quick Open, this is interpreted in a relaxed way. The picker will apply its own highlighting and scoring on the results.
17 | *
18 | * Tips for matching in Quick Open:
19 | * With the pattern, the picker will use the file name and file paths to score each entry. The score will determine the ordering and filtering.
20 | * The scoring prioritizes prefix and substring matching. Then, it checks and it checks whether the pattern's letters appear in the same order as in the target (file name and path).
21 | * If a file does not match at all using our criteria, it will be omitted from Quick Open.
22 | */
23 | pattern: string;
24 | }
25 |
26 | /**
27 | * Options that apply to file search.
28 | */
29 | export interface FileSearchOptions extends SearchOptions {
30 | /**
31 | * The maximum number of results to be returned.
32 | */
33 | maxResults?: number;
34 |
35 | /**
36 | * A CancellationToken that represents the session for this search query. If the provider chooses to, this object can be used as the key for a cache,
37 | * and searches with the same session object can search the same cache. When the token is cancelled, the session is complete and the cache can be cleared.
38 | */
39 | session?: CancellationToken;
40 | }
41 |
42 | /**
43 | * A FileSearchProvider provides search results for files in the given folder that match a query string. It can be invoked by quickopen or other extensions.
44 | *
45 | * A FileSearchProvider is the more powerful of two ways to implement file search in the editor. Use a FileSearchProvider if you wish to search within a folder for
46 | * all files that match the user's query.
47 | *
48 | * The FileSearchProvider will be invoked on every keypress in quickopen. When `workspace.findFiles` is called, it will be invoked with an empty query string,
49 | * and in that case, every file in the folder should be returned.
50 | */
51 | export interface FileSearchProvider {
52 | /**
53 | * Provide the set of files that match a certain file path pattern.
54 | * @param query The parameters for this query.
55 | * @param options A set of options to consider while searching files.
56 | * @param token A cancellation token.
57 | */
58 | provideFileSearchResults(query: FileSearchQuery, options: FileSearchOptions, token: CancellationToken): ProviderResult;
59 | }
60 |
61 | export namespace workspace {
62 | /**
63 | * Register a search provider.
64 | *
65 | * Only one provider can be registered per scheme.
66 | *
67 | * @param scheme The provider will be invoked for workspace folders that have this file scheme.
68 | * @param provider The provider.
69 | * @return A {@link Disposable} that unregisters this provider when being disposed.
70 | */
71 | export function registerFileSearchProvider(scheme: string, provider: FileSearchProvider): Disposable;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/server/extensions.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { promises as fs } from 'fs';
7 | import * as path from 'path';
8 | import { fileExists } from './download';
9 |
10 | export interface URIComponents {
11 | scheme: string;
12 | authority: string;
13 | path: string;
14 | }
15 |
16 | export async function scanForExtensions(
17 | rootPath: string,
18 | serverURI: URIComponents
19 | ): Promise {
20 | const result: URIComponents[] = [];
21 | async function getExtension(relativePosixFolderPath: string): Promise {
22 | try {
23 | const packageJSONPath = path.join(rootPath, relativePosixFolderPath, 'package.json');
24 | if ((await fs.stat(packageJSONPath)).isFile()) {
25 | return {
26 | scheme: serverURI.scheme,
27 | authority: serverURI.authority,
28 | path: path.posix.join(serverURI.path, relativePosixFolderPath),
29 | };
30 | }
31 | } catch {
32 | return undefined;
33 | }
34 | }
35 |
36 | async function processFolder(relativePosixFolderPath: string) {
37 | const extension = await getExtension(relativePosixFolderPath);
38 | if (extension) {
39 | result.push(extension);
40 | } else {
41 | const folderPath = path.join(rootPath, relativePosixFolderPath);
42 | const entries = await fs.readdir(folderPath, { withFileTypes: true });
43 | for (const entry of entries) {
44 | if (entry.isDirectory() && entry.name.charAt(0) !== '.') {
45 | await processFolder(path.posix.join(relativePosixFolderPath, entry.name));
46 | }
47 | }
48 | }
49 | }
50 |
51 | await processFolder('');
52 | return result;
53 | }
54 |
55 | /** running from VS Code sources */
56 |
57 | export interface IScannedBuiltinExtension {
58 | extensionPath: string; // name of the folder
59 | packageJSON: any;
60 | packageNLS?: any;
61 | readmePath?: string;
62 | changelogPath?: string;
63 | }
64 |
65 | export const prebuiltExtensionsLocation = '.build/builtInExtensions';
66 |
67 | export async function getScannedBuiltinExtensions(vsCodeDevLocation: string): Promise {
68 | // use the build utility as to not duplicate the code
69 | const extensionsUtil = await getExtensionsUtil(vsCodeDevLocation);
70 |
71 | const localExtensions : IScannedBuiltinExtension[] = extensionsUtil.scanBuiltinExtensions(path.join(vsCodeDevLocation, 'extensions'));
72 | const prebuiltExtensions : IScannedBuiltinExtension[] = extensionsUtil.scanBuiltinExtensions(path.join(vsCodeDevLocation, prebuiltExtensionsLocation));
73 | for (const ext of localExtensions) {
74 | let browserMain: string | undefined = ext.packageJSON.browser;
75 | if (browserMain) {
76 | if (!browserMain.endsWith('.js')) {
77 | browserMain = browserMain + '.js';
78 | }
79 | const browserMainLocation = path.join(vsCodeDevLocation, 'extensions', ext.extensionPath, browserMain);
80 | if (!(await fileExists(browserMainLocation))) {
81 | console.log(`${browserMainLocation} not found. Make sure all extensions are compiled (use 'npm run watch-web').`);
82 | }
83 | }
84 | }
85 | return localExtensions.concat(prebuiltExtensions);
86 | }
87 |
88 | async function getExtensionsUtil(vsCodeDevLocation: string) {
89 | const base = path.join(vsCodeDevLocation, 'build', 'lib');
90 | try {
91 | return await import(path.join(base, 'extensions.ts'));
92 | } catch {
93 | return await import(path.join(base, 'extensions.js'));
94 | }
95 | }
96 |
97 |
--------------------------------------------------------------------------------
/src/server/app.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { ReadStream } from 'fs';
7 | import * as Koa from 'koa';
8 | import * as morgan from 'koa-morgan';
9 | import * as kstatic from 'koa-static';
10 | import * as kmount from 'koa-mount';
11 | import * as cors from '@koa/cors';
12 | import { basename, join } from 'path';
13 | import { IConfig } from './main';
14 | import workbench from './workbench';
15 | import { configureMounts } from './mounts';
16 | import { prebuiltExtensionsLocation } from './extensions';
17 |
18 | export default async function createApp(config: IConfig): Promise {
19 | const app = new Koa();
20 |
21 | app.use(morgan('dev', { skip: (req, res) => !config.printServerLog && (res.statusCode >= 200 && res.statusCode < 300) }));
22 |
23 | // CORS
24 | app.use(
25 | cors({
26 | allowMethods: ['GET'],
27 | credentials: true,
28 | origin: (ctx: Koa.Context) => {
29 | const origin = ctx.get('Origin');
30 | if (
31 | /^https:\/\/[^.]+\.vscode-cdn\.net$/.test(origin) || // needed for the webviewContent
32 | /^https:\/\/[^.]+\.vscode-webview\.net$/.test(origin) ||
33 | new RegExp(`^${ctx.protocol}://[^.]+\\.${ctx.host}$`).test(origin) // match subdomains of localhost
34 | ) {
35 | return origin;
36 | }
37 |
38 | return undefined as any;
39 | },
40 | })
41 | );
42 |
43 | if (config.build.type !== 'sources' && config.build.type !== 'static') {
44 | // CSP: frame-ancestors
45 | app.use((ctx, next) => {
46 | ctx.set('Content-Security-Policy', `frame-ancestors 'none'`);
47 | return next();
48 | });
49 | }
50 |
51 | // COI
52 | app.use((ctx, next) => {
53 | // set COOP/COEP depending on vscode-coi-flags
54 | const value = ctx.query['vscode-coi'];
55 | if (value === '1') {
56 | ctx.set('Cross-Origin-Opener-Policy', 'same-origin');
57 | } else if (value === '2') {
58 | ctx.set('Cross-Origin-Embedder-Policy', 'require-corp');
59 | } else if (value === '3' || value === '') {
60 | ctx.set('Cross-Origin-Opener-Policy', 'same-origin');
61 | ctx.set('Cross-Origin-Embedder-Policy', 'require-corp');
62 | }
63 |
64 | // set CORP on all resources
65 | ctx.set('Cross-Origin-Resource-Policy', 'cross-origin')
66 | return next()
67 | })
68 |
69 | // shift the line numbers of source maps in extensions by 2 as the content is wrapped by an anonymous function
70 | app.use(async (ctx, next) => {
71 | await next();
72 | if (ctx.status === 200 && ctx.path.match(/\/(dev)?extensions\/.*\.js\.map$/) && ctx.body instanceof ReadStream) {
73 | // we know it's a ReadStream as that's what kstatic uses
74 | const chunks: Buffer[] = [];
75 | for await (const chunk of ctx.body) {
76 | chunks.push(Buffer.from(chunk));
77 | }
78 | const bodyContent = Buffer.concat(chunks).toString("utf-8");
79 | ctx.response.body = `{"version":3,"file":"${basename(ctx.path)}","sections":[{"offset":{"line":2,"column":0},"map":${bodyContent} }]}`;
80 | }
81 | });
82 |
83 | const serveOptions: kstatic.Options = { hidden: true };
84 |
85 | if (config.extensionDevelopmentPath) {
86 | console.log('Serving dev extensions from ' + config.extensionDevelopmentPath);
87 | app.use(kmount('/static/devextensions', kstatic(config.extensionDevelopmentPath, serveOptions)));
88 | }
89 |
90 | if (config.build.type === 'static') {
91 | app.use(kmount('/static/build', kstatic(config.build.location, serveOptions)));
92 | } else if (config.build.type === 'sources') {
93 | console.log('Serving VS Code sources from ' + config.build.location);
94 | app.use(kmount('/static/sources', kstatic(config.build.location, serveOptions)));
95 | app.use(kmount('/static/sources', kstatic(join(config.build.location, 'resources', 'server'), serveOptions))); // for manifest.json, favicon and code icons.
96 |
97 | // built-in extension are at 'extensions` as well as prebuilt extensions downloaded from the marketplace
98 | app.use(kmount(`/static/sources/extensions`, kstatic(join(config.build.location, prebuiltExtensionsLocation), serveOptions)));
99 | }
100 |
101 | configureMounts(config, app);
102 |
103 | if (config.extensionPaths) {
104 | config.extensionPaths.forEach((extensionPath, index) => {
105 | console.log('Serving additional built-in extensions from ' + extensionPath);
106 | app.use(kmount(`/static/extensions/${index}`, kstatic(extensionPath, serveOptions)));
107 | });
108 | }
109 |
110 | app.use(workbench(config));
111 |
112 | return app;
113 | }
114 |
--------------------------------------------------------------------------------
/fs-provider/src/fsExtensionMain.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { xhr } from 'request-light';
7 | import { Uri, FileStat, FileType, workspace, ExtensionContext, FileSystemError } from 'vscode';
8 | import { Entry, MemFileSystemProvider, File, Directory } from './fsProvider';
9 |
10 | const SCHEME = 'vscode-test-web';
11 |
12 | export function activate(context: ExtensionContext) {
13 | const serverUri = context.extensionUri.with({ path: '/static/mount', query: undefined });
14 | const serverBackedRootDirectory = new ServerBackedDirectory(serverUri, [], '');
15 |
16 | const memFsProvider = new MemFileSystemProvider(SCHEME, serverBackedRootDirectory, context.extensionUri);
17 | const disposable = workspace.registerFileSystemProvider(SCHEME, memFsProvider);
18 | context.subscriptions.push(disposable);
19 |
20 | const searchDisposable = workspace.registerFileSearchProvider(SCHEME, memFsProvider);
21 | context.subscriptions.push(searchDisposable);
22 |
23 | console.log(`vscode-test-web-support fs provider registers for ${SCHEME}, initial content from ${serverUri.toString(/*skipEncoding*/ true)}`);
24 | }
25 |
26 | class ServerBackedFile implements File {
27 | readonly type = FileType.File;
28 | private _stats: Promise | undefined;
29 | private _content: Promise | undefined;
30 | constructor(private readonly _serverRoot: Uri, public pathSegments: readonly string[], public name: string) {
31 | }
32 | get stats(): Promise {
33 | if (this._stats === undefined) {
34 | this._stats = getStats(this._serverRoot, this.pathSegments);
35 | }
36 | return this._stats;
37 | }
38 | set stats(stats: Promise) {
39 | this._stats = stats;
40 | }
41 | get content(): Promise {
42 | if (this._content === undefined) {
43 | this._content = getContent(this._serverRoot, this.pathSegments);
44 | }
45 | return this._content;
46 | }
47 | set content(content: Promise) {
48 | this._content = content;
49 | }
50 | }
51 |
52 | class ServerBackedDirectory implements Directory {
53 | readonly type = FileType.Directory;
54 | private _stats: Promise | undefined;
55 | private _entries: Promise