codex-terminal-themes 1.0.0

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.
Files changed (229) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/CONTRIBUTING.md +23 -0
  3. package/LICENSE +21 -0
  4. package/LICENSE.txt +21 -0
  5. package/README.md +169 -0
  6. package/SECURITY.md +5 -0
  7. package/SUPPORT.md +5 -0
  8. package/bin/codex-terminal-themes.mjs +12 -0
  9. package/docs/app.js +1393 -0
  10. package/docs/assets/theme-gallery-header.png +0 -0
  11. package/docs/favicon.svg +10 -0
  12. package/docs/index.html +240 -0
  13. package/docs/preview.svg +24 -0
  14. package/docs/site-data.json +51078 -0
  15. package/docs/styles.css +780 -0
  16. package/metadata/README.md +71 -0
  17. package/metadata/themes.json +16185 -0
  18. package/metadata/themes.schema.json +218 -0
  19. package/package.json +109 -0
  20. package/src/cli.mjs +1763 -0
  21. package/stylelint.config.mjs +12 -0
  22. package/themes/80s.tmTheme +229 -0
  23. package/themes/Active4D.tmTheme +407 -0
  24. package/themes/All Hallow's Eve Custom.tmTheme +275 -0
  25. package/themes/All Hallow's Eve.tmTheme +277 -0
  26. package/themes/All Hallows Eve.tmTheme +277 -0
  27. package/themes/Amy.tmTheme +559 -0
  28. package/themes/Artic Fall.tmTheme +275 -0
  29. package/themes/BBEdit.tmTheme +439 -0
  30. package/themes/Bespin.tmTheme +516 -0
  31. package/themes/Black Pearl II.tmTheme +496 -0
  32. package/themes/Black Pearl.tmTheme +400 -0
  33. package/themes/Blackboard 2.tmTheme +348 -0
  34. package/themes/Blackboard Black.tmTheme +350 -0
  35. package/themes/Blackboard.tmTheme +348 -0
  36. package/themes/Blueberry Jelly.tmTheme +242 -0
  37. package/themes/Bluesy.tmTheme +242 -0
  38. package/themes/Blurb 2.tmTheme +229 -0
  39. package/themes/Blurb.tmTheme +229 -0
  40. package/themes/Bongzilla 2.tmTheme +223 -0
  41. package/themes/Bongzilla.tmTheme +223 -0
  42. package/themes/Brightly Dark.tmTheme +242 -0
  43. package/themes/Brilliance Black.tmTheme +2619 -0
  44. package/themes/Brilliance Dull.tmTheme +2243 -0
  45. package/themes/Bromozoid.tmTheme +227 -0
  46. package/themes/CSSEdit.tmTheme +203 -0
  47. package/themes/Classic Modified.tmTheme +469 -0
  48. package/themes/Clouds Midnight.tmTheme +361 -0
  49. package/themes/Clouds of Ruby.tmTheme +649 -0
  50. package/themes/Clouds.tmTheme +348 -0
  51. package/themes/Cloudy Fields.tmTheme +240 -0
  52. package/themes/Coal Graal.tmTheme +282 -0
  53. package/themes/Cobalt.tmTheme +561 -0
  54. package/themes/Coda.tmTheme +317 -0
  55. package/themes/Colorful.tmTheme +307 -0
  56. package/themes/Cool Glow.tmTheme +234 -0
  57. package/themes/Corona.tmTheme +290 -0
  58. package/themes/Cowabunga.tmTheme +242 -0
  59. package/themes/DanBurst.tmTheme +665 -0
  60. package/themes/Daniel Fischer.tmTheme +627 -0
  61. package/themes/Dark Ocean.tmTheme +242 -0
  62. package/themes/DarkNeon.tmTheme +818 -0
  63. package/themes/Dawn.tmTheme +437 -0
  64. package/themes/DawnCustom.tmTheme +443 -0
  65. package/themes/Django (Smoothy).tmTheme +453 -0
  66. package/themes/Django Blues.tmTheme +182 -0
  67. package/themes/Django Extended.tmTheme +495 -0
  68. package/themes/Django.tmTheme +436 -0
  69. package/themes/Dobdark.tmTheme +615 -0
  70. package/themes/Dominion Day.tmTheme +562 -0
  71. package/themes/Doo-Daa.tmTheme +242 -0
  72. package/themes/Drankin Purp.tmTheme +227 -0
  73. package/themes/Dreamweaver_Blackbam_Aptana303.tmTheme +980 -0
  74. package/themes/Easy on my Eyes There Buddy.tmTheme +227 -0
  75. package/themes/Eiffel.tmTheme +435 -0
  76. package/themes/Emacs Strict.tmTheme +241 -0
  77. package/themes/Emacs.tmTheme +241 -0
  78. package/themes/Epic Blue.tmTheme +320 -0
  79. package/themes/Erebus.tmTheme +467 -0
  80. package/themes/Espresso Libre.tmTheme +402 -0
  81. package/themes/Espresso Tutti.tmTheme +392 -0
  82. package/themes/Espresso.tmTheme +329 -0
  83. package/themes/Fade to Grey.tmTheme +308 -0
  84. package/themes/Fluidvision.tmTheme +443 -0
  85. package/themes/ForLaTeX.tmTheme +214 -0
  86. package/themes/Freckle.tmTheme +279 -0
  87. package/themes/Friendship Bracelet.tmTheme +303 -0
  88. package/themes/GaGaGaGroovy.tmTheme +227 -0
  89. package/themes/Gangrene.tmTheme +242 -0
  90. package/themes/GitHub.tmTheme +653 -0
  91. package/themes/GlitterBomb.tmTheme +387 -0
  92. package/themes/Halloween Night.tmTheme +303 -0
  93. package/themes/Happy happy joy joy.tmTheme +841 -0
  94. package/themes/Happydeluxe.tmTheme +184 -0
  95. package/themes/HelvectorLight.tmTheme +557 -0
  96. package/themes/Humane.tmTheme +220 -0
  97. package/themes/IDLE.tmTheme +235 -0
  98. package/themes/IR_Black.tmTheme +810 -0
  99. package/themes/IR_White.tmTheme +792 -0
  100. package/themes/Jane Fonda, Baby Redux.tmTheme +264 -0
  101. package/themes/Johnny.tmTheme +798 -0
  102. package/themes/Juicy.tmTheme +250 -0
  103. package/themes/Kuroir Theme.tmTheme +707 -0
  104. package/themes/LAZY.tmTheme +291 -0
  105. package/themes/Lowlight.tmTheme +605 -0
  106. package/themes/Mac Classic.tmTheme +476 -0
  107. package/themes/Made of Code.tmTheme +695 -0
  108. package/themes/MagicWB (Amiga).tmTheme +376 -0
  109. package/themes/Malibu Nights.tmTheme +257 -0
  110. package/themes/Menage A Trois.tmTheme +881 -0
  111. package/themes/Merbivore Soft.tmTheme +285 -0
  112. package/themes/Merbivore.tmTheme +285 -0
  113. package/themes/Midnight.tmTheme +321 -0
  114. package/themes/Mmm Sandy.tmTheme +227 -0
  115. package/themes/Monokai.tmTheme +289 -0
  116. package/themes/MultiMarkdown.tmTheme +183 -0
  117. package/themes/Mustang.tmTheme +339 -0
  118. package/themes/Neopro Inverted.tmTheme +328 -0
  119. package/themes/Neopro.tmTheme +330 -0
  120. package/themes/Nice One.tmTheme +222 -0
  121. package/themes/No Way.tmTheme +255 -0
  122. package/themes/Overcast.tmTheme +659 -0
  123. package/themes/Pastels on Dark.tmTheme +703 -0
  124. package/themes/Pastie.tmTheme +321 -0
  125. package/themes/Peridinkle.tmTheme +240 -0
  126. package/themes/Play!.tmTheme +736 -0
  127. package/themes/Puss.tmTheme +227 -0
  128. package/themes/Putty.tmTheme +275 -0
  129. package/themes/Quail.tmTheme +257 -0
  130. package/themes/RDark.tmTheme +235 -0
  131. package/themes/Rails Envy.tmTheme +299 -0
  132. package/themes/Railscasts 2.tmTheme +368 -0
  133. package/themes/Railscasts.tmTheme +278 -0
  134. package/themes/Resesif.tmTheme +298 -0
  135. package/themes/Ringo.tmTheme +240 -0
  136. package/themes/Ruby Blue.tmTheme +366 -0
  137. package/themes/RubyRobot.tmTheme +250 -0
  138. package/themes/Ryan Light.tmTheme +232 -0
  139. package/themes/Seafoam.tmTheme +242 -0
  140. package/themes/Sidewalk Chalk.tmTheme +276 -0
  141. package/themes/Sin City (that yellow bastard).tmTheme +585 -0
  142. package/themes/Slate.tmTheme +436 -0
  143. package/themes/Slush & Poppies.tmTheme +336 -0
  144. package/themes/Slush and Poppies.tmTheme +336 -0
  145. package/themes/Smokey Morning.tmTheme +229 -0
  146. package/themes/Smoothy original.tmTheme +623 -0
  147. package/themes/Smoothy.tmTheme +623 -0
  148. package/themes/Solarized (dark).tmTheme +2051 -0
  149. package/themes/Solarized-dark.tmTheme +312 -0
  150. package/themes/Solarized-light.tmTheme +305 -0
  151. package/themes/Sometheme.tmTheme +240 -0
  152. package/themes/SoylentTheme.tmTheme +353 -0
  153. package/themes/SpaceCadet.tmTheme +212 -0
  154. package/themes/Spectacular.tmTheme +436 -0
  155. package/themes/Starlight.tmTheme +857 -0
  156. package/themes/Stoneship Bright.tmTheme +348 -0
  157. package/themes/Stoneship.tmTheme +361 -0
  158. package/themes/Summer Camp Mod.tmTheme +229 -0
  159. package/themes/Summer Camp.tmTheme +229 -0
  160. package/themes/Summery Drink.tmTheme +242 -0
  161. package/themes/Sunburst.tmTheme +665 -0
  162. package/themes/Swyphs II.tmTheme +306 -0
  163. package/themes/Tango Bright.tmTheme +1 -0
  164. package/themes/Tango.tmTheme +450 -0
  165. package/themes/Teenage Dream.tmTheme +242 -0
  166. package/themes/Texari.tmTheme +727 -0
  167. package/themes/Text Ex Machina.tmTheme +295 -0
  168. package/themes/Tomorrow-Night-Blue.tmTheme +175 -0
  169. package/themes/Tomorrow-Night-Eighties.tmTheme +175 -0
  170. package/themes/Tomorrow-Night.tmTheme +175 -0
  171. package/themes/Tomorrow.tmTheme +349 -0
  172. package/themes/ToyChest.tmTheme +503 -0
  173. package/themes/TravisJeffery.tmTheme +1261 -0
  174. package/themes/Tubster.tmTheme +280 -0
  175. package/themes/Twilight BG FG.tmTheme +1000 -0
  176. package/themes/Twilight.tmTheme +516 -0
  177. package/themes/TwilightMod.tmTheme +529 -0
  178. package/themes/Two Days Ago.tmTheme +242 -0
  179. package/themes/Vibrant Fin.tmTheme +447 -0
  180. package/themes/Vibrant Ink.tmTheme +447 -0
  181. package/themes/Vibrant Scala.tmTheme +292 -0
  182. package/themes/Vibrant Tango.tmTheme +438 -0
  183. package/themes/Wandering.tmTheme +681 -0
  184. package/themes/Whimsy in Blue.tmTheme +240 -0
  185. package/themes/Why/342/200/231s Poignant.tmTheme" +191 -0
  186. package/themes/Windows XP.tmTheme +350 -0
  187. package/themes/Witch.tmTheme +282 -0
  188. package/themes/Yurple.tmTheme +227 -0
  189. package/themes/ZZZ.tmTheme +131 -0
  190. package/themes/Zachstronaut.tmTheme +381 -0
  191. package/themes/Zenburnesque.tmTheme +343 -0
  192. package/themes/[ Argonaut ].tmTheme +387 -0
  193. package/themes/barf.tmTheme +254 -0
  194. package/themes/converted-vscode-AmoledShinyBlack.tmTheme +528 -0
  195. package/themes/converted-vscode-AmoledShinyBlack2.tmTheme +609 -0
  196. package/themes/converted-vscode-AmoledShinyBlack3.tmTheme +683 -0
  197. package/themes/converted-vscode-AmoledShinyBlack4.tmTheme +896 -0
  198. package/themes/converted-vscode-AmoledShinyBlack5.tmTheme +1023 -0
  199. package/themes/converted-vscode-AmoledShinyBlack6.tmTheme +2092 -0
  200. package/themes/eclips3.media (ECLM).tmTheme +294 -0
  201. package/themes/evin.tmTheme +253 -0
  202. package/themes/fake.tmTheme +669 -0
  203. package/themes/fapfap.tmTheme +508 -0
  204. package/themes/helloKitty.tmTheme +293 -0
  205. package/themes/iLife 05.tmTheme +619 -0
  206. package/themes/iPlastic.tmTheme +397 -0
  207. package/themes/idleFingers.tmTheme +380 -0
  208. package/themes/krTheme.tmTheme +551 -0
  209. package/themes/mark.james.name.tmTheme +1117 -0
  210. package/themes/minimal Theme.tmTheme +551 -0
  211. package/themes/mint.tmTheme +617 -0
  212. package/themes/mintBlue Dark.tmTheme +653 -0
  213. package/themes/mintBlue.tmTheme +655 -0
  214. package/themes/modifiedPastels.tmTheme +745 -0
  215. package/themes/monoindustrial.tmTheme +451 -0
  216. package/themes/my-theme-blackboard.tmTheme +363 -0
  217. package/themes/my-theme-classic.tmTheme +465 -0
  218. package/themes/nppGLua.tmTheme +293 -0
  219. package/themes/reST testing theme.tmTheme +554 -0
  220. package/themes/rose-pine-dawn.tmTheme +329 -0
  221. package/themes/rose-pine-moon.tmTheme +329 -0
  222. package/themes/rose-pine.tmTheme +329 -0
  223. package/themes/ryan-light.tmTheme +232 -0
  224. package/themes/wut.tmTheme +255 -0
  225. package/tools/build-pages-site.mjs +465 -0
  226. package/tools/generate-theme-metadata.mjs +680 -0
  227. package/tools/serve-pages-site.mjs +196 -0
  228. package/tools/validate-themes.mjs +272 -0
  229. package/types/index.d.ts +49 -0
