From 74e3c3f8df82e3723cf9287bb00256d8fbd1aabe Mon Sep 17 00:00:00 2001 From: Bill Sager Date: Fri, 29 Mar 2024 15:12:45 -0700 Subject: [PATCH 01/33] Shell for Home View --- extensions/vscode/package.json | 12 +- extensions/vscode/src/extension.ts | 2 + extensions/vscode/src/utils/getNonce.ts | 19 + extensions/vscode/src/utils/getUri.ts | 22 + extensions/vscode/src/views/homeView.ts | 127 ++ extensions/vscode/tsconfig.json | 3 +- .../vscode/webviews/homeView/.gitignore | 6 + extensions/vscode/webviews/homeView/README.md | 10 + extensions/vscode/webviews/homeView/env.d.ts | 1 + .../vscode/webviews/homeView/index.html | 13 + .../webviews/homeView/package-lock.json | 1597 +++++++++++++++++ .../vscode/webviews/homeView/package.json | 31 + .../webviews/homeView/public/favicon.ico | Bin 0 -> 4286 bytes .../vscode/webviews/homeView/src/App.vue | 5 + .../vscode/webviews/homeView/src/main.ts | 8 + .../vscode/webviews/homeView/tsconfig.json | 11 + .../homeView/tsconfig.vite-config.json | 8 + .../vscode/webviews/homeView/vite.config.ts | 26 + 18 files changed, 1899 insertions(+), 2 deletions(-) create mode 100644 extensions/vscode/src/utils/getNonce.ts create mode 100644 extensions/vscode/src/utils/getUri.ts create mode 100644 extensions/vscode/src/views/homeView.ts create mode 100644 extensions/vscode/webviews/homeView/.gitignore create mode 100644 extensions/vscode/webviews/homeView/README.md create mode 100644 extensions/vscode/webviews/homeView/env.d.ts create mode 100644 extensions/vscode/webviews/homeView/index.html create mode 100644 extensions/vscode/webviews/homeView/package-lock.json create mode 100644 extensions/vscode/webviews/homeView/package.json create mode 100644 extensions/vscode/webviews/homeView/public/favicon.ico create mode 100644 extensions/vscode/webviews/homeView/src/App.vue create mode 100644 extensions/vscode/webviews/homeView/src/main.ts create mode 100644 extensions/vscode/webviews/homeView/tsconfig.json create mode 100644 extensions/vscode/webviews/homeView/tsconfig.vite-config.json create mode 100644 extensions/vscode/webviews/homeView/vite.config.ts diff --git a/extensions/vscode/package.json b/extensions/vscode/package.json index 773f45b8e..4b25b88a1 100644 --- a/extensions/vscode/package.json +++ b/extensions/vscode/package.json @@ -347,6 +347,14 @@ "contextualTitle": "Publisher", "when": "workbenchState == empty || workbenchState == workspace || posit.publish.missing || posit.publish.state == 'uninitialized'" }, + { + "id": "posit.publisher.homeView", + "type": "webview", + "name": "Home", + "contextualTitle": "Publisher", + "icon": "$(symbol-file)", + "when": "workbenchState == folder && !posit.publish.missing && posit.publish.state == 'initialized'" + }, { "id": "posit.publisher.files", "name": "Deployment Files", @@ -445,7 +453,9 @@ }, "scripts": { "vscode:prepublish": "npm run compile", - "compile": "tsc -p ./", + "compile": "npm run compile-extension && npm run compile-home-view", + "compile-extension": "tsc -p ./", + "compile-home-view": "npm run --prefix webviews/homeView compile", "watch": "tsc -watch -p ./", "pretest": "npm run compile", "lint": "eslint src --ext ts", diff --git a/extensions/vscode/src/extension.ts b/extensions/vscode/src/extension.ts index 7a82e222d..814adb647 100644 --- a/extensions/vscode/src/extension.ts +++ b/extensions/vscode/src/extension.ts @@ -13,6 +13,7 @@ import { FilesTreeDataProvider } from "./views/files"; import { RequirementsTreeDataProvider } from "./views/requirements"; import { CredentialsTreeDataProvider } from "./views/credentials"; import { HelpAndFeedbackTreeDataProvider } from "./views/helpAndFeedback"; +import { HomeViewProvider } from "./views/homeView"; import { LogsTreeDataProvider } from "./views/logs"; import { EventStream } from "./events"; @@ -97,6 +98,7 @@ export async function activate(context: vscode.ExtensionContext) { new CredentialsTreeDataProvider().register(context); new HelpAndFeedbackTreeDataProvider().register(context); new LogsTreeDataProvider(stream).register(context); + new HomeViewProvider(context.extensionUri).register(context); setStateContext(PositPublishState.initialized); } diff --git a/extensions/vscode/src/utils/getNonce.ts b/extensions/vscode/src/utils/getNonce.ts new file mode 100644 index 000000000..597f230d6 --- /dev/null +++ b/extensions/vscode/src/utils/getNonce.ts @@ -0,0 +1,19 @@ +// Copyright (C) 2024 by Posit Software, PBC. + +/** + * A helper function that returns a unique alphanumeric identifier called a nonce. + * + * @remarks This function is primarily used to help enforce content security + * policies for resources/scripts being executed in a webview context. + * + * @returns A nonce + */ +export function getNonce() { + let text = ""; + const possible = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + for (let i = 0; i < 32; i++) { + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; +} diff --git a/extensions/vscode/src/utils/getUri.ts b/extensions/vscode/src/utils/getUri.ts new file mode 100644 index 000000000..84af20c32 --- /dev/null +++ b/extensions/vscode/src/utils/getUri.ts @@ -0,0 +1,22 @@ +// Copyright (C) 2024 by Posit Software, PBC. + +import { Uri, Webview } from "vscode"; + +/** + * A helper function which will get the webview URI of a given file or resource. + * + * @remarks This URI can be used within a webview's HTML as a link to the + * given file/resource. + * + * @param webview A reference to the extension webview + * @param extensionUri The URI of the directory containing the extension + * @param pathList An array of strings representing the path to a file/resource + * @returns A URI pointing to the file/resource + */ +export function getUri( + webview: Webview, + extensionUri: Uri, + pathList: string[], +) { + return webview.asWebviewUri(Uri.joinPath(extensionUri, ...pathList)); +} diff --git a/extensions/vscode/src/views/homeView.ts b/extensions/vscode/src/views/homeView.ts new file mode 100644 index 000000000..d93e57bd1 --- /dev/null +++ b/extensions/vscode/src/views/homeView.ts @@ -0,0 +1,127 @@ +// Copyright (C) 2024 by Posit Software, PBC. + +import { + Disposable, + Webview, + window, + Uri, + WebviewViewProvider, + WebviewView, + WebviewViewResolveContext, + CancellationToken, + ExtensionContext, +} from "vscode"; +import { getUri } from "../utils/getUri"; +import { getNonce } from "../utils/getNonce"; + +const viewName = "posit.publisher.homeView"; + +export class HomeViewProvider implements WebviewViewProvider { + private _disposables: Disposable[] = []; + + constructor(private readonly _extensionUri: Uri) {} + + public resolveWebviewView( + webviewView: WebviewView, + _: WebviewViewResolveContext, + _token: CancellationToken, + ) { + // Allow scripts in the webview + webviewView.webview.options = { + // Enable JavaScript in the webview + enableScripts: true, + // Restrict the webview to only load resources from the `out` directory + localResourceRoots: [ + Uri.joinPath(this._extensionUri, "out", "webviews", "homeView"), + ], + }; + + // Set the HTML content that will fill the webview view + webviewView.webview.html = this._getWebviewContent( + webviewView.webview, + this._extensionUri, + ); + } + /** + * Defines and returns the HTML that should be rendered within the webview panel. + * + * @remarks This is also the place where references to the Vue webview build files + * are created and inserted into the webview HTML. + * + * @param webview A reference to the extension webview + * @param extensionUri The URI of the directory containing the extension + * @returns A template string literal containing the HTML that should be + * rendered within the webview panel + */ + private _getWebviewContent(webview: Webview, extensionUri: Uri) { + // The CSS files from the Vue build output + const stylesUri = getUri(webview, extensionUri, [ + "out", + "webviews", + "homeView", + "index.css", + ]); + // const codiconsUri = webview.asWebviewUri(Uri.joinPath(extensionUri, 'node_modules', '@vscode/codicons', 'dist', 'codicon.css')); + // The JS file from the Vue build output + const scriptUri = getUri(webview, extensionUri, [ + "out", + "webviews", + "homeView", + "index.js", + ]); + // The codicon css (and related tff file) are needing to be loaded for icons + const codiconsUri = getUri(webview, extensionUri, [ + "out", + "webviews", + "homeView", + "codicon.css", + ]); + + const nonce = getNonce(); + + // Tip: Install the es6-string-html VS Code extension to enable code highlighting below + return /*html*/ ` + + + + + + + + + Hello World + + +
+ + + + `; + } + + /** + * Cleans up and disposes of webview resources when view is disposed + */ + public dispose() { + // Dispose of all disposables (i.e. commands) for the current webview panel + while (this._disposables.length) { + const disposable = this._disposables.pop(); + if (disposable) { + disposable.dispose(); + } + } + } + + public register(context: ExtensionContext) { + const provider = window.registerWebviewViewProvider(viewName, this, { + webviewOptions: { + retainContextWhenHidden: true, + }, + }); + context.subscriptions.push(provider); + } +} diff --git a/extensions/vscode/tsconfig.json b/extensions/vscode/tsconfig.json index 39b8f1875..8df582c27 100644 --- a/extensions/vscode/tsconfig.json +++ b/extensions/vscode/tsconfig.json @@ -13,5 +13,6 @@ "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, "noImplicitReturns": true - } + }, + "exclude": ["webviews"] } diff --git a/extensions/vscode/webviews/homeView/.gitignore b/extensions/vscode/webviews/homeView/.gitignore new file mode 100644 index 000000000..c0cebf88d --- /dev/null +++ b/extensions/vscode/webviews/homeView/.gitignore @@ -0,0 +1,6 @@ +node_modules +.vscode/* +main.js +main.js.map +vite.config.js +vite.config.js.map diff --git a/extensions/vscode/webviews/homeView/README.md b/extensions/vscode/webviews/homeView/README.md new file mode 100644 index 000000000..9598e91af --- /dev/null +++ b/extensions/vscode/webviews/homeView/README.md @@ -0,0 +1,10 @@ +# `webview-ui` Directory + +This directory contains all of the code that will be executed within the webview context. It can be thought of as the place where all the "frontend" code of a webview is contained. + +Types of content that can be contained here: + +- Frontend framework code (i.e. Vue, SolidJS, React, Svelte, etc.) +- JavaScript files +- CSS files +- Assets / resources (i.e. images, illustrations, etc.) diff --git a/extensions/vscode/webviews/homeView/env.d.ts b/extensions/vscode/webviews/homeView/env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/extensions/vscode/webviews/homeView/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/extensions/vscode/webviews/homeView/index.html b/extensions/vscode/webviews/homeView/index.html new file mode 100644 index 000000000..d80f55610 --- /dev/null +++ b/extensions/vscode/webviews/homeView/index.html @@ -0,0 +1,13 @@ + + + + + + + Required for Vite only + + +
+ + + diff --git a/extensions/vscode/webviews/homeView/package-lock.json b/extensions/vscode/webviews/homeView/package-lock.json new file mode 100644 index 000000000..4ac2d46c9 --- /dev/null +++ b/extensions/vscode/webviews/homeView/package-lock.json @@ -0,0 +1,1597 @@ +{ + "name": "project-selector-web-view-view", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "project-selector-web-view-view", + "version": "0.0.1", + "dependencies": { + "axios": "^1.6.0", + "eventsource": "^2.0.2", + "get-port": "5.1.1", + "vue": "^3.4.21" + }, + "devDependencies": { + "@tsconfig/node20": "^20.1.2", + "@types/node": "^20.11.28", + "@types/vscode-webview": "^1.57.5", + "@vitejs/plugin-vue": "^5.0.4", + "@vscode/codicons": "^0.0.35", + "@vscode/webview-ui-toolkit": "^1.4.0", + "@vue/tsconfig": "^0.5.1", + "sass": "^1.72.0", + "typescript": "~5.4.2", + "vite": "^5.1.6", + "vue-tsc": "^2.0.6" + } + }, + "node_modules/@babel/parser": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.1.tgz", + "integrity": "sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "node_modules/@microsoft/fast-element": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/@microsoft/fast-element/-/fast-element-1.12.0.tgz", + "integrity": "sha512-gQutuDHPKNxUEcQ4pypZT4Wmrbapus+P9s3bR/SEOLsMbNqNoXigGImITygI5zhb+aA5rzflM6O8YWkmRbGkPA==", + "dev": true + }, + "node_modules/@microsoft/fast-foundation": { + "version": "2.49.5", + "resolved": "https://registry.npmjs.org/@microsoft/fast-foundation/-/fast-foundation-2.49.5.tgz", + "integrity": "sha512-3PpG1BNmZ5kUM1goYU3SsxjsM2H7Rk0ZmpDJ7mnRhWDgKiM5SzJ02KvALRUqDrJQoeDnkW0Q2Q+r9SkEd68Gpg==", + "dev": true, + "dependencies": { + "@microsoft/fast-element": "^1.12.0", + "@microsoft/fast-web-utilities": "^5.4.1", + "tabbable": "^5.2.0", + "tslib": "^1.13.0" + } + }, + "node_modules/@microsoft/fast-foundation/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/@microsoft/fast-react-wrapper": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/@microsoft/fast-react-wrapper/-/fast-react-wrapper-0.3.23.tgz", + "integrity": "sha512-iuL+J2AFKJ1mwUBxSp+bqzt4X93kQwj1jpVgHgw2VRzCOTl7wzta6X+lvRIVg4eoyLfmeVSMkB+q3PD87T/MyQ==", + "dev": true, + "dependencies": { + "@microsoft/fast-element": "^1.12.0", + "@microsoft/fast-foundation": "^2.49.5" + }, + "peerDependencies": { + "react": ">=16.9.0" + } + }, + "node_modules/@microsoft/fast-web-utilities": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@microsoft/fast-web-utilities/-/fast-web-utilities-5.4.1.tgz", + "integrity": "sha512-ReWYncndjV3c8D8iq9tp7NcFNc1vbVHvcBFPME2nNFKNbS1XCesYZGlIlf3ot5EmuOXPlrzUHOWzQ2vFpIkqDg==", + "dev": true, + "dependencies": { + "exenv-es6": "^1.1.1" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.1.tgz", + "integrity": "sha512-4C4UERETjXpC4WpBXDbkgNVgHyWfG3B/NKY46e7w5H134UDOFqUJKpsLm0UYmuupW+aJmRgeScrDNfvZ5WV80A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.1.tgz", + "integrity": "sha512-TrTaFJ9pXgfXEiJKQ3yQRelpQFqgRzVR9it8DbeRzG0RX7mKUy0bqhCFsgevwXLJepQKTnLl95TnPGf9T9AMOA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.1.tgz", + "integrity": "sha512-fz7jN6ahTI3cKzDO2otQuybts5cyu0feymg0bjvYCBrZQ8tSgE8pc0sSNEuGvifrQJWiwx9F05BowihmLxeQKw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.1.tgz", + "integrity": "sha512-WTvdz7SLMlJpektdrnWRUN9C0N2qNHwNbWpNo0a3Tod3gb9leX+yrYdCeB7VV36OtoyiPAivl7/xZ3G1z5h20g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.1.tgz", + "integrity": "sha512-dBHQl+7wZzBYcIF6o4k2XkAfwP2ks1mYW2q/Gzv9n39uDcDiAGDqEyml08OdY0BIct0yLSPkDTqn4i6czpBLLw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.1.tgz", + "integrity": "sha512-bur4JOxvYxfrAmocRJIW0SADs3QdEYK6TQ7dTNz6Z4/lySeu3Z1H/+tl0a4qDYv0bCdBpUYM0sYa/X+9ZqgfSQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.1.tgz", + "integrity": "sha512-ssp77SjcDIUSoUyj7DU7/5iwM4ZEluY+N8umtCT9nBRs3u045t0KkW02LTyHouHDomnMXaXSZcCSr2bdMK63kA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.1.tgz", + "integrity": "sha512-Jv1DkIvwEPAb+v25/Unrnnq9BO3F5cbFPT821n3S5litkz+O5NuXuNhqtPx5KtcwOTtaqkTsO+IVzJOsxd11aQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.13.1.tgz", + "integrity": "sha512-U564BrhEfaNChdATQaEODtquCC7Ez+8Hxz1h5MAdMYj0AqD0GA9rHCpElajb/sQcaFL6NXmHc5O+7FXpWMa73Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.1.tgz", + "integrity": "sha512-zGRDulLTeDemR8DFYyFIQ8kMP02xpUsX4IBikc7lwL9PrwR3gWmX2NopqiGlI2ZVWMl15qZeUjumTwpv18N7sQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.1.tgz", + "integrity": "sha512-VTk/MveyPdMFkYJJPCkYBw07KcTkGU2hLEyqYMsU4NjiOfzoaDTW9PWGRsNwiOA3qI0k/JQPjkl/4FCK1smskQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.1.tgz", + "integrity": "sha512-L+hX8Dtibb02r/OYCsp4sQQIi3ldZkFI0EUkMTDwRfFykXBPptoz/tuuGqEd3bThBSLRWPR6wsixDSgOx/U3Zw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.1.tgz", + "integrity": "sha512-+dI2jVPfM5A8zme8riEoNC7UKk0Lzc7jCj/U89cQIrOjrZTCWZl/+IXUeRT2rEZ5j25lnSA9G9H1Ob9azaF/KQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.1.tgz", + "integrity": "sha512-YY1Exxo2viZ/O2dMHuwQvimJ0SqvL+OAWQLLY6rvXavgQKjhQUzn7nc1Dd29gjB5Fqi00nrBWctJBOyfVMIVxw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tsconfig/node20": { + "version": "20.1.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node20/-/node20-20.1.4.tgz", + "integrity": "sha512-sqgsT69YFeLWf5NtJ4Xq/xAF8p4ZQHlmGW74Nu2tD4+g5fAsposc4ZfaaPixVu4y01BEiDCWLRDCvDM5JOsRxg==", + "dev": true + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.11.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz", + "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/vscode-webview": { + "version": "1.57.5", + "resolved": "https://registry.npmjs.org/@types/vscode-webview/-/vscode-webview-1.57.5.tgz", + "integrity": "sha512-iBAUYNYkz+uk1kdsq05fEcoh8gJmwT3lqqFPN7MGyjQ3HVloViMdo7ZJ8DFIP8WOK74PjOEilosqAyxV2iUFUw==", + "dev": true + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz", + "integrity": "sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==", + "dev": true, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.1.5.tgz", + "integrity": "sha512-u1OHmVkCFsJqNdaM2GKuMhE67TxcEnOqJNF+VtYv2Ji8DnrUaF4FAFSNxY+MRGICl+873CsSJVKas9TQtW14LA==", + "dev": true, + "dependencies": { + "@volar/source-map": "2.1.5" + } + }, + "node_modules/@volar/source-map": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.1.5.tgz", + "integrity": "sha512-GIkAM6fHgDcTXcdH4i10fAiAZzO0HLIer8/pt3oZ9A0n7n4R5d1b2F8Xxzh/pgmgNoL+SrHX3MFxs35CKgfmtA==", + "dev": true, + "dependencies": { + "muggle-string": "^0.4.0" + } + }, + "node_modules/@volar/typescript": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.1.5.tgz", + "integrity": "sha512-zo9a3NrNMSkufIvHuExDGTfYv+zO7C5p2wg8fyP7vcqF/Qo0ztjb0ZfOgq/A85EO/MBc1Kj2Iu7PaOBtP++NMw==", + "dev": true, + "dependencies": { + "@volar/language-core": "2.1.5", + "path-browserify": "^1.0.1" + } + }, + "node_modules/@vscode/codicons": { + "version": "0.0.35", + "resolved": "https://registry.npmjs.org/@vscode/codicons/-/codicons-0.0.35.tgz", + "integrity": "sha512-7iiKdA5wHVYSbO7/Mm0hiHD3i4h+9hKUe1O4hISAe/nHhagMwb2ZbFC8jU6d7Cw+JNT2dWXN2j+WHbkhT5/l2w==", + "dev": true + }, + "node_modules/@vscode/webview-ui-toolkit": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vscode/webview-ui-toolkit/-/webview-ui-toolkit-1.4.0.tgz", + "integrity": "sha512-modXVHQkZLsxgmd5yoP3ptRC/G8NBDD+ob+ngPiWNQdlrH6H1xR/qgOBD85bfU3BhOB5sZzFWBwwhp9/SfoHww==", + "dev": true, + "dependencies": { + "@microsoft/fast-element": "^1.12.0", + "@microsoft/fast-foundation": "^2.49.4", + "@microsoft/fast-react-wrapper": "^0.3.22", + "tslib": "^2.6.2" + }, + "peerDependencies": { + "react": ">=16.9.0" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.21.tgz", + "integrity": "sha512-MjXawxZf2SbZszLPYxaFCjxfibYrzr3eYbKxwpLR9EQN+oaziSu3qKVbwBERj1IFIB8OLUewxB5m/BFzi613og==", + "dependencies": { + "@babel/parser": "^7.23.9", + "@vue/shared": "3.4.21", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.21.tgz", + "integrity": "sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA==", + "dependencies": { + "@vue/compiler-core": "3.4.21", + "@vue/shared": "3.4.21" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.21.tgz", + "integrity": "sha512-me7epoTxYlY+2CUM7hy9PCDdpMPfIwrOvAXud2Upk10g4YLv9UBW7kL798TvMeDhPthkZ0CONNrK2GoeI1ODiQ==", + "dependencies": { + "@babel/parser": "^7.23.9", + "@vue/compiler-core": "3.4.21", + "@vue/compiler-dom": "3.4.21", + "@vue/compiler-ssr": "3.4.21", + "@vue/shared": "3.4.21", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.7", + "postcss": "^8.4.35", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.21.tgz", + "integrity": "sha512-M5+9nI2lPpAsgXOGQobnIueVqc9sisBFexh5yMIMRAPYLa7+5wEJs8iqOZc1WAa9WQbx9GR2twgznU8LTIiZ4Q==", + "dependencies": { + "@vue/compiler-dom": "3.4.21", + "@vue/shared": "3.4.21" + } + }, + "node_modules/@vue/language-core": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.0.7.tgz", + "integrity": "sha512-Vh1yZX3XmYjn9yYLkjU8DN6L0ceBtEcapqiyclHne8guG84IaTzqtvizZB1Yfxm3h6m7EIvjerLO5fvOZO6IIQ==", + "dev": true, + "dependencies": { + "@volar/language-core": "~2.1.3", + "@vue/compiler-dom": "^3.4.0", + "@vue/shared": "^3.4.0", + "computeds": "^0.0.1", + "minimatch": "^9.0.3", + "path-browserify": "^1.0.1", + "vue-template-compiler": "^2.7.14" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/reactivity": { + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.21.tgz", + "integrity": "sha512-UhenImdc0L0/4ahGCyEzc/pZNwVgcglGy9HVzJ1Bq2Mm9qXOpP8RyNTjookw/gOCUlXSEtuZ2fUg5nrHcoqJcw==", + "dependencies": { + "@vue/shared": "3.4.21" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.21.tgz", + "integrity": "sha512-pQthsuYzE1XcGZznTKn73G0s14eCJcjaLvp3/DKeYWoFacD9glJoqlNBxt3W2c5S40t6CCcpPf+jG01N3ULyrA==", + "dependencies": { + "@vue/reactivity": "3.4.21", + "@vue/shared": "3.4.21" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.21.tgz", + "integrity": "sha512-gvf+C9cFpevsQxbkRBS1NpU8CqxKw0ebqMvLwcGQrNpx6gqRDodqKqA+A2VZZpQ9RpK2f9yfg8VbW/EpdFUOJw==", + "dependencies": { + "@vue/runtime-core": "3.4.21", + "@vue/shared": "3.4.21", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.21.tgz", + "integrity": "sha512-aV1gXyKSN6Rz+6kZ6kr5+Ll14YzmIbeuWe7ryJl5muJ4uwSwY/aStXTixx76TwkZFJLm1aAlA/HSWEJ4EyiMkg==", + "dependencies": { + "@vue/compiler-ssr": "3.4.21", + "@vue/shared": "3.4.21" + }, + "peerDependencies": { + "vue": "3.4.21" + } + }, + "node_modules/@vue/shared": { + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.21.tgz", + "integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==" + }, + "node_modules/@vue/tsconfig": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.5.1.tgz", + "integrity": "sha512-VcZK7MvpjuTPx2w6blwnwZAu5/LgBUtejFOi3pPGQFXQN5Ela03FUtd2Qtg4yWGGissVL0dr6Ro1LfOFh+PCuQ==", + "dev": true + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/computeds": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/computeds/-/computeds-0.0.1.tgz", + "integrity": "sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==", + "dev": true + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + }, + "node_modules/eventsource": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz", + "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/exenv-es6": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exenv-es6/-/exenv-es6-1.1.1.tgz", + "integrity": "sha512-vlVu3N8d6yEMpMsEm+7sUBAI81aqYYuEvfK0jNqmdb/OPXzzH7QWDDnVjMvDSY47JdHEqx/dfC/q8WkfoTmpGQ==", + "dev": true + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-port": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/immutable": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz", + "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "peer": true + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "peer": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/magic-string": { + "version": "0.30.8", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", + "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dev": true, + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rollup": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.1.tgz", + "integrity": "sha512-hFi+fU132IvJ2ZuihN56dwgpltpmLZHZWsx27rMCTZ2sYwrqlgL5sECGy1eeV2lAihD8EzChBVVhsXci0wD4Tg==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.13.1", + "@rollup/rollup-android-arm64": "4.13.1", + "@rollup/rollup-darwin-arm64": "4.13.1", + "@rollup/rollup-darwin-x64": "4.13.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.13.1", + "@rollup/rollup-linux-arm64-gnu": "4.13.1", + "@rollup/rollup-linux-arm64-musl": "4.13.1", + "@rollup/rollup-linux-riscv64-gnu": "4.13.1", + "@rollup/rollup-linux-s390x-gnu": "4.13.1", + "@rollup/rollup-linux-x64-gnu": "4.13.1", + "@rollup/rollup-linux-x64-musl": "4.13.1", + "@rollup/rollup-win32-arm64-msvc": "4.13.1", + "@rollup/rollup-win32-ia32-msvc": "4.13.1", + "@rollup/rollup-win32-x64-msvc": "4.13.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/sass": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.72.0.tgz", + "integrity": "sha512-Gpczt3WA56Ly0Mn8Sl21Vj94s1axi9hDIzDFn9Ph9x3C3p4nNyvsqJoQyVXKou6cBlfFWEgRW4rT8Tb4i3XnVA==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tabbable": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-5.3.3.tgz", + "integrity": "sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA==", + "dev": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, + "node_modules/typescript": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", + "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", + "devOptional": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/vite": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.6.tgz", + "integrity": "sha512-FPtnxFlSIKYjZ2eosBQamz4CbyrTizbZ3hnGJlh/wMtCrlp1Hah6AzBLjGI5I2urTfNnpovpHdrL6YRuBOPnCA==", + "dev": true, + "dependencies": { + "esbuild": "^0.20.1", + "postcss": "^8.4.36", + "rollup": "^4.13.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.4.21", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.21.tgz", + "integrity": "sha512-5hjyV/jLEIKD/jYl4cavMcnzKwjMKohureP8ejn3hhEjwhWIhWeuzL2kJAjzl/WyVsgPY56Sy4Z40C3lVshxXA==", + "dependencies": { + "@vue/compiler-dom": "3.4.21", + "@vue/compiler-sfc": "3.4.21", + "@vue/runtime-dom": "3.4.21", + "@vue/server-renderer": "3.4.21", + "@vue/shared": "3.4.21" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-template-compiler": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz", + "integrity": "sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==", + "dev": true, + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/vue-tsc": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.0.7.tgz", + "integrity": "sha512-LYa0nInkfcDBB7y8jQ9FQ4riJTRNTdh98zK/hzt4gEpBZQmf30dPhP+odzCa+cedGz6B/guvJEd0BavZaRptjg==", + "dev": true, + "dependencies": { + "@volar/typescript": "~2.1.3", + "@vue/language-core": "2.0.7", + "semver": "^7.5.4" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": "*" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } +} diff --git a/extensions/vscode/webviews/homeView/package.json b/extensions/vscode/webviews/homeView/package.json new file mode 100644 index 000000000..25c31f4ee --- /dev/null +++ b/extensions/vscode/webviews/homeView/package.json @@ -0,0 +1,31 @@ +{ + "name": "project-selector-web-view-view", + "version": "0.0.1", + "private": true, + "scripts": { + "compile": "npm run clean-dist && npm run type-check && npm run vite-build && npm run copy-codicons", + "clean-dist": "rm -rd ../../out/webviews/homeView | true", + "type-check": "vue-tsc --noEmit", + "vite-build": "vite build --outDir ../../out/webviews/homeView", + "copy-codicons": "cp node_modules/@vscode/codicons/dist/codicon.css node_modules/@vscode/codicons/dist/codicon.ttf ../../out/webviews/homeView" + }, + "dependencies": { + "vue": "^3.4.21", + "axios": "^1.6.0", + "eventsource": "^2.0.2", + "get-port": "5.1.1" + }, + "devDependencies": { + "@tsconfig/node20": "^20.1.2", + "@types/node": "^20.11.28", + "@types/vscode-webview": "^1.57.5", + "@vitejs/plugin-vue": "^5.0.4", + "@vscode/codicons": "^0.0.35", + "@vscode/webview-ui-toolkit": "^1.4.0", + "@vue/tsconfig": "^0.5.1", + "sass": "^1.72.0", + "typescript": "~5.4.2", + "vite": "^5.1.6", + "vue-tsc": "^2.0.6" + } +} diff --git a/extensions/vscode/webviews/homeView/public/favicon.ico b/extensions/vscode/webviews/homeView/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..df36fcfb72584e00488330b560ebcf34a41c64c2 GIT binary patch literal 4286 zcmds*O-Phc6o&64GDVCEQHxsW(p4>LW*W<827=Unuo8sGpRux(DN@jWP-e29Wl%wj zY84_aq9}^Am9-cWTD5GGEo#+5Fi2wX_P*bo+xO!)p*7B;iKlbFd(U~_d(U?#hLj56 zPhFkj-|A6~Qk#@g^#D^U0XT1cu=c-vu1+SElX9NR;kzAUV(q0|dl0|%h|dI$%VICy zJnu2^L*Te9JrJMGh%-P79CL0}dq92RGU6gI{v2~|)p}sG5x0U*z<8U;Ij*hB9z?ei z@g6Xq-pDoPl=MANPiR7%172VA%r)kevtV-_5H*QJKFmd;8yA$98zCxBZYXTNZ#QFk2(TX0;Y2dt&WitL#$96|gJY=3xX zpCoi|YNzgO3R`f@IiEeSmKrPSf#h#Qd<$%Ej^RIeeYfsxhPMOG`S`Pz8q``=511zm zAm)MX5AV^5xIWPyEu7u>qYs?pn$I4nL9J!=K=SGlKLXpE<5x+2cDTXq?brj?n6sp= zphe9;_JHf40^9~}9i08r{XM$7HB!`{Ys~TK0kx<}ZQng`UPvH*11|q7&l9?@FQz;8 zx!=3<4seY*%=OlbCbcae?5^V_}*K>Uo6ZWV8mTyE^B=DKy7-sdLYkR5Z?paTgK-zyIkKjIcpyO z{+uIt&YSa_$QnN_@t~L014dyK(fOOo+W*MIxbA6Ndgr=Y!f#Tokqv}n<7-9qfHkc3 z=>a|HWqcX8fzQCT=dqVbogRq!-S>H%yA{1w#2Pn;=e>JiEj7Hl;zdt-2f+j2%DeVD zsW0Ab)ZK@0cIW%W7z}H{&~yGhn~D;aiP4=;m-HCo`BEI+Kd6 z={Xwx{TKxD#iCLfl2vQGDitKtN>z|-AdCN|$jTFDg0m3O`WLD4_s#$S literal 0 HcmV?d00001 diff --git a/extensions/vscode/webviews/homeView/src/App.vue b/extensions/vscode/webviews/homeView/src/App.vue new file mode 100644 index 000000000..a283f4446 --- /dev/null +++ b/extensions/vscode/webviews/homeView/src/App.vue @@ -0,0 +1,5 @@ + + + diff --git a/extensions/vscode/webviews/homeView/src/main.ts b/extensions/vscode/webviews/homeView/src/main.ts new file mode 100644 index 000000000..5fa0ec3f2 --- /dev/null +++ b/extensions/vscode/webviews/homeView/src/main.ts @@ -0,0 +1,8 @@ +// Copyright (C) 2024 by Posit Software, PBC. + +import { createApp, inject } from "vue"; +import App from "./App.vue"; + +const theWindow = inject("window"); + +createApp(App).mount("#app"); diff --git a/extensions/vscode/webviews/homeView/tsconfig.json b/extensions/vscode/webviews/homeView/tsconfig.json new file mode 100644 index 000000000..203b57313 --- /dev/null +++ b/extensions/vscode/webviews/homeView/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": ["@tsconfig/node20/tsconfig.json", "@vue/tsconfig/tsconfig.json"], + "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + }, + "verbatimModuleSyntax": false + } +} diff --git a/extensions/vscode/webviews/homeView/tsconfig.vite-config.json b/extensions/vscode/webviews/homeView/tsconfig.vite-config.json new file mode 100644 index 000000000..156583fff --- /dev/null +++ b/extensions/vscode/webviews/homeView/tsconfig.vite-config.json @@ -0,0 +1,8 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": ["vite.config.*"], + "compilerOptions": { + "composite": true, + "types": ["node"] + } +} diff --git a/extensions/vscode/webviews/homeView/vite.config.ts b/extensions/vscode/webviews/homeView/vite.config.ts new file mode 100644 index 000000000..c56db1c74 --- /dev/null +++ b/extensions/vscode/webviews/homeView/vite.config.ts @@ -0,0 +1,26 @@ +// import { fileURLToPath, URL } from 'url' + +import { defineConfig } from "vite"; +import vue from "@vitejs/plugin-vue"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + vue({ + template: { + compilerOptions: { isCustomElement: (tag) => tag.includes("vscode-") }, + }, + }), + ], + build: { + outDir: "../../out/webviews/homeView", + rollupOptions: { + output: { + entryFileNames: `[name].js`, + chunkFileNames: `[name].js`, + assetFileNames: `[name].[ext]`, + }, + }, + sourcemap: "inline", + }, +}); From 750e3dc1ef88fec0566f9a619508784ee051302d Mon Sep 17 00:00:00 2001 From: Michael Marchetti Date: Mon, 1 Apr 2024 11:44:28 -0400 Subject: [PATCH 02/33] omit empty fields from pre-deployment files --- internal/deployment/deployment.go | 21 ++++++++++++--------- internal/deployment/deployment_test.go | 14 +++++++++++++- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/internal/deployment/deployment.go b/internal/deployment/deployment.go index 0a73db2f8..423db7b43 100644 --- a/internal/deployment/deployment.go +++ b/internal/deployment/deployment.go @@ -18,21 +18,24 @@ import ( ) type Deployment struct { + // Predeployment and full deployment fields Schema string `toml:"$schema" json:"$schema"` ServerType accounts.ServerType `toml:"server-type" json:"serverType"` ServerURL string `toml:"server-url" json:"serverUrl"` ClientVersion string `toml:"client-version" json:"-"` CreatedAt string `toml:"created-at" json:"createdAt"` - ID types.ContentID `toml:"id" json:"id"` ConfigName string `toml:"configuration-name" json:"configurationName"` - DeployedAt string `toml:"deployed-at" json:"deployedAt"` - BundleID types.BundleID `toml:"bundle-id" json:"bundleId"` - BundleURL string `toml:"bundle-url" json:"bundleUrl"` - DashboardURL string `toml:"dashboard-url" json:"dashboardUrl"` - DirectURL string `toml:"direct-url" json:"directUrl"` - Error *types.AgentError `toml:"deployment-error" json:"deploymentError"` - Files []string `toml:"files,multiline,omitempty" json:"files"` - Configuration *config.Config `toml:"configuration" json:"configuration"` + + // Full deployment fields + ID types.ContentID `toml:"id,omitempty" json:"id"` + DeployedAt string `toml:"deployed-at,omitempty" json:"deployedAt"` + BundleID types.BundleID `toml:"bundle-id,omitempty" json:"bundleId"` + BundleURL string `toml:"bundle-url,omitempty" json:"bundleUrl"` + DashboardURL string `toml:"dashboard-url,omitempty" json:"dashboardUrl"` + DirectURL string `toml:"direct-url,omitempty" json:"directUrl"` + Error *types.AgentError `toml:"deployment-error,omitempty" json:"deploymentError"` + Files []string `toml:"files,multiline,omitempty" json:"files"` + Configuration *config.Config `toml:"configuration,omitempty" json:"configuration"` } func New() *Deployment { diff --git a/internal/deployment/deployment_test.go b/internal/deployment/deployment_test.go index 1be5bdaae..01691c138 100644 --- a/internal/deployment/deployment_test.go +++ b/internal/deployment/deployment_test.go @@ -109,8 +109,20 @@ func (s *DeploymentSuite) TestWriteFile() { content, err := deploymentFile.ReadFile() s.NoError(err) - firstLine := strings.Split(string(content), "\n")[0] + stringContent := string(content) + firstLine := strings.Split(stringContent, "\n")[0] s.Equal(autogenHeader, firstLine+"\n") + + // This is a pre-deployment, so should not contain certain fields. + s.NotContains(stringContent, "id") + s.NotContains(stringContent, "deployed-at") + s.NotContains(stringContent, "bundle-id") + s.NotContains(stringContent, "bundle-url") + s.NotContains(stringContent, "dashboard-url") + s.NotContains(stringContent, "direct-url") + s.NotContains(stringContent, "deployment-error") + s.NotContains(stringContent, "files") + s.NotContains(stringContent, "[configuration") } func (s *DeploymentSuite) TestWriteFileErr() { From b1c6b9102342a7dd12da838543684cdf38431b06 Mon Sep 17 00:00:00 2001 From: Bill Sager Date: Mon, 1 Apr 2024 10:00:11 -0700 Subject: [PATCH 03/33] PR updates --- extensions/vscode/src/panels.ts | 19 ++----------- extensions/vscode/src/views/homeView.ts | 10 ++++--- extensions/vscode/tsconfig.json | 2 +- extensions/vscode/vscodeignore | 26 ++++++++++++++++++ .../vscode/webviews/homeView/index.html | 1 - .../webviews/homeView/package-lock.json | 18 ++++++------ .../vscode/webviews/homeView/package.json | 4 +-- .../webviews/homeView/public/favicon.ico | Bin 4286 -> 0 bytes .../webviews/homeView/{ => src}/env.d.ts | 0 .../vscode/webviews/homeView/src/main.ts | 2 -- .../vscode/webviews/homeView/tsconfig.json | 6 ++-- 11 files changed, 50 insertions(+), 38 deletions(-) create mode 100644 extensions/vscode/vscodeignore delete mode 100644 extensions/vscode/webviews/homeView/public/favicon.ico rename extensions/vscode/webviews/homeView/{ => src}/env.d.ts (100%) diff --git a/extensions/vscode/src/panels.ts b/extensions/vscode/src/panels.ts index 960233cfd..b93c430a8 100644 --- a/extensions/vscode/src/panels.ts +++ b/extensions/vscode/src/panels.ts @@ -2,6 +2,8 @@ import * as vscode from "vscode"; +import { getNonce } from "./utils/getNonce"; + const DEFAULT_COLUMN = vscode.ViewColumn.Beside; export class Panel { @@ -86,7 +88,7 @@ export class Panel { * @returns {string} */ export const createHTML = (url: string, webview: vscode.Webview): string => { - const nonce = createNonce(); + const nonce = getNonce(); return ( // install https://marketplace.visualstudio.com/items?itemName=Tobermory.es6-string-html to enable code highlighting below /*html*/ @@ -136,18 +138,3 @@ export const createContentSecurityPolicyContent = ( .join(" "); return `default-src 'none'; ${content}`; }; - -/** - * Creates a unique nonce value. - * - * @returns {string} - */ -const createNonce = (): string => { - let text = ""; - const possible = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - for (let i = 0; i < 32; i++) { - text += possible.charAt(Math.floor(Math.random() * possible.length)); - } - return text; -}; diff --git a/extensions/vscode/src/views/homeView.ts b/extensions/vscode/src/views/homeView.ts index d93e57bd1..eaa0b92da 100644 --- a/extensions/vscode/src/views/homeView.ts +++ b/extensions/vscode/src/views/homeView.ts @@ -87,10 +87,12 @@ export class HomeViewProvider implements WebviewViewProvider { + content=" + default-src 'none'; + font-src ${webview.cspSource}; + style-src ${webview.cspSource} 'unsafe-inline'; + script-src 'nonce-${nonce}';" + /> Hello World diff --git a/extensions/vscode/tsconfig.json b/extensions/vscode/tsconfig.json index 8df582c27..da3d8512d 100644 --- a/extensions/vscode/tsconfig.json +++ b/extensions/vscode/tsconfig.json @@ -14,5 +14,5 @@ "noFallthroughCasesInSwitch": true, "noImplicitReturns": true }, - "exclude": ["webviews"] + "exclude": ["node_modules", "webviews"] } diff --git a/extensions/vscode/vscodeignore b/extensions/vscode/vscodeignore new file mode 100644 index 000000000..a70b09f0a --- /dev/null +++ b/extensions/vscode/vscodeignore @@ -0,0 +1,26 @@ +# `.vscodeignore` + +.vscode/** +.vscode-test/** +src/** +.yarnrc +**/_.map +\*\*/_.ts +CONTRIBUTING.md +vsc-extension-quickstart.md + +# Ignore all webview files except the build directories + +webviews/homeView/src/** +webviews/homeView/index.html +webviews/homeView/package.json +webviews/homeView/package-lock.json +webviews/homeView/README.md +webviews/homeView/node_modules/** + +# Ignore configuration files + +**/.gitignore +**/.eslintrc.json +**/tsconfig\*.json +**/vite.config.json diff --git a/extensions/vscode/webviews/homeView/index.html b/extensions/vscode/webviews/homeView/index.html index d80f55610..0b9c3a218 100644 --- a/extensions/vscode/webviews/homeView/index.html +++ b/extensions/vscode/webviews/homeView/index.html @@ -2,7 +2,6 @@ - Required for Vite only diff --git a/extensions/vscode/webviews/homeView/package-lock.json b/extensions/vscode/webviews/homeView/package-lock.json index 4ac2d46c9..d49f58920 100644 --- a/extensions/vscode/webviews/homeView/package-lock.json +++ b/extensions/vscode/webviews/homeView/package-lock.json @@ -14,8 +14,8 @@ "vue": "^3.4.21" }, "devDependencies": { - "@tsconfig/node20": "^20.1.2", - "@types/node": "^20.11.28", + "@tsconfig/node18": "^18.2.4", + "@types/node": "^18.11.9", "@types/vscode-webview": "^1.57.5", "@vitejs/plugin-vue": "^5.0.4", "@vscode/codicons": "^0.0.35", @@ -639,10 +639,10 @@ "win32" ] }, - "node_modules/@tsconfig/node20": { - "version": "20.1.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node20/-/node20-20.1.4.tgz", - "integrity": "sha512-sqgsT69YFeLWf5NtJ4Xq/xAF8p4ZQHlmGW74Nu2tD4+g5fAsposc4ZfaaPixVu4y01BEiDCWLRDCvDM5JOsRxg==", + "node_modules/@tsconfig/node18": { + "version": "18.2.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node18/-/node18-18.2.4.tgz", + "integrity": "sha512-5xxU8vVs9/FNcvm3gE07fPbn9tl6tqGGWA9tSlwsUEkBxtRnTsNmwrV8gasZ9F/EobaSv9+nu8AxUKccw77JpQ==", "dev": true }, "node_modules/@types/estree": { @@ -652,9 +652,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.11.30", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz", - "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==", + "version": "18.19.28", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.28.tgz", + "integrity": "sha512-J5cOGD9n4x3YGgVuaND6khm5x07MMdAKkRyXnjVR6KFhLMNh2yONGiP7Z+4+tBOt5mK+GvDTiacTOVGGpqiecw==", "dev": true, "dependencies": { "undici-types": "~5.26.4" diff --git a/extensions/vscode/webviews/homeView/package.json b/extensions/vscode/webviews/homeView/package.json index 25c31f4ee..953d581d8 100644 --- a/extensions/vscode/webviews/homeView/package.json +++ b/extensions/vscode/webviews/homeView/package.json @@ -16,8 +16,8 @@ "get-port": "5.1.1" }, "devDependencies": { - "@tsconfig/node20": "^20.1.2", - "@types/node": "^20.11.28", + "@tsconfig/node18": "^18.2.4", + "@types/node": "^18.11.9", "@types/vscode-webview": "^1.57.5", "@vitejs/plugin-vue": "^5.0.4", "@vscode/codicons": "^0.0.35", diff --git a/extensions/vscode/webviews/homeView/public/favicon.ico b/extensions/vscode/webviews/homeView/public/favicon.ico deleted file mode 100644 index df36fcfb72584e00488330b560ebcf34a41c64c2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4286 zcmds*O-Phc6o&64GDVCEQHxsW(p4>LW*W<827=Unuo8sGpRux(DN@jWP-e29Wl%wj zY84_aq9}^Am9-cWTD5GGEo#+5Fi2wX_P*bo+xO!)p*7B;iKlbFd(U~_d(U?#hLj56 zPhFkj-|A6~Qk#@g^#D^U0XT1cu=c-vu1+SElX9NR;kzAUV(q0|dl0|%h|dI$%VICy zJnu2^L*Te9JrJMGh%-P79CL0}dq92RGU6gI{v2~|)p}sG5x0U*z<8U;Ij*hB9z?ei z@g6Xq-pDoPl=MANPiR7%172VA%r)kevtV-_5H*QJKFmd;8yA$98zCxBZYXTNZ#QFk2(TX0;Y2dt&WitL#$96|gJY=3xX zpCoi|YNzgO3R`f@IiEeSmKrPSf#h#Qd<$%Ej^RIeeYfsxhPMOG`S`Pz8q``=511zm zAm)MX5AV^5xIWPyEu7u>qYs?pn$I4nL9J!=K=SGlKLXpE<5x+2cDTXq?brj?n6sp= zphe9;_JHf40^9~}9i08r{XM$7HB!`{Ys~TK0kx<}ZQng`UPvH*11|q7&l9?@FQz;8 zx!=3<4seY*%=OlbCbcae?5^V_}*K>Uo6ZWV8mTyE^B=DKy7-sdLYkR5Z?paTgK-zyIkKjIcpyO z{+uIt&YSa_$QnN_@t~L014dyK(fOOo+W*MIxbA6Ndgr=Y!f#Tokqv}n<7-9qfHkc3 z=>a|HWqcX8fzQCT=dqVbogRq!-S>H%yA{1w#2Pn;=e>JiEj7Hl;zdt-2f+j2%DeVD zsW0Ab)ZK@0cIW%W7z}H{&~yGhn~D;aiP4=;m-HCo`BEI+Kd6 z={Xwx{TKxD#iCLfl2vQGDitKtN>z|-AdCN|$jTFDg0m3O`WLD4_s#$S diff --git a/extensions/vscode/webviews/homeView/env.d.ts b/extensions/vscode/webviews/homeView/src/env.d.ts similarity index 100% rename from extensions/vscode/webviews/homeView/env.d.ts rename to extensions/vscode/webviews/homeView/src/env.d.ts diff --git a/extensions/vscode/webviews/homeView/src/main.ts b/extensions/vscode/webviews/homeView/src/main.ts index 5fa0ec3f2..90d466a77 100644 --- a/extensions/vscode/webviews/homeView/src/main.ts +++ b/extensions/vscode/webviews/homeView/src/main.ts @@ -3,6 +3,4 @@ import { createApp, inject } from "vue"; import App from "./App.vue"; -const theWindow = inject("window"); - createApp(App).mount("#app"); diff --git a/extensions/vscode/webviews/homeView/tsconfig.json b/extensions/vscode/webviews/homeView/tsconfig.json index 203b57313..576a99edf 100644 --- a/extensions/vscode/webviews/homeView/tsconfig.json +++ b/extensions/vscode/webviews/homeView/tsconfig.json @@ -1,10 +1,10 @@ { - "extends": ["@tsconfig/node20/tsconfig.json", "@vue/tsconfig/tsconfig.json"], - "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], + "extends": ["@tsconfig/node18/tsconfig.json", "@vue/tsconfig/tsconfig.json"], + "include": ["src/**/*", "src/**/*.vue"], "compilerOptions": { "baseUrl": ".", "paths": { - "@/*": ["./src/*"] + "src/*": ["./src/*"] }, "verbatimModuleSyntax": false } From 7c44645eb32d6874d0a12b9932d5cb4f20a0ed70 Mon Sep 17 00:00:00 2001 From: Michael Marchetti Date: Mon, 1 Apr 2024 14:13:33 -0400 Subject: [PATCH 04/33] update deployed-at when record is written --- internal/publish/publish.go | 12 +++- internal/publish/publish_test.go | 108 +++++++++++++++++++++++-------- 2 files changed, 91 insertions(+), 29 deletions(-) diff --git a/internal/publish/publish.go b/internal/publish/publish.go index 00ece4dc8..16221e77a 100644 --- a/internal/publish/publish.go +++ b/internal/publish/publish.go @@ -213,6 +213,10 @@ func (p *defaultPublisher) writeDeploymentRecord(log logging.Logger) error { // Initial deployment p.TargetName = p.SaveName } + + now := time.Now().Format(time.RFC3339) + p.Target.DeployedAt = now + recordPath := deployment.GetDeploymentPath(p.Dir, p.SaveName) return p.Target.WriteFile(recordPath) } @@ -227,10 +231,12 @@ func (p *defaultPublisher) createDeploymentRecord( // bundle upload. cfg := *p.Config - now := time.Now().Format(time.RFC3339) - created := now + created := "" + if p.Target != nil { created = p.Target.CreatedAt + } else { + created = time.Now().Format(time.RFC3339) } p.Target = &deployment.Deployment{ @@ -243,7 +249,6 @@ func (p *defaultPublisher) createDeploymentRecord( ConfigName: p.ConfigName, Files: nil, Configuration: &cfg, - DeployedAt: now, BundleID: "", DashboardURL: getDashboardURL(p.Account.URL, contentID), DirectURL: getDirectURL(p.Account.URL, contentID), @@ -268,6 +273,7 @@ func (p *defaultPublisher) publishWithClient( var contentID types.ContentID if p.isDeployed() { contentID = p.Target.ID + log.Info("Updating deployment", "content_id", contentID) } else { // Create a new deployment; we will update it with details later. contentID, err = p.createDeployment(client, log) diff --git a/internal/publish/publish_test.go b/internal/publish/publish_test.go index d79bb0fb1..50f9fed69 100644 --- a/internal/publish/publish_test.go +++ b/internal/publish/publish_test.go @@ -66,11 +66,11 @@ func (s *PublishSuite) TestNewFromState() { s.Equal(stateStore, publisher.(*defaultPublisher).State) } -func (s *PublishSuite) TestPublishWithClient() { +func (s *PublishSuite) TestPublishWithClientNewSuccess() { s.publishWithClient(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil) } -func (s *PublishSuite) TestPublishWithClientUpdate() { +func (s *PublishSuite) TestPublishWithClientNewUpdate() { target := deployment.New() target.ID = "myContentID" // Make CreatedAt earlier so it will differ from DeployedAt. @@ -78,51 +78,96 @@ func (s *PublishSuite) TestPublishWithClientUpdate() { s.publishWithClient(target, nil, nil, nil, nil, nil, nil, nil, nil, nil) } -func (s *PublishSuite) TestPublishWithClientFailAuth() { +func (s *PublishSuite) TestPublishWithClientNewFailAuth() { authErr := errors.New("error from TestAuthentication") s.publishWithClient(nil, authErr, nil, nil, nil, nil, nil, nil, nil, authErr) } -func (s *PublishSuite) TestPublishWithClientFailCapabilities() { +func (s *PublishSuite) TestPublishWithClientNewFailCapabilities() { capErr := errors.New("error from CheckCapabilities") s.publishWithClient(nil, nil, capErr, nil, nil, nil, nil, nil, nil, capErr) } -func (s *PublishSuite) TestPublishWithClientFailCreate() { +func (s *PublishSuite) TestPublishWithClientNewFailCreate() { createErr := errors.New("error from Create") s.publishWithClient(nil, nil, nil, createErr, nil, nil, nil, nil, nil, createErr) } -func (s *PublishSuite) TestPublishWithClientFailUpdate() { +func (s *PublishSuite) TestPublishWithClientNewFailEnvVars() { + envVarErr := errors.New("error from SetEnvVars") + s.publishWithClient(nil, nil, nil, nil, envVarErr, nil, nil, nil, nil, envVarErr) +} + +func (s *PublishSuite) TestPublishWithClientNewFailUpload() { + uploadErr := errors.New("error from Upload") + s.publishWithClient(nil, nil, nil, nil, nil, uploadErr, nil, nil, nil, uploadErr) +} + +func (s *PublishSuite) TestPublishWithClientNewFailDeploy() { + deployErr := errors.New("error from Deploy") + s.publishWithClient(nil, nil, nil, nil, nil, nil, deployErr, nil, nil, deployErr) +} + +func (s *PublishSuite) TestPublishWithClientNewFailWaitForTask() { + waitErr := errors.New("error from WaitForTask") + s.publishWithClient(nil, nil, nil, nil, nil, nil, nil, waitErr, nil, waitErr) +} + +func (s *PublishSuite) TestPublishWithClientNewFailValidation() { + validateErr := errors.New("error from ValidateDeployment") + s.publishWithClient(nil, nil, nil, nil, nil, nil, nil, nil, validateErr, validateErr) +} + +func (s *PublishSuite) TestPublishWithClientRedeployFailAuth() { + target := deployment.New() + target.ID = "myContentID" + authErr := errors.New("error from TestAuthentication") + s.publishWithClient(target, authErr, nil, nil, nil, nil, nil, nil, nil, authErr) +} + +func (s *PublishSuite) TestPublishWithClientRedeployFailCapabilities() { + target := deployment.New() + target.ID = "myContentID" + capErr := errors.New("error from CheckCapabilities") + s.publishWithClient(target, nil, capErr, nil, nil, nil, nil, nil, nil, capErr) +} + +func (s *PublishSuite) TestPublishWithClientRedeployFailUpdate() { target := deployment.New() target.ID = "myContentID" - // Make CreatedAt earlier so it will differ from DeployedAt. - target.CreatedAt = time.Now().Add(-time.Hour).Format(time.RFC3339) updateErr := errors.New("error from Update") s.publishWithClient(target, nil, nil, updateErr, nil, nil, nil, nil, nil, updateErr) } -func (s *PublishSuite) TestPublishWithClientFailEnvVars() { +func (s *PublishSuite) TestPublishWithClientRedeployFailEnvVars() { + target := deployment.New() + target.ID = "myContentID" envVarErr := errors.New("error from SetEnvVars") - s.publishWithClient(nil, nil, nil, nil, envVarErr, nil, nil, nil, nil, envVarErr) + s.publishWithClient(target, nil, nil, nil, envVarErr, nil, nil, nil, nil, envVarErr) } -func (s *PublishSuite) TestPublishWithClientFailUpload() { +func (s *PublishSuite) TestPublishWithClientRedeployFailUpload() { + target := deployment.New() + target.ID = "myContentID" uploadErr := errors.New("error from Upload") - s.publishWithClient(nil, nil, nil, nil, nil, uploadErr, nil, nil, nil, uploadErr) + s.publishWithClient(target, nil, nil, nil, nil, uploadErr, nil, nil, nil, uploadErr) } -func (s *PublishSuite) TestPublishWithClientFailDeploy() { +func (s *PublishSuite) TestPublishWithClientRedeployFailDeploy() { + target := deployment.New() + target.ID = "myContentID" deployErr := errors.New("error from Deploy") - s.publishWithClient(nil, nil, nil, nil, nil, nil, deployErr, nil, nil, deployErr) + s.publishWithClient(target, nil, nil, nil, nil, nil, deployErr, nil, nil, deployErr) } -func (s *PublishSuite) TestPublishWithClientFailWaitForTask() { +func (s *PublishSuite) TestPublishWithClientRedeployFailWaitForTask() { + target := deployment.New() + target.ID = "myContentID" waitErr := errors.New("error from WaitForTask") - s.publishWithClient(nil, nil, nil, nil, nil, nil, nil, waitErr, nil, waitErr) + s.publishWithClient(target, nil, nil, nil, nil, nil, nil, waitErr, nil, waitErr) } -func (s *PublishSuite) TestPublishWithClientFailValidation() { +func (s *PublishSuite) TestPublishWithClientRedeployFailValidation() { validateErr := errors.New("error from ValidateDeployment") s.publishWithClient(nil, nil, nil, nil, nil, nil, nil, nil, validateErr, validateErr) } @@ -170,11 +215,17 @@ func (s *PublishSuite) publishWithClient( } saveName := "" targetName := "" + recordName := "" // name we will find the deployment record under if target != nil { targetName = "targetToLoad" + recordName = targetName + + // Make CreatedAt earlier so it will differ from DeployedAt. + target.CreatedAt = time.Now().Add(-time.Hour).Format(time.RFC3339) } else { saveName = "saveAsThis" + recordName = saveName } stateStore := &state.State{ Dir: s.cwd, @@ -200,6 +251,8 @@ func (s *PublishSuite) publishWithClient( } else { s.NotNil(err) s.Equal(expectedErr.Error(), err.Error()) + + publisher.emitErrorEvents(err, s.log) } if stateStore.Target != nil { if target != nil { @@ -209,21 +262,24 @@ func (s *PublishSuite) publishWithClient( s.Equal(stateStore.Target.CreatedAt, stateStore.Target.DeployedAt) } } - if authErr == nil && capErr == nil && createErr == nil { - recordPath := deployment.GetDeploymentPath(stateStore.Dir, stateStore.SaveName) + couldCreateDeployment := (authErr == nil && capErr == nil && createErr == nil) + if (stateStore.Target != nil) || couldCreateDeployment { + // Either a pre-existing deployment record, or we got far enough to create one + recordPath := deployment.GetDeploymentPath(stateStore.Dir, recordName) record, err := deployment.FromFile(recordPath) s.NoError(err) s.Equal(myContentID, record.ID) s.Equal(project.Version, record.ClientVersion) s.NotEqual("", record.DeployedAt) - logs := s.logBuffer.String() - s.Contains(logs, "content_id="+myContentID) - - // Files are written after upload. - if uploadErr == nil { - s.Contains(record.Files, "app.py") - s.Contains(record.Files, "requirements.txt") + if couldCreateDeployment { + logs := s.logBuffer.String() + s.Contains(logs, "content_id="+myContentID) + // Files are written after upload. + if uploadErr == nil { + s.Contains(record.Files, "app.py") + s.Contains(record.Files, "requirements.txt") + } } } // Ensure we have not created a bad deployment record (#1112) From 9fb158f8fedb8a41a1263ba201d593dd88b77ebd Mon Sep 17 00:00:00 2001 From: Jordan Jensen Date: Mon, 1 Apr 2024 12:42:24 -0700 Subject: [PATCH 05/33] Combine vscodeignore files --- extensions/vscode/.vscodeignore | 23 ++++++++++++++++++----- extensions/vscode/vscodeignore | 26 -------------------------- 2 files changed, 18 insertions(+), 31 deletions(-) delete mode 100644 extensions/vscode/vscodeignore diff --git a/extensions/vscode/.vscodeignore b/extensions/vscode/.vscodeignore index fa10c4525..1d6c6e7a2 100644 --- a/extensions/vscode/.vscodeignore +++ b/extensions/vscode/.vscodeignore @@ -1,11 +1,24 @@ .vscode/** .vscode-test/** src/** -.gitignore .yarnrc -**/tsconfig.json -**/.eslintrc.json -**/*.map -**/*.ts +**/_.map +\*\*/_.ts CONTRIBUTING.md vsc-extension-quickstart.md + +# Ignore all webview files except the build directories + +webviews/homeView/src/** +webviews/homeView/index.html +webviews/homeView/package.json +webviews/homeView/package-lock.json +webviews/homeView/README.md +webviews/homeView/node_modules/** + +# Ignore configuration files + +**/.gitignore +**/.eslintrc.json +**/tsconfig\*.json +**/vite.config.json diff --git a/extensions/vscode/vscodeignore b/extensions/vscode/vscodeignore deleted file mode 100644 index a70b09f0a..000000000 --- a/extensions/vscode/vscodeignore +++ /dev/null @@ -1,26 +0,0 @@ -# `.vscodeignore` - -.vscode/** -.vscode-test/** -src/** -.yarnrc -**/_.map -\*\*/_.ts -CONTRIBUTING.md -vsc-extension-quickstart.md - -# Ignore all webview files except the build directories - -webviews/homeView/src/** -webviews/homeView/index.html -webviews/homeView/package.json -webviews/homeView/package-lock.json -webviews/homeView/README.md -webviews/homeView/node_modules/** - -# Ignore configuration files - -**/.gitignore -**/.eslintrc.json -**/tsconfig\*.json -**/vite.config.json From 88aee87be23bb1b96a04b4275344410391570806 Mon Sep 17 00:00:00 2001 From: Jordan Jensen Date: Mon, 1 Apr 2024 12:48:30 -0700 Subject: [PATCH 06/33] Make homeView package type: module --- extensions/vscode/webviews/homeView/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/vscode/webviews/homeView/package.json b/extensions/vscode/webviews/homeView/package.json index 953d581d8..e2117d18c 100644 --- a/extensions/vscode/webviews/homeView/package.json +++ b/extensions/vscode/webviews/homeView/package.json @@ -2,6 +2,7 @@ "name": "project-selector-web-view-view", "version": "0.0.1", "private": true, + "type": "module", "scripts": { "compile": "npm run clean-dist && npm run type-check && npm run vite-build && npm run copy-codicons", "clean-dist": "rm -rd ../../out/webviews/homeView | true", From 12956946e5a016d381e232d0a10f1cbcf5103a6d Mon Sep 17 00:00:00 2001 From: Jordan Jensen Date: Mon, 1 Apr 2024 12:52:03 -0700 Subject: [PATCH 07/33] Add resolve alias to vite config --- extensions/vscode/webviews/homeView/vite.config.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/extensions/vscode/webviews/homeView/vite.config.ts b/extensions/vscode/webviews/homeView/vite.config.ts index c56db1c74..36f49f464 100644 --- a/extensions/vscode/webviews/homeView/vite.config.ts +++ b/extensions/vscode/webviews/homeView/vite.config.ts @@ -1,5 +1,6 @@ // import { fileURLToPath, URL } from 'url' +import { fileURLToPath } from "url"; import { defineConfig } from "vite"; import vue from "@vitejs/plugin-vue"; @@ -12,6 +13,11 @@ export default defineConfig({ }, }), ], + resolve: { + alias: { + src: fileURLToPath(new URL("./src", import.meta.url)), + }, + }, build: { outDir: "../../out/webviews/homeView", rollupOptions: { From 541a97e33aeba5dcd0fc7630f8c3c049fa931b56 Mon Sep 17 00:00:00 2001 From: Michael Marchetti Date: Mon, 1 Apr 2024 16:09:08 -0400 Subject: [PATCH 08/33] always enable access log --- cmd/publisher/commands/ui.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/publisher/commands/ui.go b/cmd/publisher/commands/ui.go index 62f9548fd..70a8de98e 100644 --- a/cmd/publisher/commands/ui.go +++ b/cmd/publisher/commands/ui.go @@ -16,7 +16,6 @@ type UICmd struct { OpenBrowserAt string `help:"Network address to use when launching the browser." placeholder:"HOST[:PORT]" hidden:""` Theme string `help:"UI theme, 'light' or 'dark'." hidden:""` Listen string `help:"Network address to listen on." placeholder:"HOST[:PORT]" default:"localhost:0"` - AccessLog bool `help:"Log all HTTP requests."` TLSKeyFile string `help:"Path to TLS private key file for the UI server."` TLSCertFile string `help:"Path to TLS certificate chain file for the UI server."` } @@ -45,7 +44,7 @@ func (cmd *UICmd) Run(args *cli_types.CommonArgs, ctx *cli_types.CLIContext) err cmd.OpenBrowserAt, cmd.Theme, cmd.Listen, - cmd.AccessLog, + true, cmd.TLSKeyFile, cmd.TLSCertFile, absPath, From bb1d22adf63af596a713fd6eab986c1e58fa73ef Mon Sep 17 00:00:00 2001 From: Michael Marchetti Date: Mon, 1 Apr 2024 16:09:29 -0400 Subject: [PATCH 09/33] log JSON request bodies --- internal/services/middleware/log_request.go | 45 +++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/internal/services/middleware/log_request.go b/internal/services/middleware/log_request.go index 2e0172ce0..a5bd82788 100644 --- a/internal/services/middleware/log_request.go +++ b/internal/services/middleware/log_request.go @@ -3,7 +3,12 @@ package middleware // Copyright (C) 2023 by Posit Software, PBC. import ( + "bytes" + "encoding/json" + "fmt" + "io" "net/http" + "strings" "time" "github.com/rstudio/connect-client/internal/logging" @@ -18,6 +23,7 @@ type statusCapturingResponseWriter struct { func NewStatusCapturingResponseWriter(w http.ResponseWriter) *statusCapturingResponseWriter { return &statusCapturingResponseWriter{ writer: w, + status: http.StatusOK, } } @@ -43,11 +49,47 @@ func (w *statusCapturingResponseWriter) GetBytesSent() int64 { return w.bytesSent } +func (w *statusCapturingResponseWriter) Flush() { + // sse.Server.ServeHTTP requires that the writer also implement http.Flusher + w.writer.(http.Flusher).Flush() +} + +func getBodyParams(req *http.Request) map[string]string { + contentType := req.Header.Get("Content-Type") + if contentType != "application/json" { + return nil + } + body, err := io.ReadAll(req.Body) + if err != nil { + return nil + } + req.Body = io.NopCloser(bytes.NewReader(body)) + + jsonData := map[string]any{} + err = json.Unmarshal(body, &jsonData) + if err != nil { + return nil + } + + bodyParams := map[string]string{} + for k, v := range jsonData { + if strings.HasSuffix(k, "Key") || + strings.HasSuffix(k, "Secret") || + strings.HasSuffix(k, "Password") { + bodyParams[k] = "[redacted]" + } else { + bodyParams[k] = fmt.Sprintf("%s", v) + } + } + return bodyParams +} + // LogRequest logs request info to the specified logger. func LogRequest(msg string, log logging.Logger, next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { startTime := time.Now() writer := NewStatusCapturingResponseWriter(w) + bodyParams := getBodyParams(req) next(writer, req) elapsedMs := time.Since(startTime).Milliseconds() @@ -64,6 +106,9 @@ func LogRequest(msg string, log logging.Logger, next http.HandlerFunc) http.Hand if correlationId != "" { fieldLogger = fieldLogger.WithArgs("X-Correlation-Id", correlationId) } + for k, v := range bodyParams { + fieldLogger = fieldLogger.WithArgs(k, v) + } fieldLogger.Info(msg) } } From 9b3a4d4ce97557262f35669dfa16d72219fac388 Mon Sep 17 00:00:00 2001 From: Michael Marchetti Date: Mon, 1 Apr 2024 16:22:19 -0400 Subject: [PATCH 10/33] tests --- .../services/middleware/log_request_test.go | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 internal/services/middleware/log_request_test.go diff --git a/internal/services/middleware/log_request_test.go b/internal/services/middleware/log_request_test.go new file mode 100644 index 000000000..d1cc31df5 --- /dev/null +++ b/internal/services/middleware/log_request_test.go @@ -0,0 +1,81 @@ +package middleware + +import ( + "bytes" + "log/slog" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/rstudio/connect-client/internal/logging" + "github.com/rstudio/connect-client/internal/util/utiltest" + "github.com/stretchr/testify/suite" +) + +// Copyright (C) 2023 by Posit Software, PBC. + +type LogRequestSuite struct { + utiltest.Suite + log logging.Logger + logBuffer *bytes.Buffer +} + +func TestLogRequestSuite(t *testing.T) { + suite.Run(t, new(LogRequestSuite)) +} + +func (s *LogRequestSuite) SetupTest() { + s.logBuffer = new(bytes.Buffer) + opts := &slog.HandlerOptions{Level: slog.LevelInfo} + stdLogger := slog.New(slog.NewTextHandler(s.logBuffer, opts)) + s.log = logging.FromStdLogger(stdLogger) +} + +func (s *LogRequestSuite) TestLogRequest() { + path := "/api/foo" + next := func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("hi there")) + } + + rec := httptest.NewRecorder() + req, err := http.NewRequest(http.MethodGet, path, nil) + s.Nil(err) + + LogRequest("Access", s.log, http.HandlerFunc(next)).ServeHTTP(rec, req) + + message := s.logBuffer.String() + s.Contains(message, "method=GET ") + s.Contains(message, "url=/api/foo ") + s.Contains(message, "elapsed_ms=") + s.Contains(message, "status=200 ") + s.Contains(message, "req_size=0 ") + s.Contains(message, "resp_size=8 ") + s.Contains(message, "client_addr=") +} + +func (s *LogRequestSuite) TestLogRequestJson() { + path := "/api/bar" + next := func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusBadRequest) + } + + body := `{"hi": "there", "what": "huh?"}` + rec := httptest.NewRecorder() + req, err := http.NewRequest(http.MethodPost, path, strings.NewReader(body)) + s.Nil(err) + req.Header.Set("Content-Type", "application/json") + + LogRequest("Access", s.log, http.HandlerFunc(next)).ServeHTTP(rec, req) + + message := s.logBuffer.String() + s.Contains(message, "method=POST ") + s.Contains(message, "url=/api/bar ") + s.Contains(message, "elapsed_ms=") + s.Contains(message, "status=400 ") + s.Contains(message, "req_size=31 ") + s.Contains(message, "resp_size=0 ") + s.Contains(message, "client_addr=") + s.Contains(message, "hi=there") + s.Contains(message, "what=huh?") +} From ea8b4f7b38bb380fa159f969573e712f359c045d Mon Sep 17 00:00:00 2001 From: Bill Sager Date: Mon, 1 Apr 2024 13:24:19 -0700 Subject: [PATCH 11/33] include webview's npm install/ci into extension justfile --- extensions/vscode/justfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extensions/vscode/justfile b/extensions/vscode/justfile index d1eb732d8..86ecb0ceb 100644 --- a/extensions/vscode/justfile +++ b/extensions/vscode/justfile @@ -65,8 +65,10 @@ deps: if [ {{ _ci }} = "true" ]; then npm ci --no-audit --no-fund | sed 's/^/debug: /' + npm -prefix ./webviews/homeView ci --no-audit --no-fund | sed 's/^/debug: /' else npm install --no-audit --no-fund | sed 's/^/debug: /' + npm -prefix ./webviews/homeView install --no-audit --no-fund | sed 's/^/debug: /' fi From ab2ce49f8c56be98d163d84f8e411ac8a80bbeef Mon Sep 17 00:00:00 2001 From: Michael Marchetti Date: Mon, 1 Apr 2024 16:26:27 -0400 Subject: [PATCH 12/33] change format --- internal/services/middleware/log_request.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/services/middleware/log_request.go b/internal/services/middleware/log_request.go index a5bd82788..5b1bbd5c2 100644 --- a/internal/services/middleware/log_request.go +++ b/internal/services/middleware/log_request.go @@ -78,7 +78,7 @@ func getBodyParams(req *http.Request) map[string]string { strings.HasSuffix(k, "Password") { bodyParams[k] = "[redacted]" } else { - bodyParams[k] = fmt.Sprintf("%s", v) + bodyParams[k] = fmt.Sprintf("%v", v) } } return bodyParams From 4c1c364ba70aadf6a2fde517ec09920ff93e7188 Mon Sep 17 00:00:00 2001 From: Michael Marchetti Date: Mon, 1 Apr 2024 16:38:56 -0400 Subject: [PATCH 13/33] no access-log option on the CLI --- test/bats/cli/common.bats | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/bats/cli/common.bats b/test/bats/cli/common.bats index 87e9d8715..af0a4bc10 100644 --- a/test/bats/cli/common.bats +++ b/test/bats/cli/common.bats @@ -58,7 +58,6 @@ load '../node_modules/bats-assert/load' assert_line " -v, --verbose=INT Enable verbose logging. Use -vv or --verbose=2" assert_line " -i, --interactive Launch a browser to show the UI." assert_line " --listen=HOST[:PORT] Network address to listen on." - assert_line " --access-log Log all HTTP requests." assert_line " --tls-key-file=STRING Path to TLS private key file for the UI server." assert_line " --tls-cert-file=STRING Path to TLS certificate chain file for the UI" } @@ -105,7 +104,7 @@ load '../node_modules/bats-assert/load' } @test "test missing command" { - run ${EXE} + run ${EXE} assert_failure assert_line --partial "error: expected one of \"list-accounts\", \"test-account\", \"init\", \"deploy\", \"redeploy\", ..." } From 4ccbab072b7c393d3ec16cfde71f1b1268e87a50 Mon Sep 17 00:00:00 2001 From: Michael Marchetti Date: Mon, 1 Apr 2024 16:52:38 -0400 Subject: [PATCH 14/33] rename error field in pre-deployment API response --- extensions/vscode/src/api/types/deployments.ts | 9 ++++----- internal/services/api/deployment_dto.go | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/extensions/vscode/src/api/types/deployments.ts b/extensions/vscode/src/api/types/deployments.ts index 3ecaabec4..e85ab4bee 100644 --- a/extensions/vscode/src/api/types/deployments.ts +++ b/extensions/vscode/src/api/types/deployments.ts @@ -28,11 +28,11 @@ type DeploymentRecord = { saveName: string; createdAt: string; configurationName: string; + deploymentError: AgentError | null; } & DeploymentLocation; export type PreDeployment = { state: DeploymentState.NEW; - error: AgentError | null; } & DeploymentRecord; export type Deployment = { @@ -44,7 +44,6 @@ export type Deployment = { files: string[]; deployedAt: string; state: DeploymentState.DEPLOYED; - deploymentError: AgentError | null; } & DeploymentRecord & Configuration; @@ -56,10 +55,10 @@ export function isSuccessful( if (d === undefined) { return undefined; } - if (isDeployment(d)) { - return Boolean(!d.deploymentError); + if (isDeploymentError(d)) { + return false; } - return Boolean(!d.error); + return Boolean(!d.deploymentError); } export function isUnsuccessful( diff --git a/internal/services/api/deployment_dto.go b/internal/services/api/deployment_dto.go index 2d2183ca3..4cf2aa5f4 100644 --- a/internal/services/api/deployment_dto.go +++ b/internal/services/api/deployment_dto.go @@ -31,7 +31,7 @@ type preDeploymentDTO struct { ServerURL string `json:"serverUrl"` SaveName string `json:"saveName"` CreatedAt string `json:"createdAt"` - Error *types.AgentError `json:"error,omitempty"` + Error *types.AgentError `json:"deploymentError,omitempty"` } type fullDeploymentDTO struct { From a369a2db217c4beb3739f8d928fb983b0adf94d1 Mon Sep 17 00:00:00 2001 From: Bill Sager Date: Mon, 1 Apr 2024 15:18:02 -0700 Subject: [PATCH 15/33] add justfile for webview --- extensions/vscode/justfile | 5 +-- extensions/vscode/webviews/homeView/justfile | 32 ++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 extensions/vscode/webviews/homeView/justfile diff --git a/extensions/vscode/justfile b/extensions/vscode/justfile index 86ecb0ceb..833144b34 100644 --- a/extensions/vscode/justfile +++ b/extensions/vscode/justfile @@ -56,6 +56,8 @@ clean: rm -rf dist rm -rf node_modules rm -rf out + just ./webviews/homeView/clean + # Install dependencies deps: @@ -65,11 +67,10 @@ deps: if [ {{ _ci }} = "true" ]; then npm ci --no-audit --no-fund | sed 's/^/debug: /' - npm -prefix ./webviews/homeView ci --no-audit --no-fund | sed 's/^/debug: /' else npm install --no-audit --no-fund | sed 's/^/debug: /' - npm -prefix ./webviews/homeView install --no-audit --no-fund | sed 's/^/debug: /' fi + just ./webviews/homeView/deps configure os="$(just ../../os)" arch="$(just ../../arch)": diff --git a/extensions/vscode/webviews/homeView/justfile b/extensions/vscode/webviews/homeView/justfile new file mode 100644 index 000000000..de5a98cb2 --- /dev/null +++ b/extensions/vscode/webviews/homeView/justfile @@ -0,0 +1,32 @@ +alias c := clean + +_ci := env_var_or_default("CI", "false") + +_debug := env_var_or_default("DEBUG", "false") + +_with_debug := if _debug == "true" { + "set -x pipefail" + } else { + "" + } + +# Deletes ephemeral project files (i.e., cleans the project). +clean: + #!/usr/bin/env bash + set -eou pipefail + {{ _with_debug }} + + rm -rf node_modules + + +# Install dependencies +deps: + #!/usr/bin/env bash + set -eou pipefail + {{ _with_debug }} + + if [ {{ _ci }} = "true" ]; then + npm ci --no-audit --no-fund | sed 's/^/debug: /' + else + npm install --no-audit --no-fund | sed 's/^/debug: /' + fi From e95d335834f0fbf2b154e56ae9abbd00a2747625 Mon Sep 17 00:00:00 2001 From: Bill Sager Date: Mon, 1 Apr 2024 15:51:45 -0700 Subject: [PATCH 16/33] PR request --- extensions/vscode/webviews/homeView/.gitignore | 4 ---- 1 file changed, 4 deletions(-) diff --git a/extensions/vscode/webviews/homeView/.gitignore b/extensions/vscode/webviews/homeView/.gitignore index c0cebf88d..fd1244a35 100644 --- a/extensions/vscode/webviews/homeView/.gitignore +++ b/extensions/vscode/webviews/homeView/.gitignore @@ -1,6 +1,2 @@ node_modules .vscode/* -main.js -main.js.map -vite.config.js -vite.config.js.map From 379054319860f6055e69b010eb333862c7ded442 Mon Sep 17 00:00:00 2001 From: Michael Marchetti Date: Tue, 2 Apr 2024 10:05:50 -0400 Subject: [PATCH 17/33] prefix the body fields --- internal/services/middleware/log_request.go | 8 ++++++-- internal/services/middleware/log_request_test.go | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/internal/services/middleware/log_request.go b/internal/services/middleware/log_request.go index 5b1bbd5c2..ebbb03d0f 100644 --- a/internal/services/middleware/log_request.go +++ b/internal/services/middleware/log_request.go @@ -106,8 +106,12 @@ func LogRequest(msg string, log logging.Logger, next http.HandlerFunc) http.Hand if correlationId != "" { fieldLogger = fieldLogger.WithArgs("X-Correlation-Id", correlationId) } - for k, v := range bodyParams { - fieldLogger = fieldLogger.WithArgs(k, v) + if bodyParams != nil { + fieldLogger = logging.FromStdLogger(fieldLogger.WithGroup("req")) + + for k, v := range bodyParams { + fieldLogger = fieldLogger.WithArgs(k, v) + } } fieldLogger.Info(msg) } diff --git a/internal/services/middleware/log_request_test.go b/internal/services/middleware/log_request_test.go index d1cc31df5..6620b19e7 100644 --- a/internal/services/middleware/log_request_test.go +++ b/internal/services/middleware/log_request_test.go @@ -76,6 +76,6 @@ func (s *LogRequestSuite) TestLogRequestJson() { s.Contains(message, "req_size=31 ") s.Contains(message, "resp_size=0 ") s.Contains(message, "client_addr=") - s.Contains(message, "hi=there") - s.Contains(message, "what=huh?") + s.Contains(message, "req.hi=there") + s.Contains(message, "req.what=huh?") } From 7dc6139b7c148a298e3a444659feed964b01f086 Mon Sep 17 00:00:00 2001 From: Michael Marchetti Date: Tue, 2 Apr 2024 10:53:44 -0400 Subject: [PATCH 18/33] return 409 if redeployment is to a different server --- internal/services/api/post_deployment.go | 5 ++ internal/services/api/post_deployment_test.go | 55 ++++++++++++++++++- internal/state/state.go | 6 +- 3 files changed, 63 insertions(+), 3 deletions(-) diff --git a/internal/services/api/post_deployment.go b/internal/services/api/post_deployment.go index e18eb1209..a1e6b8a14 100644 --- a/internal/services/api/post_deployment.go +++ b/internal/services/api/post_deployment.go @@ -53,11 +53,16 @@ func PostDeploymentHandlerFunc( if err != nil { if errors.Is(err, accounts.ErrAccountNotFound) { NotFound(w, log, err) + } else if errors.Is(err, state.ErrServerURLMismatch) { + // Redeployments must go to the same server + w.WriteHeader(http.StatusConflict) + return } else { BadRequest(w, req, log, err) } return } + response := PostDeploymentsReponse{ LocalID: localID, } diff --git a/internal/services/api/post_deployment_test.go b/internal/services/api/post_deployment_test.go index cce7a129c..772388ae8 100644 --- a/internal/services/api/post_deployment_test.go +++ b/internal/services/api/post_deployment_test.go @@ -12,6 +12,8 @@ import ( "github.com/gorilla/mux" "github.com/rstudio/connect-client/internal/accounts" + "github.com/rstudio/connect-client/internal/config" + "github.com/rstudio/connect-client/internal/deployment" "github.com/rstudio/connect-client/internal/events" "github.com/rstudio/connect-client/internal/logging" "github.com/rstudio/connect-client/internal/publish" @@ -80,7 +82,11 @@ func (s *PostDeploymentHandlerFuncSuite) TestPostDeploymentHandlerFunc() { s.Equal("local", accountName) s.Equal("default", configName) s.Equal("", saveName) - return state.Empty(), nil + + st := state.Empty() + st.Account = &accounts.Account{} + st.Target = deployment.New() + return st, nil } handler := PostDeploymentHandlerFunc(util.AbsolutePath{}, log, lister, events.NewNullEmitter()) handler(rec, req) @@ -121,6 +127,47 @@ func (s *PostDeploymentHandlerFuncSuite) TestPostDeploymentHandlerFuncStateErr() s.Equal(http.StatusBadRequest, rec.Result().StatusCode) } +func (s *PostDeploymentHandlerFuncSuite) TestPostDeploymentHandlerFuncWrongServer() { + log := logging.New() + + deploymentName := "myDeployment" + rec := httptest.NewRecorder() + req, err := http.NewRequest("POST", "/api/deployments/"+deploymentName, nil) + s.NoError(err) + req = mux.SetURLVars(req, map[string]string{"name": deploymentName}) + + originalAcct := &accounts.Account{ + URL: "https://connect.example.com", + } + newAcct := &accounts.Account{ + URL: "https://some.other.server.com", + } + + d := deployment.New() + d.ServerURL = originalAcct.URL + err = d.WriteFile(deployment.GetDeploymentPath(s.cwd, deploymentName)) + s.NoError(err) + + cfg := config.New() + cfg.Type = config.ContentTypeHTML + err = cfg.WriteFile(config.GetConfigPath(s.cwd, "default")) + s.NoError(err) + + lister := &accounts.MockAccountList{} + lister.On("GetAccountByName", "newAcct").Return(newAcct, nil) + + req.Body = io.NopCloser(strings.NewReader( + `{ + "account": "newAcct", + "config": "default" + }`)) + + handler := PostDeploymentHandlerFunc(s.cwd, log, lister, events.NewNullEmitter()) + handler(rec, req) + + s.Equal(http.StatusConflict, rec.Result().StatusCode) +} + func (s *PostDeploymentHandlerFuncSuite) TestPostDeploymentHandlerFuncPublishErr() { log := logging.New() rec := httptest.NewRecorder() @@ -134,7 +181,11 @@ func (s *PostDeploymentHandlerFuncSuite) TestPostDeploymentHandlerFuncPublishErr path util.AbsolutePath, accountName, configName, targetName, saveName string, accountList accounts.AccountList) (*state.State, error) { - return state.Empty(), nil + + st := state.Empty() + st.Account = &accounts.Account{} + st.Target = deployment.New() + return st, nil } testErr := errors.New("test error from PublishDirectory") diff --git a/internal/state/state.go b/internal/state/state.go index 64fbc3bcf..0b22d4618 100644 --- a/internal/state/state.go +++ b/internal/state/state.go @@ -93,6 +93,8 @@ func Empty() *State { } } +var ErrServerURLMismatch = errors.New("the account provided is for a different server; it must match the one previously used for this deployment") + func New(path util.AbsolutePath, accountName, configName, targetName string, saveName string, accountList accounts.AccountList) (*State, error) { var target *deployment.Deployment var account *accounts.Account @@ -126,7 +128,9 @@ func New(path util.AbsolutePath, accountName, configName, targetName string, sav return nil, err } - target.ServerURL = account.URL + if target.ServerURL != "" && target.ServerURL != account.URL { + return nil, ErrServerURLMismatch + } if configName == "" { configName = config.DefaultConfigName From 764da40b328a4b216cab46a0957d5ea8e2856076 Mon Sep 17 00:00:00 2001 From: Kevin Gartland Date: Tue, 2 Apr 2024 15:41:00 -0400 Subject: [PATCH 19/33] use docker compose --- test/scripts/test-runner.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/scripts/test-runner.bash b/test/scripts/test-runner.bash index 61ed49f4a..affdf839f 100755 --- a/test/scripts/test-runner.bash +++ b/test/scripts/test-runner.bash @@ -14,7 +14,7 @@ fi setup_connect() { pip install -r ../setup/requirements.txt if [[ "${DOCKER_CONNECT}" = true ]]; then - docker-compose -f ../docker-compose.yml up -d + docker compose -f ../docker-compose.yml up -d export CONNECT_SERVER="http://localhost:3939" export CONNECT_API_KEY="$(python ../setup/gen_apikey.py 'admin')" # wait until Connect is available From c3b162ed424da23971a20bce99c877a9f4608ee2 Mon Sep 17 00:00:00 2001 From: Bill Sager Date: Fri, 29 Mar 2024 16:02:40 -0700 Subject: [PATCH 20/33] Implement Home View --- extensions/vscode/package-lock.json | 16 + extensions/vscode/package.json | 65 ++- extensions/vscode/src/extension.ts | 12 +- .../createNewDeploymentFile.ts | 217 ++++++++ extensions/vscode/src/views/configurations.ts | 12 +- extensions/vscode/src/views/deployments.ts | 25 +- extensions/vscode/src/views/homeView.ts | 314 ++++++++++- .../vscode/webviews/homeView/src/App.vue | 515 +++++++++++++++++- scripts/ccheck.config | 3 + 9 files changed, 1151 insertions(+), 28 deletions(-) create mode 100644 extensions/vscode/src/multiStepInputs/createNewDeploymentFile.ts diff --git a/extensions/vscode/package-lock.json b/extensions/vscode/package-lock.json index cbefa9e95..a3eb91f61 100644 --- a/extensions/vscode/package-lock.json +++ b/extensions/vscode/package-lock.json @@ -29,6 +29,7 @@ "eslint-config-prettier": "^9.1.0", "glob": "^10.3.3", "mocha": "^10.2.0", + "prettier": "3.2.5", "typescript": "^5.2.2" }, "engines": { @@ -2134,6 +2135,21 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", diff --git a/extensions/vscode/package.json b/extensions/vscode/package.json index 236183380..64e6730ad 100644 --- a/extensions/vscode/package.json +++ b/extensions/vscode/package.json @@ -95,6 +95,12 @@ "icon": "$(add)", "category": "Posit Publisher" }, + { + "command": "posit.publisher.deployments.createNew", + "title": "Create New Deployment File", + "icon": "$(add)", + "category": "Posit Publisher" + }, { "command": "posit.publisher.deployments.deploy", "title": "Deploy", @@ -140,6 +146,12 @@ "icon": "$(eye)", "category": "Posit Publisher" }, + { + "command": "posit.publisher.homeView.refresh", + "title": "Refresh View", + "icon": "$(refresh)", + "category": "Posit Publisher" + }, { "command": "posit.publisher.helpAndFeedback.gettingStarted", "title": "Open Getting Started Documentation", @@ -155,6 +167,18 @@ "title": "View Deployment in Connect", "icon": "$(link-external)", "category": "Posit Publisher" + }, + { + "command": "posit.publisher.homeView.showBasicMode", + "title": "Basic Mode", + "icon": "$(target)", + "category": "Posit Publisher" + }, + { + "command": "posit.publisher.homeView.showAdvancedMode", + "title": "Advanced Mode", + "icon": "$(list-tree)", + "category": "Posit Publisher" } ], "configuration": { @@ -217,6 +241,21 @@ "command": "posit.publisher.credentials.refresh", "when": "view == posit.publisher.credentials", "group": "navigation" + }, + { + "command": "posit.publisher.homeView.showBasicMode", + "when": "view == posit.publisher.homeView", + "group": "navigation@1" + }, + { + "command": "posit.publisher.homeView.showAdvancedMode", + "when": "view == posit.publisher.homeView", + "group": "navigation@2" + }, + { + "command": "posit.publisher.homeView.refresh", + "when": "view == posit.publisher.homeView && posit.publisher.homeView.expanded", + "group": "navigation@3" } ], "view/item/context": [ @@ -320,6 +359,10 @@ { "command": "posit.publisher.requirements.scan", "when": "!posit.publish.missing && posit.publish.state == 'initialized'" + }, + { + "command": "posit.publisher.homeView.refresh", + "when": "!posit.publish.missing && posit.publish.state == 'initialized'" } ] }, @@ -360,40 +403,35 @@ "name": "Deployment Files", "contextualTitle": "Publisher", "icon": "$(symbol-file)", - "visibility": "collapsed", - "when": "workbenchState == folder && !posit.publish.missing && posit.publish.state == 'initialized'" + "when": "workbenchState == folder && !posit.publish.missing && posit.publish.state == 'initialized' && posit.publisher.homeView.deploymentActiveMode == 'basic-mode'" }, { "id": "posit.publisher.requirements", "name": "Requirements", "contextualTitle": "Publisher", "icon": "$(package)", - "visibility": "collapsed", - "when": "workbenchState == folder && !posit.publish.missing && posit.publish.state == 'initialized'" + "when": "workbenchState == folder && !posit.publish.missing && posit.publish.state == 'initialized' && posit.publisher.homeView.deploymentActiveMode == 'basic-mode'" }, { "id": "posit.publisher.configurations", "name": "Configurations", "contextualTitle": "Publisher", "icon": "$(gear)", - "visibility": "collapsed", - "when": "workbenchState == folder && !posit.publish.missing && posit.publish.state == 'initialized'" + "when": "workbenchState == folder && !posit.publish.missing && posit.publish.state == 'initialized' && posit.publisher.homeView.deploymentActiveMode == 'advanced-mode'" }, { "id": "posit.publisher.credentials", "name": "Credentials", "contextualTitle": "Publisher", "icon": "$(key)", - "visibility": "collapsed", - "when": "workbenchState == folder && !posit.publish.missing && posit.publish.state == 'initialized'" + "when": "workbenchState == folder && !posit.publish.missing && posit.publish.state == 'initialized' && posit.publisher.homeView.deploymentActiveMode == 'advanced-mode'" }, { "id": "posit.publisher.deployments", "name": "Deployments", "contextualTitle": "Publisher", "icon": "$(cloud-upload)", - "visibility": "collapsed", - "when": "workbenchState == folder && !posit.publish.missing && posit.publish.state == 'initialized'" + "when": "workbenchState == folder && !posit.publish.missing && posit.publish.state == 'initialized' && posit.publisher.homeView.deploymentActiveMode == 'advanced-mode'" }, { "id": "posit.publisher.helpAndFeedback", @@ -459,12 +497,14 @@ }, "scripts": { "vscode:prepublish": "npm run compile", - "compile": "npm run compile-extension && npm run compile-home-view", + "compile": "npm run compile-extension && npm run compile-deploy-selector", "compile-extension": "tsc -p ./", - "compile-home-view": "npm run --prefix webviews/homeView compile", "watch": "tsc -watch -p ./", "pretest": "npm run compile", "lint": "eslint src --ext ts", + "compile-deploy-selector": "npm run --prefix webviews/homeView compile", + "format": "prettier . --write", + "check-format": "prettier . --check", "test": "node ./out/test/runTest.js" }, "devDependencies": { @@ -482,6 +522,7 @@ "eslint-config-prettier": "^9.1.0", "glob": "^10.3.3", "mocha": "^10.2.0", + "prettier": "3.2.5", "typescript": "^5.2.2" }, "dependencies": { diff --git a/extensions/vscode/src/extension.ts b/extensions/vscode/src/extension.ts index 791350d6c..e59531ce2 100644 --- a/extensions/vscode/src/extension.ts +++ b/extensions/vscode/src/extension.ts @@ -13,9 +13,10 @@ import { FilesTreeDataProvider } from "./views/files"; import { RequirementsTreeDataProvider } from "./views/requirements"; import { CredentialsTreeDataProvider } from "./views/credentials"; import { HelpAndFeedbackTreeDataProvider } from "./views/helpAndFeedback"; -import { HomeViewProvider } from "./views/homeView"; import { LogsTreeDataProvider } from "./views/logs"; import { EventStream } from "./events"; +import { HomeViewProvider } from "./views/homeView"; +import { commands } from "vscode"; const STATE_CONTEXT = "posit.publish.state"; const MISSING_CONTEXT = "posit.publish.missing"; @@ -78,7 +79,12 @@ export async function activate(context: vscode.ExtensionContext) { }); context.subscriptions.push(watcher); - setMissingContext(await isMissingPublishDirs(folder)); + // set our initial mode + commands.executeCommand( + "setContext", + "posit.publisher.homeView.deploymentActiveMode", + "basic-mode", + ); } else { setMissingContext(true); } @@ -98,7 +104,7 @@ export async function activate(context: vscode.ExtensionContext) { new CredentialsTreeDataProvider(apiReady).register(context); new HelpAndFeedbackTreeDataProvider().register(context); new LogsTreeDataProvider(stream).register(context); - new HomeViewProvider(context.extensionUri).register(context); + new HomeViewProvider(context.extensionUri, stream).register(context); await service.start(); diff --git a/extensions/vscode/src/multiStepInputs/createNewDeploymentFile.ts b/extensions/vscode/src/multiStepInputs/createNewDeploymentFile.ts new file mode 100644 index 000000000..31824e382 --- /dev/null +++ b/extensions/vscode/src/multiStepInputs/createNewDeploymentFile.ts @@ -0,0 +1,217 @@ +// Copyright (C) 2024 by Posit Software, PBC. + +import { + MultiStepInput, + MultiStepState, + isQuickPickItem, +} from "./multiStepHelper"; + +import { + InputBoxValidationSeverity, + QuickPickItem, + ThemeIcon, + window, +} from "vscode"; + +import { AccountAuthType, useApi } from "../api"; +import { getSummaryStringFromError } from "../utils/errors"; +import { uniqueDeploymentName, untitledDeploymentName } from "../utils/names"; +import { isValidFilename } from "../utils/files"; + +export async function createNewDeploymentFile() { + const api = useApi(); + + // *************************************************************** + // API Calls and results + // *************************************************************** + + let accountListItems: QuickPickItem[] = []; + let deploymentNames: string[] = []; + + try { + const response = await api.accounts.getAll(); + accountListItems = response.data.map((account) => ({ + iconPath: new ThemeIcon("account"), + label: account.name, + description: account.source, + detail: + account.authType === AccountAuthType.API_KEY + ? "Using API Key" + : `Using Token Auth for ${account.accountName}`, + })); + } catch (error: unknown) { + const summary = getSummaryStringFromError( + "createNewDeploymentFile, accounts.getAll", + error, + ); + window.showInformationMessage( + `Unable to continue with no credentials. ${summary}`, + ); + return; + } + + try { + const response = await api.deployments.getAll(); + const deploymentList = response.data; + deploymentNames = deploymentList.map( + (deployment) => deployment.deploymentName, + ); + } catch (error: unknown) { + const summary = getSummaryStringFromError( + "createNewDeploymentFile, deployments.getAll", + error, + ); + window.showInformationMessage( + `Unable to continue due to deployment error. ${summary}`, + ); + return; + } + + // *************************************************************** + // Order of all steps + // *************************************************************** + + // Name the deployment + // Select the credential to use, if there is more than one + + // *************************************************************** + // Method which kicks off the multi-step. + // Initialize the state data + // Display the first input panel + // *************************************************************** + async function collectInputs() { + const state: MultiStepState = { + title: "Create a Deployment File for your Project", + step: -1, + lastStep: 0, + totalSteps: -1, + data: { + // each attribute is initialized to undefined + // to be returned when it has not been cancelled to assist type guards + deploymentName: undefined, // eventual type is string + credentialName: undefined, // eventual type is QuickPickItem + promptToDeploy: undefined, /// eventual type is QuickPickItem + configFile: undefined, // eventual type is QuickPickItem + }, + }; + // determin number of total steps, as each step + // will suppress its choice if there is only one option + let totalSteps = 2; + if (accountListItems.length === 1) { + totalSteps -= 1; + } + state.totalSteps = totalSteps; + + // start the progression through the steps + + await MultiStepInput.run((input) => inputDeploymentName(input, state)); + return state as MultiStepState; + } + + // *************************************************************** + // Step #1: + // Name the deployment + // *************************************************************** + async function inputDeploymentName( + input: MultiStepInput, + state: MultiStepState, + ) { + state.step = state.lastStep + 1; + + const deploymentName = await input.showInputBox({ + title: state.title, + step: state.step, + totalSteps: state.totalSteps, + value: + typeof state.data.deploymentName === "string" && + state.data.deploymentName.length + ? state.data.deploymentName + : untitledDeploymentName(deploymentNames), + prompt: "Choose a unique name for the deployment", + validate: (value) => { + if ( + value.length < 3 || + !uniqueDeploymentName(value, deploymentNames) || + !isValidFilename(value) + ) { + return Promise.resolve({ + message: `Invalid Name: Value must be unique across other deployment names for this project, be longer than 3 characters, cannot be '.' or contain '..' or any of these characters: /:*?"<>|\\`, + severity: InputBoxValidationSeverity.Error, + }); + } + return Promise.resolve(undefined); + }, + shouldResume: () => Promise.resolve(false), + }); + + state.data.deploymentName = deploymentName; + return (input: MultiStepInput) => pickCredentials(input, state); + } + + // *************************************************************** + // Step #2: + // Select the credentials to be used + // *************************************************************** + async function pickCredentials(input: MultiStepInput, state: MultiStepState) { + // skip if we only have one choice. + if (accountListItems.length > 1) { + const thisStepNumber = state.lastStep + 1; + const pick = await input.showQuickPick({ + title: state.title, + step: thisStepNumber, + totalSteps: state.totalSteps, + placeholder: "Select the credential you want to use to deploy", + items: accountListItems, + activeItem: + typeof state.data.credentialName !== "string" + ? state.data.credentialName + : undefined, + buttons: [], + shouldResume: () => Promise.resolve(false), + }); + state.data.credentialName = pick; + state.lastStep = thisStepNumber; + } else { + state.data.credentialName = accountListItems[0]; + } + } + + // *************************************************************** + // Kick off the input collection + // and await until it completes. + // This is a promise which returns the state data used to + // collect the info. + // *************************************************************** + const state = await collectInputs(); + + // make sure user has not hit escape or moved away from the window + // before completing the steps. This also serves as a type guard on + // our state data vars down to the actual type desired + if ( + state.data.deploymentName === undefined || + state.data.credentialName === undefined || + // have to add type guards here to eliminate the variability + typeof state.data.deploymentName !== "string" || + !isQuickPickItem(state.data.credentialName) + ) { + return; + } + + // Create the Predeployment File + try { + await api.deployments.createNew( + state.data.credentialName.label, + state.data.deploymentName, + ); + } catch (error: unknown) { + const summary = getSummaryStringFromError( + "addDeployment, createNew", + error, + ); + window.showInformationMessage( + `Failed to create pre-deployment. ${summary}`, + ); + return; + } + return state.data.deploymentName; +} diff --git a/extensions/vscode/src/views/configurations.ts b/extensions/vscode/src/views/configurations.ts index c2e52b1a8..899a4b6c9 100644 --- a/extensions/vscode/src/views/configurations.ts +++ b/extensions/vscode/src/views/configurations.ts @@ -106,7 +106,9 @@ export class ConfigurationsTreeDataProvider context.subscriptions.push( treeView, commands.registerCommand(refreshCommand, this.refresh), - commands.registerCommand(addCommand, this.add), + commands.registerCommand(addCommand, async () => { + return await this.add(); + }), commands.registerCommand(editCommand, this.edit), commands.registerCommand(renameCommand, this.rename), commands.registerCommand(cloneCommand, this.clone), @@ -139,6 +141,7 @@ export class ConfigurationsTreeDataProvider }; private add = async () => { + let configName: string | undefined; try { const inspectResponse = await api.configurations.inspect(); const config = await this.chooseConfig(inspectResponse.data); @@ -147,16 +150,16 @@ export class ConfigurationsTreeDataProvider return; } const defaultName = await untitledConfigurationName(); - const name = await window.showInputBox({ + configName = await window.showInputBox({ value: defaultName, prompt: "Configuration name", }); - if (name === undefined || name === "") { + if (configName === undefined || configName === "") { // canceled return; } const createResponse = await api.configurations.createOrUpdate( - name, + configName, config, ); if (this.root !== undefined) { @@ -167,6 +170,7 @@ export class ConfigurationsTreeDataProvider const summary = getSummaryStringFromError("configurations::add", error); window.showInformationMessage(summary); } + return configName; }; private edit = async (config: ConfigurationTreeItem) => { diff --git a/extensions/vscode/src/views/deployments.ts b/extensions/vscode/src/views/deployments.ts index 5f675199d..eae2f752e 100644 --- a/extensions/vscode/src/views/deployments.ts +++ b/extensions/vscode/src/views/deployments.ts @@ -30,6 +30,7 @@ import { import { confirmForget } from "../dialogs"; import { EventStream } from "../events"; import { addDeployment } from "../multiStepInputs/addDeployment"; +import { createNewDeploymentFile } from "../multiStepInputs/createNewDeploymentFile"; import { publishDeployment } from "../multiStepInputs/deployProject"; import { formatDateString } from "../utils/date"; import { getSummaryStringFromError } from "../utils/errors"; @@ -40,6 +41,7 @@ const editCommand = viewName + ".edit"; const forgetCommand = viewName + ".forget"; const visitCommand = viewName + ".visit"; const addCommand = viewName + ".add"; +const createNewCommand = viewName + ".createNew"; const deployCommand = viewName + ".deploy"; const isEmptyContext = viewName + ".isEmpty"; @@ -133,16 +135,13 @@ export class DeploymentsTreeDataProvider ); context.subscriptions.push( - commands.registerCommand(refreshCommand, this.refresh), + commands.registerCommand(createNewCommand, async () => { + return await createNewDeploymentFile(); + }), ); context.subscriptions.push( - commands.registerCommand( - editCommand, - async (item: DeploymentsTreeItem) => { - await commands.executeCommand("vscode.open", item.fileUri); - }, - ), + commands.registerCommand(refreshCommand, this.refresh), ); context.subscriptions.push( @@ -168,6 +167,18 @@ export class DeploymentsTreeDataProvider } }, ), + ); + + context.subscriptions.push( + commands.registerCommand( + editCommand, + async (item: DeploymentsTreeItem) => { + await commands.executeCommand("vscode.open", item.fileUri); + }, + ), + ); + + context.subscriptions.push( commands.registerCommand( visitCommand, async (item: DeploymentsTreeItem) => { diff --git a/extensions/vscode/src/views/homeView.ts b/extensions/vscode/src/views/homeView.ts index eaa0b92da..880dbe3bb 100644 --- a/extensions/vscode/src/views/homeView.ts +++ b/extensions/vscode/src/views/homeView.ts @@ -10,22 +10,303 @@ import { WebviewViewResolveContext, CancellationToken, ExtensionContext, + workspace, + WorkspaceFolder, + RelativePattern, + commands, + env, } from "vscode"; import { getUri } from "../utils/getUri"; import { getNonce } from "../utils/getNonce"; +import { + useApi, + isConfigurationError, + isDeploymentError, + EventStreamMessage, + Configuration, + Deployment, + PreDeployment, +} from "../api"; +import { getSummaryStringFromError } from "../utils/errors"; +import { deployProject } from "./deployProgress"; +import { EventStream } from "../events"; + +const deploymentFiles = ".posit/publish/deployments/*.toml"; +const configFiles = ".posit/publish/*.toml"; const viewName = "posit.publisher.homeView"; +const refreshCommand = viewName + ".refresh"; +const contextIsSelectorExpanded = viewName + ".expanded"; +const showBasicModeCommand = viewName + ".showBasicMode"; +const showAdvancedModeCommand = viewName + ".showAdvancedMode"; + +const contextActiveMode = viewName + ".deploymentActiveMode"; +const contextActiveModeAdvanced = "advanced-mode"; +const contextActiveModeBasic = "basic-mode"; export class HomeViewProvider implements WebviewViewProvider { private _disposables: Disposable[] = []; + private api = useApi(); + private _deployments: (Deployment | PreDeployment)[] = []; + private _credentials: string[] = []; + private _configs: Configuration[] = []; + private root: WorkspaceFolder | undefined; + private _webviewView?: WebviewView; + + constructor( + private readonly _extensionUri: Uri, + private readonly stream: EventStream, + ) { + const workspaceFolders = workspace.workspaceFolders; + if (workspaceFolders !== undefined) { + this.root = workspaceFolders[0]; + } + stream.register("publish/start", () => { + this._onPublishStart(); + }); + stream.register("publish/success", (msg: EventStreamMessage) => { + this._onPublishSuccess(msg); + }); + stream.register("publish/failure", (msg: EventStreamMessage) => { + this._onPublishFailure(msg); + }); + commands.executeCommand("setContext", contextIsSelectorExpanded, false); + + commands.registerCommand(showBasicModeCommand, () => { + commands.executeCommand( + "setContext", + contextActiveMode, + contextActiveModeBasic, + ); + }); + + commands.registerCommand(showAdvancedModeCommand, () => { + commands.executeCommand( + "setContext", + contextActiveMode, + contextActiveModeAdvanced, + ); + }); + } - constructor(private readonly _extensionUri: Uri) {} + /** + * Sets up an event listener to listen for messages passed from the webview context and + * executes code based on the message that is recieved. + * + * @param webview A reference to the extension webview + * @param context A reference to the extension context + */ + private _setWebviewMessageListener() { + if (!this._webviewView) { + return; + } + this._webviewView.webview.onDidReceiveMessage( + async (message: any) => { + const command = message.command; + switch (command) { + case "deploy": + const payload = JSON.parse(message.payload); + try { + const response = await this.api.deployments.publish( + payload.deployment, + payload.credential, + payload.configuration, + ); + deployProject(response.data.localId, this.stream); + } catch (error: unknown) { + const summary = getSummaryStringFromError( + "homeView, deploy", + error, + ); + window.showInformationMessage(`Failed to deploy . ${summary}`); + return; + } + return; + // Add more switch case statements here as more webview message commands + // are created within the webview context (i.e. inside media/main.js) + case "initializing": + // send back the data needed. + await this._refreshData(); + this._refreshWebViewViewData(); + return; + case "newDeployment": + const newFile: string = await commands.executeCommand( + "posit.publisher.deployments.createNew", + ); + if (newFile) { + this._updateDeploymentFileSelection(newFile); + } + break; + case "editConfiguration": + const config = this._configs.find( + (config) => config.configurationName === message.payload, + ); + if (config) { + await commands.executeCommand( + "vscode.open", + Uri.file(config.configurationPath), + ); + } + break; + + case "newConfiguration": + const newConfig: string = await commands.executeCommand( + "posit.publisher.configurations.add", + ); + if (newConfig) { + this._updateConfigFileSelection(newConfig); + } + break; + case "expanded": + commands.executeCommand( + "setContext", + contextIsSelectorExpanded, + true, + ); + break; + case "collapsed": + commands.executeCommand( + "setContext", + contextIsSelectorExpanded, + false, + ); + break; + case "navigate": + env.openExternal(Uri.parse(message.payload)); + break; + } + }, + undefined, + this._disposables, + ); + } + + private _onPublishStart() { + if (this._webviewView) { + this._webviewView.webview.postMessage({ + command: "publish_start", + }); + } else { + console.log("_onPublishStart: oops! No _webViewView defined!"); + } + } + + private _onPublishSuccess(msg: EventStreamMessage) { + if (this._webviewView) { + this._webviewView.webview.postMessage({ + command: "publish_finish_success", + payload: msg, + }); + } else { + console.log("_onPublishSuccess: oops! No _webViewView defined!"); + } + } + + private _onPublishFailure(msg: EventStreamMessage) { + if (this._webviewView) { + this._webviewView.webview.postMessage({ + command: "publish_finish_failure", + payload: msg, + }); + } else { + console.log("_onPublishFailure: oops! No _webViewView defined!"); + } + } + + private async _refreshData() { + try { + // API Returns: + // 200 - success + // 500 - internal server error + const response = await this.api.deployments.getAll(); + const deployments = response.data; + this._deployments = []; + deployments.forEach((deployment) => { + if (!isDeploymentError(deployment)) { + this._deployments.push(deployment); + } + }); + } catch (error: unknown) { + const summary = getSummaryStringFromError( + "_refreshData::deployments.getAll", + error, + ); + window.showInformationMessage(summary); + } + + try { + const response = await this.api.accounts.getAll(); + this._credentials = []; + response.data.forEach((account) => this._credentials.push(account.name)); + } catch (error: unknown) { + const summary = getSummaryStringFromError( + "_refreshData::accounts.getAll", + error, + ); + window.showInformationMessage(summary); + } + + try { + const response = await this.api.configurations.getAll(); + const configurations = response.data; + this._configs = []; + configurations.forEach((config) => { + if (!isConfigurationError(config)) { + this._configs.push(config); + } + }); + } catch (error: unknown) { + const summary = getSummaryStringFromError( + "configurations::getChildren", + error, + ); + window.showInformationMessage(summary); + } + } + + private _refreshWebViewViewData() { + if (this._webviewView) { + this._webviewView.webview.postMessage({ + command: "refresh_data", + payload: JSON.stringify({ + deployments: this._deployments, + configurations: this._configs.map( + (config) => config.configurationName, + ), + credentials: this._credentials, + }), + }); + } + } + + private _updateDeploymentFileSelection(name: string) { + if (this._webviewView) { + this._webviewView.webview.postMessage({ + command: "update_deployment_selection", + payload: JSON.stringify({ + name, + }), + }); + } + } + + private _updateConfigFileSelection(name: string) { + if (this._webviewView) { + this._webviewView.webview.postMessage({ + command: "update_config_selection", + payload: JSON.stringify({ + name, + }), + }); + } + } public resolveWebviewView( webviewView: WebviewView, _: WebviewViewResolveContext, _token: CancellationToken, ) { + this._webviewView = webviewView; // Allow scripts in the webview webviewView.webview.options = { // Enable JavaScript in the webview @@ -41,6 +322,10 @@ export class HomeViewProvider implements WebviewViewProvider { webviewView.webview, this._extensionUri, ); + + // Sets up an event listener to listen for messages passed from the webview view context + // and executes code based on the message that is recieved + this._setWebviewMessageListener(); } /** * Defines and returns the HTML that should be rendered within the webview panel. @@ -105,6 +390,11 @@ export class HomeViewProvider implements WebviewViewProvider { `; } + public refresh = async (_: Uri) => { + await this._refreshData(); + this._refreshWebViewViewData(); + }; + /** * Cleans up and disposes of webview resources when view is disposed */ @@ -125,5 +415,27 @@ export class HomeViewProvider implements WebviewViewProvider { }, }); context.subscriptions.push(provider); + + context.subscriptions.push( + commands.registerCommand(refreshCommand, this.refresh), + ); + + if (this.root !== undefined) { + const configFileWatcher = workspace.createFileSystemWatcher( + new RelativePattern(this.root, configFiles), + ); + configFileWatcher.onDidCreate(this.refresh); + configFileWatcher.onDidDelete(this.refresh); + configFileWatcher.onDidChange(this.refresh); + context.subscriptions.push(configFileWatcher); + + const deploymentFileWatcher = workspace.createFileSystemWatcher( + new RelativePattern(this.root, deploymentFiles), + ); + deploymentFileWatcher.onDidCreate(this.refresh); + deploymentFileWatcher.onDidDelete(this.refresh); + deploymentFileWatcher.onDidChange(this.refresh); + context.subscriptions.push(deploymentFileWatcher); + } } } diff --git a/extensions/vscode/webviews/homeView/src/App.vue b/extensions/vscode/webviews/homeView/src/App.vue index a283f4446..c8ae878e7 100644 --- a/extensions/vscode/webviews/homeView/src/App.vue +++ b/extensions/vscode/webviews/homeView/src/App.vue @@ -1,5 +1,518 @@ + + + + diff --git a/scripts/ccheck.config b/scripts/ccheck.config index 574e67152..97ac35ff4 100644 --- a/scripts/ccheck.config +++ b/scripts/ccheck.config @@ -15,3 +15,6 @@ web/src/**/*.vue extensions/vscode/src/**/*.ts extensions/vscode/src/**/*.js +extensions/vscode/webviews/homeView/src/*.ts +extensions/vscode/webviews/homeView/src/*.js +extensions/vscode/webviews/homeView/src/*.vue From 43b0e1cd9fc7391131497a6b295ad7fb78019858 Mon Sep 17 00:00:00 2001 From: Bill Sager Date: Mon, 1 Apr 2024 11:20:44 -0700 Subject: [PATCH 21/33] include webview's npm install/ci into extension justfile --- extensions/vscode/justfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extensions/vscode/justfile b/extensions/vscode/justfile index 833144b34..3f61ac8e0 100644 --- a/extensions/vscode/justfile +++ b/extensions/vscode/justfile @@ -67,8 +67,10 @@ deps: if [ {{ _ci }} = "true" ]; then npm ci --no-audit --no-fund | sed 's/^/debug: /' + npm --prefix ./webviews/homeView ci --no-audit --no-fund | sed 's/^/debug: /' else npm install --no-audit --no-fund | sed 's/^/debug: /' + npm --prefix ./webviews/homeView install --no-audit --no-fund | sed 's/^/debug: /' fi just ./webviews/homeView/deps From 3582190fa163f1a8ec957d47d6b2d403f5a7b11c Mon Sep 17 00:00:00 2001 From: Bill Sager Date: Tue, 2 Apr 2024 08:33:58 -0700 Subject: [PATCH 22/33] change for error attribute --- .../vscode/webviews/homeView/src/App.vue | 23 ++++--------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/extensions/vscode/webviews/homeView/src/App.vue b/extensions/vscode/webviews/homeView/src/App.vue index c8ae878e7..78ff975fb 100644 --- a/extensions/vscode/webviews/homeView/src/App.vue +++ b/extensions/vscode/webviews/homeView/src/App.vue @@ -117,19 +117,7 @@ {{ formatDateString(selectedDeployment.deployedAt) }}
- - - Error: {{ selectedDeployment.error.msg }} - -
-
@@ -217,15 +205,12 @@ const lastStatusDescription = computed(() => { if (!selectedDeployment.value) { return undefined; } - if (isPreDeployment(selectedDeployment.value)) { - if (selectedDeployment.value.error) { - return "Last Deployment Failed"; - } - return "Not Yet Deployed"; - } if (selectedDeployment.value.deploymentError) { return "Last Deployment Failed"; } + if (isPreDeployment(selectedDeployment.value)) { + return "Not Yet Deployed"; + } return "Last Deployment Successful"; }); From 2cb56810d146191fb67f46d004f520566af3df51 Mon Sep 17 00:00:00 2001 From: Bill Sager Date: Tue, 2 Apr 2024 09:46:40 -0700 Subject: [PATCH 23/33] PR requests plus filter credential to deployment url --- extensions/vscode/package-lock.json | 16 ---------- extensions/vscode/package.json | 7 ++--- .../createNewDeploymentFile.ts | 5 +-- extensions/vscode/src/views/configurations.ts | 4 +-- extensions/vscode/src/views/deployments.ts | 8 ++--- extensions/vscode/src/views/homeView.ts | 6 ++-- .../vscode/webviews/homeView/src/App.vue | 31 ++++++++++++++----- 7 files changed, 34 insertions(+), 43 deletions(-) diff --git a/extensions/vscode/package-lock.json b/extensions/vscode/package-lock.json index a3eb91f61..cbefa9e95 100644 --- a/extensions/vscode/package-lock.json +++ b/extensions/vscode/package-lock.json @@ -29,7 +29,6 @@ "eslint-config-prettier": "^9.1.0", "glob": "^10.3.3", "mocha": "^10.2.0", - "prettier": "3.2.5", "typescript": "^5.2.2" }, "engines": { @@ -2135,21 +2134,6 @@ "node": ">= 0.8.0" } }, - "node_modules/prettier": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", - "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", - "dev": true, - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", diff --git a/extensions/vscode/package.json b/extensions/vscode/package.json index 64e6730ad..e299de68b 100644 --- a/extensions/vscode/package.json +++ b/extensions/vscode/package.json @@ -148,7 +148,7 @@ }, { "command": "posit.publisher.homeView.refresh", - "title": "Refresh View", + "title": "Refresh Home View", "icon": "$(refresh)", "category": "Posit Publisher" }, @@ -170,13 +170,13 @@ }, { "command": "posit.publisher.homeView.showBasicMode", - "title": "Basic Mode", + "title": "Show Basic Mode", "icon": "$(target)", "category": "Posit Publisher" }, { "command": "posit.publisher.homeView.showAdvancedMode", - "title": "Advanced Mode", + "title": "Show Advanced Mode", "icon": "$(list-tree)", "category": "Posit Publisher" } @@ -522,7 +522,6 @@ "eslint-config-prettier": "^9.1.0", "glob": "^10.3.3", "mocha": "^10.2.0", - "prettier": "3.2.5", "typescript": "^5.2.2" }, "dependencies": { diff --git a/extensions/vscode/src/multiStepInputs/createNewDeploymentFile.ts b/extensions/vscode/src/multiStepInputs/createNewDeploymentFile.ts index 31824e382..e98b7d40f 100644 --- a/extensions/vscode/src/multiStepInputs/createNewDeploymentFile.ts +++ b/extensions/vscode/src/multiStepInputs/createNewDeploymentFile.ts @@ -90,8 +90,6 @@ export async function createNewDeploymentFile() { // to be returned when it has not been cancelled to assist type guards deploymentName: undefined, // eventual type is string credentialName: undefined, // eventual type is QuickPickItem - promptToDeploy: undefined, /// eventual type is QuickPickItem - configFile: undefined, // eventual type is QuickPickItem }, }; // determin number of total steps, as each step @@ -103,9 +101,8 @@ export async function createNewDeploymentFile() { state.totalSteps = totalSteps; // start the progression through the steps - await MultiStepInput.run((input) => inputDeploymentName(input, state)); - return state as MultiStepState; + return state; } // *************************************************************** diff --git a/extensions/vscode/src/views/configurations.ts b/extensions/vscode/src/views/configurations.ts index 899a4b6c9..94a8d1ab5 100644 --- a/extensions/vscode/src/views/configurations.ts +++ b/extensions/vscode/src/views/configurations.ts @@ -106,9 +106,7 @@ export class ConfigurationsTreeDataProvider context.subscriptions.push( treeView, commands.registerCommand(refreshCommand, this.refresh), - commands.registerCommand(addCommand, async () => { - return await this.add(); - }), + commands.registerCommand(addCommand, this.add), commands.registerCommand(editCommand, this.edit), commands.registerCommand(renameCommand, this.rename), commands.registerCommand(cloneCommand, this.clone), diff --git a/extensions/vscode/src/views/deployments.ts b/extensions/vscode/src/views/deployments.ts index eae2f752e..1d02711af 100644 --- a/extensions/vscode/src/views/deployments.ts +++ b/extensions/vscode/src/views/deployments.ts @@ -129,15 +129,11 @@ export class DeploymentsTreeDataProvider context.subscriptions.push(treeView); context.subscriptions.push( - commands.registerCommand(addCommand, () => { - addDeployment(this.stream); - }), + commands.registerCommand(addCommand, () => addDeployment(this.stream)), ); context.subscriptions.push( - commands.registerCommand(createNewCommand, async () => { - return await createNewDeploymentFile(); - }), + commands.registerCommand(createNewCommand, createNewDeploymentFile), ); context.subscriptions.push( diff --git a/extensions/vscode/src/views/homeView.ts b/extensions/vscode/src/views/homeView.ts index 880dbe3bb..a5a702289 100644 --- a/extensions/vscode/src/views/homeView.ts +++ b/extensions/vscode/src/views/homeView.ts @@ -26,6 +26,7 @@ import { Configuration, Deployment, PreDeployment, + Account, } from "../api"; import { getSummaryStringFromError } from "../utils/errors"; import { deployProject } from "./deployProgress"; @@ -48,7 +49,7 @@ export class HomeViewProvider implements WebviewViewProvider { private _disposables: Disposable[] = []; private api = useApi(); private _deployments: (Deployment | PreDeployment)[] = []; - private _credentials: string[] = []; + private _credentials: Account[] = []; private _configs: Configuration[] = []; private root: WorkspaceFolder | undefined; private _webviewView?: WebviewView; @@ -236,8 +237,7 @@ export class HomeViewProvider implements WebviewViewProvider { try { const response = await this.api.accounts.getAll(); - this._credentials = []; - response.data.forEach((account) => this._credentials.push(account.name)); + this._credentials = response.data; } catch (error: unknown) { const summary = getSummaryStringFromError( "_refreshData::accounts.getAll", diff --git a/extensions/vscode/webviews/homeView/src/App.vue b/extensions/vscode/webviews/homeView/src/App.vue index 78ff975fb..6015ced4f 100644 --- a/extensions/vscode/webviews/homeView/src/App.vue +++ b/extensions/vscode/webviews/homeView/src/App.vue @@ -166,6 +166,7 @@ import { isDeployment, } from "../../../src/api/types/deployments"; import { formatDateString } from "../../../../../web/src/utils/date"; +import { Account } from "../../../src/api/types/accounts"; // In order to use the Webview UI Toolkit web components they // must be registered with the browser (i.e. webview) using the @@ -185,6 +186,7 @@ declare var window: any; let deployments = ref<(Deployment | PreDeployment)[]>([]); let deploymentList = ref([]); let configList = ref([]); +let accounts = ref([]); let credentialList = ref([]); let publishingInProgress = ref(false); @@ -240,6 +242,7 @@ const disableDeployment = computed(() => { watch(selectedDeploymentName, () => { updateSelectedDevelopment(); + filterCredentialsToDeployment(); }); onBeforeMount(() => { @@ -263,6 +266,25 @@ const updateSelectedDevelopment = () => { ); }; +const filterCredentialsToDeployment = () => { + credentialList.value = accounts.value + .filter((account) => { + return ( + account.url.toLowerCase() === + selectedDeployment.value?.serverUrl.toLocaleLowerCase() + ); + }) + .map((account) => account.name); + + if (credentialList.value.length === 0) { + selectedCredential.value = ""; + } else if (!selectedCredential.value) { + selectedCredential.value = credentialList.value[0]; + } else if (!credentialList.value.includes(selectedCredential.value)) { + selectedCredential.value = credentialList.value[0]; + } +}; + const onClickDeploy = () => { vsCodeApi.postMessage({ command: "deploy", @@ -339,13 +361,8 @@ const onMessageFromProvider = (event: any) => { selectedConfig.value = ""; } - credentialList.value = payload.credentials; - if (!selectedCredential.value) { - selectedCredential.value = credentialList.value[0]; - } - if (credentialList.value.length === 0) { - selectedCredential.value = ""; - } + accounts.value = payload.credentials; + filterCredentialsToDeployment(); break; } case "publish_start": { From 141f00d19896ca095484911d43ce8b65f650fb03 Mon Sep 17 00:00:00 2001 From: Bill Sager Date: Tue, 2 Apr 2024 12:35:39 -0700 Subject: [PATCH 24/33] Cleanup of old commands --- extensions/vscode/package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/extensions/vscode/package.json b/extensions/vscode/package.json index e299de68b..4dc8cc453 100644 --- a/extensions/vscode/package.json +++ b/extensions/vscode/package.json @@ -503,8 +503,6 @@ "pretest": "npm run compile", "lint": "eslint src --ext ts", "compile-deploy-selector": "npm run --prefix webviews/homeView compile", - "format": "prettier . --write", - "check-format": "prettier . --check", "test": "node ./out/test/runTest.js" }, "devDependencies": { From cde368fbff8686de61edb6f6943e9552ba74860f Mon Sep 17 00:00:00 2001 From: Bill Sager Date: Tue, 2 Apr 2024 13:06:42 -0700 Subject: [PATCH 25/33] fix forget deployment interaction --- extensions/vscode/webviews/homeView/src/App.vue | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/extensions/vscode/webviews/homeView/src/App.vue b/extensions/vscode/webviews/homeView/src/App.vue index 6015ced4f..c753287e9 100644 --- a/extensions/vscode/webviews/homeView/src/App.vue +++ b/extensions/vscode/webviews/homeView/src/App.vue @@ -266,6 +266,11 @@ const updateSelectedDevelopment = () => { ); }; +// TODO: We need to show an error when you have no credentials which can get to +// the deployment URL +// OR +// Should we filter deployment list to just include what you can access. Maybe disable others? + const filterCredentialsToDeployment = () => { credentialList.value = accounts.value .filter((account) => { @@ -277,6 +282,7 @@ const filterCredentialsToDeployment = () => { .map((account) => account.name); if (credentialList.value.length === 0) { + // TODO: Show ERROR HERE!!!! selectedCredential.value = ""; } else if (!selectedCredential.value) { selectedCredential.value = credentialList.value[0]; @@ -345,12 +351,15 @@ const onMessageFromProvider = (event: any) => { deploymentList.value = deployments.value.map( (deployment) => deployment.saveName, ); + if (selectedDeploymentName.value) { + if (!deploymentList.value.includes(selectedDeploymentName.value)) { + selectedDeploymentName.value = ""; + } + } + if (!selectedDeploymentName.value) { selectedDeploymentName.value = deploymentList.value[0]; } - if (deploymentList.value.length === 0) { - selectedDeploymentName.value = ""; - } updateSelectedDevelopment(); configList.value = payload.configurations; @@ -363,6 +372,7 @@ const onMessageFromProvider = (event: any) => { accounts.value = payload.credentials; filterCredentialsToDeployment(); + break; } case "publish_start": { From a35f5915a7e7942e5a79c7f69a1cd3516dca333e Mon Sep 17 00:00:00 2001 From: Jordan Jensen Date: Tue, 2 Apr 2024 14:14:31 -0700 Subject: [PATCH 26/33] Exclude .d.ts files from ccheck --- scripts/ccheck.config | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/ccheck.config b/scripts/ccheck.config index 97ac35ff4..ca7a42c1d 100644 --- a/scripts/ccheck.config +++ b/scripts/ccheck.config @@ -16,5 +16,6 @@ web/src/**/*.vue extensions/vscode/src/**/*.ts extensions/vscode/src/**/*.js extensions/vscode/webviews/homeView/src/*.ts +-extensions/vscode/webviews/homeView/src/*.d.ts extensions/vscode/webviews/homeView/src/*.js extensions/vscode/webviews/homeView/src/*.vue From fd4da15fa91f3f57c11d22b4259a16a04f84fafe Mon Sep 17 00:00:00 2001 From: Jordan Jensen Date: Tue, 2 Apr 2024 14:54:27 -0700 Subject: [PATCH 27/33] Simplify CSS of home view --- .../vscode/webviews/homeView/src/App.vue | 57 +++---------------- 1 file changed, 7 insertions(+), 50 deletions(-) diff --git a/extensions/vscode/webviews/homeView/src/App.vue b/extensions/vscode/webviews/homeView/src/App.vue index c753287e9..a88ede12f 100644 --- a/extensions/vscode/webviews/homeView/src/App.vue +++ b/extensions/vscode/webviews/homeView/src/App.vue @@ -28,13 +28,12 @@
-
-
+
+
@@ -55,20 +54,18 @@
-
+
@@ -97,11 +94,8 @@ }}
-
- +
+
Deployment in Progress... @@ -120,7 +114,7 @@ v-if="selectedDeployment.deploymentError" class="last-deployment-details last-deployment-error" > - + Error: {{ selectedDeployment.deploymentError.msg }} @@ -424,42 +418,23 @@ const onMessageFromProvider = (event: any) => {