waybetter 0.1.1 → 0.6.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 (515) hide show
  1. package/README.md +140 -6
  2. package/bin/waybetter.mjs +46 -0
  3. package/build/runtime.mjs +105 -0
  4. package/cli/buildenv.mjs +130 -0
  5. package/cli/dev.mjs +97 -0
  6. package/cli/i18n.mjs +382 -0
  7. package/cli/manifests.mjs +383 -0
  8. package/cli/privacy.mjs +768 -0
  9. package/cli/release.mjs +444 -0
  10. package/cli/remote.mjs +260 -0
  11. package/cli/store.mjs +326 -0
  12. package/cli/strings.mjs +121 -0
  13. package/dist/runtime/column.d.ts +93 -0
  14. package/dist/runtime/column.js +451 -0
  15. package/dist/runtime/column.js.map +1 -0
  16. package/dist/runtime/commandReport.d.ts +19 -0
  17. package/dist/runtime/commandReport.js +47 -0
  18. package/dist/runtime/commandReport.js.map +1 -0
  19. package/dist/runtime/commands.d.ts +72 -0
  20. package/dist/runtime/commands.js +214 -0
  21. package/dist/runtime/commands.js.map +1 -0
  22. package/dist/runtime/devErrors.d.ts +16 -0
  23. package/dist/runtime/devErrors.js +30 -0
  24. package/dist/runtime/devErrors.js.map +1 -0
  25. package/dist/runtime/domHub.d.ts +21 -0
  26. package/dist/runtime/domHub.js +139 -4
  27. package/dist/runtime/domHub.js.map +1 -1
  28. package/dist/runtime/featureModules.d.ts +157 -0
  29. package/dist/runtime/featureModules.js +484 -0
  30. package/dist/runtime/featureModules.js.map +1 -0
  31. package/dist/runtime/featureTable.d.ts +144 -0
  32. package/dist/runtime/featureTable.js +223 -0
  33. package/dist/runtime/featureTable.js.map +1 -0
  34. package/dist/runtime/fonts.d.ts +22 -0
  35. package/dist/runtime/fonts.js +72 -0
  36. package/dist/runtime/fonts.js.map +1 -0
  37. package/dist/runtime/i18n.d.ts +138 -0
  38. package/dist/runtime/i18n.js +614 -0
  39. package/dist/runtime/i18n.js.map +1 -0
  40. package/dist/runtime/i18nCheck.d.ts +30 -0
  41. package/dist/runtime/i18nCheck.js +224 -0
  42. package/dist/runtime/i18nCheck.js.map +1 -0
  43. package/dist/runtime/pageLang.d.ts +8 -0
  44. package/dist/runtime/pageLang.js +64 -0
  45. package/dist/runtime/pageLang.js.map +1 -0
  46. package/dist/runtime/pageStrings.d.ts +35 -0
  47. package/dist/runtime/pageStrings.js +76 -0
  48. package/dist/runtime/pageStrings.js.map +1 -0
  49. package/dist/runtime/panelButton.d.ts +33 -0
  50. package/dist/runtime/panelButton.js +83 -0
  51. package/dist/runtime/panelButton.js.map +1 -0
  52. package/dist/runtime/permissions.d.ts +47 -0
  53. package/dist/runtime/permissions.js +141 -0
  54. package/dist/runtime/permissions.js.map +1 -0
  55. package/dist/runtime/pluginApi.d.ts +51 -2
  56. package/dist/runtime/pluginApi.js +137 -22
  57. package/dist/runtime/pluginApi.js.map +1 -1
  58. package/dist/runtime/pluginI18n.d.ts +29 -0
  59. package/dist/runtime/pluginI18n.js +119 -0
  60. package/dist/runtime/pluginI18n.js.map +1 -0
  61. package/dist/runtime/settingsForm.d.ts +66 -0
  62. package/dist/runtime/settingsForm.js +114 -0
  63. package/dist/runtime/settingsForm.js.map +1 -0
  64. package/dist/runtime/site.d.ts +5 -0
  65. package/dist/runtime/site.js +1 -1
  66. package/dist/runtime/site.js.map +1 -1
  67. package/dist/runtime/siteRuntime.d.ts +144 -0
  68. package/dist/runtime/siteRuntime.js +175 -0
  69. package/dist/runtime/siteRuntime.js.map +1 -0
  70. package/dist/runtime/stringPackHook.d.ts +84 -0
  71. package/dist/runtime/stringPackHook.js +220 -0
  72. package/dist/runtime/stringPackHook.js.map +1 -0
  73. package/dist/runtime/stringPackTranslate.d.ts +59 -0
  74. package/dist/runtime/stringPackTranslate.js +264 -0
  75. package/dist/runtime/stringPackTranslate.js.map +1 -0
  76. package/dist/runtime/stringPacks.d.ts +200 -0
  77. package/dist/runtime/stringPacks.js +474 -0
  78. package/dist/runtime/stringPacks.js.map +1 -0
  79. package/dist/runtime/styles.d.ts +15 -0
  80. package/dist/runtime/styles.js +81 -0
  81. package/dist/runtime/styles.js.map +1 -0
  82. package/dist/runtime/textNet.d.ts +15 -0
  83. package/dist/runtime/textNet.js +131 -0
  84. package/dist/runtime/textNet.js.map +1 -0
  85. package/dist/runtime/themeApi.d.ts +121 -0
  86. package/dist/runtime/themeApi.js +220 -0
  87. package/dist/runtime/themeApi.js.map +1 -0
  88. package/dist/runtime/themeChoice.d.ts +52 -0
  89. package/dist/runtime/themeChoice.js +69 -0
  90. package/dist/runtime/themeChoice.js.map +1 -0
  91. package/dist/runtime/themeComponents.d.ts +435 -0
  92. package/dist/runtime/themeComponents.js +463 -0
  93. package/dist/runtime/themeComponents.js.map +1 -0
  94. package/dist/runtime/themeDecor.d.ts +73 -0
  95. package/dist/runtime/themeDecor.js +131 -0
  96. package/dist/runtime/themeDecor.js.map +1 -0
  97. package/dist/runtime/themeDoctor.d.ts +11 -0
  98. package/dist/runtime/themeDoctor.js +51 -0
  99. package/dist/runtime/themeDoctor.js.map +1 -0
  100. package/dist/runtime/themeIcons.d.ts +130 -0
  101. package/dist/runtime/themeIcons.js +263 -0
  102. package/dist/runtime/themeIcons.js.map +1 -0
  103. package/dist/runtime/themeRuntime.d.ts +100 -0
  104. package/dist/runtime/themeRuntime.js +497 -0
  105. package/dist/runtime/themeRuntime.js.map +1 -0
  106. package/dist/runtime/themeSchema.d.ts +10 -0
  107. package/dist/runtime/themeSchema.js +150 -0
  108. package/dist/runtime/themeSchema.js.map +1 -0
  109. package/dist/runtime/themeSpec.d.ts +259 -0
  110. package/dist/runtime/themeSpec.js +439 -0
  111. package/dist/runtime/themeSpec.js.map +1 -0
  112. package/dist/runtime/themeTokens.d.ts +12 -0
  113. package/dist/runtime/themeTokens.js +14 -6
  114. package/dist/runtime/themeTokens.js.map +1 -1
  115. package/dist/runtime/themeVariants.d.ts +30 -0
  116. package/dist/runtime/themeVariants.js +53 -0
  117. package/dist/runtime/themeVariants.js.map +1 -0
  118. package/dist/runtime/ui.d.ts +192 -0
  119. package/dist/runtime/ui.js +926 -0
  120. package/dist/runtime/ui.js.map +1 -0
  121. package/dist/shell/InstallDialog.d.ts +40 -0
  122. package/dist/shell/InstallDialog.js +170 -0
  123. package/dist/shell/InstallDialog.js.map +1 -0
  124. package/dist/shell/StoreView.d.ts +66 -0
  125. package/dist/shell/StoreView.js +103 -0
  126. package/dist/shell/StoreView.js.map +1 -0
  127. package/dist/shell/addressModel.js.map +1 -1
  128. package/dist/shell/chrome.js.map +1 -1
  129. package/dist/shell/deckModel.d.ts +117 -0
  130. package/dist/shell/deckModel.js +267 -0
  131. package/dist/shell/deckModel.js.map +1 -0
  132. package/dist/shell/frame/AccountSwitcher.d.ts +42 -0
  133. package/dist/shell/frame/AccountSwitcher.js +243 -0
  134. package/dist/shell/frame/AccountSwitcher.js.map +1 -0
  135. package/dist/shell/frame/AddColumnDialog.d.ts +8 -0
  136. package/dist/shell/frame/AddColumnDialog.js +81 -0
  137. package/dist/shell/frame/AddColumnDialog.js.map +1 -0
  138. package/dist/shell/frame/AppMenu.d.ts +21 -0
  139. package/dist/shell/frame/AppMenu.js +642 -0
  140. package/dist/shell/frame/AppMenu.js.map +1 -0
  141. package/dist/shell/frame/CommandPalette.d.ts +7 -0
  142. package/dist/shell/frame/CommandPalette.js +118 -0
  143. package/dist/shell/frame/CommandPalette.js.map +1 -0
  144. package/dist/shell/frame/DeckView.d.ts +13 -0
  145. package/dist/shell/frame/DeckView.js +287 -0
  146. package/dist/shell/frame/DeckView.js.map +1 -0
  147. package/dist/shell/frame/FindBar.d.ts +10 -0
  148. package/dist/shell/frame/FindBar.js +108 -0
  149. package/dist/shell/frame/FindBar.js.map +1 -0
  150. package/dist/shell/frame/MediaPanel.d.ts +8 -0
  151. package/dist/shell/frame/MediaPanel.js +76 -0
  152. package/dist/shell/frame/MediaPanel.js.map +1 -0
  153. package/dist/shell/frame/NewTabPage.d.ts +9 -0
  154. package/dist/shell/frame/NewTabPage.js +71 -0
  155. package/dist/shell/frame/NewTabPage.js.map +1 -0
  156. package/dist/shell/frame/PaneTheme.d.ts +16 -0
  157. package/dist/shell/frame/PaneTheme.js +67 -0
  158. package/dist/shell/frame/PaneTheme.js.map +1 -0
  159. package/dist/shell/frame/ReportDialog.d.ts +3 -0
  160. package/dist/shell/frame/ReportDialog.js +95 -0
  161. package/dist/shell/frame/ReportDialog.js.map +1 -0
  162. package/dist/shell/frame/Shell.d.ts +8 -0
  163. package/dist/shell/frame/Shell.js +1131 -0
  164. package/dist/shell/frame/Shell.js.map +1 -0
  165. package/dist/shell/frame/Titlebar.d.ts +16 -0
  166. package/dist/shell/frame/Titlebar.js +77 -0
  167. package/dist/shell/frame/Titlebar.js.map +1 -0
  168. package/dist/shell/frame/TitlebarExtras.d.ts +4 -0
  169. package/dist/shell/frame/TitlebarExtras.js +202 -0
  170. package/dist/shell/frame/TitlebarExtras.js.map +1 -0
  171. package/dist/shell/frame/WebPanel.d.ts +4 -0
  172. package/dist/shell/frame/WebPanel.js +182 -0
  173. package/dist/shell/frame/WebPanel.js.map +1 -0
  174. package/dist/shell/frame/accountModel.d.ts +49 -0
  175. package/dist/shell/frame/accountModel.js +67 -0
  176. package/dist/shell/frame/accountModel.js.map +1 -0
  177. package/dist/shell/frame/accountSwitcher.css +91 -0
  178. package/dist/shell/frame/appI18n.d.ts +44 -0
  179. package/dist/shell/frame/appI18n.js +64 -0
  180. package/dist/shell/frame/appI18n.js.map +1 -0
  181. package/dist/shell/frame/appMenu.css +121 -0
  182. package/dist/shell/frame/backupModel.d.ts +38 -0
  183. package/dist/shell/frame/backupModel.js +192 -0
  184. package/dist/shell/frame/backupModel.js.map +1 -0
  185. package/dist/shell/frame/boot.d.ts +8 -0
  186. package/dist/shell/frame/boot.js +35 -0
  187. package/dist/shell/frame/boot.js.map +1 -0
  188. package/dist/shell/frame/brand.d.ts +1 -0
  189. package/dist/shell/frame/brand.js +4 -0
  190. package/dist/shell/frame/brand.js.map +1 -0
  191. package/dist/shell/frame/bridge.d.ts +35 -0
  192. package/dist/shell/frame/bridge.js +70 -0
  193. package/dist/shell/frame/bridge.js.map +1 -0
  194. package/dist/shell/frame/chromeTheme.d.ts +29 -0
  195. package/dist/shell/frame/chromeTheme.js +81 -0
  196. package/dist/shell/frame/chromeTheme.js.map +1 -0
  197. package/dist/shell/frame/deck.css +209 -0
  198. package/dist/shell/frame/deckStore.d.ts +89 -0
  199. package/dist/shell/frame/deckStore.js +239 -0
  200. package/dist/shell/frame/deckStore.js.map +1 -0
  201. package/dist/shell/frame/devHost.d.ts +35 -0
  202. package/dist/shell/frame/devHost.js +80 -0
  203. package/dist/shell/frame/devHost.js.map +1 -0
  204. package/dist/shell/frame/devModel.d.ts +7 -0
  205. package/dist/shell/frame/devModel.js +17 -0
  206. package/dist/shell/frame/devModel.js.map +1 -0
  207. package/dist/shell/frame/diag.d.ts +7 -0
  208. package/dist/shell/frame/diag.js +39 -0
  209. package/dist/shell/frame/diag.js.map +1 -0
  210. package/dist/shell/frame/findBar.css +14 -0
  211. package/dist/shell/frame/frameMessages.d.ts +248 -0
  212. package/dist/shell/frame/frameMessages.js +252 -0
  213. package/dist/shell/frame/frameMessages.js.map +1 -0
  214. package/dist/shell/frame/hooks.d.ts +39 -0
  215. package/dist/shell/frame/hooks.js +97 -0
  216. package/dist/shell/frame/hooks.js.map +1 -0
  217. package/dist/shell/frame/i18n.d.ts +27 -0
  218. package/dist/shell/frame/i18n.js +66 -0
  219. package/dist/shell/frame/i18n.js.map +1 -0
  220. package/dist/shell/frame/iconPack.d.ts +11 -0
  221. package/dist/shell/frame/iconPack.js +45 -0
  222. package/dist/shell/frame/iconPack.js.map +1 -0
  223. package/dist/shell/frame/icons.d.ts +71 -0
  224. package/dist/shell/frame/icons.js +75 -0
  225. package/dist/shell/frame/icons.js.map +1 -0
  226. package/dist/shell/frame/index.d.ts +34 -0
  227. package/dist/shell/frame/index.js +43 -0
  228. package/dist/shell/frame/index.js.map +1 -0
  229. package/dist/shell/frame/language.d.ts +58 -0
  230. package/dist/shell/frame/language.js +165 -0
  231. package/dist/shell/frame/language.js.map +1 -0
  232. package/dist/shell/frame/mediaModel.d.ts +34 -0
  233. package/dist/shell/frame/mediaModel.js +53 -0
  234. package/dist/shell/frame/mediaModel.js.map +1 -0
  235. package/dist/shell/frame/menuMessages.en.json +54 -0
  236. package/dist/shell/frame/menuModel.d.ts +277 -0
  237. package/dist/shell/frame/menuModel.js +293 -0
  238. package/dist/shell/frame/menuModel.js.map +1 -0
  239. package/dist/shell/frame/paletteModel.d.ts +139 -0
  240. package/dist/shell/frame/paletteModel.js +112 -0
  241. package/dist/shell/frame/paletteModel.js.map +1 -0
  242. package/dist/shell/frame/paneTheme.css +9 -0
  243. package/dist/shell/frame/pluginCommandStore.d.ts +23 -0
  244. package/dist/shell/frame/pluginCommandStore.js +70 -0
  245. package/dist/shell/frame/pluginCommandStore.js.map +1 -0
  246. package/dist/shell/frame/reportModel.d.ts +52 -0
  247. package/dist/shell/frame/reportModel.js +105 -0
  248. package/dist/shell/frame/reportModel.js.map +1 -0
  249. package/dist/shell/frame/safeStore.d.ts +7 -0
  250. package/dist/shell/frame/safeStore.js +40 -0
  251. package/dist/shell/frame/safeStore.js.map +1 -0
  252. package/dist/shell/frame/searchInput.d.ts +2 -0
  253. package/dist/shell/frame/searchInput.js +11 -0
  254. package/dist/shell/frame/searchInput.js.map +1 -0
  255. package/dist/shell/frame/shell.css +629 -0
  256. package/dist/shell/frame/site.d.ts +206 -0
  257. package/dist/shell/frame/site.js +22 -0
  258. package/dist/shell/frame/site.js.map +1 -0
  259. package/dist/shell/frame/state.d.ts +99 -0
  260. package/dist/shell/frame/state.js +284 -0
  261. package/dist/shell/frame/state.js.map +1 -0
  262. package/dist/shell/frame/syncGate.d.ts +10 -0
  263. package/dist/shell/frame/syncGate.js +36 -0
  264. package/dist/shell/frame/syncGate.js.map +1 -0
  265. package/dist/shell/frame/syncHost.d.ts +35 -0
  266. package/dist/shell/frame/syncHost.js +85 -0
  267. package/dist/shell/frame/syncHost.js.map +1 -0
  268. package/dist/shell/frame/tabStill.d.ts +2 -0
  269. package/dist/shell/frame/tabStill.js +62 -0
  270. package/dist/shell/frame/tabStill.js.map +1 -0
  271. package/dist/shell/frame/themeScopes.d.ts +29 -0
  272. package/dist/shell/frame/themeScopes.js +61 -0
  273. package/dist/shell/frame/themeScopes.js.map +1 -0
  274. package/dist/shell/frame/themeStore.d.ts +99 -0
  275. package/dist/shell/frame/themeStore.js +252 -0
  276. package/dist/shell/frame/themeStore.js.map +1 -0
  277. package/dist/shell/frame/windowBinding.d.ts +2 -0
  278. package/dist/shell/frame/windowBinding.js +7 -0
  279. package/dist/shell/frame/windowBinding.js.map +1 -0
  280. package/dist/shell/frame/workspaceModel.d.ts +82 -0
  281. package/dist/shell/frame/workspaceModel.js +250 -0
  282. package/dist/shell/frame/workspaceModel.js.map +1 -0
  283. package/dist/shell/frame/workspaces.css +84 -0
  284. package/dist/shell/frame/workspaces.d.ts +22 -0
  285. package/dist/shell/frame/workspaces.js +125 -0
  286. package/dist/shell/frame/workspaces.js.map +1 -0
  287. package/dist/shell/frame/zoomStore.d.ts +10 -0
  288. package/dist/shell/frame/zoomStore.js +65 -0
  289. package/dist/shell/frame/zoomStore.js.map +1 -0
  290. package/dist/shell/installController.d.ts +122 -0
  291. package/dist/shell/installController.js +146 -0
  292. package/dist/shell/installController.js.map +1 -0
  293. package/dist/shell/installerMessages.d.ts +30 -0
  294. package/dist/shell/installerMessages.js +36 -0
  295. package/dist/shell/installerMessages.js.map +1 -0
  296. package/dist/shell/linkDrop.js.map +1 -1
  297. package/dist/shell/messages.d.ts +16 -0
  298. package/dist/shell/messages.js +27 -0
  299. package/dist/shell/messages.js.map +1 -0
  300. package/dist/shell/palette.js.map +1 -1
  301. package/dist/shell/paneModel.d.ts +22 -0
  302. package/dist/shell/paneModel.js +24 -1
  303. package/dist/shell/paneModel.js.map +1 -1
  304. package/dist/shell/panel/Appearance.d.ts +8 -0
  305. package/dist/shell/panel/Appearance.js +256 -0
  306. package/dist/shell/panel/Appearance.js.map +1 -0
  307. package/dist/shell/panel/BackupPanel.d.ts +5 -0
  308. package/dist/shell/panel/BackupPanel.js +198 -0
  309. package/dist/shell/panel/BackupPanel.js.map +1 -0
  310. package/dist/shell/panel/DeveloperPanel.d.ts +4 -0
  311. package/dist/shell/panel/DeveloperPanel.js +51 -0
  312. package/dist/shell/panel/DeveloperPanel.js.map +1 -0
  313. package/dist/shell/panel/FeatureToggles.d.ts +13 -0
  314. package/dist/shell/panel/FeatureToggles.js +42 -0
  315. package/dist/shell/panel/FeatureToggles.js.map +1 -0
  316. package/dist/shell/panel/Home.d.ts +3 -0
  317. package/dist/shell/panel/Home.js +23 -0
  318. package/dist/shell/panel/Home.js.map +1 -0
  319. package/dist/shell/panel/IconPackInstall.d.ts +15 -0
  320. package/dist/shell/panel/IconPackInstall.js +41 -0
  321. package/dist/shell/panel/IconPackInstall.js.map +1 -0
  322. package/dist/shell/panel/LanguageSettings.d.ts +1 -0
  323. package/dist/shell/panel/LanguageSettings.js +54 -0
  324. package/dist/shell/panel/LanguageSettings.js.map +1 -0
  325. package/dist/shell/panel/MakePanel.d.ts +3 -0
  326. package/dist/shell/panel/MakePanel.js +237 -0
  327. package/dist/shell/panel/MakePanel.js.map +1 -0
  328. package/dist/shell/panel/MarkdownDoc.d.ts +6 -0
  329. package/dist/shell/panel/MarkdownDoc.js +35 -0
  330. package/dist/shell/panel/MarkdownDoc.js.map +1 -0
  331. package/dist/shell/panel/Modes.d.ts +40 -0
  332. package/dist/shell/panel/Modes.js +270 -0
  333. package/dist/shell/panel/Modes.js.map +1 -0
  334. package/dist/shell/panel/NavIcon.d.ts +4 -0
  335. package/dist/shell/panel/NavIcon.js +6 -0
  336. package/dist/shell/panel/NavIcon.js.map +1 -0
  337. package/dist/shell/panel/Panel.d.ts +7 -0
  338. package/dist/shell/panel/Panel.js +139 -0
  339. package/dist/shell/panel/Panel.js.map +1 -0
  340. package/dist/shell/panel/PluginFeatures.d.ts +24 -0
  341. package/dist/shell/panel/PluginFeatures.js +165 -0
  342. package/dist/shell/panel/PluginFeatures.js.map +1 -0
  343. package/dist/shell/panel/Plugins.d.ts +33 -0
  344. package/dist/shell/panel/Plugins.js +112 -0
  345. package/dist/shell/panel/Plugins.js.map +1 -0
  346. package/dist/shell/panel/PowerSettings.d.ts +4 -0
  347. package/dist/shell/panel/PowerSettings.js +52 -0
  348. package/dist/shell/panel/PowerSettings.js.map +1 -0
  349. package/dist/shell/panel/Settings.d.ts +23 -0
  350. package/dist/shell/panel/Settings.js +116 -0
  351. package/dist/shell/panel/Settings.js.map +1 -0
  352. package/dist/shell/panel/ShareSetup.d.ts +8 -0
  353. package/dist/shell/panel/ShareSetup.js +130 -0
  354. package/dist/shell/panel/ShareSetup.js.map +1 -0
  355. package/dist/shell/panel/StoreSection.d.ts +6 -0
  356. package/dist/shell/panel/StoreSection.js +48 -0
  357. package/dist/shell/panel/StoreSection.js.map +1 -0
  358. package/dist/shell/panel/StringPacks.d.ts +11 -0
  359. package/dist/shell/panel/StringPacks.js +106 -0
  360. package/dist/shell/panel/StringPacks.js.map +1 -0
  361. package/dist/shell/panel/ThemeBuilder.d.ts +5 -0
  362. package/dist/shell/panel/ThemeBuilder.js +205 -0
  363. package/dist/shell/panel/ThemeBuilder.js.map +1 -0
  364. package/dist/shell/panel/backup.css +34 -0
  365. package/dist/shell/panel/builder.css +232 -0
  366. package/dist/shell/panel/builderModel.d.ts +55 -0
  367. package/dist/shell/panel/builderModel.js +95 -0
  368. package/dist/shell/panel/builderModel.js.map +1 -0
  369. package/dist/shell/panel/compare.d.ts +10 -0
  370. package/dist/shell/panel/compare.js +51 -0
  371. package/dist/shell/panel/compare.js.map +1 -0
  372. package/dist/shell/panel/controls.d.ts +135 -0
  373. package/dist/shell/panel/controls.js +213 -0
  374. package/dist/shell/panel/controls.js.map +1 -0
  375. package/dist/shell/panel/featureModel.d.ts +69 -0
  376. package/dist/shell/panel/featureModel.js +79 -0
  377. package/dist/shell/panel/featureModel.js.map +1 -0
  378. package/dist/shell/panel/fonts/FontBrowser.d.ts +10 -0
  379. package/dist/shell/panel/fonts/FontBrowser.js +189 -0
  380. package/dist/shell/panel/fonts/FontBrowser.js.map +1 -0
  381. package/dist/shell/panel/fonts/FontRoles.d.ts +20 -0
  382. package/dist/shell/panel/fonts/FontRoles.js +77 -0
  383. package/dist/shell/panel/fonts/FontRoles.js.map +1 -0
  384. package/dist/shell/panel/fonts/catalog.d.ts +37 -0
  385. package/dist/shell/panel/fonts/catalog.js +68 -0
  386. package/dist/shell/panel/fonts/catalog.js.map +1 -0
  387. package/dist/shell/panel/fonts/googleFontsCatalog.json +1 -0
  388. package/dist/shell/panel/index.d.ts +20 -0
  389. package/dist/shell/panel/index.js +34 -0
  390. package/dist/shell/panel/index.js.map +1 -0
  391. package/dist/shell/panel/languageModel.d.ts +5 -0
  392. package/dist/shell/panel/languageModel.js +7 -0
  393. package/dist/shell/panel/languageModel.js.map +1 -0
  394. package/dist/shell/panel/make.css +72 -0
  395. package/dist/shell/panel/makeModel.d.ts +32 -0
  396. package/dist/shell/panel/makeModel.js +84 -0
  397. package/dist/shell/panel/makeModel.js.map +1 -0
  398. package/dist/shell/panel/markdown.d.ts +46 -0
  399. package/dist/shell/panel/markdown.js +100 -0
  400. package/dist/shell/panel/markdown.js.map +1 -0
  401. package/dist/shell/panel/nav.d.ts +12 -0
  402. package/dist/shell/panel/nav.js +50 -0
  403. package/dist/shell/panel/nav.js.map +1 -0
  404. package/dist/shell/panel/panel.css +401 -0
  405. package/dist/shell/panel/panelMessages.d.ts +763 -0
  406. package/dist/shell/panel/panelMessages.js +767 -0
  407. package/dist/shell/panel/panelMessages.js.map +1 -0
  408. package/dist/shell/panel/plugins.css +59 -0
  409. package/dist/shell/panel/setupModel.d.ts +87 -0
  410. package/dist/shell/panel/setupModel.js +152 -0
  411. package/dist/shell/panel/setupModel.js.map +1 -0
  412. package/dist/shell/panel/site.d.ts +259 -0
  413. package/dist/shell/panel/site.js +25 -0
  414. package/dist/shell/panel/site.js.map +1 -0
  415. package/dist/shell/panel/strings.css +7 -0
  416. package/dist/shell/panel/themes.d.ts +10 -0
  417. package/dist/shell/panel/themes.js +8 -0
  418. package/dist/shell/panel/themes.js.map +1 -0
  419. package/dist/shell/profileModel.d.ts +3 -2
  420. package/dist/shell/profileModel.js +7 -5
  421. package/dist/shell/profileModel.js.map +1 -1
  422. package/dist/shell/store.css +230 -0
  423. package/dist/shell/storeMessages.d.ts +169 -0
  424. package/dist/shell/storeMessages.js +172 -0
  425. package/dist/shell/storeMessages.js.map +1 -0
  426. package/dist/shell/storeModel.d.ts +181 -0
  427. package/dist/shell/storeModel.js +133 -0
  428. package/dist/shell/storeModel.js.map +1 -0
  429. package/dist/shell/stringPacksModel.d.ts +44 -0
  430. package/dist/shell/stringPacksModel.js +94 -0
  431. package/dist/shell/stringPacksModel.js.map +1 -0
  432. package/dist/shell/updateModel.d.ts +2 -1
  433. package/dist/shell/updateModel.js +5 -4
  434. package/dist/shell/updateModel.js.map +1 -1
  435. package/dist/shell/windowControls.d.ts +4 -0
  436. package/dist/shell/windowControls.js +5 -0
  437. package/dist/shell/windowControls.js.map +1 -1
  438. package/i18n-config.schema.json +29 -0
  439. package/i18n-meta.schema.json +26 -0
  440. package/i18n.schema.json +42 -0
  441. package/icons.schema.json +646 -0
  442. package/languages.schema.json +23 -0
  443. package/locales/ar-x-fm.json +11 -0
  444. package/locales/ar.json +1281 -0
  445. package/locales/bg.json +1280 -0
  446. package/locales/bn.json +1280 -0
  447. package/locales/ca.json +1280 -0
  448. package/locales/cs.json +1280 -0
  449. package/locales/da.json +1280 -0
  450. package/locales/de.json +1280 -0
  451. package/locales/el.json +1280 -0
  452. package/locales/en-GB.json +27 -0
  453. package/locales/en.json +1280 -0
  454. package/locales/en.meta.json +51 -0
  455. package/locales/es.json +1280 -0
  456. package/locales/eu.json +1280 -0
  457. package/locales/fa.json +1281 -0
  458. package/locales/fi.json +1280 -0
  459. package/locales/fil.json +1280 -0
  460. package/locales/fr.json +1280 -0
  461. package/locales/ga.json +1280 -0
  462. package/locales/gl.json +1280 -0
  463. package/locales/gu.json +1280 -0
  464. package/locales/ha.json +1280 -0
  465. package/locales/he.json +1281 -0
  466. package/locales/hi.json +1280 -0
  467. package/locales/hr.json +1280 -0
  468. package/locales/hu.json +1279 -0
  469. package/locales/id.json +1280 -0
  470. package/locales/ig.json +1280 -0
  471. package/locales/it.json +1280 -0
  472. package/locales/ja.json +1280 -0
  473. package/locales/kn.json +1280 -0
  474. package/locales/ko.json +1280 -0
  475. package/locales/mr.json +1280 -0
  476. package/locales/ms.json +1280 -0
  477. package/locales/nb.json +1280 -0
  478. package/locales/nl.json +1280 -0
  479. package/locales/pl.json +1280 -0
  480. package/locales/pt.json +1280 -0
  481. package/locales/ro.json +1280 -0
  482. package/locales/ru.json +1280 -0
  483. package/locales/sk.json +1280 -0
  484. package/locales/sr.json +1280 -0
  485. package/locales/sv.json +1280 -0
  486. package/locales/ta.json +1280 -0
  487. package/locales/th.json +1280 -0
  488. package/locales/tr.json +1280 -0
  489. package/locales/uk.json +1280 -0
  490. package/locales/ur.json +1281 -0
  491. package/locales/vi.json +1280 -0
  492. package/locales/yo.json +1280 -0
  493. package/locales/zh-Hans.json +1280 -0
  494. package/locales/zh-Hant.json +1280 -0
  495. package/package.json +139 -5
  496. package/store-catalog.schema.json +65 -0
  497. package/theme.schema.json +4659 -0
  498. package/windows/installer.nsi +926 -0
  499. package/windows/languages/Basque.nsh +28 -0
  500. package/windows/languages/Catalan.nsh +28 -0
  501. package/windows/languages/Croatian.nsh +28 -0
  502. package/windows/languages/Czech.nsh +28 -0
  503. package/windows/languages/Danish.nsh +28 -0
  504. package/windows/languages/Finnish.nsh +28 -0
  505. package/windows/languages/Galician.nsh +28 -0
  506. package/windows/languages/Greek.nsh +28 -0
  507. package/windows/languages/Hungarian.nsh +28 -0
  508. package/windows/languages/Indonesian.nsh +28 -0
  509. package/windows/languages/Irish.nsh +28 -0
  510. package/windows/languages/Malay.nsh +28 -0
  511. package/windows/languages/Polish.nsh +28 -0
  512. package/windows/languages/Romanian.nsh +28 -0
  513. package/windows/languages/Serbian.nsh +28 -0
  514. package/windows/languages/Slovak.nsh +28 -0
  515. package/windows/languages/Thai.nsh +28 -0
