This commit is contained in:
Коммит
0a0f80700d
|
@ -0,0 +1,15 @@
|
|||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Tab indentation
|
||||
[*]
|
||||
indent_style = tab
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
# The indent size used in the `package.json` file cannot be changed
|
||||
# https://github.com/npm/npm/pull/3180#issuecomment-16336516
|
||||
[{*.yml,*.yaml,package.json}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
|
@ -0,0 +1,26 @@
|
|||
module.exports = {
|
||||
ignorePatterns: ['**/*.d.ts', '**/*.test.ts', '**/*.js', 'sample/**/*.*'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
extends: ['plugin:@typescript-eslint/recommended'],
|
||||
plugins: ['header'],
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
|
||||
sourceType: 'module', // Allows for the use of imports
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/no-use-before-define': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'header/header': [
|
||||
'error',
|
||||
'block',
|
||||
`---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------`,
|
||||
],
|
||||
// Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
|
||||
// e.g. "@typescript-eslint/explicit-function-return-type": "off",
|
||||
},
|
||||
};
|
|
@ -0,0 +1,28 @@
|
|||
on: [push]
|
||||
|
||||
name: Tests
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
- name: Install root project dependencies
|
||||
run: yarn
|
||||
- name: Buold root project
|
||||
run: yarn compile
|
||||
- name: Install sample project dependencies
|
||||
run: yarn
|
||||
working-directory: ./sample
|
||||
- name: Run tests
|
||||
uses: GabrielBB/xvfb-action@v1.0
|
||||
with:
|
||||
run: yarn --cwd ./sample test
|
|
@ -0,0 +1,7 @@
|
|||
.vscode-test
|
||||
.vscode-test-web/
|
||||
node_modules
|
||||
|
||||
out
|
||||
|
||||
sample/dist
|
|
@ -0,0 +1,16 @@
|
|||
.vscode
|
||||
.npmignore
|
||||
.editorconfig
|
||||
.eslintrc.js
|
||||
.prettierrc
|
||||
.github/
|
||||
src/
|
||||
sample/
|
||||
.vscode-test-web/
|
||||
tsconfig.json
|
||||
tslint.json
|
||||
|
||||
**/*.js.map
|
||||
**/*.d.ts
|
||||
!out/index.d.ts
|
||||
*.tgz
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"semi": true,
|
||||
"printWidth": 120,
|
||||
"singleQuote": true
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "pwa-node",
|
||||
"request": "launch",
|
||||
"name": "Launch sample test",
|
||||
"outputCapture": "std",
|
||||
"program": "${workspaceFolder}/sample/dist/web/test/runTest.js",
|
||||
"args": ["--waitForDebugger=9229"],
|
||||
"cascadeTerminateToConfigurations": ["Launch sample test"],
|
||||
"presentation": {
|
||||
"hidden": true,
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "pwa-chrome",
|
||||
"request": "attach",
|
||||
"name": "Attach sample test",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"port": 9229,
|
||||
"timeout": 30000, // give it time to download vscode if needed
|
||||
"resolveSourceMapLocations": [
|
||||
"!**/vs/**", // exclude core vscode sources
|
||||
"!**/static/build/extensions/**", // exclude built-in extensions
|
||||
],
|
||||
"webRoot": "${workspaceFolder}/sample", // only needed since sample is in a subdir
|
||||
"presentation": {
|
||||
"hidden": true,
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "pwa-node",
|
||||
"request": "launch",
|
||||
"name": "Run in Chromium",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"program": "${workspaceFolder}/out/index.js",
|
||||
"args": [
|
||||
"--browserType=chromium",
|
||||
"--extensionDevelopmentPath=${workspaceFolder}/sample"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/out/**/*.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "pwa-node",
|
||||
"request": "launch",
|
||||
"name": "Run Test in Chromium",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"program": "${workspaceFolder}/out/index.js",
|
||||
"args": [
|
||||
"--browserType=chromium",
|
||||
"--extensionDevelopmentPath=${workspaceFolder}/sample",
|
||||
"--extensionTestsPath=${workspaceFolder}/sample/dist/web/test/suite/index.js"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/out/**/*.js"
|
||||
]
|
||||
}
|
||||
],
|
||||
"compounds": [
|
||||
{
|
||||
"name": "Debug Sample Test",
|
||||
"configurations": [
|
||||
"Launch sample test",
|
||||
"Attach sample test"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
// Place your settings in this file to overwrite default and user settings.
|
||||
{
|
||||
"editor.insertSpaces": true,
|
||||
"files.eol": "\n",
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.DS_Store": true,
|
||||
"**/*.js": {
|
||||
"when": "$(basename).ts"
|
||||
}
|
||||
},
|
||||
"prettier.semi": true
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "npm",
|
||||
"type": "shell",
|
||||
"command": "npm",
|
||||
"args": [
|
||||
"run",
|
||||
"watch"
|
||||
],
|
||||
"isBackground": true,
|
||||
"problemMatcher": "$tsc-watch",
|
||||
"group": "build"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
# Changelog
|
||||
|
||||
### 0.0.1 |
|
||||
|
||||
- Initial version
|
||||
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE
|
|
@ -0,0 +1,84 @@
|
|||
# vscode-test-web
|
||||
|
||||
<!-- ![Test Status Badge](https://github.com/microsoft/vscode-test-web/workflows/Tests/badge.svg) -->
|
||||
|
||||
This module helps testing VS Code web extensions.
|
||||
|
||||
## Usage
|
||||
|
||||
Via command line:
|
||||
|
||||
Test web extension in browser:
|
||||
|
||||
```sh
|
||||
vscode-test-web --browserType=webkit --extensionDevelopmentPath=$extensionLocation
|
||||
```
|
||||
|
||||
Run web extension tests:
|
||||
|
||||
```sh
|
||||
vscode-test-web --browserType=webkit --extensionDevelopmentPath=$extensionLocation --extensionTestsPath=$extensionLocation/dist/web/test/suite/index.js
|
||||
```
|
||||
|
||||
|
||||
Via API:
|
||||
|
||||
```ts
|
||||
async function go() {
|
||||
try {
|
||||
// The folder containing the Extension Manifest package.json
|
||||
const extensionDevelopmentPath = path.resolve(__dirname, '../../../');
|
||||
|
||||
// The path to module with the test runner and tests
|
||||
const extensionTestsPath = path.resolve(__dirname, './suite/index');
|
||||
|
||||
// Start a web server that serves VSCode in a browser, run the tests
|
||||
await runTests({ browserType: 'chromium', extensionDevelopmentPath, extensionTestsPath });
|
||||
} catch (err) {
|
||||
console.error('Failed to run tests');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
go()
|
||||
```
|
||||
|
||||
CLI options:
|
||||
```
|
||||
--browserType 'chromium' | 'firefox' | 'webkit': The browser to launch
|
||||
--extensionDevelopmentPath path. [Optional]: A path pointing to a extension to include.
|
||||
--extensionTestsPath path. [Optional]: A path to a test module to run
|
||||
--folder-uri. [Optional]: The folder to open VS Code on
|
||||
--version. 'insiders' (Default) | 'stable' | 'sources' [Optional]
|
||||
--open-devtools. Opens the dev tools [Optional]
|
||||
--headless. Whether to show the browser. Defaults to true when an extensionTestsPath is provided, otherwise false. [Optional]
|
||||
```
|
||||
|
||||
Corrsponding options are available in the API.
|
||||
|
||||
|
||||
## Development
|
||||
|
||||
- `yarn install`
|
||||
- Make necessary changes in [`src`](./src)
|
||||
- `yarn compile` (or `yarn watch`)
|
||||
|
||||
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE)
|
||||
|
||||
## Contributing
|
||||
|
||||
This project welcomes contributions and suggestions. Most contributions require you to agree to a
|
||||
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
|
||||
the rights to use your contribution. For details, visit https://cla.microsoft.com.
|
||||
|
||||
When you submit a pull request, a CLA-bot will automatically determine whether you need to provide
|
||||
a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
|
||||
provided by the bot. You will only need to do this once across all repos using our CLA.
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
||||
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
|
@ -0,0 +1,60 @@
|
|||
{
|
||||
"name": "@vscode/test-web",
|
||||
"version": "0.0.6",
|
||||
"scripts": {
|
||||
"compile": "tsc -p ./",
|
||||
"watch": "tsc -w -p ./",
|
||||
"prepublishOnly": "tsc -p ./",
|
||||
"test": "eslint src --ext ts && tsc --noEmit",
|
||||
"preversion": "npm test",
|
||||
"postversion": "git push && git push --tags",
|
||||
"compile-sample": "yarn --cwd=sample compile-web",
|
||||
"sample": "npm run compile && npm run compile-sample && node . --extensionDevelopmentPath=sample --browserType=chromium",
|
||||
"sample-tests": "npm run compile && npm run compile-sample && node . --extensionDevelopmentPath=sample --extensionTestsPath=sample/dist/web/test/suite/index.js --browserType=chromium"
|
||||
},
|
||||
"main": "./out/index.js",
|
||||
"bin": {
|
||||
"vscode-test-web": "./out/index.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@koa/router": "^10.0.0",
|
||||
"koa": "^2.13.1",
|
||||
"koa-morgan": "^1.0.1",
|
||||
"koa-mount": "^4.0.0",
|
||||
"koa-static": "^5.0.0",
|
||||
"minimist": "^1.2.5",
|
||||
"playwright": "^1.12.2",
|
||||
"vscode-uri": "^3.0.2",
|
||||
"http-proxy-agent": "^4.0.1",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
"decompress": "^4.2.1",
|
||||
"decompress-targz": "^4.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/koa": "^2.13.1",
|
||||
"@types/koa-morgan": "^1.0.4",
|
||||
"@types/koa-mount": "^4.0.0",
|
||||
"@types/koa-static": "^4.0.1",
|
||||
"@types/koa__router": "^8.0.4",
|
||||
"@types/minimist": "^1.2.1",
|
||||
"@types/node": "^12.19.9",
|
||||
"@typescript-eslint/eslint-plugin": "^4.13.0",
|
||||
"@typescript-eslint/parser": "^4.13.0",
|
||||
"@types/decompress": "^4.2.3",
|
||||
"eslint": "^7.17.0",
|
||||
"eslint-plugin-header": "^3.1.0",
|
||||
"typescript": "^4.1.3"
|
||||
},
|
||||
"license": "MIT",
|
||||
"author": "Visual Studio Code Team",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Microsoft/vscode-test-web.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/Microsoft/vscode-test-web/issues"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"eamodio.tsl-problem-matcher"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
// Place your settings in this file to overwrite default and user settings.
|
||||
{
|
||||
"files.exclude": {
|
||||
"out": false // set this to true to hide the "out" folder with the compiled JS files
|
||||
},
|
||||
"search.exclude": {
|
||||
"out": true // set this to false to include "out" folder in search results
|
||||
},
|
||||
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
|
||||
"typescript.tsc.autoDetect": "off"
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "compile-web",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"problemMatcher": [
|
||||
"$ts-webpack",
|
||||
"$tslint-webpack"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "watch-web",
|
||||
"group": "build",
|
||||
"isBackground": true,
|
||||
"problemMatcher": [
|
||||
"$ts-webpack-watch",
|
||||
"$tslint-webpack-watch"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<p>
|
||||
<h1 align="center">vscode-test-web-sample</h1>
|
||||
</p>
|
||||
|
||||
Sample for using https://github.com/microsoft/vscode-test-web.
|
||||
|
||||
Continuously tested with latest changes:
|
||||
|
||||
- [Azure DevOps](https://dev.azure.com/vscode/vscode-test-web/_build?definitionId=15)
|
||||
- [Travis](https://travis-ci.org/github/microsoft/vscode-test-web)
|
||||
|
||||
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.
|
||||
|
||||
```bash
|
||||
yarn install
|
||||
yarn compile
|
||||
yarn test
|
||||
```
|
|
@ -0,0 +1,60 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
//@ts-check
|
||||
'use strict';
|
||||
|
||||
//@ts-check
|
||||
/** @typedef {import('webpack').Configuration} WebpackConfig **/
|
||||
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
|
||||
module.exports = /** @type WebpackConfig */ {
|
||||
context: path.dirname(__dirname),
|
||||
mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production')
|
||||
target: 'webworker', // extensions run in a webworker context
|
||||
entry: {
|
||||
'extension': './src/web/extension.ts',
|
||||
'test/suite/index': './src/web/test/suite/index.ts'
|
||||
},
|
||||
resolve: {
|
||||
mainFields: ['module', 'main'],
|
||||
extensions: ['.ts', '.js'], // support ts-files and js-files
|
||||
alias: {
|
||||
},
|
||||
fallback: {
|
||||
'assert': require.resolve('assert')
|
||||
}
|
||||
},
|
||||
module: {
|
||||
rules: [{
|
||||
test: /\.ts$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
{
|
||||
loader: 'ts-loader'
|
||||
}
|
||||
]
|
||||
}]
|
||||
},
|
||||
plugins: [
|
||||
new webpack.ProvidePlugin({
|
||||
process: 'process/browser',
|
||||
}),
|
||||
],
|
||||
externals: {
|
||||
'vscode': 'commonjs vscode', // ignored because it doesn't exist
|
||||
},
|
||||
performance: {
|
||||
hints: false
|
||||
},
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
path: path.join(__dirname, '../dist/web'),
|
||||
libraryTarget: 'commonjs'
|
||||
},
|
||||
devtool: 'nosources-source-map'
|
||||
};
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"name": "vscode-test-web-sample",
|
||||
"displayName": "vscode-test-web-sample",
|
||||
"description": "",
|
||||
"version": "0.0.1",
|
||||
"engines": {
|
||||
"vscode": "^1.55.0"
|
||||
},
|
||||
"categories": [
|
||||
"Other"
|
||||
],
|
||||
"activationEvents": [
|
||||
"onCommand:vscode-test-web-sample.helloWorld"
|
||||
],
|
||||
"browser": "./dist/web/extension.js",
|
||||
"contributes": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "vscode-test-web-sample.helloWorld",
|
||||
"title": "Hello World"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"test": "node ./dist/web/test/runTest.js",
|
||||
"pretest": "npm run compile-web && tsc ./src/web/test/runTest.ts --outDir ./dist --rootDir ./src --target es6 --module commonjs",
|
||||
"vscode:prepublish": "npm run package-web",
|
||||
"compile-web": "webpack --config ./build/web-extension.webpack.config.js",
|
||||
"watch-web": "webpack --watch --config ./build/web-extension.webpack.config.js",
|
||||
"package-web": "webpack --mode production --devtool hidden-source-map --config ./build/web-extension.webpack.config.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/vscode": "^1.55.0",
|
||||
"@types/mocha": "^8.2.2",
|
||||
"@types/node": "^12.11.7",
|
||||
"mocha": "^9.0.1",
|
||||
"typescript": "^4.3.4",
|
||||
"ts-loader": "^9.2.3",
|
||||
"webpack": "^5.40.0",
|
||||
"webpack-cli": "^4.7.2",
|
||||
"@types/webpack-env": "^1.16.0",
|
||||
"assert": "^2.0.0",
|
||||
"process": "^0.11.10"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
// The module 'vscode' contains the VS Code extensibility API
|
||||
// Import the module and reference it with the alias vscode in your code below
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
// this method is called when your extension is activated
|
||||
// your extension is activated the very first time the command is executed
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
// Use the console to output diagnostic information (console.log) and errors (console.error)
|
||||
// This line of code will only be executed once when your extension is activated
|
||||
console.log('Congratulations, your extension "vscode-test-web-sample" is now active in the web extension host!');
|
||||
|
||||
// The command has been defined in the package.json file
|
||||
// Now provide the implementation of the command with registerCommand
|
||||
// The commandId parameter must match the command field in package.json
|
||||
let disposable = vscode.commands.registerCommand('vscode-test-web-sample.helloWorld', () => {
|
||||
// The code you place here will be executed every time your command is executed
|
||||
|
||||
// Display a message box to the user
|
||||
vscode.window.showInformationMessage('Hello World from vscode-test-web-sample in a web extension host!');
|
||||
});
|
||||
|
||||
context.subscriptions.push(disposable);
|
||||
}
|
||||
|
||||
// this method is called when your extension is deactivated
|
||||
export function deactivate() {}
|
|
@ -0,0 +1,29 @@
|
|||
import * as path from 'path';
|
||||
|
||||
import { runTests } from '../../../../out';
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
// The folder containing the Extension Manifest package.json
|
||||
const extensionDevelopmentPath = path.resolve(__dirname, '../../../');
|
||||
|
||||
// The path to module with the test runner and tests
|
||||
const extensionTestsPath = path.resolve(__dirname, './suite/index');
|
||||
|
||||
const attachArgName = '--waitForDebugger=';
|
||||
const waitForDebugger = process.argv.find(arg => arg.startsWith(attachArgName));
|
||||
|
||||
// Start a web server that serves VSCode in a browser, run the tests
|
||||
await runTests({
|
||||
browserType: 'chromium',
|
||||
extensionDevelopmentPath,
|
||||
extensionTestsPath,
|
||||
waitForDebugger: waitForDebugger ? Number(waitForDebugger.slice(attachArgName.length)) : undefined,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Failed to run tests');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
|
@ -0,0 +1,15 @@
|
|||
import * as assert from 'assert';
|
||||
|
||||
// You can import and use all API from the 'vscode' module
|
||||
// as well as import your extension to test it
|
||||
import * as vscode from 'vscode';
|
||||
// import * as myExtension from '../../extension';
|
||||
|
||||
suite('Web Extension Test Suite', () => {
|
||||
vscode.window.showInformationMessage('Start all tests.');
|
||||
|
||||
test('Sample test', () => {
|
||||
assert.strictEqual(-1, [1, 2, 3].indexOf(5));
|
||||
assert.strictEqual(-1, [1, 2, 3].indexOf(0));
|
||||
});
|
||||
});
|
|
@ -0,0 +1,30 @@
|
|||
// imports mocha for the browser, defining the `mocha` global.
|
||||
require('mocha/mocha');
|
||||
|
||||
export function run(): Promise<void> {
|
||||
|
||||
return new Promise((c, e) => {
|
||||
mocha.setup({
|
||||
ui: 'tdd',
|
||||
reporter: undefined
|
||||
});
|
||||
|
||||
// bundles all files in the current directory matching `*.test`
|
||||
const importAll = (r: __WebpackModuleApi.RequireContext) => r.keys().forEach(r);
|
||||
importAll(require.context('.', true, /\.test$/));
|
||||
|
||||
try {
|
||||
// Run the mocha test
|
||||
mocha.run(failures => {
|
||||
if (failures > 0) {
|
||||
e(new Error(`${failures} tests failed.`));
|
||||
} else {
|
||||
c();
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
e(err);
|
||||
}
|
||||
});
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"outDir": "dist",
|
||||
"lib": [
|
||||
"es6"
|
||||
],
|
||||
"sourceMap": true,
|
||||
"rootDir": "src",
|
||||
"strict": true /* enable all strict type-checking options */
|
||||
/* Additional Checks */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
".vscode-test"
|
||||
]
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,263 @@
|
|||
#!/usr/bin/env node
|
||||
/* eslint-disable header/header */
|
||||
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IConfig, runServer, Static, Sources } from './server/main';
|
||||
import { downloadAndUnzipVSCode } from './server/download';
|
||||
|
||||
import * as playwright from 'playwright';
|
||||
import * as minimist from 'minimist';
|
||||
import * as path from 'path';
|
||||
|
||||
export type BrowserType = 'chromium' | 'firefox' | 'webkit';
|
||||
export type VSCodeVersion = 'insiders' | 'stable' | 'sources';
|
||||
|
||||
export interface Options {
|
||||
|
||||
/**
|
||||
* Browser to run the test against: 'chromium' | 'firefox' | 'webkit'
|
||||
*/
|
||||
browserType: BrowserType;
|
||||
|
||||
/**
|
||||
* Absolute path to folder that contains one or more extensions (in subfolders).
|
||||
* Extension folders include a `package.json` extension manifest.
|
||||
*/
|
||||
extensionDevelopmentPath?: string;
|
||||
|
||||
/**
|
||||
* Absolute path to the extension tests runner module.
|
||||
* Can be either a file path or a directory path that contains an `index.js`.
|
||||
* The module is expected to have a `run` function of the following signature:
|
||||
*
|
||||
* ```ts
|
||||
* function run(): Promise<void>;
|
||||
* ```
|
||||
*
|
||||
* When running the extension test, the Extension Development Host will call this function
|
||||
* that runs the test suite. This function should throws an error if any test fails.
|
||||
*/
|
||||
extensionTestsPath?: string;
|
||||
|
||||
/**
|
||||
* The VS Code version to use. Valid versions are:
|
||||
* - `'stable'` : The latest stable build
|
||||
* - `'insiders'` : The latest insiders build
|
||||
* - `'sources'`: From sources, served at localhost:8080 by running `yarn web` in the vscode repo
|
||||
*
|
||||
* Currently defaults to `insiders`, which is latest stable insiders.
|
||||
*/
|
||||
version?: VSCodeVersion;
|
||||
|
||||
/**
|
||||
* Open the dev tools.
|
||||
*/
|
||||
devTools?: boolean;
|
||||
|
||||
/**
|
||||
* Do not show the browser. Defaults to `true` if a extensionTestsPath is provided, `false` otherwise.
|
||||
*/
|
||||
headless?: boolean;
|
||||
|
||||
/**
|
||||
* Expose browser debugging on this port number, and wait for the debugger to attach before running tests.
|
||||
*/
|
||||
waitForDebugger?: number;
|
||||
|
||||
/**
|
||||
* The folder URI to open VSCode on
|
||||
*/
|
||||
folderUri?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the tests in a browser.
|
||||
*
|
||||
* @param options The options defining browser type, extension and test location.
|
||||
*/
|
||||
export async function runTests(options: Options & { extensionTestsPath: string }): Promise<void> {
|
||||
const config: IConfig = {
|
||||
extensionDevelopmentPath: options.extensionDevelopmentPath,
|
||||
extensionTestsPath: options.extensionTestsPath,
|
||||
build: await getBuild(options.version),
|
||||
folderUri: options.folderUri
|
||||
};
|
||||
|
||||
const port = 3000;
|
||||
const server = await runServer(port, config);
|
||||
|
||||
const endpoint = `http://localhost:${port}`;
|
||||
|
||||
const result = await openInBrowser({
|
||||
browserType: options.browserType,
|
||||
endpoint,
|
||||
headless: options.headless ?? true,
|
||||
devTools: options.devTools,
|
||||
waitForDebugger: options.waitForDebugger,
|
||||
});
|
||||
|
||||
server.close();
|
||||
if (result) {
|
||||
return;
|
||||
}
|
||||
throw new Error('Test failed')
|
||||
}
|
||||
|
||||
async function getBuild(version: VSCodeVersion | undefined): Promise<Static | Sources> {
|
||||
if (version === 'sources') {
|
||||
return { type: 'sources' };
|
||||
}
|
||||
return await downloadAndUnzipVSCode(version === 'stable' ? 'stable' : 'insider');
|
||||
}
|
||||
|
||||
export async function open(options: Options): Promise<void> {
|
||||
|
||||
const config: IConfig = {
|
||||
extensionDevelopmentPath: options.extensionDevelopmentPath,
|
||||
build: await getBuild(options.version),
|
||||
folderUri: options.folderUri
|
||||
};
|
||||
|
||||
const port = 3000;
|
||||
await runServer(port, config);
|
||||
|
||||
const endpoint = `http://localhost:${port}`;
|
||||
|
||||
await openInBrowser({
|
||||
browserType: options.browserType,
|
||||
endpoint,
|
||||
headless: options.headless ?? false,
|
||||
devTools: options.devTools
|
||||
});
|
||||
}
|
||||
|
||||
const width = 1200;
|
||||
const height = 800;
|
||||
|
||||
interface BrowserOptions {
|
||||
browserType: BrowserType;
|
||||
endpoint: string;
|
||||
headless?: boolean;
|
||||
devTools?: boolean;
|
||||
waitForDebugger?: number;
|
||||
}
|
||||
|
||||
function openInBrowser(options: BrowserOptions): Promise<boolean> {
|
||||
return new Promise(async (s) => {
|
||||
const args: string[] = []
|
||||
if (process.platform === 'linux' && options.browserType === 'chromium') {
|
||||
args.push('--no-sandbox');
|
||||
}
|
||||
|
||||
if (options.waitForDebugger) {
|
||||
args.push(`--remote-debugging-port=${options.waitForDebugger}`);
|
||||
}
|
||||
|
||||
const browser = await playwright[options.browserType].launch({ headless: options.headless, args, devtools: options.devTools });
|
||||
const context = await browser.newContext();
|
||||
|
||||
const page = context.pages()[0] ?? await context.newPage();
|
||||
if (options.waitForDebugger) {
|
||||
await page.waitForFunction(() => '__jsDebugReady' in globalThis);
|
||||
}
|
||||
|
||||
await page.setViewportSize({ width, height });
|
||||
|
||||
await page.goto(options.endpoint);
|
||||
await page.exposeFunction('codeAutomationLog', (type: 'warn' | 'error' | 'info', args: unknown[]) => {
|
||||
console[type](...args);
|
||||
});
|
||||
|
||||
await page.exposeFunction('codeAutomationExit', async (code: number) => {
|
||||
try {
|
||||
await browser.close();
|
||||
} catch (error) {
|
||||
console.error(`Error when closing browser: ${error}`);
|
||||
}
|
||||
|
||||
s(code === 0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function isStringOrUndefined(value: unknown): value is string {
|
||||
return value === undefined || (typeof value === 'string');
|
||||
}
|
||||
|
||||
function isBooleanOrUndefined(value: unknown): value is string {
|
||||
return value === undefined || (typeof value === 'boolean');
|
||||
}
|
||||
|
||||
function isBrowserType(browserType: unknown): browserType is BrowserType {
|
||||
return (typeof browserType === 'string') && ['chromium', 'firefox', 'webkit'].includes(browserType);
|
||||
}
|
||||
|
||||
function isValidVersion(version: unknown): version is VSCodeVersion {
|
||||
return version === undefined || ((typeof version === 'string') && ['insiders', 'stable', 'sources'].includes(version));
|
||||
}
|
||||
|
||||
function getPortNumber(port: unknown): number | undefined {
|
||||
if (typeof port === 'string') {
|
||||
const number = Number.parseInt(port);
|
||||
if (!Number.isNaN(number) && number >= 0) {
|
||||
return number;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
interface CommandLineOptions {
|
||||
browserType?: string;
|
||||
extensionDevelopmentPath?: string;
|
||||
extensionTestsPath: string;
|
||||
type?: string;
|
||||
'open-devtools'?: boolean;
|
||||
headless?: boolean;
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
const options: minimist.Opts = { string: ['extensionDevelopmentPath', 'extensionTestsPath', 'browserType', 'version', 'waitForDebugger', 'folder-uri'], boolean: ['open-devtools', 'headless'] };
|
||||
const args = minimist<CommandLineOptions>(process.argv.slice(2), options);
|
||||
|
||||
const { browserType, extensionDevelopmentPath, extensionTestsPath, version, waitForDebugger, headless } = args;
|
||||
const port = getPortNumber(waitForDebugger);
|
||||
|
||||
if (!isBrowserType(browserType) || !isStringOrUndefined(extensionDevelopmentPath) || !isStringOrUndefined(extensionTestsPath) || !isValidVersion(version) || !isStringOrUndefined(args['folder-uri']) || !isBooleanOrUndefined(args['open-devtools']) || !isBooleanOrUndefined(headless)) {
|
||||
console.log('Usage:');
|
||||
console.log(` --browserType 'chromium' | 'firefox' | 'webkit': The browser to launch`)
|
||||
console.log(` --extensionDevelopmentPath path. [Optional]: A path pointing to a extension to include.`);
|
||||
console.log(` --extensionTestsPath path. [Optional]: A path to a test module to run`);
|
||||
console.log(` --folder-uri. [Optional]: The folder to open VS Code on`)
|
||||
console.log(` --version. 'insiders' (Default) | 'stable' | 'sources' [Optional]`);
|
||||
console.log(` --open-devtools. Opens the dev tools [Optional]`);
|
||||
console.log(` --headless. Whether to show the browser. Defaults to true when an extensionTestsPath is provided, otherwise false. [Optional]`);
|
||||
process.exit(-1);
|
||||
}
|
||||
if (extensionTestsPath) {
|
||||
runTests({
|
||||
extensionTestsPath: extensionTestsPath && path.resolve(extensionTestsPath),
|
||||
extensionDevelopmentPath: extensionDevelopmentPath && path.resolve(extensionDevelopmentPath),
|
||||
browserType,
|
||||
version,
|
||||
devTools: args['open-devtools'],
|
||||
waitForDebugger: port,
|
||||
folderUri: args['folder-uri'],
|
||||
headless
|
||||
})
|
||||
} else {
|
||||
open({
|
||||
extensionDevelopmentPath: extensionDevelopmentPath && path.resolve(extensionDevelopmentPath),
|
||||
browserType,
|
||||
version,
|
||||
devTools: args['open-devtools'],
|
||||
waitForDebugger: port,
|
||||
folderUri: args['folder-uri'],
|
||||
headless
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as Koa from 'koa';
|
||||
import * as morgan from 'koa-morgan';
|
||||
import * as kstatic from 'koa-static';
|
||||
import * as kmount from 'koa-mount';
|
||||
import { IConfig } from './main';
|
||||
import workbench from './workbench';
|
||||
import * as path from 'path';
|
||||
|
||||
export default async function createApp(config: IConfig): Promise<Koa> {
|
||||
const app = new Koa();
|
||||
|
||||
app.use(morgan('dev'));
|
||||
|
||||
// this is here such that the iframe worker can fetch the extension files
|
||||
app.use((ctx, next) => {
|
||||
ctx.set('Access-Control-Allow-Origin', '*');
|
||||
return next();
|
||||
});
|
||||
|
||||
app.use(kmount('/static', kstatic(path.join(__dirname, '../static'))));
|
||||
|
||||
if (config.extensionPath) {
|
||||
console.log('Serving extensions from ' + config.extensionPath);
|
||||
app.use(kmount('/static/extensions', kstatic(config.extensionPath)));
|
||||
}
|
||||
|
||||
if (config.extensionDevelopmentPath) {
|
||||
console.log('Serving dev extensions from ' + config.extensionDevelopmentPath);
|
||||
app.use(kmount('/static/devextensions', kstatic(config.extensionDevelopmentPath)));
|
||||
}
|
||||
|
||||
if (config.build.type === 'static') {
|
||||
app.use(kmount('/static/build', kstatic(config.build.location)));
|
||||
}
|
||||
|
||||
app.use(workbench(config));
|
||||
|
||||
return app;
|
||||
}
|
|
@ -0,0 +1,184 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { promises as fs, existsSync, createWriteStream } from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
import * as https from 'https';
|
||||
import * as http from 'http';
|
||||
import * as createHttpsProxyAgent from 'https-proxy-agent';
|
||||
import * as createHttpProxyAgent from 'http-proxy-agent';
|
||||
import { URL } from 'url';
|
||||
|
||||
import * as decompress from 'decompress';
|
||||
import * as decompressTargz from 'decompress-targz';
|
||||
import { Static } from './main';
|
||||
|
||||
interface DownloadInfo {
|
||||
url: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
const extensionRoot = process.cwd();
|
||||
const vscodeTestDir = path.resolve(extensionRoot, '.vscode-test-web');
|
||||
|
||||
|
||||
async function getLatestVersion(quality: 'stable' | 'insider'): Promise<DownloadInfo> {
|
||||
const update: DownloadInfo = await fetchJSON(`https://update.code.visualstudio.com/api/update/web-standalone/${quality}/latest`);
|
||||
return update;
|
||||
}
|
||||
|
||||
const reset = '\x1b[G\x1b[0K';
|
||||
|
||||
async function download(downloadUrl: string, destination: string, message: string) {
|
||||
process.stdout.write(message);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const httpLibrary = downloadUrl.startsWith('https') ? https : http;
|
||||
|
||||
httpLibrary.get(downloadUrl, getAgent(downloadUrl), res => {
|
||||
const total = Number(res.headers['content-length']);
|
||||
let received = 0;
|
||||
let timeout: NodeJS.Timeout | undefined;
|
||||
|
||||
const outStream = createWriteStream(destination);
|
||||
outStream.on('close', () => resolve(destination));
|
||||
outStream.on('error', reject);
|
||||
|
||||
res.on('data', chunk => {
|
||||
if (!timeout) {
|
||||
timeout = setTimeout(() => {
|
||||
process.stdout.write(`${reset}${message}: ${received}/${total} (${(received / total * 100).toFixed()}%)`);
|
||||
timeout = undefined;
|
||||
}, 100);
|
||||
}
|
||||
|
||||
received += chunk.length;
|
||||
});
|
||||
res.on('end', () => {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
|
||||
process.stdout.write(`${reset}${message}: complete\n`);
|
||||
});
|
||||
|
||||
|
||||
res.on('error', reject);
|
||||
res.pipe(outStream);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function unzip(source: string, destination: string, message: string) {
|
||||
process.stdout.write(message);
|
||||
if (!existsSync(destination)) {
|
||||
await fs.mkdir(destination, { recursive: true });
|
||||
}
|
||||
|
||||
await decompress(source, destination, {
|
||||
plugins: [
|
||||
decompressTargz()
|
||||
],
|
||||
strip: 1
|
||||
});
|
||||
process.stdout.write(`${reset}${message}: complete\n`);
|
||||
}
|
||||
|
||||
export async function downloadAndUnzipVSCode(quality: 'stable' | 'insider'): Promise<Static> {
|
||||
const info = await getLatestVersion(quality);
|
||||
|
||||
const folderName = `vscode-web-${quality}-${info.version}`;
|
||||
|
||||
const downloadedPath = path.resolve(vscodeTestDir, folderName);
|
||||
if (existsSync(downloadedPath) && existsSync(path.join(downloadedPath, 'version'))) {
|
||||
return { type: 'static', location: downloadedPath };
|
||||
}
|
||||
|
||||
if (existsSync(vscodeTestDir)) {
|
||||
await fs.rmdir(vscodeTestDir, { recursive: true, maxRetries: 5 });
|
||||
}
|
||||
|
||||
await fs.mkdir(vscodeTestDir, { recursive: true });
|
||||
|
||||
const productName = `VS Code ${quality === 'stable' ? 'Stable' : 'Insiders'}`;
|
||||
|
||||
const tmpArchiveName = `vscode-web-${quality}-${info.version}-tmp`;
|
||||
try {
|
||||
await download(info.url, tmpArchiveName, `Downloading ${productName}`);
|
||||
await unzip(tmpArchiveName, downloadedPath, `Unpacking ${productName}`);
|
||||
await fs.writeFile(path.join(downloadedPath, 'version'), folderName);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw Error(`Failed to download and unpack ${productName}`);
|
||||
} finally {
|
||||
try {
|
||||
fs.unlink(tmpArchiveName);
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
}
|
||||
return { type: 'static', location: downloadedPath };
|
||||
}
|
||||
|
||||
export async function fetch(api: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const httpLibrary = api.startsWith('https') ? https : http;
|
||||
httpLibrary.get(api, getAgent(api), res => {
|
||||
if (res.statusCode !== 200) {
|
||||
reject('Failed to get content from ');
|
||||
}
|
||||
|
||||
let data = '';
|
||||
|
||||
res.on('data', chunk => {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
resolve(data);
|
||||
});
|
||||
|
||||
res.on('error', err => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchJSON<T>(api: string): Promise<T> {
|
||||
const data = await fetch(api);
|
||||
try {
|
||||
return JSON.parse(data);
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to parse response from ${api}`);
|
||||
}
|
||||
}
|
||||
|
||||
let PROXY_AGENT: createHttpProxyAgent.HttpProxyAgent | undefined = undefined;
|
||||
let HTTPS_PROXY_AGENT: createHttpsProxyAgent.HttpsProxyAgent | undefined = undefined;
|
||||
|
||||
if (process.env.npm_config_proxy) {
|
||||
PROXY_AGENT = createHttpProxyAgent(process.env.npm_config_proxy);
|
||||
HTTPS_PROXY_AGENT = createHttpsProxyAgent(process.env.npm_config_proxy);
|
||||
}
|
||||
if (process.env.npm_config_https_proxy) {
|
||||
HTTPS_PROXY_AGENT = createHttpsProxyAgent(process.env.npm_config_https_proxy);
|
||||
}
|
||||
|
||||
function getAgent(url: string): https.RequestOptions {
|
||||
const parsed = new URL(url);
|
||||
const options: https.RequestOptions = {};
|
||||
if (PROXY_AGENT && parsed.protocol.startsWith('http:')) {
|
||||
options.agent = PROXY_AGENT;
|
||||
}
|
||||
|
||||
if (HTTPS_PROXY_AGENT && parsed.protocol.startsWith('https:')) {
|
||||
options.agent = HTTPS_PROXY_AGENT;
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { promises as fs } from 'fs';
|
||||
import * as path from 'path';
|
||||
export interface URIComponents {
|
||||
scheme: string;
|
||||
authority: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export async function scanForExtensions(
|
||||
rootPath: string,
|
||||
serverURI: URIComponents
|
||||
): Promise<URIComponents[]> {
|
||||
const result: URIComponents[] = [];
|
||||
async function getExtension(relativePosixFolderPath: string): Promise<URIComponents | undefined> {
|
||||
try {
|
||||
const packageJSONPath = path.join(rootPath, relativePosixFolderPath, 'package.json');
|
||||
if ((await fs.stat(packageJSONPath)).isFile()) {
|
||||
return {
|
||||
scheme: serverURI.scheme,
|
||||
authority: serverURI.authority,
|
||||
path: path.posix.join(serverURI.path, relativePosixFolderPath),
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async function processFolder(relativePosixFolderPath: string) {
|
||||
const extension = await getExtension(relativePosixFolderPath);
|
||||
if (extension) {
|
||||
result.push(extension);
|
||||
} else {
|
||||
const folderPath = path.join(rootPath, relativePosixFolderPath);
|
||||
const entries = await fs.readdir(folderPath, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
if (entry.isDirectory() && entry.name.charAt(0) !== '.') {
|
||||
await processFolder(path.posix.join(relativePosixFolderPath, entry.name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await processFolder('');
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import createApp from './app';
|
||||
|
||||
export interface IConfig {
|
||||
readonly extensionPath?: string;
|
||||
readonly extensionDevelopmentPath?: string;
|
||||
readonly extensionTestsPath?: string;
|
||||
readonly build: Sources | Static | CDN;
|
||||
readonly folderUri?: string;
|
||||
}
|
||||
|
||||
export interface Sources {
|
||||
type: 'sources';
|
||||
}
|
||||
|
||||
export interface Static {
|
||||
type: 'static';
|
||||
location: string;
|
||||
}
|
||||
|
||||
export interface CDN {
|
||||
type: 'cdn';
|
||||
uri: string;
|
||||
}
|
||||
|
||||
export interface IServer {
|
||||
close(): void;
|
||||
}
|
||||
|
||||
export async function runServer(port: number | undefined, config: IConfig): Promise<IServer> {
|
||||
const app = await createApp(config);
|
||||
const server = app.listen(port);
|
||||
console.log(`Listening on http://localhost:${port}`);
|
||||
return server;
|
||||
}
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as path from 'path';
|
||||
import { promises as fs } from 'fs';
|
||||
import { URI } from 'vscode-uri';
|
||||
import * as Router from '@koa/router';
|
||||
|
||||
import { IConfig } from './main';
|
||||
import { scanForExtensions, URIComponents } from './extensions';
|
||||
import { fetch, fetchJSON } from './download';
|
||||
|
||||
interface IDevelopmentOptions {
|
||||
extensionTestsPath?: URIComponents;
|
||||
extensions?: URIComponents[];
|
||||
}
|
||||
interface IWorkbenchOptions {
|
||||
additionalBuiltinExtensions?: (string | URIComponents)[];
|
||||
developmentOptions?: IDevelopmentOptions;
|
||||
folderUri?: URIComponents;
|
||||
}
|
||||
|
||||
function asJSON(value: unknown): string {
|
||||
return JSON.stringify(value).replace(/"/g, '"');
|
||||
}
|
||||
|
||||
class Workbench {
|
||||
constructor(readonly baseUrl: string, readonly dev: boolean, private readonly builtInExtensions: unknown[] = []) { }
|
||||
|
||||
async render(workbenchWebConfiguration: IWorkbenchOptions): Promise<string> {
|
||||
const values: { [key: string]: string } = {
|
||||
WORKBENCH_WEB_CONFIGURATION: asJSON(workbenchWebConfiguration),
|
||||
WORKBENCH_AUTH_SESSION: '',
|
||||
WORKBENCH_WEB_BASE_URL: this.baseUrl,
|
||||
WORKBENCH_BUILTIN_EXTENSIONS: asJSON(this.builtInExtensions),
|
||||
WORKBENCH_MAIN: this.getMain()
|
||||
};
|
||||
|
||||
try {
|
||||
const workbenchTemplate = (await fs.readFile(path.resolve(__dirname, '../../views/workbench.html'))).toString();
|
||||
return workbenchTemplate.replace(/\{\{([^}]+)\}\}/g, (_, key) => values[key] ?? 'undefined');
|
||||
} catch (e) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
getMain() {
|
||||
return this.dev
|
||||
? `<script> require(['vs/code/browser/workbench/workbench'], function() {}); </script>`
|
||||
: `<script src="${this.baseUrl}/out/vs/workbench/workbench.web.api.nls.js"></script>`
|
||||
+ `<script src="${this.baseUrl}/out/vs/workbench/workbench.web.api.js"></script>`
|
||||
+ `<script src="${this.baseUrl}/out/vs/code/browser/workbench/workbench.js"></script>`;
|
||||
}
|
||||
|
||||
|
||||
async renderCallback(): Promise<string> {
|
||||
return await fetch(`${this.baseUrl}/out/vs/code/browser/workbench/callback.html`);
|
||||
}
|
||||
}
|
||||
|
||||
function valueOrFirst<T>(value: T | T[] | undefined): T | undefined {
|
||||
return Array.isArray(value) ? value[0] : value;
|
||||
}
|
||||
|
||||
|
||||
async function getWorkbenchOptions(
|
||||
ctx: { protocol: string; host: string },
|
||||
config: IConfig
|
||||
): Promise<IWorkbenchOptions> {
|
||||
const options: IWorkbenchOptions = {};
|
||||
if (config.extensionPath) {
|
||||
options.additionalBuiltinExtensions = await scanForExtensions(config.extensionPath, {
|
||||
scheme: ctx.protocol,
|
||||
authority: ctx.host,
|
||||
path: '/static/extensions',
|
||||
});
|
||||
}
|
||||
if (config.extensionDevelopmentPath) {
|
||||
const developmentOptions: IDevelopmentOptions = options.developmentOptions = {}
|
||||
|
||||
developmentOptions.extensions = await scanForExtensions(
|
||||
config.extensionDevelopmentPath,
|
||||
{ scheme: ctx.protocol, authority: ctx.host, path: '/static/devextensions' },
|
||||
);
|
||||
if (config.extensionTestsPath) {
|
||||
let relativePath = path.relative(config.extensionDevelopmentPath, config.extensionTestsPath);
|
||||
if (process.platform === 'win32') {
|
||||
relativePath = relativePath.replace(/\\/g, '/');
|
||||
}
|
||||
developmentOptions.extensionTestsPath = {
|
||||
scheme: ctx.protocol,
|
||||
authority: ctx.host,
|
||||
path: path.posix.join('/static/devextensions', relativePath),
|
||||
};
|
||||
}
|
||||
}
|
||||
if (config.folderUri) {
|
||||
options.folderUri = URI.parse(config.folderUri);
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
export default function (config: IConfig): Router.Middleware {
|
||||
const router = new Router<{ workbench: Workbench }>();
|
||||
|
||||
router.use(async (ctx, next) => {
|
||||
if (ctx.query['dev'] || config.build.type === 'sources') {
|
||||
try {
|
||||
const builtInExtensions = await fetchJSON<unknown[]>('http://localhost:8080/builtin');
|
||||
ctx.state.workbench = new Workbench('http://localhost:8080/static', true, builtInExtensions);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
ctx.throw('Could not connect to localhost:8080, make sure you start `yarn web`', 400);
|
||||
}
|
||||
} else if (config.build.type === 'static') {
|
||||
ctx.state.workbench = new Workbench(`${ctx.protocol}://${ctx.host}/static/build`, false);
|
||||
} else if (config.build.type === 'cdn') {
|
||||
ctx.state.workbench = new Workbench(config.build.uri, false);
|
||||
}
|
||||
await next();
|
||||
});
|
||||
|
||||
const callbacks = new Map<string, URI>();
|
||||
|
||||
router.get('/callback', async ctx => {
|
||||
const {
|
||||
'vscode-requestId': vscodeRequestId,
|
||||
'vscode-scheme': vscodeScheme,
|
||||
'vscode-authority': vscodeAuthority,
|
||||
'vscode-path': vscodePath,
|
||||
'vscode-query': vscodeQuery,
|
||||
'vscode-fragment': vscodeFragment,
|
||||
} = ctx.query;
|
||||
|
||||
if (!vscodeRequestId || !vscodeScheme || !vscodeAuthority) {
|
||||
return ctx.throw(400);
|
||||
}
|
||||
|
||||
const requestId = valueOrFirst(vscodeRequestId)!;
|
||||
const uri = URI.from({
|
||||
scheme: valueOrFirst(vscodeScheme)!,
|
||||
authority: valueOrFirst(vscodeAuthority),
|
||||
path: valueOrFirst(vscodePath),
|
||||
query: valueOrFirst(vscodeQuery),
|
||||
fragment: valueOrFirst(vscodeFragment),
|
||||
});
|
||||
|
||||
callbacks.set(requestId, uri);
|
||||
|
||||
ctx.body = await ctx.state.workbench.renderCallback();
|
||||
});
|
||||
|
||||
router.get('/fetch-callback', async ctx => {
|
||||
const { 'vscode-requestId': vscodeRequestId } = ctx.query;
|
||||
|
||||
if (!vscodeRequestId) {
|
||||
return ctx.throw(400);
|
||||
}
|
||||
|
||||
const requestId = valueOrFirst(vscodeRequestId)!;
|
||||
const uri = callbacks.get(requestId);
|
||||
|
||||
if (!uri) {
|
||||
return ctx.throw(400);
|
||||
}
|
||||
|
||||
callbacks.delete(requestId);
|
||||
ctx.body = uri.toJSON();
|
||||
});
|
||||
|
||||
|
||||
router.get('/', async ctx => {
|
||||
const options = await getWorkbenchOptions(ctx, config);
|
||||
ctx.body = await ctx.state.workbench.render(options);
|
||||
});
|
||||
|
||||
return router.routes();
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2019",
|
||||
"module": "commonjs",
|
||||
"lib": [
|
||||
"ES2019"
|
||||
],
|
||||
"outDir": "out",
|
||||
"declaration": true,
|
||||
"strict": true,
|
||||
"noImplicitAny": false,
|
||||
"noImplicitThis": true,
|
||||
"noUnusedLocals": true,
|
||||
"alwaysStrict": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"sourceMap": false
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
<!-- Copyright (C) Microsoft Corporation. All rights reserved. -->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script>
|
||||
performance.mark('code/didStartRenderer')
|
||||
</script>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<!-- Disable pinch zooming -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
|
||||
|
||||
<!-- Workbench Configuration -->
|
||||
<meta id="vscode-workbench-web-configuration" data-settings="{{WORKBENCH_WEB_CONFIGURATION}}">
|
||||
|
||||
<!-- Workbench Auth Session -->
|
||||
<meta id="vscode-workbench-auth-session" data-settings="{{WORKBENCH_AUTH_SESSION}}">
|
||||
|
||||
<!-- Builtin Extensions (running out of sources) -->
|
||||
<meta id="vscode-workbench-builtin-extensions" data-settings="{{WORKBENCH_BUILTIN_EXTENSIONS}}">
|
||||
|
||||
<!-- Workbench Icon/Manifest/CSS -->
|
||||
<link rel="icon" href="{{WORKBENCH_WEB_BASE_URL}}/favicon.ico" type="image/x-icon" />
|
||||
<link rel="manifest" href="{{WORKBENCH_WEB_BASE_URL}}/manifest.json">
|
||||
</head>
|
||||
|
||||
<body aria-label="">
|
||||
</body>
|
||||
|
||||
<!-- Startup (do not modify order of script tags!) -->
|
||||
<script>
|
||||
let baseUrl = '{{WORKBENCH_WEB_BASE_URL}}';
|
||||
self.require = {
|
||||
baseUrl: `${baseUrl}/out`,
|
||||
recordStats: true,
|
||||
trustedTypesPolicy: window.trustedTypes?.createPolicy('amdLoader', {
|
||||
createScriptURL(value) {
|
||||
if (value.startsWith(baseUrl)) {
|
||||
return value;
|
||||
}
|
||||
throw new Error(`Invalid script url: ${value}`)
|
||||
}
|
||||
}),
|
||||
paths: {
|
||||
'vscode-textmate': `${baseUrl}/node_modules/vscode-textmate/release/main`,
|
||||
'vscode-oniguruma': `${baseUrl}/node_modules/vscode-oniguruma/release/main`,
|
||||
'xterm': `${baseUrl}/node_modules/xterm/lib/xterm.js`,
|
||||
'xterm-addon-search': `${baseUrl}/node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
|
||||
'xterm-addon-unicode11': `${baseUrl}/node_modules/xterm-addon-unicode11/lib/xterm-addon-unicode11.js`,
|
||||
'xterm-addon-webgl': `${baseUrl}/node_modules/xterm-addon-webgl/lib/xterm-addon-webgl.js`,
|
||||
'tas-client-umd': `${baseUrl}/node_modules/tas-client-umd/lib/tas-client-umd.js`,
|
||||
'iconv-lite-umd': `${baseUrl}/node_modules/iconv-lite-umd/lib/iconv-lite-umd.js`,
|
||||
'jschardet': `${baseUrl}/node_modules/jschardet/dist/jschardet.min.js`,
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
<script src="{{WORKBENCH_WEB_BASE_URL}}/out/vs/loader.js"></script>
|
||||
<script>
|
||||
performance.mark('code/willLoadWorkbenchMain');
|
||||
</script>
|
||||
{{WORKBENCH_MAIN}}
|
||||
</html>
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Загрузка…
Ссылка в новой задаче