@@ -0,0 +1,255 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>name</key>
6
+ <string>Wut</string>
7
+ <key>settings</key>
8
+ <array>
9
+ <dict>
10
+ <key>settings</key>
11
+ <dict>
12
+ <key>background</key>
13
+ <string>#FFFFFF</string>
14
+ <key>caret</key>
15
+ <string>#000000</string>
16
+ <key>foreground</key>
17
+ <string>#000000</string>
18
+ <key>invisibles</key>
19
+ <string>#CAE2FB3D</string>
20
+ <key>lineHighlight</key>
21
+ <string>#FFFFFF24</string>
22
+ <key>selection</key>
23
+ <string>#C8C8C8</string>
24
+ </dict>
25
+ </dict>
26
+
27
+ <dict>
28
+ <key>name</key>
29
+ <string>themes.sweyla.com</string>
30
+ <key>scope</key>
31
+ <string>meta.sweyla</string>
32
+ <key>settings</key>
33
+ <dict>
34
+ <key>fontStyle</key>
35
+ <string></string>
36
+ <key>foreground</key>
37
+ <string>#7E0A00</string>
38
+ </dict>
39
+ </dict>
40
+
41
+ <dict>
42
+ <key>name</key>
43
+ <string>Comment</string>
44
+ <key>scope</key>
45
+ <string>comment</string>
46
+ <key>settings</key>
47
+ <dict>
48
+ <key>fontStyle</key>
49
+ <string></string>
50
+ <key>foreground</key>
51
+ <string>#8DA708</string>
52
+ </dict>
53
+ </dict>
54
+
55
+ <dict>
56
+ <key>name</key>
57
+ <string>Constant</string>
58
+ <key>scope</key>
59
+ <string>constant</string>
60
+ <key>settings</key>
61
+ <dict>
62
+ <key>fontStyle</key>
63
+ <string></string>
64
+ <key>foreground</key>
65
+ <string>#FF0018</string>
66
+ </dict>
67
+ </dict>
68
+
69
+ <dict>
70
+ <key>name</key>
71
+ <string>Constant</string>
72
+ <key>scope</key>
73
+ <string>constant.language</string>
74
+ <key>settings</key>
75
+ <dict>
76
+ <key>fontStyle</key>
77
+ <string></string>
78
+ <key>foreground</key>
79
+ <string>#7E000E</string>
80
+ </dict>
81
+ </dict>
82
+
83
+ <dict>
84
+ <key>name</key>
85
+ <string>Entity</string>
86
+ <key>scope</key>
87
+ <string>entity</string>
88
+ <key>settings</key>
89
+ <dict>
90
+ <key>fontStyle</key>
91
+ <string></string>
92
+ <key>foreground</key>
93
+ <string>#E33028</string>
94
+ </dict>
95
+ </dict>
96
+
97
+ <dict>
98
+ <key>name</key>
99
+ <string>Entity Function</string>
100
+ <key>scope</key>
101
+ <string>entity.name.function</string>
102
+ <key>settings</key>
103
+ <dict>
104
+ <key>fontStyle</key>
105
+ <string></string>
106
+ <key>foreground</key>
107
+ <string>#E33028</string>
108
+ </dict>
109
+ </dict>
110
+
111
+ <dict>
112
+ <key>name</key>
113
+ <string>Entity Name</string>
114
+ <key>scope</key>
115
+ <string>entity.name</string>
116
+ <key>settings</key>
117
+ <dict>
118
+ <key>fontStyle</key>
119
+ <string></string>
120
+ <key>foreground</key>
121
+ <string>#FF5E00</string>
122
+ </dict>
123
+ </dict>
124
+
125
+ <dict>
126
+ <key>name</key>
127
+ <string>Keyword</string>
128
+ <key>scope</key>
129
+ <string>keyword</string>
130
+ <key>settings</key>
131
+ <dict>
132
+ <key>fontStyle</key>
133
+ <string></string>
134
+ <key>foreground</key>
135
+ <string>#7E0A00</string>
136
+ </dict>
137
+ </dict>
138
+
139
+ <dict>
140
+ <key>name</key>
141
+ <string>Operator</string>
142
+ <key>scope</key>
143
+ <string>keyword.operator</string>
144
+ <key>settings</key>
145
+ <dict>
146
+ <key>fontStyle</key>
147
+ <string></string>
148
+ <key>foreground</key>
149
+ <string>#2B0000</string>
150
+ </dict>
151
+ </dict>
152
+
153
+ <dict>
154
+ <key>name</key>
155
+ <string>Storage</string>
156
+ <key>scope</key>
157
+ <string>storage</string>
158
+ <key>settings</key>
159
+ <dict>
160
+ <key>fontStyle</key>
161
+ <string></string>
162
+ <key>foreground</key>
163
+ <string>#7E0A00</string>
164
+ </dict>
165
+ </dict>
166
+
167
+ <dict>
168
+ <key>name</key>
169
+ <string>Support</string>
170
+ <key>scope</key>
171
+ <string>support</string>
172
+ <key>settings</key>
173
+ <dict>
174
+ <key>fontStyle</key>
175
+ <string></string>
176
+ <key>foreground</key>
177
+ <string>#C44C54</string>
178
+ </dict>
179
+ </dict>
180
+
181
+ <dict>
182
+ <key>name</key>
183
+ <string>Support Function</string>
184
+ <key>scope</key>
185
+ <string>support.function</string>
186
+ <key>settings</key>
187
+ <dict>
188
+ <key>fontStyle</key>
189
+ <string></string>
190
+ <key>foreground</key>
191
+ <string>#C02125</string>
192
+ </dict>
193
+ </dict>
194
+
195
+ <dict>
196
+ <key>name</key>
197
+ <string>String</string>
198
+ <key>scope</key>
199
+ <string>string</string>
200
+ <key>settings</key>
201
+ <dict>
202
+ <key>fontStyle</key>
203
+ <string></string>
204
+ <key>foreground</key>
205
+ <string>#BF8D00</string>
206
+ </dict>
207
+ </dict>
208
+
209
+ <dict>
210
+ <key>name</key>
211
+ <string>Variable</string>
212
+ <key>scope</key>
213
+ <string>variable</string>
214
+ <key>settings</key>
215
+ <dict>
216
+ <key>fontStyle</key>
217
+ <string></string>
218
+ <key>foreground</key>
219
+ <string>#7E000E</string>
220
+ </dict>
221
+ </dict>
222
+
223
+ <dict>
224
+ <key>name</key>
225
+ <string>String constant</string>
226
+ <key>scope</key>
227
+ <string>string constant</string>
228
+ <key>settings</key>
229
+ <dict>
230
+ <key>fontStyle</key>
231
+ <string></string>
232
+ <key>foreground</key>
233
+ <string>#9E9B08</string>
234
+ </dict>
235
+ </dict>
236
+
237
+ <dict>
238
+ <key>name</key>
239
+ <string>Invalid</string>
240
+ <key>scope</key>
241
+ <string>invalid</string>
242
+ <key>settings</key>
243
+ <dict>
244
+ <key>fontStyle</key>
245
+ <string></string>
246
+ <key>foreground</key>
247
+ <string>#7E0A00</string>
248
+ </dict>
249
+ </dict>
250
+
251
+ </array>
252
+ <key>uuid</key>
253
+ <string>446e604a-ad3e-3292-b443-2ee9307764b4</string>
254
+ </dict>
255
+ </plist>
@@ -0,0 +1,465 @@
1
+ import { XMLParser } from "fast-xml-parser";
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import * as path from "node:path";
4
+
5
+ const rootDirectory = process.cwd();
6
+ const docsDirectory = path.join(rootDirectory, "docs");
7
+ const metadataPath = path.join(rootDirectory, "metadata", "themes.json");
8
+ const siteDataPath = path.join(docsDirectory, "site-data.json");
9
+ const shouldCheck = process.argv.includes("--check");
10
+ const shouldWrite = process.argv.includes("--write");
11
+
12
+ const parser = new XMLParser({
13
+ ignoreAttributes: false,
14
+ preserveOrder: true,
15
+ trimValues: false,
16
+ });
17
+
18
+ /**
19
+ * @typedef {{
20
+ * readonly background?: string;
21
+ * readonly fontStyle?: string;
22
+ * readonly foreground?: string;
23
+ * readonly scope: string;
24
+ * }} PreviewRule
25
+ *
26
+ * @typedef {{
27
+ * readonly appearance: string;
28
+ * readonly author: null | string;
29
+ * readonly colors: Record<string, null | string>;
30
+ * readonly fileName: string;
31
+ * readonly id: string;
32
+ * readonly name: string;
33
+ * readonly path: string;
34
+ * readonly rules: readonly PreviewRule[];
35
+ * readonly statistics: Record<string, number>;
36
+ * readonly uuid: string;
37
+ * }} PreviewTheme
38
+ */
39
+
40
+ /**
41
+ * @returns {Promise<Record<string, unknown>>}
42
+ */
43
+ async function buildSiteData() {
44
+ const metadata = await readJson(metadataPath);
45
+ const themes = getThemes(metadata);
46
+ const previewThemes = await Promise.all(
47
+ themes.map((theme) => buildThemePreview(theme))
48
+ );
49
+
50
+ return {
51
+ generatedBy: "npm run pages:build",
52
+ sourceManifest: "metadata/themes.json",
53
+ themeCount: previewThemes.length,
54
+ themes: previewThemes,
55
+ };
56
+ }
57
+
58
+ /**
59
+ * @param {Record<string, unknown>} theme
60
+ *
61
+ * @returns {Promise<PreviewTheme>}
62
+ */
63
+ async function buildThemePreview(theme) {
64
+ const themePath = getRequiredString(theme, "path");
65
+ const absoluteThemePath = path.join(rootDirectory, themePath);
66
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- Theme paths come from the committed metadata manifest.
67
+ const text = await readFile(absoluteThemePath, "utf8");
68
+ const parsedDocument = /** @type {unknown} */ (parser.parse(text));
69
+ const topLevelEntries = getTopLevelDictionary(parsedDocument);
70
+ const settings = getThemeSettings(topLevelEntries);
71
+
72
+ return {
73
+ appearance: getRequiredString(theme, "appearance"),
74
+ author: getNullableString(theme, "author"),
75
+ colors: getRecord(theme, "colors"),
76
+ fileName: getRequiredString(theme, "fileName"),
77
+ id: getRequiredString(theme, "id"),
78
+ name: getRequiredString(theme, "name"),
79
+ path: themePath,
80
+ rules: getPreviewRules(settings),
81
+ statistics: getNumberRecord(theme, "statistics"),
82
+ uuid: getRequiredString(theme, "uuid"),
83
+ };
84
+ }
85
+
86
+ /**
87
+ * @param {Record<string, unknown>} record
88
+ * @param {string} key
89
+ *
90
+ * @returns {readonly unknown[] | undefined}
91
+ */
92
+ function getArrayProperty(record, key) {
93
+ const value = record[key];
94
+ return isUnknownArray(value) ? value : undefined;
95
+ }
96
+
97
+ /**
98
+ * @param {string} siteDataJson
99
+ *
100
+ * @returns {Promise<number>}
101
+ */
102
+ async function getCheckExitCode(siteDataJson) {
103
+ const existingSiteData = await readExistingSiteData();
104
+
105
+ if (existingSiteData === siteDataJson) {
106
+ process.stdout.write("GitHub Pages site data is up to date.\n");
107
+ return 0;
108
+ }
109
+
110
+ process.stderr.write(
111
+ "GitHub Pages site data is stale. Run `npm run pages:build`.\n"
112
+ );
113
+ return 1;
114
+ }
115
+
116
+ /**
117
+ * @param {readonly unknown[]} dictChildren
118
+ *
119
+ * @returns {ReadonlyMap<string, unknown>}
120
+ */
121
+ function getDictionaryEntries(dictChildren) {
122
+ /** @type {Map<string, unknown>} */
123
+ const entries = new Map();
124
+ /** @type {string | undefined} */
125
+ let currentKey;
126
+
127
+ for (const child of dictChildren) {
128
+ if (hasOwnRecordKey(child, "key")) {
129
+ currentKey = getTextNodeValue(child.key);
130
+ continue;
131
+ }
132
+
133
+ if (currentKey !== undefined && !hasOwnRecordKey(child, "#text")) {
134
+ entries.set(currentKey, child);
135
+ currentKey = undefined;
136
+ }
137
+ }
138
+
139
+ return entries;
140
+ }
141
+
142
+ /**
143
+ * @param {ReadonlyMap<string, unknown>} entries
144
+ * @param {string} key
145
+ *
146
+ * @returns {ReadonlyMap<string, unknown>}
147
+ */
148
+ function getDictionaryValue(entries, key) {
149
+ const value = entries.get(key);
150
+
151
+ if (!isRecord(value)) {
152
+ return new Map();
153
+ }
154
+
155
+ const dictChildren = getArrayProperty(value, "dict");
156
+ return dictChildren === undefined
157
+ ? new Map()
158
+ : getDictionaryEntries(dictChildren);
159
+ }
160
+
161
+ /**
162
+ * @param {Record<string, unknown>} record
163
+ * @param {string} key
164
+ *
165
+ * @returns {null | string}
166
+ */
167
+ function getNullableString(record, key) {
168
+ const value = record[key];
169
+
170
+ if (value === null || typeof value === "string") {
171
+ return value;
172
+ }
173
+
174
+ return null;
175
+ }
176
+
177
+ /**
178
+ * @param {Record<string, unknown>} record
179
+ * @param {string} key
180
+ *
181
+ * @returns {Record<string, number>}
182
+ */
183
+ function getNumberRecord(record, key) {
184
+ const value = record[key];
185
+
186
+ if (!isRecord(value)) {
187
+ return {};
188
+ }
189
+
190
+ /** @type {Record<string, number>} */
191
+ const numberRecord = {};
192
+
193
+ for (const [entryKey, entryValue] of Object.entries(value)) {
194
+ if (typeof entryValue === "number") {
195
+ numberRecord[entryKey] = entryValue;
196
+ }
197
+ }
198
+
199
+ return numberRecord;
200
+ }
201
+
202
+ /**
203
+ * @param {ReadonlyMap<string, unknown>} settings
204
+ * @param {string} key
205
+ *
206
+ * @returns {string | undefined}
207
+ */
208
+ function getOptionalString(settings, key) {
209
+ const value = settings.get(key);
210
+
211
+ if (!isRecord(value)) {
212
+ return undefined;
213
+ }
214
+
215
+ return getTextNodeValue(value.string);
216
+ }
217
+
218
+ /**
219
+ * @param {readonly unknown[]} settingsArray
220
+ *
221
+ * @returns {readonly PreviewRule[]}
222
+ */
223
+ function getPreviewRules(settingsArray) {
224
+ return settingsArray.flatMap((item) => {
225
+ if (!hasOwnRecordKey(item, "dict")) {
226
+ return [];
227
+ }
228
+
229
+ const dictChildren = getArrayProperty(item, "dict");
230
+ const entries =
231
+ dictChildren === undefined
232
+ ? new Map()
233
+ : getDictionaryEntries(dictChildren);
234
+ const scope = getOptionalString(entries, "scope");
235
+
236
+ if (scope === undefined) {
237
+ return [];
238
+ }
239
+
240
+ const settings = getDictionaryValue(entries, "settings");
241
+ const foreground = getOptionalString(settings, "foreground");
242
+ const background = getOptionalString(settings, "background");
243
+ const fontStyle = getOptionalString(settings, "fontStyle");
244
+
245
+ return scope
246
+ .split(",")
247
+ .map((scopePart) => scopePart.trim())
248
+ .filter((scopePart) => scopePart.length > 0)
249
+ .map((scopePart) => ({
250
+ ...(background === undefined ? {} : { background }),
251
+ ...(fontStyle === undefined ? {} : { fontStyle }),
252
+ ...(foreground === undefined ? {} : { foreground }),
253
+ scope: scopePart,
254
+ }));
255
+ });
256
+ }
257
+
258
+ /**
259
+ * @param {Record<string, unknown>} record
260
+ * @param {string} key
261
+ *
262
+ * @returns {Record<string, null | string>}
263
+ */
264
+ function getRecord(record, key) {
265
+ const value = record[key];
266
+
267
+ if (!isRecord(value)) {
268
+ return {};
269
+ }
270
+
271
+ return Object.fromEntries(
272
+ Object.entries(value).map(([entryKey, entryValue]) => [
273
+ entryKey,
274
+ typeof entryValue === "string" ? entryValue : null,
275
+ ])
276
+ );
277
+ }
278
+
279
+ /**
280
+ * @param {Record<string, unknown>} record
281
+ * @param {string} key
282
+ *
283
+ * @returns {string}
284
+ */
285
+ function getRequiredString(record, key) {
286
+ const value = record[key];
287
+
288
+ if (typeof value !== "string") {
289
+ throw new TypeError(`Expected string field: ${key}`);
290
+ }
291
+
292
+ return value;
293
+ }
294
+
295
+ /**
296
+ * @param {unknown} value
297
+ *
298
+ * @returns {string | undefined}
299
+ */
300
+ function getTextNodeValue(value) {
301
+ if (!isUnknownArray(value)) {
302
+ return undefined;
303
+ }
304
+
305
+ const firstValue = value[0];
306
+ if (!isRecord(firstValue)) {
307
+ return undefined;
308
+ }
309
+
310
+ const textValue = firstValue["#text"];
311
+ return typeof textValue === "string" ? textValue : undefined;
312
+ }
313
+
314
+ /**
315
+ * @param {unknown} metadata
316
+ *
317
+ * @returns {readonly Record<string, unknown>[]}
318
+ */
319
+ function getThemes(metadata) {
320
+ if (!isRecord(metadata) || !isUnknownArray(metadata.themes)) {
321
+ throw new TypeError("metadata/themes.json does not contain themes.");
322
+ }
323
+
324
+ return metadata.themes.filter((theme) => isRecord(theme));
325
+ }
326
+
327
+ /**
328
+ * @param {ReadonlyMap<string, unknown>} topLevelEntries
329
+ *
330
+ * @returns {readonly unknown[]}
331
+ */
332
+ function getThemeSettings(topLevelEntries) {
333
+ const settingsNode = topLevelEntries.get("settings");
334
+
335
+ if (!isRecord(settingsNode)) {
336
+ return [];
337
+ }
338
+
339
+ return getArrayProperty(settingsNode, "array") ?? [];
340
+ }
341
+
342
+ /**
343
+ * @param {unknown} parsedDocument
344
+ *
345
+ * @returns {ReadonlyMap<string, unknown>}
346
+ */
347
+ function getTopLevelDictionary(parsedDocument) {
348
+ if (!isUnknownArray(parsedDocument)) {
349
+ return new Map();
350
+ }
351
+
352
+ const plistNode = parsedDocument.find((node) =>
353
+ hasOwnRecordKey(node, "plist")
354
+ );
355
+
356
+ if (!isRecord(plistNode)) {
357
+ return new Map();
358
+ }
359
+
360
+ const plistChildren = getArrayProperty(plistNode, "plist");
361
+ if (plistChildren === undefined) {
362
+ return new Map();
363
+ }
364
+
365
+ const dictNode = plistChildren.find((node) =>
366
+ hasOwnRecordKey(node, "dict")
367
+ );
368
+
369
+ if (!isRecord(dictNode)) {
370
+ return new Map();
371
+ }
372
+
373
+ const dictChildren = getArrayProperty(dictNode, "dict");
374
+ return dictChildren === undefined
375
+ ? new Map()
376
+ : getDictionaryEntries(dictChildren);
377
+ }
378
+
379
+ /**
380
+ * @param {unknown} value
381
+ * @param {string} key
382
+ *
383
+ * @returns {value is Record<string, unknown>}
384
+ */
385
+ function hasOwnRecordKey(value, key) {
386
+ return isRecord(value) && Object.hasOwn(value, key);
387
+ }
388
+
389
+ /**
390
+ * @param {unknown} value
391
+ *
392
+ * @returns {value is Record<string, unknown>}
393
+ */
394
+ function isRecord(value) {
395
+ return typeof value === "object" && value !== null && !Array.isArray(value);
396
+ }
397
+
398
+ /**
399
+ * @param {unknown} value
400
+ *
401
+ * @returns {value is readonly unknown[]}
402
+ */
403
+ function isUnknownArray(value) {
404
+ return Array.isArray(value);
405
+ }
406
+
407
+ /**
408
+ * @returns {Promise<number>}
409
+ */
410
+ async function main() {
411
+ if (shouldCheck === shouldWrite) {
412
+ process.stderr.write("Specify exactly one of --check or --write.\n");
413
+ return 1;
414
+ }
415
+
416
+ const siteData = await buildSiteData();
417
+ const siteDataJson = `${JSON.stringify(siteData, null, 4)}\n`;
418
+
419
+ if (shouldCheck) {
420
+ return getCheckExitCode(siteDataJson);
421
+ }
422
+
423
+ await mkdir(docsDirectory, { recursive: true });
424
+ await writeFile(siteDataPath, siteDataJson, "utf8");
425
+ process.stdout.write("Wrote docs/site-data.json.\n");
426
+ return 0;
427
+ }
428
+
429
+ /**
430
+ * @returns {Promise<string>}
431
+ */
432
+ async function readExistingSiteData() {
433
+ try {
434
+ return await readFile(siteDataPath, "utf8");
435
+ } catch {
436
+ return "";
437
+ }
438
+ }
439
+
440
+ /**
441
+ * @param {string} filePath
442
+ *
443
+ * @returns {Promise<unknown>}
444
+ */
445
+ async function readJson(filePath) {
446
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- JSON paths are fixed repo-local paths.
447
+ const text = await readFile(filePath, "utf8");
448
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return -- This is the typed boundary for a generated repo-local JSON file.
449
+ return JSON.parse(text);
450
+ }
451
+
452
+ /**
453
+ * @returns {Promise<void>}
454
+ */
455
+ async function run() {
456
+ try {
457
+ process.exitCode = await main();
458
+ } catch (error) {
459
+ process.stderr.write(`${String(error)}\n`);
460
+ process.exitCode = 1;
461
+ }
462
+ }
463
+
464
+ // eslint-disable-next-line unicorn/prefer-top-level-await -- This published-module config also enforces n/no-top-level-await.
465
+ void run();