diff --git a/.github/workflows/vscode-extension-samples.yml b/.github/workflows/vscode-extension-samples.yml new file mode 100644 index 0000000..4ee538d --- /dev/null +++ b/.github/workflows/vscode-extension-samples.yml @@ -0,0 +1,45 @@ +name: VSCode Extension Samples CI + +on: + pull_request: + paths: + - .github/workflows/vscode-extension-samples.yml + - vscode-extension-samples/** + schedule: + - cron: '0 10 * * *' + +jobs: + build: + runs-on: ubuntu-latest + defaults: + run: + working-directory: vscode-extension-samples/${{ matrix.sample }}-sample + + strategy: + fail-fast: false + matrix: + sample: ['menu-item', 'tree-view', 'uss-profile', 'vue-webview'] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Use Node.js LTS + uses: actions/setup-node@v4 + with: + node-version: lts/* + + - name: Install Dependencies + run: npm install + + - name: Build Source + run: npm run compile + + - name: Package VSIX + run: npx vsce package + + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.sample }}-sample + path: vscode-extension-samples/${{ matrix.sample }}-sample/*.vsix diff --git a/README.md b/README.md index 500006a..1e2fe90 100644 --- a/README.md +++ b/README.md @@ -5,4 +5,4 @@ This repository contains sample code demonstrating Zowe client tools. ## Samples - [Zowe Python SDK Sample](./python-sdk-sample) - +- [Zowe VS Code Extension Samples](./vscode-extension-samples) diff --git a/vscode-extension-samples/.gitignore b/vscode-extension-samples/.gitignore new file mode 100644 index 0000000..d8b83df --- /dev/null +++ b/vscode-extension-samples/.gitignore @@ -0,0 +1 @@ +package-lock.json diff --git a/vscode-extension-samples/README.md b/vscode-extension-samples/README.md new file mode 100644 index 0000000..5e29f19 --- /dev/null +++ b/vscode-extension-samples/README.md @@ -0,0 +1,27 @@ +# Zowe VS Code Extension Samples + +This folder contains sample VS Code extensions that demonstrate using [Zowe Explorer API](https://www.npmjs.com/package/@zowe/zowe-explorer-api) to extend the capabilities of [Zowe Explorer for VS Code](https://marketplace.visualstudio.com/items?itemName=Zowe.vscode-extension-for-zowe). + +--- + +## menu-item-sample + +Demonstrates adding a new command to the context menu shown when a tree item is right-clicked in Zowe Explorer. + +--- + +## tree-view-sample + +Demonstrates adding a new tree view to Zowe Explorer alongside data sets, USS, and jobs. + +--- + +## uss-profile-sample + +Demonstrates adding support for a new profile type to the USS tree in Zowe Explorer. + +--- + +## vue-webview-sample + +Demonstrates the use of the `WebView` class from Zowe Explorer API to create a webview panel, powered by the Vite bundler and Vue JavaScript framework. diff --git a/vscode-extension-samples/menu-item-sample/.eslintrc.js b/vscode-extension-samples/menu-item-sample/.eslintrc.js new file mode 100644 index 0000000..5e0416f --- /dev/null +++ b/vscode-extension-samples/menu-item-sample/.eslintrc.js @@ -0,0 +1,15 @@ +/**@type {import('eslint').Linter.Config} */ +// eslint-disable-next-line no-undef +module.exports = { + root: true, + parser: "@typescript-eslint/parser", + plugins: ["@typescript-eslint"], + extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], + rules: { + semi: [2, "always"], + "@typescript-eslint/no-unused-vars": 0, + "@typescript-eslint/no-explicit-any": 0, + "@typescript-eslint/explicit-module-boundary-types": 0, + "@typescript-eslint/no-non-null-assertion": 0, + }, +}; diff --git a/vscode-extension-samples/menu-item-sample/.gitignore b/vscode-extension-samples/menu-item-sample/.gitignore new file mode 100644 index 0000000..5fe00fe --- /dev/null +++ b/vscode-extension-samples/menu-item-sample/.gitignore @@ -0,0 +1,4 @@ +out +node_modules +.vscode-test/ +*.vsix diff --git a/vscode-extension-samples/menu-item-sample/.vscode/launch.json b/vscode-extension-samples/menu-item-sample/.vscode/launch.json new file mode 100644 index 0000000..e3dea5a --- /dev/null +++ b/vscode-extension-samples/menu-item-sample/.vscode/launch.json @@ -0,0 +1,18 @@ +// A launch configuration that compiles the extension and then opens it inside a new window +// Use IntelliSense to learn about possible attributes. +// Hover to view descriptions of existing attributes. +// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": ["--extensionDevelopmentPath=${workspaceFolder}"], + "outFiles": ["${workspaceFolder}/out/**/*.js"], + "preLaunchTask": "npm: watch" + } + ] +} diff --git a/vscode-extension-samples/menu-item-sample/.vscode/tasks.json b/vscode-extension-samples/menu-item-sample/.vscode/tasks.json new file mode 100644 index 0000000..078ff7e --- /dev/null +++ b/vscode-extension-samples/menu-item-sample/.vscode/tasks.json @@ -0,0 +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 + } + } + ] +} diff --git a/vscode-extension-samples/menu-item-sample/README.md b/vscode-extension-samples/menu-item-sample/README.md new file mode 100644 index 0000000..dc7d8c4 --- /dev/null +++ b/vscode-extension-samples/menu-item-sample/README.md @@ -0,0 +1,14 @@ +# Menu Item Sample + +Demonstrates adding a new command to the context menu shown when a tree item is right-clicked in Zowe Explorer. + +The `contributes` section of "package.json" defines a menu item named "Show Node Context" for all tree views that have an ID starting with `zowe.`. + +In "extension.ts" a command is registered which runs when the menu item is clicked and displays the associated [`TreeItem.contextValue`](https://code.visualstudio.com/api/references/vscode-api#TreeItem). + +## Running the sample + +- Open this sample in VS Code +- `pnpm i` +- `pnpm run compile` +- `F5` to start debugging diff --git a/vscode-extension-samples/menu-item-sample/package.json b/vscode-extension-samples/menu-item-sample/package.json new file mode 100644 index 0000000..618f039 --- /dev/null +++ b/vscode-extension-samples/menu-item-sample/package.json @@ -0,0 +1,59 @@ +{ + "name": "menu-item-sample", + "displayName": "menu-item-sample", + "description": "Menu item sample for Zowe Explorer", + "version": "0.0.1", + "publisher": "Zowe", + "repository": "https://github.com/zowe/zowe-explorer-vscode/samples/menu-item-sample", + "engines": { + "vscode": "^1.79.0" + }, + "categories": [ + "Other" + ], + "activationEvents": [], + "main": "./out/extension.js", + "contributes": { + "commands": [ + { + "command": "menu-item-sample.showNodeContext", + "title": "Show Node Context" + } + ], + "menus": { + "commandPalette": [ + { + "command": "menu-item-sample.showNodeContext", + "when": "never" + } + ], + "view/item/context": [ + { + "when": "view =~ /^zowe\\./", + "command": "menu-item-sample.showNodeContext", + "group": "100_zowe_menuItemSample@0" + } + ] + } + }, + "extensionDependencies": [ + "Zowe.vscode-extension-for-zowe" + ], + "scripts": { + "vscode:prepublish": "pnpm run compile", + "compile": "tsc -p ./", + "lint": "eslint \"src/**/*.ts\"", + "watch": "tsc -watch -p ./" + }, + "dependencies": { + "@zowe/zowe-explorer-api": "^3.0.0" + }, + "devDependencies": { + "@types/node": "^18.19.14", + "@types/vscode": "^1.53.2", + "@typescript-eslint/eslint-plugin": "^5.42.0", + "@typescript-eslint/parser": "^5.42.0", + "eslint": "^8.26.0", + "typescript": "^5.1.3" + } +} diff --git a/vscode-extension-samples/menu-item-sample/src/extension.ts b/vscode-extension-samples/menu-item-sample/src/extension.ts new file mode 100644 index 0000000..795622f --- /dev/null +++ b/vscode-extension-samples/menu-item-sample/src/extension.ts @@ -0,0 +1,23 @@ +// 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 { IZoweTreeNode } from "@zowe/zowe-explorer-api"; + +// This method is called when your extension is activated +// Your extension is activated the very first time the command is executed +export function activate(context: vscode.ExtensionContext) { + // Use the console to output diagnostic information (console.log) and errors (console.error) + // This line of code will only be executed once when your extension is activated + console.log('Congratulations, your extension "menu-item-sample" is now active!'); + + // The command has been defined in the package.json file + // Now provide the implementation of the command with registerCommand + // The commandId parameter must match the command field in package.json + const disposable = vscode.commands.registerCommand("menu-item-sample.showNodeContext", (node: IZoweTreeNode) => { + // The code you place here will be executed every time your command is executed + // Display a message box to the user + vscode.window.showInformationMessage(node.contextValue as string); + }); + + context.subscriptions.push(disposable); +} diff --git a/vscode-extension-samples/menu-item-sample/tsconfig.json b/vscode-extension-samples/menu-item-sample/tsconfig.json new file mode 100644 index 0000000..52cbd38 --- /dev/null +++ b/vscode-extension-samples/menu-item-sample/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2020", + "lib": ["es2020"], + "outDir": "out", + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "rootDir": "src" + }, + "exclude": ["node_modules", ".vscode-test"] +} diff --git a/vscode-extension-samples/tree-view-sample/.eslintrc.js b/vscode-extension-samples/tree-view-sample/.eslintrc.js new file mode 100644 index 0000000..5e0416f --- /dev/null +++ b/vscode-extension-samples/tree-view-sample/.eslintrc.js @@ -0,0 +1,15 @@ +/**@type {import('eslint').Linter.Config} */ +// eslint-disable-next-line no-undef +module.exports = { + root: true, + parser: "@typescript-eslint/parser", + plugins: ["@typescript-eslint"], + extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], + rules: { + semi: [2, "always"], + "@typescript-eslint/no-unused-vars": 0, + "@typescript-eslint/no-explicit-any": 0, + "@typescript-eslint/explicit-module-boundary-types": 0, + "@typescript-eslint/no-non-null-assertion": 0, + }, +}; diff --git a/vscode-extension-samples/tree-view-sample/.gitignore b/vscode-extension-samples/tree-view-sample/.gitignore new file mode 100644 index 0000000..5fe00fe --- /dev/null +++ b/vscode-extension-samples/tree-view-sample/.gitignore @@ -0,0 +1,4 @@ +out +node_modules +.vscode-test/ +*.vsix diff --git a/vscode-extension-samples/tree-view-sample/.vscode/launch.json b/vscode-extension-samples/tree-view-sample/.vscode/launch.json new file mode 100644 index 0000000..e3dea5a --- /dev/null +++ b/vscode-extension-samples/tree-view-sample/.vscode/launch.json @@ -0,0 +1,18 @@ +// A launch configuration that compiles the extension and then opens it inside a new window +// Use IntelliSense to learn about possible attributes. +// Hover to view descriptions of existing attributes. +// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": ["--extensionDevelopmentPath=${workspaceFolder}"], + "outFiles": ["${workspaceFolder}/out/**/*.js"], + "preLaunchTask": "npm: watch" + } + ] +} diff --git a/vscode-extension-samples/tree-view-sample/.vscode/tasks.json b/vscode-extension-samples/tree-view-sample/.vscode/tasks.json new file mode 100644 index 0000000..078ff7e --- /dev/null +++ b/vscode-extension-samples/tree-view-sample/.vscode/tasks.json @@ -0,0 +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 + } + } + ] +} diff --git a/vscode-extension-samples/tree-view-sample/README.md b/vscode-extension-samples/tree-view-sample/README.md new file mode 100644 index 0000000..a40beba --- /dev/null +++ b/vscode-extension-samples/tree-view-sample/README.md @@ -0,0 +1,14 @@ +# Tree View Sample + +Demonstrates adding a new tree view to Zowe Explorer alongside data sets, USS, and jobs. + +The `contributes` section of "package.json" defines a tree view named "Profiles" that will show inside the Zowe Explorer sidebar panel. + +In "extension.ts" the tree view is configured to use [`ProfilesTreeProvider`](/samples/tree-view-sample/src/ProfilesTreeProvider.ts) as a data provider which retrieves a list of available Zowe profiles. + +## Running the sample + +- Open this sample in VS Code +- `pnpm i` +- `pnpm run compile` +- `F5` to start debugging diff --git a/vscode-extension-samples/tree-view-sample/package.json b/vscode-extension-samples/tree-view-sample/package.json new file mode 100644 index 0000000..500f006 --- /dev/null +++ b/vscode-extension-samples/tree-view-sample/package.json @@ -0,0 +1,62 @@ +{ + "name": "tree-view-sample", + "displayName": "tree-view-sample", + "description": "Tree view sample for Zowe Explorer", + "version": "0.0.1", + "publisher": "Zowe", + "repository": "https://github.com/zowe/zowe-explorer-vscode/samples/tree-view-sample", + "engines": { + "vscode": "^1.79.0" + }, + "categories": [ + "Other" + ], + "activationEvents": [], + "main": "./out/extension.js", + "contributes": { + "commands": [ + { + "command": "tree-view-sample.refresh", + "title": "Refresh View", + "icon": "$(refresh)" + } + ], + "menus": { + "view/title": [ + { + "command": "tree-view-sample.refresh", + "when": "view == tree-view-sample.profiles", + "group": "navigation" + } + ] + }, + "views": { + "zowe": [ + { + "id": "tree-view-sample.profiles", + "name": "profiles" + } + ] + } + }, + "extensionDependencies": [ + "Zowe.vscode-extension-for-zowe" + ], + "scripts": { + "vscode:prepublish": "pnpm run compile", + "compile": "tsc -p ./", + "lint": "eslint \"src/**/*.ts\"", + "watch": "tsc -watch -p ./" + }, + "dependencies": { + "@zowe/zowe-explorer-api": "^3.0.0" + }, + "devDependencies": { + "@types/node": "^18.19.14", + "@types/vscode": "^1.53.2", + "@typescript-eslint/eslint-plugin": "^5.42.0", + "@typescript-eslint/parser": "^5.42.0", + "eslint": "^8.26.0", + "typescript": "^5.1.3" + } +} diff --git a/vscode-extension-samples/tree-view-sample/src/ProfilesTreeProvider.ts b/vscode-extension-samples/tree-view-sample/src/ProfilesTreeProvider.ts new file mode 100644 index 0000000..b1241fb --- /dev/null +++ b/vscode-extension-samples/tree-view-sample/src/ProfilesTreeProvider.ts @@ -0,0 +1,60 @@ +import * as vscode from "vscode"; +import { ProfilesCache, imperative } from "@zowe/zowe-explorer-api"; + +class ProfilesNode extends vscode.TreeItem { + constructor( + public readonly label: string, + public readonly collapsibleState: vscode.TreeItemCollapsibleState, + public readonly command?: vscode.Command + ) { + super(label, collapsibleState); + this.iconPath = collapsibleState === vscode.TreeItemCollapsibleState.None ? vscode.ThemeIcon.File : vscode.ThemeIcon.Folder; + } +} + +export class ProfilesTreeProvider implements vscode.TreeDataProvider { + private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); + public readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; + private _dirty = true; + private _profileData: imperative.IProfAttrs[] = []; + + public constructor() { + vscode.workspace.onDidChangeWorkspaceFolders(this.refresh.bind(this)); + } + + public refresh(): void { + this._dirty = false; + this._onDidChangeTreeData.fire(); + } + + public getTreeItem(node: ProfilesNode): ProfilesNode { + return node; + } + + public async getChildren(node?: ProfilesNode): Promise { + if (this._dirty) { + const profiles = new ProfilesCache(imperative.Logger.getAppLogger(), vscode.workspace.workspaceFolders?.[0]?.uri.fsPath); + this._profileData = (await profiles.getProfileInfo()).getAllProfiles(); + this._dirty = false; + } + + const children: ProfilesNode[] = []; + if (node == null) { + for (const profType of new Set(this._profileData.map((profAttrs) => profAttrs.profType))) { + children.push(new ProfilesNode(profType, vscode.TreeItemCollapsibleState.Collapsed)); + } + } else { + const profType = node.label as string; + for (const profAttrs of new Set(this._profileData.filter((profAttrs) => profAttrs.profType === profType))) { + children.push( + new ProfilesNode(profAttrs.profName, vscode.TreeItemCollapsibleState.None, { + title: "Open Profile in Editor", + command: "vscode.open", + arguments: profAttrs.profLoc.osLoc, + }) + ); + } + } + return Promise.resolve(children); + } +} diff --git a/vscode-extension-samples/tree-view-sample/src/extension.ts b/vscode-extension-samples/tree-view-sample/src/extension.ts new file mode 100644 index 0000000..2f086d0 --- /dev/null +++ b/vscode-extension-samples/tree-view-sample/src/extension.ts @@ -0,0 +1,16 @@ +// 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 { ProfilesTreeProvider } from "./ProfilesTreeProvider"; + +// This method is called when your extension is activated +// Your extension is activated the very first time the command is executed +export function activate(context: vscode.ExtensionContext) { + const treeDataProvider = new ProfilesTreeProvider(); + vscode.window.createTreeView("tree-view-sample.profiles", { treeDataProvider, showCollapseAll: true }); + + const disposable = vscode.commands.registerCommand("tree-view-sample.refresh", () => { + treeDataProvider.refresh(); + }); + context.subscriptions.push(disposable); +} diff --git a/vscode-extension-samples/tree-view-sample/tsconfig.json b/vscode-extension-samples/tree-view-sample/tsconfig.json new file mode 100644 index 0000000..52cbd38 --- /dev/null +++ b/vscode-extension-samples/tree-view-sample/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2020", + "lib": ["es2020"], + "outDir": "out", + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "rootDir": "src" + }, + "exclude": ["node_modules", ".vscode-test"] +} diff --git a/vscode-extension-samples/uss-profile-sample/.eslintrc.js b/vscode-extension-samples/uss-profile-sample/.eslintrc.js new file mode 100644 index 0000000..5e0416f --- /dev/null +++ b/vscode-extension-samples/uss-profile-sample/.eslintrc.js @@ -0,0 +1,15 @@ +/**@type {import('eslint').Linter.Config} */ +// eslint-disable-next-line no-undef +module.exports = { + root: true, + parser: "@typescript-eslint/parser", + plugins: ["@typescript-eslint"], + extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], + rules: { + semi: [2, "always"], + "@typescript-eslint/no-unused-vars": 0, + "@typescript-eslint/no-explicit-any": 0, + "@typescript-eslint/explicit-module-boundary-types": 0, + "@typescript-eslint/no-non-null-assertion": 0, + }, +}; diff --git a/vscode-extension-samples/uss-profile-sample/.gitignore b/vscode-extension-samples/uss-profile-sample/.gitignore new file mode 100644 index 0000000..5fe00fe --- /dev/null +++ b/vscode-extension-samples/uss-profile-sample/.gitignore @@ -0,0 +1,4 @@ +out +node_modules +.vscode-test/ +*.vsix diff --git a/vscode-extension-samples/uss-profile-sample/.vscode/launch.json b/vscode-extension-samples/uss-profile-sample/.vscode/launch.json new file mode 100644 index 0000000..e3dea5a --- /dev/null +++ b/vscode-extension-samples/uss-profile-sample/.vscode/launch.json @@ -0,0 +1,18 @@ +// A launch configuration that compiles the extension and then opens it inside a new window +// Use IntelliSense to learn about possible attributes. +// Hover to view descriptions of existing attributes. +// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": ["--extensionDevelopmentPath=${workspaceFolder}"], + "outFiles": ["${workspaceFolder}/out/**/*.js"], + "preLaunchTask": "npm: watch" + } + ] +} diff --git a/vscode-extension-samples/uss-profile-sample/.vscode/tasks.json b/vscode-extension-samples/uss-profile-sample/.vscode/tasks.json new file mode 100644 index 0000000..078ff7e --- /dev/null +++ b/vscode-extension-samples/uss-profile-sample/.vscode/tasks.json @@ -0,0 +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 + } + } + ] +} diff --git a/vscode-extension-samples/uss-profile-sample/README.md b/vscode-extension-samples/uss-profile-sample/README.md new file mode 100644 index 0000000..fd88eff --- /dev/null +++ b/vscode-extension-samples/uss-profile-sample/README.md @@ -0,0 +1,16 @@ +# USS Profile Sample + +Demonstrates adding support for a new profile type to the USS tree in Zowe Explorer. + +This samples adds SSH profiles to the Zowe Explorer USS tree, so that files can be managed on a mainframe or any Unix server that supports SFTP (FTP over SSH). + +In "extension.ts" the Zowe Explorer API is used to load SSH profiles, and [`SshUssApi`](/samples/uss-profile-sample/src/SshUssApi.ts) is registered to enable USS file operations such as listing, downloading, and uploading. + +**Warning:** This extension performs remote file operations and has not been thoroughly tested. Use at your own risk for testing purposes only. + +## Running the sample + +- Open this sample in VS Code +- `pnpm` +- `pnpm run compile` +- `F5` to start debugging diff --git a/vscode-extension-samples/uss-profile-sample/package.json b/vscode-extension-samples/uss-profile-sample/package.json new file mode 100644 index 0000000..7407cfd --- /dev/null +++ b/vscode-extension-samples/uss-profile-sample/package.json @@ -0,0 +1,52 @@ +{ + "name": "uss-profile-sample", + "displayName": "uss-profile-sample", + "description": "USS profile sample for Zowe Explorer", + "version": "0.0.1", + "publisher": "Zowe", + "repository": "https://github.com/zowe/zowe-explorer-vscode/samples/uss-profile-sample", + "engines": { + "vscode": "^1.79.0" + }, + "categories": [ + "Other" + ], + "activationEvents": [ + "onStartupFinished" + ], + "main": "./out/extension.js", + "contributes": { + "commands": [ + { + "command": "uss-profile-sample.helloWorld", + "title": "Hello World" + } + ] + }, + "extensionDependencies": [ + "Zowe.vscode-extension-for-zowe" + ], + "scripts": { + "vscode:prepublish": "pnpm run compile", + "compile": "tsc -p ./", + "lint": "eslint \"src/**/*.ts\"", + "watch": "tsc -watch -p ./" + }, + "dependencies": { + "@zowe/zowe-explorer-api": "^3.0.0", + "ssh2-sftp-client": "^9.1.0" + }, + "devDependencies": { + "@types/node": "^18.19.14", + "@types/ssh2-sftp-client": "^9.0.0", + "@types/vscode": "^1.53.2", + "@typescript-eslint/eslint-plugin": "^5.42.0", + "@typescript-eslint/parser": "^5.42.0", + "eslint": "^8.26.0", + "typescript": "^5.1.3" + }, + "peerDependencies": { + "@zowe/zos-files-for-zowe-sdk": "^8.0.0", + "@zowe/zos-uss-for-zowe-sdk": "^8.0.0" + } +} diff --git a/vscode-extension-samples/uss-profile-sample/src/SshUssApi.ts b/vscode-extension-samples/uss-profile-sample/src/SshUssApi.ts new file mode 100644 index 0000000..87ffea0 --- /dev/null +++ b/vscode-extension-samples/uss-profile-sample/src/SshUssApi.ts @@ -0,0 +1,126 @@ +import * as Client from "ssh2-sftp-client"; +import * as vscode from "vscode"; +import * as zosfiles from "@zowe/zos-files-for-zowe-sdk"; +import { ZosUssProfile } from "@zowe/zos-uss-for-zowe-sdk"; +import { imperative, MainframeInteraction } from "@zowe/zowe-explorer-api"; + +export class SshUssApi implements MainframeInteraction.IUss { + public constructor(public profile?: imperative.IProfileLoaded) {} + + public getProfileTypeName(): string { + return ZosUssProfile.type; + } + + public getSession(profile?: imperative.IProfileLoaded): imperative.Session { + const sessCfg: imperative.ISession = {}; + imperative.ConnectionPropsForSessCfg.resolveSessCfgProps(sessCfg, (profile || this.profile)?.profile as any); + return new imperative.Session(sessCfg); + } + + public async getStatus(profile: imperative.IProfileLoaded, profileType?: string): Promise { + if (profileType === ZosUssProfile.type) { + try { + return await this.withClient(this.getSession(profile), () => Promise.resolve("active")); + } catch (err) { + vscode.window.showErrorMessage((err as Error).toString()); + return Promise.resolve("inactive"); + } + } + return Promise.resolve("unverified"); + } + + public async fileList(ussFilePath: string): Promise { + return this.withClient(this.getSession(), async (client) => { + const response = []; + for (const fileInfo of await client.list(ussFilePath)) { + response.push({ + name: fileInfo.name, + mode: fileInfo.type + fileInfo.owner + fileInfo.group + fileInfo.rights.other, + size: fileInfo.size, + uid: fileInfo.owner, + gid: fileInfo.group, + mtime: fileInfo.modifyTime.toString(), + }); + } + return this.buildZosFilesResponse({ items: response }); + }); + } + + public isFileTagBinOrAscii(ussFilePath: string): Promise { + return Promise.resolve(false); + } + + public async getContents(ussFilePath: string, options: zosfiles.IDownloadOptions): Promise { + return this.withClient(this.getSession(), async (client) => { + const localPath = options.file as string; + imperative.IO.createDirsSyncFromFilePath(localPath); + const response = await client.fastGet(ussFilePath, localPath); + return this.buildZosFilesResponse(response); + }); + } + + public uploadFromBuffer(buffer: Buffer, filePath: string, _options?: zosfiles.IUploadOptions): Promise { + return this.withClient(this.getSession(), async (client) => { + const response = await client.put(buffer, filePath); + return this.buildZosFilesResponse(response); + }); + } + + public async putContent(inputFilePath: string, ussFilePath: string): Promise { + return this.withClient(this.getSession(), async (client) => { + const response = await client.fastPut(inputFilePath, ussFilePath); + return this.buildZosFilesResponse(response); + }); + } + + public async uploadDirectory( + inputDirectoryPath: string, + ussDirectoryPath: string, + options: zosfiles.IUploadOptions + ): Promise { + return this.withClient(this.getSession(), async (client) => { + const response = await client.uploadDir(inputDirectoryPath, ussDirectoryPath); + return this.buildZosFilesResponse(response); + }); + } + + public async create(ussPath: string, type: string, mode?: string | undefined): Promise { + return this.withClient(this.getSession(), async (client) => { + const response = type === "directory" ? await client.mkdir(ussPath) : await client.append(Buffer.from(""), ussPath, { mode }); + return this.buildZosFilesResponse(response); + }); + } + + public async delete(ussPath: string, recursive?: boolean | undefined): Promise { + return this.withClient(this.getSession(), async (client) => { + const response = recursive ? await client.rmdir(ussPath, true) : await client.delete(ussPath); + return this.buildZosFilesResponse(response); + }); + } + + public async rename(currentUssPath: string, newUssPath: string): Promise { + return this.withClient(this.getSession(), async (client) => { + const response = await client.rename(currentUssPath, newUssPath); + return this.buildZosFilesResponse(response); + }); + } + + private buildZosFilesResponse(apiResponse: any, success = true): zosfiles.IZosFilesResponse { + return { apiResponse, commandResponse: "", success }; + } + + private async withClient(session: imperative.Session, callback: (client: Client) => Promise): Promise { + const client = new Client(); + try { + await client.connect({ + host: session.ISession.hostname, + port: session.ISession.port, + username: session.ISession.user, + password: session.ISession.password, + }); + return await callback(client); + } finally { + await client.end(); + } + } +} diff --git a/vscode-extension-samples/uss-profile-sample/src/extension.ts b/vscode-extension-samples/uss-profile-sample/src/extension.ts new file mode 100644 index 0000000..4c3aa0b --- /dev/null +++ b/vscode-extension-samples/uss-profile-sample/src/extension.ts @@ -0,0 +1,19 @@ +// 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 { ZosUssProfile } from "@zowe/zos-uss-for-zowe-sdk"; +import { ZoweVsCodeExtension } from "@zowe/zowe-explorer-api"; +import { SshUssApi } from "./SshUssApi"; + +// This method is called when your extension is activated +// Your extension is activated the very first time the command is executed +export async function activate(context: vscode.ExtensionContext) { + const zoweExplorerApi = ZoweVsCodeExtension.getZoweExplorerApi(); + if (zoweExplorerApi != null) { + zoweExplorerApi.registerUssApi(new SshUssApi()); + await zoweExplorerApi.getExplorerExtenderApi().initForZowe("ssh", [ZosUssProfile]); + await zoweExplorerApi.getExplorerExtenderApi().reloadProfiles("ssh"); + } else { + vscode.window.showErrorMessage("Could not access Zowe Explorer API. Please check that the latest version of Zowe Explorer is installed."); + } +} diff --git a/vscode-extension-samples/uss-profile-sample/tsconfig.json b/vscode-extension-samples/uss-profile-sample/tsconfig.json new file mode 100644 index 0000000..52cbd38 --- /dev/null +++ b/vscode-extension-samples/uss-profile-sample/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2020", + "lib": ["es2020"], + "outDir": "out", + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "rootDir": "src" + }, + "exclude": ["node_modules", ".vscode-test"] +} diff --git a/vscode-extension-samples/vue-webview-sample/.eslintrc.js b/vscode-extension-samples/vue-webview-sample/.eslintrc.js new file mode 100644 index 0000000..5e0416f --- /dev/null +++ b/vscode-extension-samples/vue-webview-sample/.eslintrc.js @@ -0,0 +1,15 @@ +/**@type {import('eslint').Linter.Config} */ +// eslint-disable-next-line no-undef +module.exports = { + root: true, + parser: "@typescript-eslint/parser", + plugins: ["@typescript-eslint"], + extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], + rules: { + semi: [2, "always"], + "@typescript-eslint/no-unused-vars": 0, + "@typescript-eslint/no-explicit-any": 0, + "@typescript-eslint/explicit-module-boundary-types": 0, + "@typescript-eslint/no-non-null-assertion": 0, + }, +}; diff --git a/vscode-extension-samples/vue-webview-sample/.gitignore b/vscode-extension-samples/vue-webview-sample/.gitignore new file mode 100644 index 0000000..5fe00fe --- /dev/null +++ b/vscode-extension-samples/vue-webview-sample/.gitignore @@ -0,0 +1,4 @@ +out +node_modules +.vscode-test/ +*.vsix diff --git a/vscode-extension-samples/vue-webview-sample/.vscode/launch.json b/vscode-extension-samples/vue-webview-sample/.vscode/launch.json new file mode 100644 index 0000000..e3dea5a --- /dev/null +++ b/vscode-extension-samples/vue-webview-sample/.vscode/launch.json @@ -0,0 +1,18 @@ +// A launch configuration that compiles the extension and then opens it inside a new window +// Use IntelliSense to learn about possible attributes. +// Hover to view descriptions of existing attributes. +// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": ["--extensionDevelopmentPath=${workspaceFolder}"], + "outFiles": ["${workspaceFolder}/out/**/*.js"], + "preLaunchTask": "npm: watch" + } + ] +} diff --git a/vscode-extension-samples/vue-webview-sample/.vscode/tasks.json b/vscode-extension-samples/vue-webview-sample/.vscode/tasks.json new file mode 100644 index 0000000..078ff7e --- /dev/null +++ b/vscode-extension-samples/vue-webview-sample/.vscode/tasks.json @@ -0,0 +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 + } + } + ] +} diff --git a/vscode-extension-samples/vue-webview-sample/package.json b/vscode-extension-samples/vue-webview-sample/package.json new file mode 100644 index 0000000..da3897a --- /dev/null +++ b/vscode-extension-samples/vue-webview-sample/package.json @@ -0,0 +1,41 @@ +{ + "name": "vue-webview-sample", + "displayName": "vue-webview-sample", + "description": "Sample VSCode extension leveraging ZE API WebView class and a Vite-powered Vue demo", + "private": true, + "version": "0.0.1", + "engines": { + "vscode": "^1.79.0" + }, + "categories": [ + "Other" + ], + "activationEvents": [], + "main": "./out/extension.js", + "contributes": { + "commands": [ + { + "command": "extension.helloWorld", + "title": "Hello World" + } + ] + }, + "scripts": { + "vscode:prepublish": "pnpm run compile", + "compile": "pnpm --package=typescript dlx tsc -p ./ && cd webviews/vue-sample && pnpm --ignore-workspace i && pnpm build", + "lint": "eslint \"src/**/*.ts\"", + "watch": "pnpm --package=typescript dlx tsc -watch -p ./" + }, + "dependencies": { + "@zowe/zowe-explorer-api": "^3.0.0" + }, + "devDependencies": { + "@types/node": "^18.19.14", + "@types/vscode": "^1.53.2", + "@typescript-eslint/eslint-plugin": "^5.42.0", + "@typescript-eslint/parser": "^5.42.0", + "eslint": "^8.26.0", + "typescript": "^5.0.2", + "vue-tsc": "^1.8.8" + } +} diff --git a/vscode-extension-samples/vue-webview-sample/src/extension.ts b/vscode-extension-samples/vue-webview-sample/src/extension.ts new file mode 100644 index 0000000..a2c546d --- /dev/null +++ b/vscode-extension-samples/vue-webview-sample/src/extension.ts @@ -0,0 +1,16 @@ +import * as vscode from "vscode"; +import { WebView } from "@zowe/zowe-explorer-api"; + +export function activate(context: vscode.ExtensionContext) { + console.log('Congratulations, your extension "helloworld-sample" is now active!'); + + const disposable = vscode.commands.registerCommand("extension.helloWorld", () => { + const webview = new WebView("Sample Webview", "vue-sample", context, { + onDidReceiveMessage: (message: Record) => { + vscode.window.showInformationMessage(message.text); + }, + }); + }); + + context.subscriptions.push(disposable); +} diff --git a/vscode-extension-samples/vue-webview-sample/tsconfig.json b/vscode-extension-samples/vue-webview-sample/tsconfig.json new file mode 100644 index 0000000..786b1f2 --- /dev/null +++ b/vscode-extension-samples/vue-webview-sample/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2020", + "lib": ["es2020"], + "outDir": "out", + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "rootDir": "src" + }, + "exclude": ["node_modules", ".vscode-test", "webviews"] +} diff --git a/vscode-extension-samples/vue-webview-sample/webviews/vue-sample/index.html b/vscode-extension-samples/vue-webview-sample/webviews/vue-sample/index.html new file mode 100644 index 0000000..a5649bb --- /dev/null +++ b/vscode-extension-samples/vue-webview-sample/webviews/vue-sample/index.html @@ -0,0 +1,12 @@ + + + + + + Vite + Vue + TS + + +
+ + + diff --git a/vscode-extension-samples/vue-webview-sample/webviews/vue-sample/package.json b/vscode-extension-samples/vue-webview-sample/webviews/vue-sample/package.json new file mode 100644 index 0000000..cabcfb1 --- /dev/null +++ b/vscode-extension-samples/vue-webview-sample/webviews/vue-sample/package.json @@ -0,0 +1,21 @@ +{ + "name": "vue-sample", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@types/vscode-webview": "^1.57.1", + "vue": "^3.3.4" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^4.2.3", + "typescript": "~5.4.0", + "vite": "^4.5.5", + "vue-tsc": "^1.8.8" + } +} diff --git a/vscode-extension-samples/vue-webview-sample/webviews/vue-sample/src/App.vue b/vscode-extension-samples/vue-webview-sample/webviews/vue-sample/src/App.vue new file mode 100644 index 0000000..03d681a --- /dev/null +++ b/vscode-extension-samples/vue-webview-sample/webviews/vue-sample/src/App.vue @@ -0,0 +1,7 @@ + + + diff --git a/vscode-extension-samples/vue-webview-sample/webviews/vue-sample/src/components/HelloWorld.vue b/vscode-extension-samples/vue-webview-sample/webviews/vue-sample/src/components/HelloWorld.vue new file mode 100644 index 0000000..9bba458 --- /dev/null +++ b/vscode-extension-samples/vue-webview-sample/webviews/vue-sample/src/components/HelloWorld.vue @@ -0,0 +1,23 @@ + + + diff --git a/vscode-extension-samples/vue-webview-sample/webviews/vue-sample/src/main.ts b/vscode-extension-samples/vue-webview-sample/webviews/vue-sample/src/main.ts new file mode 100644 index 0000000..ade91c9 --- /dev/null +++ b/vscode-extension-samples/vue-webview-sample/webviews/vue-sample/src/main.ts @@ -0,0 +1,5 @@ +import { createApp } from "vue"; +import "./style.css"; +import App from "./App.vue"; + +createApp(App).mount("#webviewRoot"); diff --git a/vscode-extension-samples/vue-webview-sample/webviews/vue-sample/src/style.css b/vscode-extension-samples/vue-webview-sample/webviews/vue-sample/src/style.css new file mode 100644 index 0000000..8a645c5 --- /dev/null +++ b/vscode-extension-samples/vue-webview-sample/webviews/vue-sample/src/style.css @@ -0,0 +1,80 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +.card { + padding: 2em; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/vscode-extension-samples/vue-webview-sample/webviews/vue-sample/src/vite-env.d.ts b/vscode-extension-samples/vue-webview-sample/webviews/vue-sample/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/vscode-extension-samples/vue-webview-sample/webviews/vue-sample/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/vscode-extension-samples/vue-webview-sample/webviews/vue-sample/tsconfig.json b/vscode-extension-samples/vue-webview-sample/webviews/vue-sample/tsconfig.json new file mode 100644 index 0000000..fd693e5 --- /dev/null +++ b/vscode-extension-samples/vue-webview-sample/webviews/vue-sample/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "node", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/vscode-extension-samples/vue-webview-sample/webviews/vue-sample/tsconfig.node.json b/vscode-extension-samples/vue-webview-sample/webviews/vue-sample/tsconfig.node.json new file mode 100644 index 0000000..42872c5 --- /dev/null +++ b/vscode-extension-samples/vue-webview-sample/webviews/vue-sample/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/vscode-extension-samples/vue-webview-sample/webviews/vue-sample/vite.config.ts b/vscode-extension-samples/vue-webview-sample/webviews/vue-sample/vite.config.ts new file mode 100644 index 0000000..0c7e40e --- /dev/null +++ b/vscode-extension-samples/vue-webview-sample/webviews/vue-sample/vite.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from "vite"; +import vue from "@vitejs/plugin-vue"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [vue()], + build: { + rollupOptions: { + output: { + entryFileNames: `assets/[name].js`, + chunkFileNames: `assets/[name].js`, + assetFileNames: `assets/[name].[ext]`, + }, + }, + }, +});