From 130037a6ba0c31fbf86c9b34fc8e96eeaf475169 Mon Sep 17 00:00:00 2001 From: Bharat Middha <5100938+bmiddha@users.noreply.github.com> Date: Thu, 24 Jul 2025 14:50:29 -0700 Subject: [PATCH 1/9] Reimplement Certificate Manager VS Code Extension --- .vscode/launch.json | 13 ++ README.md | 4 - .../vscode-extension-publish.yaml | 32 ++- common/config/rush/version-policies.json | 10 - .../config/subspaces/default/pnpm-lock.yaml | 82 +------- .../src/VSCodeExtensionPackagePlugin.ts | 69 ++++--- .../src/CertificateStore.ts | 64 +++++- rush.json | 28 +-- .../.npmignore | 0 .../.vscodeignore | 0 .../LICENSE | 2 +- .../README.md | 7 + .../assets/extension-icon.png | Bin .../config/heft.json | 0 .../config/rig.json | 0 .../config/rush-project.json | 0 .../eslint.config.js | 0 .../package.json | 128 ++++++++++++ .../src/certificates.ts | 17 +- .../src/config.ts | 23 +-- .../src/constants.ts | 22 ++ .../src/extension.ts | 195 +++++++++--------- .../tsconfig.json | 0 .../webpack.config.js | 2 +- .../.vscodeignore | 5 - .../tls-sync-vscode-extension-pack/LICENSE | 24 --- .../tls-sync-vscode-extension-pack/README.md | 19 -- .../package.json | 45 ---- .../tls-sync-vscode-shared/.npmignore | 2 - .../tls-sync-vscode-shared/LICENSE | 24 --- .../tls-sync-vscode-shared/README.md | 3 - .../tls-sync-vscode-shared/config/rig.json | 5 - .../tls-sync-vscode-shared/eslint.config.js | 7 - .../tls-sync-vscode-shared/package.json | 29 --- .../tls-sync-vscode-shared/src/constants.ts | 34 --- .../tls-sync-vscode-shared/tsconfig.json | 3 - .../tls-sync-vscode-ui-extension/.npmignore | 2 - .../.vscodeignore | 8 - .../tls-sync-vscode-ui-extension/README.md | 7 - .../assets/extension-icon.png | Bin 19653 -> 0 bytes .../assets/walkthrough-settings.md | 1 - .../assets/walkthrough-sync.md | 1 - .../config/heft.json | 5 - .../config/rig.json | 5 - .../config/rush-project.json | 4 - .../tls-sync-vscode-ui-extension/package.json | 178 ---------------- .../webpack.config.js | 23 --- .../.npmignore | 2 - .../LICENSE | 24 --- .../README.md | 5 - .../assets/extension-icon.png | Bin 19653 -> 0 bytes .../config/heft.json | 5 - .../config/rig.json | 5 - .../config/rush-project.json | 4 - .../eslint.config.js | 7 - .../package.json | 67 ------ .../src/extension.ts | 104 ---------- .../tsconfig.json | 3 - 58 files changed, 408 insertions(+), 950 deletions(-) rename vscode-extensions/{tls-sync-vscode-extension-pack => debug-certificate-manager-vscode-extension}/.npmignore (100%) rename vscode-extensions/{tls-sync-vscode-workspace-extension => debug-certificate-manager-vscode-extension}/.vscodeignore (100%) rename vscode-extensions/{tls-sync-vscode-ui-extension => debug-certificate-manager-vscode-extension}/LICENSE (97%) create mode 100644 vscode-extensions/debug-certificate-manager-vscode-extension/README.md rename vscode-extensions/{tls-sync-vscode-extension-pack => debug-certificate-manager-vscode-extension}/assets/extension-icon.png (100%) rename vscode-extensions/{tls-sync-vscode-extension-pack => debug-certificate-manager-vscode-extension}/config/heft.json (100%) rename vscode-extensions/{tls-sync-vscode-extension-pack => debug-certificate-manager-vscode-extension}/config/rig.json (100%) rename vscode-extensions/{tls-sync-vscode-extension-pack => debug-certificate-manager-vscode-extension}/config/rush-project.json (100%) rename vscode-extensions/{tls-sync-vscode-ui-extension => debug-certificate-manager-vscode-extension}/eslint.config.js (100%) create mode 100644 vscode-extensions/debug-certificate-manager-vscode-extension/package.json rename vscode-extensions/{tls-sync-vscode-shared => debug-certificate-manager-vscode-extension}/src/certificates.ts (70%) rename vscode-extensions/{tls-sync-vscode-shared => debug-certificate-manager-vscode-extension}/src/config.ts (66%) create mode 100644 vscode-extensions/debug-certificate-manager-vscode-extension/src/constants.ts rename vscode-extensions/{tls-sync-vscode-ui-extension => debug-certificate-manager-vscode-extension}/src/extension.ts (56%) rename vscode-extensions/{tls-sync-vscode-ui-extension => debug-certificate-manager-vscode-extension}/tsconfig.json (100%) rename vscode-extensions/{tls-sync-vscode-workspace-extension => debug-certificate-manager-vscode-extension}/webpack.config.js (95%) delete mode 100644 vscode-extensions/tls-sync-vscode-extension-pack/.vscodeignore delete mode 100644 vscode-extensions/tls-sync-vscode-extension-pack/LICENSE delete mode 100644 vscode-extensions/tls-sync-vscode-extension-pack/README.md delete mode 100644 vscode-extensions/tls-sync-vscode-extension-pack/package.json delete mode 100644 vscode-extensions/tls-sync-vscode-shared/.npmignore delete mode 100644 vscode-extensions/tls-sync-vscode-shared/LICENSE delete mode 100644 vscode-extensions/tls-sync-vscode-shared/README.md delete mode 100644 vscode-extensions/tls-sync-vscode-shared/config/rig.json delete mode 100644 vscode-extensions/tls-sync-vscode-shared/eslint.config.js delete mode 100644 vscode-extensions/tls-sync-vscode-shared/package.json delete mode 100644 vscode-extensions/tls-sync-vscode-shared/src/constants.ts delete mode 100644 vscode-extensions/tls-sync-vscode-shared/tsconfig.json delete mode 100644 vscode-extensions/tls-sync-vscode-ui-extension/.npmignore delete mode 100644 vscode-extensions/tls-sync-vscode-ui-extension/.vscodeignore delete mode 100644 vscode-extensions/tls-sync-vscode-ui-extension/README.md delete mode 100644 vscode-extensions/tls-sync-vscode-ui-extension/assets/extension-icon.png delete mode 100644 vscode-extensions/tls-sync-vscode-ui-extension/assets/walkthrough-settings.md delete mode 100644 vscode-extensions/tls-sync-vscode-ui-extension/assets/walkthrough-sync.md delete mode 100644 vscode-extensions/tls-sync-vscode-ui-extension/config/heft.json delete mode 100644 vscode-extensions/tls-sync-vscode-ui-extension/config/rig.json delete mode 100644 vscode-extensions/tls-sync-vscode-ui-extension/config/rush-project.json delete mode 100644 vscode-extensions/tls-sync-vscode-ui-extension/package.json delete mode 100644 vscode-extensions/tls-sync-vscode-ui-extension/webpack.config.js delete mode 100644 vscode-extensions/tls-sync-vscode-workspace-extension/.npmignore delete mode 100644 vscode-extensions/tls-sync-vscode-workspace-extension/LICENSE delete mode 100644 vscode-extensions/tls-sync-vscode-workspace-extension/README.md delete mode 100644 vscode-extensions/tls-sync-vscode-workspace-extension/assets/extension-icon.png delete mode 100644 vscode-extensions/tls-sync-vscode-workspace-extension/config/heft.json delete mode 100644 vscode-extensions/tls-sync-vscode-workspace-extension/config/rig.json delete mode 100644 vscode-extensions/tls-sync-vscode-workspace-extension/config/rush-project.json delete mode 100644 vscode-extensions/tls-sync-vscode-workspace-extension/eslint.config.js delete mode 100644 vscode-extensions/tls-sync-vscode-workspace-extension/package.json delete mode 100644 vscode-extensions/tls-sync-vscode-workspace-extension/src/extension.ts delete mode 100644 vscode-extensions/tls-sync-vscode-workspace-extension/tsconfig.json diff --git a/.vscode/launch.json b/.vscode/launch.json index 196840c108c..b8293df3b70 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -85,6 +85,19 @@ "${workspaceFolder}/vscode-extensions/rush-vscode-extension/dist/**/*.js" ] // "preLaunchTask": "npm: build:watch - vscode-extensions/rush-vscode-extension" + }, + { + "name": "Launch Debug Certificate Manager VS Code Extension", + "type": "extensionHost", + "request": "launch", + "cwd": "${workspaceFolder}/vscode-extensions/debug-certificate-manager-vscode-extension/dist/vsix/unpacked", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}/vscode-extensions/debug-certificate-manager-vscode-extension/dist/vsix/unpacked" + ], + "sourceMaps": true, + "outFiles": [ + "${workspaceFolder}/vscode-extensions/debug-certificate-manager-vscode-extension/**" + ] } ] } diff --git a/README.md b/README.md index 2eb877f8fc5..47fd02c3af0 100644 --- a/README.md +++ b/README.md @@ -220,10 +220,6 @@ These GitHub repositories provide supplementary resources for Rush Stack: | [/rush-plugins/rush-litewatch-plugin](./rush-plugins/rush-litewatch-plugin/) | An experimental alternative approach for multi-project watch mode | | [/vscode-extensions/rush-vscode-command-webview](./vscode-extensions/rush-vscode-command-webview/) | Part of the Rush Stack VSCode extension, provides a UI for invoking Rush commands | | [/vscode-extensions/rush-vscode-extension](./vscode-extensions/rush-vscode-extension/) | Enhanced experience for monorepos that use the Rush Stack toolchain | -| [/vscode-extensions/tls-sync-vscode-extension-pack](./vscode-extensions/tls-sync-vscode-extension-pack/) | | -| [/vscode-extensions/tls-sync-vscode-shared](./vscode-extensions/tls-sync-vscode-shared/) | | -| [/vscode-extensions/tls-sync-vscode-ui-extension](./vscode-extensions/tls-sync-vscode-ui-extension/) | | -| [/vscode-extensions/tls-sync-vscode-workspace-extension](./vscode-extensions/tls-sync-vscode-workspace-extension/) | | | [/vscode-extensions/vscode-shared](./vscode-extensions/vscode-shared/) | | | [/webpack/webpack-deep-imports-plugin](./webpack/webpack-deep-imports-plugin/) | This plugin creates a bundle and commonJS files in a 'lib' folder mirroring modules in another 'lib' folder. | diff --git a/common/config/azure-pipelines/vscode-extension-publish.yaml b/common/config/azure-pipelines/vscode-extension-publish.yaml index 172133d03b5..935c984a08f 100644 --- a/common/config/azure-pipelines/vscode-extension-publish.yaml +++ b/common/config/azure-pipelines/vscode-extension-publish.yaml @@ -9,15 +9,9 @@ parameters: - name: ExtensionPublishConfig type: object default: - - key: 'tls-sync-vscode-ui-extension' + - key: 'debug-certificate-manager-vscode-extension' vsixPath: 'dist/vsix/packaged.vsix' - projectPath: '$(Build.SourcesDirectory)/vscode-extensions/tls-sync-vscode-ui-extension' - - key: 'tls-sync-vscode-workspace-extension' - vsixPath: 'dist/vsix/packaged.vsix' - projectPath: '$(Build.SourcesDirectory)/vscode-extensions/tls-sync-vscode-workspace-extension' - - key: 'tls-sync-vscode-extension-pack' - vsixPath: 'dist/vsix/packaged.vsix' - projectPath: '$(Build.SourcesDirectory)/vscode-extensions/tls-sync-vscode-extension-pack' + projectPath: '$(Build.SourcesDirectory)/vscode-extensions/debug-certificate-manager-vscode-extension' - key: 'rush-vscode-extension' vsixPath: 'dist/vsix/packaged.vsix' projectPath: '$(Build.SourcesDirectory)/vscode-extensions/rush-vscode-extension' @@ -62,7 +56,27 @@ extends: --to tag:vsix - ${{ if parameters.shouldPublish }}: + - task: AzureCLI@2 + displayName: 'Get managed identity user info' + inputs: + azureSubscription: 'rushstack-vscode-publish' + scriptType: bash + scriptLocation: inlineScript + inlineScript: | + az rest -u https://app.vssps.visualstudio.com/_apis/profile/profiles/me --resource 499b84ac-1321-427f-aa17-267ca6975798 + - ${{ each extension in parameters.ExtensionPublishConfig }}: + - bash: | + node node_modules/@rushstack/heft-vscode-extension-rig/node_modules/@rushstack/heft-vscode-extension-plugin/node_modules/@vscode/vsce/vsce generate-manifest --packagePath ${{ extension.vsixPath }} --out ${{ extension.projectPath }}/extension.manifest + displayName: 'Generate VSIX manifest: ${{ extension.key }}' + workingDirectory: ${{ extension.projectPath }} + + - bash: cp ${{ extension.projectPath }}/extension.manifest ${{ extension.projectPath }}/extension.signature.p7s + displayName: 'Prepare manifest for signing: ${{ extension.key }}' + workingDirectory: samples/extension-signing + + # TODO: add ESRP signing task here + - task: AzureCLI@2 displayName: 'Publish VSIX: ${{ extension.key }}' inputs: @@ -70,5 +84,5 @@ extends: scriptType: 'bash' scriptLocation: 'inlineScript' inlineScript: | - node node_modules/@rushstack/heft-vscode-extension-rig/node_modules/@rushstack/heft-vscode-extension-plugin/node_modules/@vscode/vsce/vsce publish --no-dependencies --azure-credential --packagePath ${{ extension.vsixPath }} + node node_modules/@rushstack/heft-vscode-extension-rig/node_modules/@rushstack/heft-vscode-extension-plugin/node_modules/@vscode/vsce/vsce publish --no-dependencies --azure-credential --packagePath ${{ extension.vsixPath }} --manifestPath ${{ extension.projectPath }}/extension.manifest --signaturePath ${{ extension.projectPath }}/extension.signature.p7s workingDirectory: ${{ extension.projectPath }} diff --git a/common/config/rush/version-policies.json b/common/config/rush/version-policies.json index c4eee5f5abb..bdbeeb351d2 100644 --- a/common/config/rush/version-policies.json +++ b/common/config/rush/version-policies.json @@ -105,15 +105,5 @@ "version": "5.156.0", "nextBump": "minor", "mainProject": "@microsoft/rush" - }, - { - // This policy is used for the TLS Sync VS Code extensions. - // Updating them in lockstep is necessary for the UI and Workspace extensions to work together. - // The Workspace and UI extensions perform a ping-pong version check to ensure they are compatible. - "policyName": "tls-sync-vscode-extensions", - "definitionName": "lockStepVersion", - "version": "0.0.1", - "nextBump": "minor", - "mainProject": "tls-sync-vscode-extension-pack" } ] diff --git a/common/config/subspaces/default/pnpm-lock.yaml b/common/config/subspaces/default/pnpm-lock.yaml index 1c5bc38a084..44e89ad3432 100644 --- a/common/config/subspaces/default/pnpm-lock.yaml +++ b/common/config/subspaces/default/pnpm-lock.yaml @@ -4744,22 +4744,7 @@ importers: specifier: ^10.1.0 version: 10.4.0 - ../../../vscode-extensions/tls-sync-vscode-extension-pack: - devDependencies: - '@rushstack/heft': - specifier: workspace:* - version: link:../../apps/heft - '@rushstack/heft-vscode-extension-rig': - specifier: workspace:* - version: link:../../rigs/heft-vscode-extension-rig - tls-sync-vscode-ui-extension: - specifier: workspace:* - version: link:../tls-sync-vscode-ui-extension - tls-sync-vscode-workspace-extension: - specifier: workspace:* - version: link:../tls-sync-vscode-workspace-extension - - ../../../vscode-extensions/tls-sync-vscode-shared: + ../../../vscode-extensions/debug-certificate-manager-vscode-extension: dependencies: '@rushstack/debug-certificate-manager': specifier: workspace:* @@ -4770,71 +4755,6 @@ importers: '@rushstack/terminal': specifier: workspace:* version: link:../../libraries/terminal - devDependencies: - '@rushstack/heft': - specifier: workspace:* - version: link:../../apps/heft - '@rushstack/heft-node-rig': - specifier: workspace:* - version: link:../../rigs/heft-node-rig - '@types/node': - specifier: 20.17.19 - version: 20.17.19 - '@types/vscode': - specifier: ^1.63.0 - version: 1.87.0 - - ../../../vscode-extensions/tls-sync-vscode-ui-extension: - dependencies: - '@rushstack/debug-certificate-manager': - specifier: workspace:* - version: link:../../libraries/debug-certificate-manager - '@rushstack/node-core-library': - specifier: workspace:* - version: link:../../libraries/node-core-library - '@rushstack/terminal': - specifier: workspace:* - version: link:../../libraries/terminal - '@rushstack/tls-sync-vscode-shared': - specifier: workspace:* - version: link:../tls-sync-vscode-shared - '@rushstack/vscode-shared': - specifier: workspace:* - version: link:../vscode-shared - tslib: - specifier: ~2.3.1 - version: 2.3.1 - devDependencies: - '@rushstack/heft': - specifier: workspace:* - version: link:../../apps/heft - '@rushstack/heft-vscode-extension-rig': - specifier: workspace:* - version: link:../../rigs/heft-vscode-extension-rig - '@types/node': - specifier: 20.17.19 - version: 20.17.19 - '@types/vscode': - specifier: ^1.63.0 - version: 1.87.0 - '@types/webpack-env': - specifier: 1.18.8 - version: 1.18.8 - - ../../../vscode-extensions/tls-sync-vscode-workspace-extension: - dependencies: - '@rushstack/debug-certificate-manager': - specifier: workspace:* - version: link:../../libraries/debug-certificate-manager - '@rushstack/node-core-library': - specifier: workspace:* - version: link:../../libraries/node-core-library - '@rushstack/terminal': - specifier: workspace:* - version: link:../../libraries/terminal - '@rushstack/tls-sync-vscode-shared': - specifier: workspace:* - version: link:../tls-sync-vscode-shared '@rushstack/vscode-shared': specifier: workspace:* version: link:../vscode-shared diff --git a/heft-plugins/heft-vscode-extension-plugin/src/VSCodeExtensionPackagePlugin.ts b/heft-plugins/heft-vscode-extension-plugin/src/VSCodeExtensionPackagePlugin.ts index e5b4cc694ba..02361d56c35 100644 --- a/heft-plugins/heft-vscode-extension-plugin/src/VSCodeExtensionPackagePlugin.ts +++ b/heft-plugins/heft-vscode-extension-plugin/src/VSCodeExtensionPackagePlugin.ts @@ -7,10 +7,10 @@ import type { IHeftTaskSession, IHeftTaskRunHookOptions } from '@rushstack/heft'; -import { Executable, IWaitForExitResult } from '@rushstack/node-core-library'; +import { Executable, IExecutableSpawnOptions, IWaitForExitResult } from '@rushstack/node-core-library'; import type { ChildProcess } from 'node:child_process'; import * as path from 'node:path'; -import { TerminalStreamWritable, TerminalProviderSeverity } from '@rushstack/terminal'; +import { TerminalStreamWritable, TerminalProviderSeverity, ITerminal } from '@rushstack/terminal'; interface IVSCodeExtensionPackagePluginOptions { /** @@ -31,6 +31,38 @@ const PLUGIN_NAME: 'vscode-extension-package-plugin' = 'vscode-extension-package const vsceBasePackagePath: string = require.resolve('@vscode/vsce/package.json'); const vsceScript: string = path.resolve(vsceBasePackagePath, '../vsce'); +async function execuateAndWaitAsync( + terminal: ITerminal, + command: string, + args: string[], + options: Omit = {} +): Promise> { + const childProcess: ChildProcess = Executable.spawn(command, args, { + ...options, + stdio: [ + 'ignore', // stdin + 'pipe', // stdout + 'pipe' // stderr + ] + }); + childProcess.stdout?.pipe( + new TerminalStreamWritable({ + terminal, + severity: TerminalProviderSeverity.log + }) + ); + childProcess.stderr?.pipe( + new TerminalStreamWritable({ + terminal, + severity: TerminalProviderSeverity.error + }) + ); + const result: IWaitForExitResult = await Executable.waitForExitAsync(childProcess, { + encoding: 'utf8' + }); + return result; +} + export default class VSCodeExtensionPackagePlugin implements IHeftTaskPlugin { @@ -47,38 +79,19 @@ export default class VSCodeExtensionPackagePlugin terminal.writeLine(`Using VSCE script: ${vsceScript}`); terminal.writeLine(`Packaging VSIX from ${unpackedFolderPath} to ${vsixPath}`); - const terminalOutStream: TerminalStreamWritable = new TerminalStreamWritable({ - terminal, - severity: TerminalProviderSeverity.log - }); - const terminalErrorStream: TerminalStreamWritable = new TerminalStreamWritable({ - terminal, - severity: TerminalProviderSeverity.error - }); - const childProcess: ChildProcess = Executable.spawn( + const packageResult: IWaitForExitResult = await execuateAndWaitAsync( + terminal, 'node', - [vsceScript, 'package', '--no-dependencies', '--out', `${path.resolve(vsixPath)}`], + [vsceScript, 'package', '--no-dependencies', '--out', path.resolve(vsixPath)], { - currentWorkingDirectory: path.resolve(unpackedFolderPath), - stdio: [ - 'ignore', // stdin - 'pipe', // stdout - 'pipe' // stderr - ] + currentWorkingDirectory: path.resolve(unpackedFolderPath) } ); - - childProcess.stdout?.pipe(terminalOutStream); - childProcess.stderr?.pipe(terminalErrorStream); - - const result: IWaitForExitResult = await Executable.waitForExitAsync(childProcess, { - encoding: 'utf8' - }); - - if (result.exitCode !== 0) { - throw new Error(`VSIX packaging failed with exit code ${result.exitCode}`); + if (packageResult.exitCode !== 0) { + throw new Error(`VSIX packaging failed with exit code ${packageResult.exitCode}`); } + terminal.writeLine('VSIX successfully packaged.'); }); } diff --git a/libraries/debug-certificate-manager/src/CertificateStore.ts b/libraries/debug-certificate-manager/src/CertificateStore.ts index 7acfbfd1ca1..246b2793705 100644 --- a/libraries/debug-certificate-manager/src/CertificateStore.ts +++ b/libraries/debug-certificate-manager/src/CertificateStore.ts @@ -49,24 +49,68 @@ export class CertificateStore { public constructor(options: ICertificateStoreOptions = {}) { const requestedStorePath: string | undefined = options.storePath; + let storePath: string | undefined; + let debugCertificateManagerConfig: ICertificateStoreOptions | undefined = undefined; + if (requestedStorePath) { storePath = path.resolve(requestedStorePath); } else { - // Default to the user's home directory under `.rushstack` - const unresolvedUserFolder: string = homedir(); - const userProfilePath: string = path.resolve(unresolvedUserFolder); - if (!FileSystem.exists(userProfilePath)) { - throw new Error("Unable to determine the current user's home directory"); + // TLS Sync extension configuration lives in `.vscode/debug-certificate-manager.json` + let currentDir: string | undefined = process.cwd(); + while (currentDir) { + const debugCertificateManagerConfigFile: string = path.join( + currentDir, + '.vscode', + 'debug-certificate-manager.json' + ); + if (FileSystem.exists(debugCertificateManagerConfigFile)) { + const configContent: string = FileSystem.readFile(debugCertificateManagerConfigFile); + debugCertificateManagerConfig = JSON.parse(configContent) as ICertificateStoreOptions; + if (debugCertificateManagerConfig.storePath) { + storePath = path.resolve(currentDir, debugCertificateManagerConfig.storePath); + } + } + const parentDir: string | undefined = path.dirname(currentDir); + if (parentDir === currentDir) { + break; // reached the root directory + } + currentDir = parentDir; + } + + if (!storePath) { + // Fallback to the user's home directory under `.rushstack` + const unresolvedUserFolder: string = homedir(); + const userProfilePath: string = path.resolve(unresolvedUserFolder); + if (!FileSystem.exists(userProfilePath)) { + throw new Error("Unable to determine the current user's home directory"); + } + storePath = path.join(userProfilePath, '.rushstack'); } - storePath = path.join(userProfilePath, '.rushstack'); } - this._storePath = storePath; FileSystem.ensureFolder(storePath); - this._caCertificatePath = path.join(storePath, options.caCertificateFilename ?? 'rushstack-ca.pem'); - this._certificatePath = path.join(storePath, options.certificateFilename ?? 'rushstack-serve.pem'); - this._keyPath = path.join(storePath, options.keyFilename ?? 'rushstack-serve.key'); + const caCertificatePath: string = path.join( + storePath, + options.caCertificateFilename ?? + debugCertificateManagerConfig?.caCertificateFilename ?? + 'rushstack-ca.pem' + ); + const certificatePath: string = path.join( + storePath, + options.certificateFilename ?? + debugCertificateManagerConfig?.certificateFilename ?? + 'rushstack-serve.pem' + ); + const keyPath: string = path.join( + storePath, + options.keyFilename ?? debugCertificateManagerConfig?.keyFilename ?? 'rushstack-serve.key' + ); + + this._storePath = storePath; + this._caCertificatePath = caCertificatePath; + this._certificatePath = certificatePath; + this._keyPath = keyPath; } /** diff --git a/rush.json b/rush.json index 23a51a475ba..e50b04f65d1 100644 --- a/rush.json +++ b/rush.json @@ -1235,12 +1235,6 @@ "shouldPublish": true, "decoupledLocalDependencies": ["@rushstack/heft"] }, - { - "packageName": "@rushstack/tls-sync-vscode-shared", - "projectFolder": "vscode-extensions/tls-sync-vscode-shared", - "reviewCategory": "libraries", - "shouldPublish": false - }, { "packageName": "@rushstack/tree-pattern", "projectFolder": "libraries/tree-pattern", @@ -1404,28 +1398,10 @@ "tags": ["vsix"] }, { - "packageName": "tls-sync-vscode-workspace-extension", - "projectFolder": "vscode-extensions/tls-sync-vscode-workspace-extension", - "reviewCategory": "vscode-extensions", - "tags": ["vsix"] - // For now, to keep this from being published to the npm package feed - // "versionPolicyName": "tls-sync-vscode-extensions" - }, - { - "packageName": "tls-sync-vscode-ui-extension", - "projectFolder": "vscode-extensions/tls-sync-vscode-ui-extension", - "reviewCategory": "vscode-extensions", - "tags": ["vsix"] - // For now, to keep this from being published to the npm package feed - // "versionPolicyName": "tls-sync-vscode-extensions" - }, - { - "packageName": "tls-sync-vscode-extension-pack", - "projectFolder": "vscode-extensions/tls-sync-vscode-extension-pack", + "packageName": "debug-certificate-manager", + "projectFolder": "vscode-extensions/debug-certificate-manager-vscode-extension", "reviewCategory": "vscode-extensions", "tags": ["vsix"] - // For now, to keep this from being published to the npm package feed - // "versionPolicyName": "tls-sync-vscode-extensions" }, // "webpack" folder (alphabetical order) diff --git a/vscode-extensions/tls-sync-vscode-extension-pack/.npmignore b/vscode-extensions/debug-certificate-manager-vscode-extension/.npmignore similarity index 100% rename from vscode-extensions/tls-sync-vscode-extension-pack/.npmignore rename to vscode-extensions/debug-certificate-manager-vscode-extension/.npmignore diff --git a/vscode-extensions/tls-sync-vscode-workspace-extension/.vscodeignore b/vscode-extensions/debug-certificate-manager-vscode-extension/.vscodeignore similarity index 100% rename from vscode-extensions/tls-sync-vscode-workspace-extension/.vscodeignore rename to vscode-extensions/debug-certificate-manager-vscode-extension/.vscodeignore diff --git a/vscode-extensions/tls-sync-vscode-ui-extension/LICENSE b/vscode-extensions/debug-certificate-manager-vscode-extension/LICENSE similarity index 97% rename from vscode-extensions/tls-sync-vscode-ui-extension/LICENSE rename to vscode-extensions/debug-certificate-manager-vscode-extension/LICENSE index 4575ab4ab2c..fd71bda5f04 100644 --- a/vscode-extensions/tls-sync-vscode-ui-extension/LICENSE +++ b/vscode-extensions/debug-certificate-manager-vscode-extension/LICENSE @@ -1,4 +1,4 @@ -tls-sync-vscode-ui-extension +debug-certificate-manager Copyright (c) Microsoft Corporation. All rights reserved. diff --git a/vscode-extensions/debug-certificate-manager-vscode-extension/README.md b/vscode-extensions/debug-certificate-manager-vscode-extension/README.md new file mode 100644 index 00000000000..31564a227b3 --- /dev/null +++ b/vscode-extensions/debug-certificate-manager-vscode-extension/README.md @@ -0,0 +1,7 @@ +# Debug Certificate Manager VS Code Extension + +## Sync Process + +The Debug Certificate Manager extension uses `@rushstack/debug-certificate-manager` to manage TLS certificates. It can also be used to sync certificates when connected to a VS Code remote workspace (WSL, Codespaces, Devcontainers, VS Code Tunnels). + +The extension reads `.vscode/debug-certificate-manager.json` for the certificate store path. When present, the extension will auto-activate and attempt to sync the certificates to the remote workspace. diff --git a/vscode-extensions/tls-sync-vscode-extension-pack/assets/extension-icon.png b/vscode-extensions/debug-certificate-manager-vscode-extension/assets/extension-icon.png similarity index 100% rename from vscode-extensions/tls-sync-vscode-extension-pack/assets/extension-icon.png rename to vscode-extensions/debug-certificate-manager-vscode-extension/assets/extension-icon.png diff --git a/vscode-extensions/tls-sync-vscode-extension-pack/config/heft.json b/vscode-extensions/debug-certificate-manager-vscode-extension/config/heft.json similarity index 100% rename from vscode-extensions/tls-sync-vscode-extension-pack/config/heft.json rename to vscode-extensions/debug-certificate-manager-vscode-extension/config/heft.json diff --git a/vscode-extensions/tls-sync-vscode-extension-pack/config/rig.json b/vscode-extensions/debug-certificate-manager-vscode-extension/config/rig.json similarity index 100% rename from vscode-extensions/tls-sync-vscode-extension-pack/config/rig.json rename to vscode-extensions/debug-certificate-manager-vscode-extension/config/rig.json diff --git a/vscode-extensions/tls-sync-vscode-extension-pack/config/rush-project.json b/vscode-extensions/debug-certificate-manager-vscode-extension/config/rush-project.json similarity index 100% rename from vscode-extensions/tls-sync-vscode-extension-pack/config/rush-project.json rename to vscode-extensions/debug-certificate-manager-vscode-extension/config/rush-project.json diff --git a/vscode-extensions/tls-sync-vscode-ui-extension/eslint.config.js b/vscode-extensions/debug-certificate-manager-vscode-extension/eslint.config.js similarity index 100% rename from vscode-extensions/tls-sync-vscode-ui-extension/eslint.config.js rename to vscode-extensions/debug-certificate-manager-vscode-extension/eslint.config.js diff --git a/vscode-extensions/debug-certificate-manager-vscode-extension/package.json b/vscode-extensions/debug-certificate-manager-vscode-extension/package.json new file mode 100644 index 00000000000..b4f3db0f5e3 --- /dev/null +++ b/vscode-extensions/debug-certificate-manager-vscode-extension/package.json @@ -0,0 +1,128 @@ +{ + "name": "debug-certificate-manager", + "version": "0.0.0", + "repository": { + "type": "git", + "url": "https://github.com/microsoft/rushstack.git", + "directory": "vscode-extensions/debug-certificate-manager-vscode-extension" + }, + "license": "MIT", + "publisher": "RushStack", + "preview": true, + "displayName": "Debug Certificate Manager", + "description": "", + "homepage": "https://github.com/microsoft/rushstack/tree/main/vscode-extensions/debug-certificate-manager-vscode-extension", + "icon": "assets/extension-icon.png", + "extensionKind": [ + "ui" + ], + "categories": [ + "Other" + ], + "keywords": [], + "galleryBanner": { + "color": "#f0f0f0", + "theme": "light" + }, + "engines": { + "vscode": "^1.98.0" + }, + "main": "./extension.js", + "scripts": { + "build": "heft build --clean", + "build:watch": "heft build-watch", + "start": "heft start", + "_phase:build": "heft run --only build -- --clean", + "_phase:test": "" + }, + "contributes": { + "commands": [ + { + "command": "debugCertificateManager.showLog", + "title": "Show Log", + "category": "Debug Certificate Manager" + }, + { + "command": "debugCertificateManager.untrustCertificate", + "title": "Untrust Certificate", + "category": "Debug Certificate Manager" + }, + { + "command": "debugCertificateManager.ensureCertificate", + "title": "Ensure Certificate", + "category": "Debug Certificate Manager" + }, + { + "command": "debugCertificateManager.sync", + "title": "Sync TLS Certificates", + "category": "Debug Certificate Manager" + }, + { + "command": "debugCertificateManager.showSettings", + "title": "Show Settings", + "category": "Debug Certificate Manager" + } + ], + "configuration": { + "title": "Debug Certificate Manager", + "properties": { + "debugCertificateManager.storePath.osx": { + "type": "string", + "title": "Local Machine - Debug Certificate Manager Store Path (macOS)", + "description": "[Local machine] [macOS] Directory where TLS certificates are read and written." + }, + "debugCertificateManager.storePath.windows": { + "type": "string", + "title": "Local Machine - Debug Certificate Manager Store Path (Windows)", + "description": "[Local machine] [Windows] Directory where TLS certificates are read and written." + }, + "debugCertificateManager.storePath.linux": { + "type": "string", + "title": "Local Machine - Debug Certificate Manager Store Path (Linux)", + "description": "[Local machine] [Linux] Directory where TLS certificates are read and written." + }, + "debugCertificateManager.caCertificateFilename": { + "type": "string", + "title": "CA Certificate Filename", + "description": "Filename for the CA certificate." + }, + "debugCertificateManager.certificateFilename": { + "type": "string", + "title": "Server Certificate Filename", + "description": "Filename for the server certificate." + }, + "debugCertificateManager.keyFilename": { + "type": "string", + "title": "Server Key Filename", + "description": "Filename for the server key." + }, + "debugCertificateManager.autoSync": { + "type": "boolean", + "title": "Automatically Sync Certificates", + "default": true, + "description": "Check certificates when extension is activated. Extension is automatically activated when a `.vscode/debug-certificate-manager.json` file is present in the workspace." + } + } + } + }, + "enabledApiProposals": [ + "resolvers" + ], + "activationEvents": [ + "workspaceContains:.vscode/debug-certificate-manager.json" + ], + "dependencies": { + "@rushstack/debug-certificate-manager": "workspace:*", + "@rushstack/node-core-library": "workspace:*", + "@rushstack/terminal": "workspace:*", + "@rushstack/vscode-shared": "workspace:*", + "tslib": "~2.3.1" + }, + "devDependencies": { + "@rushstack/heft-vscode-extension-rig": "workspace:*", + "@rushstack/heft": "workspace:*", + "@types/node": "20.17.19", + "@types/vscode": "^1.63.0", + "@types/webpack-env": "1.18.8" + } +} diff --git a/vscode-extensions/tls-sync-vscode-shared/src/certificates.ts b/vscode-extensions/debug-certificate-manager-vscode-extension/src/certificates.ts similarity index 70% rename from vscode-extensions/tls-sync-vscode-shared/src/certificates.ts rename to vscode-extensions/debug-certificate-manager-vscode-extension/src/certificates.ts index 16537281730..b918be65368 100644 --- a/vscode-extensions/tls-sync-vscode-shared/src/certificates.ts +++ b/vscode-extensions/debug-certificate-manager-vscode-extension/src/certificates.ts @@ -5,14 +5,8 @@ import { getConfig } from './config'; import { CertificateManager, CertificateStore } from '@rushstack/debug-certificate-manager'; import type { ITerminal } from '@rushstack/terminal'; -export function getCertificateManager( - terminal: ITerminal, - configType: 'ui' | 'workspace' -): CertificateManager { - const { caCertificateFilename, keyFilename, certificateFilename, storePath } = getConfig( - terminal, - configType - ); +export function getCertificateManager(terminal: ITerminal): CertificateManager { + const { caCertificateFilename, keyFilename, certificateFilename, storePath } = getConfig(terminal); const certificateManager: CertificateManager = new CertificateManager({ caCertificateFilename, keyFilename, @@ -23,11 +17,8 @@ export function getCertificateManager( return certificateManager; } -export function getCertificateStore(terminal: ITerminal, configType: 'ui' | 'workspace'): CertificateStore { - const { caCertificateFilename, keyFilename, certificateFilename, storePath } = getConfig( - terminal, - configType - ); +export function getCertificateStore(terminal: ITerminal): CertificateStore { + const { caCertificateFilename, keyFilename, certificateFilename, storePath } = getConfig(terminal); const certificateStore: CertificateStore = new CertificateStore({ caCertificateFilename, keyFilename, diff --git a/vscode-extensions/tls-sync-vscode-shared/src/config.ts b/vscode-extensions/debug-certificate-manager-vscode-extension/src/config.ts similarity index 66% rename from vscode-extensions/tls-sync-vscode-shared/src/config.ts rename to vscode-extensions/debug-certificate-manager-vscode-extension/src/config.ts index e444008c052..1a6371b81b8 100644 --- a/vscode-extensions/tls-sync-vscode-shared/src/config.ts +++ b/vscode-extensions/debug-certificate-manager-vscode-extension/src/config.ts @@ -4,7 +4,7 @@ import * as vscode from 'vscode'; import * as path from 'node:path'; import type { ITerminal } from '@rushstack/terminal'; -import type { ICertificateManagerOptions } from '@rushstack/debug-certificate-manager'; +import type { ICertificateStoreOptions } from '@rushstack/debug-certificate-manager'; import { CONFIG_AUTOSYNC, CONFIG_SECTION, @@ -15,22 +15,20 @@ import { } from './constants'; type StorePaths = Record<'windows' | 'linux' | 'osx', string>; -export interface IExtensionConfig extends ICertificateManagerOptions { +export interface IExtensionConfig extends ICertificateStoreOptions { autoSync: boolean; } -export function getConfig(terminal: ITerminal, configType: 'ui' | 'workspace'): IExtensionConfig { +export function getConfig(terminal: ITerminal): IExtensionConfig { const config: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration(CONFIG_SECTION); - const caCertificateFilename: string | undefined = config.get(CONFIG_CA_CERTIFICATE_FILENAME) || undefined; - const certificateFilename: string | undefined = config.get(CONFIG_CERTIFICATE_FILENAME) || undefined; - const keyFilename: string | undefined = config.get(CONFIG_KEY_FILENAME) || undefined; + const caCertificateFilename: string | undefined = + config.get(CONFIG_CA_CERTIFICATE_FILENAME) || 'rushstack-ca.pem'; + const certificateFilename: string | undefined = + config.get(CONFIG_CERTIFICATE_FILENAME) || 'rushstack-serve.pem'; + const keyFilename: string | undefined = config.get(CONFIG_KEY_FILENAME) || 'rushstack-serve.key'; const autoSync: boolean = config.get(CONFIG_AUTOSYNC) ?? false; let storePath: string | undefined = undefined; - const storePaths: StorePaths = { - windows: config.get(`${configType}.${CONFIG_STORE_PATH}.windows`) || '', - linux: config.get(`${configType}.${CONFIG_STORE_PATH}.linux`) || '', - osx: config.get(`${configType}.${CONFIG_STORE_PATH}.osx`) || '' - }; + const platformMap: Record = { win32: 'windows', linux: 'linux', @@ -38,8 +36,9 @@ export function getConfig(terminal: ITerminal, configType: 'ui' | 'workspace'): }; const platformKey: keyof StorePaths = platformMap[process.platform]; + if (platformKey) { - storePath = storePaths[platformKey]; + storePath = config.get(`${CONFIG_STORE_PATH}.${platformKey}`) || '~/.rushstack'; if (storePath) { const homeDir: string | undefined = process.env.HOME || process.env.USERPROFILE; if (storePath[0] === '~' && homeDir) { diff --git a/vscode-extensions/debug-certificate-manager-vscode-extension/src/constants.ts b/vscode-extensions/debug-certificate-manager-vscode-extension/src/constants.ts new file mode 100644 index 00000000000..088cd173204 --- /dev/null +++ b/vscode-extensions/debug-certificate-manager-vscode-extension/src/constants.ts @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +export const EXTENSION_DISPLAY_NAME: string = 'Debug Certificate Manager'; + +export const EXTENSION_ID: string = 'RushStack.debug-certificate-manager'; + +export const COMMAND_PREFIX: string = 'debugCertificateManager'; +export const COMMAND_SYNC: string = `${COMMAND_PREFIX}.sync`; +export const COMMAND_ENSURE_CERTIFICATE: string = `${COMMAND_PREFIX}.ensureCertificate`; +export const COMMAND_UNTRUST_CERTIFICATE: string = `${COMMAND_PREFIX}.untrustCertificate`; +export const COMMAND_SHOW_LOG: string = `${COMMAND_PREFIX}.showLog`; +export const COMMAND_SHOW_SETTINGS: string = `${COMMAND_PREFIX}.showSettings`; + +export const CONFIG_SECTION: string = 'debugCertificateManager'; +export const CONFIG_AUTOSYNC: string = 'autoSync'; +export const CONFIG_CA_CERTIFICATE_FILENAME: string = 'caCertificateFilename'; +export const CONFIG_CERTIFICATE_FILENAME: string = 'certificateFilename'; +export const CONFIG_KEY_FILENAME: string = 'keyFilename'; +export const CONFIG_STORE_PATH: string = 'storePath'; + +export const VSCODE_COMMAND_WORKSPACE_OPEN_SETTINGS: string = 'workbench.action.openSettings'; diff --git a/vscode-extensions/tls-sync-vscode-ui-extension/src/extension.ts b/vscode-extensions/debug-certificate-manager-vscode-extension/src/extension.ts similarity index 56% rename from vscode-extensions/tls-sync-vscode-ui-extension/src/extension.ts rename to vscode-extensions/debug-certificate-manager-vscode-extension/src/extension.ts index 974034d6fbf..87f293854a4 100644 --- a/vscode-extensions/tls-sync-vscode-ui-extension/src/extension.ts +++ b/vscode-extensions/debug-certificate-manager-vscode-extension/src/extension.ts @@ -7,39 +7,27 @@ import { Async } from '@rushstack/node-core-library/lib/Async'; import { Terminal } from '@rushstack/terminal'; import { CertificateManager, + ICertificateStoreOptions, ICertificateValidationResult, type ICertificate } from '@rushstack/debug-certificate-manager'; import { VScodeOutputChannelTerminalProvider } from '@rushstack/vscode-shared/lib/VScodeOutputChannelTerminalProvider'; -import { getCertificateManager } from '@rushstack/tls-sync-vscode-shared/lib/certificates'; -import { getConfig } from '@rushstack/tls-sync-vscode-shared/lib/config'; +import { getCertificateManager } from './certificates'; +import { getConfig } from './config'; import { - UI_COMMAND_ENSURE_CERTIFICATE, - UI_COMMAND_SHOW_LOG, - UI_COMMAND_SHOW_SETTINGS, - UI_COMMAND_SHOW_WALKTHROUGH, - UI_COMMAND_SYNC, - UI_COMMAND_UNTRUST_CERTIFICATE, - UI_EXTENSION_DISPLAY_NAME, - UI_EXTENSION_ID, - UI_WALKTHROUGH_ID, - VSCODE_COMMAND_WORKSPACE_OPEN_SETTINGS, - VSCODE_COMMAND_WORKSPACE_OPEN_WALKTHROUGH, - WORKSPACE_COMMAND_PING, - WORKSPACE_COMMAND_SYNC, - WORKSPACE_EXTENSION_DISPLAY_NAME -} from '@rushstack/tls-sync-vscode-shared/lib/constants'; - -import { version } from '../package.json'; - -/* - * This extension provides commands to manage debug TLS certificates on the local machine. This capability is - * primarily intended to be called by the workspace extension counterpart. - */ + COMMAND_ENSURE_CERTIFICATE, + COMMAND_SHOW_LOG, + COMMAND_SHOW_SETTINGS, + COMMAND_SYNC, + COMMAND_UNTRUST_CERTIFICATE, + EXTENSION_DISPLAY_NAME, + EXTENSION_ID, + VSCODE_COMMAND_WORKSPACE_OPEN_SETTINGS +} from './constants'; export function activate(context: vscode.ExtensionContext): void { - const outputChannel: vscode.OutputChannel = vscode.window.createOutputChannel(UI_EXTENSION_DISPLAY_NAME); + const outputChannel: vscode.OutputChannel = vscode.window.createOutputChannel(EXTENSION_DISPLAY_NAME); const terminalProvider: VScodeOutputChannelTerminalProvider = new VScodeOutputChannelTerminalProvider( outputChannel, { @@ -48,7 +36,7 @@ export function activate(context: vscode.ExtensionContext): void { } ); const terminal: Terminal = new Terminal(terminalProvider); - terminal.writeLine(`${UI_EXTENSION_DISPLAY_NAME} Extension output channel initialized.`); + terminal.writeLine(`${EXTENSION_DISPLAY_NAME} Extension output channel initialized.`); function handleShowLog(): void { outputChannel.show(); @@ -57,7 +45,7 @@ export function activate(context: vscode.ExtensionContext): void { async function handleUntrustCertificate(): Promise { try { terminal.writeLine('Attempting to clean up certificates...'); - const certificateManager: CertificateManager = getCertificateManager(terminal, 'ui'); + const certificateManager: CertificateManager = getCertificateManager(terminal); await certificateManager.untrustCertificateAsync(terminal); const message: string = 'Certificates untrusted successfully.'; @@ -79,7 +67,7 @@ export function activate(context: vscode.ExtensionContext): void { async function _handleEnsureCertificateInternal(): Promise { try { terminal.writeLine('Attempting to retrieve certificates...'); - const certificateManager: CertificateManager = getCertificateManager(terminal, 'ui'); + const certificateManager: CertificateManager = getCertificateManager(terminal); let skipCertificateTrust: boolean = true; let canGenerateNewCertificate: boolean = false; @@ -149,53 +137,8 @@ export function activate(context: vscode.ExtensionContext): void { } } - async function handleShowWalkthrough(): Promise { - await vscode.commands.executeCommand( - VSCODE_COMMAND_WORKSPACE_OPEN_WALKTHROUGH, - `${UI_EXTENSION_ID}#${UI_WALKTHROUGH_ID}`, - false - ); - } - async function handleShowSettings(): Promise { - await vscode.commands.executeCommand(VSCODE_COMMAND_WORKSPACE_OPEN_SETTINGS, `@ext:${UI_EXTENSION_ID}`); - } - - async function waitForWorkspaceExtension(): Promise { - terminal.writeLine( - `Waiting for Workspace extension (${WORKSPACE_EXTENSION_DISPLAY_NAME}) to become active...` - ); - - const maxRetries: number = 30; - try { - await Async.runWithRetriesAsync({ - action: async (attempt: number) => { - terminal.writeLine(`Pinging Workspace extension... Attempt ${attempt + 1}/${maxRetries}`); - const { version: workspaceVersion } = await vscode.commands.executeCommand<{ version: string }>( - WORKSPACE_COMMAND_PING - ); - if (!workspaceVersion) { - terminal.writeLine('Workspace extension is not yet active. Retrying...'); - return; - } - terminal.writeLine(`Workspace extension is active. Version: ${workspaceVersion}`); - if (version !== workspaceVersion) { - terminal.writeLine( - `Warning: Workspace extension version mismatch. Expected ${version}, got ${workspaceVersion}.` - ); - void vscode.window.showWarningMessage( - `Workspace extension version mismatch. Expected ${version}, got ${workspaceVersion}. Please check that both ${WORKSPACE_EXTENSION_DISPLAY_NAME} and ${UI_EXTENSION_DISPLAY_NAME} are up to date.` - ); - throw new Error('Version mismatch'); - } - }, - maxRetries, - retryDelayMs: 1000 - }); - } catch (error) { - terminal.writeLine('UI extension did not respond within the expected time frame.'); - throw new Error('UI extension did not respond within the expected time frame.'); - } + await vscode.commands.executeCommand(VSCODE_COMMAND_WORKSPACE_OPEN_SETTINGS, `@ext:${EXTENSION_ID}`); } async function handleSync(): Promise { @@ -204,11 +147,11 @@ export function activate(context: vscode.ExtensionContext): void { 'This command is only available in remote workspaces. Please open this workspace in a remote environment.'; terminal.writeLine(message); void vscode.window.showErrorMessage(message); + return; } try { - await waitForWorkspaceExtension(); - terminal.writeLine('Workspace extension is active. Proceeding with certificate synchronization...'); + terminal.writeLine('Starting certificate synchronization...'); const certificate: ICertificate | undefined = await _handleEnsureCertificateInternal(); if (!certificate) { @@ -216,20 +159,84 @@ export function activate(context: vscode.ExtensionContext): void { return; } - terminal.writeLine('Sending certificates to workspace extension for synchronization...'); - const isSynchronized: boolean = await vscode.commands.executeCommand( - WORKSPACE_COMMAND_SYNC, - certificate - ); + const { pemCaCertificate, pemCertificate, pemKey } = certificate; + if (!pemCaCertificate || !pemCertificate || !pemKey) { + terminal.writeLine('Invalid certificate data. Synchronization aborted.'); + void vscode.window.showErrorMessage('Invalid certificate data. Synchronization aborted.'); + return; + } + + terminal.writeLine('Writing certificates to the workspace...'); - if (isSynchronized) { - await vscode.commands.executeCommand('setContext', 'tlssync.ui.sync.complete', true); - terminal.writeLine('Certificates synchronized successfully.'); - void vscode.window.showInformationMessage('Certificates synchronized successfully.'); + const workspaceUri: vscode.Uri | undefined = vscode.workspace.workspaceFolders?.[0].uri; + if (!workspaceUri) { + terminal.writeLine('No workspace folder found. Synchronization aborted.'); + void vscode.window.showErrorMessage( + 'No workspace folder found. Please open a folder in the remote workspace.' + ); + return; + } + + let remoteCertificateStoreOptions: Required | undefined = undefined; + + try { + const configFileUri: vscode.Uri = vscode.Uri.joinPath( + workspaceUri, + '.vscode', + 'debug-certificate-manager.json' + ); + const configFile: Uint8Array = await vscode.workspace.fs.readFile(configFileUri); + const parsedConfig: ICertificateStoreOptions & Required> = + JSON.parse(configFile.toString()); + + remoteCertificateStoreOptions = { + storePath: parsedConfig.storePath, + caCertificateFilename: parsedConfig.caCertificateFilename || 'rushstack-ca.pem', + certificateFilename: parsedConfig.certificateFilename || 'rushstack-serve.pem', + keyFilename: parsedConfig.keyFilename || 'rushstack-serve.key' + }; + } catch (error) { + void vscode.window.showErrorMessage( + 'Failed to read or parse the configuration file. Ensure that .vscode/debug-certificate-manager.json exists and is valid.' + ); + terminal.writeLine( + `Error reading or parsing configuration file: ${ + error instanceof Error ? error.message : 'Unknown error' + }` + ); + return; + } + + const { storePath, caCertificateFilename, certificateFilename, keyFilename } = + remoteCertificateStoreOptions; + + let resolvedRemoteStorePath: string; + if (storePath.startsWith('/')) { + resolvedRemoteStorePath = storePath; } else { - terminal.writeLine('Failed to synchronize certificates.'); - void vscode.window.showErrorMessage('Failed to synchronize certificates.'); + resolvedRemoteStorePath = vscode.Uri.joinPath(workspaceUri, storePath).fsPath; } + + const storePathUri: vscode.Uri = vscode.Uri.from({ + scheme: 'vscode-remote', + authority: workspaceUri.authority, + path: resolvedRemoteStorePath + }); + const caCertificateUri: vscode.Uri = vscode.Uri.joinPath(storePathUri, caCertificateFilename); + const certificateUri: vscode.Uri = vscode.Uri.joinPath(storePathUri, certificateFilename); + const keyUri: vscode.Uri = vscode.Uri.joinPath(storePathUri, keyFilename); + + terminal.writeLine(`Writing CA certificate to: ${caCertificateUri.toString()}`); + terminal.writeLine(`Writing certificate to: ${certificateUri.toString()}`); + terminal.writeLine(`Writing key to: ${keyUri.toString()}`); + + await Promise.all([ + vscode.workspace.fs.writeFile(caCertificateUri, Buffer.from(pemCaCertificate, 'utf8')), + vscode.workspace.fs.writeFile(certificateUri, Buffer.from(pemCertificate, 'utf8')), + vscode.workspace.fs.writeFile(keyUri, Buffer.from(pemKey, 'utf8')) + ]); + + terminal.writeLine('Certificates written to the workspace successfully.'); } catch (err) { const message: string = `Error synchronizing certificates: ${ err instanceof Error ? err.message : 'Unknown error' @@ -239,20 +246,18 @@ export function activate(context: vscode.ExtensionContext): void { } } - const { autoSync } = getConfig(terminal, 'ui'); + const { autoSync } = getConfig(terminal); if (autoSync && !vscode.env.remoteName) { terminal.writeLine(`Auto-sync is enabled. Synchronizing certificates on activation...`); - void vscode.commands.executeCommand(UI_COMMAND_SYNC); } context.subscriptions.push( outputChannel, - vscode.commands.registerCommand(UI_COMMAND_SHOW_LOG, handleShowLog), - vscode.commands.registerCommand(UI_COMMAND_SHOW_SETTINGS, handleShowSettings), - vscode.commands.registerCommand(UI_COMMAND_SHOW_WALKTHROUGH, handleShowWalkthrough), - vscode.commands.registerCommand(UI_COMMAND_UNTRUST_CERTIFICATE, handleUntrustCertificate), - vscode.commands.registerCommand(UI_COMMAND_ENSURE_CERTIFICATE, handleEnsureCertificate), - vscode.commands.registerCommand(UI_COMMAND_SYNC, handleSync) + vscode.commands.registerCommand(COMMAND_SHOW_LOG, handleShowLog), + vscode.commands.registerCommand(COMMAND_SHOW_SETTINGS, handleShowSettings), + vscode.commands.registerCommand(COMMAND_UNTRUST_CERTIFICATE, handleUntrustCertificate), + vscode.commands.registerCommand(COMMAND_ENSURE_CERTIFICATE, handleEnsureCertificate), + vscode.commands.registerCommand(COMMAND_SYNC, handleSync) ); } diff --git a/vscode-extensions/tls-sync-vscode-ui-extension/tsconfig.json b/vscode-extensions/debug-certificate-manager-vscode-extension/tsconfig.json similarity index 100% rename from vscode-extensions/tls-sync-vscode-ui-extension/tsconfig.json rename to vscode-extensions/debug-certificate-manager-vscode-extension/tsconfig.json diff --git a/vscode-extensions/tls-sync-vscode-workspace-extension/webpack.config.js b/vscode-extensions/debug-certificate-manager-vscode-extension/webpack.config.js similarity index 95% rename from vscode-extensions/tls-sync-vscode-workspace-extension/webpack.config.js rename to vscode-extensions/debug-certificate-manager-vscode-extension/webpack.config.js index bc6dfe67040..c12db6ac9df 100644 --- a/vscode-extensions/tls-sync-vscode-workspace-extension/webpack.config.js +++ b/vscode-extensions/debug-certificate-manager-vscode-extension/webpack.config.js @@ -10,7 +10,7 @@ const path = require('node:path'); function createConfig({ production, webpack }) { const config = createExtensionConfig({ - production, + production: false, webpack, entry: { extension: './lib/extension.js' diff --git a/vscode-extensions/tls-sync-vscode-extension-pack/.vscodeignore b/vscode-extensions/tls-sync-vscode-extension-pack/.vscodeignore deleted file mode 100644 index 9a22e7e9769..00000000000 --- a/vscode-extensions/tls-sync-vscode-extension-pack/.vscodeignore +++ /dev/null @@ -1,5 +0,0 @@ -** -!LICENSE -!README.md -!package.json -!assets/extension-icon.png diff --git a/vscode-extensions/tls-sync-vscode-extension-pack/LICENSE b/vscode-extensions/tls-sync-vscode-extension-pack/LICENSE deleted file mode 100644 index a79de558c93..00000000000 --- a/vscode-extensions/tls-sync-vscode-extension-pack/LICENSE +++ /dev/null @@ -1,24 +0,0 @@ -tls-sync-vscode-extension-pack - -Copyright (c) Microsoft Corporation. All rights reserved. - -MIT License - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vscode-extensions/tls-sync-vscode-extension-pack/README.md b/vscode-extensions/tls-sync-vscode-extension-pack/README.md deleted file mode 100644 index fc4ddd50127..00000000000 --- a/vscode-extensions/tls-sync-vscode-extension-pack/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# TLS Sync VS Code Extension Pack - -## Sync Process - -The workspace and UI extensions work together to use `@rushstack/debug-certificate-manager` to manage TLS certificates. The UI extension manages the machine where the VS Code client is running, while the Workspace extension manages the remote workspace (WSL, Codespaces, Devcontainers, VS Code Tunnels). - -Both the UI and Workspace extensions must be installed for the sync process to work. - -1. VS Code activates the UI extension if `.tlssync` file is present in the workspace or if the user runs the Sync command. -2. The UI extension checks if the Workspace extension is available. -3. If the Workspace extension is available, it ensures that valid certificates are present in the local certificate store. If not, it generates a new certificate and stores it in the local certificate store. -4. The UI extension then sends the certificate to the Workspace extension. - -The certificate store paths and the file names can be configured in the VS Code settings. Run the `TLS Sync: Show Settings` command to view and modify the configuration. - -## Extensions - -- [TLS Sync VS Code (UI Extension)](../tls-sync-vscode-ui-extension) -- [TLS Sync VS Code (Workspace Extension)](../tls-sync-vscode-extension-pack) diff --git a/vscode-extensions/tls-sync-vscode-extension-pack/package.json b/vscode-extensions/tls-sync-vscode-extension-pack/package.json deleted file mode 100644 index 3b9db955878..00000000000 --- a/vscode-extensions/tls-sync-vscode-extension-pack/package.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "tls-sync-vscode-extension-pack", - "version": "0.0.0", - "repository": { - "type": "git", - "url": "https://github.com/microsoft/rushstack.git", - "directory": "vscode-extensions/tls-sync-vscode-extension-pack" - }, - "license": "MIT", - "publisher": "RushStack", - "preview": true, - "displayName": "TLS Sync", - "description": "", - "homepage": "https://github.com/microsoft/rushstack/tree/main/vscode-extensions/tls-sync-vscode-extension-pack", - "icon": "assets/extension-icon.png", - "categories": [ - "Other" - ], - "extensionPack": [ - "Rushstack.tls-sync-vscode-ui-extension", - "Rushstack.tls-sync-vscode-workspace-extension" - ], - "keywords": [], - "galleryBanner": { - "color": "#f0f0f0", - "theme": "light" - }, - "engines": { - "vscode": "^1.98.0" - }, - "scripts": { - "build": "heft build --clean", - "build:watch": "heft build-watch", - "start": "heft start", - "_phase:build": "heft run --only build -- --clean", - "_phase:test": "" - }, - "dependencies": {}, - "devDependencies": { - "@rushstack/heft-vscode-extension-rig": "workspace:*", - "@rushstack/heft": "workspace:*", - "tls-sync-vscode-ui-extension": "workspace:*", - "tls-sync-vscode-workspace-extension": "workspace:*" - } -} diff --git a/vscode-extensions/tls-sync-vscode-shared/.npmignore b/vscode-extensions/tls-sync-vscode-shared/.npmignore deleted file mode 100644 index dcf329e5ffa..00000000000 --- a/vscode-extensions/tls-sync-vscode-shared/.npmignore +++ /dev/null @@ -1,2 +0,0 @@ -# Ignore all files by default, to avoid accidentally publishing unintended files. -** diff --git a/vscode-extensions/tls-sync-vscode-shared/LICENSE b/vscode-extensions/tls-sync-vscode-shared/LICENSE deleted file mode 100644 index 1fefe388f93..00000000000 --- a/vscode-extensions/tls-sync-vscode-shared/LICENSE +++ /dev/null @@ -1,24 +0,0 @@ -@rushstack/tls-sync-vscode-shared - -Copyright (c) Microsoft Corporation. All rights reserved. - -MIT License - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vscode-extensions/tls-sync-vscode-shared/README.md b/vscode-extensions/tls-sync-vscode-shared/README.md deleted file mode 100644 index 7cbcf0522b6..00000000000 --- a/vscode-extensions/tls-sync-vscode-shared/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# @rushstack/tls-sync-vscode-shared - -This library provides a set of utilities for TLS Sync VS Code extensions. diff --git a/vscode-extensions/tls-sync-vscode-shared/config/rig.json b/vscode-extensions/tls-sync-vscode-shared/config/rig.json deleted file mode 100644 index 58032e098f0..00000000000 --- a/vscode-extensions/tls-sync-vscode-shared/config/rig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json", - - "rigPackageName": "@rushstack/heft-node-rig" -} diff --git a/vscode-extensions/tls-sync-vscode-shared/eslint.config.js b/vscode-extensions/tls-sync-vscode-shared/eslint.config.js deleted file mode 100644 index 006cb82d1c0..00000000000 --- a/vscode-extensions/tls-sync-vscode-shared/eslint.config.js +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -const nodeTrustedToolProfile = require('@rushstack/heft-node-rig/profiles/default/includes/eslint/flat/profile/node-trusted-tool'); -const friendlyLocalsMixin = require('@rushstack/heft-node-rig/profiles/default/includes/eslint/flat/mixins/friendly-locals'); - -module.exports = [...nodeTrustedToolProfile, ...friendlyLocalsMixin]; diff --git a/vscode-extensions/tls-sync-vscode-shared/package.json b/vscode-extensions/tls-sync-vscode-shared/package.json deleted file mode 100644 index b09528d52ac..00000000000 --- a/vscode-extensions/tls-sync-vscode-shared/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "@rushstack/tls-sync-vscode-shared", - "version": "0.0.0", - "description": "", - "main": "lib/index.js", - "typings": "dist/index.d.ts", - "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/microsoft/rushstack.git", - "directory": "vscode-extensions/tls-sync-vscode-shared" - }, - "scripts": { - "build": "heft build --clean", - "_phase:build": "heft run --only build -- --clean", - "_phase:test": "heft run --only test -- --clean" - }, - "dependencies": { - "@rushstack/debug-certificate-manager": "workspace:*", - "@rushstack/node-core-library": "workspace:*", - "@rushstack/terminal": "workspace:*" - }, - "devDependencies": { - "@rushstack/heft-node-rig": "workspace:*", - "@rushstack/heft": "workspace:*", - "@types/node": "20.17.19", - "@types/vscode": "^1.63.0" - } -} diff --git a/vscode-extensions/tls-sync-vscode-shared/src/constants.ts b/vscode-extensions/tls-sync-vscode-shared/src/constants.ts deleted file mode 100644 index 55b961db633..00000000000 --- a/vscode-extensions/tls-sync-vscode-shared/src/constants.ts +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -export const WORKSPACE_EXTENSION_DISPLAY_NAME: string = 'TLS Sync (Workspace Extension)'; -export const UI_EXTENSION_DISPLAY_NAME: string = 'TLS Sync (UI Extension)'; - -export const WORKSPACE_EXTENSION_ID: string = 'RushStack.tls-sync-vscode-workspace-extension'; -export const UI_EXTENSION_ID: string = 'RushStack.tls-sync-vscode-ui-extension'; - -export const WORKSPACE_COMMAND_PREFIX: string = 'tlssync.workspace'; -export const UI_COMMAND_PREFIX: string = 'tlssync.ui'; - -export const WORKSPACE_COMMAND_SYNC: string = `${WORKSPACE_COMMAND_PREFIX}.sync`; -export const WORKSPACE_COMMAND_SHOW_LOG: string = `${WORKSPACE_COMMAND_PREFIX}.showLog`; -export const WORKSPACE_COMMAND_PING: string = `${WORKSPACE_COMMAND_PREFIX}.ping`; - -export const UI_COMMAND_SYNC: string = `${UI_COMMAND_PREFIX}.sync`; -export const UI_COMMAND_ENSURE_CERTIFICATE: string = `${UI_COMMAND_PREFIX}.ensureCertificate`; -export const UI_COMMAND_UNTRUST_CERTIFICATE: string = `${UI_COMMAND_PREFIX}.untrustCertificate`; -export const UI_COMMAND_SHOW_LOG: string = `${UI_COMMAND_PREFIX}.showLog`; -export const UI_COMMAND_SHOW_WALKTHROUGH: string = `${UI_COMMAND_PREFIX}.showWalkthrough`; -export const UI_COMMAND_SHOW_SETTINGS: string = `${UI_COMMAND_PREFIX}.showSettings`; - -export const CONFIG_SECTION: string = 'tlssync'; -export const CONFIG_AUTOSYNC: string = 'autoSync'; -export const CONFIG_CA_CERTIFICATE_FILENAME: string = 'caCertificateFilename'; -export const CONFIG_CERTIFICATE_FILENAME: string = 'certificateFilename'; -export const CONFIG_KEY_FILENAME: string = 'keyFilename'; -export const CONFIG_STORE_PATH: string = 'storePath'; - -export const VSCODE_COMMAND_WORKSPACE_OPEN_SETTINGS: string = 'workbench.action.openSettings'; -export const VSCODE_COMMAND_WORKSPACE_OPEN_WALKTHROUGH: string = 'workbench.action.openWalkthrough'; - -export const UI_WALKTHROUGH_ID: string = 'sync-certificates'; diff --git a/vscode-extensions/tls-sync-vscode-shared/tsconfig.json b/vscode-extensions/tls-sync-vscode-shared/tsconfig.json deleted file mode 100644 index a114c3448ed..00000000000 --- a/vscode-extensions/tls-sync-vscode-shared/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "./node_modules/@rushstack/heft-node-rig/profiles/default/tsconfig-base.json" -} diff --git a/vscode-extensions/tls-sync-vscode-ui-extension/.npmignore b/vscode-extensions/tls-sync-vscode-ui-extension/.npmignore deleted file mode 100644 index dcf329e5ffa..00000000000 --- a/vscode-extensions/tls-sync-vscode-ui-extension/.npmignore +++ /dev/null @@ -1,2 +0,0 @@ -# Ignore all files by default, to avoid accidentally publishing unintended files. -** diff --git a/vscode-extensions/tls-sync-vscode-ui-extension/.vscodeignore b/vscode-extensions/tls-sync-vscode-ui-extension/.vscodeignore deleted file mode 100644 index 57c6857fd22..00000000000 --- a/vscode-extensions/tls-sync-vscode-ui-extension/.vscodeignore +++ /dev/null @@ -1,8 +0,0 @@ -** -!LICENSE -!README.md -!extension.js -!package.json -!assets/extension-icon.png -!assets/walkthrough-sync.md -!assets/walkthrough-settings.md diff --git a/vscode-extensions/tls-sync-vscode-ui-extension/README.md b/vscode-extensions/tls-sync-vscode-ui-extension/README.md deleted file mode 100644 index 7fcea8962e3..00000000000 --- a/vscode-extensions/tls-sync-vscode-ui-extension/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# TLS Sync VS Code Extension (Local / UI Extension) - -This extension is designed to work with the [TLS Sync VS Code Workspace extension](../tls-sync-vscode-workspace-extension), providing a seamless experience for managing TLS certificates in a remote development environment. - -The UI extension manages the certificates on the local machine and provides an API for the Workspace extension to synchronize certificates with the remote workspace. - -See [TLS Sync VS Code Workspace extension](../tls-sync-vscode-workspace-extension) for more details on the TLS Sync extensions. diff --git a/vscode-extensions/tls-sync-vscode-ui-extension/assets/extension-icon.png b/vscode-extensions/tls-sync-vscode-ui-extension/assets/extension-icon.png deleted file mode 100644 index ee2bd331df96e930c9181f48182347cc3da6ba3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19653 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 W+@6Y(qNxHvd>rju552JQN&XKmPupGq diff --git a/vscode-extensions/tls-sync-vscode-ui-extension/assets/walkthrough-settings.md b/vscode-extensions/tls-sync-vscode-ui-extension/assets/walkthrough-settings.md deleted file mode 100644 index ebb255bcac5..00000000000 --- a/vscode-extensions/tls-sync-vscode-ui-extension/assets/walkthrough-settings.md +++ /dev/null @@ -1 +0,0 @@ -Change where TLS Sync stores and sync certificates. diff --git a/vscode-extensions/tls-sync-vscode-ui-extension/assets/walkthrough-sync.md b/vscode-extensions/tls-sync-vscode-ui-extension/assets/walkthrough-sync.md deleted file mode 100644 index 01bd1268880..00000000000 --- a/vscode-extensions/tls-sync-vscode-ui-extension/assets/walkthrough-sync.md +++ /dev/null @@ -1 +0,0 @@ -Sync TLS certificates from your local machine to the VSCode remote workspace. diff --git a/vscode-extensions/tls-sync-vscode-ui-extension/config/heft.json b/vscode-extensions/tls-sync-vscode-ui-extension/config/heft.json deleted file mode 100644 index e1dd458ed26..00000000000 --- a/vscode-extensions/tls-sync-vscode-ui-extension/config/heft.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "$schema": "https://developer.microsoft.com/json-schemas/heft/v0/heft.schema.json", - - "extends": "@rushstack/heft-vscode-extension-rig/profiles/default/config/heft.json" -} diff --git a/vscode-extensions/tls-sync-vscode-ui-extension/config/rig.json b/vscode-extensions/tls-sync-vscode-ui-extension/config/rig.json deleted file mode 100644 index ec33a848348..00000000000 --- a/vscode-extensions/tls-sync-vscode-ui-extension/config/rig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json", - - "rigPackageName": "@rushstack/heft-vscode-extension-rig" -} diff --git a/vscode-extensions/tls-sync-vscode-ui-extension/config/rush-project.json b/vscode-extensions/tls-sync-vscode-ui-extension/config/rush-project.json deleted file mode 100644 index b2453d544bc..00000000000 --- a/vscode-extensions/tls-sync-vscode-ui-extension/config/rush-project.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/rush-project.schema.json", - "extends": "@rushstack/heft-vscode-extension-rig/profiles/default/config/rush-project.json" -} diff --git a/vscode-extensions/tls-sync-vscode-ui-extension/package.json b/vscode-extensions/tls-sync-vscode-ui-extension/package.json deleted file mode 100644 index d9fabbb2fab..00000000000 --- a/vscode-extensions/tls-sync-vscode-ui-extension/package.json +++ /dev/null @@ -1,178 +0,0 @@ -{ - "name": "tls-sync-vscode-ui-extension", - "version": "0.0.0", - "repository": { - "type": "git", - "url": "https://github.com/microsoft/rushstack.git", - "directory": "vscode-extensions/tls-sync-vscode-ui-extension" - }, - "license": "MIT", - "publisher": "RushStack", - "preview": true, - "displayName": "TLS Sync (UI Extension)", - "description": "", - "homepage": "https://github.com/microsoft/rushstack/tree/main/vscode-extensions/tls-sync-vscode-ui-extension", - "icon": "assets/extension-icon.png", - "extensionKind": [ - "ui" - ], - "categories": [ - "Other" - ], - "keywords": [], - "galleryBanner": { - "color": "#f0f0f0", - "theme": "light" - }, - "engines": { - "vscode": "^1.98.0" - }, - "main": "./extension.js", - "scripts": { - "build": "heft build --clean", - "build:watch": "heft build-watch", - "start": "heft start", - "_phase:build": "heft run --only build -- --clean", - "_phase:test": "" - }, - "contributes": { - "commands": [ - { - "command": "tlssync.ui.showLog", - "title": "Show Log", - "category": "TLS Sync" - }, - { - "command": "tlssync.ui.untrustCertificate", - "title": "Untrust Certificate", - "category": "TLS Sync" - }, - { - "command": "tlssync.ui.ensureCertificate", - "title": "Ensure Certificate", - "category": "TLS Sync" - }, - { - "command": "tlssync.ui.sync", - "title": "Sync TLS Certificates", - "category": "TLS Sync" - }, - { - "command": "tlssync.ui.showWalkthrough", - "title": "Show Walkthrough", - "category": "TLS Sync" - }, - { - "command": "tlssync.ui.showSettings", - "title": "Show Settings", - "category": "TLS Sync" - } - ], - "walkthroughs": [ - { - "id": "sync-certificates", - "title": "TLS Sync", - "description": "Sync your debug TLS certificates with the remote machine.", - "featuredFor": [ - ".tlssync" - ], - "steps": [ - { - "id": "runsynccommand", - "title": "Sync Certificates", - "description": "[Sync](command:tlssync.ui.sync)", - "completionEvents": [ - "onContext:tlssync.ui.sync.complete" - ], - "media": { - "markdown": "./media/walkthrough-sync.md" - } - }, - { - "id": "changetlssyncsettings", - "title": "Change Sync Settings", - "description": "[Open Settings](command:tlssync.ui.showSettings)", - "completionEvents": [], - "media": { - "markdown": "./media/walkthrough-settings.md" - } - } - ] - } - ], - "configuration": { - "title": "TLS Sync", - "properties": { - "tlssync.ui.storePath.osx": { - "type": "string", - "title": "UI Extension - TLS Sync Store Path (macOS)", - "description": "[UI Extension] Directory where TLS certificates are read and written." - }, - "tlssync.ui.storePath.windows": { - "type": "string", - "title": "UI Extension - TLS Sync Store Path (Windows)", - "description": "[UI Extension] Directory where TLS certificates are read and written." - }, - "tlssync.ui.storePath.linux": { - "type": "string", - "title": "UI Extension - TLS Sync Store Path (Linux)", - "description": "[UI Extension] Directory where TLS certificates are read and written." - }, - "tlssync.workspace.storePath.osx": { - "type": "string", - "title": "Workspace Extension - TLS Sync Store Path (macOS)", - "description": "[Workspace Extension] Directory where TLS certificates are read and written." - }, - "tlssync.workspace.storePath.windows": { - "type": "string", - "title": "Workspace Extension - TLS Sync Store Path (Windows)", - "description": "[Workspace Extension] Directory where TLS certificates are read and written." - }, - "tlssync.workspace.storePath.linux": { - "type": "string", - "title": "Workspace Extension - TLS Sync Store Path (Linux)", - "description": "[Workspace Extension] Directory where TLS certificates are read and written." - }, - "tlssync.caCertificateFilename": { - "type": "string", - "title": "CA Certificate Filename", - "description": "Filename for the CA certificate." - }, - "tlssync.certificateFilename": { - "type": "string", - "title": "Server Certificate Filename", - "description": "Filename for the server certificate." - }, - "tlssync.keyFilename": { - "type": "string", - "title": "Server Key Filename", - "description": "Filename for the server key." - }, - "tlssync.autoSync": { - "type": "boolean", - "title": "Automatically Sync Certificates", - "default": true, - "description": "Check certificates when extension is activated. Extension is automatically activated when a `.tlssync` file is present in the workspace." - } - } - } - }, - "activationEvents": [ - "workspaceContains:**/.tlssync" - ], - "dependencies": { - "@rushstack/debug-certificate-manager": "workspace:*", - "@rushstack/node-core-library": "workspace:*", - "@rushstack/terminal": "workspace:*", - "@rushstack/tls-sync-vscode-shared": "workspace:*", - "@rushstack/vscode-shared": "workspace:*", - "tslib": "~2.3.1" - }, - "devDependencies": { - "@rushstack/heft-vscode-extension-rig": "workspace:*", - "@rushstack/heft": "workspace:*", - "@types/node": "20.17.19", - "@types/vscode": "^1.63.0", - "@types/webpack-env": "1.18.8" - } -} diff --git a/vscode-extensions/tls-sync-vscode-ui-extension/webpack.config.js b/vscode-extensions/tls-sync-vscode-ui-extension/webpack.config.js deleted file mode 100644 index bc6dfe67040..00000000000 --- a/vscode-extensions/tls-sync-vscode-ui-extension/webpack.config.js +++ /dev/null @@ -1,23 +0,0 @@ -// @ts-check -/* eslint-env es6 */ - -'use strict'; - -const { - createExtensionConfig -} = require('@rushstack/heft-vscode-extension-rig/profiles/default/webpack.config.base'); -const path = require('node:path'); - -function createConfig({ production, webpack }) { - const config = createExtensionConfig({ - production, - webpack, - entry: { - extension: './lib/extension.js' - }, - outputPath: path.resolve(__dirname, 'dist', 'vsix', 'unpacked') - }); - return config; -} - -module.exports = createConfig; diff --git a/vscode-extensions/tls-sync-vscode-workspace-extension/.npmignore b/vscode-extensions/tls-sync-vscode-workspace-extension/.npmignore deleted file mode 100644 index dcf329e5ffa..00000000000 --- a/vscode-extensions/tls-sync-vscode-workspace-extension/.npmignore +++ /dev/null @@ -1,2 +0,0 @@ -# Ignore all files by default, to avoid accidentally publishing unintended files. -** diff --git a/vscode-extensions/tls-sync-vscode-workspace-extension/LICENSE b/vscode-extensions/tls-sync-vscode-workspace-extension/LICENSE deleted file mode 100644 index cee4fe2d135..00000000000 --- a/vscode-extensions/tls-sync-vscode-workspace-extension/LICENSE +++ /dev/null @@ -1,24 +0,0 @@ -tls-sync-vscode-workspace-extension - -Copyright (c) Microsoft Corporation. All rights reserved. - -MIT License - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vscode-extensions/tls-sync-vscode-workspace-extension/README.md b/vscode-extensions/tls-sync-vscode-workspace-extension/README.md deleted file mode 100644 index eb99cbde6e9..00000000000 --- a/vscode-extensions/tls-sync-vscode-workspace-extension/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# TLS Sync VS Code Extension (Remote / Workspace Extension) - -This extension is designed to work with the [TLS Sync VS Code UI extension](../tls-sync-vscode-ui-extension), providing a seamless experience for managing TLS certificates in a remote workspace environment. - -The Workspace extension gets the client's debug certificate from the UI extension and writes it to the remote workspace. diff --git a/vscode-extensions/tls-sync-vscode-workspace-extension/assets/extension-icon.png b/vscode-extensions/tls-sync-vscode-workspace-extension/assets/extension-icon.png deleted file mode 100644 index ee2bd331df96e930c9181f48182347cc3da6ba3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19653 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 W+@6Y(qNxHvd>rju552JQN&XKmPupGq diff --git a/vscode-extensions/tls-sync-vscode-workspace-extension/config/heft.json b/vscode-extensions/tls-sync-vscode-workspace-extension/config/heft.json deleted file mode 100644 index e1dd458ed26..00000000000 --- a/vscode-extensions/tls-sync-vscode-workspace-extension/config/heft.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "$schema": "https://developer.microsoft.com/json-schemas/heft/v0/heft.schema.json", - - "extends": "@rushstack/heft-vscode-extension-rig/profiles/default/config/heft.json" -} diff --git a/vscode-extensions/tls-sync-vscode-workspace-extension/config/rig.json b/vscode-extensions/tls-sync-vscode-workspace-extension/config/rig.json deleted file mode 100644 index ec33a848348..00000000000 --- a/vscode-extensions/tls-sync-vscode-workspace-extension/config/rig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json", - - "rigPackageName": "@rushstack/heft-vscode-extension-rig" -} diff --git a/vscode-extensions/tls-sync-vscode-workspace-extension/config/rush-project.json b/vscode-extensions/tls-sync-vscode-workspace-extension/config/rush-project.json deleted file mode 100644 index b2453d544bc..00000000000 --- a/vscode-extensions/tls-sync-vscode-workspace-extension/config/rush-project.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/rush-project.schema.json", - "extends": "@rushstack/heft-vscode-extension-rig/profiles/default/config/rush-project.json" -} diff --git a/vscode-extensions/tls-sync-vscode-workspace-extension/eslint.config.js b/vscode-extensions/tls-sync-vscode-workspace-extension/eslint.config.js deleted file mode 100644 index eac79367926..00000000000 --- a/vscode-extensions/tls-sync-vscode-workspace-extension/eslint.config.js +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -const nodeTrustedToolProfile = require('@rushstack/heft-vscode-extension-rig/profiles/default/includes/eslint/flat/profile/node-trusted-tool'); -const friendlyLocalsMixin = require('@rushstack/heft-vscode-extension-rig/profiles/default/includes/eslint/flat/mixins/friendly-locals'); - -module.exports = [...nodeTrustedToolProfile, ...friendlyLocalsMixin]; diff --git a/vscode-extensions/tls-sync-vscode-workspace-extension/package.json b/vscode-extensions/tls-sync-vscode-workspace-extension/package.json deleted file mode 100644 index 5abb8235b56..00000000000 --- a/vscode-extensions/tls-sync-vscode-workspace-extension/package.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "name": "tls-sync-vscode-workspace-extension", - "version": "0.0.0", - "repository": { - "type": "git", - "url": "https://github.com/microsoft/rushstack.git", - "directory": "vscode-extensions/tls-sync-vscode-workspace-extension" - }, - "license": "MIT", - "publisher": "RushStack", - "preview": true, - "displayName": "TLS Sync (Workspace Extension)", - "description": "", - "homepage": "https://github.com/microsoft/rushstack/tree/main/vscode-extensions/tls-sync-vscode-workspace-extension", - "icon": "assets/extension-icon.png", - "extensionKind": [ - "workspace" - ], - "categories": [ - "Other" - ], - "keywords": [], - "galleryBanner": { - "color": "#f0f0f0", - "theme": "light" - }, - "engines": { - "vscode": "^1.98.0" - }, - "main": "./extension.js", - "scripts": { - "build": "heft build --clean", - "build:watch": "heft build-watch", - "start": "heft start", - "_phase:build": "heft run --only build -- --clean", - "_phase:test": "" - }, - "contributes": { - "commands": [ - { - "command": "tlssync.workspace.showLog", - "title": "Show Log (Workspace)", - "category": "TLS Sync" - } - ] - }, - "activationEvents": [ - "onCommand:tlssync.workspace.ping", - "onCommand:tlssync.workspace.sync", - "workspaceContains:**/.tlssync" - ], - "dependencies": { - "@rushstack/debug-certificate-manager": "workspace:*", - "@rushstack/node-core-library": "workspace:*", - "@rushstack/terminal": "workspace:*", - "@rushstack/tls-sync-vscode-shared": "workspace:*", - "@rushstack/vscode-shared": "workspace:*", - "tslib": "~2.3.1" - }, - "devDependencies": { - "@rushstack/heft-vscode-extension-rig": "workspace:*", - "@rushstack/heft": "workspace:*", - "@types/node": "20.17.19", - "@types/vscode": "^1.63.0", - "@types/webpack-env": "1.18.8" - } -} diff --git a/vscode-extensions/tls-sync-vscode-workspace-extension/src/extension.ts b/vscode-extensions/tls-sync-vscode-workspace-extension/src/extension.ts deleted file mode 100644 index 30e18838981..00000000000 --- a/vscode-extensions/tls-sync-vscode-workspace-extension/src/extension.ts +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -import * as vscode from 'vscode'; - -import { CertificateStore, type ICertificate } from '@rushstack/debug-certificate-manager'; -import { Terminal } from '@rushstack/terminal'; - -import { VScodeOutputChannelTerminalProvider } from '@rushstack/vscode-shared/lib/VScodeOutputChannelTerminalProvider'; -import { getCertificateStore } from '@rushstack/tls-sync-vscode-shared/lib/certificates'; -import { - WORKSPACE_COMMAND_PING, - WORKSPACE_COMMAND_SHOW_LOG, - WORKSPACE_COMMAND_SYNC, - WORKSPACE_EXTENSION_DISPLAY_NAME -} from '@rushstack/tls-sync-vscode-shared/lib/constants'; - -import { version } from '../package.json'; - -/* - * This extension provides commands to sync debug TLS certificates with the UI extension. This allows for VS Code - * remotes to use the same certificates as the local machine. - */ - -export function activate(context: vscode.ExtensionContext): void { - const outputChannel: vscode.OutputChannel = vscode.window.createOutputChannel( - WORKSPACE_EXTENSION_DISPLAY_NAME - ); - const terminalProvider: VScodeOutputChannelTerminalProvider = new VScodeOutputChannelTerminalProvider( - outputChannel, - { - verboseEnabled: true, - debugEnabled: true - } - ); - const terminal: Terminal = new Terminal(terminalProvider); - terminal.writeLine(`${WORKSPACE_EXTENSION_DISPLAY_NAME} output channel initialized.`); - - async function handleShowLog(): Promise { - outputChannel.show(); - } - - async function handleSyncCertificates(certificatesFromUI: ICertificate): Promise { - const certificateStore: CertificateStore = getCertificateStore(terminal, 'workspace'); - - void vscode.window.showInformationMessage(`Synchronizing TLS certificates.`); - terminal.writeLine('Starting certificate synchronization...'); - try { - if (!certificatesFromUI) { - void vscode.window.showErrorMessage( - 'No certificates found in the UI extension. Please ensure the UI extension is installed and configured.' - ); - terminal.writeLine('No certificates found in the UI extension. Synchronization aborted.'); - return false; - } - - let isSynchronized: boolean = false; - isSynchronized = - certificateStore.caCertificateData === certificatesFromUI.pemCaCertificate && - certificateStore.certificateData === certificatesFromUI.pemCertificate && - certificateStore.keyData === certificatesFromUI.pemKey; - if (isSynchronized) { - void vscode.window.showInformationMessage( - 'Local certificates are already synchronized with UI certificates.' - ); - return true; - } - - certificateStore.caCertificateData = certificatesFromUI.pemCaCertificate; - terminal.writeLine(`Writing CA certificate to ${certificateStore.caCertificatePath}...`); - - certificateStore.certificateData = certificatesFromUI.pemCertificate; - terminal.writeLine(`Writing TLS server certificates to ${certificateStore.certificateData}...`); - - certificateStore.keyData = certificatesFromUI.pemKey; - terminal.writeLine(`Writing TLS private key to ${certificateStore.keyPath}...`); - - terminal.writeLine(`Certificates synchronized successfully.`); - return true; - } catch (err) { - const message: string = `Error synchronizing certificates: ${ - err instanceof Error ? err.message : 'Unknown error' - }`; - terminal.writeLine(message); - void vscode.window.showErrorMessage(message); - return false; - } - } - - function handlePing(): { version: string } { - return { - version - }; - } - - context.subscriptions.push( - outputChannel, - vscode.commands.registerCommand(WORKSPACE_COMMAND_PING, handlePing), - vscode.commands.registerCommand(WORKSPACE_COMMAND_SHOW_LOG, handleShowLog), - vscode.commands.registerCommand(WORKSPACE_COMMAND_SYNC, handleSyncCertificates) - ); -} - -export function deactivate(): void {} diff --git a/vscode-extensions/tls-sync-vscode-workspace-extension/tsconfig.json b/vscode-extensions/tls-sync-vscode-workspace-extension/tsconfig.json deleted file mode 100644 index 1226387ecf8..00000000000 --- a/vscode-extensions/tls-sync-vscode-workspace-extension/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "./node_modules/@rushstack/heft-vscode-extension-rig/profiles/default/tsconfig-base.json" -} From 24363b191a77bc1ea01b7595c9e674512685861e Mon Sep 17 00:00:00 2001 From: Bharat Middha <5100938+bmiddha@users.noreply.github.com> Date: Thu, 24 Jul 2025 15:09:01 -0700 Subject: [PATCH 2/9] rush change --- .../bmiddha-tls-sync-v2_2025-07-24-22-08.json | 10 ++++++++++ .../bmiddha-tls-sync-v2_2025-07-24-22-08.json | 10 ++++++++++ 2 files changed, 20 insertions(+) create mode 100644 common/changes/@rushstack/debug-certificate-manager/bmiddha-tls-sync-v2_2025-07-24-22-08.json create mode 100644 common/changes/@rushstack/heft-vscode-extension-plugin/bmiddha-tls-sync-v2_2025-07-24-22-08.json diff --git a/common/changes/@rushstack/debug-certificate-manager/bmiddha-tls-sync-v2_2025-07-24-22-08.json b/common/changes/@rushstack/debug-certificate-manager/bmiddha-tls-sync-v2_2025-07-24-22-08.json new file mode 100644 index 00000000000..b6da4f16a64 --- /dev/null +++ b/common/changes/@rushstack/debug-certificate-manager/bmiddha-tls-sync-v2_2025-07-24-22-08.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@rushstack/debug-certificate-manager", + "comment": "Read CertificateStore configuration from .vscode/debug-certificate-manager.json", + "type": "patch" + } + ], + "packageName": "@rushstack/debug-certificate-manager" +} \ No newline at end of file diff --git a/common/changes/@rushstack/heft-vscode-extension-plugin/bmiddha-tls-sync-v2_2025-07-24-22-08.json b/common/changes/@rushstack/heft-vscode-extension-plugin/bmiddha-tls-sync-v2_2025-07-24-22-08.json new file mode 100644 index 00000000000..d41b913ed50 --- /dev/null +++ b/common/changes/@rushstack/heft-vscode-extension-plugin/bmiddha-tls-sync-v2_2025-07-24-22-08.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@rushstack/heft-vscode-extension-plugin", + "comment": "", + "type": "none" + } + ], + "packageName": "@rushstack/heft-vscode-extension-plugin" +} \ No newline at end of file From 11cda2b70a4669f32444e1799e51f1c6cde1e5c0 Mon Sep 17 00:00:00 2001 From: Bharat Middha <5100938+bmiddha@users.noreply.github.com> Date: Thu, 24 Jul 2025 15:31:34 -0700 Subject: [PATCH 3/9] update readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 47fd02c3af0..e29e2ca185b 100644 --- a/README.md +++ b/README.md @@ -218,6 +218,7 @@ These GitHub repositories provide supplementary resources for Rush Stack: | [/rigs/local-node-rig](./rigs/local-node-rig/) | A rig package for Node.js projects that build using Heft inside the RushStack repository. | | [/rigs/local-web-rig](./rigs/local-web-rig/) | A rig package for Web projects that build using Heft inside the RushStack repository. | | [/rush-plugins/rush-litewatch-plugin](./rush-plugins/rush-litewatch-plugin/) | An experimental alternative approach for multi-project watch mode | +| [/vscode-extensions/debug-certificate-manager-vscode-extension](./vscode-extensions/debug-certificate-manager-vscode-extension/) | | | [/vscode-extensions/rush-vscode-command-webview](./vscode-extensions/rush-vscode-command-webview/) | Part of the Rush Stack VSCode extension, provides a UI for invoking Rush commands | | [/vscode-extensions/rush-vscode-extension](./vscode-extensions/rush-vscode-extension/) | Enhanced experience for monorepos that use the Rush Stack toolchain | | [/vscode-extensions/vscode-shared](./vscode-extensions/vscode-shared/) | | From 9e12fc4b6306b0be5de63005e96d377ce4599d03 Mon Sep 17 00:00:00 2001 From: Bharat Middha <5100938+bmiddha@users.noreply.github.com> Date: Thu, 24 Jul 2025 17:01:20 -0700 Subject: [PATCH 4/9] add signing task --- .../vscode-extension-publish.yaml | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/common/config/azure-pipelines/vscode-extension-publish.yaml b/common/config/azure-pipelines/vscode-extension-publish.yaml index 935c984a08f..02983d13574 100644 --- a/common/config/azure-pipelines/vscode-extension-publish.yaml +++ b/common/config/azure-pipelines/vscode-extension-publish.yaml @@ -75,7 +75,28 @@ extends: displayName: 'Prepare manifest for signing: ${{ extension.key }}' workingDirectory: samples/extension-signing - # TODO: add ESRP signing task here + - task: EsrpCodeSigning@5 + displayName: 'ESRP CodeSigning' + inputs: + connectedservicename: '' # TODO + AppRegistrationClientId: '' # TODO + AppRegistrationTenantId: '' # TODO + AuthAKVName: '' # TODO + AuthCertName: '' # TODO + AuthSignCertName: '' # TODO + FolderPath: '${{ extension.projectPath }}' + Pattern: 'extension.signature.p7s' + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "keyCode": "CP-401405", + "operationSetCode": "VSCodePublisherSign", + "parameters": [], + "toolName": "sign", + "toolVersion": "1.0" + } + ] - task: AzureCLI@2 displayName: 'Publish VSIX: ${{ extension.key }}' From afd4b1477d78ad5f0f1503143994d77858003890 Mon Sep 17 00:00:00 2001 From: Bharat Middha <5100938+bmiddha@users.noreply.github.com> Date: Thu, 24 Jul 2025 17:36:36 -0700 Subject: [PATCH 5/9] update package description --- .../debug-certificate-manager-vscode-extension/README.md | 2 ++ .../debug-certificate-manager-vscode-extension/package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/vscode-extensions/debug-certificate-manager-vscode-extension/README.md b/vscode-extensions/debug-certificate-manager-vscode-extension/README.md index 31564a227b3..d1006981386 100644 --- a/vscode-extensions/debug-certificate-manager-vscode-extension/README.md +++ b/vscode-extensions/debug-certificate-manager-vscode-extension/README.md @@ -1,5 +1,7 @@ # Debug Certificate Manager VS Code Extension +VS Code extension to manage debug TLS certificates and sync them to the VS Code workspace. Works with VS Code remote development (Codespaces, SSH, Dev Containers, WSL, VS Code Tunnels). + ## Sync Process The Debug Certificate Manager extension uses `@rushstack/debug-certificate-manager` to manage TLS certificates. It can also be used to sync certificates when connected to a VS Code remote workspace (WSL, Codespaces, Devcontainers, VS Code Tunnels). diff --git a/vscode-extensions/debug-certificate-manager-vscode-extension/package.json b/vscode-extensions/debug-certificate-manager-vscode-extension/package.json index b4f3db0f5e3..d5b51baac28 100644 --- a/vscode-extensions/debug-certificate-manager-vscode-extension/package.json +++ b/vscode-extensions/debug-certificate-manager-vscode-extension/package.json @@ -10,7 +10,7 @@ "publisher": "RushStack", "preview": true, "displayName": "Debug Certificate Manager", - "description": "", + "description": "VS Code extension to manage debug TLS certificates and sync them to the VS Code workspace. Works with VS Code remote development (Codespaces, SSH, Dev Containers, WSL, VS Code Tunnels).", "homepage": "https://github.com/microsoft/rushstack/tree/main/vscode-extensions/debug-certificate-manager-vscode-extension", "icon": "assets/extension-icon.png", "extensionKind": [ From 0d90f2ffcf861efb503dca71aa23ce974dbeebb5 Mon Sep 17 00:00:00 2001 From: Bharat Middha <5100938+bmiddha@users.noreply.github.com> Date: Thu, 24 Jul 2025 17:36:49 -0700 Subject: [PATCH 6/9] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e29e2ca185b..009488b2501 100644 --- a/README.md +++ b/README.md @@ -218,7 +218,7 @@ These GitHub repositories provide supplementary resources for Rush Stack: | [/rigs/local-node-rig](./rigs/local-node-rig/) | A rig package for Node.js projects that build using Heft inside the RushStack repository. | | [/rigs/local-web-rig](./rigs/local-web-rig/) | A rig package for Web projects that build using Heft inside the RushStack repository. | | [/rush-plugins/rush-litewatch-plugin](./rush-plugins/rush-litewatch-plugin/) | An experimental alternative approach for multi-project watch mode | -| [/vscode-extensions/debug-certificate-manager-vscode-extension](./vscode-extensions/debug-certificate-manager-vscode-extension/) | | +| [/vscode-extensions/debug-certificate-manager-vscode-extension](./vscode-extensions/debug-certificate-manager-vscode-extension/) | VS Code extension to manage debug TLS certificates and sync them to the VS Code workspace. Works with VS Code remote development (Codespaces, SSH, Dev Containers, WSL, VS Code Tunnels). | | [/vscode-extensions/rush-vscode-command-webview](./vscode-extensions/rush-vscode-command-webview/) | Part of the Rush Stack VSCode extension, provides a UI for invoking Rush commands | | [/vscode-extensions/rush-vscode-extension](./vscode-extensions/rush-vscode-extension/) | Enhanced experience for monorepos that use the Rush Stack toolchain | | [/vscode-extensions/vscode-shared](./vscode-extensions/vscode-shared/) | | From 64ddb6c9ab396f8bc657cd3ee0779277fa475ed5 Mon Sep 17 00:00:00 2001 From: Bharat Middha <5100938+bmiddha@users.noreply.github.com> Date: Thu, 24 Jul 2025 23:07:47 -0700 Subject: [PATCH 7/9] use heft plugin to generate extension manifest and publish vsix --- .../bmiddha-tls-sync-v2_2025-07-24-22-08.json | 9 +- .../vscode-extension-publish.yaml | 11 +-- .../heft-plugin.json | 32 ++++++- .../src/VSCodeExtensionPackagePlugin.ts | 80 +++++++--------- .../src/VSCodeExtensionPublishPlugin.ts | 94 +++++++++++++++++++ .../heft-vscode-extension-plugin/src/util.ts | 42 +++++++++ .../profiles/default/config/heft.json | 21 ++++- .../package.json | 4 +- 8 files changed, 231 insertions(+), 62 deletions(-) create mode 100644 heft-plugins/heft-vscode-extension-plugin/src/VSCodeExtensionPublishPlugin.ts create mode 100644 heft-plugins/heft-vscode-extension-plugin/src/util.ts diff --git a/common/changes/@rushstack/heft-vscode-extension-plugin/bmiddha-tls-sync-v2_2025-07-24-22-08.json b/common/changes/@rushstack/heft-vscode-extension-plugin/bmiddha-tls-sync-v2_2025-07-24-22-08.json index d41b913ed50..a1f9f43b0ee 100644 --- a/common/changes/@rushstack/heft-vscode-extension-plugin/bmiddha-tls-sync-v2_2025-07-24-22-08.json +++ b/common/changes/@rushstack/heft-vscode-extension-plugin/bmiddha-tls-sync-v2_2025-07-24-22-08.json @@ -2,8 +2,13 @@ "changes": [ { "packageName": "@rushstack/heft-vscode-extension-plugin", - "comment": "", - "type": "none" + "comment": "Add support for generating extension manifest.", + "type": "minor" + }, + { + "packageName": "@rushstack/heft-vscode-extension-plugin", + "comment": "Add VSIX publish plugin.", + "type": "minor" } ], "packageName": "@rushstack/heft-vscode-extension-plugin" diff --git a/common/config/azure-pipelines/vscode-extension-publish.yaml b/common/config/azure-pipelines/vscode-extension-publish.yaml index 02983d13574..b73321d7aca 100644 --- a/common/config/azure-pipelines/vscode-extension-publish.yaml +++ b/common/config/azure-pipelines/vscode-extension-publish.yaml @@ -11,9 +11,11 @@ parameters: default: - key: 'debug-certificate-manager-vscode-extension' vsixPath: 'dist/vsix/packaged.vsix' + manifestPath: 'dist/vsix/extension-manifest.json' projectPath: '$(Build.SourcesDirectory)/vscode-extensions/debug-certificate-manager-vscode-extension' - key: 'rush-vscode-extension' vsixPath: 'dist/vsix/packaged.vsix' + manifestPath: 'dist/vsix/extension-manifest.json' projectPath: '$(Build.SourcesDirectory)/vscode-extensions/rush-vscode-extension' resources: @@ -66,14 +68,9 @@ extends: az rest -u https://app.vssps.visualstudio.com/_apis/profile/profiles/me --resource 499b84ac-1321-427f-aa17-267ca6975798 - ${{ each extension in parameters.ExtensionPublishConfig }}: - - bash: | - node node_modules/@rushstack/heft-vscode-extension-rig/node_modules/@rushstack/heft-vscode-extension-plugin/node_modules/@vscode/vsce/vsce generate-manifest --packagePath ${{ extension.vsixPath }} --out ${{ extension.projectPath }}/extension.manifest - displayName: 'Generate VSIX manifest: ${{ extension.key }}' + - bash: cp ${{ extension.manifestPath }} ${{ extension.projectPath }}/extension.signature.p7s workingDirectory: ${{ extension.projectPath }} - - - bash: cp ${{ extension.projectPath }}/extension.manifest ${{ extension.projectPath }}/extension.signature.p7s displayName: 'Prepare manifest for signing: ${{ extension.key }}' - workingDirectory: samples/extension-signing - task: EsrpCodeSigning@5 displayName: 'ESRP CodeSigning' @@ -105,5 +102,5 @@ extends: scriptType: 'bash' scriptLocation: 'inlineScript' inlineScript: | - node node_modules/@rushstack/heft-vscode-extension-rig/node_modules/@rushstack/heft-vscode-extension-plugin/node_modules/@vscode/vsce/vsce publish --no-dependencies --azure-credential --packagePath ${{ extension.vsixPath }} --manifestPath ${{ extension.projectPath }}/extension.manifest --signaturePath ${{ extension.projectPath }}/extension.signature.p7s + node node_modules/@rushstack/heft/lib/start.js publish-vsix --vsix-path ${{ extension.vsixPath }} --manifest-path ${{ extension.projectPath }}/extension.manifest --signature-path ${{ extension.projectPath }}/extension.signature.p7s workingDirectory: ${{ extension.projectPath }} diff --git a/heft-plugins/heft-vscode-extension-plugin/heft-plugin.json b/heft-plugins/heft-vscode-extension-plugin/heft-plugin.json index 98845cc6151..ca301083ed4 100644 --- a/heft-plugins/heft-vscode-extension-plugin/heft-plugin.json +++ b/heft-plugins/heft-vscode-extension-plugin/heft-plugin.json @@ -1,10 +1,38 @@ { "$schema": "https://developer.microsoft.com/json-schemas/heft/v0/heft-plugin.schema.json", - "taskPlugins": [ { "pluginName": "vscode-extension-package-plugin", - "entryPoint": "./lib/VSCodeExtensionPackagePlugin.js" + "entryPoint": "./lib/VSCodeExtensionPackagePlugin.js", + "parameterScope": "package" + }, + { + "pluginName": "vscode-extension-publish-plugin", + "entryPoint": "./lib/VSCodeExtensionPublishPlugin.js", + "parameterScope": "publish-vsix", + "parameters": [ + { + "longName": "--vsix-path", + "parameterKind": "string", + "argumentName": "RELATIVE_PATH", + "description": "Use this parameter to control which VSIX file will be used for publishing.", + "required": true + }, + { + "longName": "--manifest-path", + "parameterKind": "string", + "argumentName": "RELATIVE_PATH", + "description": "Use this parameter to control which manifest file will be used for publishing.", + "required": true + }, + { + "longName": "--signature-path", + "parameterKind": "string", + "argumentName": "RELATIVE_PATH", + "description": "Use this parameter to control which signature file will be used for publishing.", + "required": true + } + ] } ] } diff --git a/heft-plugins/heft-vscode-extension-plugin/src/VSCodeExtensionPackagePlugin.ts b/heft-plugins/heft-vscode-extension-plugin/src/VSCodeExtensionPackagePlugin.ts index 02361d56c35..852886b8620 100644 --- a/heft-plugins/heft-vscode-extension-plugin/src/VSCodeExtensionPackagePlugin.ts +++ b/heft-plugins/heft-vscode-extension-plugin/src/VSCodeExtensionPackagePlugin.ts @@ -7,10 +7,9 @@ import type { IHeftTaskSession, IHeftTaskRunHookOptions } from '@rushstack/heft'; -import { Executable, IExecutableSpawnOptions, IWaitForExitResult } from '@rushstack/node-core-library'; -import type { ChildProcess } from 'node:child_process'; +import type { IWaitForExitResult } from '@rushstack/node-core-library'; import * as path from 'node:path'; -import { TerminalStreamWritable, TerminalProviderSeverity, ITerminal } from '@rushstack/terminal'; +import { execuateAndWaitAsync, vsceScriptPath } from './util'; interface IVSCodeExtensionPackagePluginOptions { /** @@ -24,45 +23,15 @@ interface IVSCodeExtensionPackagePluginOptions { * If a directory is provided, the VSIX file will be named based on the extension's `package.json` name and version. */ vsixPath: string; + /** + * The path where the generated manifest file will be saved. + * This manifest is used for signing the VS Code extension. + */ + manifestPath: string; } const PLUGIN_NAME: 'vscode-extension-package-plugin' = 'vscode-extension-package-plugin'; -const vsceBasePackagePath: string = require.resolve('@vscode/vsce/package.json'); -const vsceScript: string = path.resolve(vsceBasePackagePath, '../vsce'); - -async function execuateAndWaitAsync( - terminal: ITerminal, - command: string, - args: string[], - options: Omit = {} -): Promise> { - const childProcess: ChildProcess = Executable.spawn(command, args, { - ...options, - stdio: [ - 'ignore', // stdin - 'pipe', // stdout - 'pipe' // stderr - ] - }); - childProcess.stdout?.pipe( - new TerminalStreamWritable({ - terminal, - severity: TerminalProviderSeverity.log - }) - ); - childProcess.stderr?.pipe( - new TerminalStreamWritable({ - terminal, - severity: TerminalProviderSeverity.error - }) - ); - const result: IWaitForExitResult = await Executable.waitForExitAsync(childProcess, { - encoding: 'utf8' - }); - return result; -} - export default class VSCodeExtensionPackagePlugin implements IHeftTaskPlugin { @@ -72,27 +41,50 @@ export default class VSCodeExtensionPackagePlugin pluginOptions: IVSCodeExtensionPackagePluginOptions ): void { heftTaskSession.hooks.run.tapPromise(PLUGIN_NAME, async (runOptions: IHeftTaskRunHookOptions) => { - const { unpackedFolderPath, vsixPath } = pluginOptions; + const { unpackedFolderPath, vsixPath, manifestPath } = pluginOptions; + const { buildFolderPath } = heftConfiguration; const { logger: { terminal } } = heftTaskSession; - terminal.writeLine(`Using VSCE script: ${vsceScript}`); - terminal.writeLine(`Packaging VSIX from ${unpackedFolderPath} to ${vsixPath}`); + terminal.writeLine(`Using VSCE script: ${vsceScriptPath}`); + terminal.writeLine(`Packaging VSIX from ${unpackedFolderPath} to ${vsixPath}`); const packageResult: IWaitForExitResult = await execuateAndWaitAsync( terminal, 'node', - [vsceScript, 'package', '--no-dependencies', '--out', path.resolve(vsixPath)], + [vsceScriptPath, 'package', '--no-dependencies', '--out', path.resolve(vsixPath)], { - currentWorkingDirectory: path.resolve(unpackedFolderPath) + currentWorkingDirectory: path.resolve(buildFolderPath, unpackedFolderPath) } ); if (packageResult.exitCode !== 0) { throw new Error(`VSIX packaging failed with exit code ${packageResult.exitCode}`); } - terminal.writeLine('VSIX successfully packaged.'); + + terminal.writeLine(`Generating manifest at ${manifestPath}`); + const manifestResult: IWaitForExitResult = await execuateAndWaitAsync( + terminal, + 'node', + [ + vsceScriptPath, + 'generate-manifest', + '--packagePath', + path.resolve(vsixPath), + '--out', + path.resolve(manifestPath) + ], + { + currentWorkingDirectory: buildFolderPath + } + ); + if (manifestResult.exitCode !== 0) { + throw new Error(`Manifest generation failed with exit code ${manifestResult.exitCode}`); + } + terminal.writeLine('Manifest successfully generated.'); + + terminal.writeLine(`VSIX package and manifest generation completed successfully.`); }); } } diff --git a/heft-plugins/heft-vscode-extension-plugin/src/VSCodeExtensionPublishPlugin.ts b/heft-plugins/heft-vscode-extension-plugin/src/VSCodeExtensionPublishPlugin.ts new file mode 100644 index 00000000000..47ca14777fa --- /dev/null +++ b/heft-plugins/heft-vscode-extension-plugin/src/VSCodeExtensionPublishPlugin.ts @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import type { + HeftConfiguration, + IHeftTaskPlugin, + IHeftTaskSession, + IHeftTaskRunHookOptions, + CommandLineStringParameter +} from '@rushstack/heft'; +import type { IWaitForExitResult } from '@rushstack/node-core-library'; +import * as path from 'node:path'; +import { execuateAndWaitAsync, vsceScriptPath } from './util'; + +interface IVSCodeExtensionPublishPluginOptions {} + +const PLUGIN_NAME: 'vscode-extension-publish-plugin' = 'vscode-extension-publish-plugin'; + +const VSIX_PATH_PARAMETER_NAME: string = '--vsix-path'; +const MANIFEST_PATH_PARAMETER_NAME: string = '--manifest-path'; +const SIGNATURE_PATH_PARAMETER_NAME: string = '--signature-path'; + +export default class VSCodeExtensionPublishPlugin + implements IHeftTaskPlugin +{ + public apply( + heftTaskSession: IHeftTaskSession, + heftConfiguration: HeftConfiguration, + pluginOptions: IVSCodeExtensionPublishPluginOptions + ): void { + const vsixPathParameter: CommandLineStringParameter = + heftTaskSession.parameters.getStringParameter(VSIX_PATH_PARAMETER_NAME); + const manifestPathParameter: CommandLineStringParameter = heftTaskSession.parameters.getStringParameter( + MANIFEST_PATH_PARAMETER_NAME + ); + const signaturePathParameter: CommandLineStringParameter = heftTaskSession.parameters.getStringParameter( + SIGNATURE_PATH_PARAMETER_NAME + ); + + if (!vsixPathParameter.value) { + throw new Error( + `The parameter "${VSIX_PATH_PARAMETER_NAME}" is required for the VSCodeExtensionPublishPlugin.` + ); + } + if (!manifestPathParameter.value) { + throw new Error( + `The parameter "${MANIFEST_PATH_PARAMETER_NAME}" is required for the VSCodeExtensionPublishPlugin.` + ); + } + if (!signaturePathParameter.value) { + throw new Error( + `The parameter "${SIGNATURE_PATH_PARAMETER_NAME}" is required for the VSCodeExtensionPublishPlugin.` + ); + } + + const vsixPath: string = vsixPathParameter.value; + const manifestPath: string = manifestPathParameter.value; + const signaturePath: string = signaturePathParameter.value; + + heftTaskSession.hooks.run.tapPromise(PLUGIN_NAME, async (runOptions: IHeftTaskRunHookOptions) => { + const { buildFolderPath } = heftConfiguration; + const { + logger: { terminal } + } = heftTaskSession; + + terminal.writeLine(`Using VSCE script: ${vsceScriptPath}`); + terminal.writeLine(`Publishing VSIX ${vsixPath}`); + + const publishResult: IWaitForExitResult = await execuateAndWaitAsync( + terminal, + 'node', + [ + vsceScriptPath, + 'publish', + '--no-dependencies', + '--azure-credential', + '--packagePath', + path.resolve(vsixPath), + '--manifestPath', + path.resolve(manifestPath), + '--signaturePath', + path.resolve(signaturePath) + ], + { + currentWorkingDirectory: path.resolve(buildFolderPath) + } + ); + if (publishResult.exitCode !== 0) { + throw new Error(`VSIX publishing failed with exit code ${publishResult.exitCode}`); + } + terminal.writeLine('VSIX successfully published.'); + }); + } +} diff --git a/heft-plugins/heft-vscode-extension-plugin/src/util.ts b/heft-plugins/heft-vscode-extension-plugin/src/util.ts new file mode 100644 index 00000000000..75018ff4beb --- /dev/null +++ b/heft-plugins/heft-vscode-extension-plugin/src/util.ts @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { Executable, IExecutableSpawnOptions, IWaitForExitResult } from '@rushstack/node-core-library'; +import type { ChildProcess } from 'node:child_process'; +import * as path from 'node:path'; +import { TerminalStreamWritable, TerminalProviderSeverity, ITerminal } from '@rushstack/terminal'; + +export async function execuateAndWaitAsync( + terminal: ITerminal, + command: string, + args: string[], + options: Omit = {} +): Promise> { + const childProcess: ChildProcess = Executable.spawn(command, args, { + ...options, + stdio: [ + 'ignore', // stdin + 'pipe', // stdout + 'pipe' // stderr + ] + }); + childProcess.stdout?.pipe( + new TerminalStreamWritable({ + terminal, + severity: TerminalProviderSeverity.log + }) + ); + childProcess.stderr?.pipe( + new TerminalStreamWritable({ + terminal, + severity: TerminalProviderSeverity.error + }) + ); + const result: IWaitForExitResult = await Executable.waitForExitAsync(childProcess, { + encoding: 'utf8' + }); + return result; +} + +const vsceBasePackagePath: string = require.resolve('@vscode/vsce/package.json'); +export const vsceScriptPath: string = path.resolve(vsceBasePackagePath, '../vsce'); diff --git a/rigs/heft-vscode-extension-rig/profiles/default/config/heft.json b/rigs/heft-vscode-extension-rig/profiles/default/config/heft.json index 29002b40b08..23c63c45c5c 100644 --- a/rigs/heft-vscode-extension-rig/profiles/default/config/heft.json +++ b/rigs/heft-vscode-extension-rig/profiles/default/config/heft.json @@ -1,8 +1,6 @@ { "$schema": "https://developer.microsoft.com/json-schemas/heft/v0/heft.schema.json", - "extends": "@rushstack/heft-node-rig/profiles/default/config/heft.json", - /** * The list of Heft phases that can be run by Heft. */ @@ -33,7 +31,6 @@ } } }, - "package-vsix": { "taskDependencies": ["typescript", "webpack", "copy-assets"], "taskPlugin": { @@ -41,7 +38,23 @@ "pluginName": "vscode-extension-package-plugin", "options": { "unpackedFolderPath": "dist/vsix/unpacked", - "vsixPath": "dist/vsix/packaged.vsix" + "vsixPath": "dist/vsix/packaged.vsix", + "manifestPath": "dist/vsix/extension-manifest.json" + } + } + } + } + }, + "publish-vsix": { + "phaseDescription": "Publish the VSIX package.", + "tasksByName": { + "publish-vsix": { + "taskPlugin": { + "pluginPackage": "@rushstack/heft-vscode-extension-plugin", + "pluginName": "vscode-extension-publish-plugin", + "options": { + "vsixPath": "dist/vsix/packaged.vsix", + "manifestPath": "dist/vsix/extension-manifest.json" } } } diff --git a/vscode-extensions/debug-certificate-manager-vscode-extension/package.json b/vscode-extensions/debug-certificate-manager-vscode-extension/package.json index d5b51baac28..47f89a08701 100644 --- a/vscode-extensions/debug-certificate-manager-vscode-extension/package.json +++ b/vscode-extensions/debug-certificate-manager-vscode-extension/package.json @@ -105,9 +105,7 @@ } } }, - "enabledApiProposals": [ - "resolvers" - ], + "enabledApiProposals": [], "activationEvents": [ "workspaceContains:.vscode/debug-certificate-manager.json" ], From d7319e7c26d363d1e37b997917a876b47debf09f Mon Sep 17 00:00:00 2001 From: Bharat Middha <5100938+bmiddha@users.noreply.github.com> Date: Thu, 24 Jul 2025 23:09:53 -0700 Subject: [PATCH 8/9] rush change --- .../bmiddha-tls-sync-v2_2025-07-25-06-09.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 common/changes/@rushstack/heft-vscode-extension-rig/bmiddha-tls-sync-v2_2025-07-25-06-09.json diff --git a/common/changes/@rushstack/heft-vscode-extension-rig/bmiddha-tls-sync-v2_2025-07-25-06-09.json b/common/changes/@rushstack/heft-vscode-extension-rig/bmiddha-tls-sync-v2_2025-07-25-06-09.json new file mode 100644 index 00000000000..f6bd7e992ce --- /dev/null +++ b/common/changes/@rushstack/heft-vscode-extension-rig/bmiddha-tls-sync-v2_2025-07-25-06-09.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@rushstack/heft-vscode-extension-rig", + "comment": "Add publish vsix plugin configuration.", + "type": "patch" + } + ], + "packageName": "@rushstack/heft-vscode-extension-rig" +} \ No newline at end of file From 51f0fad07b7cd30ed38a549d578bea36d28f1925 Mon Sep 17 00:00:00 2001 From: Bharat Middha <5100938+bmiddha@users.noreply.github.com> Date: Fri, 25 Jul 2025 14:28:08 -0700 Subject: [PATCH 9/9] fix typo --- .../src/VSCodeExtensionPackagePlugin.ts | 6 +++--- .../src/VSCodeExtensionPublishPlugin.ts | 4 ++-- heft-plugins/heft-vscode-extension-plugin/src/util.ts | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/heft-plugins/heft-vscode-extension-plugin/src/VSCodeExtensionPackagePlugin.ts b/heft-plugins/heft-vscode-extension-plugin/src/VSCodeExtensionPackagePlugin.ts index 852886b8620..5bb8e63c061 100644 --- a/heft-plugins/heft-vscode-extension-plugin/src/VSCodeExtensionPackagePlugin.ts +++ b/heft-plugins/heft-vscode-extension-plugin/src/VSCodeExtensionPackagePlugin.ts @@ -9,7 +9,7 @@ import type { } from '@rushstack/heft'; import type { IWaitForExitResult } from '@rushstack/node-core-library'; import * as path from 'node:path'; -import { execuateAndWaitAsync, vsceScriptPath } from './util'; +import { executeAndWaitAsync, vsceScriptPath } from './util'; interface IVSCodeExtensionPackagePluginOptions { /** @@ -50,7 +50,7 @@ export default class VSCodeExtensionPackagePlugin terminal.writeLine(`Using VSCE script: ${vsceScriptPath}`); terminal.writeLine(`Packaging VSIX from ${unpackedFolderPath} to ${vsixPath}`); - const packageResult: IWaitForExitResult = await execuateAndWaitAsync( + const packageResult: IWaitForExitResult = await executeAndWaitAsync( terminal, 'node', [vsceScriptPath, 'package', '--no-dependencies', '--out', path.resolve(vsixPath)], @@ -64,7 +64,7 @@ export default class VSCodeExtensionPackagePlugin terminal.writeLine('VSIX successfully packaged.'); terminal.writeLine(`Generating manifest at ${manifestPath}`); - const manifestResult: IWaitForExitResult = await execuateAndWaitAsync( + const manifestResult: IWaitForExitResult = await executeAndWaitAsync( terminal, 'node', [ diff --git a/heft-plugins/heft-vscode-extension-plugin/src/VSCodeExtensionPublishPlugin.ts b/heft-plugins/heft-vscode-extension-plugin/src/VSCodeExtensionPublishPlugin.ts index 47ca14777fa..f2393e58745 100644 --- a/heft-plugins/heft-vscode-extension-plugin/src/VSCodeExtensionPublishPlugin.ts +++ b/heft-plugins/heft-vscode-extension-plugin/src/VSCodeExtensionPublishPlugin.ts @@ -10,7 +10,7 @@ import type { } from '@rushstack/heft'; import type { IWaitForExitResult } from '@rushstack/node-core-library'; import * as path from 'node:path'; -import { execuateAndWaitAsync, vsceScriptPath } from './util'; +import { executeAndWaitAsync, vsceScriptPath } from './util'; interface IVSCodeExtensionPublishPluginOptions {} @@ -66,7 +66,7 @@ export default class VSCodeExtensionPublishPlugin terminal.writeLine(`Using VSCE script: ${vsceScriptPath}`); terminal.writeLine(`Publishing VSIX ${vsixPath}`); - const publishResult: IWaitForExitResult = await execuateAndWaitAsync( + const publishResult: IWaitForExitResult = await executeAndWaitAsync( terminal, 'node', [ diff --git a/heft-plugins/heft-vscode-extension-plugin/src/util.ts b/heft-plugins/heft-vscode-extension-plugin/src/util.ts index 75018ff4beb..0772a2698a0 100644 --- a/heft-plugins/heft-vscode-extension-plugin/src/util.ts +++ b/heft-plugins/heft-vscode-extension-plugin/src/util.ts @@ -6,7 +6,7 @@ import type { ChildProcess } from 'node:child_process'; import * as path from 'node:path'; import { TerminalStreamWritable, TerminalProviderSeverity, ITerminal } from '@rushstack/terminal'; -export async function execuateAndWaitAsync( +export async function executeAndWaitAsync( terminal: ITerminal, command: string, args: string[],