package/cli/remote.mjs ADDED
@@ -0,0 +1,260 @@
1
+ // Remote and WSL builds for `waybetter release macos|linux`: the committed state of an app (its
2
+ // git HEAD plus every submodule at the commit the app records) is exported, sent to the machine
3
+ // that builds it, built there through `waybetter build-env` (no build path in the binary) and the
4
+ // artifacts come back to be scanned (`waybetter privacy-scan`) and, later, signed on this machine.
5
+ // Nothing here touches the working tree the developer has open, and no key ever leaves this
6
+ // machine: the remote build is unsigned (updater artifacts off), signing is `waybetter release`.
7
+ //
8
+ // The export is `git archive` of the app at HEAD plus `git archive` of each submodule at the commit
9
+ // HEAD points to, read from the local submodule repository. A submodule's newest commits do not
10
+ // have to be on any remote, so nothing is ever fetched from origin.
11
+ //
12
+ // Where it builds is never written down here: an SSH target (user@host, WAYBETTER_MAC_SSH or
13
+ // --remote), its key (WAYBETTER_MAC_SSH_KEY or --ssh-key) and a WSL distro (--wsl,
14
+ // WAYBETTER_WSL_DISTRO) come from the caller. The remote scripts use a login shell (macOS: bun,
15
+ // cargo and node exist only there) and a clean PATH (WSL: no Windows programs shadowing the
16
+ // Linux ones).
17
+ import { spawnSync } from 'node:child_process';
18
+ import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
19
+ import { tmpdir } from 'node:os';
20
+ import { basename, join, relative, resolve } from 'node:path';
21
+ import { assertClean } from './privacy.mjs';
22
+
23
+ class RemoteError extends Error {}
24
+ const fail = message => { throw new RemoteError(message); };
25
+
26
+ /** Single-quotes a value for a POSIX shell. */
27
+ export const shq = value => `'${String(value).replaceAll("'", `'\\''`)}'`;
28
+
29
+ function run(command, args, options = {}) {
30
+ const result = spawnSync(command, args, { encoding: 'utf8', windowsHide: true, maxBuffer: 1 << 28, ...options });
31
+ if (result.error) fail(`${command}: ${result.error.message}`);
32
+ return result;
33
+ }
34
+
35
+ function git(root, args, options) {
36
+ const result = run('git', ['-C', root, ...args], options);
37
+ if (result.status !== 0) fail(`git ${args[0]} failed: ${(result.stderr || '').trim().split('\n')[0]}`);
38
+ return result.stdout ?? '';
39
+ }
40
+
41
+ /** A file of the committed state (`git show <ref>:<path>`), for the version and product name. */
42
+ export const committedFile = (root, path, ref = 'HEAD') => git(root, ['show', `${ref}:${path}`]);
43
+
44
+ /** The submodules `ref` (default HEAD) records: [{ path, sha }]. */
45
+ export function committedSubmodules(root, ref = 'HEAD') {
46
+ return git(root, ['ls-tree', '-r', ref])
47
+ .split('\n')
48
+ .map(line => /^160000 commit ([0-9a-f]{40})\t(.+)$/.exec(line))
49
+ .filter(Boolean)
50
+ .map(([, sha, path]) => ({ path, sha }));
51
+ }
52
+
53
+ /**
54
+ * Exports the committed state of `root` (HEAD, and each submodule at its recorded commit) to a
55
+ * `.tar.gz` at `file`. Returns `{ sha, submodules, file, bytes, dirty }`; `dirty` lists tracked
56
+ * files changed in the working tree, which the export ignores by design.
57
+ */
58
+ export function exportCommitted({ root, file, ref = 'HEAD', exclude = [], log = () => {} }) {
59
+ const sha = git(root, ['rev-parse', `${ref}^{commit}`]).trim();
60
+ const submodules = committedSubmodules(root, sha);
61
+ const dirty = git(root, ['status', '--porcelain', '--untracked-files=no']).split('\n').filter(Boolean).length;
62
+ const staging = mkdtempSync(join(tmpdir(), 'wb-export-'));
63
+ try {
64
+ const parts = [{ repo: root, ref: sha, prefix: '' }, ...submodules.map(({ path, sha: ref }) => ({ repo: join(root, path), ref, prefix: `${path}/`, path }))];
65
+ for (const [index, part] of parts.entries()) {
66
+ if (part.path && !existsSync(join(part.repo, '.git'))) fail(`submodule ${part.path} is not checked out here (needed for commit ${part.ref.slice(0, 12)})`);
67
+ const tarName = `part-${index}.tar`;
68
+ // `exclude` (paths of the app repo the build never reads, e.g. screenshots) keeps the upload small.
69
+ const skip = part.path ? [] : ['--', '.', ...exclude.map(path => `:(exclude)${path}`)];
70
+ const args = ['archive', '--format=tar', ...(part.prefix ? [`--prefix=${part.prefix}`] : []), '-o', join(staging, tarName), part.ref, ...skip];
71
+ // Blobs as committed: the developer's core.autocrlf must not decide the line endings of the build.
72
+ const archived = run('git', ['-C', part.repo, '-c', 'core.autocrlf=false', ...args]);
73
+ if (archived.status !== 0) fail(`git archive ${part.path ?? 'app'} at ${part.ref.slice(0, 12)} failed: ${(archived.stderr || '').trim().split('\n')[0]} (is that commit in the local repository?)`);
74
+ // Relative names and cwd: a drive letter in an argument reads as a remote host to GNU tar.
75
+ mkdirSync(join(staging, 'tree'), { recursive: true });
76
+ const unpacked = run('tar', ['-xf', tarName, '-C', 'tree'], { cwd: staging });
77
+ if (unpacked.status !== 0) fail(`tar -x failed: ${(unpacked.stderr || '').trim().split('\n')[0]}`);
78
+ rmSync(join(staging, tarName));
79
+ }
80
+ mkdirSync(resolve(file, '..'), { recursive: true });
81
+ const outName = 'export.tgz';
82
+ const packed = run('tar', ['-czf', outName, '-C', 'tree', '.'], { cwd: staging });
83
+ if (packed.status !== 0) fail(`tar -c failed: ${(packed.stderr || '').trim().split('\n')[0]}`);
84
+ writeFileSync(file, readFileSync(join(staging, outName)));
85
+ } finally {
86
+ rmSync(staging, { recursive: true, force: true });
87
+ }
88
+ const bytes = statSync(file).size;
89
+ log(`export: ${sha.slice(0, 12)}${submodules.map(s => ` + ${s.path}@${s.sha.slice(0, 12)}`).join('')} -> ${(bytes / 1048576).toFixed(1)} MB${dirty ? ` (${dirty} tracked file(s) changed in the working tree are NOT included)` : ''}`);
90
+ return { sha, submodules, file, bytes, dirty };
91
+ }
92
+
93
+ /** Remote work folders are relative to the login home. */
94
+ const workDirOf = (dir, artifact) => (dir ?? `waybetter-builds/${artifact}`).replace(/^~\//, '').replace(/^\/+/, '') || `waybetter-builds/${artifact}`;
95
+ const sha256Line = 'if command -v shasum >/dev/null 2>&1; then shasum -a 256 "$@"; else sha256sum "$@"; fi';
96
+
97
+ /**
98
+ * The macOS build script (bash, run in a login shell on the Mac): unpack, install, universal
99
+ * ad-hoc-signed .app and .dmg through build-env, the updater bundle as a tar.gz of the .app; the
100
+ * dmg and the tar.gz under space-free names, and the .app itself, in `out/`. It never installs,
101
+ * quits or replaces an app on the Mac: whoever runs the app there swaps it in.
102
+ */
103
+ export function macBuildScript({ workDir, sha, artifact, productName, version, desktopDir, prepareCommand }) {
104
+ const app = `${productName}.app`;
105
+ return `#!/bin/bash
106
+ set -eu
107
+ # Low priority: the Mac may be recording or running an app while this compiles.
108
+ renice -n 10 $$ >/dev/null 2>&1 || true
109
+ # rustup first: Homebrew's Rust has no x86_64 standard library, which a universal build needs.
110
+ export PATH="$HOME/.cargo/bin:$PATH"
111
+ WORK="$HOME/${workDir}"
112
+ SRC="$WORK/src"
113
+ OUT="$WORK/out"
114
+ cd "$WORK"
115
+ rm -rf "$SRC" "$OUT"
116
+ # -m: files get the time of unpacking. git archive stamps every file with its commit time, which is older
117
+ # than the cached build in CARGO_TARGET_DIR: cargo would take a changed source for unchanged and reuse stale objects.
118
+ mkdir -p "$SRC" "$OUT"
119
+ tar -xzmf "$WORK/source-${sha.slice(0, 12)}.tgz" -C "$SRC"
120
+ cd "$SRC"
121
+ rustup target add aarch64-apple-darwin x86_64-apple-darwin
122
+ ${prepareCommand}
123
+ cd ${shq(desktopDir)}
124
+ # Ad-hoc signature (no Developer ID yet: Gatekeeper's "Open Anyway" path, not "is damaged"), no
125
+ # updater bundle from Tauri (it needs the signing key, which never leaves the release machine).
126
+ printf '%s' '{"bundle":{"createUpdaterArtifacts":false,"macOS":{"signingIdentity":"-"}}}' > "$WORK/override.json"
127
+ export CARGO_TARGET_DIR="$WORK/target"
128
+ bun "$SRC/waybetter/packages/waybetter/bin/waybetter.mjs" build-env -- bunx tauri build --target universal-apple-darwin --bundles app,dmg --ci --config "$WORK/override.json"
129
+ BUNDLE="$CARGO_TARGET_DIR/universal-apple-darwin/release/bundle"
130
+ DMG="$(ls "$BUNDLE"/dmg/*_${version}_universal.dmg | head -1)"
131
+ cp "$DMG" "$OUT/${artifact}_${version}_universal.dmg"
132
+ # Owner 0/root: the archive must not carry the Mac account name in its entry headers.
133
+ COPYFILE_DISABLE=1 tar --uid 0 --gid 0 --uname root --gname wheel -czf "$OUT/${artifact}_${version}_universal.app.tar.gz" -C "$BUNDLE/macos" ${shq(app)}
134
+ ditto "$BUNDLE/macos/"${shq(app)} "$OUT/"${shq(app)}
135
+ cd "$OUT"
136
+ set -- *.dmg *.tar.gz
137
+ ${sha256Line} > SHA256SUMS
138
+ ls -ln "$OUT"
139
+ echo "REMOTE_BUILD_OK ${sha.slice(0, 12)}"
140
+ `;
141
+ }
142
+
143
+ /**
144
+ * The Linux build script (bash, run in WSL or on any Ubuntu 22.04): clean PATH, unpack, install,
145
+ * AppImage and .deb through build-env with Tauri's updater bundle off (on Linux the signed
146
+ * AppImage itself is the update payload), copied to `out/` under space-free names.
147
+ */
148
+ export function linuxBuildScript({ workDir, sha, artifact, version, desktopDir, prepareCommand, tarball, outDir }) {
149
+ return `#!/bin/bash
150
+ set -eu
151
+ # A clean PATH: under WSL the Windows programs (a Windows bun, node, cargo) would shadow these.
152
+ export PATH="$HOME/.bun/bin:$HOME/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
153
+ export APPIMAGE_EXTRACT_AND_RUN=1
154
+ WORK="$HOME/${workDir}"
155
+ SRC="$WORK/src"
156
+ cd "$HOME"
157
+ mkdir -p "$WORK"
158
+ rm -rf "$SRC"
159
+ mkdir -p "$SRC"
160
+ tar -xzmf "$(wslpath -u ${shq(tarball)} 2>/dev/null || echo ${shq(tarball)})" -C "$SRC"
161
+ cd "$SRC"
162
+ ${prepareCommand}
163
+ cd ${shq(desktopDir)}
164
+ printf '%s' '{"bundle":{"createUpdaterArtifacts":false}}' > "$WORK/override.json"
165
+ export CARGO_TARGET_DIR="$WORK/target"
166
+ bun "$SRC/waybetter/packages/waybetter/bin/waybetter.mjs" build-env -- bunx tauri build --bundles appimage,deb --ci --config "$WORK/override.json"
167
+ BUNDLE="$CARGO_TARGET_DIR/release/bundle"
168
+ OUTDIR="$(wslpath -u ${shq(outDir)} 2>/dev/null || echo ${shq(outDir)})"
169
+ mkdir -p "$OUTDIR"
170
+ cp "$(ls "$BUNDLE"/appimage/*_${version}_amd64.AppImage | head -1)" "$OUTDIR/${artifact}_${version}_amd64.AppImage"
171
+ cp "$(ls "$BUNDLE"/deb/*_${version}_amd64.deb | head -1)" "$OUTDIR/${artifact}_${version}_amd64.deb"
172
+ ls -ln "$OUTDIR"
173
+ echo "REMOTE_BUILD_OK ${sha.slice(0, 12)}"
174
+ `;
175
+ }
176
+
177
+ const sshOptions = ({ key }) => ['-o', 'BatchMode=yes', '-o', 'IdentitiesOnly=yes', '-o', 'ConnectTimeout=15', '-o', 'ServerAliveInterval=30', ...(key ? ['-i', key] : [])];
178
+
179
+ /** Runs a command and streams its output; the exit code decides. */
180
+ function stream(command, args, { log, label }) {
181
+ log(`${label}: ${command} ${args.map(a => (/\s/.test(a) ? '"…"' : a)).join(' ').slice(0, 160)}`);
182
+ const result = spawnSync(command, args, { stdio: 'inherit', windowsHide: true });
183
+ if (result.error) fail(`${command}: ${result.error.message}`);
184
+ if (result.status !== 0) fail(`${label} failed (exit ${result.status})`);
185
+ }
186
+
187
+ /**
188
+ * Builds the committed state of the app on a Mac over SSH and brings the .dmg and the .app.tar.gz
189
+ * back to `<outDir>/build/macos`. Returns `{ version, sha, dir, dmg, updater }`.
190
+ */
191
+ export function buildMacRemote({ config, target, key, remoteDir, ref = 'HEAD', env = process.env, log = console.log }) {
192
+ if (!target || !/^[A-Za-z0-9._-]+@[A-Za-z0-9._:-]+$/.test(target)) fail('a Mac needs --remote user@host (or WAYBETTER_MAC_SSH)');
193
+ const { root } = config;
194
+ const version = JSON.parse(committedFile(root, 'package.json', ref)).version;
195
+ const productName = JSON.parse(committedFile(root, relTauri(config, 'tauri.conf.json'), ref)).productName;
196
+ const workDir = workDirOf(remoteDir, config.artifact);
197
+ const dir = join(config.outDir, 'build', 'macos');
198
+ mkdirSync(dir, { recursive: true });
199
+ for (const name of readdirSync(dir)) rmSync(join(dir, name), { force: true, recursive: true });
200
+ const scratch = mkdtempSync(join(tmpdir(), 'wb-mac-'));
201
+ try {
202
+ const exported = exportCommitted({ root, file: join(scratch, 'source.tgz'), ref, exclude: config.exportExclude, log });
203
+ const short = exported.sha.slice(0, 12);
204
+ const script = macBuildScript({ workDir, sha: exported.sha, artifact: config.artifact, productName, version, desktopDir: relative(root, config.desktopDir).replaceAll('\\', '/'), prepareCommand: config.prepareCommand });
205
+ writeFileSync(join(scratch, 'build.sh'), script.replaceAll('\r\n', '\n'));
206
+ const ssh = sshOptions({ key });
207
+ stream('ssh', [...ssh, target, `mkdir -p "$HOME/${workDir}" && find "$HOME/${workDir}" -maxdepth 1 -name 'source-*.tgz' -delete`], { log, label: 'mac: prepare' });
208
+ stream('scp', [...ssh, join(scratch, 'source.tgz'), `${target}:${workDir}/source-${short}.tgz`], { log, label: 'mac: upload' });
209
+ stream('scp', [...ssh, join(scratch, 'build.sh'), `${target}:${workDir}/build.sh`], { log, label: 'mac: upload script' });
210
+ const started = Date.now();
211
+ // A login shell: bun, cargo and node are set up in the Mac account's profile only.
212
+ stream('ssh', [...ssh, target, `zsh -lic "bash $HOME/${workDir}/build.sh"`], { log, label: 'mac: build' });
213
+ log(`mac: build took ${Math.round((Date.now() - started) / 1000)} s`);
214
+ stream('scp', [...ssh, `${target}:${workDir}/out/${config.artifact}_${version}_universal.dmg`, `${target}:${workDir}/out/${config.artifact}_${version}_universal.app.tar.gz`, dir], { log, label: 'mac: fetch' });
215
+ return { version, sha: exported.sha, dir, dmg: join(dir, `${config.artifact}_${version}_universal.dmg`), updater: join(dir, `${config.artifact}_${version}_universal.app.tar.gz`) };
216
+ } finally {
217
+ rmSync(scratch, { recursive: true, force: true });
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Builds the committed state of the app in a WSL distro (Ubuntu 22.04: the glibc floor) and puts
223
+ * the AppImage and the .deb in `<outDir>/build/linux`. Returns `{ version, sha, dir, appimage, deb }`.
224
+ */
225
+ export function buildLinuxWsl({ config, distro = 'Ubuntu-22.04', remoteDir, ref = 'HEAD', env = process.env, log = console.log }) {
226
+ if (process.platform !== 'win32') fail('--wsl runs from Windows; on Linux run the build on the machine itself');
227
+ if (!/^[A-Za-z0-9._-]+$/.test(distro)) fail('bad WSL distro name');
228
+ const { root } = config;
229
+ const version = JSON.parse(committedFile(root, 'package.json', ref)).version;
230
+ const workDir = workDirOf(remoteDir, config.artifact);
231
+ const dir = join(config.outDir, 'build', 'linux');
232
+ mkdirSync(dir, { recursive: true });
233
+ for (const name of readdirSync(dir)) rmSync(join(dir, name), { force: true, recursive: true });
234
+ const scratch = mkdtempSync(join(tmpdir(), 'wb-linux-'));
235
+ try {
236
+ const exported = exportCommitted({ root, file: join(scratch, 'source.tgz'), ref, exclude: config.exportExclude, log });
237
+ const script = linuxBuildScript({ workDir, sha: exported.sha, artifact: config.artifact, version, desktopDir: relative(root, config.desktopDir).replaceAll('\\', '/'), prepareCommand: config.prepareCommand, tarball: join(scratch, 'source.tgz'), outDir: dir });
238
+ writeFileSync(join(scratch, 'build.sh'), script.replaceAll('\r\n', '\n'));
239
+ const started = Date.now();
240
+ // MSYS_NO_PATHCONV: Git Bash must not rewrite the /mnt paths of the script.
241
+ const wslScript = `/mnt/${scratch[0].toLowerCase()}/${scratch.slice(3).replaceAll('\\', '/')}/build.sh`;
242
+ const result = spawnSync('wsl', ['-d', distro, '--', 'bash', wslScript], { stdio: 'inherit', windowsHide: true, env: { ...env, MSYS_NO_PATHCONV: '1' } });
243
+ if (result.status !== 0) fail(`linux build in WSL ${distro} failed (exit ${result.status})`);
244
+ log(`linux: build took ${Math.round((Date.now() - started) / 1000)} s`);
245
+ return { version, sha: exported.sha, dir, appimage: join(dir, `${config.artifact}_${version}_amd64.AppImage`), deb: join(dir, `${config.artifact}_${version}_amd64.deb`) };
246
+ } finally {
247
+ rmSync(scratch, { recursive: true, force: true });
248
+ }
249
+ }
250
+
251
+ const relTauri = (config, name) => `${relative(config.root, config.tauriDir).replaceAll('\\', '/')}/${name}`;
252
+
253
+ /** Scans built artifacts here (the terms never leave this machine) and prints their sizes. */
254
+ export function scanBuilt(config, files, { env = process.env, log = console.log } = {}) {
255
+ for (const file of files) {
256
+ if (!existsSync(file)) fail(`expected build output missing: ${file}`);
257
+ log(`built: ${basename(file)} ${(statSync(file).size / 1048576).toFixed(1)} MB`);
258
+ }
259
+ assertClean(files, { terms: config.forbiddenTerms, allowlist: config.privacyAllowlist, cwd: config.root, env, log, label: 'release: privacy-scan' });
260
+ }
package/cli/store.mjs ADDED
@@ -0,0 +1,326 @@
1
+ // `waybetter store build|sign|verify`: builds an app's part of the shared wayBetter Store catalog,
2
+ // signs it offline with the store's Ed25519 key, and verifies it. The rules are the ones the apps
3
+ // enforce natively (waybetter-host `store::catalog`); docs/STORE.md in the framework has the format.
4
+ //
5
+ // waybetter store build --config store.config.mjs [--out dir] [--base https://…/store/]
6
+ // waybetter store sign [catalog.json] --key <private key> [--pub <public key file>]
7
+ // waybetter store sign --init --key <private key> --pub <public key file> (once; never overwrites)
8
+ // waybetter store verify [catalog.json] --pub <public key file> [--app id]
9
+ //
10
+ // The private key is a PKCS#8 PEM that stays where it is: nothing here prints, copies or moves it.
11
+ // `--key` defaults to $WAYBETTER_STORE_KEY. The signature file `<catalog>.sig` is the base64 of the
12
+ // 64-byte signature over the exact catalog bytes; a public key file is the base64 of the raw 32 bytes.
13
+ import { createHash, createPrivateKey, createPublicKey, generateKeyPairSync, sign, verify } from 'node:crypto';
14
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
15
+ import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
16
+ import { pathToFileURL } from 'node:url';
17
+
18
+ export const CATALOG_FORMAT = 2;
19
+ export const DEFAULT_BASE = 'https://waybetter.fyi/store/';
20
+ /** The framework's download hosts (waybetter-host `DEFAULT_HOSTS`). */
21
+ export const DEFAULT_HOSTS = [
22
+ { host: 'waybetter.fyi', rawFilesOnly: false },
23
+ { host: 'cdn1.crt.fyi', rawFilesOnly: false },
24
+ { host: 'git.crt.fyi', rawFilesOnly: true },
25
+ ];
26
+ export const PERMISSIONS = ['storage', 'ui', 'clipboard', 'account', 'users', 'commands', 'network', 'network:none'];
27
+ const MAX_BYTES = 2 * 1024 * 1024;
28
+ const MAX_ITEMS = 500;
29
+ const MAX_CATALOG_BYTES = 1024 * 1024;
30
+ // SPKI DER prefix of an Ed25519 public key; the raw 32 bytes follow it.
31
+ const SPKI_PREFIX = Buffer.from('302a300506032b6570032100', 'hex');
32
+
33
+ export const sha256 = bytes => createHash('sha256').update(bytes).digest('hex');
34
+
35
+ /** A plugin header (waybetter-host `parse_meta`), enough for the catalog: name, description, version, author, permissions. */
36
+ export function pluginHeader(source) {
37
+ const meta = { name: '', description: '', version: '', author: '', permissions: null };
38
+ const body = /^?\s*\/\*\*([\s\S]*?)\*\//.exec(source)?.[1];
39
+ if (!body) return meta;
40
+ for (const raw of body.split(/\r?\n/)) {
41
+ const line = raw.trim().replace(/^\*+/, '').trim();
42
+ const match = /^@(\w+)\s*(.*)$/.exec(line);
43
+ if (!match) continue;
44
+ const [, key, value] = match;
45
+ if (key === 'permissions') {
46
+ const list = value.split(/[\s,]+/).map(token => token.toLowerCase()).filter(token => token && token !== 'none');
47
+ for (const token of list) if (!PERMISSIONS.includes(token)) throw new Error(`unknown permission ${token}`);
48
+ meta.permissions = [...new Set([...(meta.permissions ?? []), ...list])];
49
+ // Same cap as the plugin store's parse_meta (300 characters, no control characters).
50
+ } else if (key in meta) meta[key] = [...value.trim()].filter(ch => ch >= ' ').slice(0, 300).join('');
51
+ }
52
+ return meta;
53
+ }
54
+
55
+ // ---- Validation (the same rules as waybetter-host store::catalog) ------------------------------
56
+
57
+ const validAppId = id => typeof id === 'string' && /^[a-z0-9-]{1,32}$/.test(id);
58
+ const validId = id => typeof id === 'string' && /^[a-z0-9-]{1,40}$/.test(id);
59
+ const text = (value, max) => typeof value === 'string' && [...value].length <= max && !/[\p{Cc}]/u.test(value);
60
+ const hex = value => typeof value === 'string' && /^#[0-9a-fA-F]{6}$/.test(value);
61
+ export const validFileName = file => typeof file === 'string' && file.length > '.plugin.js'.length && file.length <= 128
62
+ && file.endsWith('.plugin.js') && !file.startsWith('.') && /^[A-Za-z0-9_\-. ]+$/.test(file);
63
+
64
+ /** An https URL native code may download from (waybetter-host `allowed_download_url`). */
65
+ export function allowedUrl(value, hosts = DEFAULT_HOSTS) {
66
+ let url;
67
+ try { url = new URL(value); } catch { return false; }
68
+ if (url.protocol !== 'https:' || url.username || url.password || url.port || url.hash || url.href.length > 2048) return false;
69
+ const rule = hosts.find(h => h.host === url.hostname);
70
+ if (!rule) return false;
71
+ if (rule.rawFilesOnly) {
72
+ const segments = url.pathname.split('/').slice(1);
73
+ return segments.length >= 4 && segments[2] === 'raw' && segments.every(s => s && s !== '..');
74
+ }
75
+ return true;
76
+ }
77
+
78
+ const ITEM_FIELDS = new Set(['apps', 'kind', 'id', 'name', 'author', 'version', 'description', 'category', 'tags', 'url', 'file', 'sha256', 'size', 'preview', 'permissions', 'colors', 'language', 'license', 'credits']);
79
+ const LANGUAGE = /^[A-Za-z]{2,8}(-[A-Za-z0-9]{1,8})*$/;
80
+
81
+ /** Problems with one item; `hosts` checks the download hosts (the reading app's own items). */
82
+ export function itemProblems(item, hosts) {
83
+ const bad = [];
84
+ if (!item || typeof item !== 'object' || Array.isArray(item)) return ['not an object'];
85
+ for (const key of Object.keys(item)) if (!ITEM_FIELDS.has(key)) bad.push(`unknown field ${key}`);
86
+ if (!Array.isArray(item.apps) || !item.apps.length || item.apps.length > 16 || !item.apps.every(validAppId) || new Set(item.apps).size !== item.apps.length) bad.push('apps');
87
+ if (item.kind !== 'theme' && item.kind !== 'plugin' && item.kind !== 'strings') bad.push('kind');
88
+ // Any item may name its licence and credit its authors; only a string pack has a language.
89
+ if ((item.license !== undefined && (!text(item.license, 60) || !String(item.license).trim())) || (item.credits !== undefined && !text(item.credits, 600))) bad.push('license or credits');
90
+ if (item.kind !== 'strings' && item.language !== undefined) bad.push('language');
91
+ if (!validId(item.id)) bad.push('id');
92
+ if (!text(item.name, 60) || !String(item.name).trim() || !text(item.author ?? '', 60) || !text(item.description ?? '', 600)) bad.push('name, author or description');
93
+ if (!text(item.version, 32) || !item.version || !text(item.category ?? '', 32)) bad.push('version or category');
94
+ if (item.tags !== undefined && (!Array.isArray(item.tags) || item.tags.length > 12 || !item.tags.every(tag => text(tag, 32)))) bad.push('tags');
95
+ if (typeof item.url !== 'string' || !item.url.startsWith('https://') || (hosts && !allowedUrl(item.url, hosts))) bad.push('url (host not allowed)');
96
+ if (item.preview !== undefined && (typeof item.preview !== 'string' || !item.preview.startsWith('https://') || (hosts && !allowedUrl(item.preview, hosts)))) bad.push('preview');
97
+ if (typeof item.sha256 !== 'string' || !/^[0-9a-f]{64}$/.test(item.sha256)) bad.push('sha256');
98
+ if (!Number.isInteger(item.size) || item.size <= 0 || item.size > MAX_BYTES) bad.push('size');
99
+ if (item.kind === 'plugin') {
100
+ if (!validFileName(item.file) || item.colors != null) bad.push('plugin file');
101
+ const p = item.permissions;
102
+ if (p != null && (!Array.isArray(p) || !p.every(x => PERMISSIONS.includes(x)) || new Set(p).size !== p.length || (p.includes('network') && p.includes('network:none')))) bad.push('permissions');
103
+ }
104
+ if (item.kind === 'strings') {
105
+ if (item.file !== undefined || item.permissions !== undefined || item.colors !== undefined) bad.push('string pack fields');
106
+ if (typeof item.language !== 'string' || !LANGUAGE.test(item.language)) bad.push('language');
107
+ }
108
+ if (item.kind === 'theme') {
109
+ if (item.file !== undefined || item.permissions !== undefined) bad.push('theme fields');
110
+ const c = item.colors;
111
+ if (c != null && (typeof c !== 'object' || Object.keys(c).some(k => !['bg', 'surface', 'text', 'accent'].includes(k)) || !hex(c.bg) || !hex(c.text) || !hex(c.accent) || (c.surface !== undefined && !hex(c.surface)))) bad.push('colors');
112
+ }
113
+ return bad;
114
+ }
115
+
116
+ /** Problems with a whole catalog (empty = valid). `app`: that app's items are held to `hosts`. */
117
+ export function catalogProblems(catalog, { app, hosts = DEFAULT_HOSTS } = {}) {
118
+ const out = [];
119
+ if (!catalog || typeof catalog !== 'object') return ['catalog: not an object'];
120
+ for (const key of Object.keys(catalog)) if (!['format', 'name', 'generatedAt', 'items'].includes(key)) out.push(`catalog: unknown field ${key}`);
121
+ if (catalog.format !== CATALOG_FORMAT) out.push(`catalog: format must be ${CATALOG_FORMAT}`);
122
+ if (typeof catalog.name !== 'string' || typeof catalog.generatedAt !== 'string') out.push('catalog: name and generatedAt are strings');
123
+ if (!Array.isArray(catalog.items)) return [...out, 'catalog: items must be a list'];
124
+ if (catalog.items.length > MAX_ITEMS) out.push(`catalog: more than ${MAX_ITEMS} items`);
125
+ const ids = new Set();
126
+ const files = new Set();
127
+ for (const item of catalog.items) {
128
+ const where = `item ${item?.kind}:${item?.id}`;
129
+ for (const problem of itemProblems(item, app && item?.apps?.includes(app) ? hosts : undefined)) out.push(`${where}: invalid ${problem}`);
130
+ for (const a of Array.isArray(item?.apps) ? item.apps : []) {
131
+ const id = `${a}/${item.kind}/${item.id}`;
132
+ if (ids.has(id)) out.push(`${where}: duplicate for app ${a}`);
133
+ ids.add(id);
134
+ if (item.file) {
135
+ const file = `${a}/${String(item.file).toLowerCase()}`;
136
+ if (files.has(file)) out.push(`${where}: duplicate plugin file for app ${a}`);
137
+ files.add(file);
138
+ }
139
+ }
140
+ }
141
+ return out;
142
+ }
143
+
144
+ // ---- Build ------------------------------------------------------------------------------------
145
+
146
+ /**
147
+ * Builds `config.app`'s part of the shared catalog in `out`: writes the files the config returns,
148
+ * replaces the app's items (other apps' items stay as they are), removes files only the app's old
149
+ * items used, validates the whole catalog and writes `catalog.json` (sign it afterwards).
150
+ *
151
+ * config: { app, name?, base?, out?, hosts?, items(context) → [{ kind, id, name, author, version,
152
+ * description, category, tags, file (plugins), permissions (plugins), colors (themes), language
153
+ * (string packs), license, credits,
154
+ * path ('plugins/x.plugin.js'), bytes (Buffer|string), preview?: { path, bytes } }] }
155
+ */
156
+ export async function buildStore(config, options = {}) {
157
+ const app = config.app;
158
+ if (!validAppId(app)) throw new Error('config.app must be an app id (a-z, 0-9, -)');
159
+ const base = options.base ?? config.base ?? DEFAULT_BASE;
160
+ if (!/^https:\/\/[^/]+\/(.*\/)?$/.test(base)) throw new Error('base must be an https URL ending in /');
161
+ const out = resolve(options.out ?? config.out ?? '.');
162
+ const hosts = config.hosts ?? DEFAULT_HOSTS;
163
+ const catalogPath = join(out, 'catalog.json');
164
+ const previous = existsSync(catalogPath) ? JSON.parse(readFileSync(catalogPath, 'utf8')) : null;
165
+ const kept = [];
166
+ const oldFiles = new Set();
167
+ for (const item of previous?.format === CATALOG_FORMAT ? previous.items : []) {
168
+ if (!item.apps?.includes(app)) { kept.push(item); continue; }
169
+ for (const url of [item.url, item.preview]) if (url?.startsWith(base)) oldFiles.add(url.slice(base.length));
170
+ const others = item.apps.filter(a => a !== app);
171
+ if (others.length) kept.push({ ...item, apps: others });
172
+ }
173
+ const inside = path => {
174
+ const target = resolve(out, path);
175
+ const rel = relative(out, target);
176
+ if (!rel || rel.startsWith('..') || isAbsolute(rel) || !/^[\w.\-/]+$/.test(path)) throw new Error(`file path must stay inside the store folder: ${path}`);
177
+ return target;
178
+ };
179
+ const entries = await config.items({ app, base, out });
180
+ const items = [];
181
+ const files = new Map();
182
+ const toBytes = value => (Buffer.isBuffer(value) ? value : Buffer.from(String(value), 'utf8'));
183
+ for (const entry of entries) {
184
+ const bytes = toBytes(entry.bytes);
185
+ files.set(entry.path, { target: inside(entry.path), bytes });
186
+ const item = { apps: [app], kind: entry.kind, id: entry.id, name: entry.name, author: entry.author ?? '', version: entry.version, description: entry.description ?? '', category: entry.category ?? '', tags: entry.tags ?? [], url: `${base}${entry.path}` };
187
+ if (entry.kind === 'plugin') item.file = entry.file;
188
+ item.sha256 = sha256(bytes);
189
+ item.size = bytes.length;
190
+ if (entry.preview) {
191
+ files.set(entry.preview.path, { target: inside(entry.preview.path), bytes: toBytes(entry.preview.bytes) });
192
+ item.preview = `${base}${entry.preview.path}`;
193
+ }
194
+ if (entry.kind === 'plugin') item.permissions = entry.permissions ?? null;
195
+ if (entry.kind === 'theme' && entry.colors) item.colors = entry.colors;
196
+ if (entry.kind === 'strings') item.language = entry.language;
197
+ if (entry.license !== undefined) item.license = entry.license;
198
+ if (entry.credits !== undefined) item.credits = entry.credits;
199
+ items.push(item);
200
+ }
201
+ const catalog = {
202
+ format: CATALOG_FORMAT,
203
+ name: config.name ?? previous?.name ?? 'wayBetter Store',
204
+ generatedAt: new Date().toISOString().replace(/\.\d+Z$/, 'Z'),
205
+ items: [...kept, ...items],
206
+ };
207
+ // Nothing is written unless the whole catalog is valid.
208
+ const problems = catalogProblems(catalog, { app, hosts });
209
+ if (problems.length) throw new Error(`the catalog would be invalid:\n ${problems.join('\n ')}`);
210
+ const json = `${JSON.stringify(catalog, null, 2)}\n`;
211
+ if (Buffer.byteLength(json) > MAX_CATALOG_BYTES) throw new Error('the catalog is larger than apps accept (1 MiB)');
212
+ for (const { target, bytes } of files.values()) { mkdirSync(dirname(target), { recursive: true }); writeFileSync(target, bytes); }
213
+ // Files this app's previous build wrote and no item uses any more (never another app's files).
214
+ const stillUsed = new Set(kept.flatMap(item => [item.url, item.preview]).filter(url => url?.startsWith(base)).map(url => url.slice(base.length)));
215
+ for (const path of oldFiles) if (!files.has(path) && !stillUsed.has(path)) rmSync(inside(path), { force: true });
216
+ mkdirSync(out, { recursive: true });
217
+ writeFileSync(catalogPath, json);
218
+ return { catalog, path: catalogPath, items };
219
+ }
220
+
221
+ // ---- Keys and signatures ----------------------------------------------------------------------
222
+
223
+ export function rawPublicKey(publicKey) {
224
+ const der = publicKey.export({ format: 'der', type: 'spki' });
225
+ if (der.length !== 44 || !der.subarray(0, 12).equals(SPKI_PREFIX)) throw new Error('not an Ed25519 public key');
226
+ return der.subarray(12);
227
+ }
228
+
229
+ export function publicKeyFromBase64(value) {
230
+ const raw = Buffer.from(value.trim(), 'base64');
231
+ if (raw.length !== 32) throw new Error('a public key file must be the base64 of 32 raw bytes');
232
+ return createPublicKey({ key: Buffer.concat([SPKI_PREFIX, raw]), format: 'der', type: 'spki' });
233
+ }
234
+
235
+ /** Creates the store key once (0600, never overwritten) and writes its public key file. */
236
+ export function initKey(keyPath, pubPath) {
237
+ if (existsSync(keyPath)) {
238
+ const base64 = rawPublicKey(createPublicKey(createPrivateKey(readFileSync(keyPath)))).toString('base64');
239
+ if (!existsSync(pubPath) || readFileSync(pubPath, 'utf8').trim() !== base64) { mkdirSync(dirname(pubPath), { recursive: true }); writeFileSync(pubPath, `${base64}\n`); }
240
+ return { created: false, publicKey: base64 };
241
+ }
242
+ mkdirSync(dirname(keyPath), { recursive: true });
243
+ const { privateKey, publicKey } = generateKeyPairSync('ed25519');
244
+ writeFileSync(keyPath, privateKey.export({ format: 'pem', type: 'pkcs8' }), { mode: 0o600, flag: 'wx' });
245
+ const base64 = rawPublicKey(publicKey).toString('base64');
246
+ mkdirSync(dirname(pubPath), { recursive: true });
247
+ writeFileSync(pubPath, `${base64}\n`);
248
+ return { created: true, publicKey: base64 };
249
+ }
250
+
251
+ /** Signs the exact bytes of `file` into `<file>.sig`; returns the public key (base64) that verifies it. */
252
+ export function signFile(file, keyPath) {
253
+ const privateKey = createPrivateKey(readFileSync(keyPath));
254
+ const signature = sign(null, readFileSync(file), privateKey).toString('base64');
255
+ writeFileSync(`${file}.sig`, `${signature}\n`);
256
+ return rawPublicKey(createPublicKey(privateKey)).toString('base64');
257
+ }
258
+
259
+ /** Whether `<file>.sig` verifies with any of the public keys (base64 strings). */
260
+ export function verifyFile(file, publicKeys) {
261
+ const signature = Buffer.from(readFileSync(`${file}.sig`, 'utf8').trim(), 'base64');
262
+ if (signature.length !== 64) return false;
263
+ const body = readFileSync(file);
264
+ return publicKeys.some(key => verify(null, body, publicKeyFromBase64(key), signature));
265
+ }
266
+
267
+ // ---- Command line -----------------------------------------------------------------------------
268
+
269
+ const USAGE = `Usage:
270
+ waybetter store build --config <store.config.mjs> [--out <dir>] [--base <https://…/store/>]
271
+ waybetter store sign [catalog.json] --key <private key> [--pub <public key file>]
272
+ waybetter store sign --init --key <private key> --pub <public key file>
273
+ waybetter store verify [catalog.json] --pub <public key file>... [--app <id>]`;
274
+
275
+ function parse(argv) {
276
+ const out = { positional: [], pub: [], init: false };
277
+ for (let i = 0; i < argv.length; i += 1) {
278
+ const arg = argv[i];
279
+ const value = () => { const next = argv[++i]; if (next === undefined) throw new Error(`${arg} needs a value`); return next; };
280
+ if (arg === '--init') out.init = true;
281
+ else if (arg === '--pub') out.pub.push(resolve(value()));
282
+ else if (arg === '--key' || arg === '--config' || arg === '--out' || arg === '--base' || arg === '--app') out[arg.slice(2)] = value();
283
+ else if (arg.startsWith('--')) throw new Error(`unknown option ${arg}`);
284
+ else out.positional.push(arg);
285
+ }
286
+ return out;
287
+ }
288
+
289
+ /** Runs `waybetter store …`; returns the lines to print. Throws on errors. */
290
+ export async function storeCommand(argv) {
291
+ const [command, ...rest] = argv;
292
+ const options = parse(rest);
293
+ const catalog = resolve(options.positional[0] ?? 'catalog.json');
294
+ const key = options.key ? resolve(options.key) : process.env.WAYBETTER_STORE_KEY;
295
+ if (command === 'build') {
296
+ if (!options.config) throw new Error(`build needs --config\n${USAGE}`);
297
+ const config = (await import(pathToFileURL(resolve(options.config)).href)).default;
298
+ const { path, items } = await buildStore(config, { out: options.out, base: options.base });
299
+ const count = kind => items.filter(item => item.kind === kind).length;
300
+ return [`store: ${config.app}: ${count('plugin')} plugins, ${count('theme')} themes → ${path}`, 'Now sign it: waybetter store sign <catalog.json> --key <private key>'];
301
+ }
302
+ if (command === 'sign') {
303
+ if (!key) throw new Error('sign needs --key (or WAYBETTER_STORE_KEY)');
304
+ if (options.init) {
305
+ if (!options.pub.length) throw new Error('--init needs --pub');
306
+ const { created, publicKey } = initKey(key, options.pub[0]);
307
+ return [`${created ? 'Created the store key' : 'Store key already exists'}; public key ${publicKey} → ${options.pub[0]}`];
308
+ }
309
+ if (!existsSync(key)) throw new Error(`no store key at ${key} (create it once with --init)`);
310
+ const publicKey = signFile(catalog, key);
311
+ const keys = options.pub.length ? options.pub.map(file => readFileSync(file, 'utf8').trim()) : [publicKey];
312
+ if (!verifyFile(catalog, keys)) throw new Error(`signature of ${catalog} does NOT verify with ${options.pub.join(', ') || 'the key'}`);
313
+ return [`Signed and verified ${catalog}.sig`];
314
+ }
315
+ if (command === 'verify') {
316
+ if (!options.pub.length) throw new Error('verify needs --pub');
317
+ const keys = options.pub.map(file => readFileSync(file, 'utf8').trim());
318
+ if (!verifyFile(catalog, keys)) throw new Error(`signature of ${catalog} does NOT verify with ${options.pub.join(', ')}`);
319
+ const problems = catalogProblems(JSON.parse(readFileSync(catalog, 'utf8')), { app: options.app });
320
+ if (problems.length) throw new Error(`${catalog} is signed but invalid:\n ${problems.join('\n ')}`);
321
+ return [`Verified ${catalog}.sig`];
322
+ }
323
+ throw new Error(USAGE);
324
+ }
325
+
326
+ export { USAGE as STORE_USAGE };