Twins of the PowerShell scripts, needing only bash, curl and one of jq, node or python3 for JSON. install.sh downloads a release (or installs from dist/), settings.sh pushes, pulls or diffs User/, both resolve the code CLI and user folder per platform. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0169iPWwKHZoBTNN9qwXiwqk
191 lines
9.5 KiB
Markdown
191 lines
9.5 KiB
Markdown
# VS Code setup
|
|
|
|
Builds three local VS Code extensions, publishes them to a **Gitea release**, installs
|
|
them from there, and keeps user settings in git.
|
|
|
|
| Extension | What it does |
|
|
| --- | --- |
|
|
| `colored-references` | Find All References in a syntax-highlighted virtual document, plus a sortable results panel |
|
|
| `vertical-tabs` | A vertical list of open tabs, colour-coded by project, with pinning |
|
|
| `dotnet-solution-launcher` | Startup project and solution configuration (`Test \| x64`) in the status bar; build and debug that pass the platform DotRush drops |
|
|
|
|
## The short version
|
|
|
|
```powershell
|
|
# once per machine, on the machine that builds
|
|
$env:GITEA_TOKEN = '<token>'
|
|
.\scripts\publish.ps1 -Build # package all three, upload to the "latest" release
|
|
|
|
# on any machine, including a fresh one
|
|
.\scripts\install.ps1 # download from the release and install
|
|
.\scripts\settings.ps1 -Pull # apply settings, keybindings, marketplace extensions
|
|
|
|
# the same on Linux or macOS, no PowerShell needed
|
|
scripts/install.sh
|
|
scripts/settings.sh --pull
|
|
```
|
|
|
|
Set the server first — either in [`config.json`](config.json) or with `GITEA_URL`,
|
|
`GITEA_OWNER` and `GITEA_REPO`. The repo named there is where releases go; it does not
|
|
have to be this repo.
|
|
|
|
## Why not Settings Sync
|
|
|
|
**VS Code's built-in Settings Sync cannot be pointed at Gitea.** It signs in with a
|
|
Microsoft or GitHub account and talks to Microsoft's own sync service; there is no
|
|
setting for a different backend, self-hosted or otherwise. The old
|
|
Gist-based *Settings Sync* extension is out too, because Gitea has no gists.
|
|
|
|
So settings live in [`User/`](User/) as ordinary files, and
|
|
[`scripts/settings.ps1`](scripts/settings.ps1) copies them in either direction. That is
|
|
less magic than Settings Sync and more legible: a diff shows what changed.
|
|
|
|
It **copies rather than symlinks** on purpose. A symlink at
|
|
`Code/User/settings.json` is fragile — an editor that saves atomically (write
|
|
a temp file, rename it over the target) replaces the link with a regular file, and the
|
|
sync stops without anything looking broken.
|
|
|
|
Extensions from the marketplace are handled separately: `settings.ps1 -Push` records
|
|
them in `User/extensions.txt`, `-Pull` installs the missing ones. Ids starting `local.`
|
|
are skipped, because those are the three above and they come from the release.
|
|
|
|
## Scripts
|
|
|
|
| Script | Does |
|
|
| --- | --- |
|
|
| [`build.ps1`](scripts/build.ps1) | Packages each extension into `dist/*.vsix`. `-Clone` fetches missing sources from Gitea, `-Pull` updates them, `-Only <id>` narrows it |
|
|
| [`publish.ps1`](scripts/publish.ps1) | Creates the release if needed and uploads `dist/*.vsix`. `-Build` builds first, `-Tag` picks the tag |
|
|
| [`install.ps1`](scripts/install.ps1) | Downloads a release's assets and installs them. `-Local` installs from `dist/` instead, `-Uninstall` removes them |
|
|
| [`settings.ps1`](scripts/settings.ps1) | `-Push` machine to repo, `-Pull` repo to machine, `-Diff` compares |
|
|
| [`dev-link.ps1`](scripts/dev-link.ps1) | Junctions the sources into `~/.vscode/extensions` for development. `-Unlink` undoes it |
|
|
| [`ci-release.sh`](scripts/ci-release.sh) | The bash equivalent of `publish.ps1`, for the Gitea Actions runner. `SKIP_PACKAGE=1` uploads `dist/` without rebuilding |
|
|
| [`install.sh`](scripts/install.sh) | The bash equivalent of `install.ps1`: `--tag`, `--local`, `--only`, `--uninstall`, `--token` |
|
|
| [`settings.sh`](scripts/settings.sh) | The bash equivalent of `settings.ps1`: `--push`, `--pull`, `--diff`, `--no-extensions` |
|
|
|
|
Every PowerShell script takes `-?` for its full help, the bash ones `--help`.
|
|
|
|
### Tags
|
|
|
|
`publish.ps1` defaults to a rolling **`latest`** release: publishing again replaces the
|
|
assets in place rather than adding duplicates, so `install.ps1` with no arguments always
|
|
gets current builds. Use `-Tag v0.2.0` for a snapshot you want to keep, and
|
|
`install.ps1 -Tag v0.2.0` to go back to it.
|
|
|
|
The tag itself is created on the default branch the first time. For a release that only
|
|
carries binaries the commit it points at does not mean much, but it is why `latest`
|
|
keeps pointing at an old commit — the assets are what move.
|
|
|
|
## Tokens
|
|
|
|
Never in this repo. `publish.ps1` and `install.ps1` read, in order:
|
|
|
|
1. `-Token <value>`
|
|
2. `$env:GITEA_TOKEN`
|
|
3. `~/.gitea-token` (`%USERPROFILE%\.gitea-token` on Windows)
|
|
|
|
Create one at `<gitea>/user/settings/applications` with **`write:repository`** scope.
|
|
`install.ps1` only reads, so a `read:repository` token is enough there — and if the
|
|
release repo is public it needs no token at all for the download, though the API call
|
|
that finds the release still wants one on most instances.
|
|
|
|
## Building on Node 18
|
|
|
|
`vsce` needs **Node 20 or newer**. On Node 18 it fails with
|
|
`ReferenceError: File is not defined`, from a transitive `undici`, and pinning an older
|
|
`vsce` does not help because `undici` still resolves to a current version.
|
|
|
|
[`vsce/package.json`](vsce/package.json) works around it by pinning both, and
|
|
`build.ps1` installs into that folder on first run. If you upgrade Node to 20+ you can
|
|
delete `vsce/` and use `npx @vscode/vsce package` directly.
|
|
|
|
Upgrading Node is the better fix. The workaround exists so a machine stuck on 18 is not
|
|
blocked.
|
|
|
|
## Automating it
|
|
|
|
[`.gitea/workflows/release.yml`](.gitea/workflows/release.yml) builds and publishes on a
|
|
`v*` tag push, or on demand. It needs:
|
|
|
|
- a registered `act_runner` on the instance
|
|
- a `GITEA_TOKEN` secret with `write:repository`, and read access to the extension repos
|
|
- the `actions/checkout` and `actions/setup-node` actions reachable — Gitea pulls those
|
|
from github.com unless the instance sets `[actions] DEFAULT_ACTIONS_URL`
|
|
|
|
The runner uses Node 20, so CI sidesteps the problem above entirely.
|
|
|
|
`ci-release.sh` needs only bash, git, node and curl — JSON goes through `node`, not `jq`,
|
|
since node is required anyway and `jq` frequently is not.
|
|
|
|
## Why not `tea`
|
|
|
|
Gitea's CLI ([`tea`](https://gitea.com/gitea/tea)) can do this — `tea release create`
|
|
and `tea release assets create` cover the upload. It is not used here because these
|
|
scripts then need nothing but PowerShell and `code`: no Go binary to install per
|
|
machine, no `tea login add` step, no second place a token lives. Downloading assets on
|
|
the install side is also plainer over the API than through `tea`.
|
|
|
|
If you already have `tea` set up, `tea release create --tag latest --asset dist/*.vsix`
|
|
is a fine substitute for `publish.ps1`.
|
|
|
|
## Layout
|
|
|
|
```
|
|
config.json Gitea server + the extensions and where they live
|
|
User/ settings.json, keybindings.json, snippets/, extensions.txt
|
|
dist/ built .vsix files (ignored)
|
|
scripts/ the scripts above
|
|
vsce/ pinned vsce, for Node 18 (node_modules ignored)
|
|
.gitea/workflows/ the release workflow
|
|
```
|
|
|
|
`config.json` paths are relative to this repo, so it expects the extension folders as
|
|
siblings. Move them and it is one edit.
|
|
|
|
## Tests
|
|
|
|
```powershell
|
|
.\test\run.ps1
|
|
```
|
|
|
|
Runs the real scripts against [`test/fake-gitea.js`](test/fake-gitea.js), a stub of the
|
|
release endpoints, with a stub `code` on `PATH` so nothing is installed into the editor.
|
|
It checks the things that fail quietly:
|
|
|
|
- the `.vsix` survives the upload **byte for byte** — it is a zip, so any text-encoding
|
|
step in the multipart body would corrupt it and still look like a success
|
|
- the token reaches the API, and survives the redirect on the download
|
|
- republishing a tag replaces its assets instead of accumulating duplicates
|
|
- every install passes `--force`
|
|
- `build.ps1` and `install.ps1 -Local` work with no Gitea configured at all
|
|
- `ci-release.sh` publishes the same set as `publish.ps1`
|
|
|
|
Two bugs came out of writing it. `MultipartFormDataContent.Add(content, name, fileName)`
|
|
emits an unquoted `name=attachment` on .NET Framework, which Gitea's Go parser accepts
|
|
but nothing guarantees — the header is now set explicitly. And `Invoke-WebRequest` drops
|
|
the `Authorization` header when it follows a redirect, so downloading a private repo's
|
|
asset returned 401; `Save-GiteaAsset` follows redirects itself, re-sending the token only
|
|
while the host does not change.
|
|
|
|
What it does not cover: a real Gitea instance, and whether VS Code loads the packages
|
|
once installed. Both were checked by hand.
|
|
|
|
## Known limitations
|
|
|
|
- **No auto-update.** VS Code only offers updates for marketplace extensions, so a new
|
|
release does not notify you. Re-run `install.ps1`.
|
|
- **`--force` on every install**, because VS Code otherwise declines to reinstall a
|
|
version it already has, which would silently do nothing whenever you republish
|
|
without bumping the version.
|
|
- **A private extension gallery** — pointing VS Code's `product.json` at your own
|
|
gallery so updates work normally — is possible but gets overwritten by VS Code
|
|
updates. Not worth it for three extensions.
|
|
- **`dev-link.ps1` and `install.ps1` conflict.** A junctioned source and an installed
|
|
`.vsix` are two copies of the same extension id; VS Code loads one arbitrarily.
|
|
`dev-link.ps1 -Unlink` before installing.
|
|
- **Linux and macOS.** `install.sh` and `settings.sh` are bash twins of the PowerShell
|
|
scripts and need only bash, curl and one of jq, node or python3 for JSON. The `.ps1`
|
|
scripts also run there under PowerShell 7 (`pwsh`). Both find VS Code's user folder at `~/.config/Code/User` (`~/Library/Application Support/Code/User`
|
|
on macOS) and the CLI in the usual apt, snap, flatpak and Homebrew locations. Set
|
|
`VSCODE_USER_DIR` for Insiders or a portable install. `dev-link.ps1` makes a symlink
|
|
instead of a junction. `ci-release.sh` needs only bash.
|