Последняя активность 6 days ago

Bundled installer scripts. VS Code: install.{sh,ps1}. JetBrains IDEs (incl. Gateway / remote-dev-server): install-jetbrains.{sh,ps1}. Each script detects your OS (Windows / macOS / Ubuntu / WSL2) and is interactive.

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
Новее Позже