зеркало из https://github.com/Azure/azapi-vscode.git
revert platform specific (#25)
This commit is contained in:
Родитель
10cdbcaf73
Коммит
d02f7c37d0
|
@ -1,69 +0,0 @@
|
|||
name: Publish release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Package
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- vsce_target: win32-x64
|
||||
ls_target: windows_amd64
|
||||
npm_config_arch: x64
|
||||
- vsce_target: win32-ia32
|
||||
ls_target: windows_386
|
||||
npm_config_arch: ia32
|
||||
- vsce_target: win32-arm64
|
||||
ls_target: windows_arm64
|
||||
npm_config_arch: arm
|
||||
- vsce_target: linux-x64
|
||||
ls_target: linux_amd64
|
||||
npm_config_arch: x64
|
||||
- vsce_target: linux-arm64
|
||||
ls_target: linux_arm64
|
||||
npm_config_arch: arm64
|
||||
- vsce_target: linux-armhf
|
||||
ls_target: linux_arm
|
||||
npm_config_arch: arm
|
||||
- vsce_target: darwin-x64
|
||||
ls_target: darwin_amd64
|
||||
npm_config_arch: x64
|
||||
- vsce_target: darwin-arm64
|
||||
ls_target: darwin_arm64
|
||||
npm_config_arch: arm64
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Read extension version
|
||||
id: ext-version
|
||||
run: |
|
||||
content=`cat ./package.json | jq -r .version`
|
||||
echo "::set-output name=content::$content"
|
||||
- name: Ensure version matches with tag
|
||||
if: ${{ github.ref != format('refs/tags/v{0}', steps.ext-version.outputs.content) }}
|
||||
run: |
|
||||
echo "Version mismatch!"
|
||||
echo "Received ref: ${{ github.ref }}"
|
||||
echo "Expected ref: refs/tags/v${{ steps.ext-version.outputs.content }}"
|
||||
exit 1
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
env:
|
||||
npm_config_arch: ${{ matrix.npm_config_arch }}
|
||||
- name: Package VSIX
|
||||
run: npm run package -- --target=${{ matrix.vsce_target }}
|
||||
env:
|
||||
ls_target: ${{ matrix.ls_target }}
|
||||
- name: Upload vsix as artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ matrix.vsce_target }}.vsix
|
||||
path: "*.vsix"
|
|
@ -23,6 +23,28 @@ jobs:
|
|||
- name: lint
|
||||
run: npm run lint
|
||||
|
||||
unit:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os:
|
||||
- windows-latest
|
||||
- macos-latest
|
||||
- ubuntu-latest
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 3
|
||||
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
- name: npm install
|
||||
run: npm ci
|
||||
- name: unit test
|
||||
run: npm run test:unit
|
||||
|
||||
test:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
|
2
.nvmrc
2
.nvmrc
|
@ -1 +1 @@
|
|||
16.13.2
|
||||
14.15.1
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
{
|
||||
"recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode", "connor4312.esbuild-problem-matchers"]
|
||||
"recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"]
|
||||
}
|
||||
|
|
|
@ -5,9 +5,13 @@
|
|||
"name": "Launch Client",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
|
||||
"outFiles": ["${workspaceFolder}/out/**/*.js"],
|
||||
"preLaunchTask": "npm: watch"
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": ["--extensionDevelopmentPath=${workspaceRoot}"],
|
||||
"outFiles": ["${workspaceRoot}/out/**/*.js"],
|
||||
"preLaunchTask": {
|
||||
"type": "npm",
|
||||
"script": "watch"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Run Extension Tests",
|
||||
|
|
|
@ -4,17 +4,15 @@
|
|||
{
|
||||
"type": "npm",
|
||||
"script": "watch",
|
||||
"group": "build",
|
||||
"problemMatcher": "$esbuild-watch",
|
||||
"problemMatcher": "$tsc-watch",
|
||||
"isBackground": true,
|
||||
"label": "npm: watch"
|
||||
"presentation": {
|
||||
"reveal": "never"
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "build",
|
||||
"group": "build",
|
||||
"problemMatcher": "$esbuild",
|
||||
"label": "npm: build"
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "terraformInitAndWatch",
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
.vscode-test
|
||||
build/
|
||||
lsp/
|
||||
node_modules/
|
||||
src/
|
||||
**/__mocks__
|
||||
out/*.test.*
|
||||
|
|
|
@ -33,7 +33,7 @@ exteral lanuage server by configuring the following:
|
|||
```
|
||||
|
||||
### Run in local development
|
||||
0. Prerequisites: golang >1.16, node 16.X, npm 8.X
|
||||
0. Prerequisites: golang >1.16, node 14.X, npm 6.X
|
||||
1. Clone [Terraform AzApi Provider Language Server](https://github.com/Azure/azapi-lsp) to local
|
||||
2. Run `go install` under project folder
|
||||
3. Add the following configuration to vscode setting file.
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
58
package.json
58
package.json
|
@ -10,12 +10,9 @@
|
|||
"preview": false,
|
||||
"private": true,
|
||||
"engines": {
|
||||
"npm": "~8.X",
|
||||
"node": "~16.X",
|
||||
"vscode": "^1.61.0"
|
||||
},
|
||||
"langServer": {
|
||||
"version": "0.25.2"
|
||||
"npm": "~6.X",
|
||||
"node": "~14.X",
|
||||
"vscode": "^1.55.0"
|
||||
},
|
||||
"qna": "https://github.com/Azure/azapi-vscode/issues",
|
||||
"bugs": {
|
||||
|
@ -102,6 +99,10 @@
|
|||
],
|
||||
"default": "off",
|
||||
"description": "Traces the communication between VS Code and the language server."
|
||||
},
|
||||
"requiredVersion": {
|
||||
"type": "string",
|
||||
"description": "The required version of the Language Server described as a semantic version string, for example '^2.0.1' or '> 1.0'. Defaults to latest available version."
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
|
@ -130,50 +131,47 @@
|
|||
"viewsWelcome": []
|
||||
},
|
||||
"scripts": {
|
||||
"esbuild-base": "esbuild ./src/extension.ts --bundle --outfile=out/extension.js --external:vscode --format=cjs --platform=node",
|
||||
"esbuild": "npm run esbuild-base -- --sourcemap",
|
||||
"esbuild-watch": "npm run esbuild-base -- --sourcemap --watch",
|
||||
"compile": "npm run esbuild",
|
||||
"watch": "npm run download:ls && npm run esbuild-watch",
|
||||
"download:ls": "ts-node ./build/downloader.ts",
|
||||
"vscode:prepublish": "npm run download:ls && npm run esbuild-base -- --minify",
|
||||
"package": "vsce package",
|
||||
"test-compile": "tsc -p ./",
|
||||
"pretest": "npm run download:ls && npm run test-compile && npm run lint",
|
||||
"test": "node ./out/test/runTest.js",
|
||||
"vscode:prepublish": "npm run compile",
|
||||
"compile": "tsc -p ./",
|
||||
"watch": "tsc -watch -p ./",
|
||||
"lint": "eslint src --ext ts",
|
||||
"pretest": "npm run compile && npm run lint",
|
||||
"test": "node ./out/test/runTest.js",
|
||||
"test:unit": "jest --silent=false",
|
||||
"package": "vsce package",
|
||||
"prettier": "prettier \"**/*.+(js|json|ts)\"",
|
||||
"format": "npm run prettier -- --write",
|
||||
"check-format": "npm run prettier -- --check",
|
||||
"preview": "ts-node ./build/preview.ts"
|
||||
"check-format": "npm run prettier -- --check"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/axios": "^0.14.0",
|
||||
"@types/semver": "^7.3.4",
|
||||
"@types/unzip-stream": "^0.3.1",
|
||||
"@vscode/extension-telemetry": "^0.4.9",
|
||||
"axios": "^0.24.0",
|
||||
"fs": "0.0.1-security",
|
||||
"openpgp": "^4.10.10",
|
||||
"semver": "^7.3.5",
|
||||
"short-unique-id": "^3.2.3",
|
||||
"vscode-extension-telemetry": "^0.4.2",
|
||||
"unzip-stream": "^0.3.1",
|
||||
"vscode-languageclient": "^7.0.0",
|
||||
"vscode-uri": "^3.0.2",
|
||||
"which": "^2.0.2"
|
||||
"which": "^2.0.2",
|
||||
"yauzl": "^2.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.2.22",
|
||||
"@types/glob": "^7.1.3",
|
||||
"@types/jest": "^27.0.3",
|
||||
"@types/mocha": "^9.0.0",
|
||||
"@types/node": "^16.11.7",
|
||||
"@types/node": "^12.12.54",
|
||||
"@types/openpgp": "^4.4.15",
|
||||
"@types/semver": "^7.3.4",
|
||||
"@types/unzip-stream": "^0.3.1",
|
||||
"@types/vscode": "^1.61.1",
|
||||
"@types/vscode": "^1.52.0",
|
||||
"@types/which": "^2.0.1",
|
||||
"@types/yauzl": "^2.9.1",
|
||||
"@typescript-eslint/eslint-plugin": "^3.9.0",
|
||||
"@typescript-eslint/parser": "^3.9.0",
|
||||
"axios": "^0.24.0",
|
||||
"chai": "^4.3.4",
|
||||
"esbuild": "^0.14.11",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-prettier": "^3.4.1",
|
||||
|
@ -181,13 +179,9 @@
|
|||
"jest": "^27.4.3",
|
||||
"mocha": "^9.1.3",
|
||||
"prettier": "^2.3.2",
|
||||
"semver": "^7.3.5",
|
||||
"temp": "^0.9.4",
|
||||
"ts-jest": "^27.1.0",
|
||||
"ts-node": "^10.4.0",
|
||||
"typescript": "^4.5.4",
|
||||
"unzip-stream": "^0.3.1",
|
||||
"vsce": "^2.6.3",
|
||||
"typescript": "^3.9.7",
|
||||
"vscode-test": "^1.5.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import ShortUniqueId from 'short-unique-id';
|
||||
import * as vscode from 'vscode';
|
||||
import TelemetryReporter from 'vscode-extension-telemetry';
|
||||
import TelemetryReporter from '@vscode/extension-telemetry';
|
||||
import {
|
||||
DocumentSelector,
|
||||
Executable,
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import * as vscode from 'vscode';
|
||||
import TelemetryReporter from 'vscode-extension-telemetry';
|
||||
import TelemetryReporter from '@vscode/extension-telemetry';
|
||||
import { ClientHandler } from './clientHandler';
|
||||
import { DEFAULT_LS_VERSION, isValidVersionString } from './installer/detector';
|
||||
import { updateOrInstall } from './installer/updater';
|
||||
import { ServerPath } from './serverPath';
|
||||
import { SingleInstanceTimeout } from './utils';
|
||||
import { config } from './vscodeUtils';
|
||||
|
||||
const brand = `Terraform AzApi Provider`;
|
||||
|
@ -10,6 +13,7 @@ export let terraformStatus: vscode.StatusBarItem;
|
|||
|
||||
let reporter: TelemetryReporter;
|
||||
let clientHandler: ClientHandler;
|
||||
const languageServerUpdater = new SingleInstanceTimeout();
|
||||
|
||||
export async function activate(context: vscode.ExtensionContext): Promise<void> {
|
||||
const manifest = context.extension.packageJSON;
|
||||
|
@ -20,6 +24,15 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
|
|||
const lsPath = new ServerPath(context);
|
||||
clientHandler = new ClientHandler(lsPath, outputChannel, reporter);
|
||||
|
||||
if (config('azapi').has('languageServer.requiredVersion')) {
|
||||
const langServerVer = config('azapi').get('languageServer.requiredVersion', DEFAULT_LS_VERSION);
|
||||
if (!isValidVersionString(langServerVer)) {
|
||||
vscode.window.showWarningMessage(
|
||||
`The Terraform Language Server Version string '${langServerVer}' is not a valid semantic version and will be ignored.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Subscriptions
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('azapi.enableLanguageServer', async () => {
|
||||
|
@ -31,7 +44,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
|
|||
vscode.ConfigurationTarget.Global,
|
||||
);
|
||||
}
|
||||
startLanguageServer();
|
||||
await updateLanguageServer(manifest.version, lsPath);
|
||||
}),
|
||||
vscode.commands.registerCommand('azapi.disableLanguageServer', async () => {
|
||||
if (enabled()) {
|
||||
|
@ -42,10 +55,11 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
|
|||
vscode.ConfigurationTarget.Global,
|
||||
);
|
||||
}
|
||||
stopLanguageServer();
|
||||
languageServerUpdater.clear();
|
||||
return clientHandler.stopClient();
|
||||
}),
|
||||
vscode.workspace.onDidChangeConfiguration(async (event: vscode.ConfigurationChangeEvent) => {
|
||||
if (event.affectsConfiguration('terraform') || event.affectsConfiguration('azapi-lsp')) {
|
||||
if (event.affectsConfiguration('azapi')) {
|
||||
const reloadMsg = 'Reload VSCode window to apply language server changes';
|
||||
const selected = await vscode.window.showInformationMessage(reloadMsg, 'Reload');
|
||||
if (selected === 'Reload') {
|
||||
|
@ -56,7 +70,13 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
|
|||
);
|
||||
|
||||
if (enabled()) {
|
||||
startLanguageServer();
|
||||
try {
|
||||
await updateLanguageServer(manifest.version, lsPath);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
reporter.sendTelemetryException(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,24 +88,33 @@ export async function deactivate(): Promise<void> {
|
|||
return clientHandler.stopClient();
|
||||
}
|
||||
|
||||
async function startLanguageServer() {
|
||||
try {
|
||||
await clientHandler.startClient();
|
||||
vscode.commands.executeCommand('setContext', 'terraform.showTreeViews', true);
|
||||
} catch (error) {
|
||||
console.log(error); // for test failure reporting
|
||||
if (error instanceof Error) {
|
||||
vscode.window.showErrorMessage(error instanceof Error ? error.message : error);
|
||||
} else if (typeof error === 'string') {
|
||||
vscode.window.showErrorMessage(error);
|
||||
async function updateLanguageServer(extVersion: string, lsPath: ServerPath, scheduled = false) {
|
||||
if (config('extensions').get<boolean>('autoCheckUpdates', true) === true) {
|
||||
console.log('Scheduling check for language server updates...');
|
||||
const hour = 1000 * 60 * 60;
|
||||
languageServerUpdater.timeout(function () {
|
||||
updateLanguageServer(extVersion, lsPath, true);
|
||||
}, 24 * hour);
|
||||
}
|
||||
|
||||
if (lsPath.hasCustomBinPath()) {
|
||||
// skip install check if user has specified a custom path to the LS
|
||||
// with custom paths we *need* to start the lang client always
|
||||
await clientHandler.startClient();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function stopLanguageServer() {
|
||||
try {
|
||||
await clientHandler.stopClient();
|
||||
vscode.commands.executeCommand('setContext', 'terraform.showTreeViews', false);
|
||||
await updateOrInstall(config('azapi').get('languageServer.requiredVersion', DEFAULT_LS_VERSION), lsPath, reporter);
|
||||
|
||||
// On scheduled checks, we download to stg and do not replace prod path
|
||||
// So we *do not* need to stop or start the LS
|
||||
if (scheduled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// On fresh starts we *need* to start the lang client always
|
||||
await clientHandler.startClient();
|
||||
} catch (error) {
|
||||
console.log(error); // for test failure reporting
|
||||
if (error instanceof Error) {
|
||||
|
@ -97,5 +126,5 @@ async function stopLanguageServer() {
|
|||
}
|
||||
|
||||
function enabled(): boolean {
|
||||
return config('azapi').get('languageServer.external', false);
|
||||
return config('azapi').get('languageServer.external', true);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
import * as semver from 'semver';
|
||||
import * as vscode from 'vscode';
|
||||
import { exec } from '../utils';
|
||||
import axios from 'axios';
|
||||
import { Build, Release } from '../types';
|
||||
|
||||
export const DEFAULT_LS_VERSION = 'latest';
|
||||
|
||||
export function isValidVersionString(value: string): boolean {
|
||||
return semver.validRange(value, { includePrerelease: true, loose: true }) !== null;
|
||||
}
|
||||
export async function getLsVersion(binPath: string): Promise<string | undefined> {
|
||||
try {
|
||||
const jsonCmd: { stdout: string } = await exec(binPath, ['version', '-json']);
|
||||
const jsonOutput = JSON.parse(jsonCmd.stdout);
|
||||
return jsonOutput.version.replace('-dev', '');
|
||||
} catch (err) {
|
||||
// assume older version of LS which didn't have json flag
|
||||
// return undefined as regex matching isn't useful here
|
||||
// if it's old enough to not have the json version, we would be updating anyway
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getRequiredVersionRelease(versionString: string): Promise<Release> {
|
||||
try {
|
||||
const response = await axios.get('https://api.github.com/repos/Azure/azapi-lsp/releases', {
|
||||
headers: {
|
||||
Authorization: 'token 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',
|
||||
},
|
||||
});
|
||||
if (response.status == 200 && response.data.length != 0) {
|
||||
if (versionString == 'latest') {
|
||||
return toRelease(response.data[0]);
|
||||
} else {
|
||||
const versions = [];
|
||||
for (const i in response.data) {
|
||||
versions.push(response.data[i].tag_name);
|
||||
}
|
||||
const matchedVersion = semver.maxSatisfying(versions, versionString);
|
||||
for (const i in response.data) {
|
||||
if (response.data[i].tag_name == matchedVersion) {
|
||||
return toRelease(response.data[i]);
|
||||
}
|
||||
}
|
||||
console.log(`Found no matched release of azapi-lsp, version: ${versionString}`);
|
||||
vscode.window.showWarningMessage(`Found no matched release of azapi-lsp, use latest`);
|
||||
return toRelease(response.data[0]);
|
||||
}
|
||||
}
|
||||
vscode.window.showWarningMessage(`Found no releases of azapi-lsp`);
|
||||
} catch (err) {
|
||||
vscode.window.showErrorMessage(`Error loading releases of azapi-lsp`);
|
||||
throw err;
|
||||
}
|
||||
|
||||
throw new Error('no valid release');
|
||||
}
|
||||
|
||||
function toRelease(data: any): Release {
|
||||
const assets: Build[] = [];
|
||||
for (const i in data.assets) {
|
||||
assets.push({
|
||||
name: data.assets[i].name,
|
||||
downloadUrl: data.assets[i].browser_download_url,
|
||||
});
|
||||
}
|
||||
return {
|
||||
version: data.name,
|
||||
assets: assets,
|
||||
};
|
||||
}
|
||||
|
||||
export async function pathExists(filePath: string): Promise<boolean> {
|
||||
try {
|
||||
await vscode.workspace.fs.stat(vscode.Uri.file(filePath));
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
import axios from 'axios';
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import TelemetryReporter from '@vscode/extension-telemetry';
|
||||
import { pathExists } from './detector';
|
||||
import * as fs from 'fs';
|
||||
import * as unzip from 'unzip-stream';
|
||||
import { Build, Release } from '../types';
|
||||
|
||||
export async function installTerraformLS(
|
||||
installPath: string,
|
||||
release: Release,
|
||||
reporter: TelemetryReporter,
|
||||
): Promise<void> {
|
||||
reporter.sendTelemetryEvent('installingLs', { terraformLsVersion: release.version });
|
||||
|
||||
const zipfile = path.resolve(installPath, `azapi-lsp_${release.version}.zip`);
|
||||
const os = getPlatform();
|
||||
const arch = getArch();
|
||||
|
||||
let build: Build | undefined;
|
||||
for (const i in release.assets) {
|
||||
if (release.assets[i].name.endsWith(`${os}_${arch}.zip`)) {
|
||||
build = release.assets[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!build) {
|
||||
throw new Error(`Install error: no matching azapi-lsp binary for ${os}/${arch}`);
|
||||
}
|
||||
|
||||
// On brand new extension installs, there isn't a directory until we execute here
|
||||
// Create it if it doesn't exist so the downloader can unpack
|
||||
if ((await pathExists(installPath)) === false) {
|
||||
await vscode.workspace.fs.createDirectory(vscode.Uri.file(installPath));
|
||||
}
|
||||
|
||||
console.log(build);
|
||||
|
||||
// Download and unpack async inside the VS Code notification window
|
||||
// This will show in the statusbar for the duration of the download and unpack
|
||||
// This was the most non-distuptive choice that still provided some status to the user
|
||||
return vscode.window.withProgress(
|
||||
{
|
||||
cancellable: false,
|
||||
location: vscode.ProgressLocation.Window,
|
||||
title: 'Installing azapi-lsp',
|
||||
},
|
||||
async (progress) => {
|
||||
// download zip
|
||||
progress.report({ increment: 30 });
|
||||
await axios.get(build!.downloadUrl, { responseType: 'stream' }).then(function (response) {
|
||||
const fileWritePipe = fs.createWriteStream(zipfile);
|
||||
response.data.pipe(fileWritePipe);
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
fileWritePipe.on('close', () => resolve());
|
||||
response.data.on('error', reject);
|
||||
});
|
||||
});
|
||||
|
||||
// verify
|
||||
progress.report({ increment: 30 });
|
||||
|
||||
// unzip
|
||||
const fileExtension = os === 'windows' ? '.exe' : '';
|
||||
const unversionedName = path.resolve(installPath, `azapi-lsp${fileExtension}`);
|
||||
progress.report({ increment: 20 });
|
||||
const fileReadStream = fs.createReadStream(zipfile);
|
||||
const unzipPipe = unzip.Extract({ path: installPath });
|
||||
fileReadStream.pipe(unzipPipe);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
unzipPipe.on('close', () => {
|
||||
fs.chmodSync(unversionedName, '755');
|
||||
return resolve();
|
||||
});
|
||||
fileReadStream.on('error', reject);
|
||||
});
|
||||
|
||||
progress.report({ increment: 10 });
|
||||
return vscode.workspace.fs.delete(vscode.Uri.file(zipfile));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function getPlatform(): string {
|
||||
const platform = process.platform.toString();
|
||||
if (platform === 'win32') {
|
||||
return 'windows';
|
||||
}
|
||||
if (platform === 'sunos') {
|
||||
return 'solaris';
|
||||
}
|
||||
return platform;
|
||||
}
|
||||
|
||||
function getArch(): string {
|
||||
// platform | terraform-ls | extension platform | vs code editor
|
||||
// -- | -- | -- | --
|
||||
// macOS | darwin_amd64 | darwin_x64 | ✅
|
||||
// macOS | darwin_arm64 | darwin_arm64 | ✅
|
||||
// Linux | linux_amd64 | linux_x64 | ✅
|
||||
// Linux | linux_arm | linux_armhf | ✅
|
||||
// Linux | linux_arm64 | linux_arm64 | ✅
|
||||
// Windows | windows_386 | win32_ia32 | ✅
|
||||
// Windows | windows_amd64 | win32_x64 | ✅
|
||||
// Windows | windows_arm64 | win32_arm64 | ✅
|
||||
const arch = process.arch;
|
||||
|
||||
if (arch === 'ia32') {
|
||||
return '386';
|
||||
}
|
||||
if (arch === 'x64') {
|
||||
return 'amd64';
|
||||
}
|
||||
if (arch === 'armhf') {
|
||||
return 'arm';
|
||||
}
|
||||
|
||||
return arch;
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
import * as semver from 'semver';
|
||||
import * as vscode from 'vscode';
|
||||
import TelemetryReporter from '@vscode/extension-telemetry';
|
||||
import { ServerPath } from '../serverPath';
|
||||
import { Release } from '../types';
|
||||
import { config } from '../vscodeUtils';
|
||||
import {
|
||||
DEFAULT_LS_VERSION,
|
||||
getLsVersion,
|
||||
getRequiredVersionRelease,
|
||||
isValidVersionString,
|
||||
pathExists,
|
||||
} from './detector';
|
||||
import { installTerraformLS } from './installer';
|
||||
|
||||
export async function updateOrInstall(
|
||||
lsVersion: string,
|
||||
lsPath: ServerPath,
|
||||
reporter: TelemetryReporter,
|
||||
): Promise<void> {
|
||||
const stgingExists = await pathExists(lsPath.stgBinPath());
|
||||
if (stgingExists) {
|
||||
// LS was updated during the last run while user was using the extension
|
||||
// Do not check for updates here, as normal execution flow will handle decision logic later
|
||||
// Need to move stg path to prod path now and return normal execution
|
||||
await vscode.workspace.fs.rename(vscode.Uri.file(lsPath.stgBinPath()), vscode.Uri.file(lsPath.binPath()), {
|
||||
overwrite: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Silently default to latest if an invalid version string is passed.
|
||||
// Actually telling the user about a bad string is left to the main extension code instead of here
|
||||
const versionString = isValidVersionString(lsVersion) ? lsVersion : DEFAULT_LS_VERSION;
|
||||
|
||||
const lsPresent = await pathExists(lsPath.binPath());
|
||||
const autoUpdate = config('extensions').get<boolean>('autoUpdate', true);
|
||||
if (lsPresent === true && autoUpdate === false) {
|
||||
// LS is present in prod path, but user does not want automatic updates
|
||||
// Return normal execution
|
||||
return;
|
||||
}
|
||||
|
||||
// Get LS release information from github release
|
||||
// Fall back to latest if not requested version not available
|
||||
let release: Release;
|
||||
try {
|
||||
release = await getRequiredVersionRelease(versionString);
|
||||
} catch (err) {
|
||||
console.log(
|
||||
`Error while finding Terraform language server release which satisfies range '${versionString}': ${err}`,
|
||||
);
|
||||
// if the releases site is inaccessible, report it and skip the install
|
||||
if (err instanceof Error) {
|
||||
reporter.sendTelemetryException(err);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (lsPresent === false) {
|
||||
// LS is not present, need to download now in order to function
|
||||
// Install directly to production path and return normal execution
|
||||
return installTerraformLS(lsPath.installPath(), release, reporter);
|
||||
}
|
||||
|
||||
// We know there is an LS Present at this point, find out version if possible
|
||||
const installedVersion = await getLsVersion(lsPath.binPath());
|
||||
if (installedVersion === undefined) {
|
||||
console.log(`Currently installed Terraform language server is version '${installedVersion}`);
|
||||
// ls is present but too old to tell us the version, so need to update now
|
||||
return installTerraformLS(lsPath.installPath(), release, reporter);
|
||||
}
|
||||
|
||||
// We know there is an LS present and know the version, so decide whether to update or not
|
||||
console.log(`Currently installed Terraform language server is version '${installedVersion}`);
|
||||
reporter.sendTelemetryEvent('foundLsInstalled', { terraformLsVersion: installedVersion });
|
||||
|
||||
// Already at the latest or specified version, no update needed
|
||||
// return to normal execution flow
|
||||
if (semver.eq(release.version, installedVersion, { includePrerelease: true })) {
|
||||
console.log(`Language server release is current: ${release.version}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// We used to prompt for decision here, but effectively downgrading or upgrading
|
||||
// are the same operation so log decision and update
|
||||
if (semver.gt(release.version, installedVersion, { includePrerelease: true })) {
|
||||
// Upgrade
|
||||
console.log(`A newer language server release is available: ${release.version}`);
|
||||
} else if (semver.lt(release.version, installedVersion, { includePrerelease: true })) {
|
||||
// Downgrade
|
||||
console.log(`An older language server release is available: ${release.version}`);
|
||||
}
|
||||
|
||||
// Update indicated and user wants autoupdates, so update to latest or specified version
|
||||
return installTerraformLS(lsPath.stgInstallPath(), release, reporter);
|
||||
}
|
|
@ -13,7 +13,11 @@ export class ServerPath {
|
|||
}
|
||||
|
||||
public installPath(): string {
|
||||
return path.join(this.context.extensionPath, INSTALL_FOLDER_NAME);
|
||||
return path.join(this.context.globalStorageUri.fsPath, INSTALL_FOLDER_NAME);
|
||||
}
|
||||
|
||||
public stgInstallPath(): string {
|
||||
return path.join(this.context.globalStorageUri.fsPath, 'stg');
|
||||
}
|
||||
|
||||
public hasCustomBinPath(): boolean {
|
||||
|
@ -28,6 +32,14 @@ export class ServerPath {
|
|||
return path.resolve(this.installPath(), this.binName());
|
||||
}
|
||||
|
||||
public stgBinPath(): string {
|
||||
if (this.customBinPath) {
|
||||
return this.customBinPath;
|
||||
}
|
||||
|
||||
return path.resolve(this.stgInstallPath(), this.binName());
|
||||
}
|
||||
|
||||
public binName(): string {
|
||||
if (this.customBinPath) {
|
||||
return path.basename(this.customBinPath);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import TelemetryReporter from 'vscode-extension-telemetry';
|
||||
import TelemetryReporter from '@vscode/extension-telemetry';
|
||||
import { BaseLanguageClient, ClientCapabilities, StaticFeature } from 'vscode-languageclient';
|
||||
|
||||
import { ExperimentalClientCapabilities } from './types';
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
import { mocked } from 'ts-jest/utils';
|
||||
import { exec as execOrg } from '../../utils';
|
||||
import { getLsVersion, isValidVersionString, getRequiredVersionRelease } from '../../installer/detector';
|
||||
|
||||
jest.mock('../../utils');
|
||||
|
||||
const exec = mocked(execOrg);
|
||||
|
||||
describe('terraform release detector', () => {
|
||||
test('returns valid release', async () => {
|
||||
const version = 'v0.0.1';
|
||||
const result = await getRequiredVersionRelease(version);
|
||||
expect(result.version).toMatch(version);
|
||||
expect(result.assets.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('returns latest if invalid version', async () => {
|
||||
const resultWithInvalidVersion = await getRequiredVersionRelease('v10000.24.0');
|
||||
const resultLatest = await getRequiredVersionRelease('latest');
|
||||
expect(resultLatest.version.length).toBeGreaterThan(0);
|
||||
expect(resultWithInvalidVersion).toMatchObject(resultLatest);
|
||||
});
|
||||
});
|
||||
|
||||
describe('terraform detector', () => {
|
||||
test('returns valid version with valid path', async () => {
|
||||
exec.mockImplementationOnce(async () => {
|
||||
return {
|
||||
stdout: '{"version": "1.2.3"}',
|
||||
stderr: '',
|
||||
};
|
||||
});
|
||||
const result = await getLsVersion('installPath');
|
||||
expect(result).toBe('1.2.3');
|
||||
});
|
||||
test('returns undefined with invalid path', async () => {
|
||||
const result = await getLsVersion('installPath');
|
||||
expect(result).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('version detector', () => {
|
||||
test('detect valid version', async () => {
|
||||
const result = isValidVersionString('1.2.3');
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
|
||||
test('detect invalid version', async () => {
|
||||
const result = isValidVersionString('1f');
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
|
||||
test('detect valid semver version', async () => {
|
||||
const result = isValidVersionString('1.2.3-alpha');
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
|
||||
test('detect invalid semver version', async () => {
|
||||
const result = isValidVersionString('1.23-alpha');
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,34 @@
|
|||
import { installTerraformLS } from '../../installer/installer';
|
||||
import { reporter } from './mocks/reporter';
|
||||
import { Build, Release } from '../../types';
|
||||
|
||||
jest.mock('../../installer/detector');
|
||||
describe('azapi-lsp installer', () => {
|
||||
describe('should install', () => {
|
||||
test('when valid version is passed', async () => {
|
||||
const expectedRelease: Release = getRelease('v0.0.1');
|
||||
|
||||
await installTerraformLS('installPath', expectedRelease, reporter);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function getRelease(version: string): Release {
|
||||
return {
|
||||
version: version,
|
||||
assets: [
|
||||
{
|
||||
downloadUrl: `https://github.com/Azure/azapi-lsp/releases/download/${version}/azapi-lsp_${version}_windows_amd64.zip`,
|
||||
name: `azapi-lsp_${version}_windows_amd64.zip`,
|
||||
},
|
||||
{
|
||||
downloadUrl: `https://github.com/Azure/azapi-lsp/releases/download/${version}/azapi-lsp_${version}_darwin_amd64.zip`,
|
||||
name: `azapi-lsp_${version}_darwin_amd64.zip`,
|
||||
},
|
||||
{
|
||||
downloadUrl: `https://github.com/Azure/azapi-lsp/releases/download/${version}/azapi-lsp_${version}_linux_amd64.zip`,
|
||||
name: `azapi-lsp_${version}_linux_amd64.zip`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
|
@ -4,4 +4,5 @@ export const reporter = {
|
|||
sendTelemetryErrorEvent: jest.fn(),
|
||||
sendTelemetryException: jest.fn(),
|
||||
dispose: jest.fn(),
|
||||
telemetryLevel: 'all' as any,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,245 @@
|
|||
import * as vscode from 'vscode';
|
||||
import { mocked } from 'ts-jest/utils';
|
||||
import { updateOrInstall } from '../../installer/updater';
|
||||
import { reporter } from './mocks/reporter';
|
||||
import { installTerraformLS } from '../../installer/installer';
|
||||
import {
|
||||
getRequiredVersionRelease as getRequiredVersionReleaseOrig,
|
||||
isValidVersionString as isValidVersionStringOrig,
|
||||
pathExists as pathExistsOrig,
|
||||
getLsVersion as getLsVersionOrig,
|
||||
} from '../../installer/detector';
|
||||
import { ServerPath } from '../../serverPath';
|
||||
import { lsPathMock } from './mocks/serverPath';
|
||||
import { Release } from '../../types';
|
||||
|
||||
jest.mock('../../installer/detector');
|
||||
jest.mock('../../installer/installer');
|
||||
|
||||
const getConfiguration = mocked(vscode.workspace.getConfiguration);
|
||||
const pathExists = mocked(pathExistsOrig);
|
||||
const isValidVersionString = mocked(isValidVersionStringOrig);
|
||||
const getRequiredVersionRelease = mocked(getRequiredVersionReleaseOrig);
|
||||
const getLsVersion = mocked(getLsVersionOrig);
|
||||
// @ts-ignore
|
||||
const lsPath: ServerPath & typeof lsPathMock = lsPathMock;
|
||||
|
||||
describe('azapi-lsp updater', () => {
|
||||
describe('should install', () => {
|
||||
test('on fresh install', async () => {
|
||||
getConfiguration.mockImplementationOnce(() => ({
|
||||
get: jest.fn(() => {
|
||||
// config('extensions').get<boolean>('autoUpdate', true);
|
||||
return true;
|
||||
}),
|
||||
has: jest.fn(),
|
||||
inspect: jest.fn(),
|
||||
update: jest.fn(),
|
||||
}));
|
||||
|
||||
pathExists
|
||||
.mockImplementationOnce(async () => false) // stg not present
|
||||
.mockImplementationOnce(async () => false); // prod not present
|
||||
|
||||
isValidVersionString.mockImplementationOnce(() => true);
|
||||
getRequiredVersionRelease.mockImplementationOnce(async () => {
|
||||
return getRelease('v0.0.1');
|
||||
});
|
||||
await updateOrInstall('v0.0.1', lsPath, reporter);
|
||||
|
||||
expect(pathExists).toBeCalledTimes(2);
|
||||
expect(installTerraformLS).toBeCalledTimes(1);
|
||||
expect(vscode.workspace.getConfiguration).toBeCalledTimes(1);
|
||||
expect(lsPath.stgBinPath).toBeCalledTimes(1);
|
||||
expect(lsPath.installPath).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
test('ls version not found', async () => {
|
||||
getConfiguration.mockImplementationOnce(() => ({
|
||||
get: jest.fn(() => {
|
||||
// config('extensions').get<boolean>('autoUpdate', true);
|
||||
return true;
|
||||
}),
|
||||
has: jest.fn(),
|
||||
inspect: jest.fn(),
|
||||
then: jest.fn(),
|
||||
update: jest.fn(),
|
||||
}));
|
||||
pathExists
|
||||
.mockImplementationOnce(async () => false) // stg not present
|
||||
.mockImplementationOnce(async () => true); // prod present
|
||||
|
||||
isValidVersionString.mockImplementationOnce(() => true);
|
||||
|
||||
getLsVersion.mockImplementationOnce(async () => undefined);
|
||||
getRequiredVersionRelease.mockImplementationOnce(async () => {
|
||||
return getRelease('v0.0.1');
|
||||
});
|
||||
await updateOrInstall('v0.0.1', lsPath, reporter);
|
||||
|
||||
expect(pathExists).toBeCalledTimes(2);
|
||||
expect(installTerraformLS).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
test('with out of date ls', async () => {
|
||||
getConfiguration.mockImplementationOnce(() => ({
|
||||
get: jest.fn(() => {
|
||||
// config('extensions').get<boolean>('autoUpdate', true);
|
||||
return true;
|
||||
}),
|
||||
has: jest.fn(),
|
||||
inspect: jest.fn(),
|
||||
then: jest.fn(),
|
||||
update: jest.fn(),
|
||||
}));
|
||||
pathExists
|
||||
.mockImplementationOnce(async () => false) // stg not present
|
||||
.mockImplementationOnce(async () => true); // prod present
|
||||
|
||||
isValidVersionString.mockImplementationOnce(() => {
|
||||
return true;
|
||||
});
|
||||
|
||||
getLsVersion.mockImplementationOnce(async () => 'v0.0.1');
|
||||
getRequiredVersionRelease.mockImplementationOnce(async () => {
|
||||
return getRelease('v0.0.2');
|
||||
});
|
||||
await updateOrInstall('v0.0.2', lsPath, reporter);
|
||||
|
||||
expect(pathExists).toBeCalledTimes(2);
|
||||
expect(installTerraformLS).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('should not install', () => {
|
||||
test('instead move staging to prod', async () => {
|
||||
// this mimics the stging path being present, which should trigger a rename
|
||||
pathExists.mockImplementationOnce(async () => true);
|
||||
|
||||
getRequiredVersionRelease.mockImplementationOnce(async () => {
|
||||
return getRelease('v0.0.1');
|
||||
});
|
||||
await updateOrInstall('v0.0.1', lsPath, reporter);
|
||||
|
||||
// expect stg to be renamed to prod
|
||||
expect(vscode.workspace.fs.rename).toBeCalledTimes(1);
|
||||
|
||||
expect(vscode.workspace.getConfiguration).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
test('ls present and autoupdate is false', async () => {
|
||||
getConfiguration.mockImplementationOnce(() => ({
|
||||
get: jest.fn(() => {
|
||||
// config('extensions').get<boolean>('autoUpdate', true);
|
||||
return false;
|
||||
}),
|
||||
has: jest.fn(),
|
||||
inspect: jest.fn(),
|
||||
then: jest.fn(),
|
||||
update: jest.fn(),
|
||||
}));
|
||||
|
||||
lsPath.installPath.mockImplementationOnce(() => 'installPath');
|
||||
lsPath.stgBinPath.mockImplementationOnce(() => 'stgbinpath');
|
||||
|
||||
isValidVersionString.mockImplementationOnce(() => {
|
||||
return true;
|
||||
});
|
||||
pathExists
|
||||
.mockImplementationOnce(async () => false) // stg
|
||||
.mockImplementationOnce(async () => true); // prod
|
||||
|
||||
getRequiredVersionRelease.mockImplementationOnce(async () => {
|
||||
return getRelease('v0.0.1');
|
||||
});
|
||||
await updateOrInstall('v0.0.1', lsPath, reporter);
|
||||
|
||||
expect(pathExists).toBeCalledTimes(2);
|
||||
expect(vscode.workspace.getConfiguration).toBeCalledTimes(1);
|
||||
expect(vscode.workspace.fs.rename).toBeCalledTimes(0);
|
||||
expect(getRequiredVersionRelease).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
test('invlaid azapi-lsp verison', async () => {
|
||||
getConfiguration.mockImplementationOnce(() => ({
|
||||
get: jest.fn(() => {
|
||||
// config('extensions').get<boolean>('autoUpdate', true);
|
||||
return true;
|
||||
}),
|
||||
has: jest.fn(),
|
||||
inspect: jest.fn(),
|
||||
then: jest.fn(),
|
||||
update: jest.fn(),
|
||||
}));
|
||||
|
||||
lsPath.installPath.mockImplementationOnce(() => 'installPath');
|
||||
lsPath.stgBinPath.mockImplementationOnce(() => 'stgbinpath');
|
||||
|
||||
isValidVersionString.mockImplementationOnce(() => {
|
||||
return true;
|
||||
});
|
||||
pathExists
|
||||
.mockImplementationOnce(async () => false) // stg
|
||||
.mockImplementationOnce(async () => false); // prod
|
||||
|
||||
getRequiredVersionRelease.mockImplementationOnce(() => {
|
||||
throw new Error('wahtever');
|
||||
});
|
||||
await updateOrInstall('v0.0.1', lsPath, reporter);
|
||||
|
||||
expect(pathExists).toBeCalledTimes(2);
|
||||
expect(getRequiredVersionRelease).toBeCalledTimes(1);
|
||||
expect(reporter.sendTelemetryException).toBeCalledTimes(1);
|
||||
expect(vscode.workspace.getConfiguration).toBeCalledTimes(1);
|
||||
expect(vscode.workspace.fs.rename).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
test('with current ls version', async () => {
|
||||
getConfiguration.mockImplementationOnce(() => ({
|
||||
get: jest.fn(() => {
|
||||
// config('extensions').get<boolean>('autoUpdate', true);
|
||||
return true;
|
||||
}),
|
||||
has: jest.fn(),
|
||||
inspect: jest.fn(),
|
||||
then: jest.fn(),
|
||||
update: jest.fn(),
|
||||
}));
|
||||
pathExists
|
||||
.mockImplementationOnce(async () => false) // stg not present
|
||||
.mockImplementationOnce(async () => true); // prod present
|
||||
|
||||
isValidVersionString.mockImplementationOnce(() => true);
|
||||
|
||||
getLsVersion.mockImplementationOnce(async () => 'v0.0.1');
|
||||
getRequiredVersionRelease.mockImplementationOnce(async () => {
|
||||
return getRelease('v0.0.1');
|
||||
});
|
||||
|
||||
await updateOrInstall('v0.0.1', lsPath, reporter);
|
||||
|
||||
expect(pathExists).toBeCalledTimes(2);
|
||||
expect(installTerraformLS).toBeCalledTimes(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function getRelease(version: string): Release {
|
||||
return {
|
||||
version: version,
|
||||
assets: [
|
||||
{
|
||||
downloadUrl: `https://github.com/Azure/azapi-lsp/releases/download/${version}/azapi-lsp_${version}_windows_amd64.zip`,
|
||||
name: `azapi-lsp_${version}_windows_amd64.zip`,
|
||||
},
|
||||
{
|
||||
downloadUrl: `https://github.com/Azure/azapi-lsp/releases/download/${version}/azapi-lsp_${version}_darwin_amd64.zip`,
|
||||
name: `azapi-lsp_${version}_darwin_amd64.zip`,
|
||||
},
|
||||
{
|
||||
downloadUrl: `https://github.com/Azure/azapi-lsp/releases/download/${version}/azapi-lsp_${version}_linux_amd64.zip`,
|
||||
name: `azapi-lsp_${version}_linux_amd64.zip`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
10
src/types.ts
10
src/types.ts
|
@ -6,3 +6,13 @@ export interface ExperimentalClientCapabilities {
|
|||
telemetryVersion?: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface Build {
|
||||
name: string;
|
||||
downloadUrl: string;
|
||||
}
|
||||
|
||||
export interface Release {
|
||||
version: string;
|
||||
assets: Build[];
|
||||
}
|
||||
|
|
28
src/utils.ts
28
src/utils.ts
|
@ -14,3 +14,31 @@ export function exec(cmd: string, args: readonly string[]): Promise<{ stdout: st
|
|||
export async function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
// A small wrapper around setTimeout which ensures that only a single timeout
|
||||
// timer can be running at a time. Attempts to add a new timeout silently fail.
|
||||
export class SingleInstanceTimeout {
|
||||
private timerLock = false;
|
||||
private timerId: NodeJS.Timeout | null = null;
|
||||
|
||||
public timeout(fn: (...args: any[]) => void, delay: number, ...args: any[]): void {
|
||||
if (!this.timerLock) {
|
||||
this.timerLock = true;
|
||||
this.timerId = setTimeout(
|
||||
() => {
|
||||
this.timerLock = false;
|
||||
fn();
|
||||
},
|
||||
delay,
|
||||
args,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
if (this.timerId) {
|
||||
clearTimeout(this.timerId);
|
||||
}
|
||||
this.timerLock = false;
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче