Skip to content

vscode.sh: auto-install a theme's bundled .vsix VS Code extension - #6

Merged
OldJobobo merged 1 commit into
OldJobobo:thpmfrom
HANCORE-linux:vscode-vsix-autoinstall
Jun 24, 2026
Merged

vscode.sh: auto-install a theme's bundled .vsix VS Code extension#6
OldJobobo merged 1 commit into
OldJobobo:thpmfrom
HANCORE-linux:vscode-vsix-autoinstall

Conversation

@HANCORE-linux

Copy link
Copy Markdown

What

Extend the vscode.sh theme-set hook so that when a theme ships its own VS Code extension bundled as a .vsix, it is installed automatically on theme switch. No behaviour change for themes without a .vsix (Marketplace-id themes are untouched).

Why

A theme declares its VS Code theme via current/theme/vscode.json → Omarchy runs code --install-extension <id> and sets workbench.colorTheme. That works for Marketplace ids (e.g. catppuccin.catppuccin-vsc, mvllow.rose-pine). But a theme with a custom palette has no Marketplace match and must bundle its own local extension. Setting extension to a local id (local.theme-foo) makes code --install-extension local.theme-foo fail (not on the Marketplace), so the theme silently never loads. Dropping the extension folder into ~/.vscode/extensions/ isn't reliable either: VS Code only loads registered extensions, and a failed install leaves the id flagged in ~/.vscode/extensions/.obsolete. The only VS Code-native, portable install unit for a local extension is a real .vsix installed via code --install-extension <file>.vsix.

How

In the existing vscode.json branch, if the theme directory contains a *.vsix, install it. Idempotent + sha-aware: reinstall only when the extension is missing or the .vsix content changed (sha256 marker), so colour updates are picked up without a version bump and it doesn't reinstall on every switch. The theme directory is resolved from theme-env.sh's input_file (config-aware). Uses the success()/warning() helpers. code/jq are already guarded above.

Theme-author convention

Ship vscode-extension/{package.json, themes/<name>-color-theme.json}, a vscode.json ({"name": "...", "extension": "local.theme-<name>"}), and a prebuilt <name>.vsix at the theme root (so it lands in current/theme/ on apply). The .vsix needs no vsce/npm — it's a plain zip of extension.vsixmanifest + [Content_Types].xml + extension/....

Tests

  • Adds tests/vscode-vsix.test.sh — mocks code, covers: no-.vsix (no install), install once, skip when unchanged, reinstall on content change, reinstall when absent. 6/6.
  • tests/run.sh still passes (no regressions).

@OldJobobo

Copy link
Copy Markdown
Owner

Thanks for adding this. The .vsix install path makes sense, but I think this needs two changes before merging:

  1. Please handle missing .extension in vscode.json without reinstalling every run.

Right now:

ext="$(jq -r '.extension // empty' "$theme_dir/vscode.json")"
...
if [[ -z "$ext" ]] || ! code --list-extensions 2>/dev/null | grep -Fxq "$ext" || [[ "$(cat "$marker" 2>/dev/null)" != "$sha" ]]; then

If a theme ships a .vsix but its vscode.json does not include extension, ext stays empty and the hook runs code --install-extension "$vsix" --force on every theme switch, even when the sha marker matches.

Recommended fix: treat the extension id as required for the optimized reinstall behavior and skip/warn when it is missing, e.g.:

ext="$(jq -r '.extension // empty' "$theme_dir/vscode.json")"
if [[ -z "$ext" ]]; then
    warning "VS Code .vsix found but vscode.json has no extension id"
    exit 0
fi

Then the install condition can just check installed state and sha drift.

  1. Please wire the new test into the documented suite.

tests/vscode-vsix.test.sh passes when run directly, but bash tests/run.sh does not execute it, so the normal repo test command and CI do not cover this new behavior. This repo currently keeps the behavioral tests inside tests/run.sh, so I’d prefer folding these assertions into the existing VS Code test section there and adding the new test function to the main test list.

I verified the PR head with:

bash tests/run.sh
bash tests/vscode-vsix.test.sh

Both pass currently, but the standalone .vsix test is not part of the main harness yet.

…stall it

A custom-palette theme can't reference a Marketplace id, so it ships its VS Code
theme as a `vscode-extension/` source folder. `code --install-extension local.*`
fails (not on the Marketplace), so the theme silently never loads. Build a real
`.vsix` from that folder and install it; a prebuilt `*.vsix` in the theme dir is
still honoured as a fallback. Marketplace ids are left untouched (omarchy-theme-
set-vscode installs those, and also always sets `workbench.colorTheme`).

Addresses review:
- Require an `extension` id for a local theme (warn + skip) so the hook never
  reinstalls on every theme switch when the id is missing.
- De-dupe via installed-state + a sha marker over the SOURCE files (a zip embeds
  mtimes, so hashing the built .vsix would differ every run).
- Fold the behavioural assertions into tests/run.sh (build/install, idempotency,
  require-ext) and drop the standalone tests/vscode-vsix.test.sh.
@HANCORE-linux
HANCORE-linux force-pushed the vscode-vsix-autoinstall branch from ac20ec2 to 296820f Compare June 24, 2026 23:16
@HANCORE-linux

Copy link
Copy Markdown
Author

Thanks for the review — addressed both points, and reworked the approach a bit.

Build from source instead of a committed binary. The hook now builds the .vsix from the theme's vscode-extension/ source folder at apply time, rather than each theme committing a prebuilt binary. That removes the binary blob and the drift risk (edit the source → it just rebuilds). A prebuilt *.vsix in the theme dir is still honoured as a fallback, and Marketplace ids are left entirely to omarchy-theme-set-vscode. The local-vs-Marketplace discriminator is the presence of a vscode-extension/ folder.

1. Require an extension id. If a local theme is present (a vscode-extension/ folder or a *.vsix) but vscode.json has no extension, the hook now warns and skips instead of reinstalling on every switch. The reinstall condition is now not-installed || sha-drift (no empty-ext clause). The sha is taken over the source files — a zip embeds mtimes, so hashing the built .vsix would differ every run and defeat the marker.

2. Tests in tests/run.sh. Folded the assertions into the VS Code section there — test_vscode_plugin_builds_local_extension_from_source (build + install + idempotency) and test_vscode_plugin_requires_extension_id_for_local_theme — and registered both in the main list; removed the standalone tests/vscode-vsix.test.sh. bash tests/run.sh now covers the .vsix path (400 assertions, all green).

On install vs. load: this hook only installs (idempotently). Loadingworkbench.colorTheme — is set unconditionally by omarchy-theme-set-vscode, which runs before this hook, so an already-installed theme is still activated without a reinstall.

New dependency: zip (the build path skips gracefully and falls back to a prebuilt *.vsix if zip is absent).

@OldJobobo
OldJobobo merged commit be90b70 into OldJobobo:thpm Jun 24, 2026
1 check passed
@OldJobobo

Copy link
Copy Markdown
Owner

Merged, thank you.

The updated version resolves the two review points cleanly: local extensions now require an explicit extension id instead of falling into a reinstall loop, and the .vsix coverage is part of tests/run.sh.

I also appreciate the switch from committed .vsix binaries to building from vscode-extension/ source at apply time. That keeps theme repos easier to inspect and avoids binary drift while still supporting a prebuilt .vsix fallback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants