@vscode/webview-themes 0.0.0-stage → 0.0.2-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +303 -2
- package/ThirdPartyNotices.txt +28 -0
- package/catalog-sources.json +11 -0
- package/dist/extractor/cli.d.ts +2 -0
- package/dist/extractor/cli.js +49 -0
- package/dist/extractor/extract.d.ts +9 -0
- package/dist/extractor/extract.js +135 -0
- package/dist/extractor/probe.cjs +201 -0
- package/dist/extractor/probe.d.cts +1 -0
- package/dist/extractor/themeFiles.d.ts +3 -0
- package/dist/extractor/themeFiles.js +100 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/dist/schema.d.ts +60 -0
- package/dist/schema.js +108 -0
- package/dist/theme.d.ts +10 -0
- package/dist/theme.js +96 -0
- package/package.json +55 -5
- package/themes/catalog.json +25749 -0
- package/themes/github-github-vscode-theme-dark-colorblind.css +999 -0
- package/themes/github-github-vscode-theme-dark-colorblind.json +942 -0
- package/themes/github-github-vscode-theme-dark-default.css +999 -0
- package/themes/github-github-vscode-theme-dark-default.json +942 -0
- package/themes/github-github-vscode-theme-dark-dimmed.css +999 -0
- package/themes/github-github-vscode-theme-dark-dimmed.json +942 -0
- package/themes/github-github-vscode-theme-dark-high-contrast.css +999 -0
- package/themes/github-github-vscode-theme-dark-high-contrast.json +896 -0
- package/themes/github-github-vscode-theme-dark.css +999 -0
- package/themes/github-github-vscode-theme-dark.json +944 -0
- package/themes/github-github-vscode-theme-light-colorblind.css +999 -0
- package/themes/github-github-vscode-theme-light-colorblind.json +942 -0
- package/themes/github-github-vscode-theme-light-default.css +999 -0
- package/themes/github-github-vscode-theme-light-default.json +942 -0
- package/themes/github-github-vscode-theme-light-high-contrast.css +999 -0
- package/themes/github-github-vscode-theme-light-high-contrast.json +909 -0
- package/themes/github-github-vscode-theme-light.css +999 -0
- package/themes/github-github-vscode-theme-light.json +944 -0
- package/themes/index.json +397 -0
- package/themes/vscode-theme-abyss-abyss-color-theme.css +999 -0
- package/themes/vscode-theme-abyss-abyss-color-theme.json +907 -0
- package/themes/vscode-theme-defaults-2026-dark.css +999 -0
- package/themes/vscode-theme-defaults-2026-dark.json +961 -0
- package/themes/vscode-theme-defaults-2026-light.css +999 -0
- package/themes/vscode-theme-defaults-2026-light.json +961 -0
- package/themes/vscode-theme-defaults-dark-modern.css +999 -0
- package/themes/vscode-theme-defaults-dark-modern.json +941 -0
- package/themes/vscode-theme-defaults-dark-plus.css +999 -0
- package/themes/vscode-theme-defaults-dark-plus.json +912 -0
- package/themes/vscode-theme-defaults-dark-vs.css +999 -0
- package/themes/vscode-theme-defaults-dark-vs.json +912 -0
- package/themes/vscode-theme-defaults-hc-black.css +999 -0
- package/themes/vscode-theme-defaults-hc-black.json +805 -0
- package/themes/vscode-theme-defaults-hc-light.css +999 -0
- package/themes/vscode-theme-defaults-hc-light.json +839 -0
- package/themes/vscode-theme-defaults-light-modern.css +999 -0
- package/themes/vscode-theme-defaults-light-modern.json +943 -0
- package/themes/vscode-theme-defaults-light-plus.css +999 -0
- package/themes/vscode-theme-defaults-light-plus.json +917 -0
- package/themes/vscode-theme-defaults-light-vs.css +999 -0
- package/themes/vscode-theme-defaults-light-vs.json +917 -0
- package/themes/vscode-theme-kimbie-dark-kimbie-dark-color-theme.css +999 -0
- package/themes/vscode-theme-kimbie-dark-kimbie-dark-color-theme.json +908 -0
- package/themes/vscode-theme-monokai-dimmed-dimmed-monokai-color-theme.css +999 -0
- package/themes/vscode-theme-monokai-dimmed-dimmed-monokai-color-theme.json +907 -0
- package/themes/vscode-theme-monokai-monokai-color-theme.css +999 -0
- package/themes/vscode-theme-monokai-monokai-color-theme.json +909 -0
- package/themes/vscode-theme-quietlight-quietlight-color-theme.css +999 -0
- package/themes/vscode-theme-quietlight-quietlight-color-theme.json +911 -0
- package/themes/vscode-theme-red-red-color-theme.css +999 -0
- package/themes/vscode-theme-red-red-color-theme.json +908 -0
- package/themes/vscode-theme-solarized-dark-solarized-dark-color-theme.css +999 -0
- package/themes/vscode-theme-solarized-dark-solarized-dark-color-theme.json +909 -0
- package/themes/vscode-theme-solarized-light-solarized-light-color-theme.css +999 -0
- package/themes/vscode-theme-solarized-light-solarized-light-color-theme.json +911 -0
- package/themes/vscode-theme-tomorrow-night-blue-tomorrow-night-blue-color-theme.css +999 -0
- package/themes/vscode-theme-tomorrow-night-blue-tomorrow-night-blue-color-theme.json +907 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Microsoft Corporation.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,304 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @vscode/webview-themes
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Actual VS Code webview colors for Component Explorer fixtures. The browser
|
|
4
|
+
runtime has **no dependencies**, works with React 16 or newer, plain DOM,
|
|
5
|
+
Webpack and Vite, and does not require VS Code or network access.
|
|
6
|
+
|
|
7
|
+
The checked-in theme data includes all built-in VS Code color themes and all nine
|
|
8
|
+
variants of `GitHub.github-vscode-theme`, including both high-contrast variants.
|
|
9
|
+
These are **resolved snapshots**, not theme JSON files layered over a guessed
|
|
10
|
+
palette. Each snapshot includes the CSS variables VS Code actually injects,
|
|
11
|
+
including default/derived colors and font settings.
|
|
12
|
+
|
|
13
|
+
## Data layout
|
|
14
|
+
|
|
15
|
+
The package ships a small JSON index plus one JSON and one CSS file per theme:
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
@vscode/webview-themes/index.json
|
|
19
|
+
@vscode/webview-themes/themes/github-github-vscode-theme-dark-default.json
|
|
20
|
+
@vscode/webview-themes/themes/github-github-vscode-theme-dark-default.css
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The index contains `schemaVersion`, build provenance in `source`, and a `themes`
|
|
24
|
+
array. Each entry has `id`, `label`, `settingsId`, `extensionId`, `themePath`,
|
|
25
|
+
`light`, `highContrast`, `className`, `json`, and `css`. Asset paths are relative
|
|
26
|
+
to the physical index file in `themes/`. There are no palettes in the index:
|
|
27
|
+
a picker can list themes without loading every theme's variables.
|
|
28
|
+
|
|
29
|
+
Each theme JSON has this shape:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
interface Theme {
|
|
33
|
+
readonly id: string;
|
|
34
|
+
readonly label: string;
|
|
35
|
+
readonly settingsId?: string;
|
|
36
|
+
readonly light: boolean;
|
|
37
|
+
readonly highContrast: boolean;
|
|
38
|
+
readonly cssVariableValues: Readonly<Record<string, string>>;
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Generated themes always have `settingsId`, the original VS Code theme selector.
|
|
43
|
+
`id` is an extension-qualified slug derived from the extension ID and theme asset
|
|
44
|
+
path, not the display label. For example, `GitHub.github-vscode-theme` and
|
|
45
|
+
`themes/dark-default.json` produce `github-github-vscode-theme-dark-default`.
|
|
46
|
+
Renaming/localizing a label does not rename the files. Slug collisions fail
|
|
47
|
+
generation explicitly instead of overwriting another theme.
|
|
48
|
+
|
|
49
|
+
| Theme kind | `light` | `highContrast` |
|
|
50
|
+
| --- | --- | --- |
|
|
51
|
+
| Dark | false | false |
|
|
52
|
+
| Light | true | false |
|
|
53
|
+
| High-contrast dark | false | true |
|
|
54
|
+
| High-contrast light | true | true |
|
|
55
|
+
|
|
56
|
+
Variable keys include the full CSS name, such as `--vscode-editor-background`.
|
|
57
|
+
`getThemeKind(theme)` derives the corresponding VS Code class name from the two
|
|
58
|
+
booleans; a redundant kind string is not stored in the per-theme files.
|
|
59
|
+
|
|
60
|
+
## Fixtures with the JS controller
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { applyTheme, parseTheme } from '@vscode/webview-themes';
|
|
64
|
+
import themeData from '@vscode/webview-themes/themes/github-github-vscode-theme-dark-default.json';
|
|
65
|
+
|
|
66
|
+
// Inside a fixture render(container, context):
|
|
67
|
+
const controller = context.addDisposable(applyTheme(container, parseTheme(themeData)));
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Import `@vscode/webview-themes/index.json` to populate a picker. The application
|
|
71
|
+
chooses how to load selected JSON files (static imports, bundler dynamic imports,
|
|
72
|
+
or fetching assets it serves); the runtime does not fetch or eagerly import data.
|
|
73
|
+
`controller.setTheme(nextTheme)` switches without remounting the component.
|
|
74
|
+
Disposal restores the previous styles, classes and attributes. Overlapping
|
|
75
|
+
controllers on the same target are rejected.
|
|
76
|
+
|
|
77
|
+
The wrapper sets `--vscode-*`, `--text-link-decoration`, VS Code's theme classes,
|
|
78
|
+
and `data-vscode-theme-id`, `data-vscode-theme-name`, and `data-vscode-theme-kind`.
|
|
79
|
+
`data-vscode-theme-id` uses `settingsId` when present, preserving real webview
|
|
80
|
+
selectors; it falls back to `id` for caller-created themes.
|
|
81
|
+
High-contrast light receives both `vscode-high-contrast-light` and
|
|
82
|
+
`vscode-high-contrast`, matching VS Code. Absent properties are masked, rather
|
|
83
|
+
than inheriting a stale value from the previous theme or Explorer.
|
|
84
|
+
|
|
85
|
+
**Layout and product CSS remain yours.** There is no padding, reset stylesheet,
|
|
86
|
+
link styling or syntax-token palette. A typical fixture surface uses:
|
|
87
|
+
|
|
88
|
+
```css
|
|
89
|
+
.fixture-root {
|
|
90
|
+
background: var(--vscode-editor-background);
|
|
91
|
+
color: var(--vscode-editor-foreground);
|
|
92
|
+
font-family: var(--vscode-font-family);
|
|
93
|
+
font-size: var(--vscode-font-size);
|
|
94
|
+
font-weight: var(--vscode-font-weight);
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
For an isolated iframe with product selectors such as `body.vscode-dark`, use:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
import { applyThemeToDocument } from '@vscode/webview-themes';
|
|
102
|
+
|
|
103
|
+
context.addDisposable(applyThemeToDocument(container.ownerDocument, theme));
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
This applies variables to that document's root and metadata to its body, just
|
|
107
|
+
like VS Code. Only use it for a document owned by the fixture; it intentionally
|
|
108
|
+
themes the entire document. Use `applyTheme` for siblings or shadow-root content.
|
|
109
|
+
CSS selectors targeting `body` cannot be simulated by a subtree wrapper.
|
|
110
|
+
|
|
111
|
+
Component Explorer's `background: 'dark' | 'light'` controls its preview canvas,
|
|
112
|
+
not the component's theme. Choose that independently. Inputs can use Explorer's
|
|
113
|
+
`watchInput` to call `setTheme`; the runtime itself does not depend on Explorer
|
|
114
|
+
or Zod, create a picker, or persist a hidden localStorage preference.
|
|
115
|
+
|
|
116
|
+
## CSS-only use
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import '@vscode/webview-themes/themes/github-github-vscode-theme-dark-default.css';
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
```html
|
|
123
|
+
<div class="vscode-theme-github-github-vscode-theme-dark-default vscode-dark">
|
|
124
|
+
<!-- Component content -->
|
|
125
|
+
</div>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Use the exact `className` from the index rather than reconstructing it. Each CSS
|
|
129
|
+
file contains only one scoped variable rule: no layout, global selectors or
|
|
130
|
+
imports. CSS exports are marked as side effects so bundlers retain them.
|
|
131
|
+
|
|
132
|
+
For switching, remove the previous named theme class before adding the next one.
|
|
133
|
+
Also update the standard kind classes if product styles use them: `vscode-light`,
|
|
134
|
+
`vscode-dark`, `vscode-high-contrast`, or **both** `vscode-high-contrast-light` and
|
|
135
|
+
`vscode-high-contrast` for high-contrast light. CSS cannot set DOM classes or
|
|
136
|
+
metadata attributes; the JS controller handles those automatically and does not
|
|
137
|
+
need a theme CSS import.
|
|
138
|
+
|
|
139
|
+
Every generated CSS file resets catalog-known variables absent from its own
|
|
140
|
+
theme to `initial`, preventing inheritance from a different enclosing theme.
|
|
141
|
+
The JSON preserves those variables by omission. Unknown variables from extensions
|
|
142
|
+
not present during extraction are outside the CSS reset set.
|
|
143
|
+
|
|
144
|
+
## Extract from any VS Code build
|
|
145
|
+
|
|
146
|
+
```powershell
|
|
147
|
+
vscode-webview-themes --vscode 'C:\path\to\Code.exe' --out-dir .\themes
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
The executable must be the desktop Electron binary, not the `code` shell
|
|
151
|
+
wrapper. On macOS use the executable inside the application bundle.
|
|
152
|
+
On Linux run under a display, for example:
|
|
153
|
+
|
|
154
|
+
```sh
|
|
155
|
+
xvfb-run -a vscode-webview-themes --vscode /path/to/code --out-dir ./themes
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The CLI supports:
|
|
159
|
+
|
|
160
|
+
| Option | Meaning |
|
|
161
|
+
| --- | --- |
|
|
162
|
+
| `--vscode <executable>` | Required; caller chooses/downloads/builds VS Code |
|
|
163
|
+
| `--out-dir <directory>` | Generate index, per-theme JSON/CSS and compatibility catalog |
|
|
164
|
+
| `--out <file>` | Legacy aggregate output instead of `--out-dir` |
|
|
165
|
+
| `--extension <directory>` | Repeatable local extension inputs |
|
|
166
|
+
| `--theme <settings-id>` | Repeatable selection; defaults to all available themes |
|
|
167
|
+
| `--timeout <seconds>` | VS Code process deadline; defaults to 300, then up to 5 seconds to stop |
|
|
168
|
+
|
|
169
|
+
The extractor uses a new temporary user-data directory and extensions directory,
|
|
170
|
+
with automatic theme switching, updates and telemetry disabled. It does not
|
|
171
|
+
connect to your running VS Code, read your profile, or install extensions into
|
|
172
|
+
your normal extensions directory.
|
|
173
|
+
|
|
174
|
+
Additional extension inputs are copied into this temporary environment. Only
|
|
175
|
+
`contributes.themes` and `contributes.colors` survive in their manifests:
|
|
176
|
+
**their extension code and other contributions are not loaded**. Theme JSONC,
|
|
177
|
+
includes, defaults, and color references are resolved by VS Code itself.
|
|
178
|
+
Supply trusted local theme assets; this is not a sandbox for hostile files.
|
|
179
|
+
|
|
180
|
+
For a PR-extension fixture, pass the PR extension directory to register its
|
|
181
|
+
`pullRequests.*` colors as well as a GitHub Theme directory to add its themes.
|
|
182
|
+
Neither input directory is modified. Registered color contributions matter:
|
|
183
|
+
a theme's arbitrary color key is not exported by VS Code unless registered.
|
|
184
|
+
|
|
185
|
+
## Automated catalog refresh
|
|
186
|
+
|
|
187
|
+
From this project directory:
|
|
188
|
+
|
|
189
|
+
```sh
|
|
190
|
+
pnpm build
|
|
191
|
+
pnpm update-catalog
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The repository-only update script downloads the VS Code version pinned in
|
|
195
|
+
[catalog-sources.json](./catalog-sources.json), downloads/checksums the pinned
|
|
196
|
+
community VSIX packages, and invokes **the same extractor**.
|
|
197
|
+
Use `pnpm update-catalog --vscode <executable>` to supply a different build, or
|
|
198
|
+
`--out <directory>` to emit an artifact without replacing the committed data.
|
|
199
|
+
Adding a community theme requires pinning its version and SHA-256 and reviewing
|
|
200
|
+
its redistribution license; update [ThirdPartyNotices.txt](./ThirdPartyNotices.txt).
|
|
201
|
+
The extractor itself performs no downloads.
|
|
202
|
+
|
|
203
|
+
**All generated data is checked in under `themes/`.** The updater changes these
|
|
204
|
+
files directly; ordinary `pnpm build` only compiles TypeScript and never downloads
|
|
205
|
+
or refreshes data. JSON, CSS, index and compatibility output are generated from
|
|
206
|
+
one extraction. The index is written last. Removed themes' old JSON/CSS files are
|
|
207
|
+
removed using the previous index; unrelated files are preserved. Validation
|
|
208
|
+
failures occur before writing. Individual file replacements are atomic, but the
|
|
209
|
+
directory is not a transactional snapshot: don't serve it while an update runs.
|
|
210
|
+
|
|
211
|
+
Both automation hosts can use this:
|
|
212
|
+
|
|
213
|
+
- **Nightly engineering job:** obtain a VS Code build, run the updater or the
|
|
214
|
+
standalone CLI, and open a catalog-data PR if the output changed.
|
|
215
|
+
- **VS Code commit CI:** pass the job's packaged executable to the same CLI and
|
|
216
|
+
upload the generated directory as an artifact. No private workbench API or
|
|
217
|
+
source patch is needed. An update PR can consume that artifact later.
|
|
218
|
+
|
|
219
|
+
Scheduling, authentication, release selection, update PRs and npm publication
|
|
220
|
+
are deliberately outside the extractor. The package CI builds/tests and uploads
|
|
221
|
+
an npm tarball; it does not publish or configure a nightly job.
|
|
222
|
+
|
|
223
|
+
The output records its schema version, VS Code version and build commit,
|
|
224
|
+
platform, architecture, and contributing extension versions. Community archive
|
|
225
|
+
checksums are recorded in the shipped source configuration. Property and theme
|
|
226
|
+
ordering is deterministic; there are no timestamps, user paths or profile IDs.
|
|
227
|
+
The supplied packaged VS Code build must contain `product.json` with a commit.
|
|
228
|
+
Unstamped source-development launches are not accepted as reproducible builds.
|
|
229
|
+
|
|
230
|
+
Fonts are VS Code's defaults **for the recorded platform**. Refresh on the same
|
|
231
|
+
platform with the same build/extension inputs to obtain byte-identical results.
|
|
232
|
+
Switching the generating OS can legitimately change font variables.
|
|
233
|
+
The JSON snapshots preserve undefined colors by omission; the CSS uses `initial`
|
|
234
|
+
for these tokens, not invented transparent values. TextMate and semantic tokenization, file/product
|
|
235
|
+
icons, product stylesheets, and host messaging are not part of this package.
|
|
236
|
+
|
|
237
|
+
## Compatibility with the initial prototype
|
|
238
|
+
|
|
239
|
+
`@vscode/webview-themes/catalog.json`, `parseCatalog`, and `getTheme` remain as
|
|
240
|
+
deprecated compatibility exports for the already-linked PR Explorer. That
|
|
241
|
+
aggregate uses VS Code settings IDs and the old `kind`/`variables` shape.
|
|
242
|
+
`parseTheme` and the controller accept it as input. Parsed themes expose
|
|
243
|
+
non-enumerable, derived `kind` and `variables` accessors for existing callers;
|
|
244
|
+
new code should use `light`, `highContrast`, and `cssVariableValues`.
|
|
245
|
+
The compatibility catalog is generated, never maintained separately.
|
|
246
|
+
|
|
247
|
+
## Publishing
|
|
248
|
+
|
|
249
|
+
The existing Azure npm pipelines publish this package using their shared
|
|
250
|
+
`microsoft/vscode-engineering` release template and credentials:
|
|
251
|
+
|
|
252
|
+
- [Nightly](../.azure-pipelines/publish-nightly.yml): pushes to `main` publish
|
|
253
|
+
a prerelease under the `next` npm tag. `publishWebviewThemes` defaults to `true`.
|
|
254
|
+
- [Stable](../.azure-pipelines/publish-stable.yml): manually queue the pipeline
|
|
255
|
+
with `publishWebviewThemes: true` to publish under `latest`. Other package
|
|
256
|
+
publishing flags can remain disabled.
|
|
257
|
+
|
|
258
|
+
Both channels use the same [build steps](../.azure-pipelines/webview-themes/build-steps.yml)
|
|
259
|
+
to compile the package and run browser and checked-in asset tests before
|
|
260
|
+
packaging. Releases ship the checked-in snapshots; they do not run extraction
|
|
261
|
+
or update the catalog. Real VS Code extraction remains covered by GitHub Actions.
|
|
262
|
+
|
|
263
|
+
Use `npm install @vscode/webview-themes@next` after the first successful nightly
|
|
264
|
+
release. The unqualified package name uses `latest` after a stable release.
|
|
265
|
+
Merging the pipeline configuration alone does not confirm publication; verify
|
|
266
|
+
that the Azure publish stage succeeded and the version is available on npm.
|
|
267
|
+
|
|
268
|
+
## Development and validation
|
|
269
|
+
|
|
270
|
+
```sh
|
|
271
|
+
pnpm build
|
|
272
|
+
pnpm exec playwright install chromium
|
|
273
|
+
pnpm test
|
|
274
|
+
pnpm test:integration
|
|
275
|
+
pnpm pack
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Tests verify every checked-in file matches deterministic generation. Browser
|
|
279
|
+
tests verify real CSS inheritance, standalone CSS switching/nesting, the JS
|
|
280
|
+
controller, cleanup, compatibility and iframe placement.
|
|
281
|
+
Integration tests launch VS Code to check all four theme kinds,
|
|
282
|
+
JSONC/includes, contributed defaults, deterministic output, inactive extension
|
|
283
|
+
code, timeouts and failure preservation. They download the pinned build unless
|
|
284
|
+
`VSCODE_THEMES_EXECUTABLE` names an existing executable. Linux integration tests
|
|
285
|
+
need Xvfb. Both the integration tests and catalog updater create the download
|
|
286
|
+
cache and its parent directories automatically on a fresh checkout.
|
|
287
|
+
Linux CI configures the downloaded `chrome-sandbox` helper with root ownership
|
|
288
|
+
and mode `4755` on the ephemeral runner, keeping Electron sandboxing enabled.
|
|
289
|
+
Integration tests and catalog generation then share that configured executable.
|
|
290
|
+
There are no edits to consumer projects.
|
|
291
|
+
|
|
292
|
+
### Extraction design
|
|
293
|
+
|
|
294
|
+
Parsing JSON alone misses derived defaults. Importing `ColorThemeData` can reuse
|
|
295
|
+
the resolver, but collecting all product and extension registrations outside
|
|
296
|
+
the workbench requires maintaining an internal integration. An internal VS Code
|
|
297
|
+
export command could be useful later but would first need a product change.
|
|
298
|
+
|
|
299
|
+
The probe instead launches VS Code in extension-test mode, selects each theme,
|
|
300
|
+
and captures an otherwise empty webview's injected root properties and body
|
|
301
|
+
metadata. It waits for the exact requested theme ID and validates the response;
|
|
302
|
+
there are no fixed sleeps or light/dark-only change notifications. Mutation
|
|
303
|
+
observers run after VS Code finishes updating the theme metadata and variables.
|
|
304
|
+
The browser receives data only; Electron is an extraction-time requirement.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
The bundled catalog includes resolved color values from VS Code's built-in
|
|
2
|
+
themes (Microsoft Corporation, MIT; see LICENSE) and GitHub Theme 6.3.5:
|
|
3
|
+
https://github.com/primer/github-vscode-theme/tree/v6.3.5
|
|
4
|
+
|
|
5
|
+
GitHub Theme
|
|
6
|
+
-----------
|
|
7
|
+
|
|
8
|
+
MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2020 Primer
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"vscodeVersion": "1.140.0",
|
|
3
|
+
"extensions": [
|
|
4
|
+
{
|
|
5
|
+
"id": "GitHub.github-vscode-theme",
|
|
6
|
+
"version": "6.3.5",
|
|
7
|
+
"url": "https://marketplace.visualstudio.com/_apis/public/gallery/publishers/GitHub/vsextensions/github-vscode-theme/6.3.5/vspackage",
|
|
8
|
+
"sha256": "74e69da1804f718ae9ce6a8ea49c06fbf9cfc137c9b65b0e14353715cb5d474a"
|
|
9
|
+
}
|
|
10
|
+
]
|
|
11
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from 'node:util';
|
|
3
|
+
import { extract } from './extract.js';
|
|
4
|
+
try {
|
|
5
|
+
const { values } = parseArgs({
|
|
6
|
+
options: {
|
|
7
|
+
vscode: { type: 'string' },
|
|
8
|
+
out: { type: 'string' },
|
|
9
|
+
'out-dir': { type: 'string' },
|
|
10
|
+
extension: { type: 'string', multiple: true, default: [] },
|
|
11
|
+
theme: { type: 'string', multiple: true, default: [] },
|
|
12
|
+
timeout: { type: 'string', default: '300' },
|
|
13
|
+
help: { type: 'boolean', short: 'h' },
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
if (values.help) {
|
|
17
|
+
console.log(`Usage: vscode-webview-themes --vscode <executable> --out-dir <themes-directory>
|
|
18
|
+
--out-dir <directory> Write index.json and per-theme JSON/CSS files
|
|
19
|
+
--out <catalog.json> Legacy aggregate output (instead of --out-dir)
|
|
20
|
+
--extension <directory> Additional theme/color extension (repeatable; code is not loaded)
|
|
21
|
+
--theme <settings-id> Only extract these themes (repeatable; default: all)
|
|
22
|
+
--timeout <seconds> VS Code process timeout (default: 300)
|
|
23
|
+
|
|
24
|
+
Uses a fresh isolated profile. No downloads, publication, or changes to your VS Code profile.
|
|
25
|
+
Pass the Electron executable, not the code/code-insiders shell wrapper. On Linux, use xvfb-run.`);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
const output = values['out-dir'] ?? values.out;
|
|
29
|
+
if (!values.vscode || !output || (values.out && values['out-dir'])) {
|
|
30
|
+
throw new Error('--vscode and exactly one of --out-dir or --out are required; see --help');
|
|
31
|
+
}
|
|
32
|
+
const timeout = Number(values.timeout);
|
|
33
|
+
if (!Number.isFinite(timeout) || timeout <= 0) {
|
|
34
|
+
throw new Error('--timeout must be a positive number of seconds');
|
|
35
|
+
}
|
|
36
|
+
await extract({
|
|
37
|
+
executable: values.vscode,
|
|
38
|
+
output,
|
|
39
|
+
format: values['out-dir'] ? 'directory' : 'catalog',
|
|
40
|
+
extensions: values.extension,
|
|
41
|
+
themes: values.theme,
|
|
42
|
+
timeoutMs: timeout * 1000,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
console.error(error);
|
|
48
|
+
process.exitCode = 1;
|
|
49
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface ExtractOptions {
|
|
2
|
+
readonly executable: string;
|
|
3
|
+
readonly output: string;
|
|
4
|
+
readonly extensions: readonly string[];
|
|
5
|
+
readonly themes: readonly string[];
|
|
6
|
+
readonly timeoutMs: number;
|
|
7
|
+
readonly format?: 'catalog' | 'directory';
|
|
8
|
+
}
|
|
9
|
+
export declare function extract(options: ExtractOptions): Promise<void>;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { cp, mkdir, mkdtemp, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { object, parseCatalog, text } from '../schema.js';
|
|
7
|
+
import { writeThemeFiles } from './themeFiles.js';
|
|
8
|
+
export async function extract(options) {
|
|
9
|
+
const temporary = await mkdtemp(join(tmpdir(), 'vscode-webview-themes-'));
|
|
10
|
+
try {
|
|
11
|
+
const extensionsPath = join(temporary, 'extensions');
|
|
12
|
+
const userDataPath = join(temporary, 'user-data');
|
|
13
|
+
const probePath = join(temporary, 'probe');
|
|
14
|
+
await Promise.all([mkdir(extensionsPath), mkdir(join(userDataPath, 'User'), { recursive: true }), mkdir(probePath)]);
|
|
15
|
+
const ids = new Set();
|
|
16
|
+
for (const directory of options.extensions) {
|
|
17
|
+
const manifest = object(JSON.parse(await readFile(join(directory, 'package.json'), 'utf8')), 'extension manifest');
|
|
18
|
+
const publisher = text(manifest.publisher, 'publisher');
|
|
19
|
+
const name = text(manifest.name, 'name');
|
|
20
|
+
const id = `${publisher}.${name}`;
|
|
21
|
+
if (!/^[\w-]+\.[\w-]+$/.test(id) || ids.has(id.toLowerCase())) {
|
|
22
|
+
throw new Error(`Invalid or duplicate extension ID: ${id}`);
|
|
23
|
+
}
|
|
24
|
+
ids.add(id.toLowerCase());
|
|
25
|
+
const contributes = object(manifest.contributes, `${id}.contributes`);
|
|
26
|
+
if (!Array.isArray(contributes.themes) && !Array.isArray(contributes.colors)) {
|
|
27
|
+
throw new Error(`${id} has no theme or color contributions`);
|
|
28
|
+
}
|
|
29
|
+
const destination = join(extensionsPath, id);
|
|
30
|
+
await cp(resolve(directory), destination, {
|
|
31
|
+
recursive: true,
|
|
32
|
+
filter: source => !['.git', 'node_modules'].includes(basename(source)),
|
|
33
|
+
});
|
|
34
|
+
// Never run supplied extensions. Only VS Code's declarative theme/color loaders are needed.
|
|
35
|
+
await writeFile(join(destination, 'package.json'), JSON.stringify({
|
|
36
|
+
name, publisher,
|
|
37
|
+
version: text(manifest.version, `${id}.version`),
|
|
38
|
+
engines: manifest.engines,
|
|
39
|
+
contributes: { themes: contributes.themes, colors: contributes.colors },
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
await writeFile(join(probePath, 'package.json'), JSON.stringify({
|
|
43
|
+
name: 'webview-theme-probe', publisher: 'vscode-theme-extractor', version: '0.0.1',
|
|
44
|
+
engines: { vscode: '^1.100.0' },
|
|
45
|
+
}));
|
|
46
|
+
await writeFile(join(userDataPath, 'User', 'settings.json'), JSON.stringify({
|
|
47
|
+
'window.autoDetectColorScheme': false,
|
|
48
|
+
'window.autoDetectHighContrast': false,
|
|
49
|
+
'workbench.startupEditor': 'none',
|
|
50
|
+
'workbench.enableExperiments': false,
|
|
51
|
+
'telemetry.telemetryLevel': 'off',
|
|
52
|
+
'update.mode': 'none',
|
|
53
|
+
'extensions.autoUpdate': false,
|
|
54
|
+
'extensions.autoCheckUpdates': false,
|
|
55
|
+
'security.workspace.trust.enabled': false,
|
|
56
|
+
}));
|
|
57
|
+
const resultPath = join(temporary, 'catalog.json');
|
|
58
|
+
await launch(options.executable, [
|
|
59
|
+
'--user-data-dir', userDataPath,
|
|
60
|
+
'--extensions-dir', extensionsPath,
|
|
61
|
+
'--extensionDevelopmentPath', probePath,
|
|
62
|
+
'--extensionTestsPath', fileURLToPath(new URL('./probe.cjs', import.meta.url)),
|
|
63
|
+
'--skip-welcome', '--skip-release-notes', '--disable-updates', '--disable-telemetry',
|
|
64
|
+
'--disable-workspace-trust', '--new-window',
|
|
65
|
+
], {
|
|
66
|
+
VSCODE_PORTABLE: temporary,
|
|
67
|
+
WEBVIEW_THEME_REQUEST: JSON.stringify({ themes: options.themes, extensions: [...ids], output: resultPath }),
|
|
68
|
+
}, options.timeoutMs);
|
|
69
|
+
const catalog = parseCatalog(JSON.parse(await readFile(resultPath, 'utf8')));
|
|
70
|
+
const output = resolve(options.output);
|
|
71
|
+
if (options.format === 'directory') {
|
|
72
|
+
await writeThemeFiles(output, catalog);
|
|
73
|
+
console.log(`Extracted ${catalog.themes.length} JSON/CSS themes from VS Code ${catalog.source.vscodeVersion} to ${output}`);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
await mkdir(dirname(output), { recursive: true });
|
|
77
|
+
const staging = `${output}.tmp-${process.pid}`;
|
|
78
|
+
try {
|
|
79
|
+
await writeFile(staging, `${JSON.stringify(catalog, null, 2)}\n`);
|
|
80
|
+
await rename(staging, output);
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
await rm(staging, { force: true });
|
|
84
|
+
}
|
|
85
|
+
console.log(`Extracted ${catalog.themes.length} themes from VS Code ${catalog.source.vscodeVersion} to ${output}`);
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
await rm(temporary, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async function launch(executable, args, environment, timeoutMs) {
|
|
92
|
+
const env = { ...process.env, ...environment };
|
|
93
|
+
delete env.ELECTRON_RUN_AS_NODE;
|
|
94
|
+
delete env.VSCODE_IPC_HOOK_CLI;
|
|
95
|
+
delete env.VSCODE_APPDATA;
|
|
96
|
+
delete env.VSCODE_EXTENSIONS;
|
|
97
|
+
delete env.VSCODE_DEV;
|
|
98
|
+
await new Promise((resolve, reject) => {
|
|
99
|
+
const child = spawn(executable, args, { env, shell: false, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
100
|
+
let failure;
|
|
101
|
+
let killTimer;
|
|
102
|
+
const terminate = () => {
|
|
103
|
+
child.kill();
|
|
104
|
+
killTimer ??= setTimeout(() => child.kill('SIGKILL'), 5000);
|
|
105
|
+
};
|
|
106
|
+
const timer = setTimeout(() => {
|
|
107
|
+
failure = new Error(`VS Code theme extraction timed out after ${timeoutMs} ms`);
|
|
108
|
+
terminate();
|
|
109
|
+
}, timeoutMs);
|
|
110
|
+
const cancel = () => {
|
|
111
|
+
failure = new Error('VS Code theme extraction interrupted');
|
|
112
|
+
terminate();
|
|
113
|
+
};
|
|
114
|
+
process.once('SIGINT', cancel);
|
|
115
|
+
process.once('SIGTERM', cancel);
|
|
116
|
+
child.stdout.on('data', data => process.stdout.write(data));
|
|
117
|
+
child.stderr.on('data', data => process.stderr.write(data));
|
|
118
|
+
const finish = (error) => {
|
|
119
|
+
clearTimeout(timer);
|
|
120
|
+
clearTimeout(killTimer);
|
|
121
|
+
process.removeListener('SIGINT', cancel);
|
|
122
|
+
process.removeListener('SIGTERM', cancel);
|
|
123
|
+
child.stdout.destroy();
|
|
124
|
+
child.stderr.destroy();
|
|
125
|
+
if (error) {
|
|
126
|
+
reject(error);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
resolve();
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
child.once('error', error => finish(error));
|
|
133
|
+
child.once('exit', (code, signal) => finish(failure ?? (code === 0 ? undefined : new Error(`VS Code exited with ${code ?? signal}`))));
|
|
134
|
+
});
|
|
135
|
+
}
|