boo-flash 0.1.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.
- package/LICENSE +21 -0
- package/README.md +302 -0
- package/THIRD_PARTY_NOTICES +39 -0
- package/dist/app-bun.js +3890 -0
- package/dist/app-node.js +4020 -0
- package/dist/boo-flash +44 -0
- package/package.json +56 -0
package/dist/app-node.js
ADDED
|
@@ -0,0 +1,4020 @@
|
|
|
1
|
+
// node_modules/@opentui/solid/index.js
|
|
2
|
+
import { CliRenderer, createCliRenderer, engine as engine2 } from "@opentui/core";
|
|
3
|
+
import { createTestRenderer } from "@opentui/core/testing";
|
|
4
|
+
import { ASCIIFontRenderable, BoxRenderable, CodeRenderable, DiffRenderable, ImageRenderable, InputRenderable, LineNumberRenderable, MarkdownRenderable, ScrollBoxRenderable, SelectRenderable, TabSelectRenderable, TextareaRenderable, TextAttributes, TextNodeRenderable, TextRenderable } from "@opentui/core";
|
|
5
|
+
import { engine, Timeline } from "@opentui/core";
|
|
6
|
+
import { createContext, createSignal, onCleanup, onMount, useContext } from "solid-js/dist/solid.js";
|
|
7
|
+
import { createEffect, createMemo as createMemo2, getOwner, onCleanup as onCleanup3, runWithOwner, splitProps, untrack as untrack2 } from "solid-js/dist/solid.js";
|
|
8
|
+
import { BaseRenderable, createTextAttributes, ImageRenderable as ImageRenderable2, InputRenderable as InputRenderable2, InputRenderableEvents, isTextNodeRenderable, isEditBufferRenderable, parseColor, Renderable, RootTextNodeRenderable, ScrollBoxRenderable as ScrollBoxRenderable2, SelectRenderable as SelectRenderable2, SelectRenderableEvents, TabSelectRenderable as TabSelectRenderable2, TabSelectRenderableEvents, TextNodeRenderable as TextNodeRenderable2, TextRenderable as TextRenderable2 } from "@opentui/core";
|
|
9
|
+
|
|
10
|
+
// node_modules/entities/dist/esm/decode-codepoint.js
|
|
11
|
+
var _a;
|
|
12
|
+
var decodeMap = /* @__PURE__ */ new Map([
|
|
13
|
+
[0, 65533],
|
|
14
|
+
// C1 Unicode control character reference replacements
|
|
15
|
+
[128, 8364],
|
|
16
|
+
[130, 8218],
|
|
17
|
+
[131, 402],
|
|
18
|
+
[132, 8222],
|
|
19
|
+
[133, 8230],
|
|
20
|
+
[134, 8224],
|
|
21
|
+
[135, 8225],
|
|
22
|
+
[136, 710],
|
|
23
|
+
[137, 8240],
|
|
24
|
+
[138, 352],
|
|
25
|
+
[139, 8249],
|
|
26
|
+
[140, 338],
|
|
27
|
+
[142, 381],
|
|
28
|
+
[145, 8216],
|
|
29
|
+
[146, 8217],
|
|
30
|
+
[147, 8220],
|
|
31
|
+
[148, 8221],
|
|
32
|
+
[149, 8226],
|
|
33
|
+
[150, 8211],
|
|
34
|
+
[151, 8212],
|
|
35
|
+
[152, 732],
|
|
36
|
+
[153, 8482],
|
|
37
|
+
[154, 353],
|
|
38
|
+
[155, 8250],
|
|
39
|
+
[156, 339],
|
|
40
|
+
[158, 382],
|
|
41
|
+
[159, 376]
|
|
42
|
+
]);
|
|
43
|
+
var fromCodePoint = (
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, n/no-unsupported-features/es-builtins
|
|
45
|
+
(_a = String.fromCodePoint) !== null && _a !== void 0 ? _a : ((codePoint) => {
|
|
46
|
+
let output = "";
|
|
47
|
+
if (codePoint > 65535) {
|
|
48
|
+
codePoint -= 65536;
|
|
49
|
+
output += String.fromCharCode(codePoint >>> 10 & 1023 | 55296);
|
|
50
|
+
codePoint = 56320 | codePoint & 1023;
|
|
51
|
+
}
|
|
52
|
+
output += String.fromCharCode(codePoint);
|
|
53
|
+
return output;
|
|
54
|
+
})
|
|
55
|
+
);
|
|
56
|
+
function replaceCodePoint(codePoint) {
|
|
57
|
+
var _a2;
|
|
58
|
+
if (codePoint >= 55296 && codePoint <= 57343 || codePoint > 1114111) {
|
|
59
|
+
return 65533;
|
|
60
|
+
}
|
|
61
|
+
return (_a2 = decodeMap.get(codePoint)) !== null && _a2 !== void 0 ? _a2 : codePoint;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// node_modules/entities/dist/esm/internal/decode-shared.js
|
|
65
|
+
function decodeBase64(input) {
|
|
66
|
+
const binary = (
|
|
67
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
68
|
+
typeof atob === "function" ? (
|
|
69
|
+
// Browser (and Node >=16)
|
|
70
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
71
|
+
atob(input)
|
|
72
|
+
) : (
|
|
73
|
+
// Older Node versions (<16)
|
|
74
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
75
|
+
typeof Buffer.from === "function" ? (
|
|
76
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
77
|
+
Buffer.from(input, "base64").toString("binary")
|
|
78
|
+
) : (
|
|
79
|
+
// eslint-disable-next-line unicorn/no-new-buffer, n/no-deprecated-api
|
|
80
|
+
new Buffer(input, "base64").toString("binary")
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
);
|
|
84
|
+
const evenLength = binary.length & ~1;
|
|
85
|
+
const out = new Uint16Array(evenLength / 2);
|
|
86
|
+
for (let index = 0, outIndex = 0; index < evenLength; index += 2) {
|
|
87
|
+
const lo = binary.charCodeAt(index);
|
|
88
|
+
const hi = binary.charCodeAt(index + 1);
|
|
89
|
+
out[outIndex++] = lo | hi << 8;
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// node_modules/entities/dist/esm/generated/decode-data-html.js
|
|
95
|
+
var htmlDecodeTree = /* @__PURE__ */ decodeBase64("QR08ALkAAgH6AYsDNQR2BO0EPgXZBQEGLAbdBxMISQrvCmQLfQurDKQNLw4fD4YPpA+6D/IPAAAAAAAAAAAAAAAAKhBMEY8TmxUWF2EYLBkxGuAa3RsJHDscWR8YIC8jSCSIJcMl6ie3Ku8rEC0CLjoupS7kLgAIRU1hYmNmZ2xtbm9wcnN0dVQAWgBeAGUAaQBzAHcAfgCBAIQAhwCSAJoAoACsALMAbABpAGcAO4DGAMZAUAA7gCYAJkBjAHUAdABlADuAwQDBQHIiZXZlAAJhAAFpeW0AcgByAGMAO4DCAMJAEGRyAADgNdgE3XIAYQB2AGUAO4DAAMBA8CFoYZFj4SFjcgBhZAAAoFMqAAFncIsAjgBvAG4ABGFmAADgNdg43fAlbHlGdW5jdGlvbgCgYSBpAG4AZwA7gMUAxUAAAWNzpACoAHIAAOA12Jzc6SFnbgCgVCJpAGwAZABlADuAwwDDQG0AbAA7gMQAxEAABGFjZWZvcnN1xQDYANoA7QDxAPYA+QD8AAABY3LJAM8AayNzbGFzaAAAoBYidgHTANUAAKDnKmUAZAAAoAYjeQARZIABY3J0AOAA5QDrAGEidXNlAACgNSLuI291bGxpcwCgLCFhAJJjcgAA4DXYBd1wAGYAAOA12Dnd5SF2ZdhiYwDyAOoAbSJwZXEAAKBOIgAHSE9hY2RlZmhpbG9yc3UXARoBHwE6AVIBVQFiAWQBZgGCAakB6QHtAfIBYwB5ACdkUABZADuAqQCpQIABY3B5ACUBKAE1AfUhdGUGYWmg0iJ0KGFsRGlmZmVyZW50aWFsRAAAoEUhbCJleXMAAKAtIQACYWVpb0EBRAFKAU0B8iFvbgxhZABpAGwAO4DHAMdAcgBjAAhhbiJpbnQAAKAwIm8AdAAKYQABZG5ZAV0BaSJsbGEAuGB0I2VyRG90ALdg8gA5AWkAp2NyImNsZQAAAkRNUFRwAXQBeQF9AW8AdAAAoJkiaSJudXMAAKCWIuwhdXMAoJUiaSJtZXMAAKCXIm8AAAFjc4cBlAFrKndpc2VDb250b3VySW50ZWdyYWwAAKAyImUjQ3VybHkAAAFEUZwBpAFvJXVibGVRdW90ZQAAoB0gdSJvdGUAAKAZIAACbG5wdbABtgHNAdgBbwBuAGWgNyIAoHQqgAFnaXQAvAHBAcUB8iJ1ZW50AKBhIm4AdAAAoC8i7yV1ckludGVncmFsAKAuIgABZnLRAdMBAKACIe8iZHVjdACgECJuLnRlckNsb2Nrd2lzZUNvbnRvdXJJbnRlZ3JhbAAAoDMi7yFzcwCgLypjAHIAAOA12J7ccABDoNMiYQBwAACgTSKABURKU1phY2VmaW9zAAsCEgIVAhgCGwIsAjQCOQI9AnMCfwNvoEUh9CJyYWhkAKARKWMAeQACZGMAeQAFZGMAeQAPZIABZ3JzACECJQIoAuchZXIAoCEgcgAAoKEhaAB2AACg5CoAAWF5MAIzAvIhb24OYRRkbAB0oAciYQCUY3IAAOA12AfdAAFhZkECawIAAWNtRQJnAvIjaXRpY2FsAAJBREdUUAJUAl8CYwJjInV0ZQC0YG8AdAFZAloC2WJiJGxlQWN1dGUA3WJyImF2ZQBgYGkibGRlANxi7yFuZACgxCJmJWVyZW50aWFsRAAAoEYhcAR9AgAAAAAAAIECjgIAABoDZgAA4DXYO91EoagAhQKJAm8AdAAAoNwgcSJ1YWwAAKBQIuIhbGUAA0NETFJVVpkCqAK1Au8C/wIRA28AbgB0AG8AdQByAEkAbgB0AGUAZwByAGEA7ADEAW8AdAKvAgAAAACwAqhgbiNBcnJvdwAAoNMhAAFlb7kC0AJmAHQAgAFBUlQAwQLGAs0CciJyb3cAAKDQIekkZ2h0QXJyb3cAoNQhZQDlACsCbgBnAAABTFLWAugC5SFmdAABQVLcAuECciJyb3cAAKD4J+kkZ2h0QXJyb3cAoPon6SRnaHRBcnJvdwCg+SdpImdodAAAAUFU9gL7AnIicm93AACg0iFlAGUAAKCoInAAQQIGAwAAAAALA3Iicm93AACg0SFvJHduQXJyb3cAAKDVIWUlcnRpY2FsQmFyAACgJSJuAAADQUJMUlRhJAM2AzoDWgNxA3oDciJyb3cAAKGTIUJVLAMwA2EAcgAAoBMpcCNBcnJvdwAAoPUhciJldmUAEWPlIWZ00gJDAwAASwMAAFIDaSVnaHRWZWN0b3IAAKBQKWUkZVZlY3RvcgAAoF4p5SJjdG9yQqC9IWEAcgAAoFYpaSJnaHQA1AFiAwAAaQNlJGVWZWN0b3IAAKBfKeUiY3RvckKgwSFhAHIAAKBXKWUAZQBBoKQiciJyb3cAAKCnIXIAcgBvAPcAtAIAAWN0gwOHA3IA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YwAAoN8i4SFycnCgtiEAoD0pgKIqImJjZG9zACsVMBU6FT4VQRVyImNhcAAAoEgqAAFhdTQVNxVwAACgRipwAACgSipvAHQAAKCNInIAAKBFKgDgKiIA/gACYWxydksVURVuFXMVcgByAG2gtyEAoDwpeQCAAWV2dwBYFWUVaRVxAHACXxUAAAAAYxVyAGUA4wAXFXUA4wAZFWUAZQAAoM4iZSJkZ2UAAKDPImUAbgA7gKQApEBlI2Fycm93AAABbHJ7FX8V5SFmdACgtiFpImdodAAAoLchZQDkAG0VAAFjaYsVkRVvAG4AaQBuAPQAkwFuAHQAAKAxImwiY3R5AACgLSOACUFIYWJjZGVmaGlqbG9yc3R1d3oAuBW7Fb8V1RXgFegV+RUKFhUWHxZUFlcWZRbFFtsW7xb7FgUXChdyAPIAtAJhAHIAAKBlKQACZ2xyc8YVyhXOFdAV5yFlcgCgICDlIXRoAKA4IfIA9QxoAHagECAAoKMiawHZFd4VYSJyb3cAAKAPKWEA4wBfAgABYXnkFecV8iFvbg9hNGQAoUYhYW/tFfQVAAFnciEC8RVyAACgyiF0InNlcQAAoHcqgAFnbG0A/xUCFgUWO4CwALBAdABhALRjcCJ0eXYAAKCxKQABaXIOFhIW8yFodACgfykA4DXYId1hAHIAAAFschsWHRYAoMMhAKDCIYACYWVnc3YAKBauAjYWOhY+Fm0AAKHEIm9zLhY0Fm4AZABzoMQi9SFpdACgZiZhIm1tYQDdY2kAbgAAoPIiAKH3AGlvQxZRFmQAZQAAgfcAO29KFksW90BuI3RpbWVzAACgxyJuAPgAUBZjAHkAUmRjAG8CXhYAAAAAYhZyAG4AAKAeI28AcAAAoA0jgAJscHR1dwBuFnEWdRaSFp4W7CFhciRgZgAA4DXYVd0AotkCZW1wc30WhBaJFo0WcQBkoFAibwB0AACgUSJpIm51cwAAoDgi7CF1cwCgFCLxInVhcmUAoKEiYgBsAGUAYgBhAHIAdwBlAGQAZwDlANcAbgCAAWFkaAClFqoWtBZyAHIAbwD3APUMbwB3AG4AYQByAHIAbwB3APMA8xVhI3Jwb29uAAABbHK8FsAWZQBmAPQAHBZpAGcAaAD0AB4WYgHJFs8WawBhAHIAbwD3AJILbwLUFgAAAADYFnIAbgAAoB8jbwBwAACgDCOAAWNvdADhFukW7BYAAXJ55RboFgDgNdi53FVkbAAAoPYp8iFvaxFhAAFkcvMW9xZvAHQAAKDxImkA5qC/JVsSAAFhaP8WAhdyAPIANQNhAPIA1wvhIm5nbGUAoKYpAAFjaQ4XEBd5AF9k5yJyYXJyAKD/JwAJRGFjZGVmZ2xtbm9wcXJzdHV4MRc4F0YXWxcyBF4XaRd5F40XrBe0F78X2RcVGCEYLRg1GEAYAAFEbzUXgRZvAPQA+BUAAWNzPBdCF3UAdABlADuA6QDpQPQhZXIAoG4qAAJhaW95TRdQF1YXWhfyIW9uG2FyAGOgViI7gOoA6kDsIW9uAKBVIk1kbwB0ABdhAAFEcmIXZhdvAHQAAKBSIgDgNdgi3XKhmipuF3QXYQB2AGUAO4DoAOhAZKCWKm8AdAAAoJgqgKGZKmlscwCAF4UXhxfuInRlcnMAoOcjAKATIWSglSpvAHQAAKCXKoABYXBzAJMXlheiF2MAcgATYXQAeQBzogUinxcAAAAAoRdlAHQAAKAFInAAMaADIDMBqRerFwCgBCAAoAUgAAFnc7AXsRdLYXAAAKACIAABZ3C4F7sXbwBuABlhZgAA4DXYVt2AAWFscwDFF8sXzxdyAHOg1SJsAACg4yl1AHMAAKBxKmkAAKG1A2x21RfYF28AbgC1Y/VjAAJjc3V24BfoF/0XEBgAAWlv5BdWF3IAYwAAoFYiaQLuFwAAAADwF+0ADQThIW50AAFnbPUX+Rd0AHIAAKCWKuUhc3MAoJUqgAFhZWkAAxgGGAoYbABzAD1gcwB0AACgXyJ2AESgYSJEAACgeCrwImFyc2wAoOUpAAFEYRkYHRhvAHQAAKBTInIAcgAAoHEpgAFjZGkAJxgqGO0XcgAAoC8hbwD0AIwCAAFhaDEYMhi3YzuA8ADwQAABbXI5GD0YbAA7gOsA60BvAACgrCCAAWNpcABGGEgYSxhsACFgcwD0ACwEAAFlb08YVxhjAHQAYQB0AGkAbwDuABoEbgBlAG4AdABpAGEAbADlADME4Ql1GAAAgRgAAIMYiBgAAAAAoRilGAAAqhgAALsYvhjRGAAA1xgnGWwAbABpAG4AZwBkAG8AdABzAGUA8QBlF3kARGRtImFsZQAAoEAmgAFpbHIAjRiRGJ0Y7CFpZwCgA/tpApcYAAAAAJoYZwAAoAD7aQBnAACgBPsA4DXYI93sIWlnAKAB++whaWcA4GYAagCAAWFsdACvGLIYthh0AACgbSZpAGcAAKAC+24AcwAAoLElbwBmAJJh8AHCGAAAxhhmAADgNdhX3QABYWvJGMwYbADsAGsEdqDUIgCg2SphI3J0aW50AACgDSoAAWFv2hgiGQABY3PeGB8ZsQPnGP0YBRkSGRUZAAAdGbID7xjyGPQY9xj5GAAA+xg7gL0AvUAAoFMhO4C8ALxAAKBVIQCgWSEAoFshswEBGQAAAxkAoFQhAKBWIbQCCxkOGQAAAAAQGTuAvgC+QACgVyEAoFwhNQAAoFghtgEZGQAAGxkAoFohAKBdITgAAKBeIWwAAKBEIHcAbgAAoCIjYwByAADgNdi73IAIRWFiY2RlZmdpamxub3JzdHYARhlKGVoZXhlmGWkZkhmWGZkZnRmgGa0ZxhnLGc8Z4BkjGmygZyIAoIwqgAFjbXAAUBlTGVgZ9SF0ZfVhbQBhAOSgswM6FgCghipyImV2ZQAfYQABaXliGWUZcgBjAB1hM2RvAHQAIWGAoWUibHFzAMYEcBl6GfGhZSLOBAAAdhlsAGEAbgD0AN8EgKF+KmNkbACBGYQZjBljAACgqSpvAHQAb6CAKmyggioAoIQqZeDbIgD+cwAAoJQqcgAA4DXYJN3noGsirATtIWVsAKA3IWMAeQBTZIChdyJFYWoApxmpGasZAKCSKgCgpSoAoKQqAAJFYWVztBm2Gb0ZwhkAoGkicABwoIoq8iFveACgiipxoIgq8aCIKrUZaQBtAACg5yJwAGYAAOA12FjdYQB2AOUAYwIAAWNp0xnWGXIAAKAKIW0AAKFzImVs3BneGQCgjioAoJAqAIM+ADtjZGxxco0E6xn0GfgZ/BkBGgABY2nvGfEZAKCnKnIAAKB6Km8AdAAAoNci0CFhcgCglSl1ImVzdAAAoHwqgAJhZGVscwAKGvQZFhrVBCAa8AEPGgAAFBpwAHIAbwD4AFkZcgAAoHgpcQAAAWxxxAQbGmwAZQBzAPMASRlpAO0A5AQAAWVuJxouGnIjdG5lcXEAAOBpIgD+xQAsGgAFQWFiY2Vma29zeUAaQxpmGmoabRqDGocalhrCGtMacgDyAMwCAAJpbG1yShpOGlAaVBpyAHMA8ABxD2YAvWBpAGwA9AASBQABZHJYGlsaYwB5AEpkAKGUIWN3YBpkGmkAcgAAoEgpAKCtIWEAcgAAoA8h6SFyYyVhgAFhbHIAcxp7Gn8a8iF0c3WgZSZpAHQAAKBlJuwhaXAAoCYg4yFvbgCguSJyAADgNdgl3XMAAAFld4wakRphInJvdwAAoCUpYSJyb3cAAKAmKYACYW1vcHIAnxqjGqcauhq+GnIAcgAAoP8h9CFodACgOyJrAAABbHKsGrMaZSRmdGFycm93AACgqSHpJGdodGFycm93AKCqIWYAAOA12Fnd4iFhcgCgFSCAAWNsdADIGswa0BpyAADgNdi93GEAcwDoAGka8iFvaydhAAFicNca2xr1IWxsAKBDIOghZW4AoBAg4Qr2GgAA/RoAAAgbExsaGwAAIRs7GwAAAAA+G2IbmRuVG6sbAACyG80b0htjAHUAdABlADuA7QDtQAChYyBpeQEbBhtyAGMAO4DuAO5AOGQAAWN4CxsNG3kANWRjAGwAO4ChAKFAAAFmcssCFhsA4DXYJt1yAGEAdgBlADuA7ADsQIChSCFpbm8AJxsyGzYbAAFpbisbLxtuAHQAAKAMKnQAAKAtIuYhaW4AoNwpdABhAACgKSHsIWlnM2GAAWFvcABDG1sbXhuAAWNndABJG0sbWRtyACthgAFlbHAAcQVRG1UbaQBuAOUAyAVhAHIA9AByBWgAMWFmAACgtyJlAGQAtWEAoggiY2ZvdGkbbRt1G3kb4SFyZQCgBSFpAG4AdKAeImkAZQAAoN0pZABvAPQAWxsAoisiY2VscIEbhRuPG5QbYQBsAACguiIAAWdyiRuNG2UAcgDzACMQ4wCCG2EicmhrAACgFyryIW9kAKA8KgACY2dwdJ8boRukG6gbeQBRZG8AbgAvYWYAAOA12FrdYQC5Y3UAZQBzAHQAO4C/AL9AAAFjabUbuRtyAADgNdi+3G4AAKIIIkVkc3bCG8QbyBvQAwCg+SJvAHQAAKD1Inag9CIAoPMiaaBiIOwhZGUpYesB1hsAANkbYwB5AFZkbAA7gO8A70AAA2NmbW9zdeYb7hvyG/Ub+hsFHAABaXnqG+0bcgBjADVhOWRyAADgNdgn3eEhdGg3YnAAZgAA4DXYW93jAf8bAAADHHIAAOA12L/c8iFjeVhk6yFjeVRkAARhY2ZnaGpvcxUcGhwiHCYcKhwtHDAcNRzwIXBhdqC6A/BjAAFleR4cIRzkIWls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ZmdobG1vcHJzdHV3CiwNLBEsHSwnLDEsQCxLLFIsYix6LIQsjyzLLOgs7Sz/LAotcgDyAAkDYQByAACgYykAAWNyFSwbLHUAdABlADuA+gD6QPIACQ1yAOMBIywAACUseQBeZHYAZQBtYQABaXkrLDAscgBjADuA+wD7QENkgAFhYmgANyw6LD0scgDyANEO7CFhY3FhYQDyAOAOAAFpckQsSCzzIWh0AKB+KQDgNdgy3XIAYQB2AGUAO4D5APlAYQFWLF8scgAAAWxyWixcLACgvyEAoL4hbABrAACggCUAAWN0Zix2LG8CbCwAAAAAcyxyAG4AZaAcI3IAAKAcI28AcAAAoA8jcgBpAACg+CUAAWFsfiyBLGMAcgBrYTuAqACoQAABZ3CILIssbwBuAHNhZgAA4DXYZt0AA2FkaGxzdZksniynLLgsuyzFLHIAcgBvAPcACQ1vAHcAbgBhAHIAcgBvAPcA2A5hI3Jwb29uAAABbHKvLLMsZQBmAPQAWyxpAGcAaAD0AF0sdQDzAKYOaQAAocUDaGzBLMIs0mNvAG4AxWPwI2Fycm93cwCgyCGAAWNpdADRLOEs5CxvAtcsAAAAAN4scgBuAGWgHSNyAACgHSNvAHAAAKAOI24AZwBvYXIAaQAAoPklYwByAADgNdjK3IABZGlyAPMs9yz6LG8AdAAAoPAi7CFkZWlhaQBmoLUlAKC0JQABYW0DLQYtcgDyAMosbAA7gPwA/EDhIm5nbGUAoKcpgAdBQkRhY2RlZmxub3Byc3oAJy0qLTAtNC2bLZ0toS2/LcMtxy3TLdgt3C3gLfwtcgDyABADYQByAHag6CoAoOkqYQBzAOgA/gIAAW5yOC08LechcnQAoJwpgANla25wcnN0AJkpSC1NLVQtXi1iLYItYQBwAHAA4QAaHG8AdABoAGkAbgDnAKEXgAFoaXIAoSmzJFotbwBwAPQAdCVooJUh7wD4JgABaXVmLWotZwBtAOEAuygAAWJwbi14LXMjZXRuZXEAceCKIgD+AODLKgD+cyNldG5lcQBx4IsiAP4A4MwqAP4AAWhyhi2KLWUAdADhABIraSNhbmdsZQAAAWxyki2WLeUhZnQAoLIiaSJnaHQAAKCzInkAMmThIXNoAKCiIoABZWxyAKcttC24LWKiKCKuLQAAAACyLWEAcgAAoLsicQAAoFoi7CFpcACg7iIAAWJ0vC1eD2EA8gBfD3IAAOA12DPddAByAOkAlS1zAHUAAAFicM0t0C0A4IIi0iAA4IMi0iBwAGYAAOA12GfdcgBvAPAAWQt0AHIA6QCaLQABY3XkLegtcgAA4DXYy9wAAWJw7C30LW4AAAFFZXUt8S0A4IoiAP5uAAABRWV/LfktAOCLIgD+6SJnemFnAKCaKYADY2Vmb3BycwANLhAuJS4pLiMuLi40LukhcmN1YQABZGkULiEuAAFiZxguHC5hAHIAAKBfKmUAcaAnIgCgWSLlIXJwAKAYIXIAAOA12DTdcABmAADgNdho3WWgQCJhAHQA6ABqD2MAcgAA4DXYzNzjCuQRUC4AAFQuAABYLmIuAAAAAGMubS5wLnQuAAAAAIguki4AAJouJxIqEnQAcgDpAB0ScgAA4DXYNd0AAUFhWy5eLnIA8gDnAnIA8gCTB75jAAFBYWYuaS5yAPIA4AJyAPIAjAdhAPAAeh5pAHMAAKD7IoABZHB0APgReS6DLgABZmx9LoAuAOA12GnddQDzAP8RaQBtAOUABBIAAUFhiy6OLnIA8gDuAnIA8gCaBwABY3GVLgoScgAA4DXYzdwAAXB0nS6hLmwAdQDzACUScgDpACASAARhY2VmaW9zdbEuvC7ELsguzC7PLtQu2S5jAAABdXm2LrsudABlADuA/QD9QE9kAAFpecAuwy5yAGMAd2FLZG4AO4ClAKVAcgAA4DXYNt1jAHkAV2RwAGYAAOA12GrdYwByAADgNdjO3AABY23dLt8ueQBOZGwAO4D/AP9AAAVhY2RlZmhpb3N38y73Lv8uAi8MLxAvEy8YLx0vIi9jInV0ZQB6YQABYXn7Lv4u8iFvbn5hN2RvAHQAfGEAAWV0Bi8KL3QAcgDmAB8QYQC2Y3IAAOA12DfdYwB5ADZk5yJyYXJyAKDdIXAAZgAA4DXYa91jAHIAAOA12M/cAAFqbiYvKC8AoA0gagAAoAwg");
|
|
96
|
+
|
|
97
|
+
// node_modules/entities/dist/esm/internal/bin-trie-flags.js
|
|
98
|
+
var BinTrieFlags;
|
|
99
|
+
(function(BinTrieFlags2) {
|
|
100
|
+
BinTrieFlags2[BinTrieFlags2["VALUE_LENGTH"] = 49152] = "VALUE_LENGTH";
|
|
101
|
+
BinTrieFlags2[BinTrieFlags2["FLAG13"] = 8192] = "FLAG13";
|
|
102
|
+
BinTrieFlags2[BinTrieFlags2["BRANCH_LENGTH"] = 8064] = "BRANCH_LENGTH";
|
|
103
|
+
BinTrieFlags2[BinTrieFlags2["JUMP_TABLE"] = 127] = "JUMP_TABLE";
|
|
104
|
+
})(BinTrieFlags || (BinTrieFlags = {}));
|
|
105
|
+
|
|
106
|
+
// node_modules/entities/dist/esm/decode.js
|
|
107
|
+
var CharCodes;
|
|
108
|
+
(function(CharCodes2) {
|
|
109
|
+
CharCodes2[CharCodes2["NUM"] = 35] = "NUM";
|
|
110
|
+
CharCodes2[CharCodes2["SEMI"] = 59] = "SEMI";
|
|
111
|
+
CharCodes2[CharCodes2["EQUALS"] = 61] = "EQUALS";
|
|
112
|
+
CharCodes2[CharCodes2["ZERO"] = 48] = "ZERO";
|
|
113
|
+
CharCodes2[CharCodes2["NINE"] = 57] = "NINE";
|
|
114
|
+
CharCodes2[CharCodes2["LOWER_A"] = 97] = "LOWER_A";
|
|
115
|
+
CharCodes2[CharCodes2["LOWER_F"] = 102] = "LOWER_F";
|
|
116
|
+
CharCodes2[CharCodes2["LOWER_X"] = 120] = "LOWER_X";
|
|
117
|
+
CharCodes2[CharCodes2["LOWER_Z"] = 122] = "LOWER_Z";
|
|
118
|
+
CharCodes2[CharCodes2["UPPER_A"] = 65] = "UPPER_A";
|
|
119
|
+
CharCodes2[CharCodes2["UPPER_F"] = 70] = "UPPER_F";
|
|
120
|
+
CharCodes2[CharCodes2["UPPER_Z"] = 90] = "UPPER_Z";
|
|
121
|
+
})(CharCodes || (CharCodes = {}));
|
|
122
|
+
var TO_LOWER_BIT = 32;
|
|
123
|
+
function isNumber(code2) {
|
|
124
|
+
return code2 >= CharCodes.ZERO && code2 <= CharCodes.NINE;
|
|
125
|
+
}
|
|
126
|
+
function isHexadecimalCharacter(code2) {
|
|
127
|
+
return code2 >= CharCodes.UPPER_A && code2 <= CharCodes.UPPER_F || code2 >= CharCodes.LOWER_A && code2 <= CharCodes.LOWER_F;
|
|
128
|
+
}
|
|
129
|
+
function isAsciiAlphaNumeric(code2) {
|
|
130
|
+
return code2 >= CharCodes.UPPER_A && code2 <= CharCodes.UPPER_Z || code2 >= CharCodes.LOWER_A && code2 <= CharCodes.LOWER_Z || isNumber(code2);
|
|
131
|
+
}
|
|
132
|
+
function isEntityInAttributeInvalidEnd(code2) {
|
|
133
|
+
return code2 === CharCodes.EQUALS || isAsciiAlphaNumeric(code2);
|
|
134
|
+
}
|
|
135
|
+
var EntityDecoderState;
|
|
136
|
+
(function(EntityDecoderState2) {
|
|
137
|
+
EntityDecoderState2[EntityDecoderState2["EntityStart"] = 0] = "EntityStart";
|
|
138
|
+
EntityDecoderState2[EntityDecoderState2["NumericStart"] = 1] = "NumericStart";
|
|
139
|
+
EntityDecoderState2[EntityDecoderState2["NumericDecimal"] = 2] = "NumericDecimal";
|
|
140
|
+
EntityDecoderState2[EntityDecoderState2["NumericHex"] = 3] = "NumericHex";
|
|
141
|
+
EntityDecoderState2[EntityDecoderState2["NamedEntity"] = 4] = "NamedEntity";
|
|
142
|
+
})(EntityDecoderState || (EntityDecoderState = {}));
|
|
143
|
+
var DecodingMode;
|
|
144
|
+
(function(DecodingMode2) {
|
|
145
|
+
DecodingMode2[DecodingMode2["Legacy"] = 0] = "Legacy";
|
|
146
|
+
DecodingMode2[DecodingMode2["Strict"] = 1] = "Strict";
|
|
147
|
+
DecodingMode2[DecodingMode2["Attribute"] = 2] = "Attribute";
|
|
148
|
+
})(DecodingMode || (DecodingMode = {}));
|
|
149
|
+
var EntityDecoder = class {
|
|
150
|
+
constructor(decodeTree, emitCodePoint, errors) {
|
|
151
|
+
this.decodeTree = decodeTree;
|
|
152
|
+
this.emitCodePoint = emitCodePoint;
|
|
153
|
+
this.errors = errors;
|
|
154
|
+
this.state = EntityDecoderState.EntityStart;
|
|
155
|
+
this.consumed = 1;
|
|
156
|
+
this.result = 0;
|
|
157
|
+
this.treeIndex = 0;
|
|
158
|
+
this.excess = 1;
|
|
159
|
+
this.decodeMode = DecodingMode.Strict;
|
|
160
|
+
this.runConsumed = 0;
|
|
161
|
+
}
|
|
162
|
+
/** Resets the instance to make it reusable. */
|
|
163
|
+
startEntity(decodeMode) {
|
|
164
|
+
this.decodeMode = decodeMode;
|
|
165
|
+
this.state = EntityDecoderState.EntityStart;
|
|
166
|
+
this.result = 0;
|
|
167
|
+
this.treeIndex = 0;
|
|
168
|
+
this.excess = 1;
|
|
169
|
+
this.consumed = 1;
|
|
170
|
+
this.runConsumed = 0;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Write an entity to the decoder. This can be called multiple times with partial entities.
|
|
174
|
+
* If the entity is incomplete, the decoder will return -1.
|
|
175
|
+
*
|
|
176
|
+
* Mirrors the implementation of `getDecoder`, but with the ability to stop decoding if the
|
|
177
|
+
* entity is incomplete, and resume when the next string is written.
|
|
178
|
+
*
|
|
179
|
+
* @param input The string containing the entity (or a continuation of the entity).
|
|
180
|
+
* @param offset The offset at which the entity begins. Should be 0 if this is not the first call.
|
|
181
|
+
* @returns The number of characters that were consumed, or -1 if the entity is incomplete.
|
|
182
|
+
*/
|
|
183
|
+
write(input, offset) {
|
|
184
|
+
switch (this.state) {
|
|
185
|
+
case EntityDecoderState.EntityStart: {
|
|
186
|
+
if (input.charCodeAt(offset) === CharCodes.NUM) {
|
|
187
|
+
this.state = EntityDecoderState.NumericStart;
|
|
188
|
+
this.consumed += 1;
|
|
189
|
+
return this.stateNumericStart(input, offset + 1);
|
|
190
|
+
}
|
|
191
|
+
this.state = EntityDecoderState.NamedEntity;
|
|
192
|
+
return this.stateNamedEntity(input, offset);
|
|
193
|
+
}
|
|
194
|
+
case EntityDecoderState.NumericStart: {
|
|
195
|
+
return this.stateNumericStart(input, offset);
|
|
196
|
+
}
|
|
197
|
+
case EntityDecoderState.NumericDecimal: {
|
|
198
|
+
return this.stateNumericDecimal(input, offset);
|
|
199
|
+
}
|
|
200
|
+
case EntityDecoderState.NumericHex: {
|
|
201
|
+
return this.stateNumericHex(input, offset);
|
|
202
|
+
}
|
|
203
|
+
case EntityDecoderState.NamedEntity: {
|
|
204
|
+
return this.stateNamedEntity(input, offset);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Switches between the numeric decimal and hexadecimal states.
|
|
210
|
+
*
|
|
211
|
+
* Equivalent to the `Numeric character reference state` in the HTML spec.
|
|
212
|
+
*
|
|
213
|
+
* @param input The string containing the entity (or a continuation of the entity).
|
|
214
|
+
* @param offset The current offset.
|
|
215
|
+
* @returns The number of characters that were consumed, or -1 if the entity is incomplete.
|
|
216
|
+
*/
|
|
217
|
+
stateNumericStart(input, offset) {
|
|
218
|
+
if (offset >= input.length) {
|
|
219
|
+
return -1;
|
|
220
|
+
}
|
|
221
|
+
if ((input.charCodeAt(offset) | TO_LOWER_BIT) === CharCodes.LOWER_X) {
|
|
222
|
+
this.state = EntityDecoderState.NumericHex;
|
|
223
|
+
this.consumed += 1;
|
|
224
|
+
return this.stateNumericHex(input, offset + 1);
|
|
225
|
+
}
|
|
226
|
+
this.state = EntityDecoderState.NumericDecimal;
|
|
227
|
+
return this.stateNumericDecimal(input, offset);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Parses a hexadecimal numeric entity.
|
|
231
|
+
*
|
|
232
|
+
* Equivalent to the `Hexademical character reference state` in the HTML spec.
|
|
233
|
+
*
|
|
234
|
+
* @param input The string containing the entity (or a continuation of the entity).
|
|
235
|
+
* @param offset The current offset.
|
|
236
|
+
* @returns The number of characters that were consumed, or -1 if the entity is incomplete.
|
|
237
|
+
*/
|
|
238
|
+
stateNumericHex(input, offset) {
|
|
239
|
+
while (offset < input.length) {
|
|
240
|
+
const char = input.charCodeAt(offset);
|
|
241
|
+
if (isNumber(char) || isHexadecimalCharacter(char)) {
|
|
242
|
+
const digit = char <= CharCodes.NINE ? char - CharCodes.ZERO : (char | TO_LOWER_BIT) - CharCodes.LOWER_A + 10;
|
|
243
|
+
this.result = this.result * 16 + digit;
|
|
244
|
+
this.consumed++;
|
|
245
|
+
offset++;
|
|
246
|
+
} else {
|
|
247
|
+
return this.emitNumericEntity(char, 3);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return -1;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Parses a decimal numeric entity.
|
|
254
|
+
*
|
|
255
|
+
* Equivalent to the `Decimal character reference state` in the HTML spec.
|
|
256
|
+
*
|
|
257
|
+
* @param input The string containing the entity (or a continuation of the entity).
|
|
258
|
+
* @param offset The current offset.
|
|
259
|
+
* @returns The number of characters that were consumed, or -1 if the entity is incomplete.
|
|
260
|
+
*/
|
|
261
|
+
stateNumericDecimal(input, offset) {
|
|
262
|
+
while (offset < input.length) {
|
|
263
|
+
const char = input.charCodeAt(offset);
|
|
264
|
+
if (isNumber(char)) {
|
|
265
|
+
this.result = this.result * 10 + (char - CharCodes.ZERO);
|
|
266
|
+
this.consumed++;
|
|
267
|
+
offset++;
|
|
268
|
+
} else {
|
|
269
|
+
return this.emitNumericEntity(char, 2);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return -1;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Validate and emit a numeric entity.
|
|
276
|
+
*
|
|
277
|
+
* Implements the logic from the `Hexademical character reference start
|
|
278
|
+
* state` and `Numeric character reference end state` in the HTML spec.
|
|
279
|
+
*
|
|
280
|
+
* @param lastCp The last code point of the entity. Used to see if the
|
|
281
|
+
* entity was terminated with a semicolon.
|
|
282
|
+
* @param expectedLength The minimum number of characters that should be
|
|
283
|
+
* consumed. Used to validate that at least one digit
|
|
284
|
+
* was consumed.
|
|
285
|
+
* @returns The number of characters that were consumed.
|
|
286
|
+
*/
|
|
287
|
+
emitNumericEntity(lastCp, expectedLength) {
|
|
288
|
+
var _a2;
|
|
289
|
+
if (this.consumed <= expectedLength) {
|
|
290
|
+
(_a2 = this.errors) === null || _a2 === void 0 ? void 0 : _a2.absenceOfDigitsInNumericCharacterReference(this.consumed);
|
|
291
|
+
return 0;
|
|
292
|
+
}
|
|
293
|
+
if (lastCp === CharCodes.SEMI) {
|
|
294
|
+
this.consumed += 1;
|
|
295
|
+
} else if (this.decodeMode === DecodingMode.Strict) {
|
|
296
|
+
return 0;
|
|
297
|
+
}
|
|
298
|
+
this.emitCodePoint(replaceCodePoint(this.result), this.consumed);
|
|
299
|
+
if (this.errors) {
|
|
300
|
+
if (lastCp !== CharCodes.SEMI) {
|
|
301
|
+
this.errors.missingSemicolonAfterCharacterReference();
|
|
302
|
+
}
|
|
303
|
+
this.errors.validateNumericCharacterReference(this.result);
|
|
304
|
+
}
|
|
305
|
+
return this.consumed;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Parses a named entity.
|
|
309
|
+
*
|
|
310
|
+
* Equivalent to the `Named character reference state` in the HTML spec.
|
|
311
|
+
*
|
|
312
|
+
* @param input The string containing the entity (or a continuation of the entity).
|
|
313
|
+
* @param offset The current offset.
|
|
314
|
+
* @returns The number of characters that were consumed, or -1 if the entity is incomplete.
|
|
315
|
+
*/
|
|
316
|
+
stateNamedEntity(input, offset) {
|
|
317
|
+
const { decodeTree } = this;
|
|
318
|
+
let current = decodeTree[this.treeIndex];
|
|
319
|
+
let valueLength = (current & BinTrieFlags.VALUE_LENGTH) >> 14;
|
|
320
|
+
while (offset < input.length) {
|
|
321
|
+
if (valueLength === 0 && (current & BinTrieFlags.FLAG13) !== 0) {
|
|
322
|
+
const runLength = (current & BinTrieFlags.BRANCH_LENGTH) >> 7;
|
|
323
|
+
if (this.runConsumed === 0) {
|
|
324
|
+
const firstChar = current & BinTrieFlags.JUMP_TABLE;
|
|
325
|
+
if (input.charCodeAt(offset) !== firstChar) {
|
|
326
|
+
return this.result === 0 ? 0 : this.emitNotTerminatedNamedEntity();
|
|
327
|
+
}
|
|
328
|
+
offset++;
|
|
329
|
+
this.excess++;
|
|
330
|
+
this.runConsumed++;
|
|
331
|
+
}
|
|
332
|
+
while (this.runConsumed < runLength) {
|
|
333
|
+
if (offset >= input.length) {
|
|
334
|
+
return -1;
|
|
335
|
+
}
|
|
336
|
+
const charIndexInPacked = this.runConsumed - 1;
|
|
337
|
+
const packedWord = decodeTree[this.treeIndex + 1 + (charIndexInPacked >> 1)];
|
|
338
|
+
const expectedChar = charIndexInPacked % 2 === 0 ? packedWord & 255 : packedWord >> 8 & 255;
|
|
339
|
+
if (input.charCodeAt(offset) !== expectedChar) {
|
|
340
|
+
this.runConsumed = 0;
|
|
341
|
+
return this.result === 0 ? 0 : this.emitNotTerminatedNamedEntity();
|
|
342
|
+
}
|
|
343
|
+
offset++;
|
|
344
|
+
this.excess++;
|
|
345
|
+
this.runConsumed++;
|
|
346
|
+
}
|
|
347
|
+
this.runConsumed = 0;
|
|
348
|
+
this.treeIndex += 1 + (runLength >> 1);
|
|
349
|
+
current = decodeTree[this.treeIndex];
|
|
350
|
+
valueLength = (current & BinTrieFlags.VALUE_LENGTH) >> 14;
|
|
351
|
+
}
|
|
352
|
+
if (offset >= input.length)
|
|
353
|
+
break;
|
|
354
|
+
const char = input.charCodeAt(offset);
|
|
355
|
+
if (char === CharCodes.SEMI && valueLength !== 0 && (current & BinTrieFlags.FLAG13) !== 0) {
|
|
356
|
+
return this.emitNamedEntityData(this.treeIndex, valueLength, this.consumed + this.excess);
|
|
357
|
+
}
|
|
358
|
+
this.treeIndex = determineBranch(decodeTree, current, this.treeIndex + Math.max(1, valueLength), char);
|
|
359
|
+
if (this.treeIndex < 0) {
|
|
360
|
+
return this.result === 0 || // If we are parsing an attribute
|
|
361
|
+
this.decodeMode === DecodingMode.Attribute && // We shouldn't have consumed any characters after the entity,
|
|
362
|
+
(valueLength === 0 || // And there should be no invalid characters.
|
|
363
|
+
isEntityInAttributeInvalidEnd(char)) ? 0 : this.emitNotTerminatedNamedEntity();
|
|
364
|
+
}
|
|
365
|
+
current = decodeTree[this.treeIndex];
|
|
366
|
+
valueLength = (current & BinTrieFlags.VALUE_LENGTH) >> 14;
|
|
367
|
+
if (valueLength !== 0) {
|
|
368
|
+
if (char === CharCodes.SEMI) {
|
|
369
|
+
return this.emitNamedEntityData(this.treeIndex, valueLength, this.consumed + this.excess);
|
|
370
|
+
}
|
|
371
|
+
if (this.decodeMode !== DecodingMode.Strict && (current & BinTrieFlags.FLAG13) === 0) {
|
|
372
|
+
this.result = this.treeIndex;
|
|
373
|
+
this.consumed += this.excess;
|
|
374
|
+
this.excess = 0;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
offset++;
|
|
378
|
+
this.excess++;
|
|
379
|
+
}
|
|
380
|
+
return -1;
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Emit a named entity that was not terminated with a semicolon.
|
|
384
|
+
*
|
|
385
|
+
* @returns The number of characters consumed.
|
|
386
|
+
*/
|
|
387
|
+
emitNotTerminatedNamedEntity() {
|
|
388
|
+
var _a2;
|
|
389
|
+
const { result, decodeTree } = this;
|
|
390
|
+
const valueLength = (decodeTree[result] & BinTrieFlags.VALUE_LENGTH) >> 14;
|
|
391
|
+
this.emitNamedEntityData(result, valueLength, this.consumed);
|
|
392
|
+
(_a2 = this.errors) === null || _a2 === void 0 ? void 0 : _a2.missingSemicolonAfterCharacterReference();
|
|
393
|
+
return this.consumed;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Emit a named entity.
|
|
397
|
+
*
|
|
398
|
+
* @param result The index of the entity in the decode tree.
|
|
399
|
+
* @param valueLength The number of bytes in the entity.
|
|
400
|
+
* @param consumed The number of characters consumed.
|
|
401
|
+
*
|
|
402
|
+
* @returns The number of characters consumed.
|
|
403
|
+
*/
|
|
404
|
+
emitNamedEntityData(result, valueLength, consumed) {
|
|
405
|
+
const { decodeTree } = this;
|
|
406
|
+
this.emitCodePoint(valueLength === 1 ? decodeTree[result] & ~(BinTrieFlags.VALUE_LENGTH | BinTrieFlags.FLAG13) : decodeTree[result + 1], consumed);
|
|
407
|
+
if (valueLength === 3) {
|
|
408
|
+
this.emitCodePoint(decodeTree[result + 2], consumed);
|
|
409
|
+
}
|
|
410
|
+
return consumed;
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Signal to the parser that the end of the input was reached.
|
|
414
|
+
*
|
|
415
|
+
* Remaining data will be emitted and relevant errors will be produced.
|
|
416
|
+
*
|
|
417
|
+
* @returns The number of characters consumed.
|
|
418
|
+
*/
|
|
419
|
+
end() {
|
|
420
|
+
var _a2;
|
|
421
|
+
switch (this.state) {
|
|
422
|
+
case EntityDecoderState.NamedEntity: {
|
|
423
|
+
return this.result !== 0 && (this.decodeMode !== DecodingMode.Attribute || this.result === this.treeIndex) ? this.emitNotTerminatedNamedEntity() : 0;
|
|
424
|
+
}
|
|
425
|
+
// Otherwise, emit a numeric entity if we have one.
|
|
426
|
+
case EntityDecoderState.NumericDecimal: {
|
|
427
|
+
return this.emitNumericEntity(0, 2);
|
|
428
|
+
}
|
|
429
|
+
case EntityDecoderState.NumericHex: {
|
|
430
|
+
return this.emitNumericEntity(0, 3);
|
|
431
|
+
}
|
|
432
|
+
case EntityDecoderState.NumericStart: {
|
|
433
|
+
(_a2 = this.errors) === null || _a2 === void 0 ? void 0 : _a2.absenceOfDigitsInNumericCharacterReference(this.consumed);
|
|
434
|
+
return 0;
|
|
435
|
+
}
|
|
436
|
+
case EntityDecoderState.EntityStart: {
|
|
437
|
+
return 0;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
function getDecoder(decodeTree) {
|
|
443
|
+
let returnValue = "";
|
|
444
|
+
const decoder = new EntityDecoder(decodeTree, (data) => returnValue += fromCodePoint(data));
|
|
445
|
+
return function decodeWithTrie(input, decodeMode) {
|
|
446
|
+
let lastIndex = 0;
|
|
447
|
+
let offset = 0;
|
|
448
|
+
while ((offset = input.indexOf("&", offset)) >= 0) {
|
|
449
|
+
returnValue += input.slice(lastIndex, offset);
|
|
450
|
+
decoder.startEntity(decodeMode);
|
|
451
|
+
const length = decoder.write(
|
|
452
|
+
input,
|
|
453
|
+
// Skip the "&"
|
|
454
|
+
offset + 1
|
|
455
|
+
);
|
|
456
|
+
if (length < 0) {
|
|
457
|
+
lastIndex = offset + decoder.end();
|
|
458
|
+
break;
|
|
459
|
+
}
|
|
460
|
+
lastIndex = offset + length;
|
|
461
|
+
offset = length === 0 ? lastIndex + 1 : lastIndex;
|
|
462
|
+
}
|
|
463
|
+
const result = returnValue + input.slice(lastIndex);
|
|
464
|
+
returnValue = "";
|
|
465
|
+
return result;
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
function determineBranch(decodeTree, current, nodeIndex, char) {
|
|
469
|
+
const branchCount = (current & BinTrieFlags.BRANCH_LENGTH) >> 7;
|
|
470
|
+
const jumpOffset = current & BinTrieFlags.JUMP_TABLE;
|
|
471
|
+
if (branchCount === 0) {
|
|
472
|
+
return jumpOffset !== 0 && char === jumpOffset ? nodeIndex : -1;
|
|
473
|
+
}
|
|
474
|
+
if (jumpOffset) {
|
|
475
|
+
const value = char - jumpOffset;
|
|
476
|
+
return value < 0 || value >= branchCount ? -1 : decodeTree[nodeIndex + value] - 1;
|
|
477
|
+
}
|
|
478
|
+
const packedKeySlots = branchCount + 1 >> 1;
|
|
479
|
+
let lo = 0;
|
|
480
|
+
let hi = branchCount - 1;
|
|
481
|
+
while (lo <= hi) {
|
|
482
|
+
const mid = lo + hi >>> 1;
|
|
483
|
+
const slot = mid >> 1;
|
|
484
|
+
const packed = decodeTree[nodeIndex + slot];
|
|
485
|
+
const midKey = packed >> (mid & 1) * 8 & 255;
|
|
486
|
+
if (midKey < char) {
|
|
487
|
+
lo = mid + 1;
|
|
488
|
+
} else if (midKey > char) {
|
|
489
|
+
hi = mid - 1;
|
|
490
|
+
} else {
|
|
491
|
+
return decodeTree[nodeIndex + packedKeySlots + mid];
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return -1;
|
|
495
|
+
}
|
|
496
|
+
var htmlDecoder = /* @__PURE__ */ getDecoder(htmlDecodeTree);
|
|
497
|
+
function decodeHTMLStrict(htmlString) {
|
|
498
|
+
return htmlDecoder(htmlString, DecodingMode.Strict);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// node_modules/entities/dist/esm/escape.js
|
|
502
|
+
var getCodePoint = (
|
|
503
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
504
|
+
String.prototype.codePointAt == null ? (c, index) => (c.charCodeAt(index) & 64512) === 55296 ? (c.charCodeAt(index) - 55296) * 1024 + c.charCodeAt(index + 1) - 56320 + 65536 : c.charCodeAt(index) : (
|
|
505
|
+
// http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
|
|
506
|
+
(input, index) => input.codePointAt(index)
|
|
507
|
+
)
|
|
508
|
+
);
|
|
509
|
+
|
|
510
|
+
// node_modules/entities/dist/esm/index.js
|
|
511
|
+
var EntityLevel;
|
|
512
|
+
(function(EntityLevel2) {
|
|
513
|
+
EntityLevel2[EntityLevel2["XML"] = 0] = "XML";
|
|
514
|
+
EntityLevel2[EntityLevel2["HTML"] = 1] = "HTML";
|
|
515
|
+
})(EntityLevel || (EntityLevel = {}));
|
|
516
|
+
var EncodingMode;
|
|
517
|
+
(function(EncodingMode2) {
|
|
518
|
+
EncodingMode2[EncodingMode2["UTF8"] = 0] = "UTF8";
|
|
519
|
+
EncodingMode2[EncodingMode2["ASCII"] = 1] = "ASCII";
|
|
520
|
+
EncodingMode2[EncodingMode2["Extensive"] = 2] = "Extensive";
|
|
521
|
+
EncodingMode2[EncodingMode2["Attribute"] = 3] = "Attribute";
|
|
522
|
+
EncodingMode2[EncodingMode2["Text"] = 4] = "Text";
|
|
523
|
+
})(EncodingMode || (EncodingMode = {}));
|
|
524
|
+
|
|
525
|
+
// node_modules/@opentui/solid/index.js
|
|
526
|
+
import { onCleanup as onCleanup2, useContext as useContext2 } from "solid-js/dist/solid.js";
|
|
527
|
+
import { createRoot, createRenderEffect, createMemo, createComponent, untrack, mergeProps } from "solid-js/dist/solid.js";
|
|
528
|
+
import { mergeProps as mergeProps2 } from "solid-js/dist/solid.js";
|
|
529
|
+
import { BaseRenderable as BaseRenderable2, isTextNodeRenderable as isTextNodeRenderable2, TextNodeRenderable as TextNodeRenderable3, TextRenderable as TextRenderable3, Yoga } from "@opentui/core";
|
|
530
|
+
import { BoxRenderable as BoxRenderable2, RootRenderable } from "@opentui/core";
|
|
531
|
+
import { createSignal as createSignal2 } from "solid-js/dist/solid.js";
|
|
532
|
+
import { TimeToFirstDrawRenderable } from "@opentui/core";
|
|
533
|
+
import { createSlotRegistry } from "@opentui/core";
|
|
534
|
+
import { children, createMemo as createMemo3, createSignal as createSignal3, ErrorBoundary, For, onCleanup as onCleanup4, splitProps as splitProps2 } from "solid-js/dist/solid.js";
|
|
535
|
+
var SpanRenderable = class extends TextNodeRenderable {
|
|
536
|
+
constructor(_ctx, options) {
|
|
537
|
+
super(options);
|
|
538
|
+
this._ctx = _ctx;
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
var TextModifierRenderable = class extends SpanRenderable {
|
|
542
|
+
constructor(options, modifier) {
|
|
543
|
+
super(null, options);
|
|
544
|
+
if (modifier === "b" || modifier === "strong") {
|
|
545
|
+
this.attributes = (this.attributes || 0) | TextAttributes.BOLD;
|
|
546
|
+
} else if (modifier === "i" || modifier === "em") {
|
|
547
|
+
this.attributes = (this.attributes || 0) | TextAttributes.ITALIC;
|
|
548
|
+
} else if (modifier === "u") {
|
|
549
|
+
this.attributes = (this.attributes || 0) | TextAttributes.UNDERLINE;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
var BoldSpanRenderable = class extends TextModifierRenderable {
|
|
554
|
+
constructor(options) {
|
|
555
|
+
super(options, "b");
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
var ItalicSpanRenderable = class extends TextModifierRenderable {
|
|
559
|
+
constructor(options) {
|
|
560
|
+
super(options, "i");
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
var UnderlineSpanRenderable = class extends TextModifierRenderable {
|
|
564
|
+
constructor(options) {
|
|
565
|
+
super(options, "u");
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
var LineBreakRenderable = class extends SpanRenderable {
|
|
569
|
+
constructor(_ctx, options) {
|
|
570
|
+
super(null, options);
|
|
571
|
+
this.add();
|
|
572
|
+
}
|
|
573
|
+
add() {
|
|
574
|
+
return super.add(`
|
|
575
|
+
`);
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
var LinkRenderable = class extends SpanRenderable {
|
|
579
|
+
constructor(_ctx, options) {
|
|
580
|
+
const linkOptions = {
|
|
581
|
+
...options,
|
|
582
|
+
link: {
|
|
583
|
+
url: options.href
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
super(null, linkOptions);
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
var baseComponents = {
|
|
590
|
+
box: BoxRenderable,
|
|
591
|
+
text: TextRenderable,
|
|
592
|
+
input: InputRenderable,
|
|
593
|
+
select: SelectRenderable,
|
|
594
|
+
textarea: TextareaRenderable,
|
|
595
|
+
ascii_font: ASCIIFontRenderable,
|
|
596
|
+
tab_select: TabSelectRenderable,
|
|
597
|
+
scrollbox: ScrollBoxRenderable,
|
|
598
|
+
code: CodeRenderable,
|
|
599
|
+
diff: DiffRenderable,
|
|
600
|
+
line_number: LineNumberRenderable,
|
|
601
|
+
markdown: MarkdownRenderable,
|
|
602
|
+
image: ImageRenderable,
|
|
603
|
+
span: SpanRenderable,
|
|
604
|
+
strong: BoldSpanRenderable,
|
|
605
|
+
b: BoldSpanRenderable,
|
|
606
|
+
em: ItalicSpanRenderable,
|
|
607
|
+
i: ItalicSpanRenderable,
|
|
608
|
+
u: UnderlineSpanRenderable,
|
|
609
|
+
br: LineBreakRenderable,
|
|
610
|
+
a: LinkRenderable
|
|
611
|
+
};
|
|
612
|
+
var componentCatalogue = {
|
|
613
|
+
...baseComponents
|
|
614
|
+
};
|
|
615
|
+
function extend(objects) {
|
|
616
|
+
Object.assign(componentCatalogue, objects);
|
|
617
|
+
}
|
|
618
|
+
function getComponentCatalogue() {
|
|
619
|
+
return componentCatalogue;
|
|
620
|
+
}
|
|
621
|
+
var RendererContext = createContext();
|
|
622
|
+
var useRenderer = () => {
|
|
623
|
+
const renderer = useContext(RendererContext);
|
|
624
|
+
if (!renderer) {
|
|
625
|
+
throw new Error("No renderer found");
|
|
626
|
+
}
|
|
627
|
+
return renderer;
|
|
628
|
+
};
|
|
629
|
+
var onResize = (callback) => {
|
|
630
|
+
const renderer = useRenderer();
|
|
631
|
+
onMount(() => {
|
|
632
|
+
renderer.on("resize", callback);
|
|
633
|
+
});
|
|
634
|
+
onCleanup(() => {
|
|
635
|
+
renderer.off("resize", callback);
|
|
636
|
+
});
|
|
637
|
+
};
|
|
638
|
+
var useTerminalDimensions = () => {
|
|
639
|
+
const renderer = useRenderer();
|
|
640
|
+
const [terminalDimensions, setTerminalDimensions] = createSignal({
|
|
641
|
+
width: renderer.width,
|
|
642
|
+
height: renderer.height
|
|
643
|
+
});
|
|
644
|
+
const callback = (width, height) => {
|
|
645
|
+
setTerminalDimensions({
|
|
646
|
+
width,
|
|
647
|
+
height
|
|
648
|
+
});
|
|
649
|
+
};
|
|
650
|
+
onResize(callback);
|
|
651
|
+
return terminalDimensions;
|
|
652
|
+
};
|
|
653
|
+
var useKeyboard = (callback, options) => {
|
|
654
|
+
const renderer = useRenderer();
|
|
655
|
+
const keyHandler = renderer.keyInput;
|
|
656
|
+
onMount(() => {
|
|
657
|
+
keyHandler.on("keypress", callback);
|
|
658
|
+
if (options?.release) {
|
|
659
|
+
keyHandler.on("keyrelease", callback);
|
|
660
|
+
}
|
|
661
|
+
});
|
|
662
|
+
onCleanup(() => {
|
|
663
|
+
keyHandler.off("keypress", callback);
|
|
664
|
+
if (options?.release) {
|
|
665
|
+
keyHandler.off("keyrelease", callback);
|
|
666
|
+
}
|
|
667
|
+
});
|
|
668
|
+
};
|
|
669
|
+
var useTimeline = (options = {}) => {
|
|
670
|
+
const timeline = new Timeline(options);
|
|
671
|
+
onMount(() => {
|
|
672
|
+
if (options.autoplay !== false) {
|
|
673
|
+
timeline.play();
|
|
674
|
+
}
|
|
675
|
+
engine.register(timeline);
|
|
676
|
+
});
|
|
677
|
+
onCleanup(() => {
|
|
678
|
+
timeline.pause();
|
|
679
|
+
engine.unregister(timeline);
|
|
680
|
+
});
|
|
681
|
+
return timeline;
|
|
682
|
+
};
|
|
683
|
+
var memo = (fn) => createMemo(() => fn());
|
|
684
|
+
function createRenderer({
|
|
685
|
+
createElement: createElement2,
|
|
686
|
+
createTextNode: createTextNode2,
|
|
687
|
+
createDynamicTextNode,
|
|
688
|
+
createSlotNode: createSlotNode2,
|
|
689
|
+
isTextNode,
|
|
690
|
+
replaceText,
|
|
691
|
+
insertNode: insertNode2,
|
|
692
|
+
removeNode,
|
|
693
|
+
setProperty,
|
|
694
|
+
getParentNode,
|
|
695
|
+
getFirstChild,
|
|
696
|
+
getNextSibling
|
|
697
|
+
}) {
|
|
698
|
+
function insert2(parent, accessor, marker, initial) {
|
|
699
|
+
if (marker !== void 0 && !initial)
|
|
700
|
+
initial = [];
|
|
701
|
+
if (typeof accessor !== "function")
|
|
702
|
+
return insertExpression(parent, accessor, initial, marker);
|
|
703
|
+
createRenderEffect((current) => insertExpression(parent, accessor(), current, marker), initial);
|
|
704
|
+
}
|
|
705
|
+
function insertExpression(parent, value, current, marker, unwrapArray) {
|
|
706
|
+
while (typeof current === "function")
|
|
707
|
+
current = current();
|
|
708
|
+
if (value === current)
|
|
709
|
+
return current;
|
|
710
|
+
const t = typeof value, multi = marker !== void 0;
|
|
711
|
+
if (t === "string" || t === "number") {
|
|
712
|
+
if (t === "number")
|
|
713
|
+
value = value.toString();
|
|
714
|
+
if (multi) {
|
|
715
|
+
let node = current[0];
|
|
716
|
+
if (node && isTextNode(node)) {
|
|
717
|
+
replaceText(node, value);
|
|
718
|
+
} else
|
|
719
|
+
node = createDynamicTextNode(value);
|
|
720
|
+
current = cleanChildren(parent, current, marker, node);
|
|
721
|
+
} else {
|
|
722
|
+
if (current !== "" && typeof current === "string") {
|
|
723
|
+
replaceText(getFirstChild(parent), current = value);
|
|
724
|
+
} else {
|
|
725
|
+
cleanChildren(parent, current, marker, createDynamicTextNode(value));
|
|
726
|
+
current = value;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
} else if (value == null || t === "boolean") {
|
|
730
|
+
current = cleanChildren(parent, current, marker);
|
|
731
|
+
} else if (t === "function") {
|
|
732
|
+
createRenderEffect(() => {
|
|
733
|
+
let v = value();
|
|
734
|
+
while (typeof v === "function")
|
|
735
|
+
v = v();
|
|
736
|
+
current = insertExpression(parent, v, current, marker);
|
|
737
|
+
});
|
|
738
|
+
return () => current;
|
|
739
|
+
} else if (Array.isArray(value)) {
|
|
740
|
+
const array = [];
|
|
741
|
+
if (normalizeIncomingArray(array, value, unwrapArray)) {
|
|
742
|
+
createRenderEffect(() => current = insertExpression(parent, array, current, marker, true));
|
|
743
|
+
return () => current;
|
|
744
|
+
}
|
|
745
|
+
if (array.length === 0) {
|
|
746
|
+
const replacement = cleanChildren(parent, current, marker);
|
|
747
|
+
if (multi)
|
|
748
|
+
return current = replacement;
|
|
749
|
+
} else {
|
|
750
|
+
if (Array.isArray(current)) {
|
|
751
|
+
if (current.length === 0) {
|
|
752
|
+
appendNodes(parent, array, marker);
|
|
753
|
+
} else
|
|
754
|
+
reconcileArrays(parent, current, array);
|
|
755
|
+
} else if (current == null || current === "") {
|
|
756
|
+
appendNodes(parent, array);
|
|
757
|
+
} else {
|
|
758
|
+
reconcileArrays(parent, multi && current || [getFirstChild(parent)], array);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
current = array;
|
|
762
|
+
} else {
|
|
763
|
+
if (Array.isArray(current)) {
|
|
764
|
+
if (multi)
|
|
765
|
+
return current = cleanChildren(parent, current, marker, value);
|
|
766
|
+
cleanChildren(parent, current, null, value);
|
|
767
|
+
} else if (current == null || current === "" || !getFirstChild(parent)) {
|
|
768
|
+
insertNode2(parent, value);
|
|
769
|
+
} else
|
|
770
|
+
replaceNode(parent, value, getFirstChild(parent));
|
|
771
|
+
current = value;
|
|
772
|
+
}
|
|
773
|
+
return current;
|
|
774
|
+
}
|
|
775
|
+
function normalizeIncomingArray(normalized, array, unwrap) {
|
|
776
|
+
let dynamic = false;
|
|
777
|
+
for (let i = 0, len = array.length; i < len; i++) {
|
|
778
|
+
let item = array[i], t;
|
|
779
|
+
if (item == null || item === true || item === false)
|
|
780
|
+
;
|
|
781
|
+
else if (Array.isArray(item)) {
|
|
782
|
+
dynamic = normalizeIncomingArray(normalized, item) || dynamic;
|
|
783
|
+
} else if ((t = typeof item) === "string" || t === "number") {
|
|
784
|
+
normalized.push(createDynamicTextNode(item));
|
|
785
|
+
} else if (t === "function") {
|
|
786
|
+
if (unwrap) {
|
|
787
|
+
while (typeof item === "function")
|
|
788
|
+
item = item();
|
|
789
|
+
dynamic = normalizeIncomingArray(normalized, Array.isArray(item) ? item : [item]) || dynamic;
|
|
790
|
+
} else {
|
|
791
|
+
normalized.push(item);
|
|
792
|
+
dynamic = true;
|
|
793
|
+
}
|
|
794
|
+
} else
|
|
795
|
+
normalized.push(item);
|
|
796
|
+
}
|
|
797
|
+
return dynamic;
|
|
798
|
+
}
|
|
799
|
+
function reconcileArrays(parentNode, a, b) {
|
|
800
|
+
let bLength = b.length, aEnd = a.length, bEnd = bLength, aStart = 0, bStart = 0, after = getNextSibling(a[aEnd - 1]), map = null;
|
|
801
|
+
while (aStart < aEnd || bStart < bEnd) {
|
|
802
|
+
if (a[aStart] === b[bStart]) {
|
|
803
|
+
aStart++;
|
|
804
|
+
bStart++;
|
|
805
|
+
continue;
|
|
806
|
+
}
|
|
807
|
+
while (a[aEnd - 1] === b[bEnd - 1]) {
|
|
808
|
+
aEnd--;
|
|
809
|
+
bEnd--;
|
|
810
|
+
}
|
|
811
|
+
if (aEnd === aStart) {
|
|
812
|
+
const node = bEnd < bLength ? bStart ? getNextSibling(b[bStart - 1]) : b[bEnd - bStart] : after;
|
|
813
|
+
while (bStart < bEnd)
|
|
814
|
+
insertNode2(parentNode, b[bStart++], node);
|
|
815
|
+
} else if (bEnd === bStart) {
|
|
816
|
+
while (aStart < aEnd) {
|
|
817
|
+
if (!map || !map.has(a[aStart]))
|
|
818
|
+
removeNode(parentNode, a[aStart]);
|
|
819
|
+
aStart++;
|
|
820
|
+
}
|
|
821
|
+
} else if (a[aStart] === b[bEnd - 1] && b[bStart] === a[aEnd - 1]) {
|
|
822
|
+
const node = getNextSibling(a[--aEnd]);
|
|
823
|
+
insertNode2(parentNode, b[bStart++], getNextSibling(a[aStart++]));
|
|
824
|
+
insertNode2(parentNode, b[--bEnd], node);
|
|
825
|
+
a[aEnd] = b[bEnd];
|
|
826
|
+
} else {
|
|
827
|
+
if (!map) {
|
|
828
|
+
map = /* @__PURE__ */ new Map();
|
|
829
|
+
let i = bStart;
|
|
830
|
+
while (i < bEnd)
|
|
831
|
+
map.set(b[i], i++);
|
|
832
|
+
}
|
|
833
|
+
const index = map.get(a[aStart]);
|
|
834
|
+
if (index != null) {
|
|
835
|
+
if (bStart < index && index < bEnd) {
|
|
836
|
+
let i = aStart, sequence = 1, t;
|
|
837
|
+
while (++i < aEnd && i < bEnd) {
|
|
838
|
+
if ((t = map.get(a[i])) == null || t !== index + sequence)
|
|
839
|
+
break;
|
|
840
|
+
sequence++;
|
|
841
|
+
}
|
|
842
|
+
if (sequence > index - bStart) {
|
|
843
|
+
const node = a[aStart];
|
|
844
|
+
while (bStart < index)
|
|
845
|
+
insertNode2(parentNode, b[bStart++], node);
|
|
846
|
+
} else
|
|
847
|
+
replaceNode(parentNode, b[bStart++], a[aStart++]);
|
|
848
|
+
} else
|
|
849
|
+
aStart++;
|
|
850
|
+
} else
|
|
851
|
+
removeNode(parentNode, a[aStart++]);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
function cleanChildren(parent, current, marker, replacement) {
|
|
856
|
+
if (marker === void 0) {
|
|
857
|
+
let removed;
|
|
858
|
+
while (removed = getFirstChild(parent))
|
|
859
|
+
removeNode(parent, removed);
|
|
860
|
+
replacement && insertNode2(parent, replacement);
|
|
861
|
+
return replacement ?? "";
|
|
862
|
+
}
|
|
863
|
+
const node = replacement || createSlotNode2();
|
|
864
|
+
if (current.length) {
|
|
865
|
+
let inserted = false;
|
|
866
|
+
for (let i = current.length - 1; i >= 0; i--) {
|
|
867
|
+
const el = current[i];
|
|
868
|
+
if (node !== el) {
|
|
869
|
+
const isParent = getParentNode(el) === parent;
|
|
870
|
+
if (!inserted && !i)
|
|
871
|
+
isParent ? replaceNode(parent, node, el) : insertNode2(parent, node, marker);
|
|
872
|
+
else
|
|
873
|
+
isParent && removeNode(parent, el);
|
|
874
|
+
} else
|
|
875
|
+
inserted = true;
|
|
876
|
+
}
|
|
877
|
+
} else
|
|
878
|
+
insertNode2(parent, node, marker);
|
|
879
|
+
return [node];
|
|
880
|
+
}
|
|
881
|
+
function appendNodes(parent, array, marker) {
|
|
882
|
+
for (let i = 0, len = array.length; i < len; i++)
|
|
883
|
+
insertNode2(parent, array[i], marker);
|
|
884
|
+
}
|
|
885
|
+
function replaceNode(parent, newNode, oldNode) {
|
|
886
|
+
insertNode2(parent, newNode, oldNode);
|
|
887
|
+
removeNode(parent, oldNode);
|
|
888
|
+
}
|
|
889
|
+
function spreadExpression(node, props, prevProps = {}, skipChildren) {
|
|
890
|
+
props || (props = {});
|
|
891
|
+
if (!skipChildren) {
|
|
892
|
+
createRenderEffect(() => prevProps.children = insertExpression(node, props.children, prevProps.children));
|
|
893
|
+
}
|
|
894
|
+
createRenderEffect(() => props.ref && props.ref(node));
|
|
895
|
+
createRenderEffect(() => {
|
|
896
|
+
for (const prop in props) {
|
|
897
|
+
if (prop === "children" || prop === "ref")
|
|
898
|
+
continue;
|
|
899
|
+
const value = props[prop];
|
|
900
|
+
if (value === prevProps[prop])
|
|
901
|
+
continue;
|
|
902
|
+
setProperty(node, prop, value, prevProps[prop]);
|
|
903
|
+
prevProps[prop] = value;
|
|
904
|
+
}
|
|
905
|
+
});
|
|
906
|
+
return prevProps;
|
|
907
|
+
}
|
|
908
|
+
return {
|
|
909
|
+
render(code2, element) {
|
|
910
|
+
let disposer;
|
|
911
|
+
createRoot((dispose) => {
|
|
912
|
+
disposer = dispose;
|
|
913
|
+
insert2(element, code2());
|
|
914
|
+
});
|
|
915
|
+
return disposer;
|
|
916
|
+
},
|
|
917
|
+
insert: insert2,
|
|
918
|
+
spread(node, accessor, skipChildren) {
|
|
919
|
+
if (typeof accessor === "function") {
|
|
920
|
+
createRenderEffect((current) => spreadExpression(node, accessor(), current, skipChildren));
|
|
921
|
+
} else
|
|
922
|
+
spreadExpression(node, accessor, void 0, skipChildren);
|
|
923
|
+
},
|
|
924
|
+
createElement: createElement2,
|
|
925
|
+
createTextNode: createTextNode2,
|
|
926
|
+
insertNode: insertNode2,
|
|
927
|
+
setProp(node, name2, value, prev) {
|
|
928
|
+
setProperty(node, name2, value, prev);
|
|
929
|
+
return value;
|
|
930
|
+
},
|
|
931
|
+
mergeProps,
|
|
932
|
+
effect: createRenderEffect,
|
|
933
|
+
memo,
|
|
934
|
+
createComponent,
|
|
935
|
+
use(fn, element, arg) {
|
|
936
|
+
return untrack(() => fn(element, arg));
|
|
937
|
+
}
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
function createRenderer2(options) {
|
|
941
|
+
const renderer = createRenderer(options);
|
|
942
|
+
renderer.mergeProps = mergeProps2;
|
|
943
|
+
return renderer;
|
|
944
|
+
}
|
|
945
|
+
var idCounter = /* @__PURE__ */ new Map();
|
|
946
|
+
function getNextId(elementType) {
|
|
947
|
+
if (!idCounter.has(elementType)) {
|
|
948
|
+
idCounter.set(elementType, 0);
|
|
949
|
+
}
|
|
950
|
+
const value = idCounter.get(elementType) + 1;
|
|
951
|
+
idCounter.set(elementType, value);
|
|
952
|
+
return `${elementType}-${value}`;
|
|
953
|
+
}
|
|
954
|
+
var log = (...args2) => {
|
|
955
|
+
if (process.env.DEBUG) {
|
|
956
|
+
console.log("[Reconciler]", ...args2);
|
|
957
|
+
}
|
|
958
|
+
};
|
|
959
|
+
var TextNode = class _TextNode extends TextNodeRenderable2 {
|
|
960
|
+
static fromString(text, options = {}) {
|
|
961
|
+
const node = new _TextNode(options);
|
|
962
|
+
node.add(text);
|
|
963
|
+
return node;
|
|
964
|
+
}
|
|
965
|
+
};
|
|
966
|
+
var logId = (node) => {
|
|
967
|
+
if (!node)
|
|
968
|
+
return;
|
|
969
|
+
return node.id;
|
|
970
|
+
};
|
|
971
|
+
var getNodeChildren = (node) => {
|
|
972
|
+
let children2;
|
|
973
|
+
if (node instanceof TextRenderable2) {
|
|
974
|
+
children2 = node.getTextChildren();
|
|
975
|
+
} else {
|
|
976
|
+
children2 = node.getChildren();
|
|
977
|
+
}
|
|
978
|
+
return children2;
|
|
979
|
+
};
|
|
980
|
+
function _insertNode(parent, node, anchor) {
|
|
981
|
+
log("Inserting node:", logId(node), "into parent:", logId(parent), "with anchor:", logId(anchor), node instanceof TextNode);
|
|
982
|
+
if (node instanceof SlotRenderable) {
|
|
983
|
+
node.parent = parent;
|
|
984
|
+
node = node.getSlotChild(parent);
|
|
985
|
+
}
|
|
986
|
+
if (anchor && anchor instanceof SlotRenderable) {
|
|
987
|
+
anchor = anchor.getSlotChild(parent);
|
|
988
|
+
}
|
|
989
|
+
if (isTextNodeRenderable(node)) {
|
|
990
|
+
if (!(parent instanceof TextRenderable2) && !isTextNodeRenderable(parent)) {
|
|
991
|
+
throw new Error(`Orphan text error: "${node.toChunks().map((c) => c.text).join("")}" must have a <text> as a parent: ${parent.id} above ${node.id}`);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
if (!(parent instanceof BaseRenderable)) {
|
|
995
|
+
console.error("[INSERT]", "Tried to mount a non base renderable");
|
|
996
|
+
throw new Error("Tried to mount a non base renderable");
|
|
997
|
+
}
|
|
998
|
+
if (!anchor) {
|
|
999
|
+
parent.add(node);
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
const children2 = getNodeChildren(parent);
|
|
1003
|
+
const anchorIndex = children2.indexOf(anchor);
|
|
1004
|
+
if (anchorIndex === -1) {
|
|
1005
|
+
log("[INSERT]", "Could not find anchor", logId(parent), logId(anchor), "[children]", ...children2.map((c) => c.id));
|
|
1006
|
+
}
|
|
1007
|
+
parent.add(node, anchorIndex);
|
|
1008
|
+
}
|
|
1009
|
+
function _removeNode(parent, node) {
|
|
1010
|
+
log("Removing node:", logId(node), "from parent:", logId(parent));
|
|
1011
|
+
let slotParent;
|
|
1012
|
+
if (node instanceof SlotRenderable) {
|
|
1013
|
+
slotParent = node;
|
|
1014
|
+
const slotChild = slotParent.getSlotChildForRemoval(parent);
|
|
1015
|
+
if (!slotChild) {
|
|
1016
|
+
if (slotParent.parent === parent) {
|
|
1017
|
+
slotParent.parent = null;
|
|
1018
|
+
}
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
node = slotChild;
|
|
1022
|
+
}
|
|
1023
|
+
parent.remove(node);
|
|
1024
|
+
slotParent?.didRemoveSlotChild(parent, node);
|
|
1025
|
+
process.nextTick(() => {
|
|
1026
|
+
if (node instanceof BaseRenderable && !node.parent) {
|
|
1027
|
+
node.destroyRecursively();
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
function _createTextNode(value, decodeEntities) {
|
|
1033
|
+
log("Creating text node:", value);
|
|
1034
|
+
const id = getNextId("text-node");
|
|
1035
|
+
if (typeof value === "number") {
|
|
1036
|
+
value = value.toString();
|
|
1037
|
+
}
|
|
1038
|
+
return TextNode.fromString(decodeEntities ? decodeHTMLStrict(value) : value, {
|
|
1039
|
+
id
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
function createSlotNode() {
|
|
1043
|
+
const id = getNextId("slot-node");
|
|
1044
|
+
log("Creating slot node", id);
|
|
1045
|
+
return new SlotRenderable(id);
|
|
1046
|
+
}
|
|
1047
|
+
function _getParentNode(childNode) {
|
|
1048
|
+
log("Getting parent of node:", logId(childNode));
|
|
1049
|
+
let parent = childNode.parent ?? void 0;
|
|
1050
|
+
if (parent instanceof RootTextNodeRenderable) {
|
|
1051
|
+
parent = parent.textParent ?? void 0;
|
|
1052
|
+
}
|
|
1053
|
+
const scrollBoxCandidate = parent?.parent?.parent?.parent;
|
|
1054
|
+
if (scrollBoxCandidate instanceof ScrollBoxRenderable2 && scrollBoxCandidate.content === parent) {
|
|
1055
|
+
parent = scrollBoxCandidate;
|
|
1056
|
+
}
|
|
1057
|
+
return parent;
|
|
1058
|
+
}
|
|
1059
|
+
var {
|
|
1060
|
+
render: _render,
|
|
1061
|
+
effect,
|
|
1062
|
+
memo: memo2,
|
|
1063
|
+
createComponent: createComponent2,
|
|
1064
|
+
createElement,
|
|
1065
|
+
createTextNode,
|
|
1066
|
+
insertNode,
|
|
1067
|
+
insert,
|
|
1068
|
+
spread,
|
|
1069
|
+
setProp,
|
|
1070
|
+
mergeProps: mergeProps3,
|
|
1071
|
+
use
|
|
1072
|
+
} = createRenderer2({
|
|
1073
|
+
createElement(tagName) {
|
|
1074
|
+
log("Creating element:", tagName);
|
|
1075
|
+
const id = getNextId(tagName);
|
|
1076
|
+
const solidRenderer = useContext2(RendererContext);
|
|
1077
|
+
if (!solidRenderer) {
|
|
1078
|
+
throw new Error("No renderer found");
|
|
1079
|
+
}
|
|
1080
|
+
const elements = getComponentCatalogue();
|
|
1081
|
+
if (!elements[tagName]) {
|
|
1082
|
+
throw new Error(`[Reconciler] Unknown component type: ${tagName}`);
|
|
1083
|
+
}
|
|
1084
|
+
const element = new elements[tagName](solidRenderer, {
|
|
1085
|
+
id
|
|
1086
|
+
});
|
|
1087
|
+
if (element instanceof ImageRenderable2) {
|
|
1088
|
+
onCleanup2(() => {
|
|
1089
|
+
element.source = void 0;
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
log("Element created with id:", id);
|
|
1093
|
+
return element;
|
|
1094
|
+
},
|
|
1095
|
+
createTextNode: (value) => _createTextNode(value, true),
|
|
1096
|
+
createDynamicTextNode: (value) => _createTextNode(value, false),
|
|
1097
|
+
createSlotNode,
|
|
1098
|
+
replaceText(textNode, value) {
|
|
1099
|
+
log("Replacing text:", value, "in node:", logId(textNode));
|
|
1100
|
+
if (!(textNode instanceof TextNode))
|
|
1101
|
+
return;
|
|
1102
|
+
textNode.replace(value, 0);
|
|
1103
|
+
},
|
|
1104
|
+
setProperty(node, name2, value, prev) {
|
|
1105
|
+
if (name2.startsWith("on:")) {
|
|
1106
|
+
const eventName = name2.slice(3);
|
|
1107
|
+
if (value) {
|
|
1108
|
+
node.on(eventName, value);
|
|
1109
|
+
}
|
|
1110
|
+
if (prev) {
|
|
1111
|
+
node.off(eventName, prev);
|
|
1112
|
+
}
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
if (isTextNodeRenderable(node)) {
|
|
1116
|
+
if (name2 === "href") {
|
|
1117
|
+
node.link = {
|
|
1118
|
+
url: value
|
|
1119
|
+
};
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
if (name2 === "style") {
|
|
1123
|
+
node.attributes |= createTextAttributes(value);
|
|
1124
|
+
node.fg = value.fg ? parseColor(value.fg) : node.fg;
|
|
1125
|
+
node.bg = value.bg ? parseColor(value.bg) : node.bg;
|
|
1126
|
+
return;
|
|
1127
|
+
}
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
switch (name2) {
|
|
1131
|
+
case "id":
|
|
1132
|
+
log("Id mapped", node.id, "=", value);
|
|
1133
|
+
node[name2] = value;
|
|
1134
|
+
break;
|
|
1135
|
+
case "focused":
|
|
1136
|
+
if (!(node instanceof Renderable))
|
|
1137
|
+
return;
|
|
1138
|
+
if (value) {
|
|
1139
|
+
node.focus();
|
|
1140
|
+
} else {
|
|
1141
|
+
node.blur();
|
|
1142
|
+
}
|
|
1143
|
+
break;
|
|
1144
|
+
case "onChange":
|
|
1145
|
+
let event = void 0;
|
|
1146
|
+
if (node instanceof SelectRenderable2) {
|
|
1147
|
+
event = SelectRenderableEvents.SELECTION_CHANGED;
|
|
1148
|
+
} else if (node instanceof TabSelectRenderable2) {
|
|
1149
|
+
event = TabSelectRenderableEvents.SELECTION_CHANGED;
|
|
1150
|
+
} else if (node instanceof InputRenderable2) {
|
|
1151
|
+
event = InputRenderableEvents.CHANGE;
|
|
1152
|
+
}
|
|
1153
|
+
if (!event)
|
|
1154
|
+
break;
|
|
1155
|
+
if (value) {
|
|
1156
|
+
node.on(event, value);
|
|
1157
|
+
}
|
|
1158
|
+
if (prev) {
|
|
1159
|
+
node.off(event, prev);
|
|
1160
|
+
}
|
|
1161
|
+
break;
|
|
1162
|
+
case "onInput":
|
|
1163
|
+
if (node instanceof InputRenderable2) {
|
|
1164
|
+
if (value) {
|
|
1165
|
+
node.on(InputRenderableEvents.INPUT, value);
|
|
1166
|
+
}
|
|
1167
|
+
if (prev) {
|
|
1168
|
+
node.off(InputRenderableEvents.INPUT, prev);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
break;
|
|
1172
|
+
case "onSubmit":
|
|
1173
|
+
if (node instanceof InputRenderable2) {
|
|
1174
|
+
if (value) {
|
|
1175
|
+
node.on(InputRenderableEvents.ENTER, value);
|
|
1176
|
+
}
|
|
1177
|
+
if (prev) {
|
|
1178
|
+
node.off(InputRenderableEvents.ENTER, prev);
|
|
1179
|
+
}
|
|
1180
|
+
} else {
|
|
1181
|
+
node[name2] = value;
|
|
1182
|
+
}
|
|
1183
|
+
break;
|
|
1184
|
+
case "onSelect":
|
|
1185
|
+
if (node instanceof SelectRenderable2) {
|
|
1186
|
+
if (value) {
|
|
1187
|
+
node.on(SelectRenderableEvents.ITEM_SELECTED, value);
|
|
1188
|
+
}
|
|
1189
|
+
if (prev) {
|
|
1190
|
+
node.off(SelectRenderableEvents.ITEM_SELECTED, prev);
|
|
1191
|
+
}
|
|
1192
|
+
} else if (node instanceof TabSelectRenderable2) {
|
|
1193
|
+
if (value) {
|
|
1194
|
+
node.on(TabSelectRenderableEvents.ITEM_SELECTED, value);
|
|
1195
|
+
}
|
|
1196
|
+
if (prev) {
|
|
1197
|
+
node.off(TabSelectRenderableEvents.ITEM_SELECTED, prev);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
break;
|
|
1201
|
+
case "style":
|
|
1202
|
+
const nextStyle = value ?? {};
|
|
1203
|
+
const previousStyle = prev ?? {};
|
|
1204
|
+
if (isEditBufferRenderable(node) && previousStyle.selectionOccupancy && !nextStyle.selectionOccupancy) {
|
|
1205
|
+
node.selectionOccupancy = void 0;
|
|
1206
|
+
}
|
|
1207
|
+
if (node instanceof ImageRenderable2) {
|
|
1208
|
+
for (const prop in previousStyle) {
|
|
1209
|
+
if (Object.prototype.hasOwnProperty.call(nextStyle, prop))
|
|
1210
|
+
continue;
|
|
1211
|
+
if (prop !== "fit" && prop !== "protocol")
|
|
1212
|
+
continue;
|
|
1213
|
+
node[prop] = void 0;
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
for (const prop in nextStyle) {
|
|
1217
|
+
const propVal = nextStyle[prop];
|
|
1218
|
+
if (propVal === previousStyle[prop])
|
|
1219
|
+
continue;
|
|
1220
|
+
node[prop] = propVal;
|
|
1221
|
+
}
|
|
1222
|
+
break;
|
|
1223
|
+
case "text":
|
|
1224
|
+
case "content": {
|
|
1225
|
+
const textValue = typeof value === "string" ? value : Array.isArray(value) ? value.join("") : `${value}`;
|
|
1226
|
+
node[name2] = textValue;
|
|
1227
|
+
break;
|
|
1228
|
+
}
|
|
1229
|
+
default:
|
|
1230
|
+
node[name2] = value;
|
|
1231
|
+
}
|
|
1232
|
+
},
|
|
1233
|
+
isTextNode(node) {
|
|
1234
|
+
return node instanceof TextNode;
|
|
1235
|
+
},
|
|
1236
|
+
insertNode: _insertNode,
|
|
1237
|
+
removeNode: _removeNode,
|
|
1238
|
+
getParentNode: _getParentNode,
|
|
1239
|
+
getFirstChild(node) {
|
|
1240
|
+
log("Getting first child of node:", logId(node));
|
|
1241
|
+
const firstChild = getNodeChildren(node)[0];
|
|
1242
|
+
if (!firstChild) {
|
|
1243
|
+
log("No first child found for node:", logId(node));
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
log("First child found:", logId(firstChild), "for node:", logId(node));
|
|
1247
|
+
return firstChild;
|
|
1248
|
+
},
|
|
1249
|
+
getNextSibling(node) {
|
|
1250
|
+
log("Getting next sibling of node:", logId(node));
|
|
1251
|
+
const parent = _getParentNode(node);
|
|
1252
|
+
if (!parent) {
|
|
1253
|
+
log("No parent found for node:", logId(node));
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
if (node instanceof SlotRenderable) {
|
|
1257
|
+
const layoutSlotNode = node.getSlotChildForRemoval(parent);
|
|
1258
|
+
if (layoutSlotNode) {
|
|
1259
|
+
node = layoutSlotNode;
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
const siblings = getNodeChildren(parent);
|
|
1263
|
+
const index = siblings.indexOf(node);
|
|
1264
|
+
if (index === -1 || index === siblings.length - 1) {
|
|
1265
|
+
log("No next sibling found for node:", logId(node));
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
const nextSibling = siblings[index + 1];
|
|
1269
|
+
if (!nextSibling) {
|
|
1270
|
+
log("Next sibling is null for node:", logId(node));
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1273
|
+
log("Next sibling found:", logId(nextSibling), "for node:", logId(node));
|
|
1274
|
+
return nextSibling;
|
|
1275
|
+
}
|
|
1276
|
+
});
|
|
1277
|
+
function getLayoutNodeConstructor(parent) {
|
|
1278
|
+
const parentLayoutNode = parent?.getLayoutNode?.();
|
|
1279
|
+
return parentLayoutNode?.constructor;
|
|
1280
|
+
}
|
|
1281
|
+
function createLayoutSlotYogaNode(parentNodeConstructor) {
|
|
1282
|
+
return parentNodeConstructor?.create?.() ?? Yoga.default.Node.create();
|
|
1283
|
+
}
|
|
1284
|
+
var SlotBaseRenderable = class extends BaseRenderable2 {
|
|
1285
|
+
constructor(id) {
|
|
1286
|
+
super({
|
|
1287
|
+
id
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
add(obj, index) {
|
|
1291
|
+
throw new Error("Can't add children on an Slot renderable");
|
|
1292
|
+
}
|
|
1293
|
+
getChildren() {
|
|
1294
|
+
return [];
|
|
1295
|
+
}
|
|
1296
|
+
remove(child) {
|
|
1297
|
+
}
|
|
1298
|
+
insertBefore(obj, anchor) {
|
|
1299
|
+
throw new Error("Can't add children on an Slot renderable");
|
|
1300
|
+
}
|
|
1301
|
+
getRenderable(id) {
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
getChildrenCount() {
|
|
1305
|
+
return 0;
|
|
1306
|
+
}
|
|
1307
|
+
requestRender() {
|
|
1308
|
+
}
|
|
1309
|
+
findDescendantById(id) {
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
};
|
|
1313
|
+
var TextSlotRenderable = class extends TextNodeRenderable3 {
|
|
1314
|
+
destroyed = false;
|
|
1315
|
+
constructor(id, parent) {
|
|
1316
|
+
super({
|
|
1317
|
+
id
|
|
1318
|
+
});
|
|
1319
|
+
this._visible = false;
|
|
1320
|
+
this.slotParent = parent;
|
|
1321
|
+
}
|
|
1322
|
+
detachFromSlot() {
|
|
1323
|
+
this.slotParent = void 0;
|
|
1324
|
+
}
|
|
1325
|
+
disposeWithoutSlotCascade() {
|
|
1326
|
+
if (this.destroyed) {
|
|
1327
|
+
return;
|
|
1328
|
+
}
|
|
1329
|
+
this.destroyed = true;
|
|
1330
|
+
this.detachFromSlot();
|
|
1331
|
+
}
|
|
1332
|
+
destroy() {
|
|
1333
|
+
if (this.destroyed) {
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1336
|
+
this.destroyed = true;
|
|
1337
|
+
const slotParent = this.slotParent;
|
|
1338
|
+
this.slotParent = void 0;
|
|
1339
|
+
slotParent?.destroy();
|
|
1340
|
+
super.destroy();
|
|
1341
|
+
}
|
|
1342
|
+
};
|
|
1343
|
+
var LayoutSlotRenderable = class extends SlotBaseRenderable {
|
|
1344
|
+
destroyed = false;
|
|
1345
|
+
yogaNodeFreed = false;
|
|
1346
|
+
constructor(id, parent, layoutParent) {
|
|
1347
|
+
super(id);
|
|
1348
|
+
this._visible = false;
|
|
1349
|
+
this.slotParent = parent;
|
|
1350
|
+
this.yogaNodeConstructor = getLayoutNodeConstructor(layoutParent);
|
|
1351
|
+
this.yogaNode = createLayoutSlotYogaNode(this.yogaNodeConstructor);
|
|
1352
|
+
this.yogaNode.setDisplay(Yoga.Display.None);
|
|
1353
|
+
}
|
|
1354
|
+
getLayoutNode() {
|
|
1355
|
+
return this.yogaNode;
|
|
1356
|
+
}
|
|
1357
|
+
updateFromLayout() {
|
|
1358
|
+
}
|
|
1359
|
+
updateLayout() {
|
|
1360
|
+
}
|
|
1361
|
+
onRemove() {
|
|
1362
|
+
}
|
|
1363
|
+
isCompatibleWith(layoutParent) {
|
|
1364
|
+
return this.yogaNodeConstructor === getLayoutNodeConstructor(layoutParent);
|
|
1365
|
+
}
|
|
1366
|
+
detachFromSlot() {
|
|
1367
|
+
this.slotParent = void 0;
|
|
1368
|
+
}
|
|
1369
|
+
freeYogaNode() {
|
|
1370
|
+
if (this.yogaNodeFreed) {
|
|
1371
|
+
return;
|
|
1372
|
+
}
|
|
1373
|
+
this.yogaNodeFreed = true;
|
|
1374
|
+
try {
|
|
1375
|
+
this.yogaNode.free();
|
|
1376
|
+
} catch {
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
disposeWithoutSlotCascade() {
|
|
1380
|
+
if (this.destroyed) {
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
this.destroyed = true;
|
|
1384
|
+
this.detachFromSlot();
|
|
1385
|
+
this.freeYogaNode();
|
|
1386
|
+
}
|
|
1387
|
+
destroy() {
|
|
1388
|
+
if (this.destroyed) {
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
1391
|
+
this.destroyed = true;
|
|
1392
|
+
const slotParent = this.slotParent;
|
|
1393
|
+
this.slotParent = void 0;
|
|
1394
|
+
this.freeYogaNode();
|
|
1395
|
+
slotParent?.destroy();
|
|
1396
|
+
}
|
|
1397
|
+
};
|
|
1398
|
+
var SlotRenderable = class extends SlotBaseRenderable {
|
|
1399
|
+
destroyed = false;
|
|
1400
|
+
layoutNodesByParent = /* @__PURE__ */ new Map();
|
|
1401
|
+
textNodesByParent = /* @__PURE__ */ new Map();
|
|
1402
|
+
layoutNodeCount = 0;
|
|
1403
|
+
textNodeCount = 0;
|
|
1404
|
+
constructor(id) {
|
|
1405
|
+
super(id);
|
|
1406
|
+
this._visible = false;
|
|
1407
|
+
}
|
|
1408
|
+
get layoutNode() {
|
|
1409
|
+
return this.getCurrentSlotChild(this.layoutNodesByParent);
|
|
1410
|
+
}
|
|
1411
|
+
get textNode() {
|
|
1412
|
+
return this.getCurrentSlotChild(this.textNodesByParent);
|
|
1413
|
+
}
|
|
1414
|
+
isTextSlotParent(parent) {
|
|
1415
|
+
return isTextNodeRenderable2(parent) || parent instanceof TextRenderable3;
|
|
1416
|
+
}
|
|
1417
|
+
getCurrentSlotChild(nodesByParent) {
|
|
1418
|
+
for (const node of nodesByParent.values()) {
|
|
1419
|
+
if (node.parent) {
|
|
1420
|
+
return node;
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
return nodesByParent.values().next().value;
|
|
1424
|
+
}
|
|
1425
|
+
getTextNodeForParent(parent) {
|
|
1426
|
+
const mappedNode = this.textNodesByParent.get(parent);
|
|
1427
|
+
if (mappedNode) {
|
|
1428
|
+
return mappedNode;
|
|
1429
|
+
}
|
|
1430
|
+
for (const [mappedParent, textNode] of this.textNodesByParent) {
|
|
1431
|
+
if (textNode.parent !== parent) {
|
|
1432
|
+
continue;
|
|
1433
|
+
}
|
|
1434
|
+
this.textNodesByParent.delete(mappedParent);
|
|
1435
|
+
this.textNodesByParent.set(parent, textNode);
|
|
1436
|
+
return textNode;
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
getLayoutNodeForParent(parent) {
|
|
1440
|
+
const mappedNode = this.layoutNodesByParent.get(parent);
|
|
1441
|
+
if (mappedNode) {
|
|
1442
|
+
return mappedNode;
|
|
1443
|
+
}
|
|
1444
|
+
for (const [mappedParent, layoutNode] of this.layoutNodesByParent) {
|
|
1445
|
+
if (layoutNode.parent !== parent) {
|
|
1446
|
+
continue;
|
|
1447
|
+
}
|
|
1448
|
+
this.layoutNodesByParent.delete(mappedParent);
|
|
1449
|
+
this.layoutNodesByParent.set(parent, layoutNode);
|
|
1450
|
+
return layoutNode;
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
takeReusableTextNode(parent) {
|
|
1454
|
+
for (const [mappedParent, textNode] of this.textNodesByParent) {
|
|
1455
|
+
if (textNode.parent) {
|
|
1456
|
+
continue;
|
|
1457
|
+
}
|
|
1458
|
+
this.textNodesByParent.delete(mappedParent);
|
|
1459
|
+
this.textNodesByParent.set(parent, textNode);
|
|
1460
|
+
return textNode;
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
takeReusableLayoutNode(parent) {
|
|
1464
|
+
for (const [mappedParent, layoutNode] of this.layoutNodesByParent) {
|
|
1465
|
+
if (layoutNode.parent) {
|
|
1466
|
+
continue;
|
|
1467
|
+
}
|
|
1468
|
+
if (!layoutNode.isCompatibleWith(parent)) {
|
|
1469
|
+
continue;
|
|
1470
|
+
}
|
|
1471
|
+
this.layoutNodesByParent.delete(mappedParent);
|
|
1472
|
+
this.layoutNodesByParent.set(parent, layoutNode);
|
|
1473
|
+
return layoutNode;
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
disposeDetachedTextNodes() {
|
|
1477
|
+
for (const [parent, textNode] of this.textNodesByParent) {
|
|
1478
|
+
if (textNode.parent) {
|
|
1479
|
+
continue;
|
|
1480
|
+
}
|
|
1481
|
+
this.textNodesByParent.delete(parent);
|
|
1482
|
+
textNode.disposeWithoutSlotCascade();
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
disposeDetachedIncompatibleLayoutNodes(parent) {
|
|
1486
|
+
for (const [mappedParent, layoutNode] of this.layoutNodesByParent) {
|
|
1487
|
+
if (layoutNode.parent || layoutNode.isCompatibleWith(parent)) {
|
|
1488
|
+
continue;
|
|
1489
|
+
}
|
|
1490
|
+
this.layoutNodesByParent.delete(mappedParent);
|
|
1491
|
+
layoutNode.disposeWithoutSlotCascade();
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
getAttachedSlotParent(excludedNode) {
|
|
1495
|
+
for (const textNode of this.textNodesByParent.values()) {
|
|
1496
|
+
if (textNode !== excludedNode && textNode.parent) {
|
|
1497
|
+
return textNode.parent;
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
for (const layoutNode of this.layoutNodesByParent.values()) {
|
|
1501
|
+
if (layoutNode !== excludedNode && layoutNode.parent) {
|
|
1502
|
+
return layoutNode.parent;
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
return null;
|
|
1506
|
+
}
|
|
1507
|
+
hasOtherAttachedSlotChildren(excludedNode) {
|
|
1508
|
+
return this.getAttachedSlotParent(excludedNode) !== null;
|
|
1509
|
+
}
|
|
1510
|
+
getSlotChild(parent) {
|
|
1511
|
+
if (this.isTextSlotParent(parent)) {
|
|
1512
|
+
const existingTextNode = this.getTextNodeForParent(parent);
|
|
1513
|
+
if (existingTextNode) {
|
|
1514
|
+
return existingTextNode;
|
|
1515
|
+
}
|
|
1516
|
+
const reusableTextNode = this.takeReusableTextNode(parent);
|
|
1517
|
+
if (reusableTextNode) {
|
|
1518
|
+
return reusableTextNode;
|
|
1519
|
+
}
|
|
1520
|
+
this.disposeDetachedIncompatibleLayoutNodes(parent);
|
|
1521
|
+
const textNode = new TextSlotRenderable(`slot-text-${this.id}-${++this.textNodeCount}`, this);
|
|
1522
|
+
this.textNodesByParent.set(parent, textNode);
|
|
1523
|
+
return textNode;
|
|
1524
|
+
}
|
|
1525
|
+
const existingLayoutNode = this.getLayoutNodeForParent(parent);
|
|
1526
|
+
if (existingLayoutNode) {
|
|
1527
|
+
return existingLayoutNode;
|
|
1528
|
+
}
|
|
1529
|
+
const reusableLayoutNode = this.takeReusableLayoutNode(parent);
|
|
1530
|
+
if (reusableLayoutNode) {
|
|
1531
|
+
return reusableLayoutNode;
|
|
1532
|
+
}
|
|
1533
|
+
this.disposeDetachedTextNodes();
|
|
1534
|
+
this.disposeDetachedIncompatibleLayoutNodes(parent);
|
|
1535
|
+
const layoutNode = new LayoutSlotRenderable(`slot-layout-${this.id}-${++this.layoutNodeCount}`, this, parent);
|
|
1536
|
+
this.layoutNodesByParent.set(parent, layoutNode);
|
|
1537
|
+
return layoutNode;
|
|
1538
|
+
}
|
|
1539
|
+
getSlotChildForRemoval(parent) {
|
|
1540
|
+
if (this.isTextSlotParent(parent)) {
|
|
1541
|
+
return this.getTextNodeForParent(parent);
|
|
1542
|
+
}
|
|
1543
|
+
return this.getLayoutNodeForParent(parent);
|
|
1544
|
+
}
|
|
1545
|
+
didRemoveSlotChild(parent, child) {
|
|
1546
|
+
const hasOtherAttachedSlotChildren = this.hasOtherAttachedSlotChildren(child);
|
|
1547
|
+
if (hasOtherAttachedSlotChildren && child instanceof TextSlotRenderable && this.getTextNodeForParent(parent) === child) {
|
|
1548
|
+
this.textNodesByParent.delete(parent);
|
|
1549
|
+
child.disposeWithoutSlotCascade();
|
|
1550
|
+
}
|
|
1551
|
+
if (hasOtherAttachedSlotChildren && child instanceof LayoutSlotRenderable && this.getLayoutNodeForParent(parent) === child) {
|
|
1552
|
+
this.layoutNodesByParent.delete(parent);
|
|
1553
|
+
child.disposeWithoutSlotCascade();
|
|
1554
|
+
}
|
|
1555
|
+
if (this.parent === parent) {
|
|
1556
|
+
this.parent = this.getAttachedSlotParent(child);
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
destroy() {
|
|
1560
|
+
if (this.destroyed) {
|
|
1561
|
+
return;
|
|
1562
|
+
}
|
|
1563
|
+
this.destroyed = true;
|
|
1564
|
+
const layoutNodes = new Set(this.layoutNodesByParent.values());
|
|
1565
|
+
this.layoutNodesByParent.clear();
|
|
1566
|
+
for (const layoutNode of layoutNodes) {
|
|
1567
|
+
layoutNode.destroy();
|
|
1568
|
+
}
|
|
1569
|
+
const textNodes = new Set(this.textNodesByParent.values());
|
|
1570
|
+
this.textNodesByParent.clear();
|
|
1571
|
+
for (const textNode of textNodes) {
|
|
1572
|
+
textNode.destroy();
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
};
|
|
1576
|
+
extend({
|
|
1577
|
+
time_to_first_draw: TimeToFirstDrawRenderable
|
|
1578
|
+
});
|
|
1579
|
+
var mountSolidRoot = (renderer, node) => {
|
|
1580
|
+
let dispose;
|
|
1581
|
+
let disposeRequested = false;
|
|
1582
|
+
let disposed = false;
|
|
1583
|
+
let mounting = true;
|
|
1584
|
+
let destroyRequested = false;
|
|
1585
|
+
const originalDestroy = renderer.destroy.bind(renderer);
|
|
1586
|
+
const runDispose = () => {
|
|
1587
|
+
if (disposed) {
|
|
1588
|
+
return;
|
|
1589
|
+
}
|
|
1590
|
+
if (!dispose) {
|
|
1591
|
+
disposeRequested = true;
|
|
1592
|
+
return;
|
|
1593
|
+
}
|
|
1594
|
+
disposed = true;
|
|
1595
|
+
dispose();
|
|
1596
|
+
};
|
|
1597
|
+
renderer.once("destroy", runDispose);
|
|
1598
|
+
renderer.destroy = () => {
|
|
1599
|
+
if (mounting) {
|
|
1600
|
+
destroyRequested = true;
|
|
1601
|
+
return;
|
|
1602
|
+
}
|
|
1603
|
+
originalDestroy();
|
|
1604
|
+
};
|
|
1605
|
+
try {
|
|
1606
|
+
dispose = _render(() => createComponent2(RendererContext.Provider, {
|
|
1607
|
+
get value() {
|
|
1608
|
+
return renderer;
|
|
1609
|
+
},
|
|
1610
|
+
get children() {
|
|
1611
|
+
return createComponent2(node, {});
|
|
1612
|
+
}
|
|
1613
|
+
}), renderer.root);
|
|
1614
|
+
} finally {
|
|
1615
|
+
mounting = false;
|
|
1616
|
+
renderer.destroy = originalDestroy;
|
|
1617
|
+
}
|
|
1618
|
+
if (disposeRequested) {
|
|
1619
|
+
runDispose();
|
|
1620
|
+
}
|
|
1621
|
+
if (destroyRequested) {
|
|
1622
|
+
originalDestroy();
|
|
1623
|
+
}
|
|
1624
|
+
};
|
|
1625
|
+
var render = async (node, rendererOrConfig = {}) => {
|
|
1626
|
+
const renderer = rendererOrConfig instanceof CliRenderer ? rendererOrConfig : await createCliRenderer({
|
|
1627
|
+
...rendererOrConfig,
|
|
1628
|
+
onDestroy: () => {
|
|
1629
|
+
rendererOrConfig.onDestroy?.();
|
|
1630
|
+
}
|
|
1631
|
+
});
|
|
1632
|
+
engine2.attach(renderer);
|
|
1633
|
+
mountSolidRoot(renderer, node);
|
|
1634
|
+
};
|
|
1635
|
+
|
|
1636
|
+
// src/index.tsx
|
|
1637
|
+
import { createCliRenderer as createCliRenderer2 } from "@opentui/core";
|
|
1638
|
+
|
|
1639
|
+
// ../../apps/shared/theme.ts
|
|
1640
|
+
import { lstatSync, readFileSync } from "node:fs";
|
|
1641
|
+
import { homedir } from "node:os";
|
|
1642
|
+
import { join } from "node:path";
|
|
1643
|
+
|
|
1644
|
+
// ../../apps/shared/Boo.ghostty
|
|
1645
|
+
var Boo_default = "# Boo: the default terminal palette, matching the native installer.\nbackground = #08080E\nforeground = #F5ECD8\ncursor-color = #25D9E8\ncursor-text = #071014\nselection-background = #0E5965\nselection-foreground = #F5ECD8\npalette = 0=#171522\npalette = 1=#FF577D\npalette = 2=#2BE566\npalette = 3=#FFD51A\npalette = 4=#5355C9\npalette = 5=#FF2A8A\npalette = 6=#25D9E8\npalette = 7=#B7AB9A\npalette = 8=#686379\npalette = 9=#FF839D\npalette = 10=#80F5A4\npalette = 11=#FFE873\npalette = 12=#9698FF\npalette = 13=#FF83BB\npalette = 14=#8CEEF5\npalette = 15=#F5ECD8\n# Extended terminal slots retain Boo's existing UI surface colors.\npalette = 16=#111018\npalette = 17=#171522\npalette = 18=#211D2C\npalette = 19=#071014\n";
|
|
1646
|
+
|
|
1647
|
+
// ../../apps/shared/theme.ts
|
|
1648
|
+
var selectionPath = join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "ghostty/auto/theme.ghostty");
|
|
1649
|
+
var resources = process.env.GHOSTTY_RESOURCES_DIR || "/usr/share/ghostty";
|
|
1650
|
+
function colors(source) {
|
|
1651
|
+
const values = {};
|
|
1652
|
+
for (const line of source.split(/\r?\n/)) {
|
|
1653
|
+
const match = line.match(/^\s*(background|foreground|cursor-color|cursor-text|selection-background|selection-foreground|palette)\s*=\s*(?:(\d+)=)?#?([\da-f]{6})\s*$/i);
|
|
1654
|
+
if (match) values[match[1] === "palette" ? `palette${match[2]}` : match[1]] = `#${match[3]}`;
|
|
1655
|
+
}
|
|
1656
|
+
for (const key of ["background", "foreground", ...Array.from({ length: 16 }, (_, i) => `palette${i}`)]) {
|
|
1657
|
+
if (!values[key]) throw new Error(`Theme is missing ${key}`);
|
|
1658
|
+
}
|
|
1659
|
+
return values;
|
|
1660
|
+
}
|
|
1661
|
+
function mix(a, b, amount) {
|
|
1662
|
+
return "#" + [1, 3, 5].map((i) => Math.round(parseInt(a.slice(i, i + 2), 16) * (1 - amount) + parseInt(b.slice(i, i + 2), 16) * amount).toString(16).padStart(2, "0")).join("");
|
|
1663
|
+
}
|
|
1664
|
+
function ui(c, boo, light) {
|
|
1665
|
+
return {
|
|
1666
|
+
background: c.background,
|
|
1667
|
+
foreground: c.foreground,
|
|
1668
|
+
card: boo ? c.palette16 : mix(c.background, c.foreground, 0.04),
|
|
1669
|
+
panel: boo ? c.palette17 : mix(c.background, c.foreground, 0.07),
|
|
1670
|
+
panelAlt: boo ? c.palette18 : mix(c.background, c.foreground, 0.12),
|
|
1671
|
+
selected: boo ? c["selection-background"] : mix(c.background, c.palette6, 0.25),
|
|
1672
|
+
muted: boo ? c.palette7 : mix(c.background, c.foreground, 0.7),
|
|
1673
|
+
accent: c.palette6,
|
|
1674
|
+
// Deepen light palettes' secondary accent for readable headings and focus fills.
|
|
1675
|
+
highlight: light ? mix(c.palette5, c.foreground, 0.6) : c.palette5,
|
|
1676
|
+
accentInk: boo ? c.palette19 : c.background,
|
|
1677
|
+
warning: c.palette3,
|
|
1678
|
+
danger: c.palette5,
|
|
1679
|
+
success: c.palette2,
|
|
1680
|
+
border: c.palette4
|
|
1681
|
+
};
|
|
1682
|
+
}
|
|
1683
|
+
var themes = [
|
|
1684
|
+
{ name: "Boo", kind: "Dark", description: "Electric cyan. Warm ink. Unmistakably Boo." },
|
|
1685
|
+
{ name: "Catppuccin Mocha", kind: "Dark", description: "Soft pastels on a deep, calm canvas." },
|
|
1686
|
+
{ name: "Catppuccin Latte", kind: "Light", description: "A bright canvas with gentle, colorful accents." }
|
|
1687
|
+
].map((entry) => {
|
|
1688
|
+
const path = join(resources, "themes", entry.name);
|
|
1689
|
+
try {
|
|
1690
|
+
const native = colors(entry.name === "Boo" ? Boo_default : readFileSync(path, "utf8"));
|
|
1691
|
+
return { ...entry, path, native, ui: ui(native, entry.name === "Boo", entry.kind === "Light"), error: "" };
|
|
1692
|
+
} catch {
|
|
1693
|
+
return { ...entry, path, native: colors(Boo_default), ui: ui(colors(Boo_default), true, false), error: "Bundled theme unavailable or invalid." };
|
|
1694
|
+
}
|
|
1695
|
+
});
|
|
1696
|
+
function readSelection() {
|
|
1697
|
+
let source = "";
|
|
1698
|
+
try {
|
|
1699
|
+
const stat = lstatSync(selectionPath);
|
|
1700
|
+
if (!stat.isFile() || stat.size > 65536) throw new Error("Selection must be a regular file under 64 KiB");
|
|
1701
|
+
source = readFileSync(selectionPath, "utf8");
|
|
1702
|
+
} catch (error) {
|
|
1703
|
+
if (error.code !== "ENOENT") throw error;
|
|
1704
|
+
}
|
|
1705
|
+
const lines = source.split(/\r?\n/).filter((line) => /^\s*theme\s*=/.test(line));
|
|
1706
|
+
const value = lines.at(-1)?.replace(/^\s*theme\s*=\s*/, "").trim().replace(/^"(.*)"$/, "$1");
|
|
1707
|
+
const malformed = source.split(/\r?\n/).some((line) => /^\s*theme(?:\s|=|$)/.test(line) && !/^\s*theme\s*=\s*\S/.test(line));
|
|
1708
|
+
const index = malformed ? -1 : value ? themes.findIndex((theme) => theme.name === value || theme.path === value) : 0;
|
|
1709
|
+
return { source, index, custom: index < 0, name: index < 0 ? "Custom theme" : themes[index].name };
|
|
1710
|
+
}
|
|
1711
|
+
var palette = (() => {
|
|
1712
|
+
try {
|
|
1713
|
+
return themes[readSelection().index]?.ui || themes[0].ui;
|
|
1714
|
+
} catch {
|
|
1715
|
+
return themes[0].ui;
|
|
1716
|
+
}
|
|
1717
|
+
})();
|
|
1718
|
+
|
|
1719
|
+
// src/App.tsx
|
|
1720
|
+
import { TextAttributes as TextAttributes2 } from "@opentui/core";
|
|
1721
|
+
import { For as For2, Show, createSignal as createSignal4, onMount as onMount2 } from "solid-js/dist/solid.js";
|
|
1722
|
+
|
|
1723
|
+
// src/backend.ts
|
|
1724
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
1725
|
+
import { chmod as chmod2, mkdtemp, open as open2, rm } from "node:fs/promises";
|
|
1726
|
+
import { tmpdir as tmpdir4 } from "node:os";
|
|
1727
|
+
import { dirname as dirname3, join as join5 } from "node:path";
|
|
1728
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
1729
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
1730
|
+
|
|
1731
|
+
// src/backends/macos.ts
|
|
1732
|
+
import { realpath } from "node:fs/promises";
|
|
1733
|
+
import { tmpdir, userInfo } from "node:os";
|
|
1734
|
+
|
|
1735
|
+
// src/shared.ts
|
|
1736
|
+
import { spawn } from "node:child_process";
|
|
1737
|
+
|
|
1738
|
+
// src/dependencies.ts
|
|
1739
|
+
import { realpathSync, statSync as statSync2 } from "node:fs";
|
|
1740
|
+
import { dirname } from "node:path";
|
|
1741
|
+
|
|
1742
|
+
// src/runtime.ts
|
|
1743
|
+
import { constants, accessSync, statSync } from "node:fs";
|
|
1744
|
+
import { delimiter, join as join2 } from "node:path";
|
|
1745
|
+
import { fileURLToPath } from "node:url";
|
|
1746
|
+
function which(name2, path = process.env.PATH ?? "") {
|
|
1747
|
+
for (const directory2 of path.split(delimiter)) {
|
|
1748
|
+
if (!directory2) continue;
|
|
1749
|
+
const candidate = join2(directory2, name2);
|
|
1750
|
+
try {
|
|
1751
|
+
accessSync(candidate, constants.X_OK);
|
|
1752
|
+
if (statSync(candidate).isFile()) return candidate;
|
|
1753
|
+
} catch {
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
return null;
|
|
1757
|
+
}
|
|
1758
|
+
function resolvePackage(name2) {
|
|
1759
|
+
return fileURLToPath(import.meta.resolve(name2));
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
// src/dependencies.ts
|
|
1763
|
+
var commonTools = ["curl", "zstd"];
|
|
1764
|
+
var candidateTools = ["gh", "unzip"];
|
|
1765
|
+
function preferredExecutable(name2, platform3, path, macSystemExists) {
|
|
1766
|
+
if (platform3 === "darwin" && (name2 === "curl" || name2 === "unzip") && macSystemExists) return `/usr/bin/${name2}`;
|
|
1767
|
+
return path;
|
|
1768
|
+
}
|
|
1769
|
+
function trustedLinuxPath(path) {
|
|
1770
|
+
if (!path) return null;
|
|
1771
|
+
try {
|
|
1772
|
+
const resolved = realpathSync(path);
|
|
1773
|
+
const binary = statSync2(resolved);
|
|
1774
|
+
if (!binary.isFile() || binary.uid !== 0 || (binary.mode & 18) !== 0 || (binary.mode & 73) === 0) return null;
|
|
1775
|
+
let parent = dirname(resolved);
|
|
1776
|
+
while (true) {
|
|
1777
|
+
const stat = statSync2(parent);
|
|
1778
|
+
if (!stat.isDirectory() || stat.uid !== 0 || (stat.mode & 18) !== 0) return null;
|
|
1779
|
+
const next = dirname(parent);
|
|
1780
|
+
if (next === parent) break;
|
|
1781
|
+
parent = next;
|
|
1782
|
+
}
|
|
1783
|
+
return resolved;
|
|
1784
|
+
} catch {
|
|
1785
|
+
return null;
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
function systemExecutable(name2) {
|
|
1789
|
+
try {
|
|
1790
|
+
return statSync2(`/usr/bin/${name2}`).isFile() ? `/usr/bin/${name2}` : null;
|
|
1791
|
+
} catch {
|
|
1792
|
+
return null;
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
function executable(name2) {
|
|
1796
|
+
const path = preferredExecutable(name2, process.platform, which(name2), systemExecutable(name2) !== null);
|
|
1797
|
+
if (process.platform === "linux") return trustedLinuxPath(path);
|
|
1798
|
+
if (path || process.platform !== "darwin") return path;
|
|
1799
|
+
const prefixes = process.arch === "arm64" ? ["/opt/homebrew", "/usr/local"] : ["/usr/local", "/opt/homebrew"];
|
|
1800
|
+
for (const prefix of prefixes) {
|
|
1801
|
+
const path2 = which(name2, `${prefix}/bin`);
|
|
1802
|
+
if (path2) return path2;
|
|
1803
|
+
}
|
|
1804
|
+
return null;
|
|
1805
|
+
}
|
|
1806
|
+
function missing(names) {
|
|
1807
|
+
return names.filter((name2) => !executable(name2));
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
// src/shared.ts
|
|
1811
|
+
var positive = (n) => Number.isSafeInteger(n) && Number(n) > 0;
|
|
1812
|
+
var cleanText = (s) => String(s).replace(/[\x00-\x1f\x7f-\x9f]/g, " ").slice(0, 300);
|
|
1813
|
+
async function command(argv, signal, options = {}) {
|
|
1814
|
+
signal.throwIfAborted();
|
|
1815
|
+
const binary = argv[0].includes("/") ? argv[0] : executable(argv[0]);
|
|
1816
|
+
if (!binary) throw new Error(`Missing ${argv[0]}. Refresh dependency setup before continuing.`);
|
|
1817
|
+
const child = spawn(binary, argv.slice(1), {
|
|
1818
|
+
stdio: options.terminal ? "inherit" : ["pipe", "pipe", "pipe"],
|
|
1819
|
+
env: { ...process.env, GH_DEBUG: "", GH_PAGER: "cat", PAGER: "cat", GH_PROMPT_DISABLED: "1" }
|
|
1820
|
+
});
|
|
1821
|
+
let failure;
|
|
1822
|
+
const exited = new Promise((resolve) => {
|
|
1823
|
+
child.on("error", (error) => {
|
|
1824
|
+
failure = error;
|
|
1825
|
+
});
|
|
1826
|
+
child.on("close", resolve);
|
|
1827
|
+
});
|
|
1828
|
+
let stderr = "";
|
|
1829
|
+
child.stderr?.on("data", (data) => {
|
|
1830
|
+
if (argv[0] !== "gh") stderr = (stderr + data.toString()).slice(-1500);
|
|
1831
|
+
});
|
|
1832
|
+
child.stdin?.on("error", (error) => {
|
|
1833
|
+
failure = error;
|
|
1834
|
+
});
|
|
1835
|
+
child.stdin?.end(options.input);
|
|
1836
|
+
const stop = () => {
|
|
1837
|
+
child.kill("SIGTERM");
|
|
1838
|
+
};
|
|
1839
|
+
signal.addEventListener("abort", stop, { once: true });
|
|
1840
|
+
let output = "";
|
|
1841
|
+
let count = 0;
|
|
1842
|
+
try {
|
|
1843
|
+
if (child.stdout) for await (const chunk of child.stdout) {
|
|
1844
|
+
signal.throwIfAborted();
|
|
1845
|
+
count += chunk.length;
|
|
1846
|
+
if (count > (options.limit ?? 8 * 1024 ** 2)) throw new Error("Command output exceeds the expected size.");
|
|
1847
|
+
if (options.consume) await options.consume(chunk);
|
|
1848
|
+
else output += chunk.toString();
|
|
1849
|
+
}
|
|
1850
|
+
const code2 = await exited;
|
|
1851
|
+
signal.throwIfAborted();
|
|
1852
|
+
if (failure || code2 !== 0) {
|
|
1853
|
+
const message = argv[0] === "gh" ? `GitHub request failed. Run ${cleanText(binary)} auth login if needed; check repository access and your connection, then refresh.` : `${argv[0].split("/").pop()} failed (${code2 ?? "not started"}). ${cleanText(stderr || failure?.message || "")}`;
|
|
1854
|
+
throw new Error(message);
|
|
1855
|
+
}
|
|
1856
|
+
return output;
|
|
1857
|
+
} finally {
|
|
1858
|
+
signal.removeEventListener("abort", stop);
|
|
1859
|
+
if (child.exitCode === null && child.signalCode === null) child.kill("SIGTERM");
|
|
1860
|
+
await exited;
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
// src/installers/homebrew.ts
|
|
1865
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
1866
|
+
var packages = [...commonTools, ...candidateTools];
|
|
1867
|
+
function dependencies(includeGitHub = false) {
|
|
1868
|
+
const required2 = includeGitHub ? packages : commonTools;
|
|
1869
|
+
const absent = missing(required2);
|
|
1870
|
+
return {
|
|
1871
|
+
missing: absent,
|
|
1872
|
+
installer: absent.length ? executable("brew") : null,
|
|
1873
|
+
guidance: "Install Homebrew from https://brew.sh, then refresh."
|
|
1874
|
+
};
|
|
1875
|
+
}
|
|
1876
|
+
async function install(approved, signal) {
|
|
1877
|
+
signal.throwIfAborted();
|
|
1878
|
+
if (process.platform !== "darwin" || process.getuid?.() === 0) throw new Error("Run Boo Flash on macOS as your normal user, not with sudo.");
|
|
1879
|
+
const absent = packages.filter((name2) => approved.includes(name2) && !executable(name2));
|
|
1880
|
+
if (!absent.length) return "installed";
|
|
1881
|
+
const brew = executable("brew");
|
|
1882
|
+
if (!brew) throw new Error("Homebrew not found. Follow https://brew.sh, then refresh.");
|
|
1883
|
+
process.stdout.write(`
|
|
1884
|
+
Boo Flash: Homebrew is installing ${absent.join(", ")} and required dependencies.
|
|
1885
|
+
Ctrl-C cancels and quits; wait for Homebrew to finish cleanup.
|
|
1886
|
+
|
|
1887
|
+
`);
|
|
1888
|
+
const child = spawn2(brew, ["install", "--formula", ...absent], {
|
|
1889
|
+
stdio: "inherit",
|
|
1890
|
+
env: {
|
|
1891
|
+
...process.env,
|
|
1892
|
+
HOMEBREW_NO_ASK: "1",
|
|
1893
|
+
HOMEBREW_NO_AUTO_UPDATE: "1",
|
|
1894
|
+
HOMEBREW_NO_INSTALL_UPGRADE: "1",
|
|
1895
|
+
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: "1",
|
|
1896
|
+
HOMEBREW_NO_INSTALL_CLEANUP: "1"
|
|
1897
|
+
}
|
|
1898
|
+
});
|
|
1899
|
+
let failed = false;
|
|
1900
|
+
const [code2, exitSignal] = await new Promise((resolve) => {
|
|
1901
|
+
child.on("error", () => {
|
|
1902
|
+
failed = true;
|
|
1903
|
+
});
|
|
1904
|
+
child.on("close", (code3, signal2) => resolve([code3, signal2]));
|
|
1905
|
+
});
|
|
1906
|
+
if (signal.aborted || code2 === 130 || exitSignal === "SIGINT") return "cancelled";
|
|
1907
|
+
if (failed || code2 !== 0) throw new Error(`Homebrew failed (${code2 ?? "not started"}). Quit to read its terminal output, resolve the reported issue, then refresh to retry.`);
|
|
1908
|
+
const remaining = approved.filter((name2) => !executable(name2));
|
|
1909
|
+
if (remaining.length) throw new Error(`Still missing: ${remaining.join(", ")}. Check Homebrew's terminal output and command paths, then refresh.`);
|
|
1910
|
+
return "installed";
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
// src/backends/platform.ts
|
|
1914
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
1915
|
+
function startWriter(binary, args2) {
|
|
1916
|
+
const child = spawn3(binary, args2, { stdio: ["pipe", "ignore", "pipe"] });
|
|
1917
|
+
let stderr = "";
|
|
1918
|
+
child.stderr.on("data", (data) => {
|
|
1919
|
+
stderr = (stderr + data.toString()).slice(-1500);
|
|
1920
|
+
});
|
|
1921
|
+
child.stdin.on("error", () => {
|
|
1922
|
+
});
|
|
1923
|
+
const done = new Promise((resolve) => {
|
|
1924
|
+
child.on("error", (error) => {
|
|
1925
|
+
stderr = `Could not start the privileged writer: ${error.message}`;
|
|
1926
|
+
});
|
|
1927
|
+
child.on("close", resolve);
|
|
1928
|
+
});
|
|
1929
|
+
return { stdin: child.stdin, done, error: () => stderr, stop: () => child.stdin.destroy() };
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
// src/backends/macos.ts
|
|
1933
|
+
var diskID = /^disk[0-9]+$/;
|
|
1934
|
+
var deviceID = /^disk[0-9]+(?:s[0-9]+)*$/;
|
|
1935
|
+
async function plist(argv, signal) {
|
|
1936
|
+
const input = await command(argv, signal);
|
|
1937
|
+
return JSON.parse(await command(["/usr/bin/plutil", "-convert", "json", "-o", "-", "--", "-"], signal, { input }));
|
|
1938
|
+
}
|
|
1939
|
+
async function disks(signal, source, staging = tmpdir(), unmounted) {
|
|
1940
|
+
const info = (id) => plist(["/usr/sbin/diskutil", "info", "-plist", id], signal);
|
|
1941
|
+
const listing = await plist(["/usr/sbin/diskutil", "list", "-plist", "physical"], signal);
|
|
1942
|
+
const physical = new Set((listing.WholeDisks ?? []).filter((id) => diskID.test(id)));
|
|
1943
|
+
if (!physical.size) throw new Error("Cannot resolve physical disks. Nothing can be selected.");
|
|
1944
|
+
const apfs = await plist(["/usr/sbin/diskutil", "apfs", "list", "-plist"], signal);
|
|
1945
|
+
if (!Array.isArray(apfs.Containers)) throw new Error("Cannot resolve APFS storage. Nothing can be selected.");
|
|
1946
|
+
const containers = apfs.Containers;
|
|
1947
|
+
const protectedDisks2 = /* @__PURE__ */ new Set();
|
|
1948
|
+
const resolve = async (id, seen = /* @__PURE__ */ new Set()) => {
|
|
1949
|
+
if (seen.has(id)) throw new Error("Unresolved storage cycle. Nothing can be selected.");
|
|
1950
|
+
const next = new Set(seen).add(id);
|
|
1951
|
+
const d = await info(id);
|
|
1952
|
+
if (!deviceID.test(d.DeviceIdentifier)) throw new Error("Unresolved backing device. Nothing can be selected.");
|
|
1953
|
+
if (d.WholeDisk === true && physical.has(d.DeviceIdentifier)) return [d.DeviceIdentifier];
|
|
1954
|
+
if (d.WholeDisk === false && physical.has(d.ParentWholeDisk)) return resolve(d.ParentWholeDisk, next);
|
|
1955
|
+
const container = containers.find((c) => c.ContainerReference === d.DeviceIdentifier || c.ContainerReference === d.APFSContainerReference || c.Volumes?.some((v) => v.DeviceIdentifier === d.DeviceIdentifier));
|
|
1956
|
+
if (container) {
|
|
1957
|
+
if (!Array.isArray(container.PhysicalStores) || !container.PhysicalStores.length) throw new Error("APFS physical stores are unavailable.");
|
|
1958
|
+
const result = [];
|
|
1959
|
+
for (const store of container.PhysicalStores) {
|
|
1960
|
+
if (!deviceID.test(store.DeviceIdentifier)) throw new Error("Invalid APFS physical store.");
|
|
1961
|
+
result.push(...await resolve(store.DeviceIdentifier, next));
|
|
1962
|
+
}
|
|
1963
|
+
return result;
|
|
1964
|
+
}
|
|
1965
|
+
if (d.WholeDisk === false && diskID.test(d.ParentWholeDisk)) return resolve(d.ParentWholeDisk, next);
|
|
1966
|
+
throw new Error("A protected path has unsupported backing storage (for example RAID or a disk image). Nothing can be selected.");
|
|
1967
|
+
};
|
|
1968
|
+
const protectedPaths2 = ["/", "/System/Volumes/Data", userInfo().homedir, process.cwd(), source, process.execPath, staging];
|
|
1969
|
+
protectedPaths2.push(resolvePackage("@opentui/core"));
|
|
1970
|
+
for (const name2 of ["@opentui/solid", "solid-js"]) {
|
|
1971
|
+
try {
|
|
1972
|
+
protectedPaths2.push(resolvePackage(name2));
|
|
1973
|
+
} catch {
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
protectedPaths2.push(process.env.OTUI_ASSET_ROOT || resolvePackage(`@opentui/core-darwin-${process.arch}`));
|
|
1977
|
+
const zstd = executable("zstd");
|
|
1978
|
+
if (!zstd) throw new Error("Install zstd before flashing.");
|
|
1979
|
+
protectedPaths2.push(zstd);
|
|
1980
|
+
const gh = executable("gh");
|
|
1981
|
+
if (gh) protectedPaths2.push(gh);
|
|
1982
|
+
for (const path of protectedPaths2) {
|
|
1983
|
+
const canonical = await realpath(path);
|
|
1984
|
+
const device = (await command(["/usr/bin/stat", "-f", "%Sd", canonical], signal)).trim();
|
|
1985
|
+
if (!deviceID.test(device)) throw new Error("Cannot resolve a protected path to a disk. Nothing can be selected.");
|
|
1986
|
+
for (const id of await resolve(device)) protectedDisks2.add(id);
|
|
1987
|
+
}
|
|
1988
|
+
for (const container of containers) {
|
|
1989
|
+
if (container.PhysicalStores?.length !== 1 || container.Volumes?.some((v) => v.Roles?.some((r) => r === "System" || r === "Data"))) {
|
|
1990
|
+
for (const id of await resolve(container.ContainerReference)) protectedDisks2.add(id);
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
const registry = await plist(["/usr/sbin/ioreg", "-a", "-r", "-c", "IOMedia", "-d", "1"], signal);
|
|
1994
|
+
if (!Array.isArray(registry)) throw new Error("Cannot identify attached media. Nothing can be selected.");
|
|
1995
|
+
const disks3 = [];
|
|
1996
|
+
for (const id of physical) {
|
|
1997
|
+
if (protectedDisks2.has(id)) continue;
|
|
1998
|
+
const d = await info(id);
|
|
1999
|
+
const media = registry.filter((m) => m["BSD Name"] === id && m.Whole === true);
|
|
2000
|
+
const layout = listing.AllDisksAndPartitions?.find((p) => p.DeviceIdentifier === id);
|
|
2001
|
+
if (!layout || JSON.stringify(layout).includes("Apple_CoreStorage") || JSON.stringify(layout).includes("Apple_RAID")) continue;
|
|
2002
|
+
if (d.DeviceIdentifier !== id || d.DeviceNode !== `/dev/${id}` || d.WholeDisk !== true || d.Internal !== false || d.BusProtocol !== "USB" || d.WritableMedia !== true || d.DeviceBlockSize !== 512 || d.RAIDMaster === true || d.RAIDSlice === true || !positive(d.Size) || media.length !== 1 || !positive(media[0].IORegistryEntryID)) continue;
|
|
2003
|
+
const identity2 = JSON.stringify([media[0].IORegistryEntryID, d.Size, d.DeviceTreePath ?? "", d.MediaName ?? ""]);
|
|
2004
|
+
if (id === unmounted) {
|
|
2005
|
+
const volumes = [id, ...(layout.Partitions ?? []).map((p) => p.DeviceIdentifier)];
|
|
2006
|
+
const targetStores = new Set(volumes);
|
|
2007
|
+
for (const c of containers) {
|
|
2008
|
+
if (c.PhysicalStores?.some((s) => targetStores.has(s.DeviceIdentifier))) {
|
|
2009
|
+
volumes.push(...(c.Volumes ?? []).map((v) => v.DeviceIdentifier));
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
for (const volume of volumes) {
|
|
2013
|
+
if (!deviceID.test(volume)) throw new Error("Cannot verify all target volumes are unmounted.");
|
|
2014
|
+
const v = await info(volume);
|
|
2015
|
+
if (v.Mounted === true || typeof v.MountPoint === "string" && v.MountPoint !== "") {
|
|
2016
|
+
throw new Error("A target volume is still mounted or was remounted. Close apps using the USB and try again.");
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
disks3.push({ id, bytes: d.Size, name: cleanText(d.MediaName || d.IORegistryEntryName || "USB drive"), identity: identity2 });
|
|
2021
|
+
}
|
|
2022
|
+
return disks3;
|
|
2023
|
+
}
|
|
2024
|
+
async function authorize(signal) {
|
|
2025
|
+
await command(["/usr/bin/sudo", "-v"], signal, { terminal: true });
|
|
2026
|
+
}
|
|
2027
|
+
var platform = {
|
|
2028
|
+
id: "macos",
|
|
2029
|
+
label: "macOS / x86_64 UEFI",
|
|
2030
|
+
successTitle: "USB safely ejected",
|
|
2031
|
+
successDetail: "You can now remove the USB and boot your target computer.",
|
|
2032
|
+
authorizationDetail: "The ordinary macOS sudo prompt follows. Boo Flash never reads your password.",
|
|
2033
|
+
ejectDetail: "Waiting for macOS to make the USB safe to remove.",
|
|
2034
|
+
incompleteDetail: "USB may be incomplete; not safely ejected. Eject it with macOS before removal.",
|
|
2035
|
+
finalAuthorization: false,
|
|
2036
|
+
reunmountBeforeEject: false,
|
|
2037
|
+
dependencies,
|
|
2038
|
+
installDependencies: install,
|
|
2039
|
+
disks,
|
|
2040
|
+
authorize,
|
|
2041
|
+
async unmount(disk, signal) {
|
|
2042
|
+
if (!diskID.test(disk.id)) throw new Error("Invalid macOS disk identifier.");
|
|
2043
|
+
await command(["/usr/sbin/diskutil", "unmountDisk", `/dev/${disk.id}`], signal);
|
|
2044
|
+
},
|
|
2045
|
+
writer(disk) {
|
|
2046
|
+
if (!diskID.test(disk.id)) throw new Error("Invalid macOS disk identifier.");
|
|
2047
|
+
return startWriter("/usr/bin/sudo", ["-n", "--", "/bin/dd", `of=/dev/r${disk.id}`, "bs=4m"]);
|
|
2048
|
+
},
|
|
2049
|
+
async flush(_disk, signal) {
|
|
2050
|
+
await command(["/bin/sync"], signal);
|
|
2051
|
+
},
|
|
2052
|
+
async eject(disk, signal) {
|
|
2053
|
+
if (!diskID.test(disk.id)) throw new Error("Invalid macOS disk identifier.");
|
|
2054
|
+
await command(["/usr/sbin/diskutil", "eject", `/dev/${disk.id}`], signal);
|
|
2055
|
+
}
|
|
2056
|
+
};
|
|
2057
|
+
|
|
2058
|
+
// src/backends/linux.ts
|
|
2059
|
+
import { lstatSync as lstatSync2, realpathSync as realpathSync2 } from "node:fs";
|
|
2060
|
+
import { lstat, readFile, realpath as realpath2 } from "node:fs/promises";
|
|
2061
|
+
import { tmpdir as tmpdir2, userInfo as userInfo2 } from "node:os";
|
|
2062
|
+
import { basename, dirname as dirname2, join as join3 } from "node:path";
|
|
2063
|
+
var diskID2 = /^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$/;
|
|
2064
|
+
var stableDevice = /^\/dev\/disk\/by-id\/[A-Za-z0-9._:+\\-]{1,240}$/;
|
|
2065
|
+
var majorMinor = /^(0|[1-9][0-9]*):(0|[1-9][0-9]*)$/;
|
|
2066
|
+
var tools = ["sudo", "dd", "sync", "lsblk", "findmnt", "udevadm", "umount", "udisksctl"];
|
|
2067
|
+
var protectedSystemPaths = ["/", "/boot", "/boot/efi", "/efi", "/usr", "/var", "/opt", "/srv", "/home"];
|
|
2068
|
+
var findmntSourceColumn = "SOURCE";
|
|
2069
|
+
function exactObject(value) {
|
|
2070
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2071
|
+
}
|
|
2072
|
+
function parseTopology(input) {
|
|
2073
|
+
let root;
|
|
2074
|
+
try {
|
|
2075
|
+
root = JSON.parse(input);
|
|
2076
|
+
} catch {
|
|
2077
|
+
throw new Error("Linux block-device inventory is not valid JSON. Nothing can be selected.");
|
|
2078
|
+
}
|
|
2079
|
+
if (!exactObject(root) || !Array.isArray(root.blockdevices)) {
|
|
2080
|
+
throw new Error("Linux block-device inventory is incomplete. Nothing can be selected.");
|
|
2081
|
+
}
|
|
2082
|
+
const devices = /* @__PURE__ */ new Map();
|
|
2083
|
+
const parents = /* @__PURE__ */ new Map();
|
|
2084
|
+
const children2 = /* @__PURE__ */ new Map();
|
|
2085
|
+
const visit = (raw, parent) => {
|
|
2086
|
+
if (!exactObject(raw)) throw new Error("Linux block-device inventory contains an invalid device. Nothing can be selected.");
|
|
2087
|
+
const block = raw;
|
|
2088
|
+
if (typeof block.path !== "string" || !/^\/dev\/[^\x00-\x1f\x7f-\x9f]{1,300}$/.test(block.path) || typeof block.name !== "string" || typeof block.kname !== "string" || typeof block.type !== "string" || !majorMinor.test(String(block["maj:min"])) || !Number.isSafeInteger(block.size) || Number(block.size) < 0 || !positive(block["log-sec"]) || typeof block.ro !== "boolean" || typeof block.rm !== "boolean" || !(block.tran === null || typeof block.tran === "string") || !(block.model === null || typeof block.model === "string") || !(block.fstype === null || typeof block.fstype === "string") || !(block.uuid === null || typeof block.uuid === "string") || !(block.label === null || typeof block.label === "string") || !(block.partuuid === null || typeof block.partuuid === "string") || !(block.partlabel === null || typeof block.partlabel === "string") || !Array.isArray(block.mountpoints) || block.mountpoints.some((point) => !(point === null || typeof point === "string"))) {
|
|
2089
|
+
throw new Error("Linux block-device inventory contains unsupported values. Nothing can be selected.");
|
|
2090
|
+
}
|
|
2091
|
+
const key = String(block["maj:min"]);
|
|
2092
|
+
const device = {
|
|
2093
|
+
id: basename(block.path),
|
|
2094
|
+
path: block.path,
|
|
2095
|
+
type: block.type,
|
|
2096
|
+
size: Number(block.size),
|
|
2097
|
+
model: cleanText(block.model ?? "USB drive").trim() || "USB drive",
|
|
2098
|
+
transport: block.tran,
|
|
2099
|
+
removable: block.rm,
|
|
2100
|
+
readonly: block.ro,
|
|
2101
|
+
sector: block["log-sec"],
|
|
2102
|
+
majorMinor: key,
|
|
2103
|
+
mountpoints: block.mountpoints.filter((point) => typeof point === "string" && point.length > 0),
|
|
2104
|
+
fstype: block.fstype,
|
|
2105
|
+
uuid: block.uuid,
|
|
2106
|
+
label: block.label,
|
|
2107
|
+
partuuid: block.partuuid,
|
|
2108
|
+
partlabel: block.partlabel,
|
|
2109
|
+
ambiguous: false
|
|
2110
|
+
};
|
|
2111
|
+
const current = devices.get(key);
|
|
2112
|
+
if (current) {
|
|
2113
|
+
const structural = ["type", "size", "readonly", "sector", "fstype", "uuid", "label", "partuuid", "partlabel"];
|
|
2114
|
+
if (structural.some((field) => current[field] !== device[field])) {
|
|
2115
|
+
throw new Error("Linux block-device inventory disagrees about a device. Nothing can be selected.");
|
|
2116
|
+
}
|
|
2117
|
+
if (JSON.stringify(current) !== JSON.stringify(device)) current.ambiguous = true;
|
|
2118
|
+
} else devices.set(key, device);
|
|
2119
|
+
if (parent) {
|
|
2120
|
+
if (!parents.has(key)) parents.set(key, /* @__PURE__ */ new Set());
|
|
2121
|
+
if (!children2.has(parent)) children2.set(parent, /* @__PURE__ */ new Set());
|
|
2122
|
+
parents.get(key).add(parent);
|
|
2123
|
+
children2.get(parent).add(key);
|
|
2124
|
+
}
|
|
2125
|
+
if (block.children !== void 0 && !Array.isArray(block.children)) {
|
|
2126
|
+
throw new Error("Linux block-device relationships are invalid. Nothing can be selected.");
|
|
2127
|
+
}
|
|
2128
|
+
for (const child of block.children ?? []) visit(child, key);
|
|
2129
|
+
};
|
|
2130
|
+
for (const block of root.blockdevices) visit(block);
|
|
2131
|
+
if (!devices.size) throw new Error("Cannot resolve Linux block devices. Nothing can be selected.");
|
|
2132
|
+
return { devices, parents, children: children2 };
|
|
2133
|
+
}
|
|
2134
|
+
function related(start, edges) {
|
|
2135
|
+
const found = /* @__PURE__ */ new Set();
|
|
2136
|
+
const pending = [start];
|
|
2137
|
+
while (pending.length) {
|
|
2138
|
+
const key = pending.pop();
|
|
2139
|
+
if (found.has(key)) continue;
|
|
2140
|
+
found.add(key);
|
|
2141
|
+
for (const next of edges.get(key) ?? []) pending.push(next);
|
|
2142
|
+
}
|
|
2143
|
+
return found;
|
|
2144
|
+
}
|
|
2145
|
+
function protectedDisks(topology2, protectedDevices) {
|
|
2146
|
+
const result = /* @__PURE__ */ new Set();
|
|
2147
|
+
for (const key of protectedDevices) {
|
|
2148
|
+
const match = majorMinor.exec(key);
|
|
2149
|
+
if (!match) throw new Error("A protected Linux path has an invalid backing-device identity. Nothing can be selected.");
|
|
2150
|
+
if (!topology2.devices.has(key)) throw new Error("A protected Linux path cannot be mapped to storage. Nothing can be selected.");
|
|
2151
|
+
const device = topology2.devices.get(key);
|
|
2152
|
+
const members = device.uuid ? [...topology2.devices.entries()].filter(([, candidate]) => candidate.uuid === device.uuid).map(([candidate]) => candidate) : [key];
|
|
2153
|
+
for (const member of members) {
|
|
2154
|
+
const ancestors = related(member, topology2.parents);
|
|
2155
|
+
const disks3 = [...ancestors].filter((candidate) => topology2.devices.get(candidate)?.type === "disk");
|
|
2156
|
+
if (!disks3.length) throw new Error("A protected Linux path has unsupported backing storage. Nothing can be selected.");
|
|
2157
|
+
for (const disk of disks3) result.add(disk);
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
return result;
|
|
2161
|
+
}
|
|
2162
|
+
function eligibleDevices(topology2, protectedDevices, unmounted, usbParents) {
|
|
2163
|
+
const protectedRoots = protectedDisks(topology2, protectedDevices);
|
|
2164
|
+
const uuidMembers = /* @__PURE__ */ new Map();
|
|
2165
|
+
for (const device of topology2.devices.values()) if (device.uuid) {
|
|
2166
|
+
const members = uuidMembers.get(device.uuid) ?? [];
|
|
2167
|
+
members.push(device);
|
|
2168
|
+
uuidMembers.set(device.uuid, members);
|
|
2169
|
+
}
|
|
2170
|
+
const activeSharedFilesystems = new Set([...uuidMembers.entries()].filter(([, members]) => members.length > 1 && members.some((member) => member.mountpoints.length > 0)).map(([uuid]) => uuid));
|
|
2171
|
+
const parentCounts = /* @__PURE__ */ new Map();
|
|
2172
|
+
if (usbParents) for (const [key, parent] of usbParents) {
|
|
2173
|
+
if (!topology2.devices.has(key) || topology2.parents.get(key)?.size) continue;
|
|
2174
|
+
parentCounts.set(parent, (parentCounts.get(parent) ?? 0) + 1);
|
|
2175
|
+
}
|
|
2176
|
+
return [...topology2.devices.values()].filter((device) => {
|
|
2177
|
+
if (device.type !== "disk" || device.transport !== "usb" || device.readonly || device.sector !== 512 || !positive(device.size) || device.ambiguous || device.path !== `/dev/${device.id}` || !diskID2.test(device.id) || protectedRoots.has(device.majorMinor) || topology2.parents.get(device.majorMinor)?.size || usbParents && (!usbParents.has(device.majorMinor) || parentCounts.get(usbParents.get(device.majorMinor)) !== 1)) return false;
|
|
2178
|
+
const descendants = related(device.majorMinor, topology2.children);
|
|
2179
|
+
if ([...descendants].some((key) => {
|
|
2180
|
+
const child = topology2.devices.get(key);
|
|
2181
|
+
return key !== device.majorMinor && child.type !== "part" || child.mountpoints.some((point) => point === "[SWAP]") || child.uuid !== null && activeSharedFilesystems.has(child.uuid) || unmounted === device.id && child.mountpoints.length > 0;
|
|
2182
|
+
})) return false;
|
|
2183
|
+
return true;
|
|
2184
|
+
});
|
|
2185
|
+
}
|
|
2186
|
+
async function topology(signal) {
|
|
2187
|
+
return parseTopology(await command([
|
|
2188
|
+
"lsblk",
|
|
2189
|
+
"--json",
|
|
2190
|
+
"--bytes",
|
|
2191
|
+
"--paths",
|
|
2192
|
+
"--tree",
|
|
2193
|
+
"--output",
|
|
2194
|
+
"NAME,KNAME,PATH,TYPE,SIZE,MODEL,TRAN,RM,RO,LOG-SEC,MOUNTPOINTS,MAJ:MIN,PKNAME,FSTYPE,UUID,LABEL,PARTUUID,PARTLABEL"
|
|
2195
|
+
], signal, { limit: 8 * 1024 ** 2 }));
|
|
2196
|
+
}
|
|
2197
|
+
async function protectedPaths(source, staging) {
|
|
2198
|
+
const paths = [
|
|
2199
|
+
...protectedSystemPaths,
|
|
2200
|
+
userInfo2().homedir,
|
|
2201
|
+
process.cwd(),
|
|
2202
|
+
source,
|
|
2203
|
+
process.execPath,
|
|
2204
|
+
staging
|
|
2205
|
+
];
|
|
2206
|
+
paths.push(resolvePackage("@opentui/core"));
|
|
2207
|
+
for (const name2 of ["@opentui/solid", "solid-js"]) {
|
|
2208
|
+
try {
|
|
2209
|
+
paths.push(resolvePackage(name2));
|
|
2210
|
+
} catch {
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
for (const name2 of [`@opentui/core-linux-${process.arch}`, `@opentui/core-linux-${process.arch}-musl`]) {
|
|
2214
|
+
try {
|
|
2215
|
+
paths.push(resolvePackage(name2));
|
|
2216
|
+
} catch {
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
if (process.env.OTUI_ASSET_ROOT) paths.push(process.env.OTUI_ASSET_ROOT);
|
|
2220
|
+
for (const name2 of [...commonTools, ...candidateTools, ...tools]) {
|
|
2221
|
+
const path = executable(name2);
|
|
2222
|
+
if (path) paths.push(path);
|
|
2223
|
+
}
|
|
2224
|
+
const result = [];
|
|
2225
|
+
for (const path of paths) {
|
|
2226
|
+
try {
|
|
2227
|
+
result.push(await realpath2(path));
|
|
2228
|
+
} catch (error) {
|
|
2229
|
+
if (error.code !== "ENOENT") throw error;
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
return result;
|
|
2233
|
+
}
|
|
2234
|
+
async function usbParent(device) {
|
|
2235
|
+
let path;
|
|
2236
|
+
try {
|
|
2237
|
+
path = await realpath2(`/sys/class/block/${device.id}/device`);
|
|
2238
|
+
} catch (error) {
|
|
2239
|
+
if (error.code === "ENOENT") return null;
|
|
2240
|
+
throw error;
|
|
2241
|
+
}
|
|
2242
|
+
while (path.startsWith("/sys/")) {
|
|
2243
|
+
try {
|
|
2244
|
+
await Promise.all([readFile(join3(path, "idVendor"), "utf8"), readFile(join3(path, "idProduct"), "utf8")]);
|
|
2245
|
+
return path;
|
|
2246
|
+
} catch (error) {
|
|
2247
|
+
if (error.code !== "ENOENT") throw error;
|
|
2248
|
+
}
|
|
2249
|
+
const next = dirname2(path);
|
|
2250
|
+
if (next === path) break;
|
|
2251
|
+
path = next;
|
|
2252
|
+
}
|
|
2253
|
+
return null;
|
|
2254
|
+
}
|
|
2255
|
+
async function usbParentMap(inventory) {
|
|
2256
|
+
const result = /* @__PURE__ */ new Map();
|
|
2257
|
+
for (const device of inventory.devices.values()) {
|
|
2258
|
+
if (inventory.parents.get(device.majorMinor)?.size || device.type === "part") continue;
|
|
2259
|
+
const parent = await usbParent(device);
|
|
2260
|
+
if (parent) result.set(device.majorMinor, parent);
|
|
2261
|
+
}
|
|
2262
|
+
return result;
|
|
2263
|
+
}
|
|
2264
|
+
async function sourceIdentity(source, signal, optional = false) {
|
|
2265
|
+
const suffix = source.indexOf("[");
|
|
2266
|
+
const path = suffix < 0 ? source : source.slice(0, suffix);
|
|
2267
|
+
if (!path.startsWith("/dev/") || cleanText(path) !== path) return null;
|
|
2268
|
+
if (optional) {
|
|
2269
|
+
try {
|
|
2270
|
+
await lstat(path);
|
|
2271
|
+
} catch (error) {
|
|
2272
|
+
if (error.code === "ENOENT") return null;
|
|
2273
|
+
throw error;
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
const identity2 = (await command(["lsblk", "--noheadings", "--raw", "--nodeps", "--output", "MAJ:MIN", "--", path], signal)).trim();
|
|
2277
|
+
if (!majorMinor.test(identity2) || identity2.includes("\n")) {
|
|
2278
|
+
throw new Error("A protected Linux block source is ambiguous. Nothing can be selected.");
|
|
2279
|
+
}
|
|
2280
|
+
return identity2;
|
|
2281
|
+
}
|
|
2282
|
+
async function backingDevices(path, signal) {
|
|
2283
|
+
const output = await command([
|
|
2284
|
+
"findmnt",
|
|
2285
|
+
"--json",
|
|
2286
|
+
"--canonicalize",
|
|
2287
|
+
"--first-only",
|
|
2288
|
+
"--target",
|
|
2289
|
+
path,
|
|
2290
|
+
"--output",
|
|
2291
|
+
`MAJ:MIN,${findmntSourceColumn}`
|
|
2292
|
+
], signal, { limit: 1024 ** 2 });
|
|
2293
|
+
let parsed;
|
|
2294
|
+
try {
|
|
2295
|
+
parsed = JSON.parse(output);
|
|
2296
|
+
} catch {
|
|
2297
|
+
throw new Error("Cannot parse protected Linux storage. Nothing can be selected.");
|
|
2298
|
+
}
|
|
2299
|
+
if (!exactObject(parsed) || !Array.isArray(parsed.filesystems) || parsed.filesystems.length !== 1 || !exactObject(parsed.filesystems[0]) || !majorMinor.test(String(parsed.filesystems[0]["maj:min"])) || !(parsed.filesystems[0].source === null || typeof parsed.filesystems[0].source === "string")) {
|
|
2300
|
+
throw new Error("Cannot resolve a protected Linux path to storage. Nothing can be selected.");
|
|
2301
|
+
}
|
|
2302
|
+
const result = /* @__PURE__ */ new Set();
|
|
2303
|
+
const mounted = String(parsed.filesystems[0]["maj:min"]);
|
|
2304
|
+
if (!mounted.startsWith("0:")) result.add(mounted);
|
|
2305
|
+
if (typeof parsed.filesystems[0].source === "string") {
|
|
2306
|
+
const identity2 = await sourceIdentity(parsed.filesystems[0].source, signal);
|
|
2307
|
+
if (identity2) result.add(identity2);
|
|
2308
|
+
}
|
|
2309
|
+
if (!result.size) throw new Error("A protected Linux path has unsupported non-block backing storage. Nothing can be selected.");
|
|
2310
|
+
return [...result];
|
|
2311
|
+
}
|
|
2312
|
+
async function fstabDevices(inventory, signal) {
|
|
2313
|
+
let fstab;
|
|
2314
|
+
try {
|
|
2315
|
+
fstab = await readFile("/etc/fstab", "utf8");
|
|
2316
|
+
} catch (error) {
|
|
2317
|
+
if (error.code === "ENOENT") return [];
|
|
2318
|
+
throw error;
|
|
2319
|
+
}
|
|
2320
|
+
if (!fstab.split("\n").some((line) => line.trim() && !line.trim().startsWith("#"))) return [];
|
|
2321
|
+
const output = await command(["findmnt", "--fstab", "--json", "--output", findmntSourceColumn], signal, { limit: 1024 ** 2 });
|
|
2322
|
+
const sources = parseConfiguredSources(output);
|
|
2323
|
+
const direct = (await Promise.all(sources.map((source) => sourceIdentity(source, signal, true)))).filter((value) => value !== null);
|
|
2324
|
+
return [...configuredTagDevices(inventory, sources), ...direct];
|
|
2325
|
+
}
|
|
2326
|
+
function parseConfiguredSources(output) {
|
|
2327
|
+
let parsed;
|
|
2328
|
+
try {
|
|
2329
|
+
parsed = JSON.parse(output);
|
|
2330
|
+
} catch {
|
|
2331
|
+
throw new Error("Cannot parse configured Linux storage. Nothing can be selected.");
|
|
2332
|
+
}
|
|
2333
|
+
if (!exactObject(parsed) || !Array.isArray(parsed.filesystems)) {
|
|
2334
|
+
throw new Error("Cannot resolve configured Linux storage. Nothing can be selected.");
|
|
2335
|
+
}
|
|
2336
|
+
const sources = [];
|
|
2337
|
+
for (const filesystem of parsed.filesystems) {
|
|
2338
|
+
if (!exactObject(filesystem) || !(filesystem.source === null || typeof filesystem.source === "string")) {
|
|
2339
|
+
throw new Error("Configured Linux storage is invalid. Nothing can be selected.");
|
|
2340
|
+
}
|
|
2341
|
+
if (typeof filesystem.source === "string") sources.push(filesystem.source);
|
|
2342
|
+
}
|
|
2343
|
+
return sources;
|
|
2344
|
+
}
|
|
2345
|
+
function configuredTagDevices(topology2, sources) {
|
|
2346
|
+
const result = [];
|
|
2347
|
+
for (const source of sources) {
|
|
2348
|
+
const match = /^(UUID|LABEL|PARTUUID|PARTLABEL)=(.+)$/.exec(source);
|
|
2349
|
+
if (!match) continue;
|
|
2350
|
+
const field = { UUID: "uuid", LABEL: "label", PARTUUID: "partuuid", PARTLABEL: "partlabel" }[match[1]];
|
|
2351
|
+
for (const device of topology2.devices.values()) if (device[field] === match[2]) result.push(device.majorMinor);
|
|
2352
|
+
}
|
|
2353
|
+
return result;
|
|
2354
|
+
}
|
|
2355
|
+
function properties(input) {
|
|
2356
|
+
const result = /* @__PURE__ */ new Map();
|
|
2357
|
+
for (const line of input.trim().split("\n")) {
|
|
2358
|
+
const split = line.indexOf("=");
|
|
2359
|
+
if (split < 1) throw new Error("Linux device identity is invalid. Nothing can be selected.");
|
|
2360
|
+
const key = line.slice(0, split);
|
|
2361
|
+
const value = line.slice(split + 1);
|
|
2362
|
+
if (result.has(key)) throw new Error("Linux device identity contains duplicate properties. Nothing can be selected.");
|
|
2363
|
+
result.set(key, value);
|
|
2364
|
+
}
|
|
2365
|
+
return result;
|
|
2366
|
+
}
|
|
2367
|
+
async function identity(device, signal) {
|
|
2368
|
+
const info = properties(await command(["udevadm", "info", "--query=property", `--name=${device.path}`], signal));
|
|
2369
|
+
const stat = await lstat(device.path);
|
|
2370
|
+
const [major, minor] = device.majorMinor.split(":");
|
|
2371
|
+
if (!stat.isBlockDevice() || info.get("DEVNAME") !== device.path || info.get("DEVTYPE") !== "disk" || info.get("DEVPATH")?.startsWith("/devices/") !== true || info.get("MAJOR") !== major || info.get("MINOR") !== minor) {
|
|
2372
|
+
throw new Error("Linux USB identity does not match its block device. Nothing can be selected.");
|
|
2373
|
+
}
|
|
2374
|
+
const links = (info.get("DEVLINKS") ?? "").split(" ").filter((path) => path.startsWith("/dev/disk/by-id/") && !/-part[0-9]+$/.test(path) && cleanText(path) === path).sort();
|
|
2375
|
+
const stable = [];
|
|
2376
|
+
for (const link2 of links) {
|
|
2377
|
+
try {
|
|
2378
|
+
if ((await lstat(link2)).isSymbolicLink() && await realpath2(link2) === device.path) stable.push(link2);
|
|
2379
|
+
} catch (error) {
|
|
2380
|
+
if (error.code !== "ENOENT") throw error;
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
if (!stable.length) return null;
|
|
2384
|
+
await command(["udisksctl", "info", "--block-device", stable[0]], signal, { limit: 1024 ** 2 });
|
|
2385
|
+
return { device: stable[0], identity: JSON.stringify([
|
|
2386
|
+
device.majorMinor,
|
|
2387
|
+
device.size,
|
|
2388
|
+
info.get("DEVPATH"),
|
|
2389
|
+
stat.rdev,
|
|
2390
|
+
stat.ino,
|
|
2391
|
+
stat.ctimeMs,
|
|
2392
|
+
stable[0],
|
|
2393
|
+
info.get("ID_PATH") ?? "",
|
|
2394
|
+
info.get("ID_SERIAL") ?? "",
|
|
2395
|
+
device.model
|
|
2396
|
+
]) };
|
|
2397
|
+
}
|
|
2398
|
+
async function disks2(signal, source, staging = tmpdir2(), unmounted) {
|
|
2399
|
+
await command(["udisksctl", "status"], signal, { limit: 1024 ** 2 });
|
|
2400
|
+
const inventory = await topology(signal);
|
|
2401
|
+
const usbParents = await usbParentMap(inventory);
|
|
2402
|
+
const roots = [...new Set(await protectedPaths(source, staging))];
|
|
2403
|
+
const protectedDevices = [
|
|
2404
|
+
...(await Promise.all(roots.map((path) => backingDevices(path, signal)))).flat(),
|
|
2405
|
+
...await fstabDevices(inventory, signal)
|
|
2406
|
+
];
|
|
2407
|
+
const eligible = eligibleDevices(inventory, protectedDevices, unmounted, usbParents);
|
|
2408
|
+
const result = [];
|
|
2409
|
+
for (const device of eligible) {
|
|
2410
|
+
const attached = await identity(device, signal);
|
|
2411
|
+
if (attached) result.push({ id: device.id, name: device.model, bytes: device.size, ...attached });
|
|
2412
|
+
}
|
|
2413
|
+
return result;
|
|
2414
|
+
}
|
|
2415
|
+
function dependencies2(includeGitHub = false) {
|
|
2416
|
+
const required2 = [...commonTools, ...tools, ...includeGitHub ? candidateTools : []];
|
|
2417
|
+
return {
|
|
2418
|
+
missing: missing(required2),
|
|
2419
|
+
installer: null,
|
|
2420
|
+
guidance: "Install the missing commands with your Linux distribution's package manager, then refresh."
|
|
2421
|
+
};
|
|
2422
|
+
}
|
|
2423
|
+
function required(name2) {
|
|
2424
|
+
const path = executable(name2);
|
|
2425
|
+
if (!path) throw new Error(`Missing ${name2}. Install it with your Linux distribution's package manager, then refresh.`);
|
|
2426
|
+
return path;
|
|
2427
|
+
}
|
|
2428
|
+
async function currentTarget(disk, signal) {
|
|
2429
|
+
const inventory = await topology(signal);
|
|
2430
|
+
const matches = [...inventory.devices.values()].filter((device) => device.id === disk.id);
|
|
2431
|
+
if (matches.length !== 1) throw new Error("The selected Linux USB is no longer uniquely attached.");
|
|
2432
|
+
const target = matches[0];
|
|
2433
|
+
if (!eligibleDevices(inventory, [], void 0, await usbParentMap(inventory)).includes(target)) {
|
|
2434
|
+
throw new Error("The selected Linux USB is no longer eligible.");
|
|
2435
|
+
}
|
|
2436
|
+
return { inventory, target };
|
|
2437
|
+
}
|
|
2438
|
+
function validateDeviceRoute(disk, inspect = { symlink: (path) => lstatSync2(path).isSymbolicLink(), realpath: realpathSync2 }) {
|
|
2439
|
+
if (!diskID2.test(disk.id) || typeof disk.device !== "string" || !stableDevice.test(disk.device) || !inspect.symlink(disk.device) || inspect.realpath(disk.device) !== `/dev/${disk.id}`) {
|
|
2440
|
+
throw new Error("Invalid or changed Linux USB identity.");
|
|
2441
|
+
}
|
|
2442
|
+
return disk.device;
|
|
2443
|
+
}
|
|
2444
|
+
var platform2 = {
|
|
2445
|
+
id: "linux",
|
|
2446
|
+
label: "Linux / x86_64 UEFI",
|
|
2447
|
+
successTitle: "USB safely powered off",
|
|
2448
|
+
successDetail: "You can now remove the USB and boot your target computer.",
|
|
2449
|
+
authorizationDetail: "The ordinary Linux sudo prompt follows. Boo Flash never reads your password.",
|
|
2450
|
+
ejectDetail: "Asking Linux to flush and safely power off the USB.",
|
|
2451
|
+
incompleteDetail: "USB may be incomplete; not safely powered off. Use Linux's safe-removal control before removal.",
|
|
2452
|
+
finalAuthorization: true,
|
|
2453
|
+
reunmountBeforeEject: true,
|
|
2454
|
+
dependencies: dependencies2,
|
|
2455
|
+
async installDependencies() {
|
|
2456
|
+
throw new Error("Automatic dependency installation is not supported on Linux. Install the missing commands with your distribution's package manager.");
|
|
2457
|
+
},
|
|
2458
|
+
disks: disks2,
|
|
2459
|
+
async authorize(signal) {
|
|
2460
|
+
await command([required("sudo"), "-v"], signal, { terminal: true });
|
|
2461
|
+
},
|
|
2462
|
+
async unmount(disk, signal) {
|
|
2463
|
+
const { inventory, target } = await currentTarget(disk, signal);
|
|
2464
|
+
const descendants = [...related(target.majorMinor, inventory.children)].map((key) => inventory.devices.get(key)).filter((device) => device.mountpoints.length > 0).reverse();
|
|
2465
|
+
for (const device of descendants) {
|
|
2466
|
+
await command([required("sudo"), "-n", "--", required("umount"), "--all-targets", "--", device.path], signal);
|
|
2467
|
+
}
|
|
2468
|
+
},
|
|
2469
|
+
writer(disk) {
|
|
2470
|
+
const device = validateDeviceRoute(disk);
|
|
2471
|
+
return startWriter(required("sudo"), ["-n", "--", required("dd"), `of=${device}`, "bs=4M", "conv=fsync", "status=none"]);
|
|
2472
|
+
},
|
|
2473
|
+
async flush(_disk, signal) {
|
|
2474
|
+
await command([required("sync")], signal);
|
|
2475
|
+
},
|
|
2476
|
+
async eject(disk, signal) {
|
|
2477
|
+
const device = validateDeviceRoute(disk);
|
|
2478
|
+
await command([required("sudo"), "-n", "--", required("udisksctl"), "power-off", "--block-device", device, "--no-user-interaction"], signal);
|
|
2479
|
+
}
|
|
2480
|
+
};
|
|
2481
|
+
|
|
2482
|
+
// src/cache.ts
|
|
2483
|
+
import { createHash } from "node:crypto";
|
|
2484
|
+
import { constants as constants2 } from "node:fs";
|
|
2485
|
+
import { chmod, link, lstat as lstat2, mkdir, open, readdir, realpath as realpath3, unlink } from "node:fs/promises";
|
|
2486
|
+
import { tmpdir as tmpdir3 } from "node:os";
|
|
2487
|
+
import { join as join4 } from "node:path";
|
|
2488
|
+
var entryName = /^[1-9][0-9]*-[a-f0-9]{64}\.(zip|raw\.zst)$/;
|
|
2489
|
+
var recovery = "Clear cached images (or run --clear-cache), then retry. Unsafe paths must be corrected manually. No disk was written.";
|
|
2490
|
+
var code = (error) => error.code;
|
|
2491
|
+
var owned = (stat) => stat.uid === process.getuid?.() && (stat.mode & 63) === 0;
|
|
2492
|
+
async function directory() {
|
|
2493
|
+
const uid = process.getuid?.();
|
|
2494
|
+
if (uid === void 0 || uid === 0) throw new Error("Use a normal user account for the image cache, not sudo.");
|
|
2495
|
+
const path = join4(await realpath3(tmpdir3()), `boo-flash-cache-${uid}`);
|
|
2496
|
+
try {
|
|
2497
|
+
await mkdir(path, { mode: 448 });
|
|
2498
|
+
} catch (error) {
|
|
2499
|
+
if (code(error) !== "EEXIST") throw error;
|
|
2500
|
+
}
|
|
2501
|
+
const stat = await lstat2(path);
|
|
2502
|
+
if (!stat.isDirectory() || !owned(stat)) throw new Error(`Unsafe image cache directory: ${path}. ${recovery}`);
|
|
2503
|
+
return path;
|
|
2504
|
+
}
|
|
2505
|
+
function name(image) {
|
|
2506
|
+
if (!positive(image.id) || !positive(image.bytes) || image.digest.length !== 64 || !/^[a-f0-9]{64}$/.test(image.digest)) throw new Error("Invalid cache identity.");
|
|
2507
|
+
return `${image.id}-${image.digest}.${image.cacheKind ?? "zip"}`;
|
|
2508
|
+
}
|
|
2509
|
+
function regular(stat) {
|
|
2510
|
+
if (!stat.isFile() || !owned(stat)) throw new Error(`Unsafe cached image. ${recovery}`);
|
|
2511
|
+
}
|
|
2512
|
+
async function reuseArchive(image, snapshot, signal, progress = () => {
|
|
2513
|
+
}) {
|
|
2514
|
+
const filename = name(image);
|
|
2515
|
+
const path = join4(await directory(), filename);
|
|
2516
|
+
let stat;
|
|
2517
|
+
try {
|
|
2518
|
+
stat = await lstat2(path);
|
|
2519
|
+
} catch (error) {
|
|
2520
|
+
if (code(error) === "ENOENT") return false;
|
|
2521
|
+
throw error;
|
|
2522
|
+
}
|
|
2523
|
+
regular(stat);
|
|
2524
|
+
try {
|
|
2525
|
+
await link(path, snapshot);
|
|
2526
|
+
} catch (error) {
|
|
2527
|
+
if (code(error) === "ENOENT") return false;
|
|
2528
|
+
throw new Error(`Cannot pin cached image (${code(error)}). No disk was written.`);
|
|
2529
|
+
}
|
|
2530
|
+
const file = await open(snapshot, constants2.O_RDONLY | constants2.O_NOFOLLOW | constants2.O_NONBLOCK);
|
|
2531
|
+
try {
|
|
2532
|
+
const pinned = await file.stat();
|
|
2533
|
+
regular(pinned);
|
|
2534
|
+
if (pinned.dev !== stat.dev || pinned.ino !== stat.ino || pinned.size !== image.bytes) {
|
|
2535
|
+
throw new Error(`Cached image changed or has the wrong size. ${recovery}`);
|
|
2536
|
+
}
|
|
2537
|
+
const hash = createHash("sha256");
|
|
2538
|
+
const buffer = Buffer.alloc(1024 ** 2);
|
|
2539
|
+
let count = 0;
|
|
2540
|
+
progress(0);
|
|
2541
|
+
while (true) {
|
|
2542
|
+
signal.throwIfAborted();
|
|
2543
|
+
const { bytesRead } = await file.read(buffer);
|
|
2544
|
+
if (!bytesRead) break;
|
|
2545
|
+
count += bytesRead;
|
|
2546
|
+
if (count > image.bytes) throw new Error(`Cached image exceeds its API size. ${recovery}`);
|
|
2547
|
+
hash.update(buffer.subarray(0, bytesRead));
|
|
2548
|
+
progress(count);
|
|
2549
|
+
}
|
|
2550
|
+
if (count !== image.bytes || hash.digest("hex") !== image.digest) throw new Error(`Cached image SHA-256 or size mismatch. ${recovery}`);
|
|
2551
|
+
signal.throwIfAborted();
|
|
2552
|
+
return true;
|
|
2553
|
+
} finally {
|
|
2554
|
+
await file.close();
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
async function publishArchive(image, verified, signal) {
|
|
2558
|
+
const filename = name(image);
|
|
2559
|
+
const path = join4(await directory(), filename);
|
|
2560
|
+
await chmod(verified, 256);
|
|
2561
|
+
try {
|
|
2562
|
+
await link(verified, path);
|
|
2563
|
+
} catch (error) {
|
|
2564
|
+
if (code(error) !== "EEXIST") throw new Error(`Cannot retain verified image in cache (${code(error)}). No disk was written.`);
|
|
2565
|
+
const snapshot = `${verified}.cached`;
|
|
2566
|
+
try {
|
|
2567
|
+
if (!await reuseArchive(image, snapshot, signal)) {
|
|
2568
|
+
throw new Error("Image cache was cleared during publication. Retry; no disk was written.");
|
|
2569
|
+
}
|
|
2570
|
+
} finally {
|
|
2571
|
+
try {
|
|
2572
|
+
await unlink(snapshot);
|
|
2573
|
+
} catch (error2) {
|
|
2574
|
+
if (code(error2) !== "ENOENT") throw error2;
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
async function clearCachedImages(signal) {
|
|
2580
|
+
const path = await directory();
|
|
2581
|
+
let count = 0;
|
|
2582
|
+
for (const filename of await readdir(path)) {
|
|
2583
|
+
signal.throwIfAborted();
|
|
2584
|
+
if (entryName.exec(filename)?.[0] !== filename || !positive(Number(filename.split("-")[0]))) continue;
|
|
2585
|
+
const entry = join4(path, filename);
|
|
2586
|
+
try {
|
|
2587
|
+
regular(await lstat2(entry));
|
|
2588
|
+
await unlink(entry);
|
|
2589
|
+
count++;
|
|
2590
|
+
} catch (error) {
|
|
2591
|
+
if (code(error) !== "ENOENT") throw error;
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
return `Removed ${count} cached archive(s) from ${path}. USB contents and active flashes are unchanged.`;
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
// src/backend.ts
|
|
2598
|
+
var profiles = ["intel", "amd", "nvidia"];
|
|
2599
|
+
var repository = "anomalyco/boo";
|
|
2600
|
+
var repositoryIdentities = /* @__PURE__ */ new Set([repository, "anomalyco/ghostty-distro"]);
|
|
2601
|
+
var releaseOrigin = "https://images.os.boo";
|
|
2602
|
+
var diskID3 = /^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$/;
|
|
2603
|
+
var sha256 = /^[a-f0-9]{64}$/;
|
|
2604
|
+
var semanticVersion = /^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/;
|
|
2605
|
+
var releaseVersion = /^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\+[1-9][0-9]*$/;
|
|
2606
|
+
var moduleDirectory = dirname3(fileURLToPath2(import.meta.url));
|
|
2607
|
+
function bytes(n) {
|
|
2608
|
+
return n >= 1024 ** 3 ? `${(n / 1024 ** 3).toFixed(1)} GiB` : `${(n / 1024 ** 2).toFixed(1)} MiB`;
|
|
2609
|
+
}
|
|
2610
|
+
async function prepareSafeRemoval(platform3, disk, signal, authorize2, revalidate, update) {
|
|
2611
|
+
if (platform3.finalAuthorization) {
|
|
2612
|
+
update({ stage: "Authorizing", detail: "Refreshing Linux authorization before safe power-off." });
|
|
2613
|
+
await authorize2();
|
|
2614
|
+
}
|
|
2615
|
+
if (platform3.reunmountBeforeEject) {
|
|
2616
|
+
update({ stage: "Unmounting", detail: "Removing any target volumes remounted after writing, without force." });
|
|
2617
|
+
await platform3.unmount(disk, signal);
|
|
2618
|
+
await revalidate(true);
|
|
2619
|
+
} else await revalidate();
|
|
2620
|
+
}
|
|
2621
|
+
async function discoverImages(signal) {
|
|
2622
|
+
const get = async (url, fresh = false) => JSON.parse(await command([
|
|
2623
|
+
"curl",
|
|
2624
|
+
"--fail",
|
|
2625
|
+
"--silent",
|
|
2626
|
+
"--show-error",
|
|
2627
|
+
"--proto",
|
|
2628
|
+
"=https",
|
|
2629
|
+
...fresh ? ["--header", "Cache-Control: no-cache"] : [],
|
|
2630
|
+
url
|
|
2631
|
+
], signal, { limit: 1024 ** 2 }));
|
|
2632
|
+
const channel = await get(`${releaseOrigin}/channels/main.json`, true);
|
|
2633
|
+
if (channel?.schema !== 1 || !releaseVersion.test(channel.version) || !/^[a-f0-9]{40}$/.test(channel.revision) || channel.prefix !== `releases/${channel.version}`) {
|
|
2634
|
+
throw new Error("The Boo main release channel is invalid.");
|
|
2635
|
+
}
|
|
2636
|
+
const manifest = await get(`${releaseOrigin}/${channel.prefix}/release.json`);
|
|
2637
|
+
const build = Number(channel.version.slice(channel.version.lastIndexOf("+") + 1));
|
|
2638
|
+
if (manifest?.schema !== 1 || manifest.version !== channel.version || manifest.revision !== channel.revision || !semanticVersion.test(manifest.semantic_version) || manifest.version !== `${manifest.semantic_version}+${manifest.build_number}` || manifest.build_number !== build || !positive(build) || !manifest.artifacts || Object.keys(manifest.artifacts).sort().join(",") !== [...profiles].sort().join(",")) {
|
|
2639
|
+
throw new Error("The Boo main release manifest is invalid.");
|
|
2640
|
+
}
|
|
2641
|
+
const images = profiles.map((profile) => {
|
|
2642
|
+
const artifact = manifest.artifacts[profile];
|
|
2643
|
+
const name2 = `boo-${channel.version}-x86_64-${profile}.raw.zst`;
|
|
2644
|
+
if (artifact?.image !== name2 || !positive(artifact.compressed_bytes) || !sha256.test(artifact.compressed_sha256) || !positive(artifact.raw_apparent_bytes) || !sha256.test(artifact.raw_sha256)) {
|
|
2645
|
+
throw new Error(`The Boo ${profile.toUpperCase()} release metadata is invalid.`);
|
|
2646
|
+
}
|
|
2647
|
+
return {
|
|
2648
|
+
profile,
|
|
2649
|
+
id: build,
|
|
2650
|
+
name: name2,
|
|
2651
|
+
bytes: artifact.compressed_bytes,
|
|
2652
|
+
digest: artifact.compressed_sha256,
|
|
2653
|
+
cacheKind: "raw.zst",
|
|
2654
|
+
url: `${releaseOrigin}/${channel.prefix}/${name2}`,
|
|
2655
|
+
rawBytes: artifact.raw_apparent_bytes,
|
|
2656
|
+
rawDigest: artifact.raw_sha256
|
|
2657
|
+
};
|
|
2658
|
+
});
|
|
2659
|
+
return { run: build, version: channel.version, revision: channel.revision, images };
|
|
2660
|
+
}
|
|
2661
|
+
async function discoverCandidates(signal) {
|
|
2662
|
+
const api = async (path) => JSON.parse(await command(["gh", "api", "--hostname", "github.com", `repos/${repository}/${path}`], signal));
|
|
2663
|
+
const result = await api("actions/workflows/candidate.yml/runs?status=success&per_page=10");
|
|
2664
|
+
if (!Array.isArray(result.workflow_runs)) throw new Error("Invalid GitHub candidate workflow response.");
|
|
2665
|
+
const candidates = [];
|
|
2666
|
+
for (const run of result.workflow_runs.slice(0, 10)) {
|
|
2667
|
+
if (run.status !== "completed" || run.conclusion !== "success" || run.path !== ".github/workflows/candidate.yml" || !positive(run.id) || !positive(run.run_number) || !Number.isFinite(Date.parse(run.created_at))) continue;
|
|
2668
|
+
const batch = await api(`actions/runs/${run.id}/artifacts?per_page=100`);
|
|
2669
|
+
if (!Array.isArray(batch.artifacts)) throw new Error("Invalid GitHub artifact response.");
|
|
2670
|
+
for (const a of batch.artifacts) {
|
|
2671
|
+
const match = /^boo-(intel|amd|nvidia)-candidate-([a-f0-9]{40})$/.exec(a.name);
|
|
2672
|
+
if (!match || a.expired !== false || !(Date.parse(a.expires_at) > Date.now()) || !positive(a.id) || !positive(a.size_in_bytes) || !/^sha256:[a-f0-9]{64}$/.test(a.digest) || batch.artifacts.filter((other) => other.name === a.name).length !== 1) continue;
|
|
2673
|
+
candidates.push({
|
|
2674
|
+
run: run.id,
|
|
2675
|
+
revision: match[2],
|
|
2676
|
+
images: [{
|
|
2677
|
+
profile: match[1],
|
|
2678
|
+
id: a.id,
|
|
2679
|
+
name: a.name,
|
|
2680
|
+
bytes: a.size_in_bytes,
|
|
2681
|
+
digest: a.digest.slice(7)
|
|
2682
|
+
}],
|
|
2683
|
+
candidate: { number: run.run_number, date: new Date(run.created_at).toISOString().slice(0, 10), title: cleanText(run.display_title ?? "") }
|
|
2684
|
+
});
|
|
2685
|
+
if (candidates.length === 10) return candidates;
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
return candidates;
|
|
2689
|
+
}
|
|
2690
|
+
var FlashBackend = class {
|
|
2691
|
+
constructor(demo2) {
|
|
2692
|
+
this.demo = demo2;
|
|
2693
|
+
}
|
|
2694
|
+
demo;
|
|
2695
|
+
demoInstalled = false;
|
|
2696
|
+
requirePlatform() {
|
|
2697
|
+
if (process.platform === "darwin") return platform;
|
|
2698
|
+
if (process.platform === "linux") return platform2;
|
|
2699
|
+
throw new Error("Boo Flash supports macOS and Linux. Use --demo for a non-writing preview.");
|
|
2700
|
+
}
|
|
2701
|
+
get platformLabel() {
|
|
2702
|
+
return this.demo ? "DEMO - no disks written" : this.requirePlatform().label;
|
|
2703
|
+
}
|
|
2704
|
+
get successTitle() {
|
|
2705
|
+
return this.demo ? "Demo complete" : this.requirePlatform().successTitle;
|
|
2706
|
+
}
|
|
2707
|
+
get successDetail() {
|
|
2708
|
+
return this.demo ? "Simulated success only. No USB was written or ejected." : this.requirePlatform().successDetail;
|
|
2709
|
+
}
|
|
2710
|
+
async clearCache(signal) {
|
|
2711
|
+
if (this.demo) return "Demo only: no cached images or USB contents were changed.";
|
|
2712
|
+
return clearCachedImages(signal);
|
|
2713
|
+
}
|
|
2714
|
+
dependencies(includeGitHub = false) {
|
|
2715
|
+
if (!this.demo) return this.requirePlatform().dependencies(includeGitHub);
|
|
2716
|
+
const setup = this.demo.startsWith("setup") || this.demo === "no-homebrew";
|
|
2717
|
+
return {
|
|
2718
|
+
missing: !setup || this.demoInstalled ? [] : this.demo === "setup-gh" ? includeGitHub ? ["gh"] : [] : this.demo === "setup-zstd" ? ["zstd"] : includeGitHub ? ["gh", "zstd"] : ["zstd"],
|
|
2719
|
+
installer: this.demo === "no-homebrew" ? null : "Homebrew",
|
|
2720
|
+
guidance: "Demo dependency guidance."
|
|
2721
|
+
};
|
|
2722
|
+
}
|
|
2723
|
+
async installDependencies(approved, signal, terminal) {
|
|
2724
|
+
if (!this.demo) {
|
|
2725
|
+
return terminal(() => this.requirePlatform().installDependencies(approved, signal));
|
|
2726
|
+
}
|
|
2727
|
+
await delay(2e3, void 0, { signal });
|
|
2728
|
+
return terminal(async () => {
|
|
2729
|
+
process.stdout.write("\nDEMO dependency setup: no software is installed.\nSimulating Homebrew terminal handoff. Ctrl-C cancels and quits.\n");
|
|
2730
|
+
await delay(3e3);
|
|
2731
|
+
if (this.demo === "setup-interrupted") process.stdout.write("Simulated Homebrew interruption result, without signaling Boo Flash. Quitting.\n");
|
|
2732
|
+
if (signal.aborted || this.demo === "setup-interrupted") return "cancelled";
|
|
2733
|
+
if (this.demo === "setup-error") throw new Error("Simulated Homebrew failure. No software was installed. Resolve the reported issue, then refresh to retry.");
|
|
2734
|
+
this.demoInstalled = true;
|
|
2735
|
+
return "installed";
|
|
2736
|
+
});
|
|
2737
|
+
}
|
|
2738
|
+
async catalog(signal) {
|
|
2739
|
+
if (!this.demo) return discoverImages(signal);
|
|
2740
|
+
await delay(350, void 0, { signal });
|
|
2741
|
+
return { run: 149, version: "0.1.0+149", revision: "9b7574859120add5a1a478bc7923aeb1629be4ba", images: profiles.map((profile, index) => ({
|
|
2742
|
+
profile,
|
|
2743
|
+
id: index,
|
|
2744
|
+
name: `demo-${profile}`,
|
|
2745
|
+
bytes: 1083197973,
|
|
2746
|
+
digest: "demo"
|
|
2747
|
+
})) };
|
|
2748
|
+
}
|
|
2749
|
+
async candidates(signal) {
|
|
2750
|
+
if (!this.demo) return discoverCandidates(signal);
|
|
2751
|
+
const catalog = await this.catalog(signal);
|
|
2752
|
+
return this.demo === "empty" ? [] : [{
|
|
2753
|
+
...catalog,
|
|
2754
|
+
images: [catalog.images[0]],
|
|
2755
|
+
candidate: { number: 1, date: "2026-08-28", title: "Simulated candidate" }
|
|
2756
|
+
}];
|
|
2757
|
+
}
|
|
2758
|
+
async disks(signal, staging = tmpdir4(), unmounted) {
|
|
2759
|
+
if (this.demo) {
|
|
2760
|
+
await delay(250, void 0, { signal });
|
|
2761
|
+
return this.demo === "empty" ? [] : [
|
|
2762
|
+
{ id: "demo-usb-a", name: "SanDisk Ultra USB 3.0", bytes: 32 * 1024 ** 3, identity: "demo-a" },
|
|
2763
|
+
{ id: "demo-usb-b", name: "Kingston DataTraveler", bytes: 64 * 1024 ** 3, identity: "demo-b" }
|
|
2764
|
+
];
|
|
2765
|
+
}
|
|
2766
|
+
if (process.getuid?.() === 0) throw new Error(`Run Boo Flash on ${this.requirePlatform().id === "macos" ? "macOS" : "Linux"} as your normal user, not with sudo.`);
|
|
2767
|
+
return this.requirePlatform().disks(signal, moduleDirectory, staging, unmounted);
|
|
2768
|
+
}
|
|
2769
|
+
async flash(catalog, image, disk, signal, update, authorize2) {
|
|
2770
|
+
if (this.demo) {
|
|
2771
|
+
if (!disk.id.startsWith("demo-") || image.digest !== "demo") throw new Error("Invalid demo selection.");
|
|
2772
|
+
for (const stage of ["Downloading", "Verifying", "Unmounting", "Writing", "Flushing", "Ejecting"]) {
|
|
2773
|
+
const total = stage === "Downloading" ? image.bytes : stage === "Writing" ? 8 * 1024 ** 3 : void 0;
|
|
2774
|
+
for (let i = 0; i <= (total ? 30 : 5); i++) {
|
|
2775
|
+
signal.throwIfAborted();
|
|
2776
|
+
update({
|
|
2777
|
+
stage,
|
|
2778
|
+
detail: "Simulated activity. No disk commands are run.",
|
|
2779
|
+
total,
|
|
2780
|
+
bytes: total ? Math.round(total * i / 30) : void 0,
|
|
2781
|
+
speed: total ? total / 3 : void 0
|
|
2782
|
+
});
|
|
2783
|
+
await delay(100, void 0, { signal });
|
|
2784
|
+
if (this.demo === "error" && stage === "Writing" && i === 13) throw new Error("Demo write failure. No disks were written or ejected. Refresh to choose again.");
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
return;
|
|
2788
|
+
}
|
|
2789
|
+
const platform3 = this.requirePlatform();
|
|
2790
|
+
if (process.getuid?.() === 0) throw new Error(`Run Boo Flash on ${platform3.id === "macos" ? "macOS" : "Linux"} as your normal user, not with sudo.`);
|
|
2791
|
+
if (!diskID3.test(disk.id) || !catalog.images.includes(image) || !profiles.includes(image.profile) || !positive(image.id) || !positive(image.bytes) || !sha256.test(image.digest)) throw new Error("Invalid flash selection.");
|
|
2792
|
+
if (catalog.candidate) {
|
|
2793
|
+
if (!positive(catalog.run) || !/^[a-f0-9]{40}$/.test(catalog.revision) || image.cacheKind || image.name !== `boo-${image.profile}-candidate-${catalog.revision}`) throw new Error("Invalid candidate selection.");
|
|
2794
|
+
const artifact = JSON.parse(await command(["gh", "api", "--hostname", "github.com", `repos/${repository}/actions/artifacts/${image.id}`], signal));
|
|
2795
|
+
if (artifact.id !== image.id || artifact.name !== image.name || artifact.workflow_run?.id !== catalog.run || artifact.expired !== false || !(Date.parse(artifact.expires_at) > Date.now()) || artifact.size_in_bytes !== image.bytes || artifact.digest !== `sha256:${image.digest}`) {
|
|
2796
|
+
throw new Error("Selected candidate artifact changed or expired. Refresh Candidates. No disk was written.");
|
|
2797
|
+
}
|
|
2798
|
+
} else {
|
|
2799
|
+
const build = Number(catalog.version?.slice(catalog.version.lastIndexOf("+") + 1));
|
|
2800
|
+
const name2 = `boo-${catalog.version}-x86_64-${image.profile}.raw.zst`;
|
|
2801
|
+
if (!releaseVersion.test(catalog.version ?? "") || !positive(build) || catalog.run !== build || image.id !== build || !/^[a-f0-9]{40}$/.test(catalog.revision) || image.name !== name2 || image.cacheKind !== "raw.zst" || image.url !== `${releaseOrigin}/releases/${catalog.version}/${name2}` || !positive(image.rawBytes) || !sha256.test(image.rawDigest ?? "")) throw new Error("Invalid Boo release selection.");
|
|
2802
|
+
}
|
|
2803
|
+
const staging = await mkdtemp(join5(tmpdir4(), "boo-flash-"));
|
|
2804
|
+
const payload = join5(staging, catalog.candidate ? "image.zip" : "image.raw.zst");
|
|
2805
|
+
let unretained = false;
|
|
2806
|
+
let destructive = false;
|
|
2807
|
+
try {
|
|
2808
|
+
await chmod2(staging, 448);
|
|
2809
|
+
let count = 0;
|
|
2810
|
+
if (!await reuseArchive(image, payload, signal, (count2) => update({
|
|
2811
|
+
stage: "Using cached image",
|
|
2812
|
+
detail: `Verifying cached ${catalog.candidate ? "GitHub ZIP" : `Boo ${catalog.version} image`} size and SHA-256; no download.`,
|
|
2813
|
+
bytes: count2,
|
|
2814
|
+
total: image.bytes
|
|
2815
|
+
}))) {
|
|
2816
|
+
const payloadFile = await open2(payload, "wx", 384);
|
|
2817
|
+
const payloadHash = createHash2("sha256");
|
|
2818
|
+
const started = performance.now();
|
|
2819
|
+
const detail = catalog.candidate ? "Selected candidate artifact only; authenticated with GitHub." : `Boo ${catalog.version} from images.os.boo.`;
|
|
2820
|
+
update({ stage: "Downloading", detail, bytes: 0, total: image.bytes });
|
|
2821
|
+
try {
|
|
2822
|
+
const argv = catalog.candidate ? ["gh", "api", "--hostname", "github.com", `repos/${repository}/actions/artifacts/${image.id}/zip`] : ["curl", "--fail", "--silent", "--show-error", "--proto", "=https", image.url];
|
|
2823
|
+
await command(argv, signal, {
|
|
2824
|
+
limit: image.bytes,
|
|
2825
|
+
consume: async (chunk) => {
|
|
2826
|
+
payloadHash.update(chunk);
|
|
2827
|
+
await payloadFile.writeFile(chunk);
|
|
2828
|
+
count += chunk.length;
|
|
2829
|
+
update({
|
|
2830
|
+
stage: "Downloading",
|
|
2831
|
+
detail,
|
|
2832
|
+
bytes: count,
|
|
2833
|
+
total: image.bytes,
|
|
2834
|
+
speed: count / Math.max(1e-3, (performance.now() - started) / 1e3)
|
|
2835
|
+
});
|
|
2836
|
+
}
|
|
2837
|
+
});
|
|
2838
|
+
} finally {
|
|
2839
|
+
await payloadFile.close();
|
|
2840
|
+
}
|
|
2841
|
+
if (count !== image.bytes || payloadHash.digest("hex") !== image.digest) {
|
|
2842
|
+
throw new Error(`${catalog.candidate ? "GitHub ZIP" : "Release image"} size or SHA-256 mismatch. No disk was written.`);
|
|
2843
|
+
}
|
|
2844
|
+
unretained = true;
|
|
2845
|
+
await publishArchive(image, payload, signal);
|
|
2846
|
+
unretained = false;
|
|
2847
|
+
}
|
|
2848
|
+
signal.throwIfAborted();
|
|
2849
|
+
let compressed = payload;
|
|
2850
|
+
let rawBytes = image.rawBytes;
|
|
2851
|
+
let rawDigest = image.rawDigest;
|
|
2852
|
+
if (catalog.candidate) {
|
|
2853
|
+
update({ stage: "Verifying", detail: "Checking GitHub ZIP digest, build identity and image checksums." });
|
|
2854
|
+
const names = (await command(["unzip", "-Z1", payload], signal)).trim().split("\n");
|
|
2855
|
+
for (const name2 of ["build-info.json", "SHA256SUMS", "SHA256SUMS.raw"]) {
|
|
2856
|
+
if (names.filter((n) => n === name2).length !== 1) throw new Error(`Archive is missing a unique ${name2}.`);
|
|
2857
|
+
}
|
|
2858
|
+
const extractText = (name2) => command(["unzip", "-p", payload, name2], signal, { limit: 1024 ** 2 });
|
|
2859
|
+
const metadata = JSON.parse(await extractText("build-info.json"));
|
|
2860
|
+
const basename2 = `boo-${image.profile}-x86_64.raw.zst`;
|
|
2861
|
+
if (metadata.semantic_version !== void 0 && (!semanticVersion.test(metadata.semantic_version) || metadata.build_number !== null || metadata.version !== metadata.semantic_version)) {
|
|
2862
|
+
throw new Error("Build metadata has an invalid candidate image version.");
|
|
2863
|
+
}
|
|
2864
|
+
if (metadata.image !== basename2 || names.filter((name2) => name2 === basename2).length !== 1 || metadata.schema !== 1 || !repositoryIdentities.has(metadata.repository) || metadata.revision !== catalog.revision || metadata.variant !== image.profile || metadata.source_state !== "clean" || !positive(metadata.compressed_bytes) || metadata.compressed_bytes > image.bytes || !positive(metadata.raw_apparent_bytes) || !sha256.test(metadata.compressed_sha256) || !sha256.test(metadata.raw_sha256)) {
|
|
2865
|
+
throw new Error("Build metadata does not match the selected candidate.");
|
|
2866
|
+
}
|
|
2867
|
+
if (metadata.candidate?.resolved_sha !== catalog.revision || metadata.candidate?.profile !== image.profile || metadata.candidate?.qualification !== "build-only" || typeof metadata.candidate?.source_ref !== "string" || !metadata.candidate.source_ref.trim()) {
|
|
2868
|
+
throw new Error("Candidate source metadata does not match the selected artifact.");
|
|
2869
|
+
}
|
|
2870
|
+
if ((await extractText("SHA256SUMS")).trim() !== `${metadata.compressed_sha256} ${basename2}` || (await extractText("SHA256SUMS.raw")).trim() !== `${metadata.raw_sha256} ${basename2.slice(0, -4)}`) {
|
|
2871
|
+
throw new Error("Image checksum manifests disagree with build metadata.");
|
|
2872
|
+
}
|
|
2873
|
+
compressed = join5(staging, basename2);
|
|
2874
|
+
const file = await open2(compressed, "wx", 384);
|
|
2875
|
+
const hash = createHash2("sha256");
|
|
2876
|
+
count = 0;
|
|
2877
|
+
try {
|
|
2878
|
+
await command(["unzip", "-p", payload, basename2], signal, {
|
|
2879
|
+
limit: metadata.compressed_bytes,
|
|
2880
|
+
consume: async (chunk) => {
|
|
2881
|
+
hash.update(chunk);
|
|
2882
|
+
await file.writeFile(chunk);
|
|
2883
|
+
count += chunk.length;
|
|
2884
|
+
update({ stage: "Verifying", detail: "Extracting only the selected image; checking compressed SHA-256.", bytes: count, total: metadata.compressed_bytes });
|
|
2885
|
+
}
|
|
2886
|
+
});
|
|
2887
|
+
} finally {
|
|
2888
|
+
await file.close();
|
|
2889
|
+
}
|
|
2890
|
+
if (count !== metadata.compressed_bytes || hash.digest("hex") !== metadata.compressed_sha256) throw new Error("Compressed image size or SHA-256 mismatch.");
|
|
2891
|
+
rawBytes = metadata.raw_apparent_bytes;
|
|
2892
|
+
rawDigest = metadata.raw_sha256;
|
|
2893
|
+
} else {
|
|
2894
|
+
update({ stage: "Verifying", detail: `Boo ${catalog.version} release metadata and compressed SHA-256 verified.` });
|
|
2895
|
+
}
|
|
2896
|
+
if (!positive(rawBytes) || !sha256.test(rawDigest ?? "")) throw new Error("Image raw-stream metadata is invalid.");
|
|
2897
|
+
if (rawBytes > disk.bytes) throw new Error(`Image needs ${bytes(rawBytes)}; the selected USB is too small.`);
|
|
2898
|
+
const revalidate = async (unmounted = false) => {
|
|
2899
|
+
const current = (await this.disks(signal, staging, unmounted ? disk.id : void 0)).find((d) => d.id === disk.id);
|
|
2900
|
+
if (!current || current.identity !== disk.identity || current.bytes < rawBytes) {
|
|
2901
|
+
throw new Error("USB identity, capacity or eligibility changed. Go back and select it again.");
|
|
2902
|
+
}
|
|
2903
|
+
};
|
|
2904
|
+
update({ stage: "Authorizing", detail: platform3.authorizationDetail });
|
|
2905
|
+
await authorize2();
|
|
2906
|
+
await revalidate();
|
|
2907
|
+
update({ stage: "Unmounting", detail: "Unmounting every volume on the USB, without force." });
|
|
2908
|
+
await platform3.unmount(disk, signal);
|
|
2909
|
+
await revalidate(true);
|
|
2910
|
+
signal.throwIfAborted();
|
|
2911
|
+
update({ stage: "Writing", detail: "Opening the raw USB device.", bytes: 0, total: rawBytes });
|
|
2912
|
+
const writer = platform3.writer(disk);
|
|
2913
|
+
destructive = true;
|
|
2914
|
+
const stopWriter = writer.stop;
|
|
2915
|
+
signal.addEventListener("abort", stopWriter, { once: true });
|
|
2916
|
+
const rawHash = createHash2("sha256");
|
|
2917
|
+
let written = 0;
|
|
2918
|
+
const writeStarted = performance.now();
|
|
2919
|
+
try {
|
|
2920
|
+
await command(["zstd", "-dc", "--", compressed], signal, {
|
|
2921
|
+
limit: rawBytes,
|
|
2922
|
+
consume: async (chunk) => {
|
|
2923
|
+
await new Promise((resolve, reject) => writer.stdin.write(chunk, (error) => error ? reject(error) : resolve()));
|
|
2924
|
+
rawHash.update(chunk);
|
|
2925
|
+
written += chunk.length;
|
|
2926
|
+
update({
|
|
2927
|
+
stage: "Writing",
|
|
2928
|
+
detail: "Raw bytes delivered to dd; device flush follows.",
|
|
2929
|
+
bytes: written,
|
|
2930
|
+
total: rawBytes,
|
|
2931
|
+
speed: written / Math.max(1e-3, (performance.now() - writeStarted) / 1e3)
|
|
2932
|
+
});
|
|
2933
|
+
}
|
|
2934
|
+
});
|
|
2935
|
+
writer.stdin.end();
|
|
2936
|
+
update({ stage: "Flushing", detail: "Waiting for dd to close the raw device." });
|
|
2937
|
+
if (await writer.done !== 0) throw new Error(`USB write failed. ${cleanText(writer.error())}`);
|
|
2938
|
+
signal.throwIfAborted();
|
|
2939
|
+
if (written !== rawBytes || rawHash.digest("hex") !== rawDigest) throw new Error("Raw stream length or SHA-256 mismatch. The USB is incomplete.");
|
|
2940
|
+
} finally {
|
|
2941
|
+
signal.removeEventListener("abort", stopWriter);
|
|
2942
|
+
writer.stdin.destroy();
|
|
2943
|
+
await writer.done;
|
|
2944
|
+
}
|
|
2945
|
+
update({ stage: "Flushing", detail: "Raw stream verified. Synchronizing pending writes (not a device read-back)." });
|
|
2946
|
+
await platform3.flush(disk, signal);
|
|
2947
|
+
await prepareSafeRemoval(platform3, disk, signal, authorize2, revalidate, update);
|
|
2948
|
+
update({ stage: "Ejecting", detail: platform3.ejectDetail });
|
|
2949
|
+
await platform3.eject(disk, signal);
|
|
2950
|
+
signal.throwIfAborted();
|
|
2951
|
+
} catch (error) {
|
|
2952
|
+
const retained = unretained ? ` Verified download retained for manual recovery (not cached): ${payload.replace(/[\x00-\x1f\x7f-\x9f]/g, " ")}` : "";
|
|
2953
|
+
if (signal.aborted) throw new Error((destructive ? `Cancelled. The writer has stopped; ${platform3.incompleteDetail}` : "Cancelled. No image was written; no safe-removal claim is made.") + retained);
|
|
2954
|
+
throw new Error(`${cleanText(error instanceof Error ? error.message : error)}${destructive ? ` ${platform3.incompleteDetail}` : ""}${retained}`);
|
|
2955
|
+
} finally {
|
|
2956
|
+
if (!unretained) await rm(staging, { recursive: true, force: true });
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
async authorize(signal) {
|
|
2960
|
+
if (this.demo) return;
|
|
2961
|
+
if (process.getuid?.() === 0) throw new Error("Run Boo Flash as your normal user, not with sudo.");
|
|
2962
|
+
await this.requirePlatform().authorize(signal);
|
|
2963
|
+
}
|
|
2964
|
+
};
|
|
2965
|
+
|
|
2966
|
+
// src/App.tsx
|
|
2967
|
+
function App(props) {
|
|
2968
|
+
const renderer = useRenderer();
|
|
2969
|
+
const dimensions = useTerminalDimensions();
|
|
2970
|
+
const [screen, setScreen] = createSignal4("profile");
|
|
2971
|
+
const [catalog, setCatalog] = createSignal4();
|
|
2972
|
+
const [candidates, setCandidates] = createSignal4([]);
|
|
2973
|
+
const [image, setImage] = createSignal4();
|
|
2974
|
+
const [disks3, setDisks] = createSignal4([]);
|
|
2975
|
+
const [disk, setDisk] = createSignal4();
|
|
2976
|
+
const [cursor, setCursor] = createSignal4(0);
|
|
2977
|
+
const [loading, setLoading] = createSignal4(true);
|
|
2978
|
+
const [stopping, setStopping] = createSignal4(false);
|
|
2979
|
+
const [error, setError] = createSignal4("");
|
|
2980
|
+
const [notice, setNotice] = createSignal4("");
|
|
2981
|
+
const [dependencies3, setDependencies] = createSignal4({
|
|
2982
|
+
missing: [],
|
|
2983
|
+
installer: null,
|
|
2984
|
+
guidance: ""
|
|
2985
|
+
});
|
|
2986
|
+
const [setupCandidates, setSetupCandidates] = createSignal4(false);
|
|
2987
|
+
const [progress, setProgress] = createSignal4({
|
|
2988
|
+
stage: "Preparing",
|
|
2989
|
+
detail: ""
|
|
2990
|
+
});
|
|
2991
|
+
const [frame, setFrame] = createSignal4(0);
|
|
2992
|
+
let controller = new AbortController();
|
|
2993
|
+
let pending = Promise.resolve();
|
|
2994
|
+
let closing = false;
|
|
2995
|
+
const spin = {
|
|
2996
|
+
value: 0
|
|
2997
|
+
};
|
|
2998
|
+
const timeline = useTimeline({
|
|
2999
|
+
duration: 800,
|
|
3000
|
+
loop: true
|
|
3001
|
+
});
|
|
3002
|
+
timeline.add(spin, {
|
|
3003
|
+
value: 4,
|
|
3004
|
+
duration: 800,
|
|
3005
|
+
onUpdate: () => setFrame(Math.floor(spin.value) % 4)
|
|
3006
|
+
});
|
|
3007
|
+
const spinner = () => ["|", "/", "-", "\\"][frame()];
|
|
3008
|
+
const busy = () => loading() || screen() === "busy";
|
|
3009
|
+
const compact = () => dimensions().height < 24;
|
|
3010
|
+
const tight = () => dimensions().height < 20;
|
|
3011
|
+
const visible = () => dimensions().width >= 40 && dimensions().height >= 16;
|
|
3012
|
+
const options = () => screen() === "profile" ? 5 : screen() === "cleared" ? 1 : screen() === "candidates" ? Math.max(1, candidates().length) : screen() === "disks" ? disks3().length : 2;
|
|
3013
|
+
const percent = () => progress().total ? Math.min(100, Math.floor((progress().bytes ?? 0) / progress().total * 100)) : void 0;
|
|
3014
|
+
const path = (d) => props.backend.demo ? d.id : `/dev/${d.id}`;
|
|
3015
|
+
const operation = (work) => {
|
|
3016
|
+
controller = new AbortController();
|
|
3017
|
+
setStopping(false);
|
|
3018
|
+
setLoading(true);
|
|
3019
|
+
pending = (async () => {
|
|
3020
|
+
try {
|
|
3021
|
+
await work(controller.signal);
|
|
3022
|
+
} catch (e) {
|
|
3023
|
+
if (!closing) {
|
|
3024
|
+
const setup = screen() === "installing";
|
|
3025
|
+
setError(controller.signal.aborted && setup ? "Setup cancelled. Refresh to check what is still missing." : controller.signal.aborted && props.backend.demo ? "Demo cancelled. No disks were written or ejected." : e instanceof Error ? e.message : "Operation failed. Refresh and try again.");
|
|
3026
|
+
if (setup) setDependencies(props.backend.dependencies(setupCandidates()));
|
|
3027
|
+
setScreen(setup ? "setup" : "error");
|
|
3028
|
+
setCursor(0);
|
|
3029
|
+
}
|
|
3030
|
+
} finally {
|
|
3031
|
+
setLoading(false);
|
|
3032
|
+
setStopping(false);
|
|
3033
|
+
}
|
|
3034
|
+
})();
|
|
3035
|
+
};
|
|
3036
|
+
const loadCatalog = async (signal) => {
|
|
3037
|
+
setSetupCandidates(false);
|
|
3038
|
+
const state = props.backend.dependencies();
|
|
3039
|
+
setDependencies(state);
|
|
3040
|
+
if (state.missing.length) {
|
|
3041
|
+
setScreen("setup");
|
|
3042
|
+
return;
|
|
3043
|
+
}
|
|
3044
|
+
setScreen("profile");
|
|
3045
|
+
setCatalog(await props.backend.catalog(signal));
|
|
3046
|
+
};
|
|
3047
|
+
const refresh = () => {
|
|
3048
|
+
setError("");
|
|
3049
|
+
setCatalog(void 0);
|
|
3050
|
+
setDisk(void 0);
|
|
3051
|
+
setScreen("profile");
|
|
3052
|
+
setCursor(0);
|
|
3053
|
+
operation(loadCatalog);
|
|
3054
|
+
};
|
|
3055
|
+
const loadCandidates = () => {
|
|
3056
|
+
setSetupCandidates(true);
|
|
3057
|
+
setCandidates([]);
|
|
3058
|
+
setCursor(0);
|
|
3059
|
+
setScreen("candidates");
|
|
3060
|
+
operation(async (signal) => {
|
|
3061
|
+
const state = props.backend.dependencies(true);
|
|
3062
|
+
if (state.missing.length) {
|
|
3063
|
+
setDependencies(state);
|
|
3064
|
+
setScreen("setup");
|
|
3065
|
+
return;
|
|
3066
|
+
}
|
|
3067
|
+
setCandidates(await props.backend.candidates(signal));
|
|
3068
|
+
});
|
|
3069
|
+
};
|
|
3070
|
+
const chooseUSB = () => {
|
|
3071
|
+
setDisk(void 0);
|
|
3072
|
+
setCursor(-1);
|
|
3073
|
+
setScreen("disks");
|
|
3074
|
+
operation(async (signal) => {
|
|
3075
|
+
const state = props.backend.dependencies();
|
|
3076
|
+
if (state.missing.length) {
|
|
3077
|
+
setDependencies(state);
|
|
3078
|
+
setCatalog(void 0);
|
|
3079
|
+
setScreen("setup");
|
|
3080
|
+
setCursor(0);
|
|
3081
|
+
return;
|
|
3082
|
+
}
|
|
3083
|
+
setDisks(await props.backend.disks(signal));
|
|
3084
|
+
});
|
|
3085
|
+
};
|
|
3086
|
+
const stop = async () => {
|
|
3087
|
+
closing = true;
|
|
3088
|
+
if (busy()) setStopping(true);
|
|
3089
|
+
controller.abort();
|
|
3090
|
+
await pending;
|
|
3091
|
+
};
|
|
3092
|
+
props.registerStop(stop);
|
|
3093
|
+
onMount2(refresh);
|
|
3094
|
+
const activate = () => {
|
|
3095
|
+
if (busy() || !visible()) return;
|
|
3096
|
+
if (screen() === "setup") {
|
|
3097
|
+
if (cursor() === 0) {
|
|
3098
|
+
setScreen("profile");
|
|
3099
|
+
return;
|
|
3100
|
+
}
|
|
3101
|
+
if (!dependencies3().installer) {
|
|
3102
|
+
refresh();
|
|
3103
|
+
return;
|
|
3104
|
+
}
|
|
3105
|
+
const approved = [...dependencies3().missing];
|
|
3106
|
+
setError("");
|
|
3107
|
+
setScreen("installing");
|
|
3108
|
+
operation(async (signal) => {
|
|
3109
|
+
const result = await props.backend.installDependencies(approved, signal, async (work) => {
|
|
3110
|
+
renderer.suspend();
|
|
3111
|
+
try {
|
|
3112
|
+
const result2 = await work();
|
|
3113
|
+
if (result2 === "cancelled") props.quit();
|
|
3114
|
+
return result2;
|
|
3115
|
+
} finally {
|
|
3116
|
+
if (!closing) renderer.resume();
|
|
3117
|
+
}
|
|
3118
|
+
});
|
|
3119
|
+
if (result === "cancelled") return;
|
|
3120
|
+
signal.throwIfAborted();
|
|
3121
|
+
setCursor(0);
|
|
3122
|
+
if (setupCandidates()) {
|
|
3123
|
+
setCandidates(await props.backend.candidates(signal));
|
|
3124
|
+
setScreen("candidates");
|
|
3125
|
+
} else await loadCatalog(signal);
|
|
3126
|
+
});
|
|
3127
|
+
} else if (screen() === "profile" && cursor() === 3) {
|
|
3128
|
+
loadCandidates();
|
|
3129
|
+
} else if (screen() === "profile" && cursor() === 4) {
|
|
3130
|
+
setScreen("clear");
|
|
3131
|
+
setCursor(0);
|
|
3132
|
+
} else if (screen() === "clear") {
|
|
3133
|
+
if (cursor() === 0) {
|
|
3134
|
+
setScreen("profile");
|
|
3135
|
+
setCursor(4);
|
|
3136
|
+
return;
|
|
3137
|
+
}
|
|
3138
|
+
operation(async (signal) => {
|
|
3139
|
+
setNotice(await props.backend.clearCache(signal));
|
|
3140
|
+
setScreen("cleared");
|
|
3141
|
+
setCursor(0);
|
|
3142
|
+
});
|
|
3143
|
+
} else if (screen() === "cleared") {
|
|
3144
|
+
setScreen("profile");
|
|
3145
|
+
setCursor(4);
|
|
3146
|
+
} else if (screen() === "candidates") {
|
|
3147
|
+
const candidate = candidates()[cursor()];
|
|
3148
|
+
if (!candidate) {
|
|
3149
|
+
refresh();
|
|
3150
|
+
return;
|
|
3151
|
+
}
|
|
3152
|
+
setCatalog(candidate);
|
|
3153
|
+
setImage(candidate.images[0]);
|
|
3154
|
+
chooseUSB();
|
|
3155
|
+
} else if (screen() === "profile" && !catalog()) {
|
|
3156
|
+
refresh();
|
|
3157
|
+
} else if (screen() === "profile" && catalog()) {
|
|
3158
|
+
setImage(catalog().images[cursor()]);
|
|
3159
|
+
chooseUSB();
|
|
3160
|
+
} else if (screen() === "disks" && disks3()[cursor()]) {
|
|
3161
|
+
setDisk(disks3()[cursor()]);
|
|
3162
|
+
setCursor(0);
|
|
3163
|
+
setScreen("confirm");
|
|
3164
|
+
} else if (screen() === "confirm") {
|
|
3165
|
+
if (cursor() === 0) {
|
|
3166
|
+
chooseUSB();
|
|
3167
|
+
return;
|
|
3168
|
+
}
|
|
3169
|
+
setScreen("busy");
|
|
3170
|
+
setProgress({
|
|
3171
|
+
stage: "Preparing",
|
|
3172
|
+
detail: "Checking the selected image and USB."
|
|
3173
|
+
});
|
|
3174
|
+
operation(async (signal) => {
|
|
3175
|
+
await props.backend.flash(catalog(), image(), disk(), signal, setProgress, async () => {
|
|
3176
|
+
renderer.suspend();
|
|
3177
|
+
try {
|
|
3178
|
+
await props.backend.authorize(signal);
|
|
3179
|
+
} finally {
|
|
3180
|
+
renderer.resume();
|
|
3181
|
+
}
|
|
3182
|
+
});
|
|
3183
|
+
signal.throwIfAborted();
|
|
3184
|
+
setScreen("success");
|
|
3185
|
+
setCursor(0);
|
|
3186
|
+
});
|
|
3187
|
+
} else if (screen() === "error" || screen() === "success") {
|
|
3188
|
+
if (cursor() === 0) refresh();
|
|
3189
|
+
else props.quit();
|
|
3190
|
+
}
|
|
3191
|
+
};
|
|
3192
|
+
useKeyboard((key) => {
|
|
3193
|
+
if (key.eventType === "release") return;
|
|
3194
|
+
if (key.name === "q" || key.ctrl && key.name === "c") {
|
|
3195
|
+
props.quit();
|
|
3196
|
+
return;
|
|
3197
|
+
}
|
|
3198
|
+
if (key.name === "escape") {
|
|
3199
|
+
if (busy()) {
|
|
3200
|
+
setStopping(true);
|
|
3201
|
+
controller.abort();
|
|
3202
|
+
return;
|
|
3203
|
+
}
|
|
3204
|
+
if (screen() === "confirm") chooseUSB();
|
|
3205
|
+
else if (screen() === "clear" || screen() === "cleared") {
|
|
3206
|
+
setScreen("profile");
|
|
3207
|
+
setCursor(4);
|
|
3208
|
+
} else if (screen() === "setup") {
|
|
3209
|
+
setScreen("profile");
|
|
3210
|
+
setCursor(0);
|
|
3211
|
+
} else refresh();
|
|
3212
|
+
return;
|
|
3213
|
+
}
|
|
3214
|
+
if (busy() || !visible()) return;
|
|
3215
|
+
if (key.name === "r") {
|
|
3216
|
+
screen() === "disks" ? chooseUSB() : screen() === "candidates" ? loadCandidates() : refresh();
|
|
3217
|
+
return;
|
|
3218
|
+
}
|
|
3219
|
+
if (["down", "right", "tab"].includes(key.name)) setCursor((n) => Math.min(options() - 1, n + 1));
|
|
3220
|
+
if (["up", "left"].includes(key.name)) setCursor((n) => Math.max(0, n - 1));
|
|
3221
|
+
if (key.name === "return" && !key.repeated) activate();
|
|
3222
|
+
});
|
|
3223
|
+
const row = (label, detail, index, danger = false) => (() => {
|
|
3224
|
+
var _el$ = createElement("box"), _el$2 = createElement("text");
|
|
3225
|
+
insertNode(_el$, _el$2);
|
|
3226
|
+
setProp(_el$, "paddingX", 1);
|
|
3227
|
+
setProp(_el$, "flexShrink", 0);
|
|
3228
|
+
setProp(_el$2, "wrapMode", "none");
|
|
3229
|
+
insert(_el$2, () => cursor() === index ? "> " : " ", null);
|
|
3230
|
+
insert(_el$2, label, null);
|
|
3231
|
+
insert(_el$, createComponent2(Show, {
|
|
3232
|
+
get when() {
|
|
3233
|
+
return detail && !tight();
|
|
3234
|
+
},
|
|
3235
|
+
get children() {
|
|
3236
|
+
var _el$3 = createElement("text"), _el$4 = createTextNode(` `);
|
|
3237
|
+
insertNode(_el$3, _el$4);
|
|
3238
|
+
setProp(_el$3, "wrapMode", "none");
|
|
3239
|
+
insert(_el$3, detail, null);
|
|
3240
|
+
effect((_$p) => setProp(_el$3, "fg", palette.muted, _$p));
|
|
3241
|
+
return _el$3;
|
|
3242
|
+
}
|
|
3243
|
+
}), null);
|
|
3244
|
+
effect((_p$) => {
|
|
3245
|
+
var _v$ = detail && !tight() ? compact() ? 2 : 3 : 1, _v$2 = cursor() === index ? palette.selected : palette.panel, _v$3 = danger ? palette.warning : cursor() === index ? palette.accent : palette.foreground, _v$4 = TextAttributes2.BOLD;
|
|
3246
|
+
_v$ !== _p$.e && (_p$.e = setProp(_el$, "height", _v$, _p$.e));
|
|
3247
|
+
_v$2 !== _p$.t && (_p$.t = setProp(_el$, "backgroundColor", _v$2, _p$.t));
|
|
3248
|
+
_v$3 !== _p$.a && (_p$.a = setProp(_el$2, "fg", _v$3, _p$.a));
|
|
3249
|
+
_v$4 !== _p$.o && (_p$.o = setProp(_el$2, "attributes", _v$4, _p$.o));
|
|
3250
|
+
return _p$;
|
|
3251
|
+
}, {
|
|
3252
|
+
e: void 0,
|
|
3253
|
+
t: void 0,
|
|
3254
|
+
a: void 0,
|
|
3255
|
+
o: void 0
|
|
3256
|
+
});
|
|
3257
|
+
return _el$;
|
|
3258
|
+
})();
|
|
3259
|
+
return (() => {
|
|
3260
|
+
var _el$5 = createElement("box"), _el$6 = createElement("box"), _el$7 = createElement("box"), _el$8 = createElement("text"), _el$0 = createElement("text"), _el$1 = createElement("box"), _el$10 = createElement("box"), _el$88 = createElement("text");
|
|
3261
|
+
insertNode(_el$5, _el$6);
|
|
3262
|
+
setProp(_el$5, "width", "100%");
|
|
3263
|
+
setProp(_el$5, "height", "100%");
|
|
3264
|
+
setProp(_el$5, "justifyContent", "center");
|
|
3265
|
+
setProp(_el$5, "alignItems", "center");
|
|
3266
|
+
insertNode(_el$6, _el$7);
|
|
3267
|
+
insertNode(_el$6, _el$1);
|
|
3268
|
+
insertNode(_el$6, _el$10);
|
|
3269
|
+
insertNode(_el$6, _el$88);
|
|
3270
|
+
setProp(_el$6, "border", true);
|
|
3271
|
+
setProp(_el$6, "borderStyle", "single");
|
|
3272
|
+
setProp(_el$6, "flexDirection", "column");
|
|
3273
|
+
setProp(_el$6, "paddingX", 1);
|
|
3274
|
+
insertNode(_el$7, _el$8);
|
|
3275
|
+
insertNode(_el$7, _el$0);
|
|
3276
|
+
setProp(_el$7, "height", 1);
|
|
3277
|
+
setProp(_el$7, "flexDirection", "row");
|
|
3278
|
+
setProp(_el$7, "justifyContent", "space-between");
|
|
3279
|
+
setProp(_el$7, "flexShrink", 0);
|
|
3280
|
+
insertNode(_el$8, createTextNode(`BOO FLASH`));
|
|
3281
|
+
insert(_el$0, () => props.backend.platformLabel);
|
|
3282
|
+
setProp(_el$1, "height", 1);
|
|
3283
|
+
setProp(_el$1, "flexShrink", 0);
|
|
3284
|
+
setProp(_el$10, "flexGrow", 1);
|
|
3285
|
+
setProp(_el$10, "minHeight", 0);
|
|
3286
|
+
setProp(_el$10, "overflow", "hidden");
|
|
3287
|
+
insert(_el$10, createComponent2(Show, {
|
|
3288
|
+
get when() {
|
|
3289
|
+
return visible();
|
|
3290
|
+
},
|
|
3291
|
+
get fallback() {
|
|
3292
|
+
return (() => {
|
|
3293
|
+
var _el$89 = createElement("text");
|
|
3294
|
+
insertNode(_el$89, createTextNode(`Enlarge the terminal to show the full choices and erase warning. Q quits.`));
|
|
3295
|
+
effect((_$p) => setProp(_el$89, "fg", palette.warning, _$p));
|
|
3296
|
+
return _el$89;
|
|
3297
|
+
})();
|
|
3298
|
+
},
|
|
3299
|
+
get children() {
|
|
3300
|
+
return [createComponent2(Show, {
|
|
3301
|
+
get when() {
|
|
3302
|
+
return screen() === "profile";
|
|
3303
|
+
},
|
|
3304
|
+
get children() {
|
|
3305
|
+
return [(() => {
|
|
3306
|
+
var _el$11 = createElement("text");
|
|
3307
|
+
insertNode(_el$11, createTextNode(`Choose the target computer's GPU`));
|
|
3308
|
+
effect((_p$) => {
|
|
3309
|
+
var _v$5 = palette.foreground, _v$6 = TextAttributes2.BOLD;
|
|
3310
|
+
_v$5 !== _p$.e && (_p$.e = setProp(_el$11, "fg", _v$5, _p$.e));
|
|
3311
|
+
_v$6 !== _p$.t && (_p$.t = setProp(_el$11, "attributes", _v$6, _p$.t));
|
|
3312
|
+
return _p$;
|
|
3313
|
+
}, {
|
|
3314
|
+
e: void 0,
|
|
3315
|
+
t: void 0
|
|
3316
|
+
});
|
|
3317
|
+
return _el$11;
|
|
3318
|
+
})(), (() => {
|
|
3319
|
+
var _el$13 = createElement("text");
|
|
3320
|
+
insert(_el$13, (() => {
|
|
3321
|
+
var _c$ = memo2(() => !!loading());
|
|
3322
|
+
return () => _c$() ? `${spinner()} Finding latest Boo release...` : memo2(() => !!catalog())() ? `Boo ${catalog().version} / ${catalog().revision.slice(0, 7)}` : "Dependencies needed. Enter to set up.";
|
|
3323
|
+
})());
|
|
3324
|
+
effect((_$p) => setProp(_el$13, "fg", palette.muted, _$p));
|
|
3325
|
+
return _el$13;
|
|
3326
|
+
})(), (() => {
|
|
3327
|
+
var _el$14 = createElement("box");
|
|
3328
|
+
setProp(_el$14, "gap", 0);
|
|
3329
|
+
insert(_el$14, createComponent2(For2, {
|
|
3330
|
+
each: profiles,
|
|
3331
|
+
children: (profile, index) => row(profile.toUpperCase(), profile === "nvidia" ? "NVIDIA graphics" : `${profile === "intel" ? "Intel" : "AMD Radeon"} graphics`, index())
|
|
3332
|
+
}), null);
|
|
3333
|
+
insert(_el$14, () => row("Candidates", "", 3), null);
|
|
3334
|
+
insert(_el$14, () => row("Clear cached images", "", 4), null);
|
|
3335
|
+
return _el$14;
|
|
3336
|
+
})(), (() => {
|
|
3337
|
+
var _el$15 = createElement("text");
|
|
3338
|
+
insertNode(_el$15, createTextNode(`All images support Intel and AMD x86_64 CPUs.`));
|
|
3339
|
+
effect((_$p) => setProp(_el$15, "fg", palette.muted, _$p));
|
|
3340
|
+
return _el$15;
|
|
3341
|
+
})()];
|
|
3342
|
+
}
|
|
3343
|
+
}), createComponent2(Show, {
|
|
3344
|
+
get when() {
|
|
3345
|
+
return screen() === "clear";
|
|
3346
|
+
},
|
|
3347
|
+
get children() {
|
|
3348
|
+
return [(() => {
|
|
3349
|
+
var _el$17 = createElement("text");
|
|
3350
|
+
insertNode(_el$17, createTextNode(`Clear cached images?`));
|
|
3351
|
+
effect((_p$) => {
|
|
3352
|
+
var _v$7 = palette.warning, _v$8 = TextAttributes2.BOLD;
|
|
3353
|
+
_v$7 !== _p$.e && (_p$.e = setProp(_el$17, "fg", _v$7, _p$.e));
|
|
3354
|
+
_v$8 !== _p$.t && (_p$.t = setProp(_el$17, "attributes", _v$8, _p$.t));
|
|
3355
|
+
return _p$;
|
|
3356
|
+
}, {
|
|
3357
|
+
e: void 0,
|
|
3358
|
+
t: void 0
|
|
3359
|
+
});
|
|
3360
|
+
return _el$17;
|
|
3361
|
+
})(), (() => {
|
|
3362
|
+
var _el$19 = createElement("text");
|
|
3363
|
+
insertNode(_el$19, createTextNode(`Removes downloaded archives only, NOT USB contents. Active flashes are unaffected.`));
|
|
3364
|
+
effect((_$p) => setProp(_el$19, "fg", palette.foreground, _$p));
|
|
3365
|
+
return _el$19;
|
|
3366
|
+
})(), (() => {
|
|
3367
|
+
var _el$21 = createElement("text");
|
|
3368
|
+
insertNode(_el$21, createTextNode(`Images will download again when needed. Pending downloads may still finish and be cached.`));
|
|
3369
|
+
effect((_$p) => setProp(_el$21, "fg", palette.muted, _$p));
|
|
3370
|
+
return _el$21;
|
|
3371
|
+
})(), (() => {
|
|
3372
|
+
var _el$23 = createElement("box");
|
|
3373
|
+
insert(_el$23, () => row("Go back", "", 0), null);
|
|
3374
|
+
insert(_el$23, () => row(loading() ? "Clearing..." : "Clear cached images", "", 1, true), null);
|
|
3375
|
+
effect((_$p) => setProp(_el$23, "gap", tight() ? 0 : 1, _$p));
|
|
3376
|
+
return _el$23;
|
|
3377
|
+
})()];
|
|
3378
|
+
}
|
|
3379
|
+
}), createComponent2(Show, {
|
|
3380
|
+
get when() {
|
|
3381
|
+
return screen() === "cleared";
|
|
3382
|
+
},
|
|
3383
|
+
get children() {
|
|
3384
|
+
return [(() => {
|
|
3385
|
+
var _el$24 = createElement("text");
|
|
3386
|
+
insertNode(_el$24, createTextNode(`Cache cleared`));
|
|
3387
|
+
effect((_p$) => {
|
|
3388
|
+
var _v$9 = palette.success, _v$0 = TextAttributes2.BOLD;
|
|
3389
|
+
_v$9 !== _p$.e && (_p$.e = setProp(_el$24, "fg", _v$9, _p$.e));
|
|
3390
|
+
_v$0 !== _p$.t && (_p$.t = setProp(_el$24, "attributes", _v$0, _p$.t));
|
|
3391
|
+
return _p$;
|
|
3392
|
+
}, {
|
|
3393
|
+
e: void 0,
|
|
3394
|
+
t: void 0
|
|
3395
|
+
});
|
|
3396
|
+
return _el$24;
|
|
3397
|
+
})(), (() => {
|
|
3398
|
+
var _el$26 = createElement("text");
|
|
3399
|
+
insert(_el$26, notice);
|
|
3400
|
+
effect((_$p) => setProp(_el$26, "fg", palette.foreground, _$p));
|
|
3401
|
+
return _el$26;
|
|
3402
|
+
})(), memo2(() => row("Go back", "", 0))];
|
|
3403
|
+
}
|
|
3404
|
+
}), createComponent2(Show, {
|
|
3405
|
+
get when() {
|
|
3406
|
+
return screen() === "candidates";
|
|
3407
|
+
},
|
|
3408
|
+
get children() {
|
|
3409
|
+
return [(() => {
|
|
3410
|
+
var _el$27 = createElement("text");
|
|
3411
|
+
insertNode(_el$27, createTextNode(`Candidates`));
|
|
3412
|
+
effect((_p$) => {
|
|
3413
|
+
var _v$1 = palette.foreground, _v$10 = TextAttributes2.BOLD;
|
|
3414
|
+
_v$1 !== _p$.e && (_p$.e = setProp(_el$27, "fg", _v$1, _p$.e));
|
|
3415
|
+
_v$10 !== _p$.t && (_p$.t = setProp(_el$27, "attributes", _v$10, _p$.t));
|
|
3416
|
+
return _p$;
|
|
3417
|
+
}, {
|
|
3418
|
+
e: void 0,
|
|
3419
|
+
t: void 0
|
|
3420
|
+
});
|
|
3421
|
+
return _el$27;
|
|
3422
|
+
})(), (() => {
|
|
3423
|
+
var _el$29 = createElement("text");
|
|
3424
|
+
insertNode(_el$29, createTextNode(`Build-only; not boot/install/hardware qualified.`));
|
|
3425
|
+
effect((_$p) => setProp(_el$29, "fg", palette.warning, _$p));
|
|
3426
|
+
return _el$29;
|
|
3427
|
+
})(), createComponent2(Show, {
|
|
3428
|
+
get when() {
|
|
3429
|
+
return !loading();
|
|
3430
|
+
},
|
|
3431
|
+
get fallback() {
|
|
3432
|
+
return (() => {
|
|
3433
|
+
var _el$91 = createElement("text"), _el$92 = createTextNode(` Finding completed candidates...`);
|
|
3434
|
+
insertNode(_el$91, _el$92);
|
|
3435
|
+
insert(_el$91, spinner, _el$92);
|
|
3436
|
+
effect((_$p) => setProp(_el$91, "fg", palette.muted, _$p));
|
|
3437
|
+
return _el$91;
|
|
3438
|
+
})();
|
|
3439
|
+
},
|
|
3440
|
+
get children() {
|
|
3441
|
+
return createComponent2(Show, {
|
|
3442
|
+
get when() {
|
|
3443
|
+
return candidates().length;
|
|
3444
|
+
},
|
|
3445
|
+
get fallback() {
|
|
3446
|
+
return [(() => {
|
|
3447
|
+
var _el$93 = createElement("text");
|
|
3448
|
+
insertNode(_el$93, createTextNode(`No completed candidates available`));
|
|
3449
|
+
effect((_$p) => setProp(_el$93, "fg", palette.muted, _$p));
|
|
3450
|
+
return _el$93;
|
|
3451
|
+
})(), memo2(() => row("Go back", "", 0))];
|
|
3452
|
+
},
|
|
3453
|
+
get children() {
|
|
3454
|
+
return [(() => {
|
|
3455
|
+
var _el$31 = createElement("box");
|
|
3456
|
+
setProp(_el$31, "gap", 0);
|
|
3457
|
+
insert(_el$31, createComponent2(For2, {
|
|
3458
|
+
get each() {
|
|
3459
|
+
return candidates().slice(Math.max(0, cursor() - 2), Math.max(3, cursor() + 1));
|
|
3460
|
+
},
|
|
3461
|
+
children: (c, index) => row(`${c.images[0].profile.toUpperCase()} ${c.revision.slice(0, 7)} #${c.candidate.number} ${c.candidate.date}`, c.candidate.title, index() + Math.max(0, cursor() - 2))
|
|
3462
|
+
}));
|
|
3463
|
+
return _el$31;
|
|
3464
|
+
})(), (() => {
|
|
3465
|
+
var _el$32 = createElement("text"), _el$33 = createTextNode(` of `), _el$34 = createTextNode(` candidates / Enter to choose`);
|
|
3466
|
+
insertNode(_el$32, _el$33);
|
|
3467
|
+
insertNode(_el$32, _el$34);
|
|
3468
|
+
insert(_el$32, () => cursor() + 1, _el$33);
|
|
3469
|
+
insert(_el$32, () => candidates().length, _el$34);
|
|
3470
|
+
effect((_$p) => setProp(_el$32, "fg", palette.muted, _$p));
|
|
3471
|
+
return _el$32;
|
|
3472
|
+
})()];
|
|
3473
|
+
}
|
|
3474
|
+
});
|
|
3475
|
+
}
|
|
3476
|
+
})];
|
|
3477
|
+
}
|
|
3478
|
+
}), createComponent2(Show, {
|
|
3479
|
+
get when() {
|
|
3480
|
+
return screen() === "setup";
|
|
3481
|
+
},
|
|
3482
|
+
get children() {
|
|
3483
|
+
return [(() => {
|
|
3484
|
+
var _el$35 = createElement("text");
|
|
3485
|
+
insertNode(_el$35, createTextNode(`Set up required tools`));
|
|
3486
|
+
effect((_p$) => {
|
|
3487
|
+
var _v$11 = palette.accent, _v$12 = TextAttributes2.BOLD;
|
|
3488
|
+
_v$11 !== _p$.e && (_p$.e = setProp(_el$35, "fg", _v$11, _p$.e));
|
|
3489
|
+
_v$12 !== _p$.t && (_p$.t = setProp(_el$35, "attributes", _v$12, _p$.t));
|
|
3490
|
+
return _p$;
|
|
3491
|
+
}, {
|
|
3492
|
+
e: void 0,
|
|
3493
|
+
t: void 0
|
|
3494
|
+
});
|
|
3495
|
+
return _el$35;
|
|
3496
|
+
})(), (() => {
|
|
3497
|
+
var _el$37 = createElement("text"), _el$38 = createTextNode(`Missing: `);
|
|
3498
|
+
insertNode(_el$37, _el$38);
|
|
3499
|
+
insert(_el$37, () => dependencies3().missing.join(", "), null);
|
|
3500
|
+
effect((_$p) => setProp(_el$37, "fg", palette.foreground, _$p));
|
|
3501
|
+
return _el$37;
|
|
3502
|
+
})(), createComponent2(Show, {
|
|
3503
|
+
get when() {
|
|
3504
|
+
return !error();
|
|
3505
|
+
},
|
|
3506
|
+
get fallback() {
|
|
3507
|
+
return (() => {
|
|
3508
|
+
var _el$95 = createElement("text");
|
|
3509
|
+
insert(_el$95, error);
|
|
3510
|
+
effect((_$p) => setProp(_el$95, "fg", palette.warning, _$p));
|
|
3511
|
+
return _el$95;
|
|
3512
|
+
})();
|
|
3513
|
+
},
|
|
3514
|
+
get children() {
|
|
3515
|
+
var _el$39 = createElement("text");
|
|
3516
|
+
insert(_el$39, (() => {
|
|
3517
|
+
var _c$2 = memo2(() => !!dependencies3().installer);
|
|
3518
|
+
return () => _c$2() ? `${dependencies3().installer} will install ${dependencies3().missing.join(" and ")} and required dependencies, then continue.` : dependencies3().guidance;
|
|
3519
|
+
})());
|
|
3520
|
+
effect((_$p) => setProp(_el$39, "fg", palette.muted, _$p));
|
|
3521
|
+
return _el$39;
|
|
3522
|
+
}
|
|
3523
|
+
}), createComponent2(Show, {
|
|
3524
|
+
get when() {
|
|
3525
|
+
return !tight();
|
|
3526
|
+
},
|
|
3527
|
+
get children() {
|
|
3528
|
+
var _el$40 = createElement("text");
|
|
3529
|
+
insert(_el$40, () => dependencies3().installer ? "Uses the ordinary terminal. Ctrl-C cancels and quits." : "Boo Flash will not change system packages on this platform.");
|
|
3530
|
+
effect((_$p) => setProp(_el$40, "fg", palette.muted, _$p));
|
|
3531
|
+
return _el$40;
|
|
3532
|
+
}
|
|
3533
|
+
}), createComponent2(Show, {
|
|
3534
|
+
get when() {
|
|
3535
|
+
return props.backend.demo;
|
|
3536
|
+
},
|
|
3537
|
+
get children() {
|
|
3538
|
+
var _el$41 = createElement("text");
|
|
3539
|
+
insertNode(_el$41, createTextNode(`Setup simulation; no software installed.`));
|
|
3540
|
+
effect((_$p) => setProp(_el$41, "fg", palette.warning, _$p));
|
|
3541
|
+
return _el$41;
|
|
3542
|
+
}
|
|
3543
|
+
}), (() => {
|
|
3544
|
+
var _el$43 = createElement("box");
|
|
3545
|
+
insert(_el$43, () => row("Go back", "", 0), null);
|
|
3546
|
+
insert(_el$43, () => row(dependencies3().installer ? `Install ${dependencies3().missing.join(" + ")}` : "Refresh", "", 1), null);
|
|
3547
|
+
effect((_$p) => setProp(_el$43, "gap", tight() ? 0 : 1, _$p));
|
|
3548
|
+
return _el$43;
|
|
3549
|
+
})()];
|
|
3550
|
+
}
|
|
3551
|
+
}), createComponent2(Show, {
|
|
3552
|
+
get when() {
|
|
3553
|
+
return screen() === "installing";
|
|
3554
|
+
},
|
|
3555
|
+
get children() {
|
|
3556
|
+
return [(() => {
|
|
3557
|
+
var _el$44 = createElement("text"), _el$45 = createTextNode(` `);
|
|
3558
|
+
insertNode(_el$44, _el$45);
|
|
3559
|
+
insert(_el$44, spinner, _el$45);
|
|
3560
|
+
insert(_el$44, () => stopping() ? "Waiting for setup to stop..." : "Installing required tools", null);
|
|
3561
|
+
effect((_p$) => {
|
|
3562
|
+
var _v$13 = palette.accent, _v$14 = TextAttributes2.BOLD;
|
|
3563
|
+
_v$13 !== _p$.e && (_p$.e = setProp(_el$44, "fg", _v$13, _p$.e));
|
|
3564
|
+
_v$14 !== _p$.t && (_p$.t = setProp(_el$44, "attributes", _v$14, _p$.t));
|
|
3565
|
+
return _p$;
|
|
3566
|
+
}, {
|
|
3567
|
+
e: void 0,
|
|
3568
|
+
t: void 0
|
|
3569
|
+
});
|
|
3570
|
+
return _el$44;
|
|
3571
|
+
})(), (() => {
|
|
3572
|
+
var _el$46 = createElement("text"), _el$47 = createTextNode(` via `);
|
|
3573
|
+
insertNode(_el$46, _el$47);
|
|
3574
|
+
insert(_el$46, () => dependencies3().missing.join(" + "), _el$47);
|
|
3575
|
+
insert(_el$46, () => dependencies3().installer, null);
|
|
3576
|
+
effect((_$p) => setProp(_el$46, "fg", palette.foreground, _$p));
|
|
3577
|
+
return _el$46;
|
|
3578
|
+
})(), (() => {
|
|
3579
|
+
var _el$48 = createElement("text"), _el$49 = createTextNode(` uses the ordinary terminal. Ctrl-C cancels and quits; wait for cleanup.`);
|
|
3580
|
+
insertNode(_el$48, _el$49);
|
|
3581
|
+
insert(_el$48, () => dependencies3().installer, _el$49);
|
|
3582
|
+
effect((_$p) => setProp(_el$48, "fg", palette.muted, _$p));
|
|
3583
|
+
return _el$48;
|
|
3584
|
+
})(), createComponent2(Show, {
|
|
3585
|
+
get when() {
|
|
3586
|
+
return props.backend.demo;
|
|
3587
|
+
},
|
|
3588
|
+
get children() {
|
|
3589
|
+
var _el$50 = createElement("text");
|
|
3590
|
+
insertNode(_el$50, createTextNode(`Setup simulation; no software installed.`));
|
|
3591
|
+
effect((_$p) => setProp(_el$50, "fg", palette.warning, _$p));
|
|
3592
|
+
return _el$50;
|
|
3593
|
+
}
|
|
3594
|
+
})];
|
|
3595
|
+
}
|
|
3596
|
+
}), createComponent2(Show, {
|
|
3597
|
+
get when() {
|
|
3598
|
+
return screen() === "disks";
|
|
3599
|
+
},
|
|
3600
|
+
get children() {
|
|
3601
|
+
return [(() => {
|
|
3602
|
+
var _el$52 = createElement("text");
|
|
3603
|
+
insertNode(_el$52, createTextNode(`Choose a USB drive`));
|
|
3604
|
+
effect((_p$) => {
|
|
3605
|
+
var _v$15 = palette.foreground, _v$16 = TextAttributes2.BOLD;
|
|
3606
|
+
_v$15 !== _p$.e && (_p$.e = setProp(_el$52, "fg", _v$15, _p$.e));
|
|
3607
|
+
_v$16 !== _p$.t && (_p$.t = setProp(_el$52, "attributes", _v$16, _p$.t));
|
|
3608
|
+
return _p$;
|
|
3609
|
+
}, {
|
|
3610
|
+
e: void 0,
|
|
3611
|
+
t: void 0
|
|
3612
|
+
});
|
|
3613
|
+
return _el$52;
|
|
3614
|
+
})(), (() => {
|
|
3615
|
+
var _el$54 = createElement("text");
|
|
3616
|
+
insert(_el$54, (() => {
|
|
3617
|
+
var _c$3 = memo2(() => !!loading());
|
|
3618
|
+
return () => _c$3() ? `${spinner()} Checking disks and protected storage...` : "System, home and app storage are excluded.";
|
|
3619
|
+
})());
|
|
3620
|
+
effect((_$p) => setProp(_el$54, "fg", palette.muted, _$p));
|
|
3621
|
+
return _el$54;
|
|
3622
|
+
})(), createComponent2(Show, {
|
|
3623
|
+
get when() {
|
|
3624
|
+
return !loading();
|
|
3625
|
+
},
|
|
3626
|
+
get children() {
|
|
3627
|
+
return createComponent2(Show, {
|
|
3628
|
+
get when() {
|
|
3629
|
+
return disks3().length > 0;
|
|
3630
|
+
},
|
|
3631
|
+
get fallback() {
|
|
3632
|
+
return (() => {
|
|
3633
|
+
var _el$96 = createElement("text");
|
|
3634
|
+
insertNode(_el$96, createTextNode(`No eligible USB drives. Connect a writable external USB drive, then press R to refresh.`));
|
|
3635
|
+
effect((_$p) => setProp(_el$96, "fg", palette.warning, _$p));
|
|
3636
|
+
return _el$96;
|
|
3637
|
+
})();
|
|
3638
|
+
},
|
|
3639
|
+
get children() {
|
|
3640
|
+
return [(() => {
|
|
3641
|
+
var _el$55 = createElement("box");
|
|
3642
|
+
setProp(_el$55, "gap", 0);
|
|
3643
|
+
insert(_el$55, createComponent2(For2, {
|
|
3644
|
+
get each() {
|
|
3645
|
+
return disks3().slice(Math.max(0, cursor() - 2), Math.max(3, cursor() + 1));
|
|
3646
|
+
},
|
|
3647
|
+
children: (d, index) => row(d.name, `${path(d)} / ${bytes(d.bytes)} / USB`, index() + Math.max(0, cursor() - 2))
|
|
3648
|
+
}));
|
|
3649
|
+
return _el$55;
|
|
3650
|
+
})(), (() => {
|
|
3651
|
+
var _el$56 = createElement("text");
|
|
3652
|
+
insert(_el$56, (() => {
|
|
3653
|
+
var _c$4 = memo2(() => cursor() < 0);
|
|
3654
|
+
return () => _c$4() ? "Use arrows to select a drive. Nothing is selected yet." : `${cursor() + 1} of ${disks3().length} drives / Enter to review`;
|
|
3655
|
+
})());
|
|
3656
|
+
effect((_$p) => setProp(_el$56, "fg", palette.muted, _$p));
|
|
3657
|
+
return _el$56;
|
|
3658
|
+
})()];
|
|
3659
|
+
}
|
|
3660
|
+
});
|
|
3661
|
+
}
|
|
3662
|
+
})];
|
|
3663
|
+
}
|
|
3664
|
+
}), createComponent2(Show, {
|
|
3665
|
+
get when() {
|
|
3666
|
+
return screen() === "confirm";
|
|
3667
|
+
},
|
|
3668
|
+
get children() {
|
|
3669
|
+
return [(() => {
|
|
3670
|
+
var _el$57 = createElement("text");
|
|
3671
|
+
insertNode(_el$57, createTextNode(`Erase this entire USB drive?`));
|
|
3672
|
+
effect((_p$) => {
|
|
3673
|
+
var _v$17 = palette.warning, _v$18 = TextAttributes2.BOLD;
|
|
3674
|
+
_v$17 !== _p$.e && (_p$.e = setProp(_el$57, "fg", _v$17, _p$.e));
|
|
3675
|
+
_v$18 !== _p$.t && (_p$.t = setProp(_el$57, "attributes", _v$18, _p$.t));
|
|
3676
|
+
return _p$;
|
|
3677
|
+
}, {
|
|
3678
|
+
e: void 0,
|
|
3679
|
+
t: void 0
|
|
3680
|
+
});
|
|
3681
|
+
return _el$57;
|
|
3682
|
+
})(), (() => {
|
|
3683
|
+
var _el$59 = createElement("box"), _el$60 = createElement("text"), _el$61 = createElement("text"), _el$62 = createElement("text"), _el$63 = createTextNode(` / `);
|
|
3684
|
+
insertNode(_el$59, _el$60);
|
|
3685
|
+
insertNode(_el$59, _el$61);
|
|
3686
|
+
insertNode(_el$59, _el$62);
|
|
3687
|
+
setProp(_el$59, "paddingX", 1);
|
|
3688
|
+
setProp(_el$60, "height", 1);
|
|
3689
|
+
setProp(_el$60, "wrapMode", "none");
|
|
3690
|
+
insert(_el$60, () => disk()?.name);
|
|
3691
|
+
insert(_el$61, (() => {
|
|
3692
|
+
var _c$5 = memo2(() => !!disk());
|
|
3693
|
+
return () => _c$5() ? `${path(disk())} / ${bytes(disk().bytes)}` : "";
|
|
3694
|
+
})());
|
|
3695
|
+
insertNode(_el$62, _el$63);
|
|
3696
|
+
insert(_el$62, () => image()?.profile.toUpperCase(), _el$63);
|
|
3697
|
+
insert(_el$62, (() => {
|
|
3698
|
+
var _c$6 = memo2(() => !!catalog()?.candidate);
|
|
3699
|
+
return () => _c$6() ? `candidate (build-only) ${catalog()?.revision.slice(0, 7)}` : `Boo ${catalog()?.version}`;
|
|
3700
|
+
})(), null);
|
|
3701
|
+
effect((_p$) => {
|
|
3702
|
+
var _v$19 = palette.panelAlt, _v$20 = palette.foreground, _v$21 = TextAttributes2.BOLD, _v$22 = palette.accent, _v$23 = catalog()?.candidate ? palette.warning : palette.muted;
|
|
3703
|
+
_v$19 !== _p$.e && (_p$.e = setProp(_el$59, "backgroundColor", _v$19, _p$.e));
|
|
3704
|
+
_v$20 !== _p$.t && (_p$.t = setProp(_el$60, "fg", _v$20, _p$.t));
|
|
3705
|
+
_v$21 !== _p$.a && (_p$.a = setProp(_el$60, "attributes", _v$21, _p$.a));
|
|
3706
|
+
_v$22 !== _p$.o && (_p$.o = setProp(_el$61, "fg", _v$22, _p$.o));
|
|
3707
|
+
_v$23 !== _p$.i && (_p$.i = setProp(_el$62, "fg", _v$23, _p$.i));
|
|
3708
|
+
return _p$;
|
|
3709
|
+
}, {
|
|
3710
|
+
e: void 0,
|
|
3711
|
+
t: void 0,
|
|
3712
|
+
a: void 0,
|
|
3713
|
+
o: void 0,
|
|
3714
|
+
i: void 0
|
|
3715
|
+
});
|
|
3716
|
+
return _el$59;
|
|
3717
|
+
})(), (() => {
|
|
3718
|
+
var _el$64 = createElement("text");
|
|
3719
|
+
insertNode(_el$64, createTextNode(`ALL data and partitions on this drive will be lost. Back up anything you need before continuing.`));
|
|
3720
|
+
effect((_$p) => setProp(_el$64, "fg", palette.warning, _$p));
|
|
3721
|
+
return _el$64;
|
|
3722
|
+
})(), (() => {
|
|
3723
|
+
var _el$66 = createElement("box");
|
|
3724
|
+
insert(_el$66, () => row("Go back", "", 0), null);
|
|
3725
|
+
insert(_el$66, () => row("Erase & flash", "", 1, true), null);
|
|
3726
|
+
effect((_$p) => setProp(_el$66, "gap", tight() ? 0 : 1, _$p));
|
|
3727
|
+
return _el$66;
|
|
3728
|
+
})()];
|
|
3729
|
+
}
|
|
3730
|
+
}), createComponent2(Show, {
|
|
3731
|
+
get when() {
|
|
3732
|
+
return screen() === "busy";
|
|
3733
|
+
},
|
|
3734
|
+
get children() {
|
|
3735
|
+
return [(() => {
|
|
3736
|
+
var _el$67 = createElement("text"), _el$68 = createTextNode(` `);
|
|
3737
|
+
insertNode(_el$67, _el$68);
|
|
3738
|
+
insert(_el$67, spinner, _el$68);
|
|
3739
|
+
insert(_el$67, (() => {
|
|
3740
|
+
var _c$7 = memo2(() => !!stopping());
|
|
3741
|
+
return () => _c$7() ? "Stopping safely..." : progress().stage;
|
|
3742
|
+
})(), null);
|
|
3743
|
+
effect((_p$) => {
|
|
3744
|
+
var _v$24 = palette.accent, _v$25 = TextAttributes2.BOLD;
|
|
3745
|
+
_v$24 !== _p$.e && (_p$.e = setProp(_el$67, "fg", _v$24, _p$.e));
|
|
3746
|
+
_v$25 !== _p$.t && (_p$.t = setProp(_el$67, "attributes", _v$25, _p$.t));
|
|
3747
|
+
return _p$;
|
|
3748
|
+
}, {
|
|
3749
|
+
e: void 0,
|
|
3750
|
+
t: void 0
|
|
3751
|
+
});
|
|
3752
|
+
return _el$67;
|
|
3753
|
+
})(), (() => {
|
|
3754
|
+
var _el$69 = createElement("text"), _el$70 = createTextNode(` / `);
|
|
3755
|
+
insertNode(_el$69, _el$70);
|
|
3756
|
+
insert(_el$69, () => disk()?.name, _el$70);
|
|
3757
|
+
insert(_el$69, () => image()?.profile.toUpperCase(), null);
|
|
3758
|
+
effect((_$p) => setProp(_el$69, "fg", palette.foreground, _$p));
|
|
3759
|
+
return _el$69;
|
|
3760
|
+
})(), (() => {
|
|
3761
|
+
var _el$71 = createElement("text");
|
|
3762
|
+
insert(_el$71, (() => {
|
|
3763
|
+
var _c$8 = memo2(() => !!stopping());
|
|
3764
|
+
return () => _c$8() ? "Waiting for all child processes to stop. Keep the USB connected." : progress().detail;
|
|
3765
|
+
})());
|
|
3766
|
+
effect((_$p) => setProp(_el$71, "fg", palette.muted, _$p));
|
|
3767
|
+
return _el$71;
|
|
3768
|
+
})(), createComponent2(Show, {
|
|
3769
|
+
get when() {
|
|
3770
|
+
return percent() !== void 0;
|
|
3771
|
+
},
|
|
3772
|
+
get children() {
|
|
3773
|
+
return [(() => {
|
|
3774
|
+
var _el$72 = createElement("box");
|
|
3775
|
+
setProp(_el$72, "border", true);
|
|
3776
|
+
setProp(_el$72, "height", 3);
|
|
3777
|
+
insert(_el$72, createComponent2(Show, {
|
|
3778
|
+
get when() {
|
|
3779
|
+
return percent() > 0;
|
|
3780
|
+
},
|
|
3781
|
+
get children() {
|
|
3782
|
+
var _el$73 = createElement("box");
|
|
3783
|
+
setProp(_el$73, "height", 1);
|
|
3784
|
+
effect((_p$) => {
|
|
3785
|
+
var _v$26 = `${percent() ?? 0}%`, _v$27 = palette.accent;
|
|
3786
|
+
_v$26 !== _p$.e && (_p$.e = setProp(_el$73, "width", _v$26, _p$.e));
|
|
3787
|
+
_v$27 !== _p$.t && (_p$.t = setProp(_el$73, "backgroundColor", _v$27, _p$.t));
|
|
3788
|
+
return _p$;
|
|
3789
|
+
}, {
|
|
3790
|
+
e: void 0,
|
|
3791
|
+
t: void 0
|
|
3792
|
+
});
|
|
3793
|
+
return _el$73;
|
|
3794
|
+
}
|
|
3795
|
+
}));
|
|
3796
|
+
effect((_p$) => {
|
|
3797
|
+
var _v$28 = palette.accent, _v$29 = palette.panelAlt;
|
|
3798
|
+
_v$28 !== _p$.e && (_p$.e = setProp(_el$72, "borderColor", _v$28, _p$.e));
|
|
3799
|
+
_v$29 !== _p$.t && (_p$.t = setProp(_el$72, "backgroundColor", _v$29, _p$.t));
|
|
3800
|
+
return _p$;
|
|
3801
|
+
}, {
|
|
3802
|
+
e: void 0,
|
|
3803
|
+
t: void 0
|
|
3804
|
+
});
|
|
3805
|
+
return _el$72;
|
|
3806
|
+
})(), (() => {
|
|
3807
|
+
var _el$74 = createElement("text"), _el$75 = createTextNode(`% `), _el$76 = createTextNode(` / `);
|
|
3808
|
+
insertNode(_el$74, _el$75);
|
|
3809
|
+
insertNode(_el$74, _el$76);
|
|
3810
|
+
insert(_el$74, percent, _el$75);
|
|
3811
|
+
insert(_el$74, () => bytes(progress().bytes ?? 0), _el$76);
|
|
3812
|
+
insert(_el$74, () => bytes(progress().total ?? 0), null);
|
|
3813
|
+
effect((_p$) => {
|
|
3814
|
+
var _v$30 = palette.foreground, _v$31 = TextAttributes2.BOLD;
|
|
3815
|
+
_v$30 !== _p$.e && (_p$.e = setProp(_el$74, "fg", _v$30, _p$.e));
|
|
3816
|
+
_v$31 !== _p$.t && (_p$.t = setProp(_el$74, "attributes", _v$31, _p$.t));
|
|
3817
|
+
return _p$;
|
|
3818
|
+
}, {
|
|
3819
|
+
e: void 0,
|
|
3820
|
+
t: void 0
|
|
3821
|
+
});
|
|
3822
|
+
return _el$74;
|
|
3823
|
+
})(), (() => {
|
|
3824
|
+
var _el$77 = createElement("text");
|
|
3825
|
+
insert(_el$77, (() => {
|
|
3826
|
+
var _c$9 = memo2(() => !!progress().speed);
|
|
3827
|
+
return () => _c$9() ? `${bytes(progress().speed)} / s` : "";
|
|
3828
|
+
})());
|
|
3829
|
+
effect((_$p) => setProp(_el$77, "fg", palette.muted, _$p));
|
|
3830
|
+
return _el$77;
|
|
3831
|
+
})()];
|
|
3832
|
+
}
|
|
3833
|
+
}), (() => {
|
|
3834
|
+
var _el$78 = createElement("text");
|
|
3835
|
+
insertNode(_el$78, createTextNode(`Keep the USB connected until safe removal succeeds.`));
|
|
3836
|
+
effect((_$p) => setProp(_el$78, "fg", palette.warning, _$p));
|
|
3837
|
+
return _el$78;
|
|
3838
|
+
})()];
|
|
3839
|
+
}
|
|
3840
|
+
}), createComponent2(Show, {
|
|
3841
|
+
get when() {
|
|
3842
|
+
return screen() === "success";
|
|
3843
|
+
},
|
|
3844
|
+
get children() {
|
|
3845
|
+
return [(() => {
|
|
3846
|
+
var _el$80 = createElement("text");
|
|
3847
|
+
insert(_el$80, () => props.backend.successTitle);
|
|
3848
|
+
effect((_p$) => {
|
|
3849
|
+
var _v$32 = palette.success, _v$33 = TextAttributes2.BOLD;
|
|
3850
|
+
_v$32 !== _p$.e && (_p$.e = setProp(_el$80, "fg", _v$32, _p$.e));
|
|
3851
|
+
_v$33 !== _p$.t && (_p$.t = setProp(_el$80, "attributes", _v$33, _p$.t));
|
|
3852
|
+
return _p$;
|
|
3853
|
+
}, {
|
|
3854
|
+
e: void 0,
|
|
3855
|
+
t: void 0
|
|
3856
|
+
});
|
|
3857
|
+
return _el$80;
|
|
3858
|
+
})(), (() => {
|
|
3859
|
+
var _el$81 = createElement("text");
|
|
3860
|
+
insert(_el$81, () => props.backend.successDetail);
|
|
3861
|
+
effect((_$p) => setProp(_el$81, "fg", palette.foreground, _$p));
|
|
3862
|
+
return _el$81;
|
|
3863
|
+
})(), (() => {
|
|
3864
|
+
var _el$82 = createElement("text");
|
|
3865
|
+
insert(_el$82, (() => {
|
|
3866
|
+
var _c$0 = memo2(() => !!props.backend.demo);
|
|
3867
|
+
return () => _c$0() ? "The real flow checks the write, flush and safe removal." : catalog()?.candidate ? "GitHub ZIP, compressed image and raw stream verified. Not a device read-back." : "Compressed image and raw stream verified. Not a device read-back.";
|
|
3868
|
+
})());
|
|
3869
|
+
effect((_$p) => setProp(_el$82, "fg", palette.muted, _$p));
|
|
3870
|
+
return _el$82;
|
|
3871
|
+
})(), (() => {
|
|
3872
|
+
var _el$83 = createElement("box");
|
|
3873
|
+
setProp(_el$83, "gap", 1);
|
|
3874
|
+
insert(_el$83, () => row("Choose another image", "", 0), null);
|
|
3875
|
+
insert(_el$83, () => row("Quit", "", 1), null);
|
|
3876
|
+
return _el$83;
|
|
3877
|
+
})()];
|
|
3878
|
+
}
|
|
3879
|
+
}), createComponent2(Show, {
|
|
3880
|
+
get when() {
|
|
3881
|
+
return screen() === "error";
|
|
3882
|
+
},
|
|
3883
|
+
get children() {
|
|
3884
|
+
return [(() => {
|
|
3885
|
+
var _el$84 = createElement("text");
|
|
3886
|
+
insertNode(_el$84, createTextNode(`Flash not completed`));
|
|
3887
|
+
effect((_p$) => {
|
|
3888
|
+
var _v$34 = palette.danger, _v$35 = TextAttributes2.BOLD;
|
|
3889
|
+
_v$34 !== _p$.e && (_p$.e = setProp(_el$84, "fg", _v$34, _p$.e));
|
|
3890
|
+
_v$35 !== _p$.t && (_p$.t = setProp(_el$84, "attributes", _v$35, _p$.t));
|
|
3891
|
+
return _p$;
|
|
3892
|
+
}, {
|
|
3893
|
+
e: void 0,
|
|
3894
|
+
t: void 0
|
|
3895
|
+
});
|
|
3896
|
+
return _el$84;
|
|
3897
|
+
})(), (() => {
|
|
3898
|
+
var _el$86 = createElement("text");
|
|
3899
|
+
insert(_el$86, error);
|
|
3900
|
+
effect((_$p) => setProp(_el$86, "fg", palette.foreground, _$p));
|
|
3901
|
+
return _el$86;
|
|
3902
|
+
})(), (() => {
|
|
3903
|
+
var _el$87 = createElement("box");
|
|
3904
|
+
setProp(_el$87, "gap", 1);
|
|
3905
|
+
insert(_el$87, () => row("Refresh & try again", "", 0), null);
|
|
3906
|
+
insert(_el$87, () => row("Quit", "", 1), null);
|
|
3907
|
+
return _el$87;
|
|
3908
|
+
})()];
|
|
3909
|
+
}
|
|
3910
|
+
})];
|
|
3911
|
+
}
|
|
3912
|
+
}));
|
|
3913
|
+
setProp(_el$88, "height", 1);
|
|
3914
|
+
setProp(_el$88, "flexShrink", 0);
|
|
3915
|
+
setProp(_el$88, "wrapMode", "none");
|
|
3916
|
+
insert(_el$88, (() => {
|
|
3917
|
+
var _c$1 = memo2(() => !!busy());
|
|
3918
|
+
return () => _c$1() ? "Esc cancel / Q stop & quit" : dimensions().width < 56 ? "Arrows / Enter / Esc / R / Q quit" : "Arrows / Enter / Esc back / R refresh / Q quit";
|
|
3919
|
+
})());
|
|
3920
|
+
effect((_p$) => {
|
|
3921
|
+
var _v$36 = palette.background, _v$37 = dimensions().width <= 80 ? "100%" : 80, _v$38 = dimensions().height <= 24 ? "100%" : 24, _v$39 = palette.border, _v$40 = palette.card, _v$41 = palette.accent, _v$42 = TextAttributes2.BOLD, _v$43 = props.backend.demo ? palette.warning : palette.muted, _v$44 = palette.danger, _v$45 = tight() ? 0 : 1, _v$46 = tight() ? 0 : 1, _v$47 = tight() || (compact() || dimensions().width < 56) && screen() === "setup" ? 0 : 1, _v$48 = palette.muted;
|
|
3922
|
+
_v$36 !== _p$.e && (_p$.e = setProp(_el$5, "backgroundColor", _v$36, _p$.e));
|
|
3923
|
+
_v$37 !== _p$.t && (_p$.t = setProp(_el$6, "width", _v$37, _p$.t));
|
|
3924
|
+
_v$38 !== _p$.a && (_p$.a = setProp(_el$6, "height", _v$38, _p$.a));
|
|
3925
|
+
_v$39 !== _p$.o && (_p$.o = setProp(_el$6, "borderColor", _v$39, _p$.o));
|
|
3926
|
+
_v$40 !== _p$.i && (_p$.i = setProp(_el$6, "backgroundColor", _v$40, _p$.i));
|
|
3927
|
+
_v$41 !== _p$.n && (_p$.n = setProp(_el$8, "fg", _v$41, _p$.n));
|
|
3928
|
+
_v$42 !== _p$.s && (_p$.s = setProp(_el$8, "attributes", _v$42, _p$.s));
|
|
3929
|
+
_v$43 !== _p$.h && (_p$.h = setProp(_el$0, "fg", _v$43, _p$.h));
|
|
3930
|
+
_v$44 !== _p$.r && (_p$.r = setProp(_el$1, "backgroundColor", _v$44, _p$.r));
|
|
3931
|
+
_v$45 !== _p$.d && (_p$.d = setProp(_el$1, "marginTop", _v$45, _p$.d));
|
|
3932
|
+
_v$46 !== _p$.l && (_p$.l = setProp(_el$10, "paddingTop", _v$46, _p$.l));
|
|
3933
|
+
_v$47 !== _p$.u && (_p$.u = setProp(_el$10, "gap", _v$47, _p$.u));
|
|
3934
|
+
_v$48 !== _p$.c && (_p$.c = setProp(_el$88, "fg", _v$48, _p$.c));
|
|
3935
|
+
return _p$;
|
|
3936
|
+
}, {
|
|
3937
|
+
e: void 0,
|
|
3938
|
+
t: void 0,
|
|
3939
|
+
a: void 0,
|
|
3940
|
+
o: void 0,
|
|
3941
|
+
i: void 0,
|
|
3942
|
+
n: void 0,
|
|
3943
|
+
s: void 0,
|
|
3944
|
+
h: void 0,
|
|
3945
|
+
r: void 0,
|
|
3946
|
+
d: void 0,
|
|
3947
|
+
l: void 0,
|
|
3948
|
+
u: void 0,
|
|
3949
|
+
c: void 0
|
|
3950
|
+
});
|
|
3951
|
+
return _el$5;
|
|
3952
|
+
})();
|
|
3953
|
+
}
|
|
3954
|
+
|
|
3955
|
+
// src/index.tsx
|
|
3956
|
+
var args = process.argv.slice(2);
|
|
3957
|
+
var demo = args.includes("--demo");
|
|
3958
|
+
var fixture = args.find((arg) => arg.startsWith("--demo="))?.slice(7);
|
|
3959
|
+
if (args.length > 1 || args.some((arg) => !["--demo", "--demo=empty", "--demo=error", "--demo=setup", "--demo=setup-gh", "--demo=setup-zstd", "--demo=setup-error", "--demo=setup-interrupted", "--demo=no-homebrew", "--list-images", "--clear-cache"].includes(arg))) {
|
|
3960
|
+
console.error("Usage: bun start [--demo[=empty|error|setup|setup-gh|setup-zstd|setup-error|setup-interrupted|no-homebrew] | --list-images | --clear-cache]");
|
|
3961
|
+
process.exitCode = 1;
|
|
3962
|
+
} else if (args.includes("--clear-cache")) {
|
|
3963
|
+
try {
|
|
3964
|
+
console.log(await new FlashBackend().clearCache(new AbortController().signal));
|
|
3965
|
+
} catch (e) {
|
|
3966
|
+
console.error(e instanceof Error ? e.message : "Cache clear failed.");
|
|
3967
|
+
process.exitCode = 1;
|
|
3968
|
+
}
|
|
3969
|
+
} else if (args.includes("--list-images")) {
|
|
3970
|
+
try {
|
|
3971
|
+
console.log(JSON.stringify(await discoverImages(new AbortController().signal), null, 2));
|
|
3972
|
+
} catch (e) {
|
|
3973
|
+
console.error(e instanceof Error ? e.message : "Image discovery failed.");
|
|
3974
|
+
process.exitCode = 1;
|
|
3975
|
+
}
|
|
3976
|
+
} else if (!demo && !fixture && (!["darwin", "linux"].includes(process.platform) || process.getuid?.() === 0)) {
|
|
3977
|
+
console.error("Boo Flash requires macOS or Linux and a normal user account. Use --demo for a non-writing UI preview.");
|
|
3978
|
+
process.exitCode = 1;
|
|
3979
|
+
} else {
|
|
3980
|
+
const backend = new FlashBackend(demo ? "normal" : fixture);
|
|
3981
|
+
const renderer = await createCliRenderer2({
|
|
3982
|
+
backgroundColor: palette.background,
|
|
3983
|
+
targetFps: 30,
|
|
3984
|
+
exitOnCtrlC: false,
|
|
3985
|
+
exitSignals: []
|
|
3986
|
+
});
|
|
3987
|
+
let stop = async () => {
|
|
3988
|
+
};
|
|
3989
|
+
let finishing;
|
|
3990
|
+
let finished;
|
|
3991
|
+
const done = new Promise((resolve) => {
|
|
3992
|
+
finished = resolve;
|
|
3993
|
+
});
|
|
3994
|
+
const quit = () => {
|
|
3995
|
+
finishing ??= (async () => {
|
|
3996
|
+
try {
|
|
3997
|
+
await stop();
|
|
3998
|
+
} finally {
|
|
3999
|
+
renderer.destroy();
|
|
4000
|
+
finished();
|
|
4001
|
+
}
|
|
4002
|
+
})();
|
|
4003
|
+
};
|
|
4004
|
+
const signals = ["SIGINT", "SIGTERM", "SIGHUP", "SIGQUIT"];
|
|
4005
|
+
for (const signal of signals) process.on(signal, quit);
|
|
4006
|
+
try {
|
|
4007
|
+
await render(() => createComponent2(App, {
|
|
4008
|
+
backend,
|
|
4009
|
+
quit,
|
|
4010
|
+
registerStop: (handler) => {
|
|
4011
|
+
stop = handler;
|
|
4012
|
+
}
|
|
4013
|
+
}), renderer);
|
|
4014
|
+
await done;
|
|
4015
|
+
} finally {
|
|
4016
|
+
await stop();
|
|
4017
|
+
renderer.destroy();
|
|
4018
|
+
for (const signal of signals) process.off(signal, quit);
|
|
4019
|
+
}
|
|
4020
|
+
}
|