From 7f21fb616aadcdfdcf42751b4a101c71de13c014 Mon Sep 17 00:00:00 2001
From: Matt Seddon <37993418+mattseddon@users.noreply.github.com>
Date: Fri, 19 May 2023 14:43:59 +1000
Subject: [PATCH 1/4] Fix extension initialization on Windows (downgrade from
esm only packages) (#3937)
---
extension/package.json | 5 +-
extension/scripts/coverIntegrationTests.js | 12 ++---
extension/scripts/virtualenv-install.js | 13 -----
extension/src/extension.ts | 4 +-
extension/src/process/execution.test.ts | 32 ++++++++++++
extension/src/process/execution.ts | 3 +-
extension/src/python/index.test.ts | 1 -
extension/src/python/index.ts | 2 -
extension/src/test/suite/cli/child.ts | 6 +--
.../src/test/suite/process/execution.test.ts | 36 -------------
extension/src/util/esm.ts | 29 -----------
package.json | 2 +-
renovate.json | 2 +-
scripts/virtualenv-install.ts | 14 ++++++
yarn.lock | 50 +++++++++----------
15 files changed, 85 insertions(+), 126 deletions(-)
delete mode 100644 extension/scripts/virtualenv-install.js
create mode 100644 extension/src/process/execution.test.ts
delete mode 100644 extension/src/test/suite/process/execution.test.ts
delete mode 100644 extension/src/util/esm.ts
create mode 100644 scripts/virtualenv-install.ts
diff --git a/extension/package.json b/extension/package.json
index 5c30da8e11..9de8252b04 100644
--- a/extension/package.json
+++ b/extension/package.json
@@ -1628,7 +1628,6 @@
"test-vscode": "node ./dist/test/runTest.js",
"test-e2e": "wdio run ./src/test/e2e/wdio.conf.ts",
"test": "jest --collect-coverage",
- "setup-venv": "node ./scripts/virtualenv-install.js",
"cover-vscode-run": "node ./scripts/coverIntegrationTests.js",
"vscode:prepublish": ""
},
@@ -1636,7 +1635,7 @@
"@hediet/std": "0.6.0",
"@vscode/extension-telemetry": "0.7.7",
"appdirs": "1.1.0",
- "execa": "7.1.1",
+ "execa": "5.1.1",
"fs-extra": "11.1.1",
"js-yaml": "4.1.0",
"json5": "2.2.3",
@@ -1646,7 +1645,7 @@
"lodash.isequal": "4.5.0",
"lodash.merge": "4.6.2",
"lodash.omit": "4.5.0",
- "process-exists": "5.0.0",
+ "process-exists": "4.1.0",
"tree-kill": "1.2.2",
"uuid": "9.0.0",
"vega-util": "1.17.2",
diff --git a/extension/scripts/coverIntegrationTests.js b/extension/scripts/coverIntegrationTests.js
index 7703fb5a33..d5fe8ed840 100644
--- a/extension/scripts/coverIntegrationTests.js
+++ b/extension/scripts/coverIntegrationTests.js
@@ -1,10 +1,6 @@
const { resolve, join } = require('path')
const { writeFileSync } = require('fs-extra')
-
-const getExeca = async () => {
- const { execa } = await import('execa')
- return execa
-}
+const execa = require('execa')
let activationEvents = []
let failed
@@ -22,7 +18,7 @@ activationEvents = packageJson.activationEvents
packageJson.activationEvents = ['onStartupFinished']
writeFileSync(packageJsonPath, JSON.stringify(packageJson))
-getExeca().then(async execa => {
+const runCover = async () => {
const tests = execa('node', [join(cwd, 'dist', 'test', 'runTest.js')], {
cwd
})
@@ -43,4 +39,6 @@ getExeca().then(async execa => {
if (failed) {
process.exit(1)
}
-})
+}
+
+runCover()
diff --git a/extension/scripts/virtualenv-install.js b/extension/scripts/virtualenv-install.js
deleted file mode 100644
index a3399329e0..0000000000
--- a/extension/scripts/virtualenv-install.js
+++ /dev/null
@@ -1,13 +0,0 @@
-const { join, resolve } = require('path')
-require('../dist/vscode/mockModule')
-
-const importModuleAfterMockingVsCode = async () => {
- const { setupTestVenv } = require('../dist/python')
- return setupTestVenv
-}
-
-importModuleAfterMockingVsCode().then(setupTestVenv => {
- const cwd = resolve(__dirname, '..', '..', 'demo')
-
- setupTestVenv(cwd, '.env', '-r', join('.', 'requirements.txt'))
-})
diff --git a/extension/src/extension.ts b/extension/src/extension.ts
index 7229f6320d..66bc19f6c8 100644
--- a/extension/src/extension.ts
+++ b/extension/src/extension.ts
@@ -50,7 +50,6 @@ import { DvcViewer } from './cli/dvc/viewer'
import { registerSetupCommands } from './setup/register'
import { Status } from './status'
import { registerPersistenceCommands } from './persistence/register'
-import { esmPackagesImported } from './util/esm'
class Extension extends Disposable {
protected readonly internalCommands: InternalCommands
@@ -304,8 +303,7 @@ class Extension extends Disposable {
let extension: undefined | Extension
-export async function activate(context: ExtensionContext): Promise {
- await esmPackagesImported
+export function activate(context: ExtensionContext): void {
extension = new Extension(context)
context.subscriptions.push(extension)
}
diff --git a/extension/src/process/execution.test.ts b/extension/src/process/execution.test.ts
new file mode 100644
index 0000000000..f8fb1e471d
--- /dev/null
+++ b/extension/src/process/execution.test.ts
@@ -0,0 +1,32 @@
+import process from 'process'
+import { executeProcess, processExists } from './execution'
+
+describe('executeProcess', () => {
+ it('should be able to run a process', async () => {
+ const output = await executeProcess({
+ args: ['some', 'text'],
+ cwd: __dirname,
+ executable: 'echo'
+ })
+ expect(output).toMatch(/some.*text/)
+ })
+
+ it('should return the stderr if the process throws with stderr', async () => {
+ await expect(
+ executeProcess({
+ args: ['me', 'outside'],
+ cwd: __dirname,
+ executable: 'find'
+ })
+ ).rejects.toBeTruthy()
+ })
+})
+
+describe('processExists', () => {
+ it('should return true if the process exists', async () => {
+ expect(await processExists(process.pid)).toBe(true)
+ })
+ it('should return false if it does not', async () => {
+ expect(await processExists(-123.321)).toBe(false)
+ })
+})
diff --git a/extension/src/process/execution.ts b/extension/src/process/execution.ts
index ce6b4a7fa7..54c3d603da 100644
--- a/extension/src/process/execution.ts
+++ b/extension/src/process/execution.ts
@@ -2,9 +2,10 @@ import { ChildProcess } from 'child_process'
import { Readable } from 'stream'
import { Event, EventEmitter } from 'vscode'
import { Disposable } from '@hediet/std/disposable'
+import execa from 'execa'
+import doesProcessExist from 'process-exists'
import kill from 'tree-kill'
import { getProcessPlatform } from '../env'
-import { doesProcessExist, execa } from '../util/esm'
interface RunningProcess extends ChildProcess {
all?: Readable
diff --git a/extension/src/python/index.test.ts b/extension/src/python/index.test.ts
index 03e37df88b..c2563cb40a 100644
--- a/extension/src/python/index.test.ts
+++ b/extension/src/python/index.test.ts
@@ -5,7 +5,6 @@ import { getProcessPlatform } from '../env'
jest.mock('../env')
jest.mock('../process/execution')
-jest.mock('../util/esm')
const mockedGetProcessPlatform = jest.mocked(getProcessPlatform)
const mockedCreateProcess = jest.mocked(createProcess)
diff --git a/extension/src/python/index.ts b/extension/src/python/index.ts
index 8e09dbfe89..86ecfa3b83 100644
--- a/extension/src/python/index.ts
+++ b/extension/src/python/index.ts
@@ -4,7 +4,6 @@ import { getProcessPlatform } from '../env'
import { exists } from '../fileSystem'
import { Logger } from '../common/logger'
import { createProcess, executeProcess, Process } from '../process/execution'
-import { esmPackagesImported } from '../util/esm'
const sendOutput = (process: Process) => {
process.all?.on('data', chunk =>
@@ -35,7 +34,6 @@ export const setupTestVenv = async (
envDir: string,
...installArgs: string[]
) => {
- await esmPackagesImported
if (!exists(join(cwd, envDir))) {
const initVenv = createProcess({
args: ['-m', 'venv', envDir],
diff --git a/extension/src/test/suite/cli/child.ts b/extension/src/test/suite/cli/child.ts
index 6867cb91b3..757d159ae8 100644
--- a/extension/src/test/suite/cli/child.ts
+++ b/extension/src/test/suite/cli/child.ts
@@ -4,15 +4,13 @@ import { delay } from '../../../util/time'
require('../../../vscode/mockModule')
-const importModuleAfterMockingVsCode = async () => {
+const importModuleAfterMockingVsCode = () => {
const { Cli } = require('../../../cli')
- const { esmPackagesImported } = require('../../../util/esm')
- await esmPackagesImported
return { Cli }
}
const main = async () => {
- const { Cli } = await importModuleAfterMockingVsCode()
+ const { Cli } = importModuleAfterMockingVsCode()
const cli = new Cli()
diff --git a/extension/src/test/suite/process/execution.test.ts b/extension/src/test/suite/process/execution.test.ts
deleted file mode 100644
index ae9a00e3e2..0000000000
--- a/extension/src/test/suite/process/execution.test.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import process from 'process'
-import { describe, it, suite } from 'mocha'
-import { expect } from 'chai'
-import { executeProcess, processExists } from '../../../process/execution'
-
-suite('Process Manager Test Suite', () => {
- describe('executeProcess', () => {
- it('should be able to run a process', async () => {
- const output = await executeProcess({
- args: ['some', 'text'],
- cwd: __dirname,
- executable: 'echo'
- })
- expect(output).to.match(/some.*text/)
- })
-
- it('should return the stderr if the process throws with stderr', async () => {
- await expect(
- executeProcess({
- args: ['me', 'outside'],
- cwd: __dirname,
- executable: 'find'
- })
- ).to.be.eventually.rejected
- })
- })
-
- describe('processExists', () => {
- it('should return true if the process exists', async () => {
- expect(await processExists(process.pid)).to.be.true
- })
- it('should return false if it does not', async () => {
- expect(await processExists(-123.321)).to.be.false
- })
- })
-})
diff --git a/extension/src/util/esm.ts b/extension/src/util/esm.ts
deleted file mode 100644
index 012ad90a5f..0000000000
--- a/extension/src/util/esm.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { Deferred } from '@hediet/std/synchronization'
-
-const deferred = new Deferred()
-export const esmPackagesImported = deferred.promise
-
-// eslint-disable-next-line @typescript-eslint/ban-ts-comment
-// @ts-ignore
-type EsmExeca = typeof import('execa').execa
-// eslint-disable-next-line @typescript-eslint/ban-ts-comment
-// @ts-ignore
-type EsmProcessExists = typeof import('process-exists').processExists
-
-const shouldImportEsm = !process.env.JEST_WORKER_ID
-
-let execa: EsmExeca
-let doesProcessExist: EsmProcessExists
-const importEsmPackages = async () => {
- const [{ execa: esmExeca }, { processExists: esmProcessExists }] =
- await Promise.all([import('execa'), import('process-exists')])
- execa = esmExeca
- doesProcessExist = esmProcessExists
- deferred.resolve()
-}
-
-if (shouldImportEsm) {
- void importEsmPackages()
-}
-
-export { execa, doesProcessExist }
diff --git a/package.json b/package.json
index b5badc564c..39c34a6d28 100644
--- a/package.json
+++ b/package.json
@@ -28,7 +28,7 @@
"postinstall": "husky install && git submodule init && git submodule update && yarn svgr",
"storybook": "yarn workspace dvc-vscode-webview storybook",
"build-storybook": "yarn turbo run build-storybook --filter=dvc-vscode-webview",
- "setup:venv": "yarn turbo run lint:build && yarn workspace dvc run setup-venv",
+ "setup:venv": "ts-node ./scripts/virtualenv-install.ts",
"scheduled:cli:test": "ts-node ./extension/src/test/cli/index.ts",
"create-svgs": "ts-node ./scripts/create-svgs.ts",
"svgr": "yarn workspace dvc-vscode-webview svgr"
diff --git a/renovate.json b/renovate.json
index d70aa3c0f5..4e6cb10b27 100644
--- a/renovate.json
+++ b/renovate.json
@@ -1,5 +1,5 @@
{
- "ignoreDeps": ["@types/node", "@types/vscode"],
+ "ignoreDeps": ["@types/node", "@types/vscode", "execa", "process-exists"],
"extends": ["config:base"],
"packageRules": [
{
diff --git a/scripts/virtualenv-install.ts b/scripts/virtualenv-install.ts
new file mode 100644
index 0000000000..e756cd683f
--- /dev/null
+++ b/scripts/virtualenv-install.ts
@@ -0,0 +1,14 @@
+import { join, resolve } from 'path'
+require('dvc/src/vscode/mockModule')
+
+const importModuleAfterMockingVsCode = () => {
+ const { setupTestVenv } = require('dvc/src/python')
+
+ return setupTestVenv
+}
+
+const setupTestVenv = importModuleAfterMockingVsCode()
+
+const cwd = resolve(__dirname, '..', 'demo')
+
+setupTestVenv(cwd, '.env', '-r', join('.', 'requirements.txt'))
diff --git a/yarn.lock b/yarn.lock
index 8014f97edf..ebee0dc4b8 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -10667,22 +10667,7 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
md5.js "^1.3.4"
safe-buffer "^5.1.1"
-execa@7.1.1, execa@^7.1.1:
- version "7.1.1"
- resolved "https://registry.yarnpkg.com/execa/-/execa-7.1.1.tgz#3eb3c83d239488e7b409d48e8813b76bb55c9c43"
- integrity sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==
- dependencies:
- cross-spawn "^7.0.3"
- get-stream "^6.0.1"
- human-signals "^4.3.0"
- is-stream "^3.0.0"
- merge-stream "^2.0.0"
- npm-run-path "^5.1.0"
- onetime "^6.0.0"
- signal-exit "^3.0.7"
- strip-final-newline "^3.0.0"
-
-execa@^5.0.0, execa@^5.1.1:
+execa@5.1.1, execa@^5.0.0, execa@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"
integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==
@@ -10712,6 +10697,21 @@ execa@^7.0.0:
signal-exit "^3.0.7"
strip-final-newline "^3.0.0"
+execa@^7.1.1:
+ version "7.1.1"
+ resolved "https://registry.yarnpkg.com/execa/-/execa-7.1.1.tgz#3eb3c83d239488e7b409d48e8813b76bb55c9c43"
+ integrity sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==
+ dependencies:
+ cross-spawn "^7.0.3"
+ get-stream "^6.0.1"
+ human-signals "^4.3.0"
+ is-stream "^3.0.0"
+ merge-stream "^2.0.0"
+ npm-run-path "^5.1.0"
+ onetime "^6.0.0"
+ signal-exit "^3.0.7"
+ strip-final-newline "^3.0.0"
+
exenv-es6@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/exenv-es6/-/exenv-es6-1.1.1.tgz#80b7a8c5af24d53331f755bac07e84abb1f6de67"
@@ -16550,12 +16550,12 @@ prismjs@^1.27.0, prismjs@~1.27.0:
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.27.0.tgz#bb6ee3138a0b438a3653dd4d6ce0cc6510a45057"
integrity sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==
-process-exists@5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/process-exists/-/process-exists-5.0.0.tgz#0b6dcd3d19e85e1f72c633f56d38e498196e2855"
- integrity sha512-6QPRh5fyHD8MaXr4GYML8K/YY0Sq5dKHGIOrAKS3cYpHQdmygFCcijIu1dVoNKAZ0TWAMoeh8KDK9dF8auBkJA==
+process-exists@4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/process-exists/-/process-exists-4.1.0.tgz#4132c516324c1da72d65896851cdbd8bbdf5b9d8"
+ integrity sha512-BBJoiorUKoP2AuM5q/yKwIfT1YWRHsaxjW+Ayu9erLhqKOfnXzzVVML0XTYoQZuI1YvcWKmc1dh06DEy4+KzfA==
dependencies:
- ps-list "^8.0.0"
+ ps-list "^6.3.0"
process-nextick-args@~2.0.0:
version "2.0.1"
@@ -16652,10 +16652,10 @@ prr@~1.0.1:
resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY=
-ps-list@^8.0.0:
- version "8.1.1"
- resolved "https://registry.yarnpkg.com/ps-list/-/ps-list-8.1.1.tgz#9ff1952b26a9a07fcc05270407e60544237ae581"
- integrity sha512-OPS9kEJYVmiO48u/B9qneqhkMvgCxT+Tm28VCEJpheTpl8cJ0ffZRRNgS5mrQRTrX5yRTpaJ+hRDeefXYmmorQ==
+ps-list@^6.3.0:
+ version "6.3.0"
+ resolved "https://registry.yarnpkg.com/ps-list/-/ps-list-6.3.0.tgz#a2b775c2db7d547a28fbaa3a05e4c281771259be"
+ integrity sha512-qau0czUSB0fzSlBOQt0bo+I2v6R+xiQdj78e1BR/Qjfl5OHWJ/urXi8+ilw1eHe+5hSeDI1wrwVTgDp2wst4oA==
pseudomap@^1.0.2:
version "1.0.2"
From 2558f1fe1f76cc8ad8efb38f9edea754955855a5 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 19 May 2023 05:00:43 +0000
Subject: [PATCH 2/4] Update version and CHANGELOG for release (#3938)
Co-authored-by: Olivaw[bot]
---
CHANGELOG.md | 11 +++++++++++
extension/package.json | 2 +-
2 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 56dc5815a4..fa4b5c480d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,17 @@
All notable changes to this project will be documented in this file.
+## [0.8.17] - 2023-05-19
+
+### 🐛 Bug Fixes
+
+- Fix extension initialization on Windows (esm imports broken) [#3937](https://github.com/iterative/vscode-dvc/pull/3937) by [@mattseddon](https://github.com/mattseddon)
+
+### 🔨 Maintenance
+
+- Fix test console errors (add tbody) [#3927](https://github.com/iterative/vscode-dvc/pull/3927) by [@mattseddon](https://github.com/mattseddon)
+- Increase timeout of flaky test [#3923](https://github.com/iterative/vscode-dvc/pull/3923) by [@mattseddon](https://github.com/mattseddon)
+
## [0.8.16] - 2023-05-18
### 🚀 New Features and Enhancements
diff --git a/extension/package.json b/extension/package.json
index 9de8252b04..9b611999b5 100644
--- a/extension/package.json
+++ b/extension/package.json
@@ -9,7 +9,7 @@
"extensionDependencies": [
"vscode.git"
],
- "version": "0.8.16",
+ "version": "0.8.17",
"license": "Apache-2.0",
"readme": "./README.md",
"repository": {
From ddce5b1544ac62e650878cb514ecfbd176068e74 Mon Sep 17 00:00:00 2001
From: Matt Seddon <37993418+mattseddon@users.noreply.github.com>
Date: Fri, 19 May 2023 16:05:43 +1000
Subject: [PATCH 3/4] Add command to add remote (#3929)
* add add remote command
* add tests
* refactor
* apply review feedback
---
.eslintrc.js | 4 +-
demo | 2 +-
extension/package.json | 10 ++
extension/src/cli/dvc/constants.ts | 5 +-
extension/src/cli/dvc/executor.ts | 7 +-
extension/src/commands/external.ts | 2 +
extension/src/extension.ts | 2 +-
extension/src/setup/commands/index.ts | 86 ++++++++++++++++
.../src/setup/{ => commands}/register.ts | 21 ++--
extension/src/setup/webview/messages.ts | 2 +
extension/src/telemetry/constants.ts | 2 +
extension/src/test/suite/setup/index.test.ts | 98 +++++++++++++++++++
extension/src/test/suite/setup/util.ts | 3 +-
extension/src/vscode/title.ts | 5 +-
extension/src/webview/contract.ts | 2 +
extension/src/workspace/index.ts | 14 +--
extension/src/workspace/util.ts | 14 +++
scripts/virtualenv-install.ts | 1 +
webview/src/setup/components/App.test.tsx | 28 +++++-
webview/src/setup/components/messages.ts | 3 +
.../src/setup/components/remote/Connect.tsx | 3 +
webview/src/stories/Setup.stories.tsx | 13 +++
22 files changed, 297 insertions(+), 30 deletions(-)
create mode 100644 extension/src/setup/commands/index.ts
rename extension/src/setup/{ => commands}/register.ts (82%)
create mode 100644 extension/src/workspace/util.ts
diff --git a/.eslintrc.js b/.eslintrc.js
index 1a67e932df..ca685b45e1 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -3,6 +3,7 @@ module.exports = {
env: {
'jest/globals': true
},
+
extends: [
'prettier-standard/prettier-file',
'plugin:@typescript-eslint/eslint-recommended',
@@ -22,7 +23,8 @@ module.exports = {
'**/*.js',
'*.d.ts',
'tsconfig.json',
- 'webpack.config.ts'
+ 'webpack.config.ts',
+ 'scripts/virtualenv-install.ts'
],
overrides: [
{
diff --git a/demo b/demo
index a20953baeb..5f06c3734d 160000
--- a/demo
+++ b/demo
@@ -1 +1 @@
-Subproject commit a20953baeb985bdfa41490d220da32942345864f
+Subproject commit 5f06c3734d76cb7ca894895e89d6d06dd878f8c4
diff --git a/extension/package.json b/extension/package.json
index 9b611999b5..187fd9afa3 100644
--- a/extension/package.json
+++ b/extension/package.json
@@ -100,6 +100,12 @@
"category": "DVC",
"icon": "$(add)"
},
+ {
+ "title": "Add Remote",
+ "command": "dvc.addRemote",
+ "category": "DVC",
+ "icon": "$(add)"
+ },
{
"title": "Filter Experiments Table to Starred",
"command": "dvc.addStarredExperimentsTableFilter",
@@ -654,6 +660,10 @@
"command": "dvc.addExperimentsTableSort",
"when": "dvc.commands.available && dvc.project.available"
},
+ {
+ "command": "dvc.addRemote",
+ "when": "dvc.commands.available && dvc.project.available && !dvc.experiment.running.workspace"
+ },
{
"command": "dvc.addStarredExperimentsTableFilter",
"when": "dvc.commands.available && dvc.project.available"
diff --git a/extension/src/cli/dvc/constants.ts b/extension/src/cli/dvc/constants.ts
index 2759f0ff5a..f2c03f7716 100644
--- a/extension/src/cli/dvc/constants.ts
+++ b/extension/src/cli/dvc/constants.ts
@@ -38,6 +38,7 @@ export enum Command {
}
export enum SubCommand {
+ ADD = 'add',
DIFF = 'diff',
LIST = 'list',
STATUS = 'status',
@@ -47,13 +48,15 @@ export enum SubCommand {
export enum Flag {
ALL_COMMITS = '-A',
FOLLOW = '-f',
+ DEFAULT = '-d',
FORCE = '-f',
GLOBAL = '--global',
GRANULAR = '--granular',
- LOCAL = '--local',
JOBS = '-j',
JSON = '--json',
KILL = '--kill',
+ LOCAL = '--local',
+ PROJECT = '--project',
NUM_COMMIT = '-n',
OUTPUT_PATH = '-o',
SUBDIRECTORY = '--subdir',
diff --git a/extension/src/cli/dvc/executor.ts b/extension/src/cli/dvc/executor.ts
index 8136837ab5..c0a9a2ef50 100644
--- a/extension/src/cli/dvc/executor.ts
+++ b/extension/src/cli/dvc/executor.ts
@@ -7,8 +7,7 @@ import {
ExperimentSubCommand,
Flag,
GcPreserveFlag,
- QueueSubCommand,
- SubCommand
+ QueueSubCommand
} from './constants'
import { addStudioAccessToken } from './options'
import { CliResult, CliStarted, typeCheckCommands } from '..'
@@ -198,8 +197,8 @@ export class DvcExecutor extends DvcCli {
return this.blockAndExecuteProcess(cwd, Command.REMOVE, ...args)
}
- public remote(cwd: string, arg: typeof SubCommand.LIST) {
- return this.executeDvcProcess(cwd, Command.REMOTE, arg)
+ public remote(cwd: string, ...args: Args) {
+ return this.executeDvcProcess(cwd, Command.REMOTE, ...args)
}
private executeExperimentProcess(cwd: string, ...args: Args) {
diff --git a/extension/src/commands/external.ts b/extension/src/commands/external.ts
index 151f106098..8e9ea99f03 100644
--- a/extension/src/commands/external.ts
+++ b/extension/src/commands/external.ts
@@ -41,6 +41,8 @@ export enum RegisteredCliCommands {
REMOVE_TARGET = 'dvc.removeTarget',
RENAME_TARGET = 'dvc.renameTarget',
+ REMOTE_ADD = 'dvc.addRemote',
+
GIT_STAGE_ALL = 'dvc.gitStageAll',
GIT_UNSTAGE_ALL = 'dvc.gitUnstageAll'
}
diff --git a/extension/src/extension.ts b/extension/src/extension.ts
index 66bc19f6c8..4a55541d39 100644
--- a/extension/src/extension.ts
+++ b/extension/src/extension.ts
@@ -47,7 +47,7 @@ import { Flag } from './cli/dvc/constants'
import { LanguageClient } from './languageClient'
import { collectRunningExperimentPids } from './experiments/processExecution/collect'
import { DvcViewer } from './cli/dvc/viewer'
-import { registerSetupCommands } from './setup/register'
+import { registerSetupCommands } from './setup/commands/register'
import { Status } from './status'
import { registerPersistenceCommands } from './persistence/register'
diff --git a/extension/src/setup/commands/index.ts b/extension/src/setup/commands/index.ts
new file mode 100644
index 0000000000..b4245c89d3
--- /dev/null
+++ b/extension/src/setup/commands/index.ts
@@ -0,0 +1,86 @@
+import { Setup } from '..'
+import { Flag, SubCommand } from '../../cli/dvc/constants'
+import { AvailableCommands, InternalCommands } from '../../commands/internal'
+import { definedAndNonEmpty } from '../../util/array'
+import { getInput } from '../../vscode/inputBox'
+import { quickPickYesOrNo } from '../../vscode/quickPick'
+import { Title } from '../../vscode/title'
+import { Toast } from '../../vscode/toast'
+import { getOnlyOrPickProject } from '../../workspace/util'
+
+const noExistingOrUserConfirms = async (
+ internalCommands: InternalCommands,
+ dvcRoot: string
+): Promise => {
+ const remoteList = await internalCommands.executeCommand(
+ AvailableCommands.REMOTE,
+ dvcRoot,
+ SubCommand.LIST
+ )
+
+ if (!remoteList) {
+ return true
+ }
+
+ return await quickPickYesOrNo(
+ 'make this new remote the default',
+ 'keep the current default',
+ {
+ placeHolder: 'Would you like to set this new remote as the default?',
+ title: Title.SET_REMOTE_AS_DEFAULT
+ }
+ )
+}
+
+const addRemoteToProject = async (
+ internalCommands: InternalCommands,
+ dvcRoot: string
+): Promise => {
+ const name = await getInput(Title.ENTER_REMOTE_NAME)
+ if (!name) {
+ return
+ }
+
+ const url = await getInput(Title.ENTER_REMOTE_URL)
+ if (!url) {
+ return
+ }
+
+ const args = [Flag.PROJECT, name, url]
+
+ const shouldSetAsDefault = await noExistingOrUserConfirms(
+ internalCommands,
+ dvcRoot
+ )
+ if (shouldSetAsDefault === undefined) {
+ return
+ }
+
+ if (shouldSetAsDefault) {
+ args.unshift(Flag.DEFAULT)
+ }
+
+ return await Toast.showOutput(
+ internalCommands.executeCommand(
+ AvailableCommands.REMOTE,
+ dvcRoot,
+ SubCommand.ADD,
+ ...args
+ )
+ )
+}
+
+export const getAddRemoteCommand =
+ (setup: Setup, internalCommands: InternalCommands) =>
+ async (): Promise => {
+ const dvcRoots = setup.getRoots()
+ if (!definedAndNonEmpty(dvcRoots)) {
+ return Toast.showError('Cannot add a remote without a DVC project')
+ }
+ const dvcRoot = await getOnlyOrPickProject(dvcRoots)
+
+ if (!dvcRoot) {
+ return
+ }
+ return addRemoteToProject(internalCommands, dvcRoot)
+ }
diff --git a/extension/src/setup/register.ts b/extension/src/setup/commands/register.ts
similarity index 82%
rename from extension/src/setup/register.ts
rename to extension/src/setup/commands/register.ts
index f0e8468abc..9efd2c852e 100644
--- a/extension/src/setup/register.ts
+++ b/extension/src/setup/commands/register.ts
@@ -1,10 +1,14 @@
import { commands } from 'vscode'
-import { Setup } from '.'
-import { run } from './runner'
-import { SetupSection } from './webview/contract'
-import { AvailableCommands, InternalCommands } from '../commands/internal'
-import { RegisteredCliCommands, RegisteredCommands } from '../commands/external'
-import { getFirstWorkspaceFolder } from '../vscode/workspaceFolders'
+import { getAddRemoteCommand } from '.'
+import { Setup } from '..'
+import { run } from '../runner'
+import { SetupSection } from '../webview/contract'
+import { AvailableCommands, InternalCommands } from '../../commands/internal'
+import {
+ RegisteredCliCommands,
+ RegisteredCommands
+} from '../../commands/external'
+import { getFirstWorkspaceFolder } from '../../vscode/workspaceFolders'
const registerSetupConfigCommands = (
setup: Setup,
@@ -100,6 +104,11 @@ export const registerSetupCommands = (
}
)
+ internalCommands.registerExternalCliCommand(
+ RegisteredCliCommands.REMOTE_ADD,
+ getAddRemoteCommand(setup, internalCommands)
+ )
+
registerSetupConfigCommands(setup, internalCommands)
registerSetupShowCommands(setup, internalCommands)
registerSetupStudioCommands(setup, internalCommands)
diff --git a/extension/src/setup/webview/messages.ts b/extension/src/setup/webview/messages.ts
index 2c6f767478..0a91f48b92 100644
--- a/extension/src/setup/webview/messages.ts
+++ b/extension/src/setup/webview/messages.ts
@@ -104,6 +104,8 @@ export class WebviewMessages {
)
case MessageFromWebviewType.OPEN_EXPERIMENTS_WEBVIEW:
return commands.executeCommand(RegisteredCommands.EXPERIMENT_SHOW)
+ case MessageFromWebviewType.REMOTE_ADD:
+ return commands.executeCommand(RegisteredCliCommands.REMOTE_ADD)
default:
Logger.error(`Unexpected message: ${JSON.stringify(message)}`)
diff --git a/extension/src/telemetry/constants.ts b/extension/src/telemetry/constants.ts
index da8ef8a87b..7fc37368c9 100644
--- a/extension/src/telemetry/constants.ts
+++ b/extension/src/telemetry/constants.ts
@@ -187,6 +187,8 @@ export interface IEventNamePropertyMapping {
[EventName.REMOVE_TARGET]: undefined
[EventName.RENAME_TARGET]: undefined
+ [EventName.REMOTE_ADD]: undefined
+
[EventName.GIT_STAGE_ALL]: undefined
[EventName.GIT_UNSTAGE_ALL]: undefined
diff --git a/extension/src/test/suite/setup/index.test.ts b/extension/src/test/suite/setup/index.test.ts
index 9ef5143756..e823b30192 100644
--- a/extension/src/test/suite/setup/index.test.ts
+++ b/extension/src/test/suite/setup/index.test.ts
@@ -69,6 +69,7 @@ suite('Setup Test Suite', () => {
])
})
+ // eslint-disable-next-line sonarjs/cognitive-complexity
describe('Setup', () => {
it('should handle an initialize git message from the webview', async () => {
const { messageSpy, setup, mockInitializeGit } = buildSetup(disposable)
@@ -843,6 +844,103 @@ suite('Setup Test Suite', () => {
expect(mockShowWebview).to.be.calledOnce
}).timeout(WEBVIEW_TEST_TIMEOUT)
+ it('should handle a message to add a remote', async () => {
+ const { messageSpy, setup, mockExecuteCommand } = buildSetup(disposable)
+
+ const webview = await setup.showWebview()
+ await webview.isReady()
+ mockExecuteCommand.restore()
+
+ const mockMessageReceived = getMessageReceivedEmitter(webview)
+
+ const mockRemote = stub(DvcExecutor.prototype, 'remote')
+
+ const remoteAdded = new Promise(resolve =>
+ mockRemote.callsFake((cwd, ...args) => {
+ if (args.includes('add')) {
+ resolve(undefined)
+ }
+ return Promise.resolve('')
+ })
+ )
+
+ const mockShowInputBox = stub(window, 'showInputBox')
+ .onFirstCall()
+ .resolves('storage')
+ .onSecondCall()
+ .resolves('s3://my-bucket')
+
+ messageSpy.resetHistory()
+ mockMessageReceived.fire({
+ type: MessageFromWebviewType.REMOTE_ADD
+ })
+
+ await remoteAdded
+
+ expect(mockShowInputBox).to.be.calledTwice
+ expect(
+ mockRemote,
+ 'new remote is set as the default'
+ ).to.be.calledWithExactly(
+ dvcDemoPath,
+ 'add',
+ '-d',
+ '--project',
+ 'storage',
+ 's3://my-bucket'
+ )
+ }).timeout(WEBVIEW_TEST_TIMEOUT)
+
+ it('should be able to add a remote', async () => {
+ const mockRemote = stub(DvcExecutor.prototype, 'remote')
+
+ const remoteAdded = new Promise(resolve =>
+ mockRemote.callsFake((cwd, ...args) => {
+ if (args.includes('list')) {
+ return Promise.resolve('storage s3://my-bucket')
+ }
+
+ if (args.includes('add')) {
+ resolve(undefined)
+ }
+ return Promise.resolve('')
+ })
+ )
+
+ const mockShowInputBox = stub(window, 'showInputBox')
+ .onFirstCall()
+ .resolves('backup')
+ .onSecondCall()
+ .resolves('s3://my-backup-bucket')
+
+ const mockShowQuickPick = (
+ stub(window, 'showQuickPick') as SinonStub<
+ [items: readonly QuickPickItem[], options: QuickPickOptionsWithTitle],
+ Thenable | undefined>
+ >
+ ).resolves({
+ label: 'No',
+ value: false
+ })
+
+ await commands.executeCommand(RegisteredCliCommands.REMOTE_ADD)
+
+ await remoteAdded
+
+ expect(mockShowInputBox).to.be.calledTwice
+ expect(mockShowQuickPick).to.be.calledOnce
+ expect(
+ mockRemote,
+ 'should not set a remote as the default unless the user explicitly chooses to'
+ ).to.be.calledWithExactly(
+ dvcDemoPath,
+ 'add',
+ '--project',
+ 'backup',
+ 's3://my-backup-bucket'
+ )
+ }).timeout(WEBVIEW_TEST_TIMEOUT)
+
it('should send the appropriate messages to the webview to focus different sections', async () => {
const { setup, messageSpy } = buildSetup(disposable)
messageSpy.restore()
diff --git a/extension/src/test/suite/setup/util.ts b/extension/src/test/suite/setup/util.ts
index fd0d8a2405..f113dfde7c 100644
--- a/extension/src/test/suite/setup/util.ts
+++ b/extension/src/test/suite/setup/util.ts
@@ -44,7 +44,7 @@ export const buildSetup = (
const mockEmitter = disposer.track(new EventEmitter())
stub(dvcReader, 'root').resolves(mockDvcRoot)
- stub(dvcExecutor, 'remote').resolves('')
+ const mockRemote = stub(dvcExecutor, 'remote').resolves('')
const mockVersion = stub(dvcReader, 'version').resolves(MIN_CLI_VERSION)
const mockGlobalVersion = stub(dvcReader, 'globalVersion').resolves(
MIN_CLI_VERSION
@@ -112,6 +112,7 @@ export const buildSetup = (
mockGlobalVersion,
mockInitializeGit,
mockOpenExternal,
+ mockRemote,
mockRunSetup,
mockShowWebview,
mockVersion,
diff --git a/extension/src/vscode/title.ts b/extension/src/vscode/title.ts
index f5eb35800a..694ceea7ab 100644
--- a/extension/src/vscode/title.ts
+++ b/extension/src/vscode/title.ts
@@ -6,6 +6,8 @@ export enum Title {
ENTER_EXPERIMENT_WORKER_COUNT = 'Enter the Number of Queue Workers',
ENTER_FILTER_VALUE = 'Enter a Filter Value',
ENTER_RELATIVE_DESTINATION = 'Enter a Destination Relative to the Root',
+ ENTER_REMOTE_NAME = 'Enter a Name for the Remote',
+ ENTER_REMOTE_URL = 'Enter the URL for the Remote',
ENTER_PATH_OR_CHOOSE_FILE = 'Enter the path to your training script or select it',
ENTER_STUDIO_USERNAME = 'Enter your Studio username',
ENTER_STUDIO_TOKEN = 'Enter your Studio access token',
@@ -36,7 +38,8 @@ export enum Title {
SELECT_SORTS_TO_REMOVE = 'Select Sort(s) to Remove',
SELECT_TRAINING_SCRIPT = 'Select your training script',
SETUP_WORKSPACE = 'Setup the Workspace',
- SET_EXPERIMENTS_HEADER_HEIGHT = 'Set Maximum Experiment Table Header Height'
+ SET_EXPERIMENTS_HEADER_HEIGHT = 'Set Maximum Experiment Table Header Height',
+ SET_REMOTE_AS_DEFAULT = 'Set Default Remote'
}
export const getEnterValueTitle = (path: string): Title =>
diff --git a/extension/src/webview/contract.ts b/extension/src/webview/contract.ts
index fbf109194b..1f98ea74cf 100644
--- a/extension/src/webview/contract.ts
+++ b/extension/src/webview/contract.ts
@@ -51,6 +51,7 @@ export enum MessageFromWebviewType {
SET_EXPERIMENTS_AND_OPEN_PLOTS = 'set-experiments-and-open-plots',
SET_STUDIO_SHARE_EXPERIMENTS_LIVE = 'set-studio-share-experiments-live',
TOGGLE_PLOTS_SECTION = 'toggle-plots-section',
+ REMOTE_ADD = 'remote-add',
REMOVE_CUSTOM_PLOTS = 'remove-custom-plots',
REMOVE_STUDIO_TOKEN = 'remove-studio-token',
MODIFY_EXPERIMENT_PARAMS_AND_QUEUE = 'modify-experiment-params-and-queue',
@@ -160,6 +161,7 @@ export type MessageFromWebview =
type: MessageFromWebviewType.REMOVE_COLUMN_SORT
payload: string
}
+ | { type: MessageFromWebviewType.REMOTE_ADD }
| {
type: MessageFromWebviewType.REMOVE_CUSTOM_PLOTS
}
diff --git a/extension/src/workspace/index.ts b/extension/src/workspace/index.ts
index f04f607355..207cc6e827 100644
--- a/extension/src/workspace/index.ts
+++ b/extension/src/workspace/index.ts
@@ -1,6 +1,6 @@
+import { getOnlyOrPickProject } from './util'
import { InternalCommands } from '../commands/internal'
import { Disposables, reset } from '../util/disposable'
-import { quickPickOne } from '../vscode/quickPick'
import { DeferredDisposable } from '../class/deferred'
export abstract class BaseWorkspace<
@@ -39,17 +39,9 @@ export abstract class BaseWorkspace<
this.resetDeferred()
}
- public async getOnlyOrPickProject() {
+ public getOnlyOrPickProject() {
const dvcRoots = this.getDvcRoots()
-
- if (dvcRoots.length === 1) {
- return dvcRoots[0]
- }
-
- return await quickPickOne(
- dvcRoots,
- 'Select which project to run command against'
- )
+ return getOnlyOrPickProject(dvcRoots)
}
public getRepository(dvcRoot: string) {
diff --git a/extension/src/workspace/util.ts b/extension/src/workspace/util.ts
new file mode 100644
index 0000000000..1ca5036298
--- /dev/null
+++ b/extension/src/workspace/util.ts
@@ -0,0 +1,14 @@
+import { quickPickOne } from '../vscode/quickPick'
+
+export const getOnlyOrPickProject = async (
+ dvcRoots: string[]
+): Promise => {
+ if (dvcRoots.length === 1) {
+ return dvcRoots[0]
+ }
+
+ return await quickPickOne(
+ dvcRoots,
+ 'Select which project to run command against'
+ )
+}
diff --git a/scripts/virtualenv-install.ts b/scripts/virtualenv-install.ts
index e756cd683f..f2b64af35a 100644
--- a/scripts/virtualenv-install.ts
+++ b/scripts/virtualenv-install.ts
@@ -1,3 +1,4 @@
+/* eslint-disable */
import { join, resolve } from 'path'
require('dvc/src/vscode/mockModule')
diff --git a/webview/src/setup/components/App.test.tsx b/webview/src/setup/components/App.test.tsx
index 672b161cc4..03beb10a26 100644
--- a/webview/src/setup/components/App.test.tsx
+++ b/webview/src/setup/components/App.test.tsx
@@ -792,10 +792,32 @@ describe('App', () => {
}
})
- const setupDVCButton = screen.getByText('Connect to Remote Storage')
+ const title = screen.getByText('Connect to Remote Storage')
- expect(setupDVCButton).toBeInTheDocument()
- expect(setupDVCButton).toBeVisible()
+ expect(title).toBeInTheDocument()
+ expect(title).toBeVisible()
+ })
+
+ it('should allow the user to connect a remote if they do not already have one', () => {
+ renderApp({
+ remoteList: { demo: undefined, 'example-get-started': undefined },
+ sectionCollapsed: {
+ [SetupSection.DVC]: true,
+ [SetupSection.EXPERIMENTS]: true,
+ [SetupSection.REMOTES]: false,
+ [SetupSection.STUDIO]: true
+ }
+ })
+ mockPostMessage.mockReset()
+ const startButton = screen.getByText('Add Remote')
+
+ expect(startButton).toBeInTheDocument()
+ expect(startButton).toBeVisible()
+ fireEvent.click(startButton)
+ expect(mockPostMessage).toHaveBeenCalledTimes(1)
+ expect(mockPostMessage).toHaveBeenCalledWith({
+ type: MessageFromWebviewType.REMOTE_ADD
+ })
})
it('should show the list of remotes if there is only one project in the workspace', () => {
diff --git a/webview/src/setup/components/messages.ts b/webview/src/setup/components/messages.ts
index 6f58caf0cc..698ac972f1 100644
--- a/webview/src/setup/components/messages.ts
+++ b/webview/src/setup/components/messages.ts
@@ -52,3 +52,6 @@ export const saveStudioToken = () =>
export const removeStudioToken = () =>
sendMessage({ type: MessageFromWebviewType.REMOVE_STUDIO_TOKEN })
+
+export const addRemote = () =>
+ sendMessage({ type: MessageFromWebviewType.REMOTE_ADD })
diff --git a/webview/src/setup/components/remote/Connect.tsx b/webview/src/setup/components/remote/Connect.tsx
index eea4acf0a1..9f5b68ca5c 100644
--- a/webview/src/setup/components/remote/Connect.tsx
+++ b/webview/src/setup/components/remote/Connect.tsx
@@ -1,5 +1,7 @@
import React from 'react'
import { EmptyState } from '../../../shared/components/emptyState/EmptyState'
+import { StartButton } from '../../../shared/components/button/StartButton'
+import { addRemote } from '../messages'
export const Connect: React.FC = () => (
@@ -18,5 +20,6 @@ export const Connect: React.FC = () => (
{' '}
for details on how to connect to a remote
+ addRemote()} text="Add Remote" />
)
diff --git a/webview/src/stories/Setup.stories.tsx b/webview/src/stories/Setup.stories.tsx
index 0e15735914..5be17782fc 100644
--- a/webview/src/stories/Setup.stories.tsx
+++ b/webview/src/stories/Setup.stories.tsx
@@ -154,6 +154,19 @@ CliAboveLatestTested.args = getUpdatedArgs({
isAboveLatestTestedVersion: true
})
+export const NoRemoteSetup = Template.bind({})
+NoRemoteSetup.args = getUpdatedArgs({
+ remoteList: {
+ '/Users/thatguy/projects/vscode-dvc/rootB': undefined
+ },
+ sectionCollapsed: {
+ [SetupSection.DVC]: true,
+ [SetupSection.EXPERIMENTS]: true,
+ [SetupSection.REMOTES]: false,
+ [SetupSection.STUDIO]: true
+ }
+})
+
export const ProjectRemoteSetup = Template.bind({})
ProjectRemoteSetup.args = getUpdatedArgs({
remoteList: {
From 53b234640dc74c2e3728e1922f366ab4a58c4551 Mon Sep 17 00:00:00 2001
From: Matt Seddon <37993418+mattseddon@users.noreply.github.com>
Date: Fri, 19 May 2023 16:25:39 +1000
Subject: [PATCH 4/4] Rename remote folder to remotes (#3930)
---
webview/src/setup/components/App.tsx | 2 +-
webview/src/setup/components/{remote => remotes}/Connect.tsx | 0
.../setup/components/{remote => remotes}/DvcUninitialized.tsx | 0
.../components/{remote => remotes}/MultiProjectRemotes.tsx | 0
.../src/setup/components/{remote => remotes}/ProjectRemotes.tsx | 0
.../src/setup/components/{remote => remotes}/RemoteDetails.tsx | 0
webview/src/setup/components/{remote => remotes}/Remotes.tsx | 0
.../src/setup/components/{remote => remotes}/styles.module.scss | 0
8 files changed, 1 insertion(+), 1 deletion(-)
rename webview/src/setup/components/{remote => remotes}/Connect.tsx (100%)
rename webview/src/setup/components/{remote => remotes}/DvcUninitialized.tsx (100%)
rename webview/src/setup/components/{remote => remotes}/MultiProjectRemotes.tsx (100%)
rename webview/src/setup/components/{remote => remotes}/ProjectRemotes.tsx (100%)
rename webview/src/setup/components/{remote => remotes}/RemoteDetails.tsx (100%)
rename webview/src/setup/components/{remote => remotes}/Remotes.tsx (100%)
rename webview/src/setup/components/{remote => remotes}/styles.module.scss (100%)
diff --git a/webview/src/setup/components/App.tsx b/webview/src/setup/components/App.tsx
index 369408b0cd..bcf4b123b0 100644
--- a/webview/src/setup/components/App.tsx
+++ b/webview/src/setup/components/App.tsx
@@ -9,7 +9,7 @@ import { Dvc } from './dvc/Dvc'
import { Experiments } from './experiments/Experiments'
import { Studio } from './studio/Studio'
import { SetupContainer } from './SetupContainer'
-import { Remotes } from './remote/Remotes'
+import { Remotes } from './remotes/Remotes'
import { useVsCodeMessaging } from '../../shared/hooks/useVsCodeMessaging'
import { sendMessage } from '../../shared/vscode'
import { TooltipIconType } from '../../shared/components/sectionContainer/InfoTooltip'
diff --git a/webview/src/setup/components/remote/Connect.tsx b/webview/src/setup/components/remotes/Connect.tsx
similarity index 100%
rename from webview/src/setup/components/remote/Connect.tsx
rename to webview/src/setup/components/remotes/Connect.tsx
diff --git a/webview/src/setup/components/remote/DvcUninitialized.tsx b/webview/src/setup/components/remotes/DvcUninitialized.tsx
similarity index 100%
rename from webview/src/setup/components/remote/DvcUninitialized.tsx
rename to webview/src/setup/components/remotes/DvcUninitialized.tsx
diff --git a/webview/src/setup/components/remote/MultiProjectRemotes.tsx b/webview/src/setup/components/remotes/MultiProjectRemotes.tsx
similarity index 100%
rename from webview/src/setup/components/remote/MultiProjectRemotes.tsx
rename to webview/src/setup/components/remotes/MultiProjectRemotes.tsx
diff --git a/webview/src/setup/components/remote/ProjectRemotes.tsx b/webview/src/setup/components/remotes/ProjectRemotes.tsx
similarity index 100%
rename from webview/src/setup/components/remote/ProjectRemotes.tsx
rename to webview/src/setup/components/remotes/ProjectRemotes.tsx
diff --git a/webview/src/setup/components/remote/RemoteDetails.tsx b/webview/src/setup/components/remotes/RemoteDetails.tsx
similarity index 100%
rename from webview/src/setup/components/remote/RemoteDetails.tsx
rename to webview/src/setup/components/remotes/RemoteDetails.tsx
diff --git a/webview/src/setup/components/remote/Remotes.tsx b/webview/src/setup/components/remotes/Remotes.tsx
similarity index 100%
rename from webview/src/setup/components/remote/Remotes.tsx
rename to webview/src/setup/components/remotes/Remotes.tsx
diff --git a/webview/src/setup/components/remote/styles.module.scss b/webview/src/setup/components/remotes/styles.module.scss
similarity index 100%
rename from webview/src/setup/components/remote/styles.module.scss
rename to webview/src/setup/components/remotes/styles.module.scss