weehong ревизий этого фрагмента 1 month ago. К ревизии
3 files changed, 3 insertions, 12 deletions
README.md
| @@ -65,13 +65,10 @@ iwr -useb <GIST>/install-jetbrains.ps1 | iex | |||
| 65 | 65 | | `com.intellij.ml.llm` | JetBrains AI Assistant | | |
| 66 | 66 | | `izhangzhihao.rainbow.brackets` | Rainbow Brackets | | |
| 67 | 67 | | `IdeaVIM` | IdeaVim | | |
| 68 | - | | `ru.adelf.idea.dotenv` | .env files | | |
| 69 | 68 | | `org.sonarlint.idea` | SonarQube for IDE (SonarLint) | | |
| 70 | - | | `org.intellij.qodana` | Qodana | | |
| 71 | 69 | | `Key Promoter X` | Key Promoter X | | |
| 72 | - | | `com.crunch42.openapi` | OpenAPI (Swagger) Editor | | |
| 73 | 70 | | `net.ashald.envfile` | EnvFile | | |
| 74 | - | | `software.xdev.saveactions` | Save Actions X | | |
| 71 | + | | `org.intellij.qodana` | Qodana | | |
| 75 | 72 | ||
| 76 | 73 | Two IDs are easy to get wrong: `IdeaVIM` (xmlId is all-caps `VIM`) and `Key Promoter X` (xmlId contains spaces — must be quoted when passed to `installPlugins`). | |
| 77 | 74 | ||
install-jetbrains.ps1
| @@ -45,13 +45,10 @@ $PluginCatalog = @( | |||
| 45 | 45 | @{ Id = 'com.intellij.ml.llm'; Label = 'JetBrains AI Assistant' } | |
| 46 | 46 | @{ Id = 'izhangzhihao.rainbow.brackets'; Label = 'Rainbow Brackets' } | |
| 47 | 47 | @{ Id = 'IdeaVIM'; Label = 'IdeaVim' } | |
| 48 | - | @{ Id = 'ru.adelf.idea.dotenv'; Label = '.env files' } | |
| 49 | 48 | @{ Id = 'org.sonarlint.idea'; Label = 'SonarQube for IDE (SonarLint)' } | |
| 50 | - | @{ Id = 'org.intellij.qodana'; Label = 'Qodana' } | |
| 51 | 49 | @{ Id = 'Key Promoter X'; Label = 'Key Promoter X' } | |
| 52 | - | @{ Id = 'com.crunch42.openapi'; Label = 'OpenAPI (Swagger) Editor' } | |
| 53 | 50 | @{ Id = 'net.ashald.envfile'; Label = 'EnvFile' } | |
| 54 | - | @{ Id = 'software.xdev.saveactions'; Label = 'Save Actions X' } | |
| 51 | + | @{ Id = 'org.intellij.qodana'; Label = 'Qodana' } | |
| 55 | 52 | ) | |
| 56 | 53 | ||
| 57 | 54 | $KnownLauncherStems = @( | |
install-jetbrains.sh
| @@ -27,13 +27,10 @@ readonly PLUGIN_CATALOG=( | |||
| 27 | 27 | "com.intellij.ml.llm|JetBrains AI Assistant" | |
| 28 | 28 | "izhangzhihao.rainbow.brackets|Rainbow Brackets" | |
| 29 | 29 | "IdeaVIM|IdeaVim" | |
| 30 | - | "ru.adelf.idea.dotenv|.env files" | |
| 31 | 30 | "org.sonarlint.idea|SonarQube for IDE (SonarLint)" | |
| 32 | - | "org.intellij.qodana|Qodana" | |
| 33 | 31 | "Key Promoter X|Key Promoter X" | |
| 34 | - | "com.crunch42.openapi|OpenAPI (Swagger) Editor" | |
| 35 | 32 | "net.ashald.envfile|EnvFile" | |
| 36 | - | "software.xdev.saveactions|Save Actions X" | |
| 33 | + | "org.intellij.qodana|Qodana" | |
| 37 | 34 | ) | |
| 38 | 35 | ||
| 39 | 36 | # Known JetBrains IDE launcher basenames (Toolbox shims + Linux .sh names). | |
weehong ревизий этого фрагмента 1 month ago. К ревизии
3 files changed, 7 insertions, 8 deletions
README.md
| @@ -19,15 +19,15 @@ Replace `<GIST>` with the raw base of this Gist (everything up to and including | |||
| 19 | 19 | ||
| 20 | 20 | ```bash | |
| 21 | 21 | # macOS / Ubuntu (default profile) | |
| 22 | - | curl -sSL https://opengist.rmrf.online/weehong/b2a7c0a2ae6d40e8a14f8d85964a9186/raw/HEAD/install.sh | bash | |
| 22 | + | curl -sSL <GIST>/install.sh | bash | |
| 23 | 23 | ||
| 24 | 24 | # named profile | |
| 25 | - | bash <(curl -sSL https://opengist.rmrf.online/weehong/b2a7c0a2ae6d40e8a14f8d85964a9186/raw/HEAD/install.sh) --profile "WorkSetup" | |
| 25 | + | bash <(curl -sSL <GIST>/install.sh) --profile "WorkSetup" | |
| 26 | 26 | ``` | |
| 27 | 27 | ||
| 28 | 28 | ```powershell | |
| 29 | 29 | # Windows | |
| 30 | - | iwr -useb https://opengist.rmrf.online/weehong/b2a7c0a2ae6d40e8a14f8d85964a9186/raw/HEAD/install.ps1 | iex | |
| 30 | + | iwr -useb <GIST>/install.ps1 | iex | |
| 31 | 31 | ||
| 32 | 32 | # named profile | |
| 33 | 33 | .\install.ps1 -ProfileName "WorkSetup" | |
| @@ -37,12 +37,12 @@ iwr -useb https://opengist.rmrf.online/weehong/b2a7c0a2ae6d40e8a14f8d85964a9186/ | |||
| 37 | 37 | ||
| 38 | 38 | ```bash | |
| 39 | 39 | # macOS / Ubuntu / WSL2 — interactive picker for IDEs and plugins | |
| 40 | - | bash <(curl -sSL https://opengist.rmrf.online/weehong/b2a7c0a2ae6d40e8a14f8d85964a9186/raw/HEAD/install-jetbrains.sh) | |
| 40 | + | bash <(curl -sSL <GIST>/install-jetbrains.sh) | |
| 41 | 41 | ``` | |
| 42 | 42 | ||
| 43 | 43 | ```powershell | |
| 44 | 44 | # Windows — interactive picker | |
| 45 | - | iwr -useb https://opengist.rmrf.online/weehong/b2a7c0a2ae6d40e8a14f8d85964a9186/raw/HEAD/install-jetbrains.ps1 | iex | |
| 45 | + | iwr -useb <GIST>/install-jetbrains.ps1 | iex | |
| 46 | 46 | ``` | |
| 47 | 47 | ||
| 48 | 48 | --- | |
| @@ -52,11 +52,12 @@ iwr -useb https://opengist.rmrf.online/weehong/b2a7c0a2ae6d40e8a14f8d85964a9186/ | |||
| 52 | 52 | ### VS Code extensions | |
| 53 | 53 | ||
| 54 | 54 | - `docker.docker` | |
| 55 | - | - `github.copilot-chat` | |
| 56 | 55 | - `github.github-vscode-theme` | |
| 57 | 56 | - `ms-vscode-remote.vscode-remote-extensionpack` | |
| 58 | 57 | - `pkief.material-icon-theme` | |
| 59 | 58 | ||
| 59 | + | (GitHub Copilot Chat is no longer in the list — it ships built-in with VS Code now.) | |
| 60 | + | ||
| 60 | 61 | ### JetBrains plugins (canonical marketplace IDs) | |
| 61 | 62 | ||
| 62 | 63 | | xmlId | Plugin | | |
install.ps1
| @@ -43,7 +43,6 @@ $ErrorActionPreference = 'Stop' | |||
| 43 | 43 | ||
| 44 | 44 | $Extensions = @( | |
| 45 | 45 | 'docker.docker', | |
| 46 | - | 'github.copilot-chat', | |
| 47 | 46 | 'github.github-vscode-theme', | |
| 48 | 47 | 'ms-vscode-remote.vscode-remote-extensionpack', | |
| 49 | 48 | 'pkief.material-icon-theme' | |
install.sh
| @@ -15,7 +15,6 @@ IFS=$'\n\t' | |||
| 15 | 15 | ||
| 16 | 16 | readonly EXTENSIONS=( | |
| 17 | 17 | "docker.docker" | |
| 18 | - | "github.copilot-chat" | |
| 19 | 18 | "github.github-vscode-theme" | |
| 20 | 19 | "ms-vscode-remote.vscode-remote-extensionpack" | |
| 21 | 20 | "pkief.material-icon-theme" | |
weehong ревизий этого фрагмента 1 month ago. К ревизии
1 file changed, 2 insertions, 2 deletions
README.md
| @@ -37,12 +37,12 @@ iwr -useb https://opengist.rmrf.online/weehong/b2a7c0a2ae6d40e8a14f8d85964a9186/ | |||
| 37 | 37 | ||
| 38 | 38 | ```bash | |
| 39 | 39 | # macOS / Ubuntu / WSL2 — interactive picker for IDEs and plugins | |
| 40 | - | bash <(curl -sSL <GIST>/install-jetbrains.sh) | |
| 40 | + | bash <(curl -sSL https://opengist.rmrf.online/weehong/b2a7c0a2ae6d40e8a14f8d85964a9186/raw/HEAD/install-jetbrains.sh) | |
| 41 | 41 | ``` | |
| 42 | 42 | ||
| 43 | 43 | ```powershell | |
| 44 | 44 | # Windows — interactive picker | |
| 45 | - | iwr -useb <GIST>/install-jetbrains.ps1 | iex | |
| 45 | + | iwr -useb https://opengist.rmrf.online/weehong/b2a7c0a2ae6d40e8a14f8d85964a9186/raw/HEAD/install-jetbrains.ps1 | iex | |
| 46 | 46 | ``` | |
| 47 | 47 | ||
| 48 | 48 | --- | |
weehong ревизий этого фрагмента 1 month ago. К ревизии
1 file changed, 3 insertions, 3 deletions
README.md
| @@ -19,15 +19,15 @@ Replace `<GIST>` with the raw base of this Gist (everything up to and including | |||
| 19 | 19 | ||
| 20 | 20 | ```bash | |
| 21 | 21 | # macOS / Ubuntu (default profile) | |
| 22 | - | curl -sSL <GIST>/install.sh | bash | |
| 22 | + | curl -sSL https://opengist.rmrf.online/weehong/b2a7c0a2ae6d40e8a14f8d85964a9186/raw/HEAD/install.sh | bash | |
| 23 | 23 | ||
| 24 | 24 | # named profile | |
| 25 | - | bash <(curl -sSL <GIST>/install.sh) --profile "WorkSetup" | |
| 25 | + | bash <(curl -sSL https://opengist.rmrf.online/weehong/b2a7c0a2ae6d40e8a14f8d85964a9186/raw/HEAD/install.sh) --profile "WorkSetup" | |
| 26 | 26 | ``` | |
| 27 | 27 | ||
| 28 | 28 | ```powershell | |
| 29 | 29 | # Windows | |
| 30 | - | iwr -useb <GIST>/install.ps1 | iex | |
| 30 | + | iwr -useb https://opengist.rmrf.online/weehong/b2a7c0a2ae6d40e8a14f8d85964a9186/raw/HEAD/install.ps1 | iex | |
| 31 | 31 | ||
| 32 | 32 | # named profile | |
| 33 | 33 | .\install.ps1 -ProfileName "WorkSetup" | |
weehong ревизий этого фрагмента 1 month ago. К ревизии
1 file changed, 168 insertions
README.md(файл создан)
| @@ -0,0 +1,168 @@ | |||
| 1 | + | # VS Code + JetBrains Extension Installers | |
| 2 | + | ||
| 3 | + | A bundle of cross-platform scripts that install a curated set of editor extensions / plugins, one command per platform. | |
| 4 | + | ||
| 5 | + | | File | Editor | Platforms | | |
| 6 | + | | ----------------------- | ----------------------------------- | ------------------------ | | |
| 7 | + | | `install.sh` | VS Code | macOS, Linux (Ubuntu) | | |
| 8 | + | | `install.ps1` | VS Code | Windows | | |
| 9 | + | | `install-jetbrains.sh` | JetBrains IDEs + remote-dev-server | macOS, Linux, WSL2 | | |
| 10 | + | | `install-jetbrains.ps1` | JetBrains IDEs + Gateway Client | Windows | | |
| 11 | + | ||
| 12 | + | --- | |
| 13 | + | ||
| 14 | + | ## Quick start | |
| 15 | + | ||
| 16 | + | Replace `<GIST>` with the raw base of this Gist (everything up to and including `/raw/HEAD/`). | |
| 17 | + | ||
| 18 | + | ### VS Code | |
| 19 | + | ||
| 20 | + | ```bash | |
| 21 | + | # macOS / Ubuntu (default profile) | |
| 22 | + | curl -sSL <GIST>/install.sh | bash | |
| 23 | + | ||
| 24 | + | # named profile | |
| 25 | + | bash <(curl -sSL <GIST>/install.sh) --profile "WorkSetup" | |
| 26 | + | ``` | |
| 27 | + | ||
| 28 | + | ```powershell | |
| 29 | + | # Windows | |
| 30 | + | iwr -useb <GIST>/install.ps1 | iex | |
| 31 | + | ||
| 32 | + | # named profile | |
| 33 | + | .\install.ps1 -ProfileName "WorkSetup" | |
| 34 | + | ``` | |
| 35 | + | ||
| 36 | + | ### JetBrains | |
| 37 | + | ||
| 38 | + | ```bash | |
| 39 | + | # macOS / Ubuntu / WSL2 — interactive picker for IDEs and plugins | |
| 40 | + | bash <(curl -sSL <GIST>/install-jetbrains.sh) | |
| 41 | + | ``` | |
| 42 | + | ||
| 43 | + | ```powershell | |
| 44 | + | # Windows — interactive picker | |
| 45 | + | iwr -useb <GIST>/install-jetbrains.ps1 | iex | |
| 46 | + | ``` | |
| 47 | + | ||
| 48 | + | --- | |
| 49 | + | ||
| 50 | + | ## What's in the catalogs | |
| 51 | + | ||
| 52 | + | ### VS Code extensions | |
| 53 | + | ||
| 54 | + | - `docker.docker` | |
| 55 | + | - `github.copilot-chat` | |
| 56 | + | - `github.github-vscode-theme` | |
| 57 | + | - `ms-vscode-remote.vscode-remote-extensionpack` | |
| 58 | + | - `pkief.material-icon-theme` | |
| 59 | + | ||
| 60 | + | ### JetBrains plugins (canonical marketplace IDs) | |
| 61 | + | ||
| 62 | + | | xmlId | Plugin | | |
| 63 | + | | ------------------------------- | ----------------------------- | | |
| 64 | + | | `com.intellij.ml.llm` | JetBrains AI Assistant | | |
| 65 | + | | `izhangzhihao.rainbow.brackets` | Rainbow Brackets | | |
| 66 | + | | `IdeaVIM` | IdeaVim | | |
| 67 | + | | `ru.adelf.idea.dotenv` | .env files | | |
| 68 | + | | `org.sonarlint.idea` | SonarQube for IDE (SonarLint) | | |
| 69 | + | | `org.intellij.qodana` | Qodana | | |
| 70 | + | | `Key Promoter X` | Key Promoter X | | |
| 71 | + | | `com.crunch42.openapi` | OpenAPI (Swagger) Editor | | |
| 72 | + | | `net.ashald.envfile` | EnvFile | | |
| 73 | + | | `software.xdev.saveactions` | Save Actions X | | |
| 74 | + | ||
| 75 | + | Two IDs are easy to get wrong: `IdeaVIM` (xmlId is all-caps `VIM`) and `Key Promoter X` (xmlId contains spaces — must be quoted when passed to `installPlugins`). | |
| 76 | + | ||
| 77 | + | --- | |
| 78 | + | ||
| 79 | + | ## VS Code installer details | |
| 80 | + | ||
| 81 | + | `install.sh` / `install.ps1` are thin wrappers around `code --install-extension`: | |
| 82 | + | ||
| 83 | + | - `--profile "Name"` / `-ProfileName "Name"` installs into a named profile; VS Code creates the profile if it doesn't exist. | |
| 84 | + | - `--force` is passed so re-runs are idempotent. | |
| 85 | + | - Both scripts exit non-zero if any extension fails, and print a final summary. | |
| 86 | + | ||
| 87 | + | Prereq: the `code` CLI must be on `PATH`. | |
| 88 | + | ||
| 89 | + | - **macOS:** open VS Code → Cmd+Shift+P → "Shell Command: Install 'code' command in PATH". | |
| 90 | + | - **Ubuntu:** install from <https://code.visualstudio.com/> or `sudo snap install --classic code`. | |
| 91 | + | - **Windows:** during install, check "Add to PATH" (the default). | |
| 92 | + | ||
| 93 | + | --- | |
| 94 | + | ||
| 95 | + | ## JetBrains installer details | |
| 96 | + | ||
| 97 | + | Both JetBrains scripts: | |
| 98 | + | ||
| 99 | + | 1. Detect the host OS. | |
| 100 | + | 2. Scan the system for installed JetBrains IDEs and (where applicable) Gateway / remote-dev targets. | |
| 101 | + | 3. Show an interactive multi-select picker for IDEs and another for plugins. | |
| 102 | + | 4. Run each IDE's `installPlugins` against every selected plugin. | |
| 103 | + | ||
| 104 | + | ### What gets scanned | |
| 105 | + | ||
| 106 | + | #### `install-jetbrains.sh` | |
| 107 | + | ||
| 108 | + | | OS | Source | | |
| 109 | + | | ----- | ---------------------------------------------------------------------------- | | |
| 110 | + | | macOS | `~/Library/Application Support/JetBrains/Toolbox/scripts/*` (Toolbox shims) | | |
| 111 | + | | macOS | `/Applications/<JetBrains IDE>.app/Contents/MacOS/*` | | |
| 112 | + | | Linux | `~/.local/share/JetBrains/Toolbox/apps/*/bin/*.sh` | | |
| 113 | + | | Linux | `/opt/*/bin/*.sh` (snap / .deb / tarball) | | |
| 114 | + | | Linux | `~/.cache/JetBrains/RemoteDev/dist/*/bin/remote-dev-server.sh` (Gateway, SSH/Orbstack) | | |
| 115 | + | | WSL2 | `/mnt/c/Users/<WindowsUser>/AppData/Local/Programs/*/bin/*.exe` | | |
| 116 | + | | WSL2 | `/mnt/c/Users/<WindowsUser>/AppData/Local/JetBrains/Toolbox/scripts/*.cmd` | | |
| 117 | + | | WSL2 | `~/.cache/JetBrains/RemoteDev/dist/*/bin/remote-dev-server.sh` | | |
| 118 | + | ||
| 119 | + | WSL2 detection works by grepping `microsoft` in `/proc/version`. The Windows username is read at runtime via `cmd.exe /c 'echo %USERNAME%'`. | |
| 120 | + | ||
| 121 | + | #### `install-jetbrains.ps1` | |
| 122 | + | ||
| 123 | + | | Source | What it is | | |
| 124 | + | | ------------------------------------------------------- | -------------------------------- | | |
| 125 | + | | `%LOCALAPPDATA%\Programs\*\bin\*64.exe` | Standalone + Toolbox app installs| | |
| 126 | + | | `%LOCALAPPDATA%\JetBrains\Toolbox\scripts\*.cmd` | Toolbox shims | | |
| 127 | + | | `%LOCALAPPDATA%\JetBrains\JetBrainsClient*` (newest) | Gateway Client (special path) | | |
| 128 | + | ||
| 129 | + | ### Picker | |
| 130 | + | ||
| 131 | + | - **Bash:** uses `fzf --multi` if `fzf` is on `PATH` (TAB to toggle, Enter to confirm); otherwise prints a numbered list and accepts comma-separated indexes (`1,3,5`) or `a` for all. | |
| 132 | + | - **PowerShell:** uses `Out-GridView -OutputMode Multiple` when available; otherwise the same numbered/comma-input fallback. | |
| 133 | + | ||
| 134 | + | ### Gateway Client path (Windows) | |
| 135 | + | ||
| 136 | + | When the script detects `%LOCALAPPDATA%\JetBrains\JetBrainsClient*` and you select it from the picker, the install logic switches: the bundled Gateway client has no `installPlugins` CLI, so the script reads the client's `build.txt` and downloads each plugin directly from `plugins.jetbrains.com`, unpacking it into the client's `plugins\` folder. | |
| 137 | + | ||
| 138 | + | If `build.txt` is missing, the client folder hasn't been initialized yet — open a Remote Project from Gateway once, then re-run. | |
| 139 | + | ||
| 140 | + | After Gateway installs finish, **close and re-open the Gateway Client** for it to pick up the new plugins. | |
| 141 | + | ||
| 142 | + | --- | |
| 143 | + | ||
| 144 | + | ## Troubleshooting | |
| 145 | + | ||
| 146 | + | **`code` not on PATH.** See the VS Code installer details section above. | |
| 147 | + | ||
| 148 | + | **PowerShell refuses to run the script.** Each `.ps1` already sets `RemoteSigned` for the current process only. If your environment blocks that too, invoke explicitly: | |
| 149 | + | ||
| 150 | + | ```powershell | |
| 151 | + | powershell -ExecutionPolicy Bypass -File .\install.ps1 | |
| 152 | + | ``` | |
| 153 | + | ||
| 154 | + | **`installPlugins` reports a plugin is already installed.** Harmless — it's a no-op. Both scripts treat it as success. | |
| 155 | + | ||
| 156 | + | **No JetBrains IDEs detected.** Install one via the [JetBrains Toolbox](https://www.jetbrains.com/toolbox-app/) (which sets up the standard paths the script scans), or install standalone — both layouts are supported. | |
| 157 | + | ||
| 158 | + | **WSL2 picker is empty.** Verify the Windows username is resolved: in WSL run `cmd.exe /c 'echo %USERNAME%'`. If that prints nothing, your `interop.appendWindowsPath` may be disabled; either re-enable it in `/etc/wsl.conf` or run `install-jetbrains.ps1` natively on Windows instead. | |
| 159 | + | ||
| 160 | + | **`Key Promoter X` install fails.** The xmlId contains spaces; if you're typing it manually elsewhere, quote it. The bundled scripts already do. | |
| 161 | + | ||
| 162 | + | --- | |
| 163 | + | ||
| 164 | + | ## Notes for re-running | |
| 165 | + | ||
| 166 | + | - Scripts are idempotent — already-installed extensions/plugins are skipped or upgraded in place. | |
| 167 | + | - Exit code is non-zero if any extension/plugin failed. | |
| 168 | + | - The JetBrains scripts ask every time; there's no flag to skip the picker by design. | |
weehong ревизий этого фрагмента 1 month ago. К ревизии
2 files changed, 772 insertions
install-jetbrains.ps1(файл создан)
| @@ -0,0 +1,334 @@ | |||
| 1 | + | <# | |
| 2 | + | .SYNOPSIS | |
| 3 | + | Install JetBrains plugins into locally installed IDEs (and into the | |
| 4 | + | JetBrains Gateway Client) on Windows. | |
| 5 | + | ||
| 6 | + | .DESCRIPTION | |
| 7 | + | Detects every JetBrains IDE under %LOCALAPPDATA%\Programs\* (standalone | |
| 8 | + | installs and Toolbox-managed installs) plus Toolbox shims under | |
| 9 | + | %LOCALAPPDATA%\JetBrains\Toolbox\scripts\*.cmd, and the most-recent | |
| 10 | + | JetBrains Gateway Client under %LOCALAPPDATA%\JetBrains\JetBrainsClient*. | |
| 11 | + | ||
| 12 | + | You multi-select IDE(s) and plugin(s) interactively. Standard IDEs get | |
| 13 | + | "installPlugins <id>..." invoked once with all selected plugins. The | |
| 14 | + | Gateway Client path is special: the bundled client has no installPlugins | |
| 15 | + | CLI, so plugins are downloaded directly from plugins.jetbrains.com and | |
| 16 | + | unpacked into the client's plugins folder (mirrors the user's | |
| 17 | + | plugins_gateway.ps1). | |
| 18 | + | ||
| 19 | + | .NOTES | |
| 20 | + | Process-scope ExecutionPolicy is set to RemoteSigned. No machine-wide | |
| 21 | + | change is persisted. If PowerShell still refuses to run the file: | |
| 22 | + | powershell -ExecutionPolicy Bypass -File .\install-jetbrains.ps1 | |
| 23 | + | #> | |
| 24 | + | ||
| 25 | + | [CmdletBinding()] | |
| 26 | + | param() | |
| 27 | + | ||
| 28 | + | # Loosen execution policy for THIS process only. | |
| 29 | + | try { | |
| 30 | + | $current = Get-ExecutionPolicy -Scope Process -ErrorAction Stop | |
| 31 | + | if ($current -in @('Restricted', 'AllSigned', 'Undefined')) { | |
| 32 | + | Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned -Force -ErrorAction Stop | |
| 33 | + | } | |
| 34 | + | } catch { | |
| 35 | + | Write-Verbose ("Could not adjust process execution policy: {0}" -f $_.Exception.Message) | |
| 36 | + | } | |
| 37 | + | ||
| 38 | + | $ErrorActionPreference = 'Stop' | |
| 39 | + | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 | |
| 40 | + | ||
| 41 | + | # ------------------------------------------------------------------ | |
| 42 | + | # Canonical plugin catalog. Each entry: @{ Id; Label } | |
| 43 | + | # ------------------------------------------------------------------ | |
| 44 | + | $PluginCatalog = @( | |
| 45 | + | @{ Id = 'com.intellij.ml.llm'; Label = 'JetBrains AI Assistant' } | |
| 46 | + | @{ Id = 'izhangzhihao.rainbow.brackets'; Label = 'Rainbow Brackets' } | |
| 47 | + | @{ Id = 'IdeaVIM'; Label = 'IdeaVim' } | |
| 48 | + | @{ Id = 'ru.adelf.idea.dotenv'; Label = '.env files' } | |
| 49 | + | @{ Id = 'org.sonarlint.idea'; Label = 'SonarQube for IDE (SonarLint)' } | |
| 50 | + | @{ Id = 'org.intellij.qodana'; Label = 'Qodana' } | |
| 51 | + | @{ Id = 'Key Promoter X'; Label = 'Key Promoter X' } | |
| 52 | + | @{ Id = 'com.crunch42.openapi'; Label = 'OpenAPI (Swagger) Editor' } | |
| 53 | + | @{ Id = 'net.ashald.envfile'; Label = 'EnvFile' } | |
| 54 | + | @{ Id = 'software.xdev.saveactions'; Label = 'Save Actions X' } | |
| 55 | + | ) | |
| 56 | + | ||
| 57 | + | $KnownLauncherStems = @( | |
| 58 | + | 'idea64', 'pycharm64', 'webstorm64', 'goland64', 'rubymine64', | |
| 59 | + | 'clion64', 'phpstorm64', 'datagrip64', 'rustrover64', 'rider64', | |
| 60 | + | 'studio64', 'fleet' | |
| 61 | + | ) | |
| 62 | + | ||
| 63 | + | function Get-PrettyLabel { | |
| 64 | + | param([string]$Stem) | |
| 65 | + | switch -Regex ($Stem) { | |
| 66 | + | '^idea' { return 'IntelliJ IDEA' } | |
| 67 | + | '^pycharm' { return 'PyCharm' } | |
| 68 | + | '^webstorm' { return 'WebStorm' } | |
| 69 | + | '^goland' { return 'GoLand' } | |
| 70 | + | '^rubymine' { return 'RubyMine' } | |
| 71 | + | '^clion' { return 'CLion' } | |
| 72 | + | '^phpstorm' { return 'PhpStorm' } | |
| 73 | + | '^datagrip' { return 'DataGrip' } | |
| 74 | + | '^rustrover' { return 'RustRover' } | |
| 75 | + | '^rider' { return 'Rider' } | |
| 76 | + | '^studio' { return 'Android Studio' } | |
| 77 | + | '^fleet' { return 'Fleet' } | |
| 78 | + | default { return $Stem } | |
| 79 | + | } | |
| 80 | + | } | |
| 81 | + | ||
| 82 | + | # ------------------------------------------------------------------ | |
| 83 | + | # IDE candidate scan | |
| 84 | + | # Each candidate: PSCustomObject @{ Type; Launcher; Label; Path; Client } | |
| 85 | + | # Type: 'Ide' (run launcher installPlugins ...) | |
| 86 | + | # | 'WinCmd' (run .cmd shim via cmd.exe) | |
| 87 | + | # | 'Gateway' (download-and-unzip flow into $Client\plugins) | |
| 88 | + | # ------------------------------------------------------------------ | |
| 89 | + | $Candidates = New-Object System.Collections.Generic.List[object] | |
| 90 | + | ||
| 91 | + | # 1) Standalone & Toolbox-app installs under %LOCALAPPDATA%\Programs\*\bin\*.exe | |
| 92 | + | $programs = Join-Path $env:LOCALAPPDATA 'Programs' | |
| 93 | + | if (Test-Path $programs) { | |
| 94 | + | Get-ChildItem -Path $programs -Directory -ErrorAction SilentlyContinue | ForEach-Object { | |
| 95 | + | $binDir = Join-Path $_.FullName 'bin' | |
| 96 | + | if (-not (Test-Path $binDir)) { return } | |
| 97 | + | Get-ChildItem -Path $binDir -Filter '*.exe' -File -ErrorAction SilentlyContinue | ForEach-Object { | |
| 98 | + | $stem = [System.IO.Path]::GetFileNameWithoutExtension($_.Name) | |
| 99 | + | if ($KnownLauncherStems -notcontains $stem) { return } | |
| 100 | + | $Candidates.Add([pscustomobject]@{ | |
| 101 | + | Type = 'Ide' | |
| 102 | + | Launcher = $_.FullName | |
| 103 | + | Label = ("{0} ({1})" -f (Get-PrettyLabel $stem), $_.FullName) | |
| 104 | + | Path = $_.FullName | |
| 105 | + | Client = $null | |
| 106 | + | }) | |
| 107 | + | } | |
| 108 | + | } | |
| 109 | + | } | |
| 110 | + | ||
| 111 | + | # 2) Toolbox .cmd shims | |
| 112 | + | $tbScripts = Join-Path $env:LOCALAPPDATA 'JetBrains\Toolbox\scripts' | |
| 113 | + | if (Test-Path $tbScripts) { | |
| 114 | + | Get-ChildItem -Path $tbScripts -Filter '*.cmd' -File -ErrorAction SilentlyContinue | ForEach-Object { | |
| 115 | + | $stem = [System.IO.Path]::GetFileNameWithoutExtension($_.Name) | |
| 116 | + | # Toolbox shims keep names like "idea.cmd", "rider.cmd" — strip trailing 64 etc. | |
| 117 | + | $Candidates.Add([pscustomobject]@{ | |
| 118 | + | Type = 'WinCmd' | |
| 119 | + | Launcher = $_.FullName | |
| 120 | + | Label = ("{0} ({1})" -f (Get-PrettyLabel $stem), $_.FullName) | |
| 121 | + | Path = $_.FullName | |
| 122 | + | Client = $null | |
| 123 | + | }) | |
| 124 | + | } | |
| 125 | + | } | |
| 126 | + | ||
| 127 | + | # 3) JetBrains Gateway Client — pick the most-recently-modified one if any. | |
| 128 | + | $jbRoot = Join-Path $env:LOCALAPPDATA 'JetBrains' | |
| 129 | + | if (Test-Path $jbRoot) { | |
| 130 | + | $client = Get-ChildItem -Path $jbRoot -Directory -Filter 'JetBrainsClient*' -ErrorAction SilentlyContinue | | |
| 131 | + | Sort-Object LastWriteTime -Descending | Select-Object -First 1 | |
| 132 | + | if ($client) { | |
| 133 | + | $Candidates.Add([pscustomobject]@{ | |
| 134 | + | Type = 'Gateway' | |
| 135 | + | Launcher = $client.FullName | |
| 136 | + | Label = ("JetBrains Gateway Client ({0})" -f $client.FullName) | |
| 137 | + | Path = $client.FullName | |
| 138 | + | Client = $client.FullName | |
| 139 | + | }) | |
| 140 | + | } | |
| 141 | + | } | |
| 142 | + | ||
| 143 | + | if ($Candidates.Count -eq 0) { | |
| 144 | + | Write-Host "" | |
| 145 | + | Write-Host "No JetBrains IDEs or Gateway clients found." -ForegroundColor Red | |
| 146 | + | Write-Host "Install one via the JetBrains Toolbox, then re-run." -ForegroundColor Yellow | |
| 147 | + | exit 1 | |
| 148 | + | } | |
| 149 | + | ||
| 150 | + | # ------------------------------------------------------------------ | |
| 151 | + | # Multi-select picker — prefer Out-GridView when available. | |
| 152 | + | # ------------------------------------------------------------------ | |
| 153 | + | function Select-Multi { | |
| 154 | + | param( | |
| 155 | + | [Parameter(Mandatory)] [string]$Title, | |
| 156 | + | [Parameter(Mandatory)] [array]$Items, | |
| 157 | + | [Parameter(Mandatory)] [string]$Property | |
| 158 | + | ) | |
| 159 | + | ||
| 160 | + | $hasOgv = $false | |
| 161 | + | try { | |
| 162 | + | $cmd = Get-Command Out-GridView -ErrorAction Stop | |
| 163 | + | if ($cmd) { $hasOgv = $true } | |
| 164 | + | } catch { $hasOgv = $false } | |
| 165 | + | ||
| 166 | + | if ($hasOgv) { | |
| 167 | + | $chosen = $Items | Out-GridView -Title $Title -OutputMode Multiple | |
| 168 | + | return ,@($chosen) | |
| 169 | + | } | |
| 170 | + | ||
| 171 | + | # Numbered-prompt fallback. | |
| 172 | + | Write-Host "" | |
| 173 | + | Write-Host $Title -ForegroundColor Cyan | |
| 174 | + | for ($i = 0; $i -lt $Items.Count; $i++) { | |
| 175 | + | Write-Host (" {0,2}) {1}" -f ($i + 1), $Items[$i].$Property) | |
| 176 | + | } | |
| 177 | + | while ($true) { | |
| 178 | + | $raw = Read-Host "Select (e.g. 1,3,5 or a for all)" | |
| 179 | + | if ([string]::IsNullOrWhiteSpace($raw)) { | |
| 180 | + | Write-Host " please enter at least one number, or 'a'." -ForegroundColor Yellow | |
| 181 | + | continue | |
| 182 | + | } | |
| 183 | + | if ($raw.Trim().ToLower() -eq 'a') { | |
| 184 | + | return ,@($Items) | |
| 185 | + | } | |
| 186 | + | $idxs = $raw -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' } | |
| 187 | + | $bad = $false | |
| 188 | + | $sel = @() | |
| 189 | + | foreach ($t in $idxs) { | |
| 190 | + | if ($t -notmatch '^[0-9]+$') { $bad = $true; break } | |
| 191 | + | $n = [int]$t | |
| 192 | + | if ($n -lt 1 -or $n -gt $Items.Count) { $bad = $true; break } | |
| 193 | + | $sel += $Items[$n - 1] | |
| 194 | + | } | |
| 195 | + | if ($bad -or $sel.Count -eq 0) { | |
| 196 | + | Write-Host " invalid input; try again." -ForegroundColor Yellow | |
| 197 | + | continue | |
| 198 | + | } | |
| 199 | + | return ,@($sel) | |
| 200 | + | } | |
| 201 | + | } | |
| 202 | + | ||
| 203 | + | Write-Host ("Detected {0} JetBrains target(s)." -f $Candidates.Count) -ForegroundColor Green | |
| 204 | + | ||
| 205 | + | $selectedIdes = Select-Multi -Title "Pick IDE(s)" -Items $Candidates -Property 'Label' | |
| 206 | + | if ($selectedIdes.Count -eq 0) { | |
| 207 | + | Write-Host "No IDEs selected; exiting." -ForegroundColor Red | |
| 208 | + | exit 1 | |
| 209 | + | } | |
| 210 | + | ||
| 211 | + | $selectedPlugins = Select-Multi -Title "Pick plugin(s)" -Items $PluginCatalog -Property 'Label' | |
| 212 | + | if ($selectedPlugins.Count -eq 0) { | |
| 213 | + | Write-Host "No plugins selected; exiting." -ForegroundColor Red | |
| 214 | + | exit 1 | |
| 215 | + | } | |
| 216 | + | ||
| 217 | + | # ------------------------------------------------------------------ | |
| 218 | + | # Install loop | |
| 219 | + | # ------------------------------------------------------------------ | |
| 220 | + | $installed = 0 | |
| 221 | + | $failed = 0 | |
| 222 | + | $failedList = @() | |
| 223 | + | ||
| 224 | + | Write-Host "" | |
| 225 | + | Write-Host ("Installing {0} plugin(s) into {1} IDE(s)..." -f $selectedPlugins.Count, $selectedIdes.Count) -ForegroundColor Cyan | |
| 226 | + | Write-Host "" | |
| 227 | + | ||
| 228 | + | foreach ($ide in $selectedIdes) { | |
| 229 | + | Write-Host (">>> {0}" -f $ide.Label) | |
| 230 | + | ||
| 231 | + | switch ($ide.Type) { | |
| 232 | + | ||
| 233 | + | # ---- standard IDE launcher ---- | |
| 234 | + | 'Ide' { | |
| 235 | + | $args = @('installPlugins') + ($selectedPlugins | ForEach-Object { $_.Id }) | |
| 236 | + | try { | |
| 237 | + | $output = & $ide.Launcher @args 2>&1 | |
| 238 | + | if ($LASTEXITCODE -eq 0) { | |
| 239 | + | Write-Host (" all {0} plugin(s) ok" -f $selectedPlugins.Count) -ForegroundColor Green | |
| 240 | + | $installed += $selectedPlugins.Count | |
| 241 | + | } else { | |
| 242 | + | Write-Host (" installPlugins exited {0} FAILED" -f $LASTEXITCODE) -ForegroundColor Red | |
| 243 | + | $failed += $selectedPlugins.Count | |
| 244 | + | foreach ($p in $selectedPlugins) { $failedList += ("{0} :: {1}" -f $ide.Label, $p.Id) } | |
| 245 | + | $output | ForEach-Object { Write-Host " $_" -ForegroundColor DarkRed } | |
| 246 | + | } | |
| 247 | + | } catch { | |
| 248 | + | Write-Host (" launcher invocation threw FAILED") -ForegroundColor Red | |
| 249 | + | $failed += $selectedPlugins.Count | |
| 250 | + | foreach ($p in $selectedPlugins) { $failedList += ("{0} :: {1}" -f $ide.Label, $p.Id) } | |
| 251 | + | Write-Host (" {0}" -f $_.Exception.Message) -ForegroundColor DarkRed | |
| 252 | + | } | |
| 253 | + | } | |
| 254 | + | ||
| 255 | + | # ---- Toolbox .cmd shim ---- | |
| 256 | + | 'WinCmd' { | |
| 257 | + | $idArgs = ($selectedPlugins | ForEach-Object { '"{0}"' -f $_.Id }) -join ' ' | |
| 258 | + | try { | |
| 259 | + | $output = cmd.exe /c "`"$($ide.Launcher)`" installPlugins $idArgs" 2>&1 | |
| 260 | + | if ($LASTEXITCODE -eq 0) { | |
| 261 | + | Write-Host (" all {0} plugin(s) ok" -f $selectedPlugins.Count) -ForegroundColor Green | |
| 262 | + | $installed += $selectedPlugins.Count | |
| 263 | + | } else { | |
| 264 | + | Write-Host (" installPlugins exited {0} FAILED" -f $LASTEXITCODE) -ForegroundColor Red | |
| 265 | + | $failed += $selectedPlugins.Count | |
| 266 | + | foreach ($p in $selectedPlugins) { $failedList += ("{0} :: {1}" -f $ide.Label, $p.Id) } | |
| 267 | + | $output | ForEach-Object { Write-Host " $_" -ForegroundColor DarkRed } | |
| 268 | + | } | |
| 269 | + | } catch { | |
| 270 | + | Write-Host (" cmd.exe invocation threw FAILED") -ForegroundColor Red | |
| 271 | + | $failed += $selectedPlugins.Count | |
| 272 | + | foreach ($p in $selectedPlugins) { $failedList += ("{0} :: {1}" -f $ide.Label, $p.Id) } | |
| 273 | + | Write-Host (" {0}" -f $_.Exception.Message) -ForegroundColor DarkRed | |
| 274 | + | } | |
| 275 | + | } | |
| 276 | + | ||
| 277 | + | # ---- Gateway Client ---- | |
| 278 | + | 'Gateway' { | |
| 279 | + | $client = $ide.Client | |
| 280 | + | $buildFile = Join-Path $client 'build.txt' | |
| 281 | + | if (-not (Test-Path $buildFile)) { | |
| 282 | + | Write-Host " build.txt missing under client; cannot determine build id SKIPPED" -ForegroundColor Yellow | |
| 283 | + | Write-Host " (open a Remote Project once to populate the client folder)" -ForegroundColor DarkYellow | |
| 284 | + | $failed += $selectedPlugins.Count | |
| 285 | + | foreach ($p in $selectedPlugins) { $failedList += ("{0} :: {1}" -f $ide.Label, $p.Id) } | |
| 286 | + | continue | |
| 287 | + | } | |
| 288 | + | $buildId = (Get-Content $buildFile -Raw -ErrorAction Stop).Trim() | |
| 289 | + | ||
| 290 | + | $pluginDir = Join-Path $client 'plugins' | |
| 291 | + | if (-not (Test-Path $pluginDir)) { | |
| 292 | + | New-Item -ItemType Directory -Path $pluginDir | Out-Null | |
| 293 | + | } | |
| 294 | + | ||
| 295 | + | $ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" | |
| 296 | + | ||
| 297 | + | foreach ($p in $selectedPlugins) { | |
| 298 | + | Write-Host -NoNewline (" {0,-34} " -f $p.Id) | |
| 299 | + | $encoded = [uri]::EscapeDataString($p.Id) | |
| 300 | + | $url = "https://plugins.jetbrains.com/pluginManager/?action=download&id=$encoded&build=$buildId" | |
| 301 | + | $tmp = Join-Path $env:TEMP ("jb-" + [guid]::NewGuid().ToString() + ".bin") | |
| 302 | + | try { | |
| 303 | + | Invoke-WebRequest -Uri $url -OutFile $tmp -UserAgent $ua -ErrorAction Stop -MaximumRedirection 5 | |
| 304 | + | $bytes = [System.IO.File]::ReadAllBytes($tmp) | Select-Object -First 2 | |
| 305 | + | if ($bytes.Count -ge 2 -and $bytes[0] -eq 0x50 -and $bytes[1] -eq 0x4B) { | |
| 306 | + | Expand-Archive -Path $tmp -DestinationPath $pluginDir -Force -ErrorAction Stop | |
| 307 | + | Write-Host "ok (zip)" -ForegroundColor Green | |
| 308 | + | } else { | |
| 309 | + | $jarName = Join-Path $pluginDir ($p.Id -replace '[^A-Za-z0-9._-]', '_') + '.jar' | |
| 310 | + | Move-Item -Path $tmp -Destination $jarName -Force | |
| 311 | + | Write-Host "ok (jar)" -ForegroundColor Green | |
| 312 | + | } | |
| 313 | + | $installed++ | |
| 314 | + | } catch { | |
| 315 | + | Write-Host "FAILED" -ForegroundColor Red | |
| 316 | + | Write-Host (" {0}" -f $_.Exception.Message) -ForegroundColor DarkRed | |
| 317 | + | $failed++ | |
| 318 | + | $failedList += ("{0} :: {1}" -f $ide.Label, $p.Id) | |
| 319 | + | } finally { | |
| 320 | + | if (Test-Path $tmp) { Remove-Item $tmp -Force -ErrorAction SilentlyContinue } | |
| 321 | + | } | |
| 322 | + | } | |
| 323 | + | Write-Host " Reopen the Gateway Client to load the new plugins." -ForegroundColor Yellow | |
| 324 | + | } | |
| 325 | + | } | |
| 326 | + | Write-Host "" | |
| 327 | + | } | |
| 328 | + | ||
| 329 | + | Write-Host ("Done. Installed: {0}, Failed: {1}" -f $installed, $failed) | |
| 330 | + | if ($failed -gt 0) { | |
| 331 | + | Write-Host "Failed:" -ForegroundColor Red | |
| 332 | + | foreach ($f in $failedList) { Write-Host (" - {0}" -f $f) -ForegroundColor Red } | |
| 333 | + | exit 1 | |
| 334 | + | } | |
install-jetbrains.sh(файл создан)
| @@ -0,0 +1,438 @@ | |||
| 1 | + | #!/usr/bin/env bash | |
| 2 | + | # | |
| 3 | + | # JetBrains IDE plugin installer for macOS, Linux (Ubuntu Desktop) and WSL2. | |
| 4 | + | # | |
| 5 | + | # Detects every locally installed JetBrains IDE (Toolbox + standalone) and, | |
| 6 | + | # on Linux, any JetBrains Gateway remote-dev-server.sh distributions. Lets | |
| 7 | + | # you multi-select IDE(s) and plugin(s) interactively, then installs each | |
| 8 | + | # plugin into each selected IDE. | |
| 9 | + | # | |
| 10 | + | # Picker UX: | |
| 11 | + | # - Uses fzf with --multi when fzf is on PATH (TAB to toggle, Enter to confirm). | |
| 12 | + | # - Otherwise prints a numbered list and accepts comma-separated indexes | |
| 13 | + | # (e.g. "1,3,5") or "a" for all. | |
| 14 | + | # | |
| 15 | + | # Usage: | |
| 16 | + | # ./install-jetbrains.sh # interactive | |
| 17 | + | # ./install-jetbrains.sh --help | |
| 18 | + | ||
| 19 | + | set -euo pipefail | |
| 20 | + | IFS=$'\n\t' | |
| 21 | + | ||
| 22 | + | # ------------------------------------------------------------------ | |
| 23 | + | # Canonical marketplace plugin list (xmlId | display label). | |
| 24 | + | # Both columns separated by a literal pipe; the label is for the picker. | |
| 25 | + | # ------------------------------------------------------------------ | |
| 26 | + | readonly PLUGIN_CATALOG=( | |
| 27 | + | "com.intellij.ml.llm|JetBrains AI Assistant" | |
| 28 | + | "izhangzhihao.rainbow.brackets|Rainbow Brackets" | |
| 29 | + | "IdeaVIM|IdeaVim" | |
| 30 | + | "ru.adelf.idea.dotenv|.env files" | |
| 31 | + | "org.sonarlint.idea|SonarQube for IDE (SonarLint)" | |
| 32 | + | "org.intellij.qodana|Qodana" | |
| 33 | + | "Key Promoter X|Key Promoter X" | |
| 34 | + | "com.crunch42.openapi|OpenAPI (Swagger) Editor" | |
| 35 | + | "net.ashald.envfile|EnvFile" | |
| 36 | + | "software.xdev.saveactions|Save Actions X" | |
| 37 | + | ) | |
| 38 | + | ||
| 39 | + | # Known JetBrains IDE launcher basenames (Toolbox shims + Linux .sh names). | |
| 40 | + | readonly KNOWN_IDE_NAMES=( | |
| 41 | + | idea idea.sh | |
| 42 | + | pycharm pycharm.sh | |
| 43 | + | webstorm webstorm.sh | |
| 44 | + | goland goland.sh | |
| 45 | + | rubymine rubymine.sh | |
| 46 | + | clion clion.sh | |
| 47 | + | phpstorm phpstorm.sh | |
| 48 | + | datagrip datagrip.sh | |
| 49 | + | rustrover rustrover.sh | |
| 50 | + | rider rider.sh | |
| 51 | + | studio studio.sh | |
| 52 | + | fleet | |
| 53 | + | ) | |
| 54 | + | ||
| 55 | + | usage() { | |
| 56 | + | cat <<EOF | |
| 57 | + | Usage: $(basename "$0") | |
| 58 | + | ||
| 59 | + | Interactive JetBrains IDE plugin installer for macOS, Linux and WSL2. | |
| 60 | + | ||
| 61 | + | The script: | |
| 62 | + | 1. Detects your OS (macOS / Linux / WSL2) | |
| 63 | + | 2. Scans for installed JetBrains IDEs (Toolbox + standalone) and any | |
| 64 | + | local remote-dev-server.sh distributions | |
| 65 | + | 3. Lets you multi-select IDE(s) and plugin(s) | |
| 66 | + | 4. Runs each launcher's "installPlugins" command for every selected plugin | |
| 67 | + | ||
| 68 | + | Use \`brew install fzf\` (or your distro's package manager) for a nicer picker. | |
| 69 | + | EOF | |
| 70 | + | } | |
| 71 | + | ||
| 72 | + | while [[ $# -gt 0 ]]; do | |
| 73 | + | case "$1" in | |
| 74 | + | -h|--help) usage; exit 0 ;; | |
| 75 | + | *) echo "Error: unknown argument '$1'." >&2; usage >&2; exit 2 ;; | |
| 76 | + | esac | |
| 77 | + | done | |
| 78 | + | ||
| 79 | + | # ------------------------------------------------------------------ | |
| 80 | + | # OS detection | |
| 81 | + | # ------------------------------------------------------------------ | |
| 82 | + | OS_KIND="" | |
| 83 | + | case "$(uname -s)" in | |
| 84 | + | Darwin) OS_KIND=macos ;; | |
| 85 | + | Linux) | |
| 86 | + | if grep -qi microsoft /proc/version 2>/dev/null; then | |
| 87 | + | OS_KIND=wsl2 | |
| 88 | + | else | |
| 89 | + | OS_KIND=linux | |
| 90 | + | fi | |
| 91 | + | ;; | |
| 92 | + | *) | |
| 93 | + | echo "Error: unsupported OS '$(uname -s)'. Use install-jetbrains.ps1 on Windows." >&2 | |
| 94 | + | exit 1 | |
| 95 | + | ;; | |
| 96 | + | esac | |
| 97 | + | ||
| 98 | + | # ------------------------------------------------------------------ | |
| 99 | + | # Helpers | |
| 100 | + | # ------------------------------------------------------------------ | |
| 101 | + | ||
| 102 | + | # is_known_ide_name <basename> -> 0 if it matches a known JetBrains launcher. | |
| 103 | + | is_known_ide_name() { | |
| 104 | + | local n="$1" | |
| 105 | + | local k | |
| 106 | + | for k in "${KNOWN_IDE_NAMES[@]}"; do | |
| 107 | + | [[ "$n" == "$k" ]] && return 0 | |
| 108 | + | done | |
| 109 | + | return 1 | |
| 110 | + | } | |
| 111 | + | ||
| 112 | + | # pretty_from_path /path/to/idea.sh -> "IntelliJ IDEA (~/.local/share/.../idea.sh)" | |
| 113 | + | # Builds a short, readable label for the picker. | |
| 114 | + | pretty_label() { | |
| 115 | + | local path="$1" base | |
| 116 | + | base="$(basename "$path")" | |
| 117 | + | base="${base%.sh}" | |
| 118 | + | base="${base%.cmd}" | |
| 119 | + | base="${base%.exe}" | |
| 120 | + | case "$base" in | |
| 121 | + | idea|idea64) printf 'IntelliJ IDEA' ;; | |
| 122 | + | pycharm|pycharm64) printf 'PyCharm' ;; | |
| 123 | + | webstorm|webstorm64) printf 'WebStorm' ;; | |
| 124 | + | goland|goland64) printf 'GoLand' ;; | |
| 125 | + | rubymine|rubymine64) printf 'RubyMine' ;; | |
| 126 | + | clion|clion64) printf 'CLion' ;; | |
| 127 | + | phpstorm|phpstorm64) printf 'PhpStorm' ;; | |
| 128 | + | datagrip|datagrip64) printf 'DataGrip' ;; | |
| 129 | + | rustrover|rustrover64) printf 'RustRover' ;; | |
| 130 | + | rider|rider64) printf 'Rider' ;; | |
| 131 | + | studio|studio64) printf 'Android Studio' ;; | |
| 132 | + | fleet) printf 'Fleet' ;; | |
| 133 | + | *) printf '%s' "$base" ;; | |
| 134 | + | esac | |
| 135 | + | printf ' (%s)' "$path" | |
| 136 | + | } | |
| 137 | + | ||
| 138 | + | # Translate a Linux/WSL home into the Windows-side user dir under /mnt/c. | |
| 139 | + | win_user_dir() { | |
| 140 | + | local winuser | |
| 141 | + | winuser="$(cmd.exe /c 'echo %USERNAME%' 2>/dev/null | tr -d '\r\n' || true)" | |
| 142 | + | [[ -z "$winuser" ]] && winuser="$USER" | |
| 143 | + | printf '/mnt/c/Users/%s' "$winuser" | |
| 144 | + | } | |
| 145 | + | ||
| 146 | + | # ------------------------------------------------------------------ | |
| 147 | + | # IDE candidate scan | |
| 148 | + | # Each candidate is recorded as a single line: | |
| 149 | + | # <type>|<launcher>|<label> | |
| 150 | + | # where <type> is one of: | |
| 151 | + | # ide -> run "<launcher> installPlugins <id>..." | |
| 152 | + | # wincmd -> run "cmd.exe /c <launcher> installPlugins <id>..." | |
| 153 | + | # remotedev -> remote-dev-server.sh installPlugins ... | |
| 154 | + | # ------------------------------------------------------------------ | |
| 155 | + | declare -a CANDIDATES=() | |
| 156 | + | ||
| 157 | + | add_candidate() { | |
| 158 | + | local type="$1" launcher="$2" | |
| 159 | + | CANDIDATES+=("${type}|${launcher}|$(pretty_label "$launcher")") | |
| 160 | + | } | |
| 161 | + | ||
| 162 | + | scan_macos() { | |
| 163 | + | local d="$HOME/Library/Application Support/JetBrains/Toolbox/scripts" | |
| 164 | + | if [[ -d "$d" ]]; then | |
| 165 | + | local f | |
| 166 | + | while IFS= read -r -d '' f; do | |
| 167 | + | local base | |
| 168 | + | base="$(basename "$f")" | |
| 169 | + | is_known_ide_name "$base" || continue | |
| 170 | + | [[ -x "$f" ]] && add_candidate ide "$f" | |
| 171 | + | done < <(find "$d" -maxdepth 1 -type f -print0 2>/dev/null) | |
| 172 | + | fi | |
| 173 | + | ||
| 174 | + | local app bin | |
| 175 | + | for app in /Applications/*.app; do | |
| 176 | + | [[ -d "$app/Contents/MacOS" ]] || continue | |
| 177 | + | [[ "$(basename "$app")" =~ ^(IntelliJ|PyCharm|WebStorm|GoLand|RubyMine|CLion|PhpStorm|DataGrip|RustRover|Rider|Android\ Studio|Fleet) ]] || continue | |
| 178 | + | for bin in "$app"/Contents/MacOS/*; do | |
| 179 | + | [[ -x "$bin" ]] || continue | |
| 180 | + | local b | |
| 181 | + | b="$(basename "$bin")" | |
| 182 | + | is_known_ide_name "$b" || continue | |
| 183 | + | add_candidate ide "$bin" | |
| 184 | + | done | |
| 185 | + | done | |
| 186 | + | } | |
| 187 | + | ||
| 188 | + | scan_linux() { | |
| 189 | + | local apps="$HOME/.local/share/JetBrains/Toolbox/apps" | |
| 190 | + | if [[ -d "$apps" ]]; then | |
| 191 | + | local f | |
| 192 | + | while IFS= read -r -d '' f; do | |
| 193 | + | local b | |
| 194 | + | b="$(basename "$f")" | |
| 195 | + | is_known_ide_name "$b" || continue | |
| 196 | + | [[ -x "$f" ]] && add_candidate ide "$f" | |
| 197 | + | done < <(find "$apps" -maxdepth 3 -type f -name '*.sh' -print0 2>/dev/null) | |
| 198 | + | fi | |
| 199 | + | ||
| 200 | + | # /opt/<ide>/bin/<ide>.sh — common for .deb / snap / tarball installs. | |
| 201 | + | local opt | |
| 202 | + | for opt in /opt/*/bin/*.sh; do | |
| 203 | + | [[ -x "$opt" ]] || continue | |
| 204 | + | local b | |
| 205 | + | b="$(basename "$opt")" | |
| 206 | + | is_known_ide_name "$b" || continue | |
| 207 | + | add_candidate ide "$opt" | |
| 208 | + | done | |
| 209 | + | ||
| 210 | + | # Remote-dev-server distributions (Orbstack/Gateway). | |
| 211 | + | local rd | |
| 212 | + | while IFS= read -r -d '' rd; do | |
| 213 | + | add_candidate remotedev "$rd" | |
| 214 | + | done < <(find "$HOME/.cache/JetBrains/RemoteDev/dist" -maxdepth 3 -type f -name 'remote-dev-server.sh' -print0 2>/dev/null) | |
| 215 | + | } | |
| 216 | + | ||
| 217 | + | scan_wsl2() { | |
| 218 | + | local winhome | |
| 219 | + | winhome="$(win_user_dir)" | |
| 220 | + | [[ -d "$winhome" ]] || return 0 | |
| 221 | + | ||
| 222 | + | # Standalone installs under AppData\Local\Programs\<IDE>\bin\*.exe | |
| 223 | + | local exe b | |
| 224 | + | while IFS= read -r -d '' exe; do | |
| 225 | + | b="$(basename "$exe")" | |
| 226 | + | case "$b" in | |
| 227 | + | idea64.exe|pycharm64.exe|webstorm64.exe|goland64.exe|rubymine64.exe|\ | |
| 228 | + | clion64.exe|phpstorm64.exe|datagrip64.exe|rustrover64.exe|rider64.exe|\ | |
| 229 | + | studio64.exe|fleet.exe) | |
| 230 | + | add_candidate ide "$exe" | |
| 231 | + | ;; | |
| 232 | + | esac | |
| 233 | + | done < <(find "$winhome/AppData/Local/Programs" -maxdepth 4 -type f -name '*.exe' -print0 2>/dev/null) | |
| 234 | + | ||
| 235 | + | # Toolbox shell shims (.cmd) — invoke via cmd.exe. | |
| 236 | + | local scripts="$winhome/AppData/Local/JetBrains/Toolbox/scripts" | |
| 237 | + | if [[ -d "$scripts" ]]; then | |
| 238 | + | local f | |
| 239 | + | while IFS= read -r -d '' f; do | |
| 240 | + | b="$(basename "${f%.cmd}")" | |
| 241 | + | is_known_ide_name "$b" || continue | |
| 242 | + | add_candidate wincmd "$f" | |
| 243 | + | done < <(find "$scripts" -maxdepth 1 -type f -name '*.cmd' -print0 2>/dev/null) | |
| 244 | + | fi | |
| 245 | + | ||
| 246 | + | # Local Linux-side remote-dev-server.sh (still useful inside WSL2). | |
| 247 | + | local rd | |
| 248 | + | while IFS= read -r -d '' rd; do | |
| 249 | + | add_candidate remotedev "$rd" | |
| 250 | + | done < <(find "$HOME/.cache/JetBrains/RemoteDev/dist" -maxdepth 3 -type f -name 'remote-dev-server.sh' -print0 2>/dev/null) | |
| 251 | + | } | |
| 252 | + | ||
| 253 | + | case "$OS_KIND" in | |
| 254 | + | macos) scan_macos ;; | |
| 255 | + | linux) scan_linux ;; | |
| 256 | + | wsl2) scan_wsl2 ;; | |
| 257 | + | esac | |
| 258 | + | ||
| 259 | + | if [[ ${#CANDIDATES[@]} -eq 0 ]]; then | |
| 260 | + | echo "No JetBrains IDEs detected on this system." >&2 | |
| 261 | + | echo "Install one via the JetBrains Toolbox, then re-run." >&2 | |
| 262 | + | exit 1 | |
| 263 | + | fi | |
| 264 | + | ||
| 265 | + | # ------------------------------------------------------------------ | |
| 266 | + | # Multi-select picker | |
| 267 | + | # Reads newline-separated options on stdin, prints selected lines on stdout. | |
| 268 | + | # ------------------------------------------------------------------ | |
| 269 | + | pick_multi() { | |
| 270 | + | local prompt="$1" header="$2" | |
| 271 | + | if command -v fzf >/dev/null 2>&1; then | |
| 272 | + | fzf --multi --reverse --prompt="$prompt " --header="$header" | |
| 273 | + | return | |
| 274 | + | fi | |
| 275 | + | ||
| 276 | + | # Fallback: numbered list + comma-input. | |
| 277 | + | local -a items=() | |
| 278 | + | local line | |
| 279 | + | while IFS= read -r line; do items+=("$line"); done | |
| 280 | + | ||
| 281 | + | local total=${#items[@]} i | |
| 282 | + | while :; do | |
| 283 | + | { | |
| 284 | + | echo "$header" | |
| 285 | + | for (( i=0; i<total; i++ )); do | |
| 286 | + | printf ' %2d) %s\n' "$((i+1))" "${items[i]}" | |
| 287 | + | done | |
| 288 | + | printf '%s' "$prompt (e.g. 1,3,5 or a for all): " | |
| 289 | + | } >&2 | |
| 290 | + | ||
| 291 | + | local input | |
| 292 | + | if ! IFS= read -r input </dev/tty; then | |
| 293 | + | echo "no tty available; install fzf or run interactively" >&2 | |
| 294 | + | return 1 | |
| 295 | + | fi | |
| 296 | + | ||
| 297 | + | if [[ "$input" == "a" || "$input" == "A" ]]; then | |
| 298 | + | printf '%s\n' "${items[@]}" | |
| 299 | + | return | |
| 300 | + | fi | |
| 301 | + | [[ -z "$input" ]] && { echo " please enter at least one number, or 'a'." >&2; continue; } | |
| 302 | + | ||
| 303 | + | local -a picked=() bad=0 | |
| 304 | + | local tok | |
| 305 | + | IFS=',' read -r -a toks <<<"$input" | |
| 306 | + | for tok in "${toks[@]}"; do | |
| 307 | + | tok="${tok// /}" | |
| 308 | + | [[ -z "$tok" ]] && continue | |
| 309 | + | if [[ ! "$tok" =~ ^[0-9]+$ ]]; then bad=1; break; fi | |
| 310 | + | if (( tok < 1 || tok > total )); then bad=1; break; fi | |
| 311 | + | picked+=("${items[tok-1]}") | |
| 312 | + | done | |
| 313 | + | if (( bad )); then | |
| 314 | + | echo " invalid input; try again." >&2 | |
| 315 | + | continue | |
| 316 | + | fi | |
| 317 | + | if (( ${#picked[@]} == 0 )); then | |
| 318 | + | echo " no valid selections; try again." >&2 | |
| 319 | + | continue | |
| 320 | + | fi | |
| 321 | + | printf '%s\n' "${picked[@]}" | |
| 322 | + | return | |
| 323 | + | done | |
| 324 | + | } | |
| 325 | + | ||
| 326 | + | # ------------------------------------------------------------------ | |
| 327 | + | # IDE picker | |
| 328 | + | # ------------------------------------------------------------------ | |
| 329 | + | declare -a ide_lines=() | |
| 330 | + | for c in "${CANDIDATES[@]}"; do | |
| 331 | + | # human-readable picker line: just the third field | |
| 332 | + | ide_lines+=("${c#*|*|}") | |
| 333 | + | done | |
| 334 | + | ||
| 335 | + | # Map label -> (type, launcher) so we can recover after picking. | |
| 336 | + | declare -A label_type label_launcher | |
| 337 | + | for c in "${CANDIDATES[@]}"; do | |
| 338 | + | IFS='|' read -r t l lab <<<"$c" | |
| 339 | + | label_type[$lab]="$t" | |
| 340 | + | label_launcher[$lab]="$l" | |
| 341 | + | done | |
| 342 | + | ||
| 343 | + | echo "Detected ${#CANDIDATES[@]} JetBrains target(s)." | |
| 344 | + | mapfile -t SELECTED_IDES < <( | |
| 345 | + | printf '%s\n' "${ide_lines[@]}" | | |
| 346 | + | pick_multi "IDEs>" "Pick IDE(s) — TAB to toggle (fzf) or comma indexes" | |
| 347 | + | ) | |
| 348 | + | ||
| 349 | + | if (( ${#SELECTED_IDES[@]} == 0 )); then | |
| 350 | + | echo "No IDEs selected; exiting." >&2 | |
| 351 | + | exit 1 | |
| 352 | + | fi | |
| 353 | + | ||
| 354 | + | # ------------------------------------------------------------------ | |
| 355 | + | # Plugin picker | |
| 356 | + | # ------------------------------------------------------------------ | |
| 357 | + | declare -a plugin_lines=() | |
| 358 | + | declare -A label_to_id | |
| 359 | + | for entry in "${PLUGIN_CATALOG[@]}"; do | |
| 360 | + | IFS='|' read -r pid plabel <<<"$entry" | |
| 361 | + | line="$(printf '%-32s %s' "$plabel" "$pid")" | |
| 362 | + | plugin_lines+=("$line") | |
| 363 | + | label_to_id[$line]="$pid" | |
| 364 | + | done | |
| 365 | + | ||
| 366 | + | mapfile -t SELECTED_PLUGIN_LINES < <( | |
| 367 | + | printf '%s\n' "${plugin_lines[@]}" | | |
| 368 | + | pick_multi "Plugins>" "Pick plugin(s) — TAB to toggle (fzf) or comma indexes" | |
| 369 | + | ) | |
| 370 | + | ||
| 371 | + | if (( ${#SELECTED_PLUGIN_LINES[@]} == 0 )); then | |
| 372 | + | echo "No plugins selected; exiting." >&2 | |
| 373 | + | exit 1 | |
| 374 | + | fi | |
| 375 | + | ||
| 376 | + | declare -a SELECTED_PLUGIN_IDS=() | |
| 377 | + | for line in "${SELECTED_PLUGIN_LINES[@]}"; do | |
| 378 | + | SELECTED_PLUGIN_IDS+=("${label_to_id[$line]}") | |
| 379 | + | done | |
| 380 | + | ||
| 381 | + | # ------------------------------------------------------------------ | |
| 382 | + | # Install loop | |
| 383 | + | # ------------------------------------------------------------------ | |
| 384 | + | LOG_FILE="$(mktemp -t jb-install.XXXXXX)" | |
| 385 | + | trap 'rm -f "$LOG_FILE"' EXIT | |
| 386 | + | ||
| 387 | + | installed=0 | |
| 388 | + | failed=0 | |
| 389 | + | declare -a FAILED_LIST=() | |
| 390 | + | ||
| 391 | + | echo | |
| 392 | + | echo "Installing ${#SELECTED_PLUGIN_IDS[@]} plugin(s) into ${#SELECTED_IDES[@]} IDE(s)..." | |
| 393 | + | echo | |
| 394 | + | ||
| 395 | + | for ide_label in "${SELECTED_IDES[@]}"; do | |
| 396 | + | itype="${label_type[$ide_label]}" | |
| 397 | + | launcher="${label_launcher[$ide_label]}" | |
| 398 | + | ||
| 399 | + | echo ">>> $ide_label" | |
| 400 | + | for pid in "${SELECTED_PLUGIN_IDS[@]}"; do | |
| 401 | + | printf ' %-34s ' "$pid" | |
| 402 | + | case "$itype" in | |
| 403 | + | ide|remotedev) | |
| 404 | + | if "$launcher" installPlugins "$pid" >"$LOG_FILE" 2>&1; then | |
| 405 | + | echo "ok" | |
| 406 | + | installed=$((installed + 1)) | |
| 407 | + | else | |
| 408 | + | echo "FAILED" | |
| 409 | + | failed=$((failed + 1)) | |
| 410 | + | FAILED_LIST+=("${ide_label} :: ${pid}") | |
| 411 | + | sed 's/^/ /' "$LOG_FILE" >&2 || true | |
| 412 | + | fi | |
| 413 | + | ;; | |
| 414 | + | wincmd) | |
| 415 | + | if cmd.exe /c "$launcher" installPlugins "$pid" >"$LOG_FILE" 2>&1; then | |
| 416 | + | echo "ok" | |
| 417 | + | installed=$((installed + 1)) | |
| 418 | + | else | |
| 419 | + | echo "FAILED" | |
| 420 | + | failed=$((failed + 1)) | |
| 421 | + | FAILED_LIST+=("${ide_label} :: ${pid}") | |
| 422 | + | sed 's/^/ /' "$LOG_FILE" >&2 || true | |
| 423 | + | fi | |
| 424 | + | ;; | |
| 425 | + | esac | |
| 426 | + | done | |
| 427 | + | echo | |
| 428 | + | done | |
| 429 | + | ||
| 430 | + | echo "Done. Installed: ${installed}, Failed: ${failed}" | |
| 431 | + | ||
| 432 | + | if (( failed > 0 )); then | |
| 433 | + | echo "Failed:" >&2 | |
| 434 | + | for f in "${FAILED_LIST[@]}"; do | |
| 435 | + | echo " - $f" >&2 | |
| 436 | + | done | |
| 437 | + | exit 1 | |
| 438 | + | fi | |
weehong ревизий этого фрагмента 1 month ago. К ревизии
2 files changed, 226 insertions
install.ps1(файл создан)
| @@ -0,0 +1,105 @@ | |||
| 1 | + | <# | |
| 2 | + | .SYNOPSIS | |
| 3 | + | Install a curated list of VS Code extensions on Windows. | |
| 4 | + | ||
| 5 | + | .DESCRIPTION | |
| 6 | + | Installs the bundled extension list using the VS Code 'code' CLI. | |
| 7 | + | Sets the current process's ExecutionPolicy to RemoteSigned (no machine-wide | |
| 8 | + | change) so the script can run on a default Windows configuration. | |
| 9 | + | ||
| 10 | + | .PARAMETER ProfileName | |
| 11 | + | Optional VS Code profile name. If supplied, extensions are installed into | |
| 12 | + | that profile. VS Code creates the profile if it doesn't already exist. | |
| 13 | + | Quote names that contain spaces. | |
| 14 | + | ||
| 15 | + | .EXAMPLE | |
| 16 | + | PS> .\install.ps1 | |
| 17 | + | ||
| 18 | + | .EXAMPLE | |
| 19 | + | PS> .\install.ps1 -ProfileName "WorkSetup" | |
| 20 | + | ||
| 21 | + | .NOTES | |
| 22 | + | If PowerShell refuses to run the file, invoke it as: | |
| 23 | + | powershell -ExecutionPolicy Bypass -File .\install.ps1 | |
| 24 | + | #> | |
| 25 | + | ||
| 26 | + | [CmdletBinding()] | |
| 27 | + | param( | |
| 28 | + | [Parameter(Mandatory = $false, Position = 0)] | |
| 29 | + | [string]$ProfileName = "" | |
| 30 | + | ) | |
| 31 | + | ||
| 32 | + | # Loosen execution policy for THIS process only (no persisted change). | |
| 33 | + | try { | |
| 34 | + | $current = Get-ExecutionPolicy -Scope Process -ErrorAction Stop | |
| 35 | + | if ($current -in @('Restricted', 'AllSigned', 'Undefined')) { | |
| 36 | + | Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned -Force -ErrorAction Stop | |
| 37 | + | } | |
| 38 | + | } catch { | |
| 39 | + | Write-Verbose ("Could not adjust process execution policy: {0}" -f $_.Exception.Message) | |
| 40 | + | } | |
| 41 | + | ||
| 42 | + | $ErrorActionPreference = 'Stop' | |
| 43 | + | ||
| 44 | + | $Extensions = @( | |
| 45 | + | 'docker.docker', | |
| 46 | + | 'github.copilot-chat', | |
| 47 | + | 'github.github-vscode-theme', | |
| 48 | + | 'ms-vscode-remote.vscode-remote-extensionpack', | |
| 49 | + | 'pkief.material-icon-theme' | |
| 50 | + | ) | |
| 51 | + | ||
| 52 | + | $codeCmd = Get-Command code -ErrorAction SilentlyContinue | |
| 53 | + | if (-not $codeCmd) { | |
| 54 | + | Write-Host "" | |
| 55 | + | Write-Host "Error: the 'code' command is not on your PATH." -ForegroundColor Red | |
| 56 | + | Write-Host " Install VS Code from https://code.visualstudio.com/ and make sure" -ForegroundColor Yellow | |
| 57 | + | Write-Host ' "Add to PATH" was checked during setup, then re-open your terminal.' -ForegroundColor Yellow | |
| 58 | + | exit 1 | |
| 59 | + | } | |
| 60 | + | ||
| 61 | + | $extraArgs = @() | |
| 62 | + | if (-not [string]::IsNullOrWhiteSpace($ProfileName)) { | |
| 63 | + | $extraArgs += @('--profile', $ProfileName) | |
| 64 | + | } | |
| 65 | + | ||
| 66 | + | $header = "Installing $($Extensions.Count) extension(s)" | |
| 67 | + | if ($ProfileName) { $header += " into profile '$ProfileName'" } | |
| 68 | + | Write-Host ("{0}..." -f $header) -ForegroundColor Cyan | |
| 69 | + | Write-Host "" | |
| 70 | + | ||
| 71 | + | $installed = 0 | |
| 72 | + | $failed = 0 | |
| 73 | + | $failedList = @() | |
| 74 | + | ||
| 75 | + | foreach ($ext in $Extensions) { | |
| 76 | + | Write-Host -NoNewline (" -> {0,-55} " -f $ext) | |
| 77 | + | try { | |
| 78 | + | $output = & code @extraArgs --install-extension $ext --force 2>&1 | |
| 79 | + | if ($LASTEXITCODE -eq 0) { | |
| 80 | + | Write-Host "ok" -ForegroundColor Green | |
| 81 | + | $installed++ | |
| 82 | + | } else { | |
| 83 | + | Write-Host "FAILED" -ForegroundColor Red | |
| 84 | + | $failed++ | |
| 85 | + | $failedList += $ext | |
| 86 | + | $output | ForEach-Object { Write-Host " $_" -ForegroundColor DarkRed } | |
| 87 | + | } | |
| 88 | + | } catch { | |
| 89 | + | Write-Host "FAILED" -ForegroundColor Red | |
| 90 | + | $failed++ | |
| 91 | + | $failedList += $ext | |
| 92 | + | Write-Host (" {0}" -f $_.Exception.Message) -ForegroundColor DarkRed | |
| 93 | + | } | |
| 94 | + | } | |
| 95 | + | ||
| 96 | + | Write-Host "" | |
| 97 | + | Write-Host ("Done. Installed: {0}, Failed: {1}" -f $installed, $failed) | |
| 98 | + | ||
| 99 | + | if ($failed -gt 0) { | |
| 100 | + | Write-Host "Failed extensions:" -ForegroundColor Red | |
| 101 | + | foreach ($f in $failedList) { | |
| 102 | + | Write-Host (" - {0}" -f $f) -ForegroundColor Red | |
| 103 | + | } | |
| 104 | + | exit 1 | |
| 105 | + | } | |
install.sh(файл создан)
| @@ -0,0 +1,121 @@ | |||
| 1 | + | #!/usr/bin/env bash | |
| 2 | + | # | |
| 3 | + | # VS Code extension installer for macOS and Linux (Ubuntu). | |
| 4 | + | # | |
| 5 | + | # Usage: | |
| 6 | + | # ./install.sh # install into the default profile | |
| 7 | + | # ./install.sh --profile "MyProfile" # install into a named profile | |
| 8 | + | # ./install.sh --help | |
| 9 | + | # | |
| 10 | + | # If --profile is provided and the profile does not exist, VS Code creates | |
| 11 | + | # it automatically. Quote names that contain spaces. | |
| 12 | + | ||
| 13 | + | set -euo pipefail | |
| 14 | + | IFS=$'\n\t' | |
| 15 | + | ||
| 16 | + | readonly EXTENSIONS=( | |
| 17 | + | "docker.docker" | |
| 18 | + | "github.copilot-chat" | |
| 19 | + | "github.github-vscode-theme" | |
| 20 | + | "ms-vscode-remote.vscode-remote-extensionpack" | |
| 21 | + | "pkief.material-icon-theme" | |
| 22 | + | ) | |
| 23 | + | ||
| 24 | + | PROFILE="" | |
| 25 | + | ||
| 26 | + | usage() { | |
| 27 | + | cat <<EOF | |
| 28 | + | Usage: $(basename "$0") [--profile "ProfileName"] | |
| 29 | + | ||
| 30 | + | Installs a curated list of VS Code extensions on macOS or Linux. | |
| 31 | + | ||
| 32 | + | Options: | |
| 33 | + | -p, --profile <name> Install into the named VS Code profile. | |
| 34 | + | VS Code creates the profile if it doesn't exist. | |
| 35 | + | -h, --help Show this help and exit. | |
| 36 | + | EOF | |
| 37 | + | } | |
| 38 | + | ||
| 39 | + | while [[ $# -gt 0 ]]; do | |
| 40 | + | case "$1" in | |
| 41 | + | -p|--profile) | |
| 42 | + | if [[ $# -lt 2 || -z "${2:-}" ]]; then | |
| 43 | + | echo "Error: --profile requires a non-empty value." >&2 | |
| 44 | + | exit 2 | |
| 45 | + | fi | |
| 46 | + | PROFILE="$2" | |
| 47 | + | shift 2 | |
| 48 | + | ;; | |
| 49 | + | -h|--help) | |
| 50 | + | usage | |
| 51 | + | exit 0 | |
| 52 | + | ;; | |
| 53 | + | *) | |
| 54 | + | echo "Error: unknown argument '$1'." >&2 | |
| 55 | + | usage >&2 | |
| 56 | + | exit 2 | |
| 57 | + | ;; | |
| 58 | + | esac | |
| 59 | + | done | |
| 60 | + | ||
| 61 | + | OS="$(uname -s)" | |
| 62 | + | case "$OS" in | |
| 63 | + | Darwin|Linux) ;; | |
| 64 | + | *) | |
| 65 | + | echo "Error: unsupported OS '$OS'. Use install.ps1 on Windows." >&2 | |
| 66 | + | exit 1 | |
| 67 | + | ;; | |
| 68 | + | esac | |
| 69 | + | ||
| 70 | + | if ! command -v code >/dev/null 2>&1; then | |
| 71 | + | cat >&2 <<'EOF' | |
| 72 | + | Error: the 'code' command is not on your PATH. | |
| 73 | + | ||
| 74 | + | macOS: open VS Code, press Cmd+Shift+P, run | |
| 75 | + | "Shell Command: Install 'code' command in PATH". | |
| 76 | + | Linux: install VS Code from https://code.visualstudio.com/ | |
| 77 | + | (or: sudo snap install --classic code), then reopen your shell. | |
| 78 | + | EOF | |
| 79 | + | exit 1 | |
| 80 | + | fi | |
| 81 | + | ||
| 82 | + | declare -a CODE_ARGS=() | |
| 83 | + | if [[ -n "$PROFILE" ]]; then | |
| 84 | + | CODE_ARGS+=("--profile" "$PROFILE") | |
| 85 | + | fi | |
| 86 | + | ||
| 87 | + | LOG_FILE="$(mktemp -t vscode-install.XXXXXX)" | |
| 88 | + | trap 'rm -f "$LOG_FILE"' EXIT | |
| 89 | + | ||
| 90 | + | header="Installing ${#EXTENSIONS[@]} extension(s)" | |
| 91 | + | [[ -n "$PROFILE" ]] && header+=" into profile '$PROFILE'" | |
| 92 | + | echo "$header..." | |
| 93 | + | echo | |
| 94 | + | ||
| 95 | + | installed=0 | |
| 96 | + | failed=0 | |
| 97 | + | declare -a FAILED_LIST=() | |
| 98 | + | ||
| 99 | + | for ext in "${EXTENSIONS[@]}"; do | |
| 100 | + | printf ' -> %-55s ' "$ext" | |
| 101 | + | if code "${CODE_ARGS[@]}" --install-extension "$ext" --force >"$LOG_FILE" 2>&1; then | |
| 102 | + | echo "ok" | |
| 103 | + | installed=$((installed + 1)) | |
| 104 | + | else | |
| 105 | + | echo "FAILED" | |
| 106 | + | failed=$((failed + 1)) | |
| 107 | + | FAILED_LIST+=("$ext") | |
| 108 | + | sed 's/^/ /' "$LOG_FILE" >&2 || true | |
| 109 | + | fi | |
| 110 | + | done | |
| 111 | + | ||
| 112 | + | echo | |
| 113 | + | echo "Done. Installed: ${installed}, Failed: ${failed}" | |
| 114 | + | ||
| 115 | + | if (( failed > 0 )); then | |
| 116 | + | echo "Failed extensions:" >&2 | |
| 117 | + | for f in "${FAILED_LIST[@]}"; do | |
| 118 | + | echo " - $f" >&2 | |
| 119 | + | done | |
| 120 | + | exit 1 | |
| 121 | + | fi | |