diff --git a/extensions/vscode/.eslintrc.json b/extensions/vscode/.eslintrc.json index 0f3821a73..2bab21b5c 100644 --- a/extensions/vscode/.eslintrc.json +++ b/extensions/vscode/.eslintrc.json @@ -5,12 +5,8 @@ "ecmaVersion": 6, "sourceType": "module" }, - "plugins": [ - "@typescript-eslint" - ], - "extends": [ - "prettier" - ], + "plugins": ["@typescript-eslint"], + "extends": ["prettier"], "rules": { "@typescript-eslint/naming-convention": "warn", "@typescript-eslint/semi": "warn", @@ -19,10 +15,5 @@ "no-throw-literal": "warn", "semi": "off" }, - "ignorePatterns": [ - "out", - "dist", - "**/*.d.ts", - "api" - ] + "ignorePatterns": ["out", "dist", "**/*.d.ts", "api"] } diff --git a/extensions/vscode/.vscode/extensions.json b/extensions/vscode/.vscode/extensions.json index 3ac9aeb61..c0a2258b0 100644 --- a/extensions/vscode/.vscode/extensions.json +++ b/extensions/vscode/.vscode/extensions.json @@ -1,7 +1,5 @@ { - // See http://go.microsoft.com/fwlink/?LinkId=827846 - // for the documentation about the extensions.json format - "recommendations": [ - "dbaeumer.vscode-eslint" - ] + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": ["dbaeumer.vscode-eslint"] } diff --git a/extensions/vscode/.vscode/launch.json b/extensions/vscode/.vscode/launch.json index 670d6e66c..1210201da 100644 --- a/extensions/vscode/.vscode/launch.json +++ b/extensions/vscode/.vscode/launch.json @@ -3,32 +3,26 @@ // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 { - "version": "0.2.0", - "configurations": [ - { - "name": "Run Extension", - "type": "extensionHost", - "request": "launch", - "args": [ - "--extensionDevelopmentPath=${workspaceFolder}" - ], - "outFiles": [ - "${workspaceFolder}/out/**/*.js" - ], - "preLaunchTask": "${defaultBuildTask}" - }, - { - "name": "Extension Tests", - "type": "extensionHost", - "request": "launch", - "args": [ - "--extensionDevelopmentPath=${workspaceFolder}", - "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" - ], - "outFiles": [ - "${workspaceFolder}/out/test/**/*.js" - ], - "preLaunchTask": "${defaultBuildTask}" - } - ] + "version": "0.2.0", + "configurations": [ + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "args": ["--extensionDevelopmentPath=${workspaceFolder}"], + "outFiles": ["${workspaceFolder}/out/**/*.js"], + "preLaunchTask": "${defaultBuildTask}" + }, + { + "name": "Extension Tests", + "type": "extensionHost", + "request": "launch", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}", + "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" + ], + "outFiles": ["${workspaceFolder}/out/test/**/*.js"], + "preLaunchTask": "${defaultBuildTask}" + } + ] } diff --git a/extensions/vscode/.vscode/settings.json b/extensions/vscode/.vscode/settings.json index cea5e4076..b6a38b769 100644 --- a/extensions/vscode/.vscode/settings.json +++ b/extensions/vscode/.vscode/settings.json @@ -8,11 +8,9 @@ }, // Turn off tsc task auto detection since we have the necessary tasks as npm scripts "typescript.tsc.autoDetect": "off", - "cSpell.words": [ - "mutexify" - ], + "cSpell.words": ["mutexify"], "svg.preview.background": "editor", "editor.tabSize": 2, "editor.formatOnSave": true, "editor.detectIndentation": false -} \ No newline at end of file +} diff --git a/extensions/vscode/.vscode/tasks.json b/extensions/vscode/.vscode/tasks.json index 3b17e53b6..078ff7e01 100644 --- a/extensions/vscode/.vscode/tasks.json +++ b/extensions/vscode/.vscode/tasks.json @@ -1,20 +1,20 @@ // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format { - "version": "2.0.0", - "tasks": [ - { - "type": "npm", - "script": "watch", - "problemMatcher": "$tsc-watch", - "isBackground": true, - "presentation": { - "reveal": "never" - }, - "group": { - "kind": "build", - "isDefault": true - } - } - ] + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "watch", + "problemMatcher": "$tsc-watch", + "isBackground": true, + "presentation": { + "reveal": "never" + }, + "group": { + "kind": "build", + "isDefault": true + } + } + ] } diff --git a/extensions/vscode/README.md b/extensions/vscode/README.md index f89a56970..ca79da4f9 100644 --- a/extensions/vscode/README.md +++ b/extensions/vscode/README.md @@ -5,11 +5,11 @@ Install the extension. [https://cdn.posit.co/publisher/releases/tags/v0.0.alpha2/publisher-0.0.alpha2.vsix](https://cdn.posit.co/publisher/releases/tags/v0.0.alpha2/publisher-0.0.alpha2.vsix). -To learn how to install a `.vsix` file, see the [*Install from a VSIX*](https://code.visualstudio.com/docs/editor/extension-marketplace#_install-from-a-vsix) guide from Visual Studio Code. +To learn how to install a `.vsix` file, see the [_Install from a VSIX_](https://code.visualstudio.com/docs/editor/extension-marketplace#_install-from-a-vsix) guide from Visual Studio Code. ## Tutorial -Once installed, open *Posit Publisher* by clicking the icon in the editor menu bar. +Once installed, open _Posit Publisher_ by clicking the icon in the editor menu bar. ![](https://cdn.posit.co/publisher/assets/img/tutorial.png) @@ -17,18 +17,18 @@ Once installed, open *Posit Publisher* by clicking the icon in the editor menu b ### Posit: Open Publisher -This command opens *Posit Publisher* in a new window or focuses on an existing window. +This command opens _Posit Publisher_ in a new window or focuses on an existing window. -This command invokes the same action as clicking the *Posit Publisher* icon. +This command invokes the same action as clicking the _Posit Publisher_ icon. ### Posit: Close Publisher -This command closes *Posit Publisher* and shuts down the server. +This command closes _Posit Publisher_ and shuts down the server. ## Configuration ### `posit.publisher.executable.path` -By default, the extension uses the bundled Posit Publisher binary executable. To override this behavior, configure the `posit.publisher.executable.path` property in your *User* or *Workspace* settings. +By default, the extension uses the bundled Posit Publisher binary executable. To override this behavior, configure the `posit.publisher.executable.path` property in your _User_ or _Workspace_ settings. ![](https://cdn.posit.co/publisher/assets/img/settings.png) diff --git a/extensions/vscode/src/api/README.md b/extensions/vscode/src/api/README.md index 5ec435d67..59f24b1e2 100644 --- a/extensions/vscode/src/api/README.md +++ b/extensions/vscode/src/api/README.md @@ -1,4 +1,4 @@ -# API Wrapper Library +# API Wrapper Library A library to enable convenient access to the Publisher Client API and its response types. @@ -6,7 +6,7 @@ response types. ## Usage ```typescript -import { useApi } from 'src/api'; +import { useApi } from "src/api"; const api = useApi(); @@ -22,7 +22,7 @@ using `setBaseUrl`. This changes the client so all requests will use the new base URL. This will need to be done before any requests are made. ```typescript -api.setBaseUrl('http://localhost:9000/api'); +api.setBaseUrl("http://localhost:9000/api"); ``` ## Organization @@ -73,4 +73,4 @@ try { } catch (err) { // handle the error } -``` \ No newline at end of file +``` diff --git a/extensions/vscode/src/api/axios.d.ts b/extensions/vscode/src/api/axios.d.ts index 2ce5f7074..e6dec3501 100644 --- a/extensions/vscode/src/api/axios.d.ts +++ b/extensions/vscode/src/api/axios.d.ts @@ -1,10 +1,10 @@ // Copyright (C) 2023 by Posit Software, PBC. // eslint-disable-next-line @typescript-eslint/no-unused-vars -import { AxiosRequestConfig } from 'axios'; +import { AxiosRequestConfig } from "axios"; -declare module 'axios' { +declare module "axios" { interface AxiosRequestConfig { - ignoreCamelCase?: string[] + ignoreCamelCase?: string[]; } } diff --git a/extensions/vscode/src/api/client.ts b/extensions/vscode/src/api/client.ts index af6b227d1..c9eadcdbe 100644 --- a/extensions/vscode/src/api/client.ts +++ b/extensions/vscode/src/api/client.ts @@ -1,12 +1,12 @@ // Copyright (C) 2023 by Posit Software, PBC. -import axios from 'axios'; +import axios from "axios"; -import { Accounts } from './resources/Accounts'; -import { Deployments } from './resources/Deployments'; -import { Configurations } from './resources/Configurations'; -import { Files } from './resources/Files'; -import { Requirements } from './resources/Requirements'; +import { Accounts } from "./resources/Accounts"; +import { Deployments } from "./resources/Deployments"; +import { Configurations } from "./resources/Configurations"; +import { Files } from "./resources/Files"; +import { Requirements } from "./resources/Requirements"; class PublishingClientApi { private client; @@ -19,7 +19,7 @@ class PublishingClientApi { constructor() { this.client = axios.create({ - baseURL: '/api', + baseURL: "/api", }); this.accounts = new Accounts(this.client); @@ -36,4 +36,4 @@ class PublishingClientApi { export const api = new PublishingClientApi(); -export const useApi = () => api; \ No newline at end of file +export const useApi = () => api; diff --git a/extensions/vscode/src/api/index.ts b/extensions/vscode/src/api/index.ts index 8f687a1ef..a57459549 100644 --- a/extensions/vscode/src/api/index.ts +++ b/extensions/vscode/src/api/index.ts @@ -1,10 +1,10 @@ // Copyright (C) 2023 by Posit Software, PBC. -export { api as default, useApi } from './client'; +export { api as default, useApi } from "./client"; -export * from './types/accounts'; -export * from './types/configurations'; -export * from './types/connect'; -export * from './types/deployments'; -export * from './types/events'; -export * from './types/schema'; +export * from "./types/accounts"; +export * from "./types/configurations"; +export * from "./types/connect"; +export * from "./types/deployments"; +export * from "./types/events"; +export * from "./types/schema"; diff --git a/extensions/vscode/src/api/resources/Accounts.ts b/extensions/vscode/src/api/resources/Accounts.ts index c99af0799..e9473566a 100644 --- a/extensions/vscode/src/api/resources/Accounts.ts +++ b/extensions/vscode/src/api/resources/Accounts.ts @@ -1,8 +1,8 @@ // Copyright (C) 2023 by Posit Software, PBC. -import { AxiosInstance } from 'axios'; +import { AxiosInstance } from "axios"; -import { Account } from '../types/accounts'; +import { Account } from "../types/accounts"; export class Accounts { private client: AxiosInstance; @@ -15,9 +15,7 @@ export class Accounts { // 200 - success // 500 - internal server error getAll() { - return this.client.get<{ accounts: Account[] }>( - '/accounts', - ); + return this.client.get<{ accounts: Account[] }>("/accounts"); } // Returns: @@ -26,8 +24,6 @@ export class Accounts { // 500 - internal server error get(accountName: string) { const encodedAccountName = encodeURIComponent(accountName); - return this.client.get( - `/accounts/${encodedAccountName}`, - ); + return this.client.get(`/accounts/${encodedAccountName}`); } } diff --git a/extensions/vscode/src/api/resources/Configurations.ts b/extensions/vscode/src/api/resources/Configurations.ts index 1c96fa25c..254eba2db 100644 --- a/extensions/vscode/src/api/resources/Configurations.ts +++ b/extensions/vscode/src/api/resources/Configurations.ts @@ -1,12 +1,12 @@ // Copyright (C) 2023 by Posit Software, PBC. -import { AxiosInstance } from 'axios'; +import { AxiosInstance } from "axios"; import { Configuration, ConfigurationDetails, ConfigurationError, -} from '../types/configurations'; +} from "../types/configurations"; export class Configurations { private client: AxiosInstance; @@ -20,7 +20,7 @@ export class Configurations { // 500 - internal server error getAll() { return this.client.get>( - '/configurations', + "/configurations", ); } @@ -48,6 +48,6 @@ export class Configurations { // 400 - bad request // 500 - internal server error inspect() { - return this.client.post('/inspect'); + return this.client.post("/inspect"); } } diff --git a/extensions/vscode/src/api/resources/Deployments.ts b/extensions/vscode/src/api/resources/Deployments.ts index 6790031cb..cb44c7e42 100644 --- a/extensions/vscode/src/api/resources/Deployments.ts +++ b/extensions/vscode/src/api/resources/Deployments.ts @@ -1,8 +1,8 @@ // Copyright (C) 2023 by Posit Software, PBC. -import { AxiosInstance } from 'axios'; +import { AxiosInstance } from "axios"; -import { PreDeployment, AllDeploymentTypes } from '../types/deployments'; +import { PreDeployment, AllDeploymentTypes } from "../types/deployments"; export class Deployments { private client: AxiosInstance; @@ -15,9 +15,7 @@ export class Deployments { // 200 - success // 500 - internal server error getAll() { - return this.client.get>( - '/deployments', - ); + return this.client.get>("/deployments"); } // Returns: @@ -26,9 +24,7 @@ export class Deployments { // 500 - internal server error get(id: string) { const encodedId = encodeURIComponent(id); - return this.client.get( - `deployments/${encodedId}`, - ); + return this.client.get(`deployments/${encodedId}`); } // Returns: @@ -42,10 +38,7 @@ export class Deployments { account: accountName, saveName, }; - return this.client.post( - '/deployments', - params, - ); + return this.client.post("/deployments", params); } // Returns: @@ -53,7 +46,11 @@ export class Deployments { // 400 - bad request // 500 - internal server error // Errors returned through event stream - publish(targetName: string, accountName: string, configName: string = 'default') { + publish( + targetName: string, + accountName: string, + configName: string = "default", + ) { const params = { account: accountName, config: configName, @@ -71,8 +68,6 @@ export class Deployments { // 500 - internal server error delete(saveName: string) { const encodedSaveName = encodeURIComponent(saveName); - return this.client.delete( - `deployments/${encodedSaveName}` - ); + return this.client.delete(`deployments/${encodedSaveName}`); } } diff --git a/extensions/vscode/src/api/resources/Files.ts b/extensions/vscode/src/api/resources/Files.ts index 6a6b872be..380b05c64 100644 --- a/extensions/vscode/src/api/resources/Files.ts +++ b/extensions/vscode/src/api/resources/Files.ts @@ -1,8 +1,8 @@ // Copyright (C) 2023 by Posit Software, PBC. -import { AxiosInstance } from 'axios'; +import { AxiosInstance } from "axios"; -import { DeploymentFile } from '../types/files'; +import { DeploymentFile } from "../types/files"; export class Files { private client: AxiosInstance; @@ -16,8 +16,6 @@ export class Files { // 403 - pathname is not safe - forbidden // 500 - internal server error get() { - return this.client.get( - '/files', - ); + return this.client.get("/files"); } } diff --git a/extensions/vscode/src/api/resources/Requirements.ts b/extensions/vscode/src/api/resources/Requirements.ts index de3f30f86..031956577 100644 --- a/extensions/vscode/src/api/resources/Requirements.ts +++ b/extensions/vscode/src/api/resources/Requirements.ts @@ -1,7 +1,7 @@ // Copyright (C) 2023 by Posit Software, PBC. -import { AxiosInstance } from 'axios'; -import { RequirementsResponse } from '../types/requirements'; +import { AxiosInstance } from "axios"; +import { RequirementsResponse } from "../types/requirements"; export class Requirements { private client: AxiosInstance; @@ -15,9 +15,7 @@ export class Requirements { // 404 - no requirements file found // 500 - internal server error getAll() { - return this.client.get( - 'requirements', - ); + return this.client.get("requirements"); } // Returns: @@ -25,9 +23,6 @@ export class Requirements { // 400 - bad request // 500 - internal server error create(saveName: string | undefined) { - return this.client.post( - 'requirements', - { saveName } - ); + return this.client.post("requirements", { saveName }); } } diff --git a/extensions/vscode/src/api/types/accounts.ts b/extensions/vscode/src/api/types/accounts.ts index 2226bc982..86f861076 100644 --- a/extensions/vscode/src/api/types/accounts.ts +++ b/extensions/vscode/src/api/types/accounts.ts @@ -1,22 +1,22 @@ // Copyright (C) 2023 by Posit Software, PBC. export enum ServerType { - CONNECT = 'connect', - SHINY_APPS = 'shinyapps', - CLOUD = 'cloud', + CONNECT = "connect", + SHINY_APPS = "shinyapps", + CLOUD = "cloud", } export enum AccountSource { - RSCONNECT_PYTHON = 'rsconnect-python', - RSCONNECT = 'rsconnect', - ENVIRONMENT = 'environment', + RSCONNECT_PYTHON = "rsconnect-python", + RSCONNECT = "rsconnect", + ENVIRONMENT = "environment", } export enum AccountAuthType { - NONE = 'none', - API_KEY = 'api-key', - TOKEN_KEY = 'token-key', - TOKEN_SECRET = 'token-secret', + NONE = "none", + API_KEY = "api-key", + TOKEN_KEY = "token-key", + TOKEN_SECRET = "token-secret", } export type Account = { @@ -28,4 +28,4 @@ export type Account = { source: AccountSource; type: ServerType; url: string; -} +}; diff --git a/extensions/vscode/src/api/types/configurations.ts b/extensions/vscode/src/api/types/configurations.ts index 0f424268a..1367bd6e6 100644 --- a/extensions/vscode/src/api/types/configurations.ts +++ b/extensions/vscode/src/api/types/configurations.ts @@ -1,112 +1,112 @@ // Copyright (C) 2023 by Posit Software, PBC. -import { AgentError } from 'src/api/types/error'; -import { ConnectConfig } from 'src/api/types/connect'; -import { SchemaURL } from 'src/api/types/schema'; +import { AgentError } from "src/api/types/error"; +import { ConnectConfig } from "src/api/types/connect"; +import { SchemaURL } from "src/api/types/schema"; export type ConfigurationLocation = { configurationName: string; configurationPath: string; -} +}; export type ConfigurationError = { error: AgentError; -} & ConfigurationLocation +} & ConfigurationLocation; export type Configuration = { - configuration: ConfigurationDetails -} & ConfigurationLocation + configuration: ConfigurationDetails; +} & ConfigurationLocation; export function isConfigurationError( - c: Configuration | ConfigurationError + c: Configuration | ConfigurationError, ): c is ConfigurationError { return (c as ConfigurationError).error !== undefined; } export enum ContentType { - HTML = 'html', - JUPYTER_NOTEBOOK = 'jupyter-notebook', - JUPYTER_VOILA = 'jupyter-voila', - PYTHON_BOKEH = 'python-bokeh', - PYTHON_DASH = 'python-dash', - PYTHON_FASTAPI = 'python-fastapi', - PYTHON_FLASK = 'python-flask', - PYTHON_SHINY = 'python-shiny', - PYTHON_STREAMLIT = 'python-streamlit', - QUARTO_SHINY = 'quarto-shiny', - QUARTO = 'quarto', - R_PLUMBER = 'r-plumber', - R_SHINY = 'r-shiny', - RMD_SHINY = 'rmd-shiny', - RMD = 'rmd', - UNKNOWN = 'unknown' + HTML = "html", + JUPYTER_NOTEBOOK = "jupyter-notebook", + JUPYTER_VOILA = "jupyter-voila", + PYTHON_BOKEH = "python-bokeh", + PYTHON_DASH = "python-dash", + PYTHON_FASTAPI = "python-fastapi", + PYTHON_FLASK = "python-flask", + PYTHON_SHINY = "python-shiny", + PYTHON_STREAMLIT = "python-streamlit", + QUARTO_SHINY = "quarto-shiny", + QUARTO = "quarto", + R_PLUMBER = "r-plumber", + R_SHINY = "r-shiny", + RMD_SHINY = "rmd-shiny", + RMD = "rmd", + UNKNOWN = "unknown", } export type ConfigurationDetails = { - $schema: SchemaURL, - type: ContentType, - entrypoint?: string, - title?: string, - description?: string, - thumbnail?: string, - tags?: string[], - python?: PythonConfig, - r?: RConfig, - quarto?: QuartoConfig, - environment?: EnvironmentConfig, - validate: boolean, - secrets?: string[], - schedules?: ScheduleConfig[], - access?: AccessConfig, - connect?: ConnectConfig, -} + $schema: SchemaURL; + type: ContentType; + entrypoint?: string; + title?: string; + description?: string; + thumbnail?: string; + tags?: string[]; + python?: PythonConfig; + r?: RConfig; + quarto?: QuartoConfig; + environment?: EnvironmentConfig; + validate: boolean; + secrets?: string[]; + schedules?: ScheduleConfig[]; + access?: AccessConfig; + connect?: ConnectConfig; +}; export type PythonConfig = { - version: string, - packageFile: string, - packageManager: string -} + version: string; + packageFile: string; + packageManager: string; +}; export type RConfig = { - version: string, - packageFile: string, - packageManager: string -} + version: string; + packageFile: string; + packageManager: string; +}; export type QuartoConfig = { - version: string, - engines?: string[] -} + version: string; + engines?: string[]; +}; -export type EnvironmentConfig = Record +export type EnvironmentConfig = Record; export type ScheduleConfig = { - start: string, - recurrence: string -} + start: string; + recurrence: string; +}; export enum AccessType { - ANONYMOUS = 'all', - LOGGED_IN = 'logged-in', - ACL = 'acl' + ANONYMOUS = "all", + LOGGED_IN = "logged-in", + ACL = "acl", } export type AccessConfig = { - type: AccessType, - users?: User[], - groups?: Group[] -} + type: AccessType; + users?: User[]; + groups?: Group[]; +}; export type User = { - id?: string, - guid?: string, - name?: string, - permissions: string -} + id?: string; + guid?: string; + name?: string; + permissions: string; +}; export type Group = { - id?: string, - guid?: string, - name?: string, - permissions: string -} + id?: string; + guid?: string; + name?: string; + permissions: string; +}; diff --git a/extensions/vscode/src/api/types/connect.ts b/extensions/vscode/src/api/types/connect.ts index 4691f3f9a..1c53bf2ef 100644 --- a/extensions/vscode/src/api/types/connect.ts +++ b/extensions/vscode/src/api/types/connect.ts @@ -1,34 +1,34 @@ // Copyright (C) 2023 by Posit Software, PBC. export type ConnectConfig = { - access?: ConnectAccess, - runtime?: ConnectRuntime, - kubernetes?: ConnectKubernetes -} + access?: ConnectAccess; + runtime?: ConnectRuntime; + kubernetes?: ConnectKubernetes; +}; export type ConnectAccess = { - runAs?: string, - runAsCurrentUser?: boolean, -} + runAs?: string; + runAsCurrentUser?: boolean; +}; export type ConnectRuntime = { - connectionTimeout?: number, - readTimeout?: number, - initTimeout?: number, - idleTimeout?: number, - maxProcesses?: number, - minProcesses?: number, - maxConnections?: number, - loadFactor?: number, -} + connectionTimeout?: number; + readTimeout?: number; + initTimeout?: number; + idleTimeout?: number; + maxProcesses?: number; + minProcesses?: number; + maxConnections?: number; + loadFactor?: number; +}; export type ConnectKubernetes = { - memoryRequest?: number, - memoryLimit?: number, - cpuRequest?: number, - cpuLimit?: number, - amdGpuLimit?: number, - nvidiaGpuLimit?: number, - serviceAccountName?: string, - imageName?: string, -} + memoryRequest?: number; + memoryLimit?: number; + cpuRequest?: number; + cpuLimit?: number; + amdGpuLimit?: number; + nvidiaGpuLimit?: number; + serviceAccountName?: string; + imageName?: string; +}; diff --git a/extensions/vscode/src/api/types/deployments.ts b/extensions/vscode/src/api/types/deployments.ts index dfb5ecc48..03502488c 100644 --- a/extensions/vscode/src/api/types/deployments.ts +++ b/extensions/vscode/src/api/types/deployments.ts @@ -1,55 +1,56 @@ // Copyright (C) 2023 by Posit Software, PBC. -import { AgentError } from './error'; -import { Configuration } from './configurations'; -import { SchemaURL } from './schema'; -import { ServerType } from './accounts'; +import { AgentError } from "./error"; +import { Configuration } from "./configurations"; +import { SchemaURL } from "./schema"; +import { ServerType } from "./accounts"; export enum DeploymentState { - NEW = 'new', - DEPLOYED = 'deployed', - ERROR = 'error', + NEW = "new", + DEPLOYED = "deployed", + ERROR = "error", } export type DeploymentLocation = { deploymentName: string; deploymentPath: string; -} +}; export type DeploymentError = { - error: AgentError, - state: DeploymentState.ERROR, -} & DeploymentLocation + error: AgentError; + state: DeploymentState.ERROR; +} & DeploymentLocation; type DeploymentRecord = { - $schema: SchemaURL, - serverType: ServerType, - serverUrl: string, - saveName: string, - createdAt: string, + $schema: SchemaURL; + serverType: ServerType; + serverUrl: string; + saveName: string; + createdAt: string; } & DeploymentLocation; export type PreDeployment = { - state: DeploymentState.NEW, - error: AgentError | null, + state: DeploymentState.NEW; + error: AgentError | null; } & DeploymentRecord; export type Deployment = { - id: string, - bundleId: string, - bundleUrl: string, - dashboardUrl: string, - directUrl: string, - files: string[], - deployedAt: string, - state: DeploymentState.DEPLOYED, - deploymentError: AgentError | null, -} & DeploymentRecord & Configuration; - -export type AllDeploymentTypes = Deployment | PreDeployment | DeploymentError + id: string; + bundleId: string; + bundleUrl: string; + dashboardUrl: string; + directUrl: string; + files: string[]; + deployedAt: string; + state: DeploymentState.DEPLOYED; + deploymentError: AgentError | null; +} & DeploymentRecord & + Configuration; + +export type AllDeploymentTypes = Deployment | PreDeployment | DeploymentError; export function isSuccessful( - d: AllDeploymentTypes | undefined + d: AllDeploymentTypes | undefined, ): boolean | undefined { if (d === undefined) { return undefined; @@ -61,7 +62,7 @@ export function isSuccessful( } export function isUnsuccessful( - d: AllDeploymentTypes | undefined + d: AllDeploymentTypes | undefined, ): boolean | undefined { const result = isSuccessful(d); if (result === undefined) { @@ -71,25 +72,19 @@ export function isUnsuccessful( } export function isDeploymentError( - d: AllDeploymentTypes | undefined + d: AllDeploymentTypes | undefined, ): d is DeploymentError { - return Boolean( - d && - d.state === DeploymentState.ERROR - ); + return Boolean(d && d.state === DeploymentState.ERROR); } export function isPreDeployment( - d: AllDeploymentTypes | undefined + d: AllDeploymentTypes | undefined, ): d is PreDeployment { - return Boolean( - d && - d.state === DeploymentState.NEW - ); + return Boolean(d && d.state === DeploymentState.NEW); } export function isSuccessfulPreDeployment( - d: AllDeploymentTypes | undefined + d: AllDeploymentTypes | undefined, ): d is PreDeployment { if (isPreDeployment(d)) { const success = isSuccessful(d); @@ -101,7 +96,7 @@ export function isSuccessfulPreDeployment( } export function isUnsuccessfulPreDeployment( - d: AllDeploymentTypes | undefined + d: AllDeploymentTypes | undefined, ): d is PreDeployment { if (isPreDeployment(d)) { const failure = isUnsuccessful(d); @@ -113,16 +108,13 @@ export function isUnsuccessfulPreDeployment( } export function isDeployment( - d: AllDeploymentTypes | undefined + d: AllDeploymentTypes | undefined, ): d is Deployment { - return Boolean( - d && - d.state === DeploymentState.DEPLOYED - ); + return Boolean(d && d.state === DeploymentState.DEPLOYED); } export function isSuccessfulDeployment( - d: AllDeploymentTypes | undefined + d: AllDeploymentTypes | undefined, ): d is Deployment { if (isDeployment(d)) { const success = isSuccessful(d); @@ -134,7 +126,7 @@ export function isSuccessfulDeployment( } export function isUnsuccessfulDeployment( - d: AllDeploymentTypes | undefined + d: AllDeploymentTypes | undefined, ): d is Deployment { if (isDeployment(d)) { const failure = isUnsuccessful(d); diff --git a/extensions/vscode/src/api/types/error.ts b/extensions/vscode/src/api/types/error.ts index 9f65df649..8a4ca2d3d 100644 --- a/extensions/vscode/src/api/types/error.ts +++ b/extensions/vscode/src/api/types/error.ts @@ -5,6 +5,6 @@ export type AgentError = { msg: string; operation: string; data: { - [key: string]: unknown + [key: string]: unknown; }; -} +}; diff --git a/extensions/vscode/src/api/types/events.ts b/extensions/vscode/src/api/types/events.ts index 2c7d138b8..a52a0383f 100644 --- a/extensions/vscode/src/api/types/events.ts +++ b/extensions/vscode/src/api/types/events.ts @@ -7,28 +7,28 @@ export enum EventSourceReadyState { } export enum EventStreamMessageType { - ERROR = 'error', - LOG = 'log', + ERROR = "error", + LOG = "log", } export type MethodResult = { - ok: boolean, - error?: string, -} + ok: boolean; + error?: string; +}; export type EventStatus = { - isOpen?: boolean, - eventSource: string, - withCredentials?: boolean, - readyState?: EventSourceReadyState, - url: string | null, - lastError: string | null, -} + isOpen?: boolean; + eventSource: string; + withCredentials?: boolean; + readyState?: EventSourceReadyState; + url: string | null; + lastError: string | null; +}; export type CallbackQueueEntry = { - eventType: EventSubscriptionTarget, - callback: OnMessageEventSourceCallback, -} + eventType: EventSubscriptionTarget; + callback: OnMessageEventSourceCallback; +}; export type EventSubscriptionTarget = keyof EventSubscriptionTargetCallbackMap; @@ -37,109 +37,109 @@ export type EventSubscriptionTarget = keyof EventSubscriptionTargetCallbackMap; */ export interface EventSubscriptionTargetCallbackMap { // all events - '*': OnMessageEventSourceCallback + "*": OnMessageEventSourceCallback; // agent console log messages - 'agent/log': OnAgentLogCallback + "agent/log": OnAgentLogCallback; // all errors - 'errors/*': OnMessageEventSourceCallback - 'errors/sse': OnErrorsSseCallback - 'errors/open': OnErrorsOpenCallback - 'errors/unknownEvent': OnErrorsUnknownEventCallback + "errors/*": OnMessageEventSourceCallback; + "errors/sse": OnErrorsSseCallback; + "errors/open": OnErrorsOpenCallback; + "errors/unknownEvent": OnErrorsUnknownEventCallback; // open events - 'open/*': OnMessageEventSourceCallback - 'open/sse': OnOpenSseCallback + "open/*": OnMessageEventSourceCallback; + "open/sse": OnOpenSseCallback; // publish events - 'publish/*': OnMessageEventSourceCallback - 'publish/**/log': OnMessageEventSourceCallback - 'publish/**/failure': OnMessageEventSourceCallback - 'publish/start': OnPublishStartCallback + "publish/*": OnMessageEventSourceCallback; + "publish/**/log": OnMessageEventSourceCallback; + "publish/**/failure": OnMessageEventSourceCallback; + "publish/start": OnPublishStartCallback; - 'publish/checkCapabilities/start': OnPublishCheckCapabilitiesStartCallback - 'publish/checkCapabilities/log': OnPublishCheckCapabilitiesLogCallback - 'publish/checkCapabilities/success': OnPublishCheckCapabilitiesSuccessCallback - 'publish/checkCapabilities/failure': OnPublishCheckCapabilitiesFailureCallback + "publish/checkCapabilities/start": OnPublishCheckCapabilitiesStartCallback; + "publish/checkCapabilities/log": OnPublishCheckCapabilitiesLogCallback; + "publish/checkCapabilities/success": OnPublishCheckCapabilitiesSuccessCallback; + "publish/checkCapabilities/failure": OnPublishCheckCapabilitiesFailureCallback; // 'publish/createBundle/failure/authFailure' | // received but temporarily converted - 'publish/createNewDeployment/start': OnPublishCreateNewDeploymentStartCallback - 'publish/createNewDeployment/success': OnPublishCreateNewDeploymentSuccessCallback - 'publish/createNewDeployment/failure': OnPublishCreateNewDeploymentFailureCallback - - 'publish/setEnvVars/start': OnPublishSetEnvVarsStartCallback - 'publish/setEnvVars/success': OnPublishSetEnvVarsSuccessCallback - 'publish/setEnvVars/failure': OnPublishSetEnvVarsFailureCallback - - 'publish/createBundle/start': OnPublishCreateBundleStartCallback - 'publish/createBundle/log': OnPublishCreateBundleLogCallback - 'publish/createBundle/success': OnPublishCreateBundleSuccessCallback - 'publish/createBundle/failure': OnPublishCreateBundleFailureCallback - - 'publish/createDeployment/start': OnPublishCreateDeploymentStartCallback - 'publish/createDeployment/log': OnPublishCreateDeploymentLogCallback - 'publish/createDeployment/success': OnPublishCreateDeploymentSuccessCallback - 'publish/createDeployment/failure': OnPublishCreateDeploymentFailureCallback - - 'publish/uploadBundle/start': OnPublishUploadBundleStartCallback - 'publish/uploadBundle/log': OnPublishUploadBundleLogCallback - 'publish/uploadBundle/success': OnPublishUploadBundleSuccessCallback - 'publish/uploadBundle/failure': OnPublishUploadBundleFailureCallback - - 'publish/deployBundle/start': OnPublishDeployBundleStartCallback - 'publish/deployBundle/log': OnPublishDeployBundleLogCallback - 'publish/deployBundle/success': OnPublishDeployBundleSuccessCallback - 'publish/deployBundle/failure': OnPublishDeployBundleFailureCallback - - 'publish/restorePythonEnv/start': OnPublishRestorePythonEnvStartCallback - 'publish/restorePythonEnv/log': OnPublishRestorePythonEnvLogCallback - 'publish/restorePythonEnv/progress': OnPublishRestorePythonEnvProgressCallback - 'publish/restorePythonEnv/status': OnPublishRestorePythonEnvStatusCallback - 'publish/restorePythonEnv/success': OnPublishRestorePythonEnvSuccessCallback - 'publish/restorePythonEnv/failure': OnPublishRestorePythonEnvFailureCallback + "publish/createNewDeployment/start": OnPublishCreateNewDeploymentStartCallback; + "publish/createNewDeployment/success": OnPublishCreateNewDeploymentSuccessCallback; + "publish/createNewDeployment/failure": OnPublishCreateNewDeploymentFailureCallback; + + "publish/setEnvVars/start": OnPublishSetEnvVarsStartCallback; + "publish/setEnvVars/success": OnPublishSetEnvVarsSuccessCallback; + "publish/setEnvVars/failure": OnPublishSetEnvVarsFailureCallback; + + "publish/createBundle/start": OnPublishCreateBundleStartCallback; + "publish/createBundle/log": OnPublishCreateBundleLogCallback; + "publish/createBundle/success": OnPublishCreateBundleSuccessCallback; + "publish/createBundle/failure": OnPublishCreateBundleFailureCallback; + + "publish/createDeployment/start": OnPublishCreateDeploymentStartCallback; + "publish/createDeployment/log": OnPublishCreateDeploymentLogCallback; + "publish/createDeployment/success": OnPublishCreateDeploymentSuccessCallback; + "publish/createDeployment/failure": OnPublishCreateDeploymentFailureCallback; + + "publish/uploadBundle/start": OnPublishUploadBundleStartCallback; + "publish/uploadBundle/log": OnPublishUploadBundleLogCallback; + "publish/uploadBundle/success": OnPublishUploadBundleSuccessCallback; + "publish/uploadBundle/failure": OnPublishUploadBundleFailureCallback; + + "publish/deployBundle/start": OnPublishDeployBundleStartCallback; + "publish/deployBundle/log": OnPublishDeployBundleLogCallback; + "publish/deployBundle/success": OnPublishDeployBundleSuccessCallback; + "publish/deployBundle/failure": OnPublishDeployBundleFailureCallback; + + "publish/restorePythonEnv/start": OnPublishRestorePythonEnvStartCallback; + "publish/restorePythonEnv/log": OnPublishRestorePythonEnvLogCallback; + "publish/restorePythonEnv/progress": OnPublishRestorePythonEnvProgressCallback; + "publish/restorePythonEnv/status": OnPublishRestorePythonEnvStatusCallback; + "publish/restorePythonEnv/success": OnPublishRestorePythonEnvSuccessCallback; + "publish/restorePythonEnv/failure": OnPublishRestorePythonEnvFailureCallback; // 'publish/restorePythonEnv/failure/serverErr' | // received but temporarily converted - 'publish/runContent/start': OnPublishRunContentStartCallback - 'publish/runContent/log': OnPublishRunContentLogCallback - 'publish/runContent/success': OnPublishRunContentSuccessCallback - 'publish/runContent/failure': OnPublishRunContentFailureCallback + "publish/runContent/start": OnPublishRunContentStartCallback; + "publish/runContent/log": OnPublishRunContentLogCallback; + "publish/runContent/success": OnPublishRunContentSuccessCallback; + "publish/runContent/failure": OnPublishRunContentFailureCallback; - 'publish/setVanityURL/start': OnPublishSetVanityURLStartCallback - 'publish/setVanityURL/log': OnPublishSetVanityURLLogCallback - 'publish/setVanityURL/success': OnPublishSetVanityURLSuccessCallback - 'publish/setVanityURL/failure': OnPublishSetVanityURLFailureCallback + "publish/setVanityURL/start": OnPublishSetVanityURLStartCallback; + "publish/setVanityURL/log": OnPublishSetVanityURLLogCallback; + "publish/setVanityURL/success": OnPublishSetVanityURLSuccessCallback; + "publish/setVanityURL/failure": OnPublishSetVanityURLFailureCallback; - 'publish/validateDeployment/start': OnPublishValidateDeploymentStartCallback - 'publish/validateDeployment/log': OnPublishValidateDeploymentLogCallback - 'publish/validateDeployment/success': OnPublishValidateDeploymentSuccessCallback - 'publish/validateDeployment/failure': OnPublishValidateDeploymentFailureCallback + "publish/validateDeployment/start": OnPublishValidateDeploymentStartCallback; + "publish/validateDeployment/log": OnPublishValidateDeploymentLogCallback; + "publish/validateDeployment/success": OnPublishValidateDeploymentSuccessCallback; + "publish/validateDeployment/failure": OnPublishValidateDeploymentFailureCallback; - 'publish/success': OnPublishSuccessCallback - 'publish/failure': OnPublishFailureCallback + "publish/success": OnPublishSuccessCallback; + "publish/failure": OnPublishFailureCallback; } export const eventTypeToString = (eventTypeStr: string): string => { const eventVerbToString: Record = { - 'publish/checkCapabilities': 'Check Capabilities', - 'publish/createBundle': 'Create Bundle', - 'publish/uploadBundle': 'Upload Bundle', - 'publish/createDeployment': 'Create Deployment', - 'publish/deployBundle': 'Deploy Bundle', - 'publish/restorePythonEnv': 'Restore Python Environment', - 'publish/runContent': 'Run Content', - 'publish/setVanityURL': 'Set Vanity URL', - 'publish/validateDeployment': 'Validate Deployment', - 'publish/success': 'Wrapping up Deployment', + "publish/checkCapabilities": "Check Capabilities", + "publish/createBundle": "Create Bundle", + "publish/uploadBundle": "Upload Bundle", + "publish/createDeployment": "Create Deployment", + "publish/deployBundle": "Deploy Bundle", + "publish/restorePythonEnv": "Restore Python Environment", + "publish/runContent": "Run Content", + "publish/setVanityURL": "Set Vanity URL", + "publish/validateDeployment": "Validate Deployment", + "publish/success": "Wrapping up Deployment", }; // we do not provide strings for wildcards - if (eventTypeStr.includes('*')) { + if (eventTypeStr.includes("*")) { return eventTypeStr; } // not in the format we're expecting - const parts = eventTypeStr.split('/'); + const parts = eventTypeStr.split("/"); if (parts.length !== 3) { return eventTypeStr; } @@ -153,17 +153,17 @@ export const eventTypeToString = (eventTypeStr: string): string => { } return base; -} +}; export function getLocalId(arg: EventStreamMessage) { return arg.data.localId; } export interface EventStreamMessage { - type: EventSubscriptionTarget, - time: string, - data: Record, - error?: string, + type: EventSubscriptionTarget; + time: string; + data: Record; + error?: string; } export interface EventStreamMessageWithError extends EventStreamMessage { @@ -171,18 +171,23 @@ export interface EventStreamMessageWithError extends EventStreamMessage { } export function isErrorEventStreamMessage( - msg: EventStreamMessage + msg: EventStreamMessage, ): msg is EventStreamMessageWithError { return msg.error !== undefined; } -export type OnMessageEventSourceCallback = (msg: T) => void; +export type OnMessageEventSourceCallback = ( + msg: T, +) => void; export function isEventStreamMessage(o: object): o is EventStreamMessage { return ( - 'type' in o && typeof o.type === 'string' && - 'time' in o && typeof o.time === 'string' && - 'data' in o && typeof o.data === 'object' + "type" in o && + typeof o.type === "string" && + "time" in o && + typeof o.time === "string" && + "data" in o && + typeof o.data === "object" ); } @@ -190,685 +195,790 @@ export function isEventStreamMessage(o: object): o is EventStreamMessage { // and provide type guards for them to support proper typing. export interface AgentLog extends EventStreamMessage { - type: 'agent/log', + type: "agent/log"; // structured data not guaranteed, use selective or generic queries // from data map } export type OnAgentLogCallback = (msg: AgentLog) => void; -export function isAgentLog(arg: Events): - arg is AgentLog { - return arg.type === 'agent/log'; +export function isAgentLog(arg: Events): arg is AgentLog { + return arg.type === "agent/log"; } export interface ErrorsSse extends EventStreamMessage { - type: 'errors/sse', + type: "errors/sse"; } export type OnErrorsSseCallback = (msg: ErrorsSse) => void; -export function isErrorsSse(arg: Events): - arg is ErrorsSse { - return arg.type === 'errors/sse'; +export function isErrorsSse(arg: Events): arg is ErrorsSse { + return arg.type === "errors/sse"; } export interface ErrorsOpen extends EventStreamMessage { - type: 'errors/open', + type: "errors/open"; } export type OnErrorsOpenCallback = (msg: ErrorsOpen) => void; -export function isErrorsOpen(arg: Events): - arg is ErrorsOpen { - return arg.type === 'errors/open'; +export function isErrorsOpen(arg: Events): arg is ErrorsOpen { + return arg.type === "errors/open"; } export interface ErrorsUnknownEvent extends EventStreamMessage { - type: 'errors/unknownEvent', + type: "errors/unknownEvent"; } export type OnErrorsUnknownEventCallback = (msg: ErrorsUnknownEvent) => void; -export function isErrorsUnknownEvent(arg: Events): - arg is ErrorsUnknownEvent { - return arg.type === 'errors/unknownEvent'; +export function isErrorsUnknownEvent(arg: Events): arg is ErrorsUnknownEvent { + return arg.type === "errors/unknownEvent"; } export interface OpenSse extends EventStreamMessage { - type: 'open/sse', + type: "open/sse"; } export type OnOpenSseCallback = (msg: OpenSse) => void; -export function isOpenSse(arg: Events): - arg is OpenSse { - return arg.type === 'open/sse'; +export function isOpenSse(arg: Events): arg is OpenSse { + return arg.type === "open/sse"; } export interface PublishStart extends EventStreamMessage { - type: 'publish/start', + type: "publish/start"; data: { - localId: string, - server: string, - } + localId: string; + server: string; + }; } export type OnPublishStartCallback = (msg: PublishStart) => void; -export function isPublishStart(arg: Events): - arg is PublishStart { - return arg.type === 'publish/start'; +export function isPublishStart(arg: Events): arg is PublishStart { + return arg.type === "publish/start"; } export interface PublishCheckCapabilitiesStart extends EventStreamMessage { - type: 'publish/checkCapabilities/start', + type: "publish/checkCapabilities/start"; data: { - localId: string, - } + localId: string; + }; } -export type OnPublishCheckCapabilitiesStartCallback = - (msg: PublishCheckCapabilitiesStart) => void; -export function isPublishCheckCapabilitiesStart(arg: Events): - arg is PublishCheckCapabilitiesStart { - return arg.type === 'publish/checkCapabilities/start'; +export type OnPublishCheckCapabilitiesStartCallback = ( + msg: PublishCheckCapabilitiesStart, +) => void; +export function isPublishCheckCapabilitiesStart( + arg: Events, +): arg is PublishCheckCapabilitiesStart { + return arg.type === "publish/checkCapabilities/start"; } export interface PublishCheckCapabilitiesLog extends EventStreamMessage { - type: 'publish/checkCapabilities/log', + type: "publish/checkCapabilities/log"; // structured data not guaranteed, use selective or generic queries // from data map } -export type OnPublishCheckCapabilitiesLogCallback = - (msg: PublishCheckCapabilitiesLog) => void; -export function isPublishCheckCapabilitiesLog(arg: Events): - arg is PublishCheckCapabilitiesLog { - return arg.type === 'publish/checkCapabilities/log'; +export type OnPublishCheckCapabilitiesLogCallback = ( + msg: PublishCheckCapabilitiesLog, +) => void; +export function isPublishCheckCapabilitiesLog( + arg: Events, +): arg is PublishCheckCapabilitiesLog { + return arg.type === "publish/checkCapabilities/log"; } export interface PublishCheckCapabilitiesSuccess extends EventStreamMessage { - type: 'publish/checkCapabilities/success', + type: "publish/checkCapabilities/success"; data: { - localId: string, - } + localId: string; + }; } -export type OnPublishCheckCapabilitiesSuccessCallback = - (msg: PublishCheckCapabilitiesSuccess) => void; -export function isPublishCheckCapabilitiesSuccess(arg: Events): - arg is PublishCheckCapabilitiesSuccess { - return arg.type === 'publish/checkCapabilities/success'; +export type OnPublishCheckCapabilitiesSuccessCallback = ( + msg: PublishCheckCapabilitiesSuccess, +) => void; +export function isPublishCheckCapabilitiesSuccess( + arg: Events, +): arg is PublishCheckCapabilitiesSuccess { + return arg.type === "publish/checkCapabilities/success"; } export interface PublishCheckCapabilitiesFailure extends EventStreamMessage { - type: 'publish/checkCapabilities/failure', - error: string, // translated internally + type: "publish/checkCapabilities/failure"; + error: string; // translated internally // structured data not guaranteed, use selective or generic queries // from data map } -export type OnPublishCheckCapabilitiesFailureCallback = - (msg: PublishCheckCapabilitiesFailure) => void; -export function isPublishCheckCapabilitiesFailure(arg: Events): - arg is PublishCheckCapabilitiesFailure { - return arg.type === 'publish/checkCapabilities/failure'; +export type OnPublishCheckCapabilitiesFailureCallback = ( + msg: PublishCheckCapabilitiesFailure, +) => void; +export function isPublishCheckCapabilitiesFailure( + arg: Events, +): arg is PublishCheckCapabilitiesFailure { + return arg.type === "publish/checkCapabilities/failure"; } export interface PublishCreateNewDeploymentStart extends EventStreamMessage { - type: 'publish/createNewDeployment/start', + type: "publish/createNewDeployment/start"; data: { - localId: string, - saveName: string, - } + localId: string; + saveName: string; + }; } -export type OnPublishCreateNewDeploymentStartCallback = - (msg: PublishCreateNewDeploymentStart) => void; -export function isPublishCreateNewDeploymentStart(arg: Events): - arg is PublishCreateNewDeploymentStart { - return arg.type === 'publish/createNewDeployment/start'; +export type OnPublishCreateNewDeploymentStartCallback = ( + msg: PublishCreateNewDeploymentStart, +) => void; +export function isPublishCreateNewDeploymentStart( + arg: Events, +): arg is PublishCreateNewDeploymentStart { + return arg.type === "publish/createNewDeployment/start"; } export interface PublishCreateNewDeploymentSuccess extends EventStreamMessage { - type: 'publish/createNewDeployment/success', + type: "publish/createNewDeployment/success"; data: { - localId: string, - contentId: string, - saveName: string, - } + localId: string; + contentId: string; + saveName: string; + }; } -export type OnPublishCreateNewDeploymentSuccessCallback = - (msg: PublishCreateNewDeploymentSuccess) => void; -export function isPublishCreateNewDeploymentSuccess(arg: Events): - arg is PublishCreateNewDeploymentSuccess { - return arg.type === 'publish/createNewDeployment/success'; +export type OnPublishCreateNewDeploymentSuccessCallback = ( + msg: PublishCreateNewDeploymentSuccess, +) => void; +export function isPublishCreateNewDeploymentSuccess( + arg: Events, +): arg is PublishCreateNewDeploymentSuccess { + return arg.type === "publish/createNewDeployment/success"; } export interface PublishCreateNewDeploymentFailure extends EventStreamMessage { - type: 'publish/createNewDeployment/failure', - error: string, // translated internally + type: "publish/createNewDeployment/failure"; + error: string; // translated internally // structured data not guaranteed, use selective or generic queries // from data map } -export type OnPublishCreateNewDeploymentFailureCallback = - (msg: PublishCreateNewDeploymentFailure) => void; -export function isPublishCreateNewDeploymentFailure(arg: Events): - arg is PublishCreateNewDeploymentFailure { - return arg.type === 'publish/createNewDeployment/failure'; +export type OnPublishCreateNewDeploymentFailureCallback = ( + msg: PublishCreateNewDeploymentFailure, +) => void; +export function isPublishCreateNewDeploymentFailure( + arg: Events, +): arg is PublishCreateNewDeploymentFailure { + return arg.type === "publish/createNewDeployment/failure"; } export interface PublishSetEnvVarsStart extends EventStreamMessage { - type: 'publish/setEnvVars/start', + type: "publish/setEnvVars/start"; data: { - localId: string, - } + localId: string; + }; } -export type OnPublishSetEnvVarsStartCallback = (msg: PublishSetEnvVarsStart) => void; -export function isPublishSetEnvVarsStart(arg: Events): - arg is PublishSetEnvVarsStart { - return arg.type === 'publish/setEnvVars/start'; +export type OnPublishSetEnvVarsStartCallback = ( + msg: PublishSetEnvVarsStart, +) => void; +export function isPublishSetEnvVarsStart( + arg: Events, +): arg is PublishSetEnvVarsStart { + return arg.type === "publish/setEnvVars/start"; } export interface PublishSetEnvVarsSuccess extends EventStreamMessage { - type: 'publish/setEnvVars/success', + type: "publish/setEnvVars/success"; data: { - localId: string, + localId: string; // should include echo of variables/values - } + }; } -export type OnPublishSetEnvVarsSuccessCallback = - (msg: PublishSetEnvVarsSuccess) => void; -export function isPublishSetEnvVarsSuccess(arg: Events): - arg is PublishSetEnvVarsSuccess { - return arg.type === 'publish/setEnvVars/success'; +export type OnPublishSetEnvVarsSuccessCallback = ( + msg: PublishSetEnvVarsSuccess, +) => void; +export function isPublishSetEnvVarsSuccess( + arg: Events, +): arg is PublishSetEnvVarsSuccess { + return arg.type === "publish/setEnvVars/success"; } export interface PublishSetEnvVarsFailure extends EventStreamMessage { - type: 'publish/setEnvVars/failure', - error: string, // translated internally + type: "publish/setEnvVars/failure"; + error: string; // translated internally // structured data not guaranteed, use selective or generic queries // from data map } -export type OnPublishSetEnvVarsFailureCallback = - (msg: PublishSetEnvVarsFailure) => void; -export function isPublishSetEnvVarsFailure(arg: Events): - arg is PublishSetEnvVarsFailure { - return arg.type === 'publish/setEnvVars/failure'; +export type OnPublishSetEnvVarsFailureCallback = ( + msg: PublishSetEnvVarsFailure, +) => void; +export function isPublishSetEnvVarsFailure( + arg: Events, +): arg is PublishSetEnvVarsFailure { + return arg.type === "publish/setEnvVars/failure"; } export interface PublishCreateBundleStart extends EventStreamMessage { - type: 'publish/createBundle/start', + type: "publish/createBundle/start"; data: { - localId: string, - } + localId: string; + }; } -export type OnPublishCreateBundleStartCallback = (msg: PublishCreateBundleStart) => void; -export function isPublishCreateBundleStart(arg: Events): - arg is PublishCreateBundleStart { - return arg.type === 'publish/createBundle/start'; +export type OnPublishCreateBundleStartCallback = ( + msg: PublishCreateBundleStart, +) => void; +export function isPublishCreateBundleStart( + arg: Events, +): arg is PublishCreateBundleStart { + return arg.type === "publish/createBundle/start"; } export interface PublishCreateBundleLog extends EventStreamMessage { - type: 'publish/createBundle/log', + type: "publish/createBundle/log"; // structured data not guaranteed, use selective or generic queries // from data map } -export type OnPublishCreateBundleLogCallback = (msg: PublishCreateBundleLog) => void; -export function isPublishCreateBundleLog(arg: Events): - arg is PublishCreateBundleLog { - return arg.type === 'publish/createBundle/log'; +export type OnPublishCreateBundleLogCallback = ( + msg: PublishCreateBundleLog, +) => void; +export function isPublishCreateBundleLog( + arg: Events, +): arg is PublishCreateBundleLog { + return arg.type === "publish/createBundle/log"; } export interface PublishCreateBundleSuccess extends EventStreamMessage { - type: 'publish/createBundle/success', + type: "publish/createBundle/success"; data: { - localId: string, - filename: string, - } + localId: string; + filename: string; + }; } -export type OnPublishCreateBundleSuccessCallback = (msg: PublishCreateBundleSuccess) => void; -export function isPublishCreateBundleSuccess(arg: Events): - arg is PublishCreateBundleSuccess { - return arg.type === 'publish/createBundle/success'; +export type OnPublishCreateBundleSuccessCallback = ( + msg: PublishCreateBundleSuccess, +) => void; +export function isPublishCreateBundleSuccess( + arg: Events, +): arg is PublishCreateBundleSuccess { + return arg.type === "publish/createBundle/success"; } export interface PublishCreateBundleFailure extends EventStreamMessage { - type: 'publish/createBundle/failure', - error: string, // translated internally + type: "publish/createBundle/failure"; + error: string; // translated internally // structured data not guaranteed, use selective or generic queries // from data map } -export type OnPublishCreateBundleFailureCallback = (msg: PublishCreateBundleFailure) => void; -export function isPublishCreateBundleFailure(arg: Events): - arg is PublishCreateBundleFailure { - return arg.type === 'publish/createBundle/failure'; +export type OnPublishCreateBundleFailureCallback = ( + msg: PublishCreateBundleFailure, +) => void; +export function isPublishCreateBundleFailure( + arg: Events, +): arg is PublishCreateBundleFailure { + return arg.type === "publish/createBundle/failure"; } export interface PublishCreateDeploymentStart extends EventStreamMessage { - type: 'publish/createDeployment/start', + type: "publish/createDeployment/start"; data: { - localId: string, - contentId: string, - saveName: string, - } + localId: string; + contentId: string; + saveName: string; + }; } -export type OnPublishCreateDeploymentStartCallback = (msg: PublishCreateDeploymentStart) => void; -export function isPublishCreateDeploymentStart(arg: Events): - arg is PublishCreateDeploymentStart { - return arg.type === 'publish/createDeployment/start'; +export type OnPublishCreateDeploymentStartCallback = ( + msg: PublishCreateDeploymentStart, +) => void; +export function isPublishCreateDeploymentStart( + arg: Events, +): arg is PublishCreateDeploymentStart { + return arg.type === "publish/createDeployment/start"; } export interface PublishCreateDeploymentLog extends EventStreamMessage { - type: 'publish/createDeployment/log', + type: "publish/createDeployment/log"; data: { // structured data not guaranteed, use selective or generic queries // from data map - } + }; } -export type OnPublishCreateDeploymentLogCallback = (msg: PublishCreateDeploymentLog) => void; -export function isPublishCreateDeploymentLog(arg: Events): - arg is PublishCreateDeploymentLog { - return arg.type === 'publish/createDeployment/log'; +export type OnPublishCreateDeploymentLogCallback = ( + msg: PublishCreateDeploymentLog, +) => void; +export function isPublishCreateDeploymentLog( + arg: Events, +): arg is PublishCreateDeploymentLog { + return arg.type === "publish/createDeployment/log"; } export interface PublishCreateDeploymentSuccess extends EventStreamMessage { - type: 'publish/createDeployment/success', + type: "publish/createDeployment/success"; data: { - localId: string, - } + localId: string; + }; } -export type OnPublishCreateDeploymentSuccessCallback = - (msg: PublishCreateDeploymentSuccess) => void; -export function isPublishCreateDeploymentSuccess(arg: Events): - arg is PublishCreateDeploymentSuccess { - return arg.type === 'publish/createDeployment/success'; +export type OnPublishCreateDeploymentSuccessCallback = ( + msg: PublishCreateDeploymentSuccess, +) => void; +export function isPublishCreateDeploymentSuccess( + arg: Events, +): arg is PublishCreateDeploymentSuccess { + return arg.type === "publish/createDeployment/success"; } export interface PublishCreateDeploymentFailure extends EventStreamMessage { - type: 'publish/createDeployment/failure', - error: string, // translated internally + type: "publish/createDeployment/failure"; + error: string; // translated internally // structured data not guaranteed, use selective or generic queries // from data map } -export type OnPublishCreateDeploymentFailureCallback = - (msg: PublishCreateDeploymentFailure) => void; -export function isPublishCreateDeploymentFailure(arg: Events): - arg is PublishCreateDeploymentFailure { - return arg.type === 'publish/createDeployment/failure'; +export type OnPublishCreateDeploymentFailureCallback = ( + msg: PublishCreateDeploymentFailure, +) => void; +export function isPublishCreateDeploymentFailure( + arg: Events, +): arg is PublishCreateDeploymentFailure { + return arg.type === "publish/createDeployment/failure"; } export interface PublishUploadBundleStart extends EventStreamMessage { - type: 'publish/uploadBundle/start', + type: "publish/uploadBundle/start"; data: { - localId: string, - } + localId: string; + }; } -export type OnPublishUploadBundleStartCallback = (msg: PublishUploadBundleStart) => void; -export function isPublishUploadBundleStart(arg: Events): - arg is PublishUploadBundleStart { - return arg.type === 'publish/uploadBundle/start'; +export type OnPublishUploadBundleStartCallback = ( + msg: PublishUploadBundleStart, +) => void; +export function isPublishUploadBundleStart( + arg: Events, +): arg is PublishUploadBundleStart { + return arg.type === "publish/uploadBundle/start"; } export interface PublishUploadBundleLog extends EventStreamMessage { - type: 'publish/uploadBundle/log', + type: "publish/uploadBundle/log"; data: { // structured data not guaranteed, use selective or generic queries // from data map - } + }; } -export type OnPublishUploadBundleLogCallback = (msg: PublishUploadBundleLog) => void; -export function isPublishUploadBundleLog(arg: Events): - arg is PublishUploadBundleLog { - return arg.type === 'publish/uploadBundle/log'; +export type OnPublishUploadBundleLogCallback = ( + msg: PublishUploadBundleLog, +) => void; +export function isPublishUploadBundleLog( + arg: Events, +): arg is PublishUploadBundleLog { + return arg.type === "publish/uploadBundle/log"; } export interface PublishUploadBundleSuccess extends EventStreamMessage { - type: 'publish/uploadBundle/success', + type: "publish/uploadBundle/success"; data: { - localId: string, - bundleId: string, - } + localId: string; + bundleId: string; + }; } -export type OnPublishUploadBundleSuccessCallback = (msg: PublishUploadBundleSuccess) => void; -export function isPublishUploadBundleSuccess(arg: Events): - arg is PublishUploadBundleSuccess { - return arg.type === 'publish/uploadBundle/success'; +export type OnPublishUploadBundleSuccessCallback = ( + msg: PublishUploadBundleSuccess, +) => void; +export function isPublishUploadBundleSuccess( + arg: Events, +): arg is PublishUploadBundleSuccess { + return arg.type === "publish/uploadBundle/success"; } export interface PublishUploadBundleFailure extends EventStreamMessage { - type: 'publish/uploadBundle/failure', - error: string, // translated internally + type: "publish/uploadBundle/failure"; + error: string; // translated internally // structured data not guaranteed, use selective or generic queries // from data map } -export type OnPublishUploadBundleFailureCallback = (msg: PublishUploadBundleFailure) => void; -export function isPublishUploadBundleFailure(arg: Events): - arg is PublishUploadBundleFailure { - return arg.type === 'publish/uploadBundle/failure'; +export type OnPublishUploadBundleFailureCallback = ( + msg: PublishUploadBundleFailure, +) => void; +export function isPublishUploadBundleFailure( + arg: Events, +): arg is PublishUploadBundleFailure { + return arg.type === "publish/uploadBundle/failure"; } export interface PublishDeployBundleStart extends EventStreamMessage { - type: 'publish/deployBundle/start', + type: "publish/deployBundle/start"; data: { - localId: string, - } + localId: string; + }; } -export type OnPublishDeployBundleStartCallback = (msg: PublishDeployBundleStart) => void; -export function isPublishDeployBundleStart(arg: Events): - arg is PublishDeployBundleStart { - return arg.type === 'publish/deployBundle/start'; +export type OnPublishDeployBundleStartCallback = ( + msg: PublishDeployBundleStart, +) => void; +export function isPublishDeployBundleStart( + arg: Events, +): arg is PublishDeployBundleStart { + return arg.type === "publish/deployBundle/start"; } export interface PublishDeployBundleLog extends EventStreamMessage { - type: 'publish/deployBundle/log', + type: "publish/deployBundle/log"; data: { // structured data not guaranteed, use selective or generic queries // from data map - } + }; } -export type OnPublishDeployBundleLogCallback = (msg: PublishDeployBundleLog) => void; -export function isPublishDeployBundleLog(arg: Events): - arg is PublishDeployBundleLog { - return arg.type === 'publish/deployBundle/log'; +export type OnPublishDeployBundleLogCallback = ( + msg: PublishDeployBundleLog, +) => void; +export function isPublishDeployBundleLog( + arg: Events, +): arg is PublishDeployBundleLog { + return arg.type === "publish/deployBundle/log"; } export interface PublishDeployBundleSuccess extends EventStreamMessage { - type: 'publish/deployBundle/success', + type: "publish/deployBundle/success"; data: { - localId: string, - taskId: string, - } + localId: string; + taskId: string; + }; } -export type OnPublishDeployBundleSuccessCallback = (msg: PublishDeployBundleSuccess) => void; -export function isPublishDeployBundleSuccess(arg: Events): - arg is PublishDeployBundleSuccess { - return arg.type === 'publish/deployBundle/success'; +export type OnPublishDeployBundleSuccessCallback = ( + msg: PublishDeployBundleSuccess, +) => void; +export function isPublishDeployBundleSuccess( + arg: Events, +): arg is PublishDeployBundleSuccess { + return arg.type === "publish/deployBundle/success"; } export interface PublishDeployBundleFailure extends EventStreamMessage { - type: 'publish/deployBundle/failure', - error: string, // translated internally + type: "publish/deployBundle/failure"; + error: string; // translated internally // structured data not guaranteed, use selective or generic queries // from data map } -export type OnPublishDeployBundleFailureCallback = (msg: PublishDeployBundleFailure) => void; -export function isPublishDeployBundleFailure(arg: Events): - arg is PublishDeployBundleFailure { - return arg.type === 'publish/deployBundle/failure'; +export type OnPublishDeployBundleFailureCallback = ( + msg: PublishDeployBundleFailure, +) => void; +export function isPublishDeployBundleFailure( + arg: Events, +): arg is PublishDeployBundleFailure { + return arg.type === "publish/deployBundle/failure"; } export interface PublishRestorePythonEnvStart extends EventStreamMessage { - type: 'publish/restorePythonEnv/start', + type: "publish/restorePythonEnv/start"; data: { - localId: string, - } + localId: string; + }; } -export type OnPublishRestorePythonEnvStartCallback = (msg: PublishRestorePythonEnvStart) => void; -export function isPublishRestorePythonEnvStart(arg: Events): - arg is PublishRestorePythonEnvStart { - return arg.type === 'publish/restorePythonEnv/start'; +export type OnPublishRestorePythonEnvStartCallback = ( + msg: PublishRestorePythonEnvStart, +) => void; +export function isPublishRestorePythonEnvStart( + arg: Events, +): arg is PublishRestorePythonEnvStart { + return arg.type === "publish/restorePythonEnv/start"; } export interface PublishRestorePythonEnvLog extends EventStreamMessage { - type: 'publish/restorePythonEnv/log', + type: "publish/restorePythonEnv/log"; // structured data not guaranteed, use selective or generic queries // from data map } -export type OnPublishRestorePythonEnvLogCallback = (msg: PublishRestorePythonEnvLog) => void; -export function isPublishRestorePythonEnvLog(arg: Events): - arg is PublishRestorePythonEnvLog { - return arg.type === 'publish/restorePythonEnv/log'; +export type OnPublishRestorePythonEnvLogCallback = ( + msg: PublishRestorePythonEnvLog, +) => void; +export function isPublishRestorePythonEnvLog( + arg: Events, +): arg is PublishRestorePythonEnvLog { + return arg.type === "publish/restorePythonEnv/log"; } export interface PublishRestorePythonEnvProgress extends EventStreamMessage { - type: 'publish/restorePythonEnv/progress', + type: "publish/restorePythonEnv/progress"; // structured data not guaranteed, use selective or generic queries // from data map } export type OnPublishRestorePythonEnvProgressCallback = ( - msg: PublishRestorePythonEnvProgress + msg: PublishRestorePythonEnvProgress, ) => void; -export function isPublishRestorePythonEnvProgress(arg: Events): - arg is PublishRestorePythonEnvProgress { - return arg.type === 'publish/restorePythonEnv/progress'; +export function isPublishRestorePythonEnvProgress( + arg: Events, +): arg is PublishRestorePythonEnvProgress { + return arg.type === "publish/restorePythonEnv/progress"; } -type packageRuntime = 'r' | 'python'; -type packageStatus = 'download+install' | 'download' | 'install'; +type packageRuntime = "r" | "python"; +type packageStatus = "download+install" | "download" | "install"; export interface PublishRestorePythonEnvStatus extends EventStreamMessage { - type: 'publish/restorePythonEnv/status', + type: "publish/restorePythonEnv/status"; data: { - localId: string, - name: string, - runtime: packageRuntime, - status: packageStatus, - version: string - } + localId: string; + name: string; + runtime: packageRuntime; + status: packageStatus; + version: string; + }; } export type OnPublishRestorePythonEnvStatusCallback = ( - msg: PublishRestorePythonEnvStatus + msg: PublishRestorePythonEnvStatus, ) => void; -export function isPublishRestorePythonEnvStatus(arg: Events): - arg is PublishRestorePythonEnvStatus { - return arg.type === 'publish/restorePythonEnv/status'; +export function isPublishRestorePythonEnvStatus( + arg: Events, +): arg is PublishRestorePythonEnvStatus { + return arg.type === "publish/restorePythonEnv/status"; } export interface PublishRestorePythonEnvSuccess extends EventStreamMessage { - type: 'publish/restorePythonEnv/success', + type: "publish/restorePythonEnv/success"; data: { - localId: string, - } + localId: string; + }; } -export type OnPublishRestorePythonEnvSuccessCallback = - (msg: PublishRestorePythonEnvSuccess) => void; -export function isPublishRestorePythonEnvSuccess(arg: Events): - arg is PublishRestorePythonEnvSuccess { - return arg.type === 'publish/restorePythonEnv/success'; +export type OnPublishRestorePythonEnvSuccessCallback = ( + msg: PublishRestorePythonEnvSuccess, +) => void; +export function isPublishRestorePythonEnvSuccess( + arg: Events, +): arg is PublishRestorePythonEnvSuccess { + return arg.type === "publish/restorePythonEnv/success"; } export interface PublishRestorePythonEnvFailure extends EventStreamMessage { - type: 'publish/restorePythonEnv/failure', - error: string, // translated internally + type: "publish/restorePythonEnv/failure"; + error: string; // translated internally // structured data not guaranteed, use selective or generic queries // from data map } -export type OnPublishRestorePythonEnvFailureCallback = - (msg: PublishRestorePythonEnvFailure) => void; -export function isPublishRestorePythonEnvFailure(arg: Events): - arg is PublishRestorePythonEnvFailure { - return arg.type === 'publish/restorePythonEnv/failure'; +export type OnPublishRestorePythonEnvFailureCallback = ( + msg: PublishRestorePythonEnvFailure, +) => void; +export function isPublishRestorePythonEnvFailure( + arg: Events, +): arg is PublishRestorePythonEnvFailure { + return arg.type === "publish/restorePythonEnv/failure"; } export interface PublishRunContentStart extends EventStreamMessage { - type: 'publish/runContent/start', + type: "publish/runContent/start"; data: { - localId: string, - } + localId: string; + }; } -export type OnPublishRunContentStartCallback = (msg: PublishRunContentStart) => void; -export function isPublishRunContentStart(arg: Events): - arg is PublishRunContentStart { - return arg.type === 'publish/runContent/start'; +export type OnPublishRunContentStartCallback = ( + msg: PublishRunContentStart, +) => void; +export function isPublishRunContentStart( + arg: Events, +): arg is PublishRunContentStart { + return arg.type === "publish/runContent/start"; } export interface PublishRunContentLog extends EventStreamMessage { - type: 'publish/runContent/log', + type: "publish/runContent/log"; // structured data not guaranteed, use selective or generic queries // from data map } -export type OnPublishRunContentLogCallback = (msg: PublishRunContentLog) => void; -export function isPublishRunContentLog(arg: Events): - arg is PublishRunContentLog { - return arg.type === 'publish/runContent/log'; +export type OnPublishRunContentLogCallback = ( + msg: PublishRunContentLog, +) => void; +export function isPublishRunContentLog( + arg: Events, +): arg is PublishRunContentLog { + return arg.type === "publish/runContent/log"; } export interface PublishRunContentSuccess extends EventStreamMessage { - type: 'publish/runContent/success', + type: "publish/runContent/success"; data: { - localId: string, - } + localId: string; + }; } -export type OnPublishRunContentSuccessCallback = (msg: PublishRunContentSuccess) => void; -export function isPublishRunContentSuccess(arg: Events): - arg is PublishRunContentSuccess { - return arg.type === 'publish/runContent/success'; +export type OnPublishRunContentSuccessCallback = ( + msg: PublishRunContentSuccess, +) => void; +export function isPublishRunContentSuccess( + arg: Events, +): arg is PublishRunContentSuccess { + return arg.type === "publish/runContent/success"; } export interface PublishRunContentFailure extends EventStreamMessage { - type: 'publish/runContent/failure', - error: string, // translated internally + type: "publish/runContent/failure"; + error: string; // translated internally // structured data not guaranteed, use selective or generic queries // from data map } -export type OnPublishRunContentFailureCallback = (msg: PublishRunContentFailure) => void; -export function isPublishRunContentFailure(arg: Events): - arg is PublishRestorePythonEnvFailure { - return arg.type === 'publish/runContent/failure'; +export type OnPublishRunContentFailureCallback = ( + msg: PublishRunContentFailure, +) => void; +export function isPublishRunContentFailure( + arg: Events, +): arg is PublishRestorePythonEnvFailure { + return arg.type === "publish/runContent/failure"; } export interface PublishSetVanityURLStart extends EventStreamMessage { - type: 'publish/setVanityURL/start', + type: "publish/setVanityURL/start"; data: { - localId: string, - } + localId: string; + }; } -export type OnPublishSetVanityURLStartCallback = (msg: PublishSetVanityURLStart) => void; -export function isPublishSetVanityURLStart(arg: Events): - arg is PublishSetVanityURLStart { - return arg.type === 'publish/setVanityURL/start'; +export type OnPublishSetVanityURLStartCallback = ( + msg: PublishSetVanityURLStart, +) => void; +export function isPublishSetVanityURLStart( + arg: Events, +): arg is PublishSetVanityURLStart { + return arg.type === "publish/setVanityURL/start"; } export interface PublishSetVanityURLLog extends EventStreamMessage { - type: 'publish/setVanityURL/log', + type: "publish/setVanityURL/log"; // structured data not guaranteed, use selective or generic queries // from data map } -export type OnPublishSetVanityURLLogCallback = (msg: PublishSetVanityURLLog) => void; -export function isPublishSetVanityURLLog(arg: Events): - arg is PublishSetVanityURLLog { - return arg.type === 'publish/setVanityURL/log'; +export type OnPublishSetVanityURLLogCallback = ( + msg: PublishSetVanityURLLog, +) => void; +export function isPublishSetVanityURLLog( + arg: Events, +): arg is PublishSetVanityURLLog { + return arg.type === "publish/setVanityURL/log"; } export interface PublishSetVanityURLSuccess extends EventStreamMessage { - type: 'publish/setVanityURL/success', + type: "publish/setVanityURL/success"; data: { - localId: string, - } + localId: string; + }; } -export type OnPublishSetVanityURLSuccessCallback = (msg: PublishSetVanityURLSuccess) => void; -export function isPublishSetVanityURLSuccess(arg: Events): - arg is PublishSetVanityURLSuccess { - return arg.type === 'publish/setVanityURL/success'; +export type OnPublishSetVanityURLSuccessCallback = ( + msg: PublishSetVanityURLSuccess, +) => void; +export function isPublishSetVanityURLSuccess( + arg: Events, +): arg is PublishSetVanityURLSuccess { + return arg.type === "publish/setVanityURL/success"; } export interface PublishSetVanityURLFailure extends EventStreamMessage { - type: 'publish/setVanityURL/failure', - error: string, // translated internally + type: "publish/setVanityURL/failure"; + error: string; // translated internally // structured data not guaranteed, use selective or generic queries // from data map } -export type OnPublishSetVanityURLFailureCallback = (msg: PublishSetVanityURLFailure) => void; -export function isPublishSetVanityURLFailure(arg: Events): - arg is PublishRestorePythonEnvFailure { - return arg.type === 'publish/setVanityURL/failure'; +export type OnPublishSetVanityURLFailureCallback = ( + msg: PublishSetVanityURLFailure, +) => void; +export function isPublishSetVanityURLFailure( + arg: Events, +): arg is PublishRestorePythonEnvFailure { + return arg.type === "publish/setVanityURL/failure"; } export interface PublishValidateDeploymentStart extends EventStreamMessage { - type: 'publish/validateDeployment/start', + type: "publish/validateDeployment/start"; data: { - localId: string, - url: string, - } + localId: string; + url: string; + }; } -export type OnPublishValidateDeploymentStartCallback = - (msg: PublishValidateDeploymentStart) => void; -export function isPublishValidateDeploymentStart(arg: Events): - arg is PublishValidateDeploymentStart { - return arg.type === 'publish/validateDeployment/start'; +export type OnPublishValidateDeploymentStartCallback = ( + msg: PublishValidateDeploymentStart, +) => void; +export function isPublishValidateDeploymentStart( + arg: Events, +): arg is PublishValidateDeploymentStart { + return arg.type === "publish/validateDeployment/start"; } export interface PublishValidateDeploymentLog extends EventStreamMessage { - type: 'publish/validateDeployment/log', + type: "publish/validateDeployment/log"; // structured data not guaranteed, use selective or generic queries // from data map } -export type OnPublishValidateDeploymentLogCallback = (msg: PublishValidateDeploymentLog) => void; -export function isPublishValidateDeploymentLog(arg: Events): - arg is PublishValidateDeploymentLog { - return arg.type === 'publish/validateDeployment/log'; +export type OnPublishValidateDeploymentLogCallback = ( + msg: PublishValidateDeploymentLog, +) => void; +export function isPublishValidateDeploymentLog( + arg: Events, +): arg is PublishValidateDeploymentLog { + return arg.type === "publish/validateDeployment/log"; } export interface PublishValidateDeploymentSuccess extends EventStreamMessage { - type: 'publish/validateDeployment/success', + type: "publish/validateDeployment/success"; data: { - localId: string, - } + localId: string; + }; } -export type OnPublishValidateDeploymentSuccessCallback = - (msg: PublishValidateDeploymentSuccess) => void; -export function isPublisValidateDeploymentSuccess(arg: Events): - arg is PublishValidateDeploymentSuccess { - return arg.type === 'publish/validateDeployment/success'; +export type OnPublishValidateDeploymentSuccessCallback = ( + msg: PublishValidateDeploymentSuccess, +) => void; +export function isPublisValidateDeploymentSuccess( + arg: Events, +): arg is PublishValidateDeploymentSuccess { + return arg.type === "publish/validateDeployment/success"; } export interface PublishValidateDeploymentFailure extends EventStreamMessage { - type: 'publish/validateDeployment/failure', - error: string, // translated internally + type: "publish/validateDeployment/failure"; + error: string; // translated internally // structured data not guaranteed, use selective or generic queries // from data map } -export type OnPublishValidateDeploymentFailureCallback = - (msg: PublishValidateDeploymentFailure) => void; -export function isPublishValidateDeploymentFailure(arg: Events): - arg is PublishValidateDeploymentFailure { - return arg.type === 'publish/validateDeployment/failure'; +export type OnPublishValidateDeploymentFailureCallback = ( + msg: PublishValidateDeploymentFailure, +) => void; +export function isPublishValidateDeploymentFailure( + arg: Events, +): arg is PublishValidateDeploymentFailure { + return arg.type === "publish/validateDeployment/failure"; } export interface PublishSuccess extends EventStreamMessage { - type: 'publish/success', + type: "publish/success"; data: { - localId: string, - contentId: string, - dashboardUrl: string, - directUrl: string, - serverUrl: string, - } + localId: string; + contentId: string; + dashboardUrl: string; + directUrl: string; + serverUrl: string; + }; } export type OnPublishSuccessCallback = (msg: PublishSuccess) => void; export function isPublishSuccess(arg: Events): arg is PublishSuccess { - return arg.type === 'publish/success'; + return arg.type === "publish/success"; } export interface PublishFailure extends EventStreamMessage { - type: 'publish/failure', + type: "publish/failure"; data: { - dashboardUrl: string, - url: string, + dashboardUrl: string; + url: string; // and other non-defined attributes - }, - error: string, // translated internally + }; + error: string; // translated internally } export type OnPublishFailureCallback = (msg: PublishFailure) => void; -export function isPublishFailure(arg: Events): - arg is PublishFailure { - return arg.type === 'publish/failure'; +export function isPublishFailure(arg: Events): arg is PublishFailure { + return arg.type === "publish/failure"; } // Events are a union type of our base and our extended interfaces export type Events = - EventStreamMessage | - AgentLog | - ErrorsSse | - ErrorsOpen | - ErrorsUnknownEvent | - OpenSse | - PublishStart | - PublishCreateBundleStart | - PublishCreateBundleLog | - PublishCreateBundleSuccess | - PublishCreateBundleFailure | - PublishCreateDeploymentStart | - PublishCreateDeploymentSuccess | - PublishCreateDeploymentFailure | - PublishUploadBundleStart | - PublishUploadBundleSuccess | - PublishUploadBundleFailure | - PublishDeployBundleStart | - PublishDeployBundleSuccess | - PublishDeployBundleFailure | - PublishRestorePythonEnvStart | - PublishRestorePythonEnvLog | - PublishRestorePythonEnvSuccess | - PublishRestorePythonEnvFailure | - PublishRunContentStart | - PublishRunContentLog | - PublishRunContentSuccess | - PublishRunContentFailure | - PublishSuccess | - PublishFailure; + | EventStreamMessage + | AgentLog + | ErrorsSse + | ErrorsOpen + | ErrorsUnknownEvent + | OpenSse + | PublishStart + | PublishCreateBundleStart + | PublishCreateBundleLog + | PublishCreateBundleSuccess + | PublishCreateBundleFailure + | PublishCreateDeploymentStart + | PublishCreateDeploymentSuccess + | PublishCreateDeploymentFailure + | PublishUploadBundleStart + | PublishUploadBundleSuccess + | PublishUploadBundleFailure + | PublishDeployBundleStart + | PublishDeployBundleSuccess + | PublishDeployBundleFailure + | PublishRestorePythonEnvStart + | PublishRestorePythonEnvLog + | PublishRestorePythonEnvSuccess + | PublishRestorePythonEnvFailure + | PublishRunContentStart + | PublishRunContentLog + | PublishRunContentSuccess + | PublishRunContentFailure + | PublishSuccess + | PublishFailure; diff --git a/extensions/vscode/src/api/types/files.ts b/extensions/vscode/src/api/types/files.ts index abb54796d..06817d9b9 100644 --- a/extensions/vscode/src/api/types/files.ts +++ b/extensions/vscode/src/api/types/files.ts @@ -1,33 +1,33 @@ // Copyright (C) 2023 by Posit Software, PBC. export enum DeploymentFileType { - REGULAR = 'REGULAR', - DIRECTORY = 'DIR', + REGULAR = "REGULAR", + DIRECTORY = "DIR", } export type DeploymentFile = { - id: string - fileType: DeploymentFileType - base: string - exclusion: ExclusionMatch | null - files: DeploymentFile[] - isDir: boolean - isEntrypoint: boolean - isFile: boolean - modifiedDatetime: string - rel: string - size: number - abs: string -} + id: string; + fileType: DeploymentFileType; + base: string; + exclusion: ExclusionMatch | null; + files: DeploymentFile[]; + isDir: boolean; + isEntrypoint: boolean; + isFile: boolean; + modifiedDatetime: string; + rel: string; + size: number; + abs: string; +}; export enum ExclusionMatchSource { - FILE = 'file', - BUILT_IN = 'built-in', + FILE = "file", + BUILT_IN = "built-in", } export type ExclusionMatch = { - source: ExclusionMatchSource - pattern: string - filePath: string - line: number -} + source: ExclusionMatchSource; + pattern: string; + filePath: string; + line: number; +}; diff --git a/extensions/vscode/src/commands.ts b/extensions/vscode/src/commands.ts index c6a269812..a0ada708e 100644 --- a/extensions/vscode/src/commands.ts +++ b/extensions/vscode/src/commands.ts @@ -1,20 +1,27 @@ // Copyright (C) 2024 by Posit Software, PBC. -import * as fs from 'fs/promises'; -import * as path from 'path'; -import * as vscode from 'vscode'; +import * as fs from "fs/promises"; +import * as path from "path"; +import * as vscode from "vscode"; -import { HOST } from '.'; +import { HOST } from "."; -const CONFIG_KEY = 'publisher.executable.path'; +const CONFIG_KEY = "publisher.executable.path"; -export const create = async (context: vscode.ExtensionContext, path: string, port: number, subcommand: string = "ui"): Promise<[string, string[]]> => { +export const create = async ( + context: vscode.ExtensionContext, + path: string, + port: number, + subcommand: string = "ui", +): Promise<[string, string[]]> => { const executable = await getExecutableBinary(context); - return [executable, [subcommand, '-v', `--listen=${HOST}:${port}`, path]]; + return [executable, [subcommand, "-v", `--listen=${HOST}:${port}`, path]]; }; -const getExecutableBinary = async (context: vscode.ExtensionContext): Promise => { - const configuration = vscode.workspace.getConfiguration('posit'); +const getExecutableBinary = async ( + context: vscode.ExtensionContext, +): Promise => { + const configuration = vscode.workspace.getConfiguration("posit"); let executable: string | undefined = configuration.get(CONFIG_KEY); if (executable) { try { @@ -36,7 +43,7 @@ const getExecutableBinary = async (context: vscode.ExtensionContext): Promise { - const choice = await window.showInformationMessage(message, { - modal: true, - }, affirmativeItem); +async function confirm( + message: string, + affirmativeItem: MessageItem, +): Promise { + const choice = await window.showInformationMessage( + message, + { + modal: true, + }, + affirmativeItem, + ); return choice === affirmativeItem; } diff --git a/extensions/vscode/src/events.ts b/extensions/vscode/src/events.ts index 95488c234..7ea70f25c 100644 --- a/extensions/vscode/src/events.ts +++ b/extensions/vscode/src/events.ts @@ -2,10 +2,10 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import { Disposable } from 'vscode'; +import { Disposable } from "vscode"; -import * as EventSource from 'eventsource'; -import { Readable } from 'stream'; +import * as EventSource from "eventsource"; +import { Readable } from "stream"; export type EventStreamMessage = { type: string; @@ -18,25 +18,25 @@ export type EventStreamRegistration = (message: EventStreamMessage) => void; export type UnregisterCallback = { unregister: () => void }; export function displayEventStreamMessage(msg: EventStreamMessage): string { - if (msg.type === 'publish/checkCapabilities/log') { + if (msg.type === "publish/checkCapabilities/log") { if (msg.data.username) { return `${msg.data.message}: username ${msg.data.username}, email ${msg.data.email}`; } } - if (msg.type === 'publish/createNewDeployment/success') { + if (msg.type === "publish/createNewDeployment/success") { return `Created new deployment as ${msg.data.saveName}`; } - if (msg.type === 'publish/createBundle/success') { + if (msg.type === "publish/createBundle/success") { return `Prepared file archive: ${msg.data.filename}`; } - if (msg.type === 'publish/createDeployment/start') { + if (msg.type === "publish/createDeployment/start") { return `Updating existing deployment with ID ${msg.data.contentId}`; } - if (msg.type === 'publish/createBundle/log') { + if (msg.type === "publish/createBundle/log") { if (msg.data.sourceDir) { return `${msg.data.message} ${msg.data.sourceDir}`; } @@ -50,15 +50,15 @@ export function displayEventStreamMessage(msg: EventStreamMessage): string { } } - if (msg.type === 'publish/restorePythonEnv/log') { + if (msg.type === "publish/restorePythonEnv/log") { return `${msg.data.message}`; } - if ( msg.type === 'publish/setVanityURL/log') { + if (msg.type === "publish/setVanityURL/log") { return `${msg.data.message} ${msg.data.path}`; } - if (msg.type === 'publish/success') { + if (msg.type === "publish/success") { return `Successfully deployed at ${msg.data.dashboardUrl}`; } @@ -87,9 +87,11 @@ export class EventStream extends Readable implements Disposable { constructor(port: number) { super(); // Create a new EventSource instance to connect to the event stream - this.eventSource = new EventSource(`http://127.0.0.1:${port}/api/events?stream=messages`); + this.eventSource = new EventSource( + `http://127.0.0.1:${port}/api/events?stream=messages`, + ); // Listen for 'message' events from the EventSource - this.eventSource.addEventListener('message', (event) => { + this.eventSource.addEventListener("message", (event) => { // Parse the event data and convert keys to camel case const message = convertKeysToCamelCase(JSON.parse(event.data)); @@ -99,7 +101,7 @@ export class EventStream extends Readable implements Disposable { // Add the message to the messages array this.messages.push(message); // Emit a 'message' event with the message as the payload - this.emit('message', message); + this.emit("message", message); // Invoke the registered callbacks for the message type this.invokeCallbacks(message); }); @@ -117,7 +119,10 @@ export class EventStream extends Readable implements Disposable { * @param callback The callback function to be invoked when the event occurs. * @returns An object with an `unregister` method that can be used to remove the callback. */ - public register(type: string, callback: EventStreamRegistration): UnregisterCallback { + public register( + type: string, + callback: EventStreamRegistration, + ): UnregisterCallback { if (!this.callbacks.has(type)) { this.callbacks.set(type, []); } @@ -129,9 +134,9 @@ export class EventStream extends Readable implements Disposable { // Remove the callback from the callbacks array for the specified event type this.callbacks.set( type, - this.callbacks.get(type)?.filter(cb => cb !== callback) || [] + this.callbacks.get(type)?.filter((cb) => cb !== callback) || [], ); - } + }, }; } @@ -139,7 +144,7 @@ export class EventStream extends Readable implements Disposable { const type = message.type; if (this.callbacks.has(type)) { // Invoke all the callbacks for the specified event type with the message as the argument - this.callbacks.get(type)?.forEach(callback => callback(message)); + this.callbacks.get(type)?.forEach((callback) => callback(message)); } } } @@ -150,13 +155,13 @@ export class EventStream extends Readable implements Disposable { * @returns The object with camel case keys. */ const convertKeysToCamelCase = (object: any): any => { - if (typeof object !== 'object' || object === null) { + if (typeof object !== "object" || object === null) { return object; } if (Array.isArray(object)) { // Recursively convert keys for each item in the array - return object.map(item => convertKeysToCamelCase(item)); + return object.map((item) => convertKeysToCamelCase(item)); } const newObject: any = {}; diff --git a/extensions/vscode/src/extension.ts b/extensions/vscode/src/extension.ts index d5427317f..7a82e222d 100644 --- a/extensions/vscode/src/extension.ts +++ b/extensions/vscode/src/extension.ts @@ -2,50 +2,54 @@ // The module 'vscode' contains the VS Code extensibility API // Import the module and reference it with the alias vscode in your code below -import * as vscode from 'vscode'; - -import * as ports from './ports'; -import { Service } from './services'; -import { ProjectTreeDataProvider } from './views/project'; -import { DeploymentsTreeDataProvider } from './views/deployments'; -import { ConfigurationsTreeDataProvider } from './views/configurations'; -import { FilesTreeDataProvider } from './views/files'; -import { RequirementsTreeDataProvider } from './views/requirements'; -import { CredentialsTreeDataProvider } from './views/credentials'; -import { HelpAndFeedbackTreeDataProvider } from './views/helpAndFeedback'; -import { LogsTreeDataProvider } from './views/logs'; -import { EventStream } from './events'; - -const STATE_CONTEXT = 'posit.publish.state'; -const MISSING_CONTEXT = 'posit.publish.missing'; +import * as vscode from "vscode"; + +import * as ports from "./ports"; +import { Service } from "./services"; +import { ProjectTreeDataProvider } from "./views/project"; +import { DeploymentsTreeDataProvider } from "./views/deployments"; +import { ConfigurationsTreeDataProvider } from "./views/configurations"; +import { FilesTreeDataProvider } from "./views/files"; +import { RequirementsTreeDataProvider } from "./views/requirements"; +import { CredentialsTreeDataProvider } from "./views/credentials"; +import { HelpAndFeedbackTreeDataProvider } from "./views/helpAndFeedback"; +import { LogsTreeDataProvider } from "./views/logs"; +import { EventStream } from "./events"; + +const STATE_CONTEXT = "posit.publish.state"; +const MISSING_CONTEXT = "posit.publish.missing"; enum PositPublishState { - initialized = 'initialized', - uninitialized = 'uninitialized', + initialized = "initialized", + uninitialized = "uninitialized", } // Once the extension is activate, hang on to the service so that we can stop it on deactivation. let service: Service; -async function isMissingPublishDirs(folder: vscode.WorkspaceFolder): Promise { +async function isMissingPublishDirs( + folder: vscode.WorkspaceFolder, +): Promise { try { const stats = await Promise.all([ - vscode.workspace.fs.stat(vscode.Uri.joinPath(folder.uri, '.posit')), - vscode.workspace.fs.stat(vscode.Uri.joinPath(folder.uri, '.posit/publish')) + vscode.workspace.fs.stat(vscode.Uri.joinPath(folder.uri, ".posit")), + vscode.workspace.fs.stat( + vscode.Uri.joinPath(folder.uri, ".posit/publish"), + ), ]); - return !stats.every(stat => stat.type === vscode.FileType.Directory); + return !stats.every((stat) => stat.type === vscode.FileType.Directory); } catch { return true; } } function setMissingContext(context: boolean) { - vscode.commands.executeCommand('setContext', MISSING_CONTEXT, context); + vscode.commands.executeCommand("setContext", MISSING_CONTEXT, context); } function setStateContext(context: PositPublishState) { - vscode.commands.executeCommand('setContext', STATE_CONTEXT, context); + vscode.commands.executeCommand("setContext", STATE_CONTEXT, context); } // This method is called when your extension is activated @@ -53,14 +57,17 @@ function setStateContext(context: PositPublishState) { export async function activate(context: vscode.ExtensionContext) { setStateContext(PositPublishState.uninitialized); - if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length) { + if ( + vscode.workspace.workspaceFolders && + vscode.workspace.workspaceFolders.length + ) { const folder = vscode.workspace.workspaceFolders[0]; const watcher = vscode.workspace.createFileSystemWatcher( - new vscode.RelativePattern(folder, '{.posit,.posit/publish}'), + new vscode.RelativePattern(folder, "{.posit,.posit/publish}"), false, true, - false + false, ); watcher.onDidCreate(async () => { setMissingContext(await isMissingPublishDirs(folder)); diff --git a/extensions/vscode/src/multiStepInputs/addDeployment.ts b/extensions/vscode/src/multiStepInputs/addDeployment.ts index 95edb0cbb..658184dbd 100644 --- a/extensions/vscode/src/multiStepInputs/addDeployment.ts +++ b/extensions/vscode/src/multiStepInputs/addDeployment.ts @@ -1,15 +1,24 @@ // Copyright (C) 2024 by Posit Software, PBC. -import { MultiStepInput, MultiStepState, isQuickPickItem } from './multiStepHelper'; +import { + MultiStepInput, + MultiStepState, + isQuickPickItem, +} from "./multiStepHelper"; -import { InputBoxValidationSeverity, QuickPickItem, ThemeIcon, window } from 'vscode'; +import { + InputBoxValidationSeverity, + QuickPickItem, + ThemeIcon, + window, +} from "vscode"; -import { AccountAuthType, useApi } from '../api'; -import { getSummaryStringFromError } from '../utils/errors'; -import { uniqueDeploymentName, untitledDeploymentName } from '../utils/names'; -import { deployProject } from '../views/deployProgress'; -import { EventStream } from '../events'; -import { isValidFilename } from '../utils/files'; +import { AccountAuthType, useApi } from "../api"; +import { getSummaryStringFromError } from "../utils/errors"; +import { uniqueDeploymentName, untitledDeploymentName } from "../utils/names"; +import { deployProject } from "../views/deployProgress"; +import { EventStream } from "../events"; +import { isValidFilename } from "../utils/files"; export async function addDeployment(stream: EventStream) { const api = useApi(); @@ -25,19 +34,22 @@ export async function addDeployment(stream: EventStream) { try { const response = await api.accounts.getAll(); const accounts = response.data.accounts; - accountListItems = accounts - .map(account => ({ - iconPath: new ThemeIcon('account'), - label: account.name, - description: account.source, - detail: account.authType === AccountAuthType.API_KEY - ? 'Using API Key' + accountListItems = accounts.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('addDeployment, accounts.getAll', error); + const summary = getSummaryStringFromError( + "addDeployment, accounts.getAll", + error, + ); window.showInformationMessage( - `Unable to continue with no credentials. ${summary}` + `Unable to continue with no credentials. ${summary}`, ); return; } @@ -45,15 +57,18 @@ export async function addDeployment(stream: EventStream) { try { const response = await api.configurations.getAll(); const configurations = response.data; - configFileListItems = configurations.map(configuration => ({ - iconPath: new ThemeIcon('file-code'), + configFileListItems = configurations.map((configuration) => ({ + iconPath: new ThemeIcon("file-code"), label: configuration.configurationName, detail: configuration.configurationPath, })); } catch (error: unknown) { - const summary = getSummaryStringFromError('addDeployment, configurations.getAll', error); + const summary = getSummaryStringFromError( + "addDeployment, configurations.getAll", + error, + ); window.showInformationMessage( - `Unable to continue with no configurations. ${summary}` + `Unable to continue with no configurations. ${summary}`, ); return; } @@ -61,11 +76,16 @@ export async function addDeployment(stream: EventStream) { try { const response = await api.deployments.getAll(); const deploymentList = response.data; - deploymentNames = deploymentList.map(deployment => deployment.deploymentName); + deploymentNames = deploymentList.map( + (deployment) => deployment.deploymentName, + ); } catch (error: unknown) { - const summary = getSummaryStringFromError('addDeployment, deployments.getAll', error); + const summary = getSummaryStringFromError( + "addDeployment, deployments.getAll", + error, + ); window.showInformationMessage( - `Unable to continue due to deployment error. ${summary}` + `Unable to continue due to deployment error. ${summary}`, ); return; } @@ -87,7 +107,7 @@ export async function addDeployment(stream: EventStream) { // *************************************************************** async function collectInputs() { const state: MultiStepState = { - title: 'Deploy Your Project to a New Location', + title: "Deploy Your Project to a New Location", step: -1, lastStep: 0, totalSteps: -1, @@ -113,19 +133,17 @@ export async function addDeployment(stream: EventStream) { // start the progression through the steps - await MultiStepInput.run(input => inputDeploymentName(input, state)); + await MultiStepInput.run((input) => inputDeploymentName(input, state)); return state as MultiStepState; } - - // *************************************************************** // Step #1: // Name the deployment // *************************************************************** async function inputDeploymentName( input: MultiStepInput, - state: MultiStepState + state: MultiStepState, ) { state.step = state.lastStep + 1; @@ -133,12 +151,15 @@ export async function addDeployment(stream: EventStream) { 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', + 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 || + if ( + value.length < 3 || !uniqueDeploymentName(value, deploymentNames) || !isValidFilename(value) ) { @@ -160,10 +181,7 @@ export async function addDeployment(stream: EventStream) { // Step #2: // Select the credentials to be used // *************************************************************** - async function pickCredentials( - input: MultiStepInput, - state: MultiStepState, - ) { + async function pickCredentials(input: MultiStepInput, state: MultiStepState) { // skip if we only have one choice. if (accountListItems.length > 1) { const thisStepNumber = state.lastStep + 1; @@ -171,9 +189,12 @@ export async function addDeployment(stream: EventStream) { title: state.title, step: thisStepNumber, totalSteps: state.totalSteps, - placeholder: 'Select the credential you want to use to deploy', + placeholder: "Select the credential you want to use to deploy", items: accountListItems, - activeItem: typeof state.data.credentialName !== 'string' ? state.data.credentialName : undefined, + activeItem: + typeof state.data.credentialName !== "string" + ? state.data.credentialName + : undefined, buttons: [], shouldResume: () => Promise.resolve(false), }); @@ -189,33 +210,33 @@ export async function addDeployment(stream: EventStream) { // Step #3: // Does the user want to continue through into deploying the project? // *************************************************************** - async function promptToDeploy( - input: MultiStepInput, - state: MultiStepState, - ) { + async function promptToDeploy(input: MultiStepInput, state: MultiStepState) { const thisStepNumber = state.lastStep + 1; const pick = await input.showQuickPick({ title: state.title, step: thisStepNumber, totalSteps: state.totalSteps, - placeholder: 'Do you wish to initiate the deployment at this time?', + placeholder: "Do you wish to initiate the deployment at this time?", items: [ { - label: 'Yes', - description: 'Proceed with deployment' + label: "Yes", + description: "Proceed with deployment", }, { - label: 'No', - description: 'Just save my deployment for use at a later time', - } + label: "No", + description: "Just save my deployment for use at a later time", + }, ], - activeItem: typeof state.data.promptToDeploy !== 'string' ? state.data.promptToDeploy : undefined, + activeItem: + typeof state.data.promptToDeploy !== "string" + ? state.data.promptToDeploy + : undefined, buttons: [], shouldResume: () => Promise.resolve(false), }); state.data.promptToDeploy = pick; state.lastStep = thisStepNumber; - if (state.data.promptToDeploy.label === 'Yes') { + if (state.data.promptToDeploy.label === "Yes") { return (input: MultiStepInput) => inputConfigFileSelection(input, state); } return undefined; @@ -236,9 +257,12 @@ export async function addDeployment(stream: EventStream) { title: state.title, step: thisStepNumber, totalSteps: state.totalSteps, - placeholder: 'Select the config file you wish to deploy with', + placeholder: "Select the config file you wish to deploy with", items: configFileListItems, - activeItem: typeof state.data.configFile !== 'string' ? state.data.configFile : undefined, + activeItem: + typeof state.data.configFile !== "string" + ? state.data.configFile + : undefined, buttons: [], shouldResume: () => Promise.resolve(false), }); @@ -252,7 +276,7 @@ export async function addDeployment(stream: EventStream) { // *************************************************************** // Kick off the input collection // and await until it completes. - // This is a promise which returns the state data used to + // This is a promise which returns the state data used to // collect the info. // *************************************************************** const state = await collectInputs(); @@ -265,7 +289,7 @@ export async function addDeployment(stream: EventStream) { state.data.credentialName === undefined || state.data.promptToDeploy === undefined || // have to add type guards here to eliminate the variability - typeof (state.data.deploymentName) !== 'string' || + typeof state.data.deploymentName !== "string" || !isQuickPickItem(state.data.credentialName) || !isQuickPickItem(state.data.promptToDeploy) ) { @@ -279,15 +303,18 @@ export async function addDeployment(stream: EventStream) { state.data.deploymentName, ); } catch (error: unknown) { - const summary = getSummaryStringFromError('addDeployment, createNew', error); + const summary = getSummaryStringFromError( + "addDeployment, createNew", + error, + ); window.showInformationMessage( - `Failed to create pre-deployment. ${summary}` + `Failed to create pre-deployment. ${summary}`, ); return; } // Should we deploy and did we get an answer for the config file? if ( - state.data.promptToDeploy.label === 'Yes' && + state.data.promptToDeploy.label === "Yes" && state.data.configFile !== undefined && isQuickPickItem(state.data.configFile) ) { @@ -299,16 +326,12 @@ export async function addDeployment(stream: EventStream) { ); deployProject(response.data.localId, stream); } catch (error: unknown) { - const summary = getSummaryStringFromError('addDeployment, deploy', error); - window.showInformationMessage( - `Failed to deploy . ${summary}` - ); + const summary = getSummaryStringFromError("addDeployment, deploy", error); + window.showInformationMessage(`Failed to deploy . ${summary}`); return; } } else { // no, they didn't want us to. - window.showInformationMessage( - `Skipping deployment of this project` - ); + window.showInformationMessage(`Skipping deployment of this project`); } } diff --git a/extensions/vscode/src/multiStepInputs/deployProject.ts b/extensions/vscode/src/multiStepInputs/deployProject.ts index c63325ce7..42e6077de 100644 --- a/extensions/vscode/src/multiStepInputs/deployProject.ts +++ b/extensions/vscode/src/multiStepInputs/deployProject.ts @@ -1,15 +1,22 @@ // Copyright (C) 2024 by Posit Software, PBC. -import { MultiStepInput, MultiStepState, isQuickPickItem } from './multiStepHelper'; - -import { QuickPickItem, ThemeIcon, window } from 'vscode'; - -import { AccountAuthType, PreDeployment, Deployment, useApi } from '../api'; -import { getSummaryStringFromError } from '../utils/errors'; -import { deployProject } from '../views/deployProgress'; -import { EventStream } from '../events'; - -export async function publishDeployment(deployment: PreDeployment | Deployment, stream: EventStream) { +import { + MultiStepInput, + MultiStepState, + isQuickPickItem, +} from "./multiStepHelper"; + +import { QuickPickItem, ThemeIcon, window } from "vscode"; + +import { AccountAuthType, PreDeployment, Deployment, useApi } from "../api"; +import { getSummaryStringFromError } from "../utils/errors"; +import { deployProject } from "../views/deployProgress"; +import { EventStream } from "../events"; + +export async function publishDeployment( + deployment: PreDeployment | Deployment, + stream: EventStream, +) { const api = useApi(); // *************************************************************** @@ -24,25 +31,29 @@ export async function publishDeployment(deployment: PreDeployment | Deployment, const accounts = response.data.accounts; // account list is filtered to match the deployment being published accountListItems = accounts - .filter(account => (account.url === deployment.serverUrl)) - .map(account => ({ - iconPath: new ThemeIcon('account'), + .filter((account) => account.url === deployment.serverUrl) + .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}`, + detail: + account.authType === AccountAuthType.API_KEY + ? "Using API Key" + : `Using Token Auth for ${account.accountName}`, })); } catch (error: unknown) { - const summary = getSummaryStringFromError('publishDeployment, accounts.getAll', error); + const summary = getSummaryStringFromError( + "publishDeployment, accounts.getAll", + error, + ); window.showInformationMessage( - `Unable to continue with no credentials. ${summary}` + `Unable to continue with no credentials. ${summary}`, ); return; } if (accountListItems.length === 0) { window.showInformationMessage( - `Unable to continue with no maching credentials for deployment URL: ${deployment.serverUrl}` + `Unable to continue with no maching credentials for deployment URL: ${deployment.serverUrl}`, ); return; } @@ -50,15 +61,18 @@ export async function publishDeployment(deployment: PreDeployment | Deployment, try { const response = await api.configurations.getAll(); const configurations = response.data; - configFileListItems = configurations.map(configuration => ({ - iconPath: new ThemeIcon('file-code'), + configFileListItems = configurations.map((configuration) => ({ + iconPath: new ThemeIcon("file-code"), label: configuration.configurationName, detail: configuration.configurationPath, })); } catch (error: unknown) { - const summary = getSummaryStringFromError('publishDeployment, configurations.getAll', error); + const summary = getSummaryStringFromError( + "publishDeployment, configurations.getAll", + error, + ); window.showInformationMessage( - `Unable to continue with no configurations. ${summary}` + `Unable to continue with no configurations. ${summary}`, ); return; } @@ -78,7 +92,7 @@ export async function publishDeployment(deployment: PreDeployment | Deployment, // *************************************************************** async function collectInputs() { const state: MultiStepState = { - title: 'Deploy Your Project', + title: "Deploy Your Project", step: -1, lastStep: 0, totalSteps: -1, @@ -101,7 +115,7 @@ export async function publishDeployment(deployment: PreDeployment | Deployment, } state.totalSteps = totalSteps; - await MultiStepInput.run(input => pickCredentials(input, state)); + await MultiStepInput.run((input) => pickCredentials(input, state)); return state; } @@ -109,10 +123,7 @@ export async function publishDeployment(deployment: PreDeployment | Deployment, // Step #1: // Select the credentials to be used // *************************************************************** - async function pickCredentials( - input: MultiStepInput, - state: MultiStepState, - ) { + async function pickCredentials(input: MultiStepInput, state: MultiStepState) { // skip if we only have one choice. if (accountListItems.length > 1) { const thisStepNumber = state.lastStep + 1; @@ -120,9 +131,12 @@ export async function publishDeployment(deployment: PreDeployment | Deployment, title: state.title, step: thisStepNumber, totalSteps: state.totalSteps, - placeholder: 'Select the credential you want to use to deploy', + placeholder: "Select the credential you want to use to deploy", items: accountListItems, - activeItem: typeof state.data.credentialName !== 'string' ? state.data.credentialName : undefined, + activeItem: + typeof state.data.credentialName !== "string" + ? state.data.credentialName + : undefined, buttons: [], shouldResume: () => Promise.resolve(false), }); @@ -149,9 +163,12 @@ export async function publishDeployment(deployment: PreDeployment | Deployment, title: state.title, step: thisStepNumber, totalSteps: state.totalSteps, - placeholder: 'Select the config file you wish to deploy with', + placeholder: "Select the config file you wish to deploy with", items: configFileListItems, - activeItem: typeof state.data.configFile !== 'string' ? state.data.configFile : undefined, + activeItem: + typeof state.data.configFile !== "string" + ? state.data.configFile + : undefined, buttons: [], shouldResume: () => Promise.resolve(false), }); @@ -166,7 +183,7 @@ export async function publishDeployment(deployment: PreDeployment | Deployment, // *************************************************************** // Kick off the input collection // and await until it completes. - // This is a promise which returns the state data used to + // This is a promise which returns the state data used to // collect the info. // *************************************************************** @@ -194,10 +211,11 @@ export async function publishDeployment(deployment: PreDeployment | Deployment, ); deployProject(response.data.localId, stream); } catch (error: unknown) { - const summary = getSummaryStringFromError('publishDeployment, deploy', error); - window.showInformationMessage( - `Failed to deploy . ${summary}` + const summary = getSummaryStringFromError( + "publishDeployment, deploy", + error, ); + window.showInformationMessage(`Failed to deploy . ${summary}`); return; } } diff --git a/extensions/vscode/src/multiStepInputs/multiStepHelper.ts b/extensions/vscode/src/multiStepInputs/multiStepHelper.ts index 9c8145775..14bd01c5d 100644 --- a/extensions/vscode/src/multiStepInputs/multiStepHelper.ts +++ b/extensions/vscode/src/multiStepInputs/multiStepHelper.ts @@ -1,6 +1,14 @@ // Copyright (C) 2024 by Posit Software, PBC. -import { QuickPickItem, window, Disposable, QuickInputButton, QuickInput, QuickInputButtons, InputBoxValidationMessage } from 'vscode'; +import { + QuickPickItem, + window, + Disposable, + QuickInputButton, + QuickInput, + QuickInputButtons, + InputBoxValidationMessage, +} from "vscode"; class InputFlowAction { static back = new InputFlowAction(); @@ -8,10 +16,8 @@ class InputFlowAction { static resume = new InputFlowAction(); } -export function isQuickPickItem( - d: QuickPickItem | string -): d is QuickPickItem { - return typeof d !== 'string'; +export function isQuickPickItem(d: QuickPickItem | string): d is QuickPickItem { + return typeof d !== "string"; } export interface MultiStepState { @@ -19,7 +25,7 @@ export interface MultiStepState { step: number; lastStep: number; totalSteps: number; - data: Record + data: Record; } type InputStep = (input: MultiStepInput) => Thenable; @@ -42,7 +48,9 @@ interface InputBoxParameters { totalSteps: number; value: string; prompt: string; - validate: (value: string) => Promise; + validate: ( + value: string, + ) => Promise; buttons?: QuickInputButton[]; ignoreFocusOut?: boolean; placeholder?: string; @@ -50,7 +58,6 @@ interface InputBoxParameters { } export class MultiStepInput { - // These were templatized: static async run(start: InputStep) { static async run(start: InputStep) { const input = new MultiStepInput(); @@ -89,10 +96,25 @@ export class MultiStepInput { } } - async showQuickPick>({ title, step, totalSteps, items, activeItem, ignoreFocusOut, placeholder, buttons, shouldResume }: P) { + async showQuickPick< + T extends QuickPickItem, + P extends QuickPickParameters, + >({ + title, + step, + totalSteps, + items, + activeItem, + ignoreFocusOut, + placeholder, + buttons, + shouldResume, + }: P) { const disposables: Disposable[] = []; try { - return await new Promise((resolve, reject) => { + return await new Promise< + T | (P extends { buttons: (infer I)[] } ? I : never) + >((resolve, reject) => { const input = window.createQuickPick(); input.title = title; input.step = step; @@ -105,23 +127,26 @@ export class MultiStepInput { } input.buttons = [ ...(this.steps.length > 1 ? [QuickInputButtons.Back] : []), - ...(buttons || []) + ...(buttons || []), ]; disposables.push( - input.onDidTriggerButton(item => { + input.onDidTriggerButton((item) => { if (item === QuickInputButtons.Back) { reject(InputFlowAction.back); } else { resolve(item); } }), - input.onDidChangeSelection(items => resolve(items[0])), + input.onDidChangeSelection((items) => resolve(items[0])), input.onDidHide(() => { (async () => { - reject(shouldResume && await shouldResume() ? InputFlowAction.resume : InputFlowAction.cancel); - })() - .catch(reject); - }) + reject( + shouldResume && (await shouldResume()) + ? InputFlowAction.resume + : InputFlowAction.cancel, + ); + })().catch(reject); + }), ); if (this.current) { this.current.dispose(); @@ -130,29 +155,42 @@ export class MultiStepInput { this.current.show(); }); } finally { - disposables.forEach(d => d.dispose()); + disposables.forEach((d) => d.dispose()); } } - async showInputBox

({ title, step, totalSteps, value, prompt, validate, buttons, ignoreFocusOut, placeholder, shouldResume }: P) { + async showInputBox

({ + title, + step, + totalSteps, + value, + prompt, + validate, + buttons, + ignoreFocusOut, + placeholder, + shouldResume, + }: P) { const disposables: Disposable[] = []; try { - return await new Promise((resolve, reject) => { + return await new Promise< + string | (P extends { buttons: (infer I)[] } ? I : never) + >((resolve, reject) => { const input = window.createInputBox(); input.title = title; input.step = step; input.totalSteps = totalSteps; - input.value = value || ''; + input.value = value || ""; input.prompt = prompt; input.ignoreFocusOut = ignoreFocusOut ?? false; input.placeholder = placeholder; input.buttons = [ ...(this.steps.length > 1 ? [QuickInputButtons.Back] : []), - ...(buttons || []) + ...(buttons || []), ]; - let validating = validate(''); + let validating = validate(""); disposables.push( - input.onDidTriggerButton(item => { + input.onDidTriggerButton((item) => { if (item === QuickInputButtons.Back) { reject(InputFlowAction.back); } else { @@ -169,7 +207,7 @@ export class MultiStepInput { input.enabled = true; input.busy = false; }), - input.onDidChangeValue(async text => { + input.onDidChangeValue(async (text) => { const current = validate(text); validating = current; const validationMessage = await current; @@ -179,10 +217,13 @@ export class MultiStepInput { }), input.onDidHide(() => { (async () => { - reject(shouldResume && await shouldResume() ? InputFlowAction.resume : InputFlowAction.cancel); - })() - .catch(reject); - }) + reject( + shouldResume && (await shouldResume()) + ? InputFlowAction.resume + : InputFlowAction.cancel, + ); + })().catch(reject); + }), ); if (this.current) { this.current.dispose(); @@ -191,7 +232,7 @@ export class MultiStepInput { this.current.show(); }); } finally { - disposables.forEach(d => d.dispose()); + disposables.forEach((d) => d.dispose()); } } } diff --git a/extensions/vscode/src/panels.ts b/extensions/vscode/src/panels.ts index 1a10429d0..960233cfd 100644 --- a/extensions/vscode/src/panels.ts +++ b/extensions/vscode/src/panels.ts @@ -1,11 +1,10 @@ // Copyright (C) 2024 by Posit Software, PBC. -import * as vscode from 'vscode'; +import * as vscode from "vscode"; const DEFAULT_COLUMN = vscode.ViewColumn.Beside; export class Panel { - private readonly url: string; private column: vscode.ViewColumn = DEFAULT_COLUMN; @@ -24,14 +23,14 @@ export class Panel { // initialize panel this.panel = vscode.window.createWebviewPanel( - 'posit.publisher', - 'Posit Publisher', + "posit.publisher", + "Posit Publisher", this.column, { enableScripts: true, enableForms: true, retainContextWhenHidden: true, - } + }, ); // set html content @@ -41,15 +40,17 @@ export class Panel { // listen for messages this.panel.webview.onDidReceiveMessage( - message => { + (message) => { switch (message.command) { - case 'reload-webview': - vscode.commands.executeCommand('workbench.action.webview.reloadWebviewAction'); + case "reload-webview": + vscode.commands.executeCommand( + "workbench.action.webview.reloadWebviewAction", + ); return; } }, undefined, - context.subscriptions + context.subscriptions, ); // register view state change @@ -58,7 +59,7 @@ export class Panel { this.column = event.webviewPanel.viewColumn || DEFAULT_COLUMN; }, null, - context.subscriptions + context.subscriptions, ); // register dispose @@ -68,7 +69,7 @@ export class Panel { this.panel = undefined; }, null, - context.subscriptions + context.subscriptions, ); } @@ -76,10 +77,8 @@ export class Panel { // this invokes this panel.onDidDispose callback above, which resets the this.panel?.dispose(); } - } - /** * * @param {string} url - The target server URL (i.e., http://localhost:8080). @@ -120,16 +119,21 @@ export const createHTML = (url: string, webview: vscode.Webview): string => { * * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy */ -export const createContentSecurityPolicyContent = (nonce: string, ...allowable: string[]): string => { +export const createContentSecurityPolicyContent = ( + nonce: string, + ...allowable: string[] +): string => { const directives: string[] = [ - 'connect-src', - 'font-src', - 'frame-src', + "connect-src", + "font-src", + "frame-src", `script-src nonce-${nonce}`, - 'style-src', + "style-src", ]; const urls: string = allowable.join(" "); - const content: string = directives.map(_ => `${_} ${urls} https:;`).join(" "); + const content: string = directives + .map((_) => `${_} ${urls} https:;`) + .join(" "); return `default-src 'none'; ${content}`; }; @@ -139,8 +143,9 @@ export const createContentSecurityPolicyContent = (nonce: string, ...allowable: * @returns {string} */ const createNonce = (): string => { - let text = ''; - const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + let text = ""; + const possible = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; for (let i = 0; i < 32; i++) { text += possible.charAt(Math.floor(Math.random() * possible.length)); } diff --git a/extensions/vscode/src/ports.ts b/extensions/vscode/src/ports.ts index 17a6df763..e5432f40d 100644 --- a/extensions/vscode/src/ports.ts +++ b/extensions/vscode/src/ports.ts @@ -1,6 +1,6 @@ // Copyright (C) 2024 by Posit Software, PBC. -import getPort = require('get-port'); +import getPort = require("get-port"); export const acquire = async (): Promise => { return getPort(); diff --git a/extensions/vscode/src/servers.ts b/extensions/vscode/src/servers.ts index 1d8f2fc99..da5f6cb54 100644 --- a/extensions/vscode/src/servers.ts +++ b/extensions/vscode/src/servers.ts @@ -1,17 +1,16 @@ // Copyright (C) 2024 by Posit Software, PBC. -import * as net from 'net'; -import * as retry from 'retry'; -import * as vscode from 'vscode'; +import * as net from "net"; +import * as retry from "retry"; +import * as vscode from "vscode"; -import { ChildProcessWithoutNullStreams, spawn } from 'node:child_process'; -import { HOST } from '.'; +import { ChildProcessWithoutNullStreams, spawn } from "node:child_process"; +import { HOST } from "."; -import * as commands from './commands'; -import * as workspaces from './workspaces'; +import * as commands from "./commands"; +import * as workspaces from "./workspaces"; export class Server implements vscode.Disposable { - readonly port: number; readonly outputChannel: vscode.OutputChannel; @@ -33,7 +32,9 @@ export class Server implements vscode.Disposable { // Check if the server is stopped if (await this.isDown()) { // Display status message to user - const message = vscode.window.setStatusBarMessage("Starting Posit Publisher. Please wait..."); + const message = vscode.window.setStatusBarMessage( + "Starting Posit Publisher. Please wait...", + ); // todo - make this configurable const path = workspaces.path(); // Create command to send to terminal stdin @@ -41,7 +42,7 @@ export class Server implements vscode.Disposable { // Spawn child process this.process = spawn(command, args); // Handle error output - this.process.stderr.on('data', (data) => { + this.process.stderr.on("data", (data) => { // Write stderr to output channel this.outputChannel.append(data.toString()); }); @@ -66,9 +67,11 @@ export class Server implements vscode.Disposable { return; } // Display status message to user - const message = vscode.window.setStatusBarMessage("Stopping Posit Publisher. Please wait..."); + const message = vscode.window.setStatusBarMessage( + "Stopping Posit Publisher. Please wait...", + ); // Send interrupt signal to terminal - this.process?.kill('SIGINT'); + this.process?.kill("SIGINT"); // Wait for server to stop await this.isDown(); // Dispose of status message @@ -79,7 +82,7 @@ export class Server implements vscode.Disposable { * Disposes of the resources associated with the server. */ dispose() { - this.process?.kill('SIGINT'); + this.process?.kill("SIGINT"); } /** @@ -165,7 +168,7 @@ export class Server implements vscode.Disposable { socket.setTimeout(timeout); // Event handler for successful connection - socket.on('connect', () => { + socket.on("connect", () => { // Close the socket socket.end(); // Resolve the Promise indicating successful connection @@ -173,7 +176,7 @@ export class Server implements vscode.Disposable { }); // Event handler for connection timeout - socket.on('timeout', () => { + socket.on("timeout", () => { // Destroy the socket socket.end(); // Resolve the Promise indicating connection timeout @@ -181,7 +184,7 @@ export class Server implements vscode.Disposable { }); // Event handler for connection error - socket.on('error', (error) => { + socket.on("error", (error) => { // Destroy the socket socket.destroy(); // Reject the Promise with the encountered error @@ -189,11 +192,11 @@ export class Server implements vscode.Disposable { }); // Event handler for socket close - socket.on('close', (error) => { + socket.on("close", (error) => { // Check if the close event had an error if (error) { // Reject the Promise with an error indicating connection closure with error - reject(new Error('Connection closed with error')); + reject(new Error("Connection closed with error")); } else { // Resolve the Promise indicating successful closure without error resolve(true); @@ -204,5 +207,4 @@ export class Server implements vscode.Disposable { socket.connect(this.port, HOST); }); } - } diff --git a/extensions/vscode/src/services.ts b/extensions/vscode/src/services.ts index a2f3b4d29..d2212c9be 100644 --- a/extensions/vscode/src/services.ts +++ b/extensions/vscode/src/services.ts @@ -1,14 +1,13 @@ // Copyright (C) 2024 by Posit Software, PBC. -import * as vscode from 'vscode'; +import * as vscode from "vscode"; -import { HOST } from '.'; -import { Panel } from './panels'; -import { Server } from './servers'; -import { useApi } from './api'; +import { HOST } from "."; +import { Panel } from "./panels"; +import { Server } from "./servers"; +import { useApi } from "./api"; export class Service implements vscode.Disposable { - private context: vscode.ExtensionContext; private panel: Panel; private server: Server; @@ -42,5 +41,4 @@ export class Service implements vscode.Disposable { this.panel.dispose(); this.server.dispose(); } - } diff --git a/extensions/vscode/src/test/runTest.ts b/extensions/vscode/src/test/runTest.ts index f30b07fe8..505a21d74 100644 --- a/extensions/vscode/src/test/runTest.ts +++ b/extensions/vscode/src/test/runTest.ts @@ -1,27 +1,37 @@ // Copyright (C) 2024 by Posit Software, PBC. -import * as path from 'path'; +import * as path from "path"; -import { downloadAndUnzipVSCode, runTests } from '@vscode/test-electron'; +import { downloadAndUnzipVSCode, runTests } from "@vscode/test-electron"; async function main() { try { - const vscodeExecutablePath = await downloadAndUnzipVSCode(process.env.VSCODE_VERSION); + const vscodeExecutablePath = await downloadAndUnzipVSCode( + process.env.VSCODE_VERSION, + ); // The folder containing the Extension Manifest package.json // Passed to `--extensionDevelopmentPath` - const extensionDevelopmentPath = path.resolve(__dirname, '../../'); + const extensionDevelopmentPath = path.resolve(__dirname, "../../"); // The path to test runner // Passed to --extensionTestsPath - const extensionTestsPath = path.resolve(__dirname, './suite/index'); + const extensionTestsPath = path.resolve(__dirname, "./suite/index"); - const testWorkspace = path.resolve(__dirname, '../../../../test/sample-content/fastapi-simple/'); + const testWorkspace = path.resolve( + __dirname, + "../../../../test/sample-content/fastapi-simple/", + ); // Download VS Code, unzip it and run the integration test - await runTests({ vscodeExecutablePath, extensionDevelopmentPath, extensionTestsPath, launchArgs: [testWorkspace] }); + await runTests({ + vscodeExecutablePath, + extensionDevelopmentPath, + extensionTestsPath, + launchArgs: [testWorkspace], + }); } catch (err) { - console.error('Failed to run tests', err); + console.error("Failed to run tests", err); process.exit(1); } } diff --git a/extensions/vscode/src/test/suite/extension.test.ts b/extensions/vscode/src/test/suite/extension.test.ts index 488977945..fd12c3881 100644 --- a/extensions/vscode/src/test/suite/extension.test.ts +++ b/extensions/vscode/src/test/suite/extension.test.ts @@ -1,15 +1,16 @@ // Copyright (C) 2024 by Posit Software, PBC. -import * as assert from 'assert'; +import * as assert from "assert"; // You can import and use all API from the 'vscode' module // as well as import your extension to test it -import * as vscode from 'vscode'; +import * as vscode from "vscode"; // import * as myExtension from '../../extension'; -suite('Extension Test Suite', async () => { - test('extension is registered', async () => { - const extension: vscode.Extension = vscode.extensions.getExtension("posit.publisher")!; +suite("Extension Test Suite", async () => { + test("extension is registered", async () => { + const extension: vscode.Extension = + vscode.extensions.getExtension("posit.publisher")!; assert.ok(extension !== undefined); }); }); diff --git a/extensions/vscode/src/test/suite/index.ts b/extensions/vscode/src/test/suite/index.ts index 676b103c2..762068a63 100644 --- a/extensions/vscode/src/test/suite/index.ts +++ b/extensions/vscode/src/test/suite/index.ts @@ -1,19 +1,19 @@ // Copyright (C) 2024 by Posit Software, PBC. /* eslint-disable @typescript-eslint/naming-convention */ -import * as path from 'path'; -import * as Mocha from 'mocha'; -import * as glob from 'glob'; +import * as path from "path"; +import * as Mocha from "mocha"; +import * as glob from "glob"; export function run(): Promise { // Create the mocha test const mocha = new Mocha({ - ui: 'tdd', + ui: "tdd", color: true, - timeout: 10000 + timeout: 10000, }); - const testsRoot = path.resolve(__dirname, '..'); + const testsRoot = path.resolve(__dirname, ".."); return new Promise((c, e) => { const testFiles = new glob.Glob("**/**.test.js", { cwd: testsRoot }); @@ -28,7 +28,7 @@ export function run(): Promise { testFileStream.on("end", () => { try { // Run the mocha test - mocha.run(failures => { + mocha.run((failures) => { if (failures > 0) { e(new Error(`${failures} tests failed.`)); } else { diff --git a/extensions/vscode/src/test/suite/panels.test.ts b/extensions/vscode/src/test/suite/panels.test.ts index 65c750f62..a0cb53663 100644 --- a/extensions/vscode/src/test/suite/panels.test.ts +++ b/extensions/vscode/src/test/suite/panels.test.ts @@ -1,16 +1,19 @@ // Copyright (C) 2024 by Posit Software, PBC. -import * as assert from 'assert'; +import * as assert from "assert"; -import { createContentSecurityPolicyContent } from '../../panels'; +import { createContentSecurityPolicyContent } from "../../panels"; -suite('toContentSecurityPolicyContent', () => { - test('url injection', async () => { +suite("toContentSecurityPolicyContent", () => { + test("url injection", async () => { const url1 = "url1"; const url2 = "url2"; const nonce = "nonce"; const res = createContentSecurityPolicyContent(nonce, url1, url2); console.log(res); - assert.equal(res, "default-src 'none'; connect-src url1 url2 https:; font-src url1 url2 https:; frame-src url1 url2 https:; script-src nonce-nonce url1 url2 https:; style-src url1 url2 https:;"); + assert.equal( + res, + "default-src 'none'; connect-src url1 url2 https:; font-src url1 url2 https:; frame-src url1 url2 https:; script-src nonce-nonce url1 url2 https:; style-src url1 url2 https:;", + ); }); }); diff --git a/extensions/vscode/src/utils/date.ts b/extensions/vscode/src/utils/date.ts index 1ea08199a..3d38822f2 100644 --- a/extensions/vscode/src/utils/date.ts +++ b/extensions/vscode/src/utils/date.ts @@ -8,9 +8,9 @@ export function formatDateString( { includeTime } = { includeTime: true }, ) { const dateResult = new Date(dateString).toLocaleDateString(undefined, { - day: '2-digit', - month: 'short', - year: 'numeric', + day: "2-digit", + month: "short", + year: "numeric", }); if (!includeTime) { return dateResult; @@ -24,11 +24,11 @@ export function formatTimeString( { includeSeconds } = { includeSeconds: false }, ) { const options: Intl.DateTimeFormatOptions = { - hour: '2-digit', - minute: '2-digit', + hour: "2-digit", + minute: "2-digit", }; if (includeSeconds) { - options.second = '2-digit'; + options.second = "2-digit"; } return new Date(`${dateString}`).toLocaleTimeString(undefined, options); } diff --git a/extensions/vscode/src/utils/errors.ts b/extensions/vscode/src/utils/errors.ts index 70fa7a2c5..17cc4338e 100644 --- a/extensions/vscode/src/utils/errors.ts +++ b/extensions/vscode/src/utils/errors.ts @@ -1,18 +1,18 @@ // Copyright (C) 2023 by Posit Software, PBC. -import axios from 'axios'; +import axios from "axios"; export type ErrorMessage = string[]; export type ErrorMessages = ErrorMessage[]; -export const getStatusFromError = (error: unknown): (number | undefined) => { +export const getStatusFromError = (error: unknown): number | undefined => { if (axios.isAxiosError(error)) { return error.status; } return undefined; }; -export const getCodeStringFromError = (error: unknown): (string | undefined) => { +export const getCodeStringFromError = (error: unknown): string | undefined => { if (axios.isAxiosError(error)) { return error.code; } @@ -76,7 +76,10 @@ export const getSummaryFromError = (error: unknown) => { return undefined; }; -export const checkForResponseWithStatus = (error: unknown, statusValue: number) => { +export const checkForResponseWithStatus = ( + error: unknown, + statusValue: number, +) => { const errorStatus = getStatusFromError(error); return errorStatus === statusValue; }; @@ -89,7 +92,10 @@ export const scrubErrorData = (data: Record | undefined) => { // in this unknown list of attributes const { // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-shadow - file, method, status, url, + file, + method, + status, + url, ...remainingData } = data; diff --git a/extensions/vscode/src/utils/files.ts b/extensions/vscode/src/utils/files.ts index 8601d1482..08a1c93cf 100644 --- a/extensions/vscode/src/utils/files.ts +++ b/extensions/vscode/src/utils/files.ts @@ -7,7 +7,7 @@ import { workspace, window, commands, -} from 'vscode'; +} from "vscode"; export async function fileExists(fileUri: Uri): Promise { try { @@ -30,7 +30,7 @@ export function isValidFilename(filename: string): boolean { if (filename === "." || filename.includes("..")) { return false; } - const forbidden = '/:*?"<>|\'\\'; + const forbidden = "/:*?\"<>|'\\"; for (let c of filename) { if (forbidden.includes(c)) { return false; @@ -43,22 +43,18 @@ export function isValidFilename(filename: string): boolean { return true; } -export async function viewFileInPreview( - uri: Uri, - moveCursorToEnd = true, -) { +export async function viewFileInPreview(uri: Uri, moveCursorToEnd = true) { const doc = await workspace.openTextDocument(uri); if (moveCursorToEnd) { - await commands.executeCommand('cursorMove', { - to: 'down', - by: 'line', + await commands.executeCommand("cursorMove", { + to: "down", + by: "line", value: doc.lineCount - 1, }); } await window.showTextDocument(doc, { preview: true }); } - export async function openNewOrExistingFileInPreview( filePath: string, newFileContents: string, @@ -66,7 +62,7 @@ export async function openNewOrExistingFileInPreview( ) { let fileExist = true; const existingUri = Uri.parse(filePath); - const newUri = Uri.file(filePath).with({ scheme: 'untitled' }); + const newUri = Uri.file(filePath).with({ scheme: "untitled" }); try { await workspace.fs.stat(existingUri); @@ -74,15 +70,13 @@ export async function openNewOrExistingFileInPreview( fileExist = false; } - const doc = await workspace.openTextDocument(fileExist ? existingUri : newUri); + const doc = await workspace.openTextDocument( + fileExist ? existingUri : newUri, + ); const wsedit = new WorkspaceEdit(); if (!fileExist) { // insert our template - wsedit.insert( - newUri, - new Position(0, 0), - newFileContents, - ); + wsedit.insert(newUri, new Position(0, 0), newFileContents); } // append contents if (appendedContents) { @@ -97,9 +91,9 @@ export async function openNewOrExistingFileInPreview( viewFileInPreview(fileExist ? existingUri : newUri); await window.showTextDocument(doc, { preview: true }); - await commands.executeCommand('cursorMove', { - to: 'down', - by: 'line', + await commands.executeCommand("cursorMove", { + to: "down", + by: "line", value: doc.lineCount - 1, }); } @@ -141,7 +135,7 @@ export async function updateNewOrExistingFile( } } -// Path Sorting was inspired by: +// Path Sorting was inspired by: // https://github.com/hughsk/path-sort // Very old, but MIT license // @@ -152,11 +146,14 @@ export async function updateNewOrExistingFile( // const sep: string = (os.platform() === 'win32') ? '\\' : '/'; export function pathSort(paths: string[], sep: string): string[] { - return paths.map((el: string) => { - return el.split(sep); - }).sort(pathSorter).map((el: string[]) => { - return el.join(sep); - }); + return paths + .map((el: string) => { + return el.split(sep); + }) + .sort(pathSorter) + .map((el: string[]) => { + return el.join(sep); + }); } export function pathSorter(a: string[], b: string[]): number { diff --git a/extensions/vscode/src/utils/names.ts b/extensions/vscode/src/utils/names.ts index 3aa954944..0a726e9c0 100644 --- a/extensions/vscode/src/utils/names.ts +++ b/extensions/vscode/src/utils/names.ts @@ -1,7 +1,6 @@ // Copyright (C) 2024 by Posit Software, PBC. - -import api from '../api'; +import api from "../api"; export async function untitledConfigurationName(): Promise { const existingConfigurations = (await api.configurations.getAll()).data; @@ -11,30 +10,33 @@ export async function untitledConfigurationName(): Promise { } let id = 0; - let defaultName = ''; + let defaultName = ""; do { id += 1; const trialName = `Untitled-${id}`; - if (!existingConfigurations.find( - config => { - return config.configurationName.toLowerCase() === trialName.toLowerCase(); - } - )) { + if ( + !existingConfigurations.find((config) => { + return ( + config.configurationName.toLowerCase() === trialName.toLowerCase() + ); + }) + ) { defaultName = trialName; } } while (!defaultName); return defaultName; } -export function untitledDeploymentName(existingDeploymentNames: string[]): string { - +export function untitledDeploymentName( + existingDeploymentNames: string[], +): string { if (existingDeploymentNames.length === 0) { return "Untitled-1"; } let id = 0; - let defaultName = ''; + let defaultName = ""; do { id += 1; const trialName = `Untitled-${id}`; @@ -46,10 +48,11 @@ export function untitledDeploymentName(existingDeploymentNames: string[]): strin return defaultName; } -export function uniqueDeploymentName(nameToTest: string, existingNames: string[]) { - return !existingNames.find( - existingName => { - return existingName.toLowerCase() === nameToTest.toLowerCase(); - } - ); -} \ No newline at end of file +export function uniqueDeploymentName( + nameToTest: string, + existingNames: string[], +) { + return !existingNames.find((existingName) => { + return existingName.toLowerCase() === nameToTest.toLowerCase(); + }); +} diff --git a/extensions/vscode/src/views/configurations.ts b/extensions/vscode/src/views/configurations.ts index 41a6a7e86..bddbe76db 100644 --- a/extensions/vscode/src/views/configurations.ts +++ b/extensions/vscode/src/views/configurations.ts @@ -15,38 +15,43 @@ import { commands, window, workspace, -} from 'vscode'; +} from "vscode"; -import api from '../api'; +import api from "../api"; import { Configuration, ConfigurationDetails, ConfigurationError, - isConfigurationError + isConfigurationError, } from "../api/types/configurations"; -import { confirmDelete, confirmReplace } from '../dialogs'; -import { getSummaryStringFromError } from '../utils/errors'; -import { ensureSuffix, fileExists, isValidFilename } from '../utils/files'; -import { untitledConfigurationName } from '../utils/names'; - -const viewName = 'posit.publisher.configurations'; -const refreshCommand = viewName + '.refresh'; -const addCommand = viewName + '.add'; -const editCommand = viewName + '.edit'; -const cloneCommand = viewName + '.clone'; -const renameCommand = viewName + '.rename'; -const deleteCommand = viewName + '.delete'; -const contextIsEmpty = viewName + '.isEmpty'; -const fileStore = '.posit/publish/*.toml'; - -type ConfigurationEventEmitter = EventEmitter; +import { confirmDelete, confirmReplace } from "../dialogs"; +import { getSummaryStringFromError } from "../utils/errors"; +import { ensureSuffix, fileExists, isValidFilename } from "../utils/files"; +import { untitledConfigurationName } from "../utils/names"; + +const viewName = "posit.publisher.configurations"; +const refreshCommand = viewName + ".refresh"; +const addCommand = viewName + ".add"; +const editCommand = viewName + ".edit"; +const cloneCommand = viewName + ".clone"; +const renameCommand = viewName + ".rename"; +const deleteCommand = viewName + ".delete"; +const contextIsEmpty = viewName + ".isEmpty"; +const fileStore = ".posit/publish/*.toml"; + +type ConfigurationEventEmitter = EventEmitter< + ConfigurationTreeItem | undefined | void +>; type ConfigurationEvent = Event; -export class ConfigurationsTreeDataProvider implements TreeDataProvider { +export class ConfigurationsTreeDataProvider + implements TreeDataProvider +{ private root: WorkspaceFolder | undefined; private _onDidChangeTreeData: ConfigurationEventEmitter = new EventEmitter(); - readonly onDidChangeTreeData: ConfigurationEvent = this._onDidChangeTreeData.event; + readonly onDidChangeTreeData: ConfigurationEvent = + this._onDidChangeTreeData.event; constructor() { const workspaceFolders = workspace.workspaceFolders; @@ -59,7 +64,9 @@ export class ConfigurationsTreeDataProvider implements TreeDataProvider { + async getChildren( + element: ConfigurationTreeItem | undefined, + ): Promise { if (element !== undefined) { // Config elements have no children. return []; @@ -75,12 +82,15 @@ export class ConfigurationsTreeDataProvider implements TreeDataProvider { + return configurations.map((config) => { const fileUri = Uri.file(config.configurationPath); return new ConfigurationTreeItem(config, fileUri); }); } catch (error: unknown) { - const summary = getSummaryStringFromError('configurations::getChildren', error); + const summary = getSummaryStringFromError( + "configurations::getChildren", + error, + ); window.showInformationMessage(summary); await this.setContextIsEmpty(true); return []; @@ -88,7 +98,9 @@ export class ConfigurationsTreeDataProvider implements TreeDataProvider { - await commands.executeCommand('setContext', contextIsEmpty, isEmpty ? "empty" : "notEmpty"); + await commands.executeCommand( + "setContext", + contextIsEmpty, + isEmpty ? "empty" : "notEmpty", + ); } private createFileSystemWatcher(root: WorkspaceFolder): FileSystemWatcher { @@ -115,7 +131,7 @@ export class ConfigurationsTreeDataProvider implements TreeDataProvider { this._onDidChangeTreeData.fire(); @@ -134,23 +150,26 @@ export class ConfigurationsTreeDataProvider implements TreeDataProvider { - await commands.executeCommand('vscode.open', config.fileUri); + await commands.executeCommand("vscode.open", config.fileUri); }; private rename = async (item: ConfigurationTreeItem) => { @@ -171,11 +190,14 @@ export class ConfigurationsTreeDataProvider implements TreeDataProvider { + private async promptForNewName( + oldUri: Uri, + defaultName: string, + ): Promise { const newName = await window.showInputBox({ value: defaultName, prompt: "New configuration name", - validateInput: filename => { + validateInput: (filename) => { if (isValidFilename(filename)) { return undefined; } else { @@ -184,9 +206,9 @@ export class ConfigurationsTreeDataProvider implements TreeDataProvider { const name = item.config.configurationName; - const ok = await confirmDelete(`Are you sure you want to delete the configuration '${name}'?`); + const ok = await confirmDelete( + `Are you sure you want to delete the configuration '${name}'?`, + ); if (ok) { try { await api.configurations.delete(name); } catch (error: unknown) { - const summary = getSummaryStringFromError('configurations::delete', error); + const summary = getSummaryStringFromError( + "configurations::delete", + error, + ); window.showInformationMessage(summary); } } @@ -220,7 +249,9 @@ export class ConfigurationsTreeDataProvider implements TreeDataProvider `${config.entrypoint} (type ${config.type})`); + const labels = configs.map( + (config) => `${config.entrypoint} (type ${config.type})`, + ); const labelMap = new Map(); for (let i = 0; i < configs.length; i++) { labelMap.set(labels[i], configs[i]); @@ -237,24 +268,24 @@ export class ConfigurationsTreeDataProvider implements TreeDataProvider; +type CredentialEventEmitter = EventEmitter< + CredentialsTreeItem | undefined | void +>; type CredentialEvent = Event; -export class CredentialsTreeDataProvider implements TreeDataProvider { - +export class CredentialsTreeDataProvider + implements TreeDataProvider +{ private _onDidChangeTreeData: CredentialEventEmitter = new EventEmitter(); - readonly onDidChangeTreeData: CredentialEvent = this._onDidChangeTreeData.event; + readonly onDidChangeTreeData: CredentialEvent = + this._onDidChangeTreeData.event; - constructor() { } + constructor() {} getTreeItem(element: CredentialsTreeItem): TreeItem | Thenable { return element; } - async getChildren(element: CredentialsTreeItem | undefined): Promise { + async getChildren( + element: CredentialsTreeItem | undefined, + ): Promise { if (element) { return []; } @@ -39,11 +45,14 @@ export class CredentialsTreeDataProvider implements TreeDataProvider { + return accounts.map((account) => { return new CredentialsTreeItem(account); }); } catch (error: unknown) { - const summary = getSummaryStringFromError('credentials::getChildren', error); + const summary = getSummaryStringFromError( + "credentials::getChildren", + error, + ); window.showInformationMessage(summary); return []; } @@ -55,30 +64,30 @@ export class CredentialsTreeDataProvider implements TreeDataProvider { - - constructor() { } +export class DependenciesTreeDataProvider + implements TreeDataProvider +{ + constructor() {} getTreeItem(element: DependenciesTreeItem): TreeItem | Thenable { return element; } - getChildren(element: DependenciesTreeItem | undefined): ProviderResult { + getChildren( + element: DependenciesTreeItem | undefined, + ): ProviderResult { if (element === undefined) { - return [ - new DependenciesTreeItem('Dummy Dependencies'), - ]; + return [new DependenciesTreeItem("Dummy Dependencies")]; } return []; } public register(context: ExtensionContext) { context.subscriptions.push( - window.createTreeView(viewName, { treeDataProvider: this }) + window.createTreeView(viewName, { treeDataProvider: this }), ); } } export class DependenciesTreeItem extends TreeItem { - constructor(itemString: string) { super(itemString); } - contextValue = 'posit.publisher.dependencies.tree.item'; - tooltip = 'This is a \nDependencies Tree Item'; + contextValue = "posit.publisher.dependencies.tree.item"; + tooltip = "This is a \nDependencies Tree Item"; } diff --git a/extensions/vscode/src/views/deployProgress.ts b/extensions/vscode/src/views/deployProgress.ts index 4afccb559..efb5b85ec 100644 --- a/extensions/vscode/src/views/deployProgress.ts +++ b/extensions/vscode/src/views/deployProgress.ts @@ -1,298 +1,433 @@ // Copyright (C) 2024 by Posit Software, PBC. -import { ProgressLocation, Uri, env, window } from 'vscode'; -import { eventTypeToString } from '../api'; -import { EventStream, EventStreamMessage, UnregisterCallback } from '../events'; +import { ProgressLocation, Uri, env, window } from "vscode"; +import { eventTypeToString } from "../api"; +import { EventStream, EventStreamMessage, UnregisterCallback } from "../events"; export function deployProject(localID: string, stream: EventStream) { - window.withProgress({ - location: ProgressLocation.Notification, - title: `Deploying your project...`, - cancellable: false - }, (progress) => { - let resolveCB: (reason?: any) => void; - let rejectCB: (reason?: any) => void; - const registrations: UnregisterCallback[] = []; + window.withProgress( + { + location: ProgressLocation.Notification, + title: `Deploying your project...`, + cancellable: false, + }, + (progress) => { + let resolveCB: (reason?: any) => void; + let rejectCB: (reason?: any) => void; + const registrations: UnregisterCallback[] = []; - const unregiserAll = () => { - registrations.forEach(cb => cb.unregister()); - }; + const unregiserAll = () => { + registrations.forEach((cb) => cb.unregister()); + }; - const promise = new Promise((resolve, reject) => { - resolveCB = resolve; - rejectCB = reject; - }); + const promise = new Promise((resolve, reject) => { + resolveCB = resolve; + rejectCB = reject; + }); - registrations.push( - stream.register('publish/start', (msg: EventStreamMessage) => { - if (localID === msg.data.localId) { - progress.report({ message: "Starting to Deploy..." }); - } - }) - ); - - const handleProgressMessages = (msg: EventStreamMessage) => { - if (localID === msg.data.localId) { - const progressStr = eventTypeToString(msg.type); - progress.report({ - message: progressStr, - }); - console.log(progressStr); - } - }; - - registrations.push( - stream.register('publish/checkCapabilities/start', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/checkCapabilities/log', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/checkCapabilities/success', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/checkCapabilities/failure', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/createNewDeployment/start', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/createNewDeployment/success', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/createNewDeployment/failure', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/setEnvVars/start', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/setEnvVars/success', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/setEnvVars/failure', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/createBundle/start', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/createBundle/success', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/createBundle/failure', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/createBundle/failure', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/createDeployment/start', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/createDeployment/success', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/createDeployment/failure', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/createDeployment/log', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/uploadBundle/start', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/uploadBundle/success', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/uploadBundle/failure', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/uploadBundle/log', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/deployBundle/start', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/deployBundle/success', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/deployBundle/failure', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/deployBundle/log', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/restorePythonEnv/start', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/restorePythonEnv/success', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/restorePythonEnv/failure', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/restorePythonEnv/log', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/restorePythonEnv/progress', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/restorePythonEnv/status', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/runContent/start', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/runContent/success', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/runContent/failure', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/runContent/log', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/setVanityURL/start', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/setVanityURL/success', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/setVanityURL/failure', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/setVanityURL/log', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/validateDeployment/start', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/validateDeployment/success', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/validateDeployment/failure', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); - registrations.push( - stream.register('publish/validateDeployment/log', (msg: EventStreamMessage) => { - handleProgressMessages(msg); - }) - ); + registrations.push( + stream.register("publish/start", (msg: EventStreamMessage) => { + if (localID === msg.data.localId) { + progress.report({ message: "Starting to Deploy..." }); + } + }), + ); - registrations.push( - stream.register('publish/success', async (msg: EventStreamMessage) => { + const handleProgressMessages = (msg: EventStreamMessage) => { if (localID === msg.data.localId) { - unregiserAll(); + const progressStr = eventTypeToString(msg.type); progress.report({ - message: "Deployment was successful", + message: progressStr, }); - resolveCB('Success!'); + console.log(progressStr); + } + }; + + registrations.push( + stream.register( + "publish/checkCapabilities/start", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/checkCapabilities/log", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/checkCapabilities/success", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/checkCapabilities/failure", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/createNewDeployment/start", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/createNewDeployment/success", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/createNewDeployment/failure", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/setEnvVars/start", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/setEnvVars/success", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/setEnvVars/failure", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/createBundle/start", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/createBundle/success", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/createBundle/failure", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/createBundle/failure", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/createDeployment/start", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/createDeployment/success", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/createDeployment/failure", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/createDeployment/log", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/uploadBundle/start", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/uploadBundle/success", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/uploadBundle/failure", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/uploadBundle/log", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/deployBundle/start", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/deployBundle/success", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/deployBundle/failure", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/deployBundle/log", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/restorePythonEnv/start", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/restorePythonEnv/success", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/restorePythonEnv/failure", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/restorePythonEnv/log", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/restorePythonEnv/progress", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/restorePythonEnv/status", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/runContent/start", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/runContent/success", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/runContent/failure", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register("publish/runContent/log", (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }), + ); + registrations.push( + stream.register( + "publish/setVanityURL/start", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/setVanityURL/success", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/setVanityURL/failure", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/setVanityURL/log", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/validateDeployment/start", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/validateDeployment/success", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/validateDeployment/failure", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); + registrations.push( + stream.register( + "publish/validateDeployment/log", + (msg: EventStreamMessage) => { + handleProgressMessages(msg); + }, + ), + ); - let visitOption = "Visit"; - const selection = await window.showInformationMessage("Deployment was successful", visitOption); - if (selection === visitOption) { - const uri = Uri.parse(msg.data.dashboardUrl, true); - await env.openExternal(uri); + registrations.push( + stream.register("publish/success", async (msg: EventStreamMessage) => { + if (localID === msg.data.localId) { + unregiserAll(); + progress.report({ + message: "Deployment was successful", + }); + resolveCB("Success!"); + + let visitOption = "Visit"; + const selection = await window.showInformationMessage( + "Deployment was successful", + visitOption, + ); + if (selection === visitOption) { + const uri = Uri.parse(msg.data.dashboardUrl, true); + await env.openExternal(uri); + } } - } - }) - ); + }), + ); - registrations.push( - stream.register('publish/failure', (msg: EventStreamMessage) => { - if (localID === msg.data.localId) { - unregiserAll(); - progress.report({ - message: "Deployment process encountered an error", - }); - rejectCB('Error Encountered!'); - } - }) - ); + registrations.push( + stream.register("publish/failure", (msg: EventStreamMessage) => { + if (localID === msg.data.localId) { + unregiserAll(); + progress.report({ + message: "Deployment process encountered an error", + }); + rejectCB("Error Encountered!"); + } + }), + ); - return promise; - }); -} \ No newline at end of file + return promise; + }, + ); +} diff --git a/extensions/vscode/src/views/deployments.ts b/extensions/vscode/src/views/deployments.ts index fbe14f270..e3de71147 100644 --- a/extensions/vscode/src/views/deployments.ts +++ b/extensions/vscode/src/views/deployments.ts @@ -14,7 +14,7 @@ import { env, window, workspace, -} from 'vscode'; +} from "vscode"; import { AllDeploymentTypes, @@ -25,39 +25,42 @@ import { isDeploymentError, isPreDeployment, useApi, -} from '../api'; - -import { confirmForget } from '../dialogs'; -import { EventStream } from '../events'; -import { addDeployment } from '../multiStepInputs/addDeployment'; -import { publishDeployment } from '../multiStepInputs/deployProject'; -import { formatDateString } from '../utils/date'; -import { getSummaryStringFromError } from '../utils/errors'; - -const viewName = 'posit.publisher.deployments'; -const refreshCommand = viewName + '.refresh'; -const editCommand = viewName + '.edit'; -const forgetCommand = viewName + '.forget'; -const visitCommand = viewName + '.visit'; -const addCommand = viewName + '.add'; -const deployCommand = viewName + '.deploy'; -const isEmptyContext = viewName + '.isEmpty'; - -const fileStore = '.posit/publish/deployments/*.toml'; - -type DeploymentsEventEmitter = EventEmitter; +} from "../api"; + +import { confirmForget } from "../dialogs"; +import { EventStream } from "../events"; +import { addDeployment } from "../multiStepInputs/addDeployment"; +import { publishDeployment } from "../multiStepInputs/deployProject"; +import { formatDateString } from "../utils/date"; +import { getSummaryStringFromError } from "../utils/errors"; + +const viewName = "posit.publisher.deployments"; +const refreshCommand = viewName + ".refresh"; +const editCommand = viewName + ".edit"; +const forgetCommand = viewName + ".forget"; +const visitCommand = viewName + ".visit"; +const addCommand = viewName + ".add"; +const deployCommand = viewName + ".deploy"; +const isEmptyContext = viewName + ".isEmpty"; + +const fileStore = ".posit/publish/deployments/*.toml"; + +type DeploymentsEventEmitter = EventEmitter< + DeploymentsTreeItem | undefined | void +>; type DeploymentsEvent = Event; -export class DeploymentsTreeDataProvider implements TreeDataProvider { +export class DeploymentsTreeDataProvider + implements TreeDataProvider +{ private root: WorkspaceFolder | undefined; private _onDidChangeTreeData: DeploymentsEventEmitter = new EventEmitter(); - readonly onDidChangeTreeData: DeploymentsEvent = this._onDidChangeTreeData.event; + readonly onDidChangeTreeData: DeploymentsEvent = + this._onDidChangeTreeData.event; private api = useApi(); - constructor( - private stream: EventStream - ) { + constructor(private stream: EventStream) { const workspaceFolders = workspace.workspaceFolders; if (workspaceFolders !== undefined) { this.root = workspaceFolders[0]; @@ -73,7 +76,9 @@ export class DeploymentsTreeDataProvider implements TreeDataProvider { + async getChildren( + element: DeploymentsTreeItem | undefined, + ): Promise { if (element) { // flat organization of deployments, so no children. return []; @@ -90,68 +95,90 @@ export class DeploymentsTreeDataProvider implements TreeDataProvider { + return deployments.map((deployment) => { const fileUri = Uri.file(deployment.deploymentPath); return new DeploymentsTreeItem(deployment, fileUri); }); } catch (error: unknown) { - const summary = getSummaryStringFromError('deployments::getChildren', error); - commands.executeCommand('setContext', isEmptyContext, true); + const summary = getSummaryStringFromError( + "deployments::getChildren", + error, + ); + commands.executeCommand("setContext", isEmptyContext, true); window.showInformationMessage(summary); return []; } } public register(context: ExtensionContext) { - const treeView = window.createTreeView(viewName, { treeDataProvider: this }); + const treeView = window.createTreeView(viewName, { + treeDataProvider: this, + }); context.subscriptions.push(treeView); context.subscriptions.push( commands.registerCommand(addCommand, () => { addDeployment(this.stream); - }) + }), ); context.subscriptions.push( - commands.registerCommand(refreshCommand, this.refresh) + commands.registerCommand(refreshCommand, this.refresh), ); context.subscriptions.push( - commands.registerCommand(editCommand, async (item: DeploymentsTreeItem) => { - await commands.executeCommand('vscode.open', item.fileUri); - }) + commands.registerCommand( + editCommand, + async (item: DeploymentsTreeItem) => { + await commands.executeCommand("vscode.open", item.fileUri); + }, + ), ); context.subscriptions.push( - commands.registerCommand(deployCommand, async (item: DeploymentsTreeItem) => { - if (!isDeploymentError(item.deployment)) { - publishDeployment(item.deployment, this.stream); - } - }) + commands.registerCommand( + deployCommand, + async (item: DeploymentsTreeItem) => { + if (!isDeploymentError(item.deployment)) { + publishDeployment(item.deployment, this.stream); + } + }, + ), ); context.subscriptions.push( - commands.registerCommand(forgetCommand, async (item: DeploymentsTreeItem) => { - const ok = await confirmForget(`Are you sure you want to forget this deployment '${item.deployment.deploymentName}' locally?`); - if (ok) { - await this.api.deployments.delete(item.deployment.deploymentName); - } - }), - commands.registerCommand(visitCommand, async (item: DeploymentsTreeItem) => { - // This command is only registered for Deployments - if (isDeployment(item.deployment)) { - const uri = Uri.parse(item.deployment.dashboardUrl, true); - await env.openExternal(uri); - } - }) + commands.registerCommand( + forgetCommand, + async (item: DeploymentsTreeItem) => { + const ok = await confirmForget( + `Are you sure you want to forget this deployment '${item.deployment.deploymentName}' locally?`, + ); + if (ok) { + await this.api.deployments.delete(item.deployment.deploymentName); + } + }, + ), + commands.registerCommand( + visitCommand, + async (item: DeploymentsTreeItem) => { + // This command is only registered for Deployments + if (isDeployment(item.deployment)) { + const uri = Uri.parse(item.deployment.dashboardUrl, true); + await env.openExternal(uri); + } + }, + ), ); - if (this.root !== undefined) { const watcher = workspace.createFileSystemWatcher( - new RelativePattern(this.root, fileStore) + new RelativePattern(this.root, fileStore), ); watcher.onDidCreate(this.refresh); watcher.onDidDelete(this.refresh); @@ -164,7 +191,7 @@ export class DeploymentsTreeDataProvider implements TreeDataProvider { constructor() { const workspaceFolders = workspace.workspaceFolders; - this.root = Uri.parse('positPublisherFiles://unknown'); + this.root = Uri.parse("positPublisherFiles://unknown"); if (workspaceFolders !== undefined) { this.root = workspaceFolders[0].uri; } @@ -81,30 +78,26 @@ export class FilesTreeDataProvider implements TreeDataProvider { async getChildren(element: TreeEntries | undefined): Promise { if (element === undefined) { - // first call. + // first call. try { const response = await this.api.files.get(); const file = response.data; - commands.executeCommand('setContext', isEmptyContext, Boolean(file)); + commands.executeCommand("setContext", isEmptyContext, Boolean(file)); resetFileTrees(); // skipping the top level - file.files.forEach(f => buildFileTrees(f, this.root)); + file.files.forEach((f) => buildFileTrees(f, this.root)); sortFileTrees(); // we have a fixed top hiearchy return [ - new IncludedFilesSection( - `Included Files`, - ), - new ExcludedFilesSection( - `Excluded Files`, - ), + new IncludedFilesSection(`Included Files`), + new ExcludedFilesSection(`Excluded Files`), ]; } catch (error: unknown) { - const summary = getSummaryStringFromError('files::getChildren', error); - commands.executeCommand('setContext', isEmptyContext, true); + const summary = getSummaryStringFromError("files::getChildren", error); + commands.executeCommand("setContext", isEmptyContext, true); window.showInformationMessage(summary); return []; } @@ -119,44 +112,44 @@ export class FilesTreeDataProvider implements TreeDataProvider { } public register(context: ExtensionContext) { - const treeView = window.createTreeView( - viewName, - { - treeDataProvider: this, - }, - ); + const treeView = window.createTreeView(viewName, { + treeDataProvider: this, + }); context.subscriptions.push(treeView); context.subscriptions.push( - commands.registerCommand(refreshCommand, this.refresh) + commands.registerCommand(refreshCommand, this.refresh), ); context.subscriptions.push( commands.registerCommand(editPositIgnoreCommand, async () => { if (this.root !== undefined) { updateNewOrExistingFile( - path.join(this.root.fsPath, '.positignore'), + path.join(this.root.fsPath, ".positignore"), positIgnoreFileTemplate, undefined, // No suffix true, // open file preview after update ); } - }) + }), ); context.subscriptions.push( - commands.registerCommand(addExclusionCommand, async (item: FileTreeItem) => { - if (this.root !== undefined) { - updateNewOrExistingFile( - path.join(this.root.fsPath, '.positignore'), - positIgnoreFileTemplate, - `${item.id}\n`, - true, // open file preview after update - ); - } - }) + commands.registerCommand( + addExclusionCommand, + async (item: FileTreeItem) => { + if (this.root !== undefined) { + updateNewOrExistingFile( + path.join(this.root.fsPath, ".positignore"), + positIgnoreFileTemplate, + `${item.id}\n`, + true, // open file preview after update + ); + } + }, + ), ); if (this.root !== undefined) { const watcher = workspace.createFileSystemWatcher( - new RelativePattern(this.root, '**') + new RelativePattern(this.root, "**"), ); watcher.onDidCreate(this.refresh); watcher.onDidDelete(this.refresh); @@ -167,12 +160,12 @@ export class FilesTreeDataProvider implements TreeDataProvider { } function sortFilesTreeItemByPath(a: FileTreeItem, b: FileTreeItem) { - const sep: string = (os.platform() === 'win32') ? '\\' : '/'; + const sep: string = os.platform() === "win32" ? "\\" : "/"; if (a.fileUri.fsPath && b.fileUri.fsPath) { return pathSorter(a.fileUri.fsPath.split(sep), b.fileUri.fsPath.split(sep)); } return 0; -}; +} const sortFileTrees = () => { includedFiles.sort(sortFilesTreeItemByPath); @@ -194,71 +187,55 @@ const buildFileTrees = ( ) => { if (file.isFile) { if (file.exclusion || exclusionOverride) { - const f = new ExcludedFile( - root, - { - ...file, - exclusion: file.exclusion || exclusionOverride || null, - } - ); + const f = new ExcludedFile(root, { + ...file, + exclusion: file.exclusion || exclusionOverride || null, + }); excludedFiles.push(f); } else { - const f = new IncludedFile( - root, - file, - ); + const f = new IncludedFile(root, file); includedFiles.push(f); } } else { // We're not showing our .posit subdirectory, but it will be included - // in the deployment bundle unless they explicitly exclude it in the + // in the deployment bundle unless they explicitly exclude it in the // .positignore. - if (file.id === '.posit') { + if (file.id === ".posit") { return; } } - file.files.forEach(d => { + file.files.forEach((d) => { buildFileTrees(d, root, file.exclusion || exclusionOverride); }); }; export type TreeEntries = - IncludedFilesSection | - ExcludedFilesSection | - FileEntries - ; -export type FileEntries = - IncludedFile | - ExcludedFile - ; + | IncludedFilesSection + | ExcludedFilesSection + | FileEntries; +export type FileEntries = IncludedFile | ExcludedFile; export class IncludedFilesSection extends TreeItem { - constructor( - label: string, - ) { + constructor(label: string) { super(label); this.contextValue = isIncludedTitle; this.resourceUri = Uri.parse(`positPublisherFiles://includedTitle`); this.collapsibleState = TreeItemCollapsibleState.Expanded; - this.tooltip = - `Files listed within this tree node will be uploaded during the next deployment.`; - this.iconPath = new ThemeIcon('list-unordered'); + this.tooltip = `Files listed within this tree node will be uploaded during the next deployment.`; + this.iconPath = new ThemeIcon("list-unordered"); } } export class ExcludedFilesSection extends TreeItem { - constructor( - label: string, - ) { + constructor(label: string) { super(label); this.contextValue = isExcludedTitle; this.resourceUri = Uri.parse(`positPublisherFiles://excludedTitle`); this.collapsibleState = TreeItemCollapsibleState.Expanded; - this.tooltip = - `Files listed within this tree node will not be uploaded during the next deployment.`; - this.iconPath = new ThemeIcon('list-unordered'); + this.tooltip = `Files listed within this tree node will not be uploaded during the next deployment.`; + this.iconPath = new ThemeIcon("list-unordered"); } } @@ -266,10 +243,7 @@ export class FileTreeItem extends TreeItem { public fileUri: Uri; public exclusion: ExclusionMatch | null; - constructor( - root: Uri, - deploymentFile: DeploymentFile, - ) { + constructor(root: Uri, deploymentFile: DeploymentFile) { super(deploymentFile.base); this.id = deploymentFile.id; @@ -278,57 +252,40 @@ export class FileTreeItem extends TreeItem { this.collapsibleState = TreeItemCollapsibleState.None; this.exclusion = deploymentFile.exclusion; this.command = { - title: 'Open', - command: 'vscode.open', - arguments: [this.fileUri] + title: "Open", + command: "vscode.open", + arguments: [this.fileUri], }; this.description = path.dirname(deploymentFile.id); - if (this.description === '.') { + if (this.description === ".") { this.description = undefined; } - this.iconPath = new ThemeIcon('debug-stackframe-dot'); + this.iconPath = new ThemeIcon("debug-stackframe-dot"); } } export class IncludedFile extends FileTreeItem { - constructor( - root: Uri, - deploymentFile: DeploymentFile, - ) { - super( - root, - deploymentFile, - ); + constructor(root: Uri, deploymentFile: DeploymentFile) { + super(root, deploymentFile); this.contextValue = isIncludedFile; this.resourceUri = Uri.parse(`positPublisherFilesIncluded://${this.id}`); - this.tooltip = - `This file will be included in the next deployment.\n${this.fileUri.fsPath}`; + this.tooltip = `This file will be included in the next deployment.\n${this.fileUri.fsPath}`; } } export class ExcludedFile extends FileTreeItem { - constructor( - root: Uri, - deploymentFile: DeploymentFile, - ) { - super( - root, - deploymentFile, - ); + constructor(root: Uri, deploymentFile: DeploymentFile) { + super(root, deploymentFile); this.contextValue = isExcludedFile; this.resourceUri = Uri.parse(`positPublisherFilesExcluded://${this.id}`); - this.tooltip = - `This file will be excluded in the next deployment.\n${this.fileUri.fsPath}\n\n`; + this.tooltip = `This file will be excluded in the next deployment.\n${this.fileUri.fsPath}\n\n`; if (this.exclusion) { - if (this.exclusion.source === 'built-in') { - this.tooltip += - `This is a built-in exclusion for the pattern: '${this.exclusion?.pattern}'`; + if (this.exclusion.source === "built-in") { + this.tooltip += `This is a built-in exclusion for the pattern: '${this.exclusion?.pattern}'`; } else { - this.tooltip += - `The project's .positignore file is excluding it\nwith the pattern '${this.exclusion?.pattern}' on line #${this.exclusion?.line}`; + this.tooltip += `The project's .positignore file is excluding it\nwith the pattern '${this.exclusion?.pattern}' on line #${this.exclusion?.line}`; } } } } - diff --git a/extensions/vscode/src/views/helpAndFeedback.ts b/extensions/vscode/src/views/helpAndFeedback.ts index 27db75d66..474c949b2 100644 --- a/extensions/vscode/src/views/helpAndFeedback.ts +++ b/extensions/vscode/src/views/helpAndFeedback.ts @@ -9,31 +9,34 @@ import { Uri, commands, env, -} from 'vscode'; +} from "vscode"; -const viewName = 'posit.publisher.helpAndFeedback'; -const openGettingStartedCommand = viewName + '.gettingStarted'; -const openFeedbackCommand = viewName + 'openFeedback'; +const viewName = "posit.publisher.helpAndFeedback"; +const openGettingStartedCommand = viewName + ".gettingStarted"; +const openFeedbackCommand = viewName + "openFeedback"; -export class HelpAndFeedbackTreeDataProvider implements TreeDataProvider { - - constructor() { } +export class HelpAndFeedbackTreeDataProvider + implements TreeDataProvider +{ + constructor() {} getTreeItem(element: HelpAndFeedbackTreeItem): TreeItem | Thenable { return element; } - getChildren(element: HelpAndFeedbackTreeItem | undefined): ProviderResult { + getChildren( + element: HelpAndFeedbackTreeItem | undefined, + ): ProviderResult { if (element === undefined) { return [ new HelpAndFeedbackTreeItem( - 'Get Started with Posit Publisher', - 'Open Getting Started Documentation', + "Get Started with Posit Publisher", + "Open Getting Started Documentation", openGettingStartedCommand, ), new HelpAndFeedbackTreeItem( - 'Provide Feedback', - 'Open Feedback Slack Channel', + "Provide Feedback", + "Open Feedback Slack Channel", openFeedbackCommand, ), ]; @@ -43,25 +46,30 @@ export class HelpAndFeedbackTreeDataProvider implements TreeDataProvider { - env.openExternal(Uri.parse('https://github.com/posit-dev/publisher/blob/e72828f3585497649b8b55470a665f7fa890a21f/docs/vscode.md')); - }) + env.openExternal( + Uri.parse( + "https://github.com/posit-dev/publisher/blob/e72828f3585497649b8b55470a665f7fa890a21f/docs/vscode.md", + ), + ); + }), ); context.subscriptions.push( commands.registerCommand(openFeedbackCommand, () => { - env.openExternal(Uri.parse('https://positpbc.slack.com/channels/publisher-feedback')); - }) + env.openExternal( + Uri.parse("https://positpbc.slack.com/channels/publisher-feedback"), + ); + }), ); } } export class HelpAndFeedbackTreeItem extends TreeItem { - constructor(itemString: string, commandTitle: string, command: string) { super(itemString); this.command = { @@ -69,4 +77,4 @@ export class HelpAndFeedbackTreeItem extends TreeItem { command, }; } -} \ No newline at end of file +} diff --git a/extensions/vscode/src/views/logs.ts b/extensions/vscode/src/views/logs.ts index f62fe090b..de783ff7e 100644 --- a/extensions/vscode/src/views/logs.ts +++ b/extensions/vscode/src/views/logs.ts @@ -12,7 +12,7 @@ import { Uri, commands, env, - window + window, } from "vscode"; import { @@ -26,15 +26,15 @@ enum LogStageStatus { neverStarted, inProgress, completed, - failed + failed, } type LogStage = { - label: string, - collapseState?: TreeItemCollapsibleState, - status: LogStageStatus, - stages: LogStage[], - events: EventStreamMessage[] + label: string; + collapseState?: TreeItemCollapsibleState; + status: LogStageStatus; + stages: LogStage[]; + events: EventStreamMessage[]; }; type LogsTreeItem = LogsTreeStageItem | LogsTreeLogItem; @@ -55,7 +55,7 @@ const createLogStage = ( }; }; -const viewName = 'posit.publisher.logs'; +const viewName = "posit.publisher.logs"; const visitCommand = viewName + ".visit"; /** @@ -69,7 +69,8 @@ export class LogsTreeDataProvider implements TreeDataProvider { * Event emitter for when the tree data of the Logs view changes. * @private */ - private _onDidChangeTreeData: EventEmitter = new EventEmitter(); + private _onDidChangeTreeData: EventEmitter = + new EventEmitter(); /** * Creates an instance of LogsTreeDataProvider. @@ -82,44 +83,47 @@ export class LogsTreeDataProvider implements TreeDataProvider { // Register all of the events this view cares about this.registerEvents(stream); - }; + } private resetStages() { this.stages = new Map([ - ['publish/checkCapabilities', createLogStage('Check Capabilities')], - ['publish/createBundle', createLogStage('Create Bundle')], - ['publish/uploadBundle', createLogStage('Upload Bundle')], - ['publish/createDeployment', createLogStage('Create Deployment')], - ['publish/deployBundle', createLogStage('Deploy Bundle')], - ['publish/restorePythonEnv', createLogStage('Restore Python Environment')], - ['publish/runContent', createLogStage('Run Content')], - ['publish/validateDeployment', createLogStage('Validate Deployment')], + ["publish/checkCapabilities", createLogStage("Check Capabilities")], + ["publish/createBundle", createLogStage("Create Bundle")], + ["publish/uploadBundle", createLogStage("Upload Bundle")], + ["publish/createDeployment", createLogStage("Create Deployment")], + ["publish/deployBundle", createLogStage("Deploy Bundle")], + [ + "publish/restorePythonEnv", + createLogStage("Restore Python Environment"), + ], + ["publish/runContent", createLogStage("Run Content")], + ["publish/validateDeployment", createLogStage("Validate Deployment")], ]); this.publishingStage = createLogStage( - 'Publishing', + "Publishing", TreeItemCollapsibleState.Expanded, LogStageStatus.notStarted, - Array.from(this.stages.values()) + Array.from(this.stages.values()), ); } private registerEvents(stream: EventStream) { // Reset events when a new publish starts - stream.register('publish/start', (msg: EventStreamMessage) => { + stream.register("publish/start", (msg: EventStreamMessage) => { this.resetStages(); this.publishingStage.label = `Publishing to ${msg.data.server}`; this.publishingStage.status = LogStageStatus.inProgress; this.refresh(); }); - stream.register('publish/success', (msg: EventStreamMessage) => { + stream.register("publish/success", (msg: EventStreamMessage) => { this.publishingStage.status = LogStageStatus.completed; this.publishingStage.events.push(msg); this.refresh(); }); - stream.register('publish/failure', (msg: EventStreamMessage) => { + stream.register("publish/failure", (msg: EventStreamMessage) => { this.publishingStage.status = LogStageStatus.failed; this.publishingStage.events.push(msg); @@ -144,7 +148,7 @@ export class LogsTreeDataProvider implements TreeDataProvider { stream.register(`${stageName}/log`, (msg: EventStreamMessage) => { const stage = this.stages.get(stageName); - if (stage && msg.data.level !== 'DEBUG') { + if (stage && msg.data.level !== "DEBUG") { stage.events.push(msg); } this.refresh(); @@ -208,7 +212,7 @@ export class LogsTreeDataProvider implements TreeDataProvider { element.stages.forEach((stage: LogStage) => { result.push(new LogsTreeStageItem(stage)); }); - result.push(...element.events.map(e => new LogsTreeLogItem(e))); + result.push(...element.events.map((e) => new LogsTreeLogItem(e))); return result; } @@ -223,13 +227,13 @@ export class LogsTreeDataProvider implements TreeDataProvider { // Create a tree view with the specified view name and options context.subscriptions.push( window.createTreeView(viewName, { - treeDataProvider: this + treeDataProvider: this, }), commands.registerCommand(visitCommand, async (dashboardUrl: string) => { // This command is only attached to messages with a dashboardUrl field. const uri = Uri.parse(dashboardUrl, true); await env.openExternal(uri); - }) + }), ); } } @@ -241,9 +245,10 @@ export class LogsTreeStageItem extends TreeItem { constructor(stage: LogStage) { let collapsibleState = stage.collapseState; if (collapsibleState === undefined) { - collapsibleState = stage.events.length || stage.stages.length ? - TreeItemCollapsibleState.Collapsed : - TreeItemCollapsibleState.None; + collapsibleState = + stage.events.length || stage.stages.length + ? TreeItemCollapsibleState.Collapsed + : TreeItemCollapsibleState.None; } super(stage.label, collapsibleState); @@ -256,19 +261,19 @@ export class LogsTreeStageItem extends TreeItem { setIcon(status: LogStageStatus) { switch (status) { case LogStageStatus.notStarted: - this.iconPath = new ThemeIcon('circle-large-outline'); + this.iconPath = new ThemeIcon("circle-large-outline"); break; case LogStageStatus.neverStarted: - this.iconPath = new ThemeIcon('circle-slash'); + this.iconPath = new ThemeIcon("circle-slash"); break; case LogStageStatus.inProgress: - this.iconPath = new ThemeIcon('loading~spin'); + this.iconPath = new ThemeIcon("loading~spin"); break; case LogStageStatus.completed: - this.iconPath = new ThemeIcon('check'); + this.iconPath = new ThemeIcon("check"); break; case LogStageStatus.failed: - this.iconPath = new ThemeIcon('error'); + this.iconPath = new ThemeIcon("error"); break; } } @@ -280,16 +285,16 @@ export class LogsTreeStageItem extends TreeItem { export class LogsTreeLogItem extends TreeItem { constructor( msg: EventStreamMessage, - state: TreeItemCollapsibleState = TreeItemCollapsibleState.None + state: TreeItemCollapsibleState = TreeItemCollapsibleState.None, ) { super(displayEventStreamMessage(msg), state); this.tooltip = JSON.stringify(msg); if (msg.data.dashboardUrl !== undefined) { this.command = { - title: 'Visit', + title: "Visit", command: visitCommand, - arguments: [msg.data.dashboardUrl] + arguments: [msg.data.dashboardUrl], }; } } diff --git a/extensions/vscode/src/views/project.ts b/extensions/vscode/src/views/project.ts index 2b8d03613..cd6affce4 100644 --- a/extensions/vscode/src/views/project.ts +++ b/extensions/vscode/src/views/project.ts @@ -6,35 +6,37 @@ import { ProviderResult, ExtensionContext, window, -} from 'vscode'; +} from "vscode"; -const viewName = 'posit.publisher.project'; +const viewName = "posit.publisher.project"; -export class ProjectTreeDataProvider implements TreeDataProvider { - - constructor() { } +export class ProjectTreeDataProvider + implements TreeDataProvider +{ + constructor() {} getTreeItem(element: ProjectTreeItem): TreeItem | Thenable { return element; } - getChildren(_: ProjectTreeItem | undefined): ProviderResult { + getChildren( + _: ProjectTreeItem | undefined, + ): ProviderResult { return []; } public register(context: ExtensionContext) { context.subscriptions.push( - window.createTreeView(viewName, { treeDataProvider: this }) + window.createTreeView(viewName, { treeDataProvider: this }), ); } } export class ProjectTreeItem extends TreeItem { - constructor(itemString: string) { super(itemString); } - contextValue = 'posit.publisher.project.tree.item'; - tooltip = 'This is a \nProject Tree Item'; + contextValue = "posit.publisher.project.tree.item"; + tooltip = "This is a \nProject Tree Item"; } diff --git a/extensions/vscode/src/views/requirements.ts b/extensions/vscode/src/views/requirements.ts index 410e50f3f..75726fd2a 100644 --- a/extensions/vscode/src/views/requirements.ts +++ b/extensions/vscode/src/views/requirements.ts @@ -14,29 +14,34 @@ import { commands, window, workspace, -} from 'vscode'; - -import { isAxiosError } from 'axios'; -import api from '../api'; -import { confirmOverwrite } from '../dialogs'; -import { getSummaryStringFromError } from '../utils/errors'; -import { fileExists } from '../utils/files'; - -const viewName = 'posit.publisher.requirements'; -const editCommand = viewName + '.edit'; -const refreshCommand = viewName + '.refresh'; -const scanCommand = viewName + '.scan'; -const contextIsEmpty = viewName + '.isEmpty'; -const fileStore = 'requirements.txt'; - -type RequirementsEventEmitter = EventEmitter; +} from "vscode"; + +import { isAxiosError } from "axios"; +import api from "../api"; +import { confirmOverwrite } from "../dialogs"; +import { getSummaryStringFromError } from "../utils/errors"; +import { fileExists } from "../utils/files"; + +const viewName = "posit.publisher.requirements"; +const editCommand = viewName + ".edit"; +const refreshCommand = viewName + ".refresh"; +const scanCommand = viewName + ".scan"; +const contextIsEmpty = viewName + ".isEmpty"; +const fileStore = "requirements.txt"; + +type RequirementsEventEmitter = EventEmitter< + RequirementsTreeItem | undefined | void +>; type RequirementsEvent = Event; -export class RequirementsTreeDataProvider implements TreeDataProvider { +export class RequirementsTreeDataProvider + implements TreeDataProvider +{ private root: WorkspaceFolder | undefined; private fileUri: Uri | undefined; private _onDidChangeTreeData: RequirementsEventEmitter = new EventEmitter(); - readonly onDidChangeTreeData: RequirementsEvent = this._onDidChangeTreeData.event; + readonly onDidChangeTreeData: RequirementsEvent = + this._onDidChangeTreeData.event; constructor() { const workspaceFolders = workspace.workspaceFolders; @@ -50,7 +55,9 @@ export class RequirementsTreeDataProvider implements TreeDataProvider { + async getChildren( + element: RequirementsTreeItem | undefined, + ): Promise { if (element !== undefined) { // Requirements items have no children. return []; @@ -62,14 +69,19 @@ export class RequirementsTreeDataProvider implements TreeDataProvider new RequirementsTreeItem(line)); + return response.data.requirements.map( + (line) => new RequirementsTreeItem(line), + ); } catch (error: unknown) { if (isAxiosError(error) && error.response?.status === 404) { // No requirements file; show the welcome view. await this.setContextIsEmpty(true); return []; } else { - const summary = getSummaryStringFromError('requirements::getChildren', error); + const summary = getSummaryStringFromError( + "requirements::getChildren", + error, + ); window.showInformationMessage(summary); return []; } @@ -77,19 +89,23 @@ export class RequirementsTreeDataProvider implements TreeDataProvider { - await commands.executeCommand('setContext', contextIsEmpty, isEmpty ? "empty" : "notEmpty"); + await commands.executeCommand( + "setContext", + contextIsEmpty, + isEmpty ? "empty" : "notEmpty", + ); } public register(context: ExtensionContext) { context.subscriptions.push( - window.createTreeView(viewName, { treeDataProvider: this }) + window.createTreeView(viewName, { treeDataProvider: this }), ); if (this.root !== undefined) { context.subscriptions.push( commands.registerCommand(editCommand, this.edit), commands.registerCommand(refreshCommand, this.refresh), commands.registerCommand(scanCommand, this.scan), - this.createFileSystemWatcher(this.root) + this.createFileSystemWatcher(this.root), ); } } @@ -101,7 +117,7 @@ export class RequirementsTreeDataProvider implements TreeDataProvider { this._onDidChangeTreeData.fire(); @@ -113,7 +129,9 @@ export class RequirementsTreeDataProvider implements TreeDataProvider { if (this.fileUri !== undefined) { - await commands.executeCommand('vscode.open', this.fileUri); + await commands.executeCommand("vscode.open", this.fileUri); } }; } export class RequirementsTreeItem extends TreeItem { - constructor(itemString: string) { super(itemString); - if (itemString.startsWith('-')) { + if (itemString.startsWith("-")) { // Looks like a pip configuration parameter, e.g. --index-url - this.iconPath = new ThemeIcon('gear'); + this.iconPath = new ThemeIcon("gear"); } else { - this.iconPath = new ThemeIcon('package'); + this.iconPath = new ThemeIcon("package"); } } - contextValue = 'posit.publisher.dependencies.tree.item'; - tooltip = ''; + contextValue = "posit.publisher.dependencies.tree.item"; + tooltip = ""; } diff --git a/extensions/vscode/src/workspaces.ts b/extensions/vscode/src/workspaces.ts index 8479192de..d5b4370be 100644 --- a/extensions/vscode/src/workspaces.ts +++ b/extensions/vscode/src/workspaces.ts @@ -1,6 +1,6 @@ // Copyright (C) 2024 by Posit Software, PBC. -import * as vscode from 'vscode'; +import * as vscode from "vscode"; export const path = (): string | undefined => { return vscode.workspace.workspaceFolders?.at(0)?.uri.fsPath; diff --git a/extensions/vscode/tsconfig.json b/extensions/vscode/tsconfig.json index d9e51335c..39b8f1875 100644 --- a/extensions/vscode/tsconfig.json +++ b/extensions/vscode/tsconfig.json @@ -4,10 +4,7 @@ "module": "commonjs", "target": "ES2020", "outDir": "out", - "lib": [ - "ES2020", - "DOM" - ], + "lib": ["ES2020", "DOM"], "sourceMap": true, "rootDir": "src", /* Linting */ @@ -16,5 +13,5 @@ "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, "noImplicitReturns": true - }, + } } diff --git a/extensions/vscode/vsc-extension-quickstart.md b/extensions/vscode/vsc-extension-quickstart.md index ab627c448..7a7bef716 100644 --- a/extensions/vscode/vsc-extension-quickstart.md +++ b/extensions/vscode/vsc-extension-quickstart.md @@ -2,41 +2,41 @@ ## What's in the folder -* This folder contains all of the files necessary for your extension. -* `package.json` - this is the manifest file in which you declare your extension and command. - * The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. -* `src/extension.ts` - this is the main file where you will provide the implementation of your command. - * The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. - * We pass the function containing the implementation of the command as the second parameter to `registerCommand`. +- This folder contains all of the files necessary for your extension. +- `package.json` - this is the manifest file in which you declare your extension and command. + - The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. +- `src/extension.ts` - this is the main file where you will provide the implementation of your command. + - The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. + - We pass the function containing the implementation of the command as the second parameter to `registerCommand`. ## Get up and running straight away -* Press `F5` to open a new window with your extension loaded. -* Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello, Publish`. -* Set breakpoints in your code inside `src/extension.ts` to debug your extension. -* Find output from your extension in the debug console. +- Press `F5` to open a new window with your extension loaded. +- Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello, Publish`. +- Set breakpoints in your code inside `src/extension.ts` to debug your extension. +- Find output from your extension in the debug console. ## Make changes -* You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`. -* You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. +- You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`. +- You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. ## Explore the API -* You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`. +- You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`. ## Run tests -* Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Extension Tests`. -* Press `F5` to run the tests in a new window with your extension loaded. -* See the output of the test result in the debug console. -* Make changes to `src/test/suite/extension.test.ts` or create new test files inside the `test/suite` folder. - * The provided test runner will only consider files matching the name pattern `**.test.ts`. - * You can create folders inside the `test` folder to structure your tests any way you want. +- Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Extension Tests`. +- Press `F5` to run the tests in a new window with your extension loaded. +- See the output of the test result in the debug console. +- Make changes to `src/test/suite/extension.test.ts` or create new test files inside the `test/suite` folder. + - The provided test runner will only consider files matching the name pattern `**.test.ts`. + - You can create folders inside the `test` folder to structure your tests any way you want. ## Go further -* [Follow UX guidelines](https://code.visualstudio.com/api/ux-guidelines/overview) to create extensions that seamlessly integrate with VS Code's native interface and patterns. - * Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension). - * [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VS Code extension marketplace. - * Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration). +- [Follow UX guidelines](https://code.visualstudio.com/api/ux-guidelines/overview) to create extensions that seamlessly integrate with VS Code's native interface and patterns. +- Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension). +- [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VS Code extension marketplace. +- Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration).