This commit is contained in:
Bernie White 2021-05-20 14:31:23 +10:00 коммит произвёл GitHub
Родитель f647c586bd
Коммит 7f89e52f79
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
14 изменённых файлов: 808 добавлений и 204 удалений

5
.vscode/settings.json поставляемый
Просмотреть файл

@ -30,5 +30,8 @@
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
"typescript.tsc.autoDetect": "off"
"typescript.tsc.autoDetect": "off",
"cSpell.words": [
"pwsh"
]
}

Просмотреть файл

@ -13,10 +13,14 @@ Continue reading to see the changes included in the latest version.
## Unreleased
- New features:
- Added `PSRule: Run analysis` quick task to call `Assert-PSRule` for the current workspace. [#226](https://github.com/microsoft/PSRule-vscode/issues/226)
- To configure set `path`, `inputPath`, `baseline`, `module`, and `outcome` per task.
- The default task will run analysis in the current workspace using rules in `.ps-rule/`.
- General improvements:
- Preview channel will notify that a stable version is available. [#235](https://github.com/microsoft/PSRule-vscode/issues/235)
- Engineering:
- Bump vscode engine to v1.55.0. [#241](https://github.com/microsoft/PSRule-vscode/pull/241)
- Bump vscode engine to v1.56.0. [#241](https://github.com/microsoft/PSRule-vscode/pull/241)
## v1.0.0

Просмотреть файл

@ -3,7 +3,7 @@
Validate infrastructure as code (IaC) and DevOps repositories using the PSRule PowerShell module.
PSRule is powerful, feature rich, and highly customizable to meet your needs.
![ext-stable-version-badge] ![ext-stable-installs-badge] ![module-version-badge]
![module-version-badge]
This extension is available in two release channels:
@ -39,10 +39,33 @@ Channel | Description | Version/ downloads
</p>
- Adds snippets for creating markdown documentation.
- **Quick documentation** &mdash; create rule documentation to provide rule recommendations and examples.
- **Quick documentation** &mdash; create rule documentation to provide rule recommendations and examples.
- Trigger IntelliSense by typing `rule` in a `.md` file.
IntelliSense can also be triggered by using the shortcut `Ctrl+Space`.
### Quick tasks
<p align="center">
<img src="./docs/images/tasks-provider.png" alt="Built-in tasks shown in task list" />
</p>
- Adds quick tasks for analysis directly from Visual Studio Code.
- **Run analysis** &mdash; Runs rules against files in the current workspace.
- _Input path_, _Baseline_, _Modules_, and _Outcome_ options can be configured per task.
- _Output as_, and showing a _Not processed warning_ options can be configured by workspace or user.
- Rule stored in `.ps-rule/` are automatically used by default.
## Configuration
In addition to configuring the [ps-rule.yaml] options file, the following settings are available.
Name | Description
---- | -----------
`PSRule.execution.notProcessedWarning` | Warn when objects are not processed by any rule.
`PSRule.experimental.enabled` | Enables experimental features in the PSRule extension.
`PSRule.notifications.showChannelUpgrade` | Specifies if a notification to switch to the stable channel is shown on start up.
`PSRule.output.as` | Configures the output of analysis tasks, either summary or detailed.
## Support
This project uses GitHub Issues to track bugs and feature requests.
@ -110,3 +133,4 @@ This project is [licensed under the MIT License][license].
[license]: https://github.com/Microsoft/PSRule-vscode/blob/main/LICENSE
[chat]: https://gitter.im/PSRule/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
[chat-badge]: https://img.shields.io/static/v1.svg?label=chat&message=on%20gitter&color=informational&logo=gitter
[ps-rule.yaml]: https://microsoft.github.io/PSRule/concepts/PSRule/en-US/about_PSRule_Options.html

Двоичные данные
docs/images/tasks-provider.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 23 KiB

151
package-lock.json сгенерированный
Просмотреть файл

@ -135,9 +135,9 @@
"dev": true
},
"@types/node": {
"version": "15.3.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-15.3.0.tgz",
"integrity": "sha512-8/bnjSZD86ZfpBsDlCIkNXIvm+h6wi9g7IqL+kmFkQ+Wvu3JrasgLElfiPgoo8V8vVfnEi0QVS12gbl94h9YsQ==",
"version": "15.3.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-15.3.1.tgz",
"integrity": "sha512-weaeiP4UF4XgF++3rpQhpIJWsCTS4QJw5gvBhQu6cFIxTwyxWIe3xbnrY/o2lTCQ0lsdb8YIUDUvLR4Vuz5rbw==",
"dev": true
},
"@types/vscode": {
@ -147,142 +147,71 @@
"dev": true
},
"@typescript-eslint/eslint-plugin": {
"version": "4.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.23.0.tgz",
"integrity": "sha512-tGK1y3KIvdsQEEgq6xNn1DjiFJtl+wn8JJQiETtCbdQxw1vzjXyAaIkEmO2l6Nq24iy3uZBMFQjZ6ECf1QdgGw==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.24.0.tgz",
"integrity": "sha512-qbCgkPM7DWTsYQGjx9RTuQGswi+bEt0isqDBeo+CKV0953zqI0Tp7CZ7Fi9ipgFA6mcQqF4NOVNwS/f2r6xShw==",
"dev": true,
"requires": {
"@typescript-eslint/experimental-utils": "4.23.0",
"@typescript-eslint/scope-manager": "4.23.0",
"@typescript-eslint/experimental-utils": "4.24.0",
"@typescript-eslint/scope-manager": "4.24.0",
"debug": "^4.1.1",
"functional-red-black-tree": "^1.0.1",
"lodash": "^4.17.15",
"regexpp": "^3.0.0",
"semver": "^7.3.2",
"tsutils": "^3.17.1"
},
"dependencies": {
"@typescript-eslint/scope-manager": {
"version": "4.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.23.0.tgz",
"integrity": "sha512-ZZ21PCFxPhI3n0wuqEJK9omkw51wi2bmeKJvlRZPH5YFkcawKOuRMQMnI8mH6Vo0/DoHSeZJnHiIx84LmVQY+w==",
"dev": true,
"requires": {
"@typescript-eslint/types": "4.23.0",
"@typescript-eslint/visitor-keys": "4.23.0"
}
},
"@typescript-eslint/types": {
"version": "4.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.23.0.tgz",
"integrity": "sha512-oqkNWyG2SLS7uTWLZf6Sr7Dm02gA5yxiz1RP87tvsmDsguVATdpVguHr4HoGOcFOpCvx9vtCSCyQUGfzq28YCw==",
"dev": true
},
"@typescript-eslint/visitor-keys": {
"version": "4.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.23.0.tgz",
"integrity": "sha512-5PNe5cmX9pSifit0H+nPoQBXdbNzi5tOEec+3riK+ku4e3er37pKxMKDH5Ct5Y4fhWxcD4spnlYjxi9vXbSpwg==",
"dev": true,
"requires": {
"@typescript-eslint/types": "4.23.0",
"eslint-visitor-keys": "^2.0.0"
}
}
}
},
"@typescript-eslint/experimental-utils": {
"version": "4.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.23.0.tgz",
"integrity": "sha512-WAFNiTDnQfrF3Z2fQ05nmCgPsO5o790vOhmWKXbbYQTO9erE1/YsFot5/LnOUizLzU2eeuz6+U/81KV5/hFTGA==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.24.0.tgz",
"integrity": "sha512-IwTT2VNDKH1h8RZseMH4CcYBz6lTvRoOLDuuqNZZoThvfHEhOiZPQCow+5El3PtyxJ1iDr6UXZwYtE3yZQjhcw==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.3",
"@typescript-eslint/scope-manager": "4.23.0",
"@typescript-eslint/types": "4.23.0",
"@typescript-eslint/typescript-estree": "4.23.0",
"@typescript-eslint/scope-manager": "4.24.0",
"@typescript-eslint/types": "4.24.0",
"@typescript-eslint/typescript-estree": "4.24.0",
"eslint-scope": "^5.0.0",
"eslint-utils": "^2.0.0"
},
"dependencies": {
"@typescript-eslint/scope-manager": {
"version": "4.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.23.0.tgz",
"integrity": "sha512-ZZ21PCFxPhI3n0wuqEJK9omkw51wi2bmeKJvlRZPH5YFkcawKOuRMQMnI8mH6Vo0/DoHSeZJnHiIx84LmVQY+w==",
"dev": true,
"requires": {
"@typescript-eslint/types": "4.23.0",
"@typescript-eslint/visitor-keys": "4.23.0"
}
},
"@typescript-eslint/types": {
"version": "4.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.23.0.tgz",
"integrity": "sha512-oqkNWyG2SLS7uTWLZf6Sr7Dm02gA5yxiz1RP87tvsmDsguVATdpVguHr4HoGOcFOpCvx9vtCSCyQUGfzq28YCw==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
"version": "4.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.23.0.tgz",
"integrity": "sha512-5Sty6zPEVZF5fbvrZczfmLCOcby3sfrSPu30qKoY1U3mca5/jvU5cwsPb/CO6Q3ByRjixTMIVsDkqwIxCf/dMw==",
"dev": true,
"requires": {
"@typescript-eslint/types": "4.23.0",
"@typescript-eslint/visitor-keys": "4.23.0",
"debug": "^4.1.1",
"globby": "^11.0.1",
"is-glob": "^4.0.1",
"semver": "^7.3.2",
"tsutils": "^3.17.1"
}
},
"@typescript-eslint/visitor-keys": {
"version": "4.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.23.0.tgz",
"integrity": "sha512-5PNe5cmX9pSifit0H+nPoQBXdbNzi5tOEec+3riK+ku4e3er37pKxMKDH5Ct5Y4fhWxcD4spnlYjxi9vXbSpwg==",
"dev": true,
"requires": {
"@typescript-eslint/types": "4.23.0",
"eslint-visitor-keys": "^2.0.0"
}
}
}
},
"@typescript-eslint/parser": {
"version": "4.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.23.0.tgz",
"integrity": "sha512-wsvjksHBMOqySy/Pi2Q6UuIuHYbgAMwLczRl4YanEPKW5KVxI9ZzDYh3B5DtcZPQTGRWFJrfcbJ6L01Leybwug==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.24.0.tgz",
"integrity": "sha512-dj1ZIh/4QKeECLb2f/QjRwMmDArcwc2WorWPRlB8UNTZlY1KpTVsbX7e3ZZdphfRw29aTFUSNuGB8w9X5sS97w==",
"dev": true,
"requires": {
"@typescript-eslint/scope-manager": "4.23.0",
"@typescript-eslint/types": "4.23.0",
"@typescript-eslint/typescript-estree": "4.23.0",
"@typescript-eslint/scope-manager": "4.24.0",
"@typescript-eslint/types": "4.24.0",
"@typescript-eslint/typescript-estree": "4.24.0",
"debug": "^4.1.1"
}
},
"@typescript-eslint/scope-manager": {
"version": "4.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.23.0.tgz",
"integrity": "sha512-ZZ21PCFxPhI3n0wuqEJK9omkw51wi2bmeKJvlRZPH5YFkcawKOuRMQMnI8mH6Vo0/DoHSeZJnHiIx84LmVQY+w==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.24.0.tgz",
"integrity": "sha512-9+WYJGDnuC9VtYLqBhcSuM7du75fyCS/ypC8c5g7Sdw7pGL4NDTbeH38eJPfzIydCHZDoOgjloxSAA3+4l/zsA==",
"dev": true,
"requires": {
"@typescript-eslint/types": "4.23.0",
"@typescript-eslint/visitor-keys": "4.23.0"
"@typescript-eslint/types": "4.24.0",
"@typescript-eslint/visitor-keys": "4.24.0"
}
},
"@typescript-eslint/types": {
"version": "4.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.23.0.tgz",
"integrity": "sha512-oqkNWyG2SLS7uTWLZf6Sr7Dm02gA5yxiz1RP87tvsmDsguVATdpVguHr4HoGOcFOpCvx9vtCSCyQUGfzq28YCw==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.24.0.tgz",
"integrity": "sha512-tkZUBgDQKdvfs8L47LaqxojKDE+mIUmOzdz7r+u+U54l3GDkTpEbQ1Jp3cNqqAU9vMUCBA1fitsIhm7yN0vx9Q==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
"version": "4.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.23.0.tgz",
"integrity": "sha512-5Sty6zPEVZF5fbvrZczfmLCOcby3sfrSPu30qKoY1U3mca5/jvU5cwsPb/CO6Q3ByRjixTMIVsDkqwIxCf/dMw==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.24.0.tgz",
"integrity": "sha512-kBDitL/by/HK7g8CYLT7aKpAwlR8doshfWz8d71j97n5kUa5caHWvY0RvEUEanL/EqBJoANev8Xc/mQ6LLwXGA==",
"dev": true,
"requires": {
"@typescript-eslint/types": "4.23.0",
"@typescript-eslint/visitor-keys": "4.23.0",
"@typescript-eslint/types": "4.24.0",
"@typescript-eslint/visitor-keys": "4.24.0",
"debug": "^4.1.1",
"globby": "^11.0.1",
"is-glob": "^4.0.1",
@ -291,12 +220,12 @@
}
},
"@typescript-eslint/visitor-keys": {
"version": "4.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.23.0.tgz",
"integrity": "sha512-5PNe5cmX9pSifit0H+nPoQBXdbNzi5tOEec+3riK+ku4e3er37pKxMKDH5Ct5Y4fhWxcD4spnlYjxi9vXbSpwg==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.24.0.tgz",
"integrity": "sha512-4ox1sjmGHIxjEDBnMCtWFFhErXtKA1Ec0sBpuz0fqf3P+g3JFGyTxxbF06byw0FRsPnnbq44cKivH7Ks1/0s6g==",
"dev": true,
"requires": {
"@typescript-eslint/types": "4.23.0",
"@typescript-eslint/types": "4.24.0",
"eslint-visitor-keys": "^2.0.0"
}
},
@ -770,9 +699,9 @@
"dev": true
},
"esbuild": {
"version": "0.11.23",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.11.23.tgz",
"integrity": "sha512-iaiZZ9vUF5wJV8ob1tl+5aJTrwDczlvGP0JoMmnpC2B0ppiMCu8n8gmy5ZTGl5bcG081XBVn+U+jP+mPFm5T5Q==",
"version": "0.12.1",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.1.tgz",
"integrity": "sha512-WfQ00MKm/Y4ysz1u9PCUAsV66k5lbrcEvS6aG9jhBIavpB94FBdaWeBkaZXxCZB4w+oqh+j4ozJFWnnFprOXbg==",
"dev": true
},
"escalade": {

Просмотреть файл

@ -35,19 +35,84 @@
"preview": true,
"activationEvents": [
"onLanguage:powershell",
"workspaceContains:/ps-rule.yaml"
"workspaceContains:/ps-rule.yaml",
"workspaceContains:**/*.Rule.yaml",
"workspaceContains:**/*.Rule.ps1",
"onCommand:workbench.action.tasks.runTask"
],
"main": "./out/dist/extension.js",
"main": "./out/dist/main.js",
"contributes": {
"configuration": [
{
"title": "PSRule",
"properties": {
"PSRule.execution.notProcessedWarning": {
"type": "boolean",
"default": false,
"description": "Warn when objects are not processed by any rule.",
"scope": "window"
},
"PSRule.experimental.enabled": {
"type": "boolean",
"default": false,
"description": "Enables experimental features in the PSRule extension.",
"scope": "application"
},
"PSRule.notifications.showChannelUpgrade": {
"type": "boolean",
"default": true,
"description": "Specifies if a notification to switch to the stable channel is shown on start up.",
"scope": "application"
},
"PSRule.output.as": {
"type": "string",
"default": "Summary",
"description": "Configures the output of analysis tasks, either summary or detailed.",
"enum": [
"Detail",
"Summary"
],
"scope": "window"
}
}
}
],
"taskDefinitions": [
{
"type": "PSRule",
"required": [],
"properties": {
"path": {
"type": "string",
"description": "The path containing rules.",
"default": "./.ps-rule/"
},
"inputPath": {
"type": "string",
"description": "The path PSRule will look for input files. Defaults to workspace root.",
"default": "."
},
"baseline": {
"type": "string",
"description": "The name of a PSRule baseline to use. Baselines can be used from modules or specified in a separate file."
},
"modules": {
"type": "array",
"description": "The name of one or more modules to use."
},
"outcome": {
"type": "array",
"items": {
"enum": [
"Pass",
"Fail",
"Error"
]
},
"default": [
"Fail",
"Error"
]
}
}
}
@ -128,7 +193,7 @@
"pretest": "npm run compile",
"test": "node ./out/dist/test/runTest.js",
"vscode:prepublish": "npm run -S esbuild-base -- --minify",
"esbuild-base": "esbuild ./src/extension.ts --bundle --outfile=out/dist/extension.js --external:vscode --format=cjs --platform=node",
"esbuild-base": "esbuild ./src/main.ts --bundle --outfile=out/dist/main.js --external:vscode --format=cjs --platform=node",
"esbuild": "npm run -S esbuild-base -- --sourcemap",
"esbuild-watch": "npm run -S esbuild-base -- --sourcemap --watch"
},
@ -137,16 +202,17 @@
},
"extensionDependencies": [
"vscode.powershell",
"ms-vscode.powershell",
"redhat.vscode-yaml"
],
"devDependencies": {
"@types/glob": "^7.1.3",
"@types/mocha": "^8.2.2",
"@types/node": "~15.3.0",
"@types/node": "^15.3.1",
"@types/vscode": "1.56.0",
"@typescript-eslint/eslint-plugin": "^4.23.0",
"@typescript-eslint/parser": "^4.23.0",
"esbuild": "^0.11.23",
"@typescript-eslint/eslint-plugin": "^4.24.0",
"@typescript-eslint/parser": "^4.24.0",
"esbuild": "^0.12.1",
"eslint": "^7.26.0",
"glob": "^7.1.7",
"lodash": ">=4.17.21",

91
src/configuration.ts Normal file
Просмотреть файл

@ -0,0 +1,91 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
'use strict';
import { ConfigurationChangeEvent, ExtensionContext, workspace } from 'vscode';
import { configurationItemPrefix } from './consts';
/**
* The output of analysis tasks.
*/
export enum OutputAs {
Detail = 0,
Summary = 1,
}
/**
* PSRule extension settings.
*/
export interface ISetting {
executionNotProcessedWarning: boolean;
outputAs: OutputAs;
}
/**
* Default configuration for PSRule extension settings.
*/
const globalDefaults: ISetting = {
executionNotProcessedWarning: false,
outputAs: OutputAs.Summary,
};
/**
* A configuration manager class for PSRule.
*/
export class ConfigurationManager {
private current: ISetting;
private readonly default: ISetting;
/**
* A flag for when setting require reload.
*/
private pendingLoad: boolean = true;
constructor(setting?: ISetting) {
this.default = setting ?? globalDefaults;
this.current = this.default;
this.loadSettings();
}
static configure(context: ExtensionContext) {
if (context) {
context.subscriptions.push(
workspace.onDidChangeConfiguration(
configuration.onConfigurationChanged,
configuration
)
);
}
}
public get(): ISetting {
if (this.pendingLoad) {
this.loadSettings();
}
return this.current;
}
private onConfigurationChanged(e: ConfigurationChangeEvent) {
if (!e.affectsConfiguration(configurationItemPrefix)) {
return;
}
this.pendingLoad = true;
}
private loadSettings(): void {
const config = workspace.getConfiguration(configurationItemPrefix);
// Read settings
this.current.executionNotProcessedWarning =
config.get<boolean>('execution.notProcessedWarning') ??
this.default.executionNotProcessedWarning;
this.current.outputAs = config.get<OutputAs>('output.as') ?? this.default.outputAs;
// Clear dirty settings flag
this.pendingLoad = false;
}
}
export const configuration = new ConfigurationManager();

Просмотреть файл

@ -6,91 +6,139 @@
import * as path from 'path';
import * as vscode from 'vscode';
import { logger } from './logger';
import { PSRuleTaskProvider } from './tasks';
import { ConfigurationManager } from './configuration';
import { pwsh } from './powershell';
/**
* Activate PSRule extension.
* @param context An extension context.
*/
export function activate(context: vscode.ExtensionContext): void {
// Check the extension
checkExtension(context);
export let taskManager: PSRuleTaskProvider | undefined;
export interface ExtensionInfo {
id: string;
version: string;
channel: string;
}
/**
* Deactivate PSRule extension.
*/
export function deactivate(): void {
if (logger) {
logger.dispose();
export class ExtensionManager implements vscode.Disposable {
private _info!: ExtensionInfo;
constructor() {}
public get info(): Promise<ExtensionInfo> {
const parent = this;
return new Promise<ExtensionInfo>((resolve, reject) => {
if (parent._info) {
resolve(parent._info);
} else {
setTimeout(function (): void {
if (parent._info) {
resolve(parent._info);
} else {
reject('Failed to get info.');
}
}, 1000);
}
});
}
public activate(context: vscode.ExtensionContext) {
this._info = this.checkExtension(context);
ConfigurationManager.configure(context);
pwsh.configure(this._info);
taskManager = new PSRuleTaskProvider(logger, context);
taskManager.register();
}
public dispose(): void {
if (taskManager) {
taskManager.dispose();
}
if (pwsh) {
pwsh.dispose();
}
if (logger) {
logger.dispose();
}
}
/**
* Check channel and version of the extension activated.
* @param context An extension context.
*/
private checkExtension(context: vscode.ExtensionContext): ExtensionInfo {
const extensionVersionKey = 'ps-rule-extension-version';
// Get channel
let extensionId = 'bewhite.psrule-vscode';
let extensionChannel = 'stable';
if (path.basename(context.globalStorageUri.fsPath) === 'bewhite.psrule-vscode-preview') {
extensionId = 'bewhite.psrule-vscode-preview';
extensionChannel = 'preview';
}
if (path.basename(context.globalStorageUri.fsPath) === 'bewhite.psrule-vscode-dev') {
extensionId = 'bewhite.psrule-vscode-dev';
extensionChannel = 'dev';
}
logger.verbose(`Running extension channel: ${extensionChannel}`);
// Get current version
const extension = vscode.extensions.getExtension(extensionId)!;
const extensionVersion: string = extension.packageJSON.version;
logger.verbose(`Running extension version: ${extensionVersion}`);
// Get last version
const lastVersion = context.globalState.get(extensionVersionKey);
// Save the extension version
context.globalState.update(extensionVersionKey, extensionVersion);
// Determine if the channel upgrade message is shown
const showChannelUpgrade: boolean = vscode.workspace
.getConfiguration('PSRule.notifications')
.get('showChannelUpgrade', true);
if ((extensionChannel === 'preview' || extensionChannel === 'dev') && showChannelUpgrade) {
const showReleaseNotes = 'Show Release Notes';
const showExtension = 'Show Extension';
const alwaysIgnore = 'Always Ignore';
vscode.window
.showInformationMessage(
`You are running the ${extensionChannel} version of PSRule. A stable version is available.`,
showReleaseNotes,
showExtension,
alwaysIgnore
)
.then((choice) => {
if (choice === showReleaseNotes) {
vscode.commands.executeCommand(
'markdown.showPreview',
vscode.Uri.file(path.resolve(__dirname, '../../CHANGELOG.md'))
);
}
if (choice === showExtension) {
vscode.commands.executeCommand(
'workbench.extensions.search',
'bewhite.psrule-vscode'
);
}
if (choice === alwaysIgnore) {
vscode.workspace
.getConfiguration('PSRule.notifications')
.update('showChannelUpgrade', false, vscode.ConfigurationTarget.Global);
}
});
}
const result: ExtensionInfo = {
id: extensionId,
version: extensionVersion,
channel: extensionChannel,
};
return result;
}
}
/**
* Check channel and version of the extension activated.
* @param context An extension context.
*/
function checkExtension(context: vscode.ExtensionContext): void {
const extensionVersionKey = 'ps-rule-extension-version';
// Get channel
let extensionId = 'bewhite.psrule-vscode';
let extensionChannel = 'stable';
if (path.basename(context.globalStorageUri.fsPath) === 'bewhite.psrule-vscode-preview') {
extensionId = 'bewhite.psrule-vscode-preview';
extensionChannel = 'preview';
}
if (path.basename(context.globalStorageUri.fsPath) === 'bewhite.psrule-vscode-dev') {
extensionId = 'bewhite.psrule-vscode-dev';
extensionChannel = 'dev';
}
logger.verbose(`Running extension channel: ${extensionChannel}`);
// Get current version
const extension = vscode.extensions.getExtension(extensionId)!;
const extensionVersion: string = extension.packageJSON.version;
logger.verbose(`Running extension version: ${extensionVersion}`);
// Get last version
const lastVersion = context.globalState.get(extensionVersionKey);
// Save the extension version
context.globalState.update(extensionVersionKey, extensionVersion);
// Determine if the channel upgrade message is shown
const showChannelUpgrade: boolean = vscode.workspace
.getConfiguration('PSRule.notifications')
.get('showChannelUpgrade', true);
if ((extensionChannel === 'preview' || extensionChannel === 'dev') && showChannelUpgrade) {
const showReleaseNotes = 'Show Release Notes';
const showExtension = 'Show Extension';
const alwaysIgnore = 'Always Ignore';
vscode.window
.showInformationMessage(
`You are running the ${extensionChannel} version of PSRule. A stable version is available.`,
showReleaseNotes,
showExtension,
alwaysIgnore
)
.then((choice) => {
if (choice === showReleaseNotes) {
vscode.commands.executeCommand(
'markdown.showPreview',
vscode.Uri.file(path.resolve(__dirname, '../../CHANGELOG.md'))
);
}
if (choice === showExtension) {
vscode.commands.executeCommand(
'workbench.extensions.search',
'bewhite.psrule-vscode'
);
}
if (choice === alwaysIgnore) {
vscode.workspace
.getConfiguration('PSRule.notifications')
.update('showChannelUpgrade', false, vscode.ConfigurationTarget.Global);
}
});
}
}
export const ext: ExtensionManager = new ExtensionManager();

24
src/main.ts Normal file
Просмотреть файл

@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
'use strict';
import * as vscode from 'vscode';
import { ext } from './extension';
/**
* Activate PSRule extension.
* @param context An extension context.
*/
export function activate(context: vscode.ExtensionContext): void {
ext.activate(context);
}
/**
* Deactivate PSRule extension.
*/
export function deactivate(): void {
if (ext) {
ext.dispose();
}
}

78
src/powershell.ts Normal file
Просмотреть файл

@ -0,0 +1,78 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
'use strict';
import * as vscode from 'vscode';
import { ExtensionInfo } from './extension';
import { logger } from './logger';
/**
* External interface from PowerShell extension.
*/
interface IExternalPowerShellDetails {
exePath: string;
version: string;
displayName: string;
architecture: string;
}
/**
* External interface from PowerShell extension.
*/
interface IPowerShellExtensionClient {
registerExternalExtension(id: string, apiVersion?: string): string;
unregisterExternalExtension(uuid: string): boolean;
getPowerShellVersionDetails(uuid: string): Promise<IExternalPowerShellDetails>;
}
export class PowerShellExtension implements vscode.Disposable {
private readonly extension: vscode.Extension<IPowerShellExtensionClient> | undefined;
private _version!: string;
private _path!: string;
private uuid!: string;
constructor() {
this.extension = this.getExtension();
if (this.extension !== undefined && !this.extension.isActive) {
this.extension.activate();
}
}
public get path(): string {
return this._path;
}
public configure(info: ExtensionInfo): void {
const powerShellExtensionClient = this.extension!.exports as IPowerShellExtensionClient;
this.uuid = powerShellExtensionClient.registerExternalExtension(info.id, 'v1');
powerShellExtensionClient
.getPowerShellVersionDetails(this.uuid)
.then((v) => this.handlePowerShell(v));
}
public dispose(): void {
if (this.extension && this.uuid !== undefined) {
const powerShellExtensionClient = this.extension!.exports as IPowerShellExtensionClient;
powerShellExtensionClient.unregisterExternalExtension(this.uuid);
}
}
private handlePowerShell(value: IExternalPowerShellDetails): void {
this._version = value.version;
this._path = value.exePath;
logger.verbose(`Using PowerShell ${this._version} from ${this._path}`);
}
private getExtension(): vscode.Extension<IPowerShellExtensionClient> | undefined {
return (
vscode.extensions.getExtension<IPowerShellExtensionClient>(
'ms-vscode.powershell-preview'
) ?? vscode.extensions.getExtension<IPowerShellExtensionClient>('ms-vscode.powershell')
);
}
}
export const pwsh = new PowerShellExtension();

288
src/tasks.ts Normal file
Просмотреть файл

@ -0,0 +1,288 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
'use strict';
import * as fs from 'fs';
import * as path from 'path';
import * as vscode from 'vscode';
import { defaultOptionsFile } from './consts';
import { ILogger } from './logger';
import { configuration } from './configuration';
import { pwsh } from './powershell';
const emptyTasks: vscode.Task[] = [];
interface IContext {
readonly logger: ILogger;
readonly extensionContext: vscode.ExtensionContext;
}
interface PSRuleTaskDefinition extends vscode.TaskDefinition {
/**
* The a path to rules to use for analysis.
*/
path?: string;
/**
* The input path to run.
*/
inputPath?: string;
/**
* An optional baseline to use.
*/
baseline?: string;
/**
* Rule modules to use for analysis.
*/
modules?: string[];
outcome?: string[];
}
/**
* A task provider for PSRule.
*/
export class PSRuleTaskProvider implements vscode.TaskProvider {
// Fields
private readonly context: IContext;
private static taskType: string = 'PSRule';
private tasks: vscode.Task[] | undefined;
// We use a CustomExecution task when state needs to be shared across runs of the task or when
// the task requires use of some VS Code API to run.
// If you don't need to share state between runs and if you don't need to execute VS Code API in your task,
// then a simple ShellExecution or ProcessExecution should be enough.
// Since our build has this shared state, the CustomExecution is used below.
private sharedState: string | undefined;
private providerRegistration!: vscode.Disposable;
constructor(logger: ILogger, extensionContext: vscode.ExtensionContext) {
this.context = { logger, extensionContext };
}
public dispose() {
// Do nothing yet
if (this.providerRegistration) {
this.providerRegistration.dispose();
}
}
public register(): void {
// Register a task provider
this.providerRegistration = vscode.tasks.registerTaskProvider(
PSRuleTaskProvider.taskType,
this
);
this.context.logger.verbose('Registered task provider');
}
/**
* Get tasks for Visual Studio Code API.
* @returns Returns a list of tasks.
*/
public async provideTasks(): Promise<vscode.Task[]> {
return this.getTasks();
}
/**
* Complete a task object for Visual Studio Code.
* @param _task A task object that might need completing.
* @returns A completed Visual Studio Code task.
*/
public resolveTask(_task: vscode.Task): vscode.Task | undefined {
const definition: PSRuleTaskDefinition = <any>_task.definition;
const scope = _task.scope as vscode.WorkspaceFolder;
return this.createTask(
'Run analysis',
scope,
definition.path,
definition.inputPath,
definition.baseline,
definition.modules,
definition.outcome,
undefined,
definition
);
}
/**
* Get a list of PSRule tasks for each Visual Studio Code workspace.
* @returns A list of tasks.
*/
private async getTasks(): Promise<vscode.Task[]> {
const folders = vscode.workspace.workspaceFolders;
if (!folders) {
return Promise.resolve([]);
}
const result: vscode.Task[] = [];
for (let i = 0, len = folders.length; i < len; i++) {
if (this.isEnabled(folders[i])) {
const tasks = await this.getWorkspaceTasks(folders[i]);
result.push(...tasks);
}
}
return result;
}
private isEnabled(folder: vscode.WorkspaceFolder): boolean {
return true;
}
private async exists(file: string): Promise<boolean> {
return new Promise<boolean>((resolve) => {
fs.exists(file, (value) => {
resolve(value);
});
});
}
private async readFile(file: string): Promise<string> {
return new Promise<string>((resolve, reject) => {
fs.readFile(file, (err, data) => {
if (err) {
reject(err);
}
resolve(data.toString());
});
});
}
private async getWorkspaceTasks(folder: vscode.WorkspaceFolder): Promise<vscode.Task[]> {
if (folder.uri.scheme !== 'file') {
return emptyTasks;
}
const rootPath = folder.uri.fsPath;
const optionFilePath = path.join(rootPath, defaultOptionsFile);
if (!(await this.exists(optionFilePath))) {
return emptyTasks;
}
try {
const result: vscode.Task[] = [];
let t = this.createTask(
'Run analysis',
folder,
undefined,
undefined,
undefined,
undefined,
undefined
);
result.push(t);
return result;
} catch (e) {
return emptyTasks;
}
}
/**
* Creates a task.
* @param name The name of the task.
* @param path The root of the workspace.
* @param baseline An optional baseline.
* @param matcher A task filter.
*/
private createTask(
name: string,
folder: vscode.WorkspaceFolder | undefined,
path?: string,
inputPath?: string,
baseline?: string,
modules?: string[],
outcome?: string[],
matcher?: any,
definition?: PSRuleTaskDefinition
): vscode.Task {
if (definition === undefined) {
definition = {
type: PSRuleTaskProvider.taskType,
};
}
function getTaskName() {
return name;
}
function getCmd(): string {
let params = '';
// Path
if (path !== undefined && path !== '') {
params += ` -Path '${path}'`;
} else {
params += " -Path './.ps-rule/'";
}
if (inputPath !== undefined && inputPath !== '') {
params += ` -InputPath '${inputPath}'`;
} else {
params += ` -InputPath .`;
}
// Baseline
if (baseline !== undefined && baseline !== '') {
params += ` -Baseline '${baseline}'`;
}
// Modules
if (modules !== undefined && modules.length > 0) {
for (let i = 0; i < modules.length; i++) {
if (i > 0) {
params += `, ${modules[i]}`;
} else {
params += ` -Module ${modules[i]}`;
}
}
}
// Outcome
if (outcome !== undefined && outcome.length > 0) {
for (let i = 0; i < outcome.length; i++) {
if (i > 0) {
params += `, ${outcome[i]}`;
} else {
params += ` -Outcome ${outcome[i]}`;
}
}
} else {
params += ' -Outcome Fail, Error';
}
return `Assert-PSRule -Format File${params};`;
}
const taskName = getTaskName();
const executionNotProcessedWarning = configuration.get().executionNotProcessedWarning;
const outputAs = configuration.get().outputAs;
// Return the task instance
return new vscode.Task(
definition,
folder ?? vscode.TaskScope.Workspace,
taskName,
PSRuleTaskProvider.taskType,
new vscode.ShellExecution(getCmd(), {
executable: pwsh.path,
shellArgs: ['-NoLogo', '-NoProfile', '-NonInteractive', '-Command'],
env: {
PSRULE_OUTPUT_STYLE: 'Client',
PSRULE_OUTPUT_AS: outputAs.toString(),
PSRULE_OUTPUT_CULTURE: vscode.env.language,
PSRULE_OUTPUT_BANNER: 'Minimal',
PSRULE_EXECUTION_NOTPROCESSEDWARNING: executionNotProcessedWarning
? 'true'
: 'false',
},
}),
matcher
);
}
}

Просмотреть файл

@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import * as assert from 'assert';
import { configuration, OutputAs } from '../../configuration';
suite('ConfigurationManager tests', () => {
test('Defaults', () => {
assert.strictEqual(configuration.get().executionNotProcessedWarning, false);
assert.strictEqual(configuration.get().outputAs, OutputAs.Summary);
});
});

Просмотреть файл

@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import * as assert from 'assert';
import * as Extension from '../../extension';
suite('Extension tests', () => {
test('Get extension info', () => {
Extension.ext.info
.then((info) => {
assert.strictEqual('dev', info.channel);
assert.strictEqual('0.0.1', info.version);
assert.strictEqual('bewhite.psrule-vscode-dev', info.id);
})
.catch((reason) => {
assert.fail(`Failed to get extension info. ${reason}`);
});
});
});

Просмотреть файл

@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import * as assert from 'assert';
import * as Extension from '../../extension';
suite('PSRuleTaskProvider tests', () => {
test('Call taskManager', () => {
Extension.taskManager
?.provideTasks()
.then((t) => {
assert.strictEqual(1, t.length);
})
.catch((reason) => {
assert.fail(`Failed to get tasks. ${reason}`);
});
});
});