bx-mac 0.6.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +100 -6
- package/dist/bx-native +0 -0
- package/dist/bx.js +1325 -180
- package/package.json +3 -1
package/dist/bx.js
CHANGED
|
@@ -1,29 +1,900 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const __VERSION__ = "0.6.0";
|
|
3
2
|
import { accessSync, constants, cpSync, existsSync, globSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { dirname, join, resolve } from "node:path";
|
|
5
|
-
import { spawn } from "node:child_process";
|
|
3
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
4
|
+
import { execFileSync, spawn } from "node:child_process";
|
|
5
|
+
import { createInterface } from "node:readline";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
-
import process from "node:process";
|
|
7
|
+
import process$1 from "node:process";
|
|
8
|
+
//#region node_modules/.pnpm/smol-toml@1.6.1/node_modules/smol-toml/dist/error.js
|
|
9
|
+
/*!
|
|
10
|
+
* Copyright (c) Squirrel Chat et al., All rights reserved.
|
|
11
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
12
|
+
*
|
|
13
|
+
* Redistribution and use in source and binary forms, with or without
|
|
14
|
+
* modification, are permitted provided that the following conditions are met:
|
|
15
|
+
*
|
|
16
|
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
|
17
|
+
* list of conditions and the following disclaimer.
|
|
18
|
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
19
|
+
* this list of conditions and the following disclaimer in the
|
|
20
|
+
* documentation and/or other materials provided with the distribution.
|
|
21
|
+
* 3. Neither the name of the copyright holder nor the names of its contributors
|
|
22
|
+
* may be used to endorse or promote products derived from this software without
|
|
23
|
+
* specific prior written permission.
|
|
24
|
+
*
|
|
25
|
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
26
|
+
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
27
|
+
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
28
|
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
29
|
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
30
|
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
31
|
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
32
|
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
33
|
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
34
|
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
35
|
+
*/
|
|
36
|
+
function getLineColFromPtr(string, ptr) {
|
|
37
|
+
let lines = string.slice(0, ptr).split(/\r\n|\n|\r/g);
|
|
38
|
+
return [lines.length, lines.pop().length + 1];
|
|
39
|
+
}
|
|
40
|
+
function makeCodeBlock(string, line, column) {
|
|
41
|
+
let lines = string.split(/\r\n|\n|\r/g);
|
|
42
|
+
let codeblock = "";
|
|
43
|
+
let numberLen = (Math.log10(line + 1) | 0) + 1;
|
|
44
|
+
for (let i = line - 1; i <= line + 1; i++) {
|
|
45
|
+
let l = lines[i - 1];
|
|
46
|
+
if (!l) continue;
|
|
47
|
+
codeblock += i.toString().padEnd(numberLen, " ");
|
|
48
|
+
codeblock += ": ";
|
|
49
|
+
codeblock += l;
|
|
50
|
+
codeblock += "\n";
|
|
51
|
+
if (i === line) {
|
|
52
|
+
codeblock += " ".repeat(numberLen + column + 2);
|
|
53
|
+
codeblock += "^\n";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return codeblock;
|
|
57
|
+
}
|
|
58
|
+
var TomlError = class extends Error {
|
|
59
|
+
line;
|
|
60
|
+
column;
|
|
61
|
+
codeblock;
|
|
62
|
+
constructor(message, options) {
|
|
63
|
+
const [line, column] = getLineColFromPtr(options.toml, options.ptr);
|
|
64
|
+
const codeblock = makeCodeBlock(options.toml, line, column);
|
|
65
|
+
super(`Invalid TOML document: ${message}\n\n${codeblock}`, options);
|
|
66
|
+
this.line = line;
|
|
67
|
+
this.column = column;
|
|
68
|
+
this.codeblock = codeblock;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region node_modules/.pnpm/smol-toml@1.6.1/node_modules/smol-toml/dist/util.js
|
|
73
|
+
/*!
|
|
74
|
+
* Copyright (c) Squirrel Chat et al., All rights reserved.
|
|
75
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
76
|
+
*
|
|
77
|
+
* Redistribution and use in source and binary forms, with or without
|
|
78
|
+
* modification, are permitted provided that the following conditions are met:
|
|
79
|
+
*
|
|
80
|
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
|
81
|
+
* list of conditions and the following disclaimer.
|
|
82
|
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
83
|
+
* this list of conditions and the following disclaimer in the
|
|
84
|
+
* documentation and/or other materials provided with the distribution.
|
|
85
|
+
* 3. Neither the name of the copyright holder nor the names of its contributors
|
|
86
|
+
* may be used to endorse or promote products derived from this software without
|
|
87
|
+
* specific prior written permission.
|
|
88
|
+
*
|
|
89
|
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
90
|
+
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
91
|
+
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
92
|
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
93
|
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
94
|
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
95
|
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
96
|
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
97
|
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
98
|
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
99
|
+
*/
|
|
100
|
+
function isEscaped(str, ptr) {
|
|
101
|
+
let i = 0;
|
|
102
|
+
while (str[ptr - ++i] === "\\");
|
|
103
|
+
return --i && i % 2;
|
|
104
|
+
}
|
|
105
|
+
function indexOfNewline(str, start = 0, end = str.length) {
|
|
106
|
+
let idx = str.indexOf("\n", start);
|
|
107
|
+
if (str[idx - 1] === "\r") idx--;
|
|
108
|
+
return idx <= end ? idx : -1;
|
|
109
|
+
}
|
|
110
|
+
function skipComment(str, ptr) {
|
|
111
|
+
for (let i = ptr; i < str.length; i++) {
|
|
112
|
+
let c = str[i];
|
|
113
|
+
if (c === "\n") return i;
|
|
114
|
+
if (c === "\r" && str[i + 1] === "\n") return i + 1;
|
|
115
|
+
if (c < " " && c !== " " || c === "") throw new TomlError("control characters are not allowed in comments", {
|
|
116
|
+
toml: str,
|
|
117
|
+
ptr
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
return str.length;
|
|
121
|
+
}
|
|
122
|
+
function skipVoid(str, ptr, banNewLines, banComments) {
|
|
123
|
+
let c;
|
|
124
|
+
while (1) {
|
|
125
|
+
while ((c = str[ptr]) === " " || c === " " || !banNewLines && (c === "\n" || c === "\r" && str[ptr + 1] === "\n")) ptr++;
|
|
126
|
+
if (banComments || c !== "#") break;
|
|
127
|
+
ptr = skipComment(str, ptr);
|
|
128
|
+
}
|
|
129
|
+
return ptr;
|
|
130
|
+
}
|
|
131
|
+
function skipUntil(str, ptr, sep, end, banNewLines = false) {
|
|
132
|
+
if (!end) {
|
|
133
|
+
ptr = indexOfNewline(str, ptr);
|
|
134
|
+
return ptr < 0 ? str.length : ptr;
|
|
135
|
+
}
|
|
136
|
+
for (let i = ptr; i < str.length; i++) {
|
|
137
|
+
let c = str[i];
|
|
138
|
+
if (c === "#") i = indexOfNewline(str, i);
|
|
139
|
+
else if (c === sep) return i + 1;
|
|
140
|
+
else if (c === end || banNewLines && (c === "\n" || c === "\r" && str[i + 1] === "\n")) return i;
|
|
141
|
+
}
|
|
142
|
+
throw new TomlError("cannot find end of structure", {
|
|
143
|
+
toml: str,
|
|
144
|
+
ptr
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
function getStringEnd(str, seek) {
|
|
148
|
+
let first = str[seek];
|
|
149
|
+
let target = first === str[seek + 1] && str[seek + 1] === str[seek + 2] ? str.slice(seek, seek + 3) : first;
|
|
150
|
+
seek += target.length - 1;
|
|
151
|
+
do
|
|
152
|
+
seek = str.indexOf(target, ++seek);
|
|
153
|
+
while (seek > -1 && first !== "'" && isEscaped(str, seek));
|
|
154
|
+
if (seek > -1) {
|
|
155
|
+
seek += target.length;
|
|
156
|
+
if (target.length > 1) {
|
|
157
|
+
if (str[seek] === first) seek++;
|
|
158
|
+
if (str[seek] === first) seek++;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return seek;
|
|
162
|
+
}
|
|
163
|
+
//#endregion
|
|
164
|
+
//#region node_modules/.pnpm/smol-toml@1.6.1/node_modules/smol-toml/dist/date.js
|
|
165
|
+
/*!
|
|
166
|
+
* Copyright (c) Squirrel Chat et al., All rights reserved.
|
|
167
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
168
|
+
*
|
|
169
|
+
* Redistribution and use in source and binary forms, with or without
|
|
170
|
+
* modification, are permitted provided that the following conditions are met:
|
|
171
|
+
*
|
|
172
|
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
|
173
|
+
* list of conditions and the following disclaimer.
|
|
174
|
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
175
|
+
* this list of conditions and the following disclaimer in the
|
|
176
|
+
* documentation and/or other materials provided with the distribution.
|
|
177
|
+
* 3. Neither the name of the copyright holder nor the names of its contributors
|
|
178
|
+
* may be used to endorse or promote products derived from this software without
|
|
179
|
+
* specific prior written permission.
|
|
180
|
+
*
|
|
181
|
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
182
|
+
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
183
|
+
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
184
|
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
185
|
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
186
|
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
187
|
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
188
|
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
189
|
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
190
|
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
191
|
+
*/
|
|
192
|
+
let DATE_TIME_RE = /^(\d{4}-\d{2}-\d{2})?[T ]?(?:(\d{2}):\d{2}(?::\d{2}(?:\.\d+)?)?)?(Z|[-+]\d{2}:\d{2})?$/i;
|
|
193
|
+
var TomlDate = class TomlDate extends Date {
|
|
194
|
+
#hasDate = false;
|
|
195
|
+
#hasTime = false;
|
|
196
|
+
#offset = null;
|
|
197
|
+
constructor(date) {
|
|
198
|
+
let hasDate = true;
|
|
199
|
+
let hasTime = true;
|
|
200
|
+
let offset = "Z";
|
|
201
|
+
if (typeof date === "string") {
|
|
202
|
+
let match = date.match(DATE_TIME_RE);
|
|
203
|
+
if (match) {
|
|
204
|
+
if (!match[1]) {
|
|
205
|
+
hasDate = false;
|
|
206
|
+
date = `0000-01-01T${date}`;
|
|
207
|
+
}
|
|
208
|
+
hasTime = !!match[2];
|
|
209
|
+
hasTime && date[10] === " " && (date = date.replace(" ", "T"));
|
|
210
|
+
if (match[2] && +match[2] > 23) date = "";
|
|
211
|
+
else {
|
|
212
|
+
offset = match[3] || null;
|
|
213
|
+
date = date.toUpperCase();
|
|
214
|
+
if (!offset && hasTime) date += "Z";
|
|
215
|
+
}
|
|
216
|
+
} else date = "";
|
|
217
|
+
}
|
|
218
|
+
super(date);
|
|
219
|
+
if (!isNaN(this.getTime())) {
|
|
220
|
+
this.#hasDate = hasDate;
|
|
221
|
+
this.#hasTime = hasTime;
|
|
222
|
+
this.#offset = offset;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
isDateTime() {
|
|
226
|
+
return this.#hasDate && this.#hasTime;
|
|
227
|
+
}
|
|
228
|
+
isLocal() {
|
|
229
|
+
return !this.#hasDate || !this.#hasTime || !this.#offset;
|
|
230
|
+
}
|
|
231
|
+
isDate() {
|
|
232
|
+
return this.#hasDate && !this.#hasTime;
|
|
233
|
+
}
|
|
234
|
+
isTime() {
|
|
235
|
+
return this.#hasTime && !this.#hasDate;
|
|
236
|
+
}
|
|
237
|
+
isValid() {
|
|
238
|
+
return this.#hasDate || this.#hasTime;
|
|
239
|
+
}
|
|
240
|
+
toISOString() {
|
|
241
|
+
let iso = super.toISOString();
|
|
242
|
+
if (this.isDate()) return iso.slice(0, 10);
|
|
243
|
+
if (this.isTime()) return iso.slice(11, 23);
|
|
244
|
+
if (this.#offset === null) return iso.slice(0, -1);
|
|
245
|
+
if (this.#offset === "Z") return iso;
|
|
246
|
+
let offset = +this.#offset.slice(1, 3) * 60 + +this.#offset.slice(4, 6);
|
|
247
|
+
offset = this.#offset[0] === "-" ? offset : -offset;
|
|
248
|
+
return (/* @__PURE__ */ new Date(this.getTime() - offset * 6e4)).toISOString().slice(0, -1) + this.#offset;
|
|
249
|
+
}
|
|
250
|
+
static wrapAsOffsetDateTime(jsDate, offset = "Z") {
|
|
251
|
+
let date = new TomlDate(jsDate);
|
|
252
|
+
date.#offset = offset;
|
|
253
|
+
return date;
|
|
254
|
+
}
|
|
255
|
+
static wrapAsLocalDateTime(jsDate) {
|
|
256
|
+
let date = new TomlDate(jsDate);
|
|
257
|
+
date.#offset = null;
|
|
258
|
+
return date;
|
|
259
|
+
}
|
|
260
|
+
static wrapAsLocalDate(jsDate) {
|
|
261
|
+
let date = new TomlDate(jsDate);
|
|
262
|
+
date.#hasTime = false;
|
|
263
|
+
date.#offset = null;
|
|
264
|
+
return date;
|
|
265
|
+
}
|
|
266
|
+
static wrapAsLocalTime(jsDate) {
|
|
267
|
+
let date = new TomlDate(jsDate);
|
|
268
|
+
date.#hasDate = false;
|
|
269
|
+
date.#offset = null;
|
|
270
|
+
return date;
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
//#endregion
|
|
274
|
+
//#region node_modules/.pnpm/smol-toml@1.6.1/node_modules/smol-toml/dist/primitive.js
|
|
275
|
+
/*!
|
|
276
|
+
* Copyright (c) Squirrel Chat et al., All rights reserved.
|
|
277
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
278
|
+
*
|
|
279
|
+
* Redistribution and use in source and binary forms, with or without
|
|
280
|
+
* modification, are permitted provided that the following conditions are met:
|
|
281
|
+
*
|
|
282
|
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
|
283
|
+
* list of conditions and the following disclaimer.
|
|
284
|
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
285
|
+
* this list of conditions and the following disclaimer in the
|
|
286
|
+
* documentation and/or other materials provided with the distribution.
|
|
287
|
+
* 3. Neither the name of the copyright holder nor the names of its contributors
|
|
288
|
+
* may be used to endorse or promote products derived from this software without
|
|
289
|
+
* specific prior written permission.
|
|
290
|
+
*
|
|
291
|
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
292
|
+
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
293
|
+
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
294
|
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
295
|
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
296
|
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
297
|
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
298
|
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
299
|
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
300
|
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
301
|
+
*/
|
|
302
|
+
let INT_REGEX = /^((0x[0-9a-fA-F](_?[0-9a-fA-F])*)|(([+-]|0[ob])?\d(_?\d)*))$/;
|
|
303
|
+
let FLOAT_REGEX = /^[+-]?\d(_?\d)*(\.\d(_?\d)*)?([eE][+-]?\d(_?\d)*)?$/;
|
|
304
|
+
let LEADING_ZERO = /^[+-]?0[0-9_]/;
|
|
305
|
+
let ESCAPE_REGEX = /^[0-9a-f]{2,8}$/i;
|
|
306
|
+
let ESC_MAP = {
|
|
307
|
+
b: "\b",
|
|
308
|
+
t: " ",
|
|
309
|
+
n: "\n",
|
|
310
|
+
f: "\f",
|
|
311
|
+
r: "\r",
|
|
312
|
+
e: "\x1B",
|
|
313
|
+
"\"": "\"",
|
|
314
|
+
"\\": "\\"
|
|
315
|
+
};
|
|
316
|
+
function parseString(str, ptr = 0, endPtr = str.length) {
|
|
317
|
+
let isLiteral = str[ptr] === "'";
|
|
318
|
+
let isMultiline = str[ptr++] === str[ptr] && str[ptr] === str[ptr + 1];
|
|
319
|
+
if (isMultiline) {
|
|
320
|
+
endPtr -= 2;
|
|
321
|
+
if (str[ptr += 2] === "\r") ptr++;
|
|
322
|
+
if (str[ptr] === "\n") ptr++;
|
|
323
|
+
}
|
|
324
|
+
let tmp = 0;
|
|
325
|
+
let isEscape;
|
|
326
|
+
let parsed = "";
|
|
327
|
+
let sliceStart = ptr;
|
|
328
|
+
while (ptr < endPtr - 1) {
|
|
329
|
+
let c = str[ptr++];
|
|
330
|
+
if (c === "\n" || c === "\r" && str[ptr] === "\n") {
|
|
331
|
+
if (!isMultiline) throw new TomlError("newlines are not allowed in strings", {
|
|
332
|
+
toml: str,
|
|
333
|
+
ptr: ptr - 1
|
|
334
|
+
});
|
|
335
|
+
} else if (c < " " && c !== " " || c === "") throw new TomlError("control characters are not allowed in strings", {
|
|
336
|
+
toml: str,
|
|
337
|
+
ptr: ptr - 1
|
|
338
|
+
});
|
|
339
|
+
if (isEscape) {
|
|
340
|
+
isEscape = false;
|
|
341
|
+
if (c === "x" || c === "u" || c === "U") {
|
|
342
|
+
let code = str.slice(ptr, ptr += c === "x" ? 2 : c === "u" ? 4 : 8);
|
|
343
|
+
if (!ESCAPE_REGEX.test(code)) throw new TomlError("invalid unicode escape", {
|
|
344
|
+
toml: str,
|
|
345
|
+
ptr: tmp
|
|
346
|
+
});
|
|
347
|
+
try {
|
|
348
|
+
parsed += String.fromCodePoint(parseInt(code, 16));
|
|
349
|
+
} catch {
|
|
350
|
+
throw new TomlError("invalid unicode escape", {
|
|
351
|
+
toml: str,
|
|
352
|
+
ptr: tmp
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
} else if (isMultiline && (c === "\n" || c === " " || c === " " || c === "\r")) {
|
|
356
|
+
ptr = skipVoid(str, ptr - 1, true);
|
|
357
|
+
if (str[ptr] !== "\n" && str[ptr] !== "\r") throw new TomlError("invalid escape: only line-ending whitespace may be escaped", {
|
|
358
|
+
toml: str,
|
|
359
|
+
ptr: tmp
|
|
360
|
+
});
|
|
361
|
+
ptr = skipVoid(str, ptr);
|
|
362
|
+
} else if (c in ESC_MAP) parsed += ESC_MAP[c];
|
|
363
|
+
else throw new TomlError("unrecognized escape sequence", {
|
|
364
|
+
toml: str,
|
|
365
|
+
ptr: tmp
|
|
366
|
+
});
|
|
367
|
+
sliceStart = ptr;
|
|
368
|
+
} else if (!isLiteral && c === "\\") {
|
|
369
|
+
tmp = ptr - 1;
|
|
370
|
+
isEscape = true;
|
|
371
|
+
parsed += str.slice(sliceStart, tmp);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return parsed + str.slice(sliceStart, endPtr - 1);
|
|
375
|
+
}
|
|
376
|
+
function parseValue(value, toml, ptr, integersAsBigInt) {
|
|
377
|
+
if (value === "true") return true;
|
|
378
|
+
if (value === "false") return false;
|
|
379
|
+
if (value === "-inf") return -Infinity;
|
|
380
|
+
if (value === "inf" || value === "+inf") return Infinity;
|
|
381
|
+
if (value === "nan" || value === "+nan" || value === "-nan") return NaN;
|
|
382
|
+
if (value === "-0") return integersAsBigInt ? 0n : 0;
|
|
383
|
+
let isInt = INT_REGEX.test(value);
|
|
384
|
+
if (isInt || FLOAT_REGEX.test(value)) {
|
|
385
|
+
if (LEADING_ZERO.test(value)) throw new TomlError("leading zeroes are not allowed", {
|
|
386
|
+
toml,
|
|
387
|
+
ptr
|
|
388
|
+
});
|
|
389
|
+
value = value.replace(/_/g, "");
|
|
390
|
+
let numeric = +value;
|
|
391
|
+
if (isNaN(numeric)) throw new TomlError("invalid number", {
|
|
392
|
+
toml,
|
|
393
|
+
ptr
|
|
394
|
+
});
|
|
395
|
+
if (isInt) {
|
|
396
|
+
if ((isInt = !Number.isSafeInteger(numeric)) && !integersAsBigInt) throw new TomlError("integer value cannot be represented losslessly", {
|
|
397
|
+
toml,
|
|
398
|
+
ptr
|
|
399
|
+
});
|
|
400
|
+
if (isInt || integersAsBigInt === true) numeric = BigInt(value);
|
|
401
|
+
}
|
|
402
|
+
return numeric;
|
|
403
|
+
}
|
|
404
|
+
const date = new TomlDate(value);
|
|
405
|
+
if (!date.isValid()) throw new TomlError("invalid value", {
|
|
406
|
+
toml,
|
|
407
|
+
ptr
|
|
408
|
+
});
|
|
409
|
+
return date;
|
|
410
|
+
}
|
|
411
|
+
//#endregion
|
|
412
|
+
//#region node_modules/.pnpm/smol-toml@1.6.1/node_modules/smol-toml/dist/extract.js
|
|
413
|
+
/*!
|
|
414
|
+
* Copyright (c) Squirrel Chat et al., All rights reserved.
|
|
415
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
416
|
+
*
|
|
417
|
+
* Redistribution and use in source and binary forms, with or without
|
|
418
|
+
* modification, are permitted provided that the following conditions are met:
|
|
419
|
+
*
|
|
420
|
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
|
421
|
+
* list of conditions and the following disclaimer.
|
|
422
|
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
423
|
+
* this list of conditions and the following disclaimer in the
|
|
424
|
+
* documentation and/or other materials provided with the distribution.
|
|
425
|
+
* 3. Neither the name of the copyright holder nor the names of its contributors
|
|
426
|
+
* may be used to endorse or promote products derived from this software without
|
|
427
|
+
* specific prior written permission.
|
|
428
|
+
*
|
|
429
|
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
430
|
+
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
431
|
+
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
432
|
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
433
|
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
434
|
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
435
|
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
436
|
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
437
|
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
438
|
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
439
|
+
*/
|
|
440
|
+
function sliceAndTrimEndOf(str, startPtr, endPtr) {
|
|
441
|
+
let value = str.slice(startPtr, endPtr);
|
|
442
|
+
let commentIdx = value.indexOf("#");
|
|
443
|
+
if (commentIdx > -1) {
|
|
444
|
+
skipComment(str, commentIdx);
|
|
445
|
+
value = value.slice(0, commentIdx);
|
|
446
|
+
}
|
|
447
|
+
return [value.trimEnd(), commentIdx];
|
|
448
|
+
}
|
|
449
|
+
function extractValue(str, ptr, end, depth, integersAsBigInt) {
|
|
450
|
+
if (depth === 0) throw new TomlError("document contains excessively nested structures. aborting.", {
|
|
451
|
+
toml: str,
|
|
452
|
+
ptr
|
|
453
|
+
});
|
|
454
|
+
let c = str[ptr];
|
|
455
|
+
if (c === "[" || c === "{") {
|
|
456
|
+
let [value, endPtr] = c === "[" ? parseArray(str, ptr, depth, integersAsBigInt) : parseInlineTable(str, ptr, depth, integersAsBigInt);
|
|
457
|
+
if (end) {
|
|
458
|
+
endPtr = skipVoid(str, endPtr);
|
|
459
|
+
if (str[endPtr] === ",") endPtr++;
|
|
460
|
+
else if (str[endPtr] !== end) throw new TomlError("expected comma or end of structure", {
|
|
461
|
+
toml: str,
|
|
462
|
+
ptr: endPtr
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
return [value, endPtr];
|
|
466
|
+
}
|
|
467
|
+
let endPtr;
|
|
468
|
+
if (c === "\"" || c === "'") {
|
|
469
|
+
endPtr = getStringEnd(str, ptr);
|
|
470
|
+
let parsed = parseString(str, ptr, endPtr);
|
|
471
|
+
if (end) {
|
|
472
|
+
endPtr = skipVoid(str, endPtr);
|
|
473
|
+
if (str[endPtr] && str[endPtr] !== "," && str[endPtr] !== end && str[endPtr] !== "\n" && str[endPtr] !== "\r") throw new TomlError("unexpected character encountered", {
|
|
474
|
+
toml: str,
|
|
475
|
+
ptr: endPtr
|
|
476
|
+
});
|
|
477
|
+
endPtr += +(str[endPtr] === ",");
|
|
478
|
+
}
|
|
479
|
+
return [parsed, endPtr];
|
|
480
|
+
}
|
|
481
|
+
endPtr = skipUntil(str, ptr, ",", end);
|
|
482
|
+
let slice = sliceAndTrimEndOf(str, ptr, endPtr - +(str[endPtr - 1] === ","));
|
|
483
|
+
if (!slice[0]) throw new TomlError("incomplete key-value declaration: no value specified", {
|
|
484
|
+
toml: str,
|
|
485
|
+
ptr
|
|
486
|
+
});
|
|
487
|
+
if (end && slice[1] > -1) {
|
|
488
|
+
endPtr = skipVoid(str, ptr + slice[1]);
|
|
489
|
+
endPtr += +(str[endPtr] === ",");
|
|
490
|
+
}
|
|
491
|
+
return [parseValue(slice[0], str, ptr, integersAsBigInt), endPtr];
|
|
492
|
+
}
|
|
493
|
+
//#endregion
|
|
494
|
+
//#region node_modules/.pnpm/smol-toml@1.6.1/node_modules/smol-toml/dist/struct.js
|
|
495
|
+
/*!
|
|
496
|
+
* Copyright (c) Squirrel Chat et al., All rights reserved.
|
|
497
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
498
|
+
*
|
|
499
|
+
* Redistribution and use in source and binary forms, with or without
|
|
500
|
+
* modification, are permitted provided that the following conditions are met:
|
|
501
|
+
*
|
|
502
|
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
|
503
|
+
* list of conditions and the following disclaimer.
|
|
504
|
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
505
|
+
* this list of conditions and the following disclaimer in the
|
|
506
|
+
* documentation and/or other materials provided with the distribution.
|
|
507
|
+
* 3. Neither the name of the copyright holder nor the names of its contributors
|
|
508
|
+
* may be used to endorse or promote products derived from this software without
|
|
509
|
+
* specific prior written permission.
|
|
510
|
+
*
|
|
511
|
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
512
|
+
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
513
|
+
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
514
|
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
515
|
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
516
|
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
517
|
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
518
|
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
519
|
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
520
|
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
521
|
+
*/
|
|
522
|
+
let KEY_PART_RE = /^[a-zA-Z0-9-_]+[ \t]*$/;
|
|
523
|
+
function parseKey(str, ptr, end = "=") {
|
|
524
|
+
let dot = ptr - 1;
|
|
525
|
+
let parsed = [];
|
|
526
|
+
let endPtr = str.indexOf(end, ptr);
|
|
527
|
+
if (endPtr < 0) throw new TomlError("incomplete key-value: cannot find end of key", {
|
|
528
|
+
toml: str,
|
|
529
|
+
ptr
|
|
530
|
+
});
|
|
531
|
+
do {
|
|
532
|
+
let c = str[ptr = ++dot];
|
|
533
|
+
if (c !== " " && c !== " ") if (c === "\"" || c === "'") {
|
|
534
|
+
if (c === str[ptr + 1] && c === str[ptr + 2]) throw new TomlError("multiline strings are not allowed in keys", {
|
|
535
|
+
toml: str,
|
|
536
|
+
ptr
|
|
537
|
+
});
|
|
538
|
+
let eos = getStringEnd(str, ptr);
|
|
539
|
+
if (eos < 0) throw new TomlError("unfinished string encountered", {
|
|
540
|
+
toml: str,
|
|
541
|
+
ptr
|
|
542
|
+
});
|
|
543
|
+
dot = str.indexOf(".", eos);
|
|
544
|
+
let strEnd = str.slice(eos, dot < 0 || dot > endPtr ? endPtr : dot);
|
|
545
|
+
let newLine = indexOfNewline(strEnd);
|
|
546
|
+
if (newLine > -1) throw new TomlError("newlines are not allowed in keys", {
|
|
547
|
+
toml: str,
|
|
548
|
+
ptr: ptr + dot + newLine
|
|
549
|
+
});
|
|
550
|
+
if (strEnd.trimStart()) throw new TomlError("found extra tokens after the string part", {
|
|
551
|
+
toml: str,
|
|
552
|
+
ptr: eos
|
|
553
|
+
});
|
|
554
|
+
if (endPtr < eos) {
|
|
555
|
+
endPtr = str.indexOf(end, eos);
|
|
556
|
+
if (endPtr < 0) throw new TomlError("incomplete key-value: cannot find end of key", {
|
|
557
|
+
toml: str,
|
|
558
|
+
ptr
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
parsed.push(parseString(str, ptr, eos));
|
|
562
|
+
} else {
|
|
563
|
+
dot = str.indexOf(".", ptr);
|
|
564
|
+
let part = str.slice(ptr, dot < 0 || dot > endPtr ? endPtr : dot);
|
|
565
|
+
if (!KEY_PART_RE.test(part)) throw new TomlError("only letter, numbers, dashes and underscores are allowed in keys", {
|
|
566
|
+
toml: str,
|
|
567
|
+
ptr
|
|
568
|
+
});
|
|
569
|
+
parsed.push(part.trimEnd());
|
|
570
|
+
}
|
|
571
|
+
} while (dot + 1 && dot < endPtr);
|
|
572
|
+
return [parsed, skipVoid(str, endPtr + 1, true, true)];
|
|
573
|
+
}
|
|
574
|
+
function parseInlineTable(str, ptr, depth, integersAsBigInt) {
|
|
575
|
+
let res = {};
|
|
576
|
+
let seen = /* @__PURE__ */ new Set();
|
|
577
|
+
let c;
|
|
578
|
+
ptr++;
|
|
579
|
+
while ((c = str[ptr++]) !== "}" && c) if (c === ",") throw new TomlError("expected value, found comma", {
|
|
580
|
+
toml: str,
|
|
581
|
+
ptr: ptr - 1
|
|
582
|
+
});
|
|
583
|
+
else if (c === "#") ptr = skipComment(str, ptr);
|
|
584
|
+
else if (c !== " " && c !== " " && c !== "\n" && c !== "\r") {
|
|
585
|
+
let k;
|
|
586
|
+
let t = res;
|
|
587
|
+
let hasOwn = false;
|
|
588
|
+
let [key, keyEndPtr] = parseKey(str, ptr - 1);
|
|
589
|
+
for (let i = 0; i < key.length; i++) {
|
|
590
|
+
if (i) t = hasOwn ? t[k] : t[k] = {};
|
|
591
|
+
k = key[i];
|
|
592
|
+
if ((hasOwn = Object.hasOwn(t, k)) && (typeof t[k] !== "object" || seen.has(t[k]))) throw new TomlError("trying to redefine an already defined value", {
|
|
593
|
+
toml: str,
|
|
594
|
+
ptr
|
|
595
|
+
});
|
|
596
|
+
if (!hasOwn && k === "__proto__") Object.defineProperty(t, k, {
|
|
597
|
+
enumerable: true,
|
|
598
|
+
configurable: true,
|
|
599
|
+
writable: true
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
if (hasOwn) throw new TomlError("trying to redefine an already defined value", {
|
|
603
|
+
toml: str,
|
|
604
|
+
ptr
|
|
605
|
+
});
|
|
606
|
+
let [value, valueEndPtr] = extractValue(str, keyEndPtr, "}", depth - 1, integersAsBigInt);
|
|
607
|
+
seen.add(value);
|
|
608
|
+
t[k] = value;
|
|
609
|
+
ptr = valueEndPtr;
|
|
610
|
+
}
|
|
611
|
+
if (!c) throw new TomlError("unfinished table encountered", {
|
|
612
|
+
toml: str,
|
|
613
|
+
ptr
|
|
614
|
+
});
|
|
615
|
+
return [res, ptr];
|
|
616
|
+
}
|
|
617
|
+
function parseArray(str, ptr, depth, integersAsBigInt) {
|
|
618
|
+
let res = [];
|
|
619
|
+
let c;
|
|
620
|
+
ptr++;
|
|
621
|
+
while ((c = str[ptr++]) !== "]" && c) if (c === ",") throw new TomlError("expected value, found comma", {
|
|
622
|
+
toml: str,
|
|
623
|
+
ptr: ptr - 1
|
|
624
|
+
});
|
|
625
|
+
else if (c === "#") ptr = skipComment(str, ptr);
|
|
626
|
+
else if (c !== " " && c !== " " && c !== "\n" && c !== "\r") {
|
|
627
|
+
let e = extractValue(str, ptr - 1, "]", depth - 1, integersAsBigInt);
|
|
628
|
+
res.push(e[0]);
|
|
629
|
+
ptr = e[1];
|
|
630
|
+
}
|
|
631
|
+
if (!c) throw new TomlError("unfinished array encountered", {
|
|
632
|
+
toml: str,
|
|
633
|
+
ptr
|
|
634
|
+
});
|
|
635
|
+
return [res, ptr];
|
|
636
|
+
}
|
|
637
|
+
//#endregion
|
|
638
|
+
//#region node_modules/.pnpm/smol-toml@1.6.1/node_modules/smol-toml/dist/parse.js
|
|
639
|
+
/*!
|
|
640
|
+
* Copyright (c) Squirrel Chat et al., All rights reserved.
|
|
641
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
642
|
+
*
|
|
643
|
+
* Redistribution and use in source and binary forms, with or without
|
|
644
|
+
* modification, are permitted provided that the following conditions are met:
|
|
645
|
+
*
|
|
646
|
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
|
647
|
+
* list of conditions and the following disclaimer.
|
|
648
|
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
649
|
+
* this list of conditions and the following disclaimer in the
|
|
650
|
+
* documentation and/or other materials provided with the distribution.
|
|
651
|
+
* 3. Neither the name of the copyright holder nor the names of its contributors
|
|
652
|
+
* may be used to endorse or promote products derived from this software without
|
|
653
|
+
* specific prior written permission.
|
|
654
|
+
*
|
|
655
|
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
656
|
+
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
657
|
+
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
658
|
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
659
|
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
660
|
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
661
|
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
662
|
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
663
|
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
664
|
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
665
|
+
*/
|
|
666
|
+
function peekTable(key, table, meta, type) {
|
|
667
|
+
let t = table;
|
|
668
|
+
let m = meta;
|
|
669
|
+
let k;
|
|
670
|
+
let hasOwn = false;
|
|
671
|
+
let state;
|
|
672
|
+
for (let i = 0; i < key.length; i++) {
|
|
673
|
+
if (i) {
|
|
674
|
+
t = hasOwn ? t[k] : t[k] = {};
|
|
675
|
+
m = (state = m[k]).c;
|
|
676
|
+
if (type === 0 && (state.t === 1 || state.t === 2)) return null;
|
|
677
|
+
if (state.t === 2) {
|
|
678
|
+
let l = t.length - 1;
|
|
679
|
+
t = t[l];
|
|
680
|
+
m = m[l].c;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
k = key[i];
|
|
684
|
+
if ((hasOwn = Object.hasOwn(t, k)) && m[k]?.t === 0 && m[k]?.d) return null;
|
|
685
|
+
if (!hasOwn) {
|
|
686
|
+
if (k === "__proto__") {
|
|
687
|
+
Object.defineProperty(t, k, {
|
|
688
|
+
enumerable: true,
|
|
689
|
+
configurable: true,
|
|
690
|
+
writable: true
|
|
691
|
+
});
|
|
692
|
+
Object.defineProperty(m, k, {
|
|
693
|
+
enumerable: true,
|
|
694
|
+
configurable: true,
|
|
695
|
+
writable: true
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
m[k] = {
|
|
699
|
+
t: i < key.length - 1 && type === 2 ? 3 : type,
|
|
700
|
+
d: false,
|
|
701
|
+
i: 0,
|
|
702
|
+
c: {}
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
state = m[k];
|
|
707
|
+
if (state.t !== type && !(type === 1 && state.t === 3)) return null;
|
|
708
|
+
if (type === 2) {
|
|
709
|
+
if (!state.d) {
|
|
710
|
+
state.d = true;
|
|
711
|
+
t[k] = [];
|
|
712
|
+
}
|
|
713
|
+
t[k].push(t = {});
|
|
714
|
+
state.c[state.i++] = state = {
|
|
715
|
+
t: 1,
|
|
716
|
+
d: false,
|
|
717
|
+
i: 0,
|
|
718
|
+
c: {}
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
if (state.d) return null;
|
|
722
|
+
state.d = true;
|
|
723
|
+
if (type === 1) t = hasOwn ? t[k] : t[k] = {};
|
|
724
|
+
else if (type === 0 && hasOwn) return null;
|
|
725
|
+
return [
|
|
726
|
+
k,
|
|
727
|
+
t,
|
|
728
|
+
state.c
|
|
729
|
+
];
|
|
730
|
+
}
|
|
731
|
+
function parse(toml, { maxDepth = 1e3, integersAsBigInt } = {}) {
|
|
732
|
+
let res = {};
|
|
733
|
+
let meta = {};
|
|
734
|
+
let tbl = res;
|
|
735
|
+
let m = meta;
|
|
736
|
+
for (let ptr = skipVoid(toml, 0); ptr < toml.length;) {
|
|
737
|
+
if (toml[ptr] === "[") {
|
|
738
|
+
let isTableArray = toml[++ptr] === "[";
|
|
739
|
+
let k = parseKey(toml, ptr += +isTableArray, "]");
|
|
740
|
+
if (isTableArray) {
|
|
741
|
+
if (toml[k[1] - 1] !== "]") throw new TomlError("expected end of table declaration", {
|
|
742
|
+
toml,
|
|
743
|
+
ptr: k[1] - 1
|
|
744
|
+
});
|
|
745
|
+
k[1]++;
|
|
746
|
+
}
|
|
747
|
+
let p = peekTable(k[0], res, meta, isTableArray ? 2 : 1);
|
|
748
|
+
if (!p) throw new TomlError("trying to redefine an already defined table or value", {
|
|
749
|
+
toml,
|
|
750
|
+
ptr
|
|
751
|
+
});
|
|
752
|
+
m = p[2];
|
|
753
|
+
tbl = p[1];
|
|
754
|
+
ptr = k[1];
|
|
755
|
+
} else {
|
|
756
|
+
let k = parseKey(toml, ptr);
|
|
757
|
+
let p = peekTable(k[0], tbl, m, 0);
|
|
758
|
+
if (!p) throw new TomlError("trying to redefine an already defined table or value", {
|
|
759
|
+
toml,
|
|
760
|
+
ptr
|
|
761
|
+
});
|
|
762
|
+
let v = extractValue(toml, k[1], void 0, maxDepth, integersAsBigInt);
|
|
763
|
+
p[1][p[0]] = v[0];
|
|
764
|
+
ptr = v[1];
|
|
765
|
+
}
|
|
766
|
+
ptr = skipVoid(toml, ptr, true);
|
|
767
|
+
if (toml[ptr] && toml[ptr] !== "\n" && toml[ptr] !== "\r") throw new TomlError("each key-value declaration must be followed by an end-of-line", {
|
|
768
|
+
toml,
|
|
769
|
+
ptr
|
|
770
|
+
});
|
|
771
|
+
ptr = skipVoid(toml, ptr);
|
|
772
|
+
}
|
|
773
|
+
return res;
|
|
774
|
+
}
|
|
775
|
+
//#endregion
|
|
776
|
+
//#region src/fmt.ts
|
|
777
|
+
const DIM$1 = "\x1B[2m";
|
|
778
|
+
const RESET$1 = "\x1B[0m";
|
|
779
|
+
const PREFIX = `[36mbx${RESET$1}`;
|
|
780
|
+
const fmt = {
|
|
781
|
+
info: (msg) => `🔒 ${PREFIX} · ${msg}`,
|
|
782
|
+
warn: (msg) => `⚠️ ${PREFIX} · ${msg}`,
|
|
783
|
+
error: (msg) => `🚫 ${PREFIX} · ${msg}`,
|
|
784
|
+
detail: (msg) => ` ${DIM$1}${msg}${RESET$1}`
|
|
785
|
+
};
|
|
786
|
+
//#endregion
|
|
787
|
+
//#region src/config.ts
|
|
788
|
+
/** Built-in app definitions — always available, can be overridden via config */
|
|
789
|
+
const BUILTIN_APPS = {
|
|
790
|
+
code: {
|
|
791
|
+
bundle: "com.microsoft.VSCode",
|
|
792
|
+
binary: "Contents/MacOS/Electron",
|
|
793
|
+
fallback: "/Applications/Visual Studio Code.app/Contents/MacOS/Electron",
|
|
794
|
+
args: ["--no-sandbox"]
|
|
795
|
+
},
|
|
796
|
+
xcode: {
|
|
797
|
+
bundle: "com.apple.dt.Xcode",
|
|
798
|
+
binary: "Contents/MacOS/Xcode",
|
|
799
|
+
fallback: "/Applications/Xcode.app/Contents/MacOS/Xcode"
|
|
800
|
+
}
|
|
801
|
+
};
|
|
802
|
+
/** Shell-only built-in modes that are not app definitions */
|
|
803
|
+
const BUILTIN_MODES = [
|
|
804
|
+
"term",
|
|
805
|
+
"claude",
|
|
806
|
+
"exec"
|
|
807
|
+
];
|
|
808
|
+
/**
|
|
809
|
+
* Load and parse ~/.bxconfig.toml. Returns empty apps if file missing or invalid.
|
|
810
|
+
*/
|
|
811
|
+
function loadConfig(home) {
|
|
812
|
+
const configPath = join(home, ".bxconfig.toml");
|
|
813
|
+
if (!existsSync(configPath)) return { apps: {} };
|
|
814
|
+
try {
|
|
815
|
+
const doc = parse(readFileSync(configPath, "utf-8"));
|
|
816
|
+
const apps = {};
|
|
817
|
+
if (doc.apps && typeof doc.apps === "object") for (const [name, def] of Object.entries(doc.apps)) apps[name] = {
|
|
818
|
+
bundle: typeof def.bundle === "string" ? def.bundle : void 0,
|
|
819
|
+
binary: typeof def.binary === "string" ? def.binary : void 0,
|
|
820
|
+
path: typeof def.path === "string" ? def.path : void 0,
|
|
821
|
+
fallback: typeof def.fallback === "string" ? def.fallback : void 0,
|
|
822
|
+
args: Array.isArray(def.args) ? def.args.filter((a) => typeof a === "string") : void 0,
|
|
823
|
+
passWorkdirs: typeof def.passWorkdirs === "boolean" ? def.passWorkdirs : void 0,
|
|
824
|
+
workdirs: Array.isArray(def.workdirs) ? def.workdirs.filter((a) => typeof a === "string") : void 0
|
|
825
|
+
};
|
|
826
|
+
return { apps };
|
|
827
|
+
} catch (err) {
|
|
828
|
+
console.error(`\n${fmt.warn(`failed to parse ${configPath}: ${err instanceof Error ? err.message : err}`)}`);
|
|
829
|
+
return { apps: {} };
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Merge built-in apps with user config (config wins on conflict).
|
|
834
|
+
*/
|
|
835
|
+
function getAvailableApps(config) {
|
|
836
|
+
const merged = {};
|
|
837
|
+
for (const [name, def] of Object.entries(BUILTIN_APPS)) merged[name] = { ...def };
|
|
838
|
+
for (const [name, def] of Object.entries(config.apps)) if (merged[name]) merged[name] = {
|
|
839
|
+
...merged[name],
|
|
840
|
+
...stripUndefined(def)
|
|
841
|
+
};
|
|
842
|
+
else merged[name] = def;
|
|
843
|
+
return merged;
|
|
844
|
+
}
|
|
845
|
+
function stripUndefined(obj) {
|
|
846
|
+
const result = {};
|
|
847
|
+
for (const [k, v] of Object.entries(obj)) if (v !== void 0) result[k] = v;
|
|
848
|
+
return result;
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Get all valid mode names (builtin modes + app names).
|
|
852
|
+
*/
|
|
853
|
+
function getValidModes(apps) {
|
|
854
|
+
return [...BUILTIN_MODES, ...Object.keys(apps)];
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Resolve an AppDefinition to an executable path.
|
|
858
|
+
* Resolution chain: path (explicit) → mdfind + binary → fallback
|
|
859
|
+
*/
|
|
860
|
+
function resolveAppPath(app) {
|
|
861
|
+
if (app.path) {
|
|
862
|
+
if (existsSync(app.path)) return app.path;
|
|
863
|
+
console.error(`\n${fmt.warn(`configured path not found: ${app.path}`)}`);
|
|
864
|
+
}
|
|
865
|
+
if (app.bundle) try {
|
|
866
|
+
const appPath = execFileSync("mdfind", [`kMDItemCFBundleIdentifier == '${app.bundle.replace(/'/g, "'\\''")}'`], {
|
|
867
|
+
encoding: "utf-8",
|
|
868
|
+
timeout: 5e3
|
|
869
|
+
}).trim().split("\n")[0];
|
|
870
|
+
if (appPath) if (app.binary) {
|
|
871
|
+
const fullPath = join(appPath, app.binary);
|
|
872
|
+
if (existsSync(fullPath)) return fullPath;
|
|
873
|
+
} else return appPath;
|
|
874
|
+
} catch {}
|
|
875
|
+
if (app.fallback && existsSync(app.fallback)) return app.fallback;
|
|
876
|
+
return null;
|
|
877
|
+
}
|
|
878
|
+
//#endregion
|
|
8
879
|
//#region src/guards.ts
|
|
9
880
|
/**
|
|
10
881
|
* Abort if we're already inside a bx sandbox (env var set by us).
|
|
11
882
|
*/
|
|
12
883
|
function checkOwnSandbox() {
|
|
13
|
-
if (process.env.CODEBOX_SANDBOX === "1") {
|
|
14
|
-
console.error("
|
|
15
|
-
console.error("
|
|
16
|
-
process.exit(1);
|
|
884
|
+
if (process$1.env.CODEBOX_SANDBOX === "1") {
|
|
885
|
+
console.error(`\n${fmt.error("already running inside a bx sandbox")}`);
|
|
886
|
+
console.error(fmt.detail("nesting sandbox-exec causes silent failures\n"));
|
|
887
|
+
process$1.exit(1);
|
|
17
888
|
}
|
|
18
889
|
}
|
|
19
890
|
/**
|
|
20
891
|
* Warn if launched from inside a VSCode terminal.
|
|
21
892
|
*/
|
|
22
893
|
function checkVSCodeTerminal() {
|
|
23
|
-
if (process.env.VSCODE_PID) {
|
|
24
|
-
console.error("
|
|
25
|
-
console.error("
|
|
26
|
-
console.error("
|
|
894
|
+
if (process$1.env.VSCODE_PID) {
|
|
895
|
+
console.error(`\n${fmt.warn("running from inside a VSCode terminal")}`);
|
|
896
|
+
console.error(fmt.detail("this will launch a *new* instance in a sandbox"));
|
|
897
|
+
console.error(fmt.detail("the current VSCode instance will NOT be sandboxed"));
|
|
27
898
|
}
|
|
28
899
|
}
|
|
29
900
|
/**
|
|
@@ -32,18 +903,50 @@ function checkVSCodeTerminal() {
|
|
|
32
903
|
function checkWorkDirs(workDirs, home) {
|
|
33
904
|
for (const dir of workDirs) {
|
|
34
905
|
if (dir === home) {
|
|
35
|
-
console.error("
|
|
36
|
-
console.error("
|
|
37
|
-
process.exit(1);
|
|
906
|
+
console.error(`\n${fmt.error("working directory cannot be $HOME itself")}`);
|
|
907
|
+
console.error(fmt.detail("sandboxing your entire home directory is not supported\n"));
|
|
908
|
+
process$1.exit(1);
|
|
38
909
|
}
|
|
39
910
|
if (!dir.startsWith(home + "/")) {
|
|
40
|
-
console.error(`
|
|
41
|
-
console.error("
|
|
42
|
-
process.exit(1);
|
|
911
|
+
console.error(`\n${fmt.error(`working directory is outside $HOME: ${dir}`)}`);
|
|
912
|
+
console.error(fmt.detail("only directories inside $HOME are supported\n"));
|
|
913
|
+
process$1.exit(1);
|
|
43
914
|
}
|
|
44
915
|
}
|
|
45
916
|
}
|
|
46
917
|
/**
|
|
918
|
+
* Warn if the target app is already running — the new workspace will open
|
|
919
|
+
* in the existing (unsandboxed) instance, bypassing our sandbox profile.
|
|
920
|
+
*/
|
|
921
|
+
async function checkAppAlreadyRunning(mode, apps) {
|
|
922
|
+
if (BUILTIN_MODES.includes(mode)) return;
|
|
923
|
+
const app = apps[mode];
|
|
924
|
+
if (!app?.bundle) return;
|
|
925
|
+
let running = false;
|
|
926
|
+
try {
|
|
927
|
+
running = execFileSync("lsappinfo", ["list"], {
|
|
928
|
+
encoding: "utf-8",
|
|
929
|
+
timeout: 3e3
|
|
930
|
+
}).includes(`bundleID="${app.bundle}"`);
|
|
931
|
+
} catch {
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
if (!running) return;
|
|
935
|
+
console.error(`\n${fmt.warn(`"${mode}" is already running`)}`);
|
|
936
|
+
console.error(fmt.detail("the workspace will open in the EXISTING instance — sandbox will NOT apply"));
|
|
937
|
+
if (mode === "code") console.error(fmt.detail("quit the app first, or use --profile-sandbox for an isolated instance"));
|
|
938
|
+
else console.error(fmt.detail("quit the app first to ensure sandbox protection"));
|
|
939
|
+
const rl = createInterface({
|
|
940
|
+
input: process$1.stdin,
|
|
941
|
+
output: process$1.stderr
|
|
942
|
+
});
|
|
943
|
+
const answer = await new Promise((res) => {
|
|
944
|
+
rl.question(` continue without sandbox? [y/N] `, res);
|
|
945
|
+
});
|
|
946
|
+
rl.close();
|
|
947
|
+
if (!answer.match(/^y(es)?$/i)) process$1.exit(0);
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
47
950
|
* Detect if we're inside an unknown sandbox by probing well-known
|
|
48
951
|
* directories that exist on every Mac but would be blocked.
|
|
49
952
|
*/
|
|
@@ -53,52 +956,57 @@ function checkExternalSandbox() {
|
|
|
53
956
|
"Desktop",
|
|
54
957
|
"Downloads"
|
|
55
958
|
]) {
|
|
56
|
-
const target = join(process.env.HOME, dir);
|
|
959
|
+
const target = join(process$1.env.HOME, dir);
|
|
57
960
|
try {
|
|
58
961
|
accessSync(target, constants.R_OK);
|
|
59
962
|
} catch (e) {
|
|
60
963
|
if (e.code === "EPERM") {
|
|
61
|
-
console.error("
|
|
62
|
-
console.error("
|
|
63
|
-
process.exit(1);
|
|
964
|
+
console.error(`\n${fmt.error("already running inside a sandbox")}`);
|
|
965
|
+
console.error(fmt.detail("nesting sandbox-exec may cause silent failures\n"));
|
|
966
|
+
process$1.exit(1);
|
|
64
967
|
}
|
|
65
968
|
}
|
|
66
969
|
}
|
|
67
970
|
}
|
|
68
971
|
//#endregion
|
|
69
972
|
//#region src/args.ts
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
function parseArgs() {
|
|
77
|
-
const rawArgs = process.argv.slice(2);
|
|
973
|
+
/**
|
|
974
|
+
* Parse CLI arguments. `validModes` is the list of recognized mode names
|
|
975
|
+
* (builtin modes + app names from config).
|
|
976
|
+
*/
|
|
977
|
+
function parseArgs(validModes) {
|
|
978
|
+
const rawArgs = process$1.argv.slice(2);
|
|
78
979
|
const verbose = rawArgs.includes("--verbose");
|
|
980
|
+
const dry = rawArgs.includes("--dry");
|
|
79
981
|
const profileSandbox = rawArgs.includes("--profile-sandbox");
|
|
80
982
|
const positional = rawArgs.filter((a) => !a.startsWith("--"));
|
|
81
983
|
const doubleDashIdx = rawArgs.indexOf("--");
|
|
82
|
-
const
|
|
984
|
+
const appArgs = doubleDashIdx >= 0 ? rawArgs.slice(doubleDashIdx + 1) : [];
|
|
83
985
|
const beforeDash = doubleDashIdx >= 0 ? rawArgs.slice(0, doubleDashIdx).filter((a) => !a.startsWith("--")) : positional;
|
|
84
986
|
let mode = "code";
|
|
85
987
|
let workArgs;
|
|
86
|
-
|
|
988
|
+
let implicitWorkdirs = false;
|
|
989
|
+
if (beforeDash.length > 0 && validModes.includes(beforeDash[0])) {
|
|
87
990
|
mode = beforeDash[0];
|
|
88
991
|
workArgs = beforeDash.slice(1);
|
|
89
992
|
} else workArgs = beforeDash;
|
|
90
|
-
if (workArgs.length === 0)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
993
|
+
if (workArgs.length === 0) {
|
|
994
|
+
workArgs = ["."];
|
|
995
|
+
implicitWorkdirs = true;
|
|
996
|
+
}
|
|
997
|
+
if (mode === "exec" && appArgs.length === 0) {
|
|
998
|
+
console.error(`\n${fmt.error("exec mode requires a command after \"--\"")}`);
|
|
999
|
+
console.error(fmt.detail("usage: bx exec [workdir...] -- command [args...]\n"));
|
|
1000
|
+
process$1.exit(1);
|
|
95
1001
|
}
|
|
96
1002
|
return {
|
|
97
1003
|
mode,
|
|
98
1004
|
workArgs,
|
|
99
1005
|
verbose,
|
|
1006
|
+
dry,
|
|
100
1007
|
profileSandbox,
|
|
101
|
-
|
|
1008
|
+
appArgs,
|
|
1009
|
+
implicit: implicitWorkdirs
|
|
102
1010
|
};
|
|
103
1011
|
}
|
|
104
1012
|
//#endregion
|
|
@@ -113,34 +1021,29 @@ const PROTECTED_DOTDIRS = [
|
|
|
113
1021
|
".gradle",
|
|
114
1022
|
".gem"
|
|
115
1023
|
];
|
|
116
|
-
/**
|
|
117
|
-
* Parse a config file with one entry per line (supports # comments).
|
|
118
|
-
*/
|
|
119
1024
|
function parseLines(filePath) {
|
|
120
1025
|
if (!existsSync(filePath)) return [];
|
|
121
1026
|
return readFileSync(filePath, "utf-8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
|
|
122
1027
|
}
|
|
123
1028
|
/**
|
|
124
|
-
* Convert a .bxignore line to a glob pattern
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
1029
|
+
* Convert a .bxignore line to a glob pattern (.gitignore semantics):
|
|
1030
|
+
*
|
|
1031
|
+
* "/foo" → anchored to base dir → "foo"
|
|
1032
|
+
* "foo/bar" → relative path, use as-is → "foo/bar"
|
|
1033
|
+
* "foo" → no slash, match recursively → "** /foo"
|
|
1034
|
+
* "secrets/" → trailing slash = dir marker, still recursive
|
|
129
1035
|
*/
|
|
130
1036
|
function toGlobPattern(line) {
|
|
131
1037
|
if (line.startsWith("/")) return line.slice(1);
|
|
132
1038
|
if ((line.endsWith("/") ? line.slice(0, -1) : line).includes("/")) return line;
|
|
133
1039
|
return `**/${line}`;
|
|
134
1040
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
1041
|
+
function resolveGlobMatches(pattern, baseDir) {
|
|
1042
|
+
return globSync(toGlobPattern(pattern), { cwd: baseDir }).map((match) => resolve(baseDir, match));
|
|
1043
|
+
}
|
|
138
1044
|
function applyIgnoreFile(filePath, baseDir, ignored) {
|
|
139
|
-
for (const line of parseLines(filePath))
|
|
1045
|
+
for (const line of parseLines(filePath)) ignored.push(...resolveGlobMatches(line, baseDir));
|
|
140
1046
|
}
|
|
141
|
-
/**
|
|
142
|
-
* Recursively find and apply .bxignore files in a directory tree.
|
|
143
|
-
*/
|
|
144
1047
|
function collectIgnoreFilesRecursive(dir, ignored) {
|
|
145
1048
|
const ignoreFile = join(dir, ".bxignore");
|
|
146
1049
|
if (existsSync(ignoreFile)) applyIgnoreFile(ignoreFile, dir, ignored);
|
|
@@ -158,11 +1061,7 @@ function collectIgnoreFilesRecursive(dir, ignored) {
|
|
|
158
1061
|
} catch {}
|
|
159
1062
|
}
|
|
160
1063
|
}
|
|
161
|
-
|
|
162
|
-
* Parse ~/.bxignore for RW:/RO: prefixed lines and return allowed directories.
|
|
163
|
-
* Lines without prefix are ignored here (handled by collectIgnoredPaths).
|
|
164
|
-
* Also checks for deprecated ~/.bxallow and migrates its entries.
|
|
165
|
-
*/
|
|
1064
|
+
const ACCESS_PREFIX_RE = /^(RW|RO):(.+)$/i;
|
|
166
1065
|
function parseHomeConfig(home, workDirs) {
|
|
167
1066
|
const allowed = new Set(workDirs);
|
|
168
1067
|
const readOnly = /* @__PURE__ */ new Set();
|
|
@@ -175,17 +1074,12 @@ function parseHomeConfig(home, workDirs) {
|
|
|
175
1074
|
}
|
|
176
1075
|
}
|
|
177
1076
|
for (const line of parseLines(join(home, ".bxignore"))) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
prefix = match[1].toUpperCase();
|
|
183
|
-
path = match[2].trim();
|
|
184
|
-
}
|
|
185
|
-
if (!prefix) continue;
|
|
186
|
-
const absolute = resolve(home, path);
|
|
1077
|
+
const match = line.match(ACCESS_PREFIX_RE);
|
|
1078
|
+
if (!match) continue;
|
|
1079
|
+
const [, prefix, rawPath] = match;
|
|
1080
|
+
const absolute = resolve(home, rawPath.trim());
|
|
187
1081
|
if (!existsSync(absolute) || !statSync(absolute).isDirectory()) continue;
|
|
188
|
-
if (prefix === "RW") allowed.add(absolute);
|
|
1082
|
+
if (prefix.toUpperCase() === "RW") allowed.add(absolute);
|
|
189
1083
|
else readOnly.add(absolute);
|
|
190
1084
|
}
|
|
191
1085
|
return {
|
|
@@ -193,20 +1087,29 @@ function parseHomeConfig(home, workDirs) {
|
|
|
193
1087
|
readOnly
|
|
194
1088
|
};
|
|
195
1089
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
1090
|
+
function isAllowedOrAncestor(fullPath, allowedDirs) {
|
|
1091
|
+
if (allowedDirs.has(fullPath)) return "allowed";
|
|
1092
|
+
const prefix = fullPath + "/";
|
|
1093
|
+
for (const dir of allowedDirs) if (dir.startsWith(prefix)) return "ancestor";
|
|
1094
|
+
return "none";
|
|
1095
|
+
}
|
|
200
1096
|
function collectBlockedDirs(parentDir, home, scriptDir, allowedDirs) {
|
|
201
1097
|
const blocked = [];
|
|
202
1098
|
for (const name of readdirSync(parentDir)) {
|
|
203
1099
|
if (name.startsWith(".")) continue;
|
|
204
1100
|
const fullPath = join(parentDir, name);
|
|
205
|
-
|
|
1101
|
+
let isDir;
|
|
1102
|
+
try {
|
|
1103
|
+
isDir = statSync(fullPath).isDirectory();
|
|
1104
|
+
} catch {
|
|
1105
|
+
continue;
|
|
1106
|
+
}
|
|
1107
|
+
if (!isDir) continue;
|
|
206
1108
|
if (parentDir === home && name === "Library") continue;
|
|
207
1109
|
if (scriptDir.startsWith(fullPath + "/") || scriptDir === fullPath) continue;
|
|
208
|
-
|
|
209
|
-
if (
|
|
1110
|
+
const status = isAllowedOrAncestor(fullPath, allowedDirs);
|
|
1111
|
+
if (status === "allowed") continue;
|
|
1112
|
+
if (status === "ancestor") {
|
|
210
1113
|
blocked.push(...collectBlockedDirs(fullPath, home, scriptDir, allowedDirs));
|
|
211
1114
|
continue;
|
|
212
1115
|
}
|
|
@@ -214,78 +1117,92 @@ function collectBlockedDirs(parentDir, home, scriptDir, allowedDirs) {
|
|
|
214
1117
|
}
|
|
215
1118
|
return blocked;
|
|
216
1119
|
}
|
|
217
|
-
/**
|
|
218
|
-
* Collect paths to deny from .bxignore files and built-in protected dotdirs.
|
|
219
|
-
* Searches ~/.bxignore (skipping RW:/RO: lines) and recursively through all workdirs.
|
|
220
|
-
*/
|
|
221
1120
|
function collectIgnoredPaths(home, workDirs) {
|
|
222
1121
|
const ignored = PROTECTED_DOTDIRS.map((d) => join(home, d));
|
|
223
1122
|
const globalIgnore = join(home, ".bxignore");
|
|
224
1123
|
if (existsSync(globalIgnore)) {
|
|
225
|
-
const denyLines = parseLines(globalIgnore).filter((l) => !
|
|
226
|
-
for (const line of denyLines)
|
|
1124
|
+
const denyLines = parseLines(globalIgnore).filter((l) => !ACCESS_PREFIX_RE.test(l));
|
|
1125
|
+
for (const line of denyLines) ignored.push(...resolveGlobMatches(line, home));
|
|
227
1126
|
}
|
|
228
1127
|
for (const workDir of workDirs) collectIgnoreFilesRecursive(workDir, ignored);
|
|
229
1128
|
return ignored;
|
|
230
1129
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
1130
|
+
function sbplEscape(path) {
|
|
1131
|
+
return path.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
1132
|
+
}
|
|
1133
|
+
function sbplSubpath(path) {
|
|
1134
|
+
return ` (subpath "${sbplEscape(path)}")`;
|
|
1135
|
+
}
|
|
1136
|
+
function sbplLiteral(path) {
|
|
1137
|
+
return ` (literal "${sbplEscape(path)}")`;
|
|
1138
|
+
}
|
|
1139
|
+
function sbplPathRule(path) {
|
|
1140
|
+
let isDir = false;
|
|
1141
|
+
try {
|
|
1142
|
+
isDir = existsSync(path) && statSync(path).isDirectory();
|
|
1143
|
+
} catch {}
|
|
1144
|
+
return isDir ? sbplSubpath(path) : sbplLiteral(path);
|
|
1145
|
+
}
|
|
1146
|
+
function sbplDenyBlock(comment, verb, rules) {
|
|
1147
|
+
if (rules.length === 0) return "";
|
|
1148
|
+
return `\n; ${comment}\n(deny ${verb}\n${rules.join("\n")}\n)\n`;
|
|
1149
|
+
}
|
|
234
1150
|
function generateProfile(workDirs, blockedDirs, ignoredPaths, readOnlyDirs = []) {
|
|
235
|
-
const
|
|
236
|
-
const ignoredRules =
|
|
237
|
-
|
|
238
|
-
}).join("\n")}\n)\n` : "";
|
|
239
|
-
const readOnlyRules = readOnlyDirs.length > 0 ? `\n; Read-only directories\n(deny file-write*\n${readOnlyDirs.map((dir) => ` (subpath "${dir}")`).join("\n")}\n)\n` : "";
|
|
1151
|
+
const blockedRules = sbplDenyBlock("Blocked directories (auto-generated from $HOME contents)", "file*", blockedDirs.map(sbplSubpath));
|
|
1152
|
+
const ignoredRules = sbplDenyBlock("Hidden paths from .bxignore", "file*", ignoredPaths.map(sbplPathRule));
|
|
1153
|
+
const readOnlyRules = sbplDenyBlock("Read-only directories", "file-write*", readOnlyDirs.map(sbplSubpath));
|
|
240
1154
|
return `; Auto-generated sandbox profile
|
|
241
1155
|
; Working directories: ${workDirs.join(", ")}
|
|
242
1156
|
|
|
243
1157
|
(version 1)
|
|
244
1158
|
(allow default)
|
|
245
|
-
|
|
246
|
-
; Blocked directories (auto-generated from $HOME contents)
|
|
247
|
-
(deny file*
|
|
248
|
-
${denyRules}
|
|
249
|
-
)
|
|
250
|
-
${ignoredRules}${readOnlyRules}
|
|
1159
|
+
${blockedRules}${ignoredRules}${readOnlyRules}
|
|
251
1160
|
`;
|
|
252
1161
|
}
|
|
253
1162
|
//#endregion
|
|
254
1163
|
//#region src/modes.ts
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
1164
|
+
function isBuiltinMode(mode) {
|
|
1165
|
+
return BUILTIN_MODES.includes(mode);
|
|
1166
|
+
}
|
|
1167
|
+
function shouldPassWorkdirs(app, mode) {
|
|
1168
|
+
if (typeof app.passWorkdirs === "boolean") return app.passWorkdirs;
|
|
1169
|
+
return mode !== "xcode";
|
|
1170
|
+
}
|
|
1171
|
+
function appBundleFromPath(path) {
|
|
1172
|
+
if (path.endsWith(".app")) return path;
|
|
1173
|
+
const idx = path.indexOf(".app/");
|
|
1174
|
+
if (idx < 0) return null;
|
|
1175
|
+
return path.slice(0, idx + 4);
|
|
1176
|
+
}
|
|
1177
|
+
function executableFromBundle(bundlePath, app) {
|
|
1178
|
+
if (!bundlePath.endsWith(".app")) return bundlePath;
|
|
1179
|
+
if (app.binary) return join(bundlePath, app.binary);
|
|
1180
|
+
return join(bundlePath, "Contents", "MacOS", basename(bundlePath, ".app"));
|
|
1181
|
+
}
|
|
1182
|
+
const SANDBOX_KEY = "com.apple.security.app-sandbox";
|
|
1183
|
+
function hasAppSandboxEntitlement(entitlements) {
|
|
1184
|
+
if (new RegExp(`<key>\\s*${SANDBOX_KEY.replace(/\./g, "\\.")}\\s*</key>\\s*<true\\s*/>`, "i").test(entitlements)) return true;
|
|
1185
|
+
if (new RegExp(`<key>\\s*${SANDBOX_KEY.replace(/\./g, "\\.")}\\s*</key>\\s*<false\\s*/>`, "i").test(entitlements)) return false;
|
|
1186
|
+
return new RegExp(`${SANDBOX_KEY.replace(/\./g, "\\.")}\\s*[=:]\\s*(1|true)`, "i").test(entitlements);
|
|
1187
|
+
}
|
|
259
1188
|
function setupVSCodeProfile(home) {
|
|
260
1189
|
const dataDir = join(home, ".vscode-sandbox");
|
|
261
1190
|
const globalExt = join(home, ".vscode", "extensions");
|
|
262
1191
|
const localExt = join(dataDir, "extensions");
|
|
263
1192
|
mkdirSync(dataDir, { recursive: true });
|
|
264
1193
|
if (!existsSync(localExt) && existsSync(globalExt)) {
|
|
265
|
-
console.error("
|
|
1194
|
+
console.error(fmt.detail("copying extensions from global install..."));
|
|
266
1195
|
cpSync(globalExt, localExt, { recursive: true });
|
|
267
1196
|
}
|
|
268
1197
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
1198
|
+
function buildCommand(mode, workDirs, home, profileSandbox, appArgs, apps) {
|
|
1199
|
+
if (isBuiltinMode(mode)) return buildBuiltinCommand(mode, appArgs);
|
|
1200
|
+
return buildAppCommand(mode, workDirs, home, profileSandbox, appArgs, apps);
|
|
1201
|
+
}
|
|
1202
|
+
function buildBuiltinCommand(mode, appArgs) {
|
|
273
1203
|
switch (mode) {
|
|
274
|
-
case "code": {
|
|
275
|
-
const dataDir = join(home, ".vscode-sandbox");
|
|
276
|
-
const args = ["--no-sandbox"];
|
|
277
|
-
if (profileSandbox) {
|
|
278
|
-
args.push("--user-data-dir", join(dataDir, "data"));
|
|
279
|
-
args.push("--extensions-dir", join(dataDir, "extensions"));
|
|
280
|
-
}
|
|
281
|
-
args.push(...workDirs);
|
|
282
|
-
return {
|
|
283
|
-
bin: VSCODE_APP,
|
|
284
|
-
args
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
1204
|
case "term": return {
|
|
288
|
-
bin: process.env.SHELL ?? "/bin/zsh",
|
|
1205
|
+
bin: process$1.env.SHELL ?? "/bin/zsh",
|
|
289
1206
|
args: ["-l"]
|
|
290
1207
|
};
|
|
291
1208
|
case "claude": return {
|
|
@@ -293,89 +1210,317 @@ function buildCommand(mode, workDirs, home, profileSandbox, execCmd) {
|
|
|
293
1210
|
args: []
|
|
294
1211
|
};
|
|
295
1212
|
case "exec": return {
|
|
296
|
-
bin:
|
|
297
|
-
args:
|
|
1213
|
+
bin: appArgs[0],
|
|
1214
|
+
args: appArgs.slice(1)
|
|
298
1215
|
};
|
|
299
1216
|
}
|
|
300
1217
|
}
|
|
1218
|
+
function buildAppCommand(mode, workDirs, home, profileSandbox, appArgs, apps) {
|
|
1219
|
+
const app = apps[mode];
|
|
1220
|
+
if (!app) {
|
|
1221
|
+
console.error(`\n${fmt.error(`unknown mode "${mode}"`)}\n`);
|
|
1222
|
+
process$1.exit(1);
|
|
1223
|
+
}
|
|
1224
|
+
const resolvedPath = resolveAppPath(app);
|
|
1225
|
+
if (!resolvedPath) {
|
|
1226
|
+
console.error(`\n${fmt.error(`could not find application for "${mode}"`)}`);
|
|
1227
|
+
if (app.bundle) console.error(fmt.detail(`bundle: ${app.bundle}`));
|
|
1228
|
+
console.error(fmt.detail("hint: set an explicit path in ~/.bxconfig.toml\n"));
|
|
1229
|
+
process$1.exit(1);
|
|
1230
|
+
}
|
|
1231
|
+
const bin = executableFromBundle(resolvedPath, app);
|
|
1232
|
+
const args = [];
|
|
1233
|
+
if (mode === "code" && profileSandbox) {
|
|
1234
|
+
const dataDir = join(home, ".vscode-sandbox");
|
|
1235
|
+
args.push("--user-data-dir", join(dataDir, "data"));
|
|
1236
|
+
args.push("--extensions-dir", join(dataDir, "extensions"));
|
|
1237
|
+
}
|
|
1238
|
+
if (app.args) args.push(...app.args);
|
|
1239
|
+
if (appArgs.length > 0) args.push(...appArgs);
|
|
1240
|
+
if (shouldPassWorkdirs(app, mode)) args.push(...workDirs);
|
|
1241
|
+
return {
|
|
1242
|
+
bin,
|
|
1243
|
+
args
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
function getActivationCommand(mode, apps) {
|
|
1247
|
+
if (isBuiltinMode(mode)) return null;
|
|
1248
|
+
const app = apps[mode];
|
|
1249
|
+
if (!app) return null;
|
|
1250
|
+
if (app.bundle) return {
|
|
1251
|
+
bin: "/usr/bin/open",
|
|
1252
|
+
args: ["-b", app.bundle]
|
|
1253
|
+
};
|
|
1254
|
+
const resolved = resolveAppPath(app);
|
|
1255
|
+
if (!resolved) return null;
|
|
1256
|
+
const bundlePath = appBundleFromPath(resolved);
|
|
1257
|
+
if (!bundlePath) return null;
|
|
1258
|
+
return {
|
|
1259
|
+
bin: "/usr/bin/open",
|
|
1260
|
+
args: ["-a", bundlePath]
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1263
|
+
function getNestedSandboxWarning(mode, apps) {
|
|
1264
|
+
if (isBuiltinMode(mode)) return null;
|
|
1265
|
+
const app = apps[mode];
|
|
1266
|
+
if (!app) return null;
|
|
1267
|
+
const resolvedPath = resolveAppPath(app);
|
|
1268
|
+
if (!resolvedPath) return null;
|
|
1269
|
+
const target = appBundleFromPath(resolvedPath) ?? resolvedPath;
|
|
1270
|
+
try {
|
|
1271
|
+
if (hasAppSandboxEntitlement(execFileSync("codesign", [
|
|
1272
|
+
"-d",
|
|
1273
|
+
"--entitlements",
|
|
1274
|
+
"-",
|
|
1275
|
+
target
|
|
1276
|
+
], {
|
|
1277
|
+
encoding: "utf-8",
|
|
1278
|
+
stdio: [
|
|
1279
|
+
"ignore",
|
|
1280
|
+
"pipe",
|
|
1281
|
+
"pipe"
|
|
1282
|
+
]
|
|
1283
|
+
}))) return `⚠️ "${mode}" has Apple App Sandbox enabled — nested sandboxing may cause issues`;
|
|
1284
|
+
} catch {}
|
|
1285
|
+
return null;
|
|
1286
|
+
}
|
|
1287
|
+
function bringAppToFront(mode, apps) {
|
|
1288
|
+
const cmd = getActivationCommand(mode, apps);
|
|
1289
|
+
if (!cmd) return;
|
|
1290
|
+
setTimeout(() => {
|
|
1291
|
+
try {
|
|
1292
|
+
spawn(cmd.bin, cmd.args, {
|
|
1293
|
+
stdio: "ignore",
|
|
1294
|
+
detached: true
|
|
1295
|
+
}).unref();
|
|
1296
|
+
} catch {}
|
|
1297
|
+
}, 250);
|
|
1298
|
+
}
|
|
301
1299
|
//#endregion
|
|
302
|
-
//#region src/
|
|
303
|
-
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
console.log(
|
|
307
|
-
|
|
1300
|
+
//#region src/help.ts
|
|
1301
|
+
function printHelp(version) {
|
|
1302
|
+
const HOME = process.env.HOME;
|
|
1303
|
+
const usageLines = buildUsageLines(HOME, version);
|
|
1304
|
+
console.log(usageLines);
|
|
1305
|
+
console.log(OPTIONS_TEXT);
|
|
308
1306
|
}
|
|
309
|
-
|
|
310
|
-
|
|
1307
|
+
function buildUsageLines(home, version) {
|
|
1308
|
+
return `${`bx ${version} — launch apps in a macOS sandbox`}
|
|
311
1309
|
|
|
312
1310
|
Usage:
|
|
313
1311
|
bx [workdir...] VSCode (default)
|
|
314
|
-
bx
|
|
315
|
-
bx term [workdir...] sandboxed login shell
|
|
1312
|
+
${home ? buildAppUsageLines(home) : ""} bx term [workdir...] sandboxed login shell
|
|
316
1313
|
bx claude [workdir...] Claude Code CLI
|
|
317
|
-
bx exec [workdir...] -- command [args...] arbitrary command
|
|
318
|
-
|
|
1314
|
+
bx exec [workdir...] -- command [args...] arbitrary command`;
|
|
1315
|
+
}
|
|
1316
|
+
function buildAppUsageLines(home) {
|
|
1317
|
+
const apps = getAvailableApps(loadConfig(home));
|
|
1318
|
+
const names = Object.keys(apps);
|
|
1319
|
+
const maxLen = Math.max(...names.map((n) => n.length));
|
|
1320
|
+
const colWidth = 41;
|
|
1321
|
+
return names.map((name) => {
|
|
1322
|
+
const left = ` bx ${name} [workdir...] [-- app-args...]`;
|
|
1323
|
+
return `${left}${" ".repeat(Math.max(1, colWidth + maxLen - name.length - left.length + 2))}${name} (app)`;
|
|
1324
|
+
}).join("\n") + "\n";
|
|
1325
|
+
}
|
|
1326
|
+
const OPTIONS_TEXT = `
|
|
319
1327
|
Options:
|
|
1328
|
+
--dry show what will be protected, don't launch
|
|
320
1329
|
--verbose print the generated sandbox profile
|
|
321
1330
|
--profile-sandbox use an isolated VSCode profile (code mode only)
|
|
322
1331
|
-v, --version show version
|
|
323
1332
|
-h, --help show this help
|
|
324
1333
|
|
|
325
1334
|
Configuration:
|
|
1335
|
+
~/.bxconfig.toml app definitions (TOML):
|
|
1336
|
+
[apps.name] add a new app
|
|
1337
|
+
bundle = "..." macOS bundle ID (auto-discovery)
|
|
1338
|
+
binary = "..." relative path in .app bundle
|
|
1339
|
+
path = "..." explicit executable path
|
|
1340
|
+
args = ["..."] extra arguments
|
|
1341
|
+
passWorkdirs = true|false pass workdirs as launch args
|
|
1342
|
+
built-in apps (code, xcode) can be overridden
|
|
326
1343
|
~/.bxignore sandbox rules (one per line):
|
|
327
1344
|
path block access (deny)
|
|
328
1345
|
rw:path allow read-write access
|
|
329
1346
|
ro:path allow read-only access
|
|
330
1347
|
<workdir>/.bxignore blocked paths in project (.gitignore-style matching)
|
|
331
1348
|
|
|
332
|
-
https://github.com/holtwick/bx-mac
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
const
|
|
339
|
-
const
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
})
|
|
1349
|
+
https://github.com/holtwick/bx-mac`;
|
|
1350
|
+
//#endregion
|
|
1351
|
+
//#region src/drytree.ts
|
|
1352
|
+
const RED = "\x1B[31m";
|
|
1353
|
+
const GREEN = "\x1B[32m";
|
|
1354
|
+
const YELLOW = "\x1B[33m";
|
|
1355
|
+
const CYAN = "\x1B[36m";
|
|
1356
|
+
const DIM = "\x1B[2m";
|
|
1357
|
+
const RESET = "\x1B[0m";
|
|
1358
|
+
function kindIcon(kind) {
|
|
1359
|
+
switch (kind) {
|
|
1360
|
+
case "read-only": return `${YELLOW}◉${RESET}`;
|
|
1361
|
+
case "workdir": return `${GREEN}✔${RESET}`;
|
|
1362
|
+
default: return `${RED}✖${RESET}`;
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
function insertPath(root, home, absPath, kind, isDir) {
|
|
1366
|
+
const rel = absPath.startsWith(home + "/") ? absPath.slice(home.length + 1) : absPath;
|
|
1367
|
+
let node = root;
|
|
1368
|
+
for (const part of rel.split("/")) {
|
|
1369
|
+
if (!node.children.has(part)) node.children.set(part, { children: /* @__PURE__ */ new Map() });
|
|
1370
|
+
node = node.children.get(part);
|
|
1371
|
+
}
|
|
1372
|
+
node.kind = kind;
|
|
1373
|
+
node.isDir = isDir;
|
|
1374
|
+
}
|
|
1375
|
+
function isDirectory(path) {
|
|
1376
|
+
try {
|
|
1377
|
+
return statSync(path).isDirectory();
|
|
1378
|
+
} catch {
|
|
1379
|
+
return path.slice(path.lastIndexOf("/") + 1).startsWith(".");
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
function printNode(node, prefix) {
|
|
1383
|
+
const entries = [...node.children.entries()].sort((a, b) => a[0].localeCompare(b[0]));
|
|
1384
|
+
for (let i = 0; i < entries.length; i++) {
|
|
1385
|
+
const [name, child] = entries[i];
|
|
1386
|
+
const isLast = i === entries.length - 1;
|
|
1387
|
+
const connector = isLast ? "└── " : "├── ";
|
|
1388
|
+
const continuation = isLast ? " " : "│ ";
|
|
1389
|
+
if (child.kind) {
|
|
1390
|
+
const suffix = child.isDir ? "/" : "";
|
|
1391
|
+
console.log(`${prefix}${connector}${kindIcon(child.kind)} ${name}${suffix} ${DIM}${child.kind}${RESET}`);
|
|
1392
|
+
} else console.log(`${prefix}${connector}${CYAN}${name}/${RESET}`);
|
|
1393
|
+
if (child.children.size > 0) printNode(child, prefix + continuation);
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
function printDryRunTree({ home, blockedDirs, ignoredPaths, readOnlyDirs, workDirs }) {
|
|
1397
|
+
const root = { children: /* @__PURE__ */ new Map() };
|
|
1398
|
+
for (const dir of blockedDirs) insertPath(root, home, dir, "blocked", true);
|
|
1399
|
+
for (const path of ignoredPaths) insertPath(root, home, path, "ignored", isDirectory(path));
|
|
1400
|
+
for (const dir of readOnlyDirs) insertPath(root, home, dir, "read-only", true);
|
|
1401
|
+
for (const dir of workDirs) insertPath(root, home, dir, "workdir", true);
|
|
1402
|
+
console.log(`\n${CYAN}~/${RESET}`);
|
|
1403
|
+
printNode(root, "");
|
|
1404
|
+
console.log(`\n${RED}✖${RESET} = denied ${YELLOW}◉${RESET} = read-only ${GREEN}✔${RESET} = read-write\n`);
|
|
1405
|
+
}
|
|
1406
|
+
//#endregion
|
|
1407
|
+
//#region src/index.ts
|
|
1408
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
1409
|
+
const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : "dev";
|
|
1410
|
+
if (process$1.argv.includes("--version") || process$1.argv.includes("-v")) {
|
|
1411
|
+
console.log(`bx ${VERSION}`);
|
|
1412
|
+
process$1.exit(0);
|
|
1413
|
+
}
|
|
1414
|
+
if (process$1.argv.includes("--help") || process$1.argv.includes("-h")) {
|
|
1415
|
+
printHelp(VERSION);
|
|
1416
|
+
process$1.exit(0);
|
|
1417
|
+
}
|
|
1418
|
+
if (!process$1.env.HOME) {
|
|
1419
|
+
console.error(`\n${fmt.error("$HOME environment variable is not set")}\n`);
|
|
1420
|
+
process$1.exit(1);
|
|
1421
|
+
}
|
|
1422
|
+
const HOME = process$1.env.HOME;
|
|
1423
|
+
async function main() {
|
|
1424
|
+
const apps = getAvailableApps(loadConfig(HOME));
|
|
1425
|
+
const { mode, workArgs, verbose, dry, profileSandbox, appArgs, implicit } = parseArgs(getValidModes(apps));
|
|
1426
|
+
const app = apps[mode];
|
|
1427
|
+
const workDirs = (implicit && app?.workdirs?.length ? app.workdirs : workArgs).map((a) => resolve(a.replace(/^~\//, HOME + "/")));
|
|
1428
|
+
if (implicit && !app?.workdirs?.length) {
|
|
1429
|
+
if (workDirs.some((d) => d === HOME)) {
|
|
1430
|
+
console.error(`\n${fmt.error("no working directory specified and current directory is $HOME")}\n`);
|
|
1431
|
+
console.error(fmt.detail(`Usage: bx ${mode} <workdir>`));
|
|
1432
|
+
console.error(fmt.detail(`Config: set default workdirs in ~/.bxconfig.toml:\n`));
|
|
1433
|
+
console.error(fmt.detail(`[apps.${mode}]`));
|
|
1434
|
+
console.error(fmt.detail(`workdirs = ["~/work/my-project"]\n`));
|
|
1435
|
+
process$1.exit(1);
|
|
1436
|
+
}
|
|
1437
|
+
if (!dry) await confirmLaunch(workDirs[0], mode);
|
|
1438
|
+
}
|
|
1439
|
+
if (!dry) {
|
|
1440
|
+
checkOwnSandbox();
|
|
1441
|
+
checkVSCodeTerminal();
|
|
1442
|
+
checkExternalSandbox();
|
|
1443
|
+
}
|
|
1444
|
+
checkWorkDirs(workDirs, HOME);
|
|
1445
|
+
await checkAppAlreadyRunning(mode, apps);
|
|
1446
|
+
if (mode === "code" && profileSandbox) setupVSCodeProfile(HOME);
|
|
1447
|
+
const { allowed, readOnly } = parseHomeConfig(HOME, workDirs);
|
|
1448
|
+
const blockedDirs = collectBlockedDirs(HOME, HOME, __dirname, new Set([...allowed, ...readOnly]));
|
|
1449
|
+
const ignoredPaths = collectIgnoredPaths(HOME, workDirs);
|
|
1450
|
+
printPolicySummary(mode, workDirs, blockedDirs, ignoredPaths, readOnly);
|
|
1451
|
+
const profile = generateProfile(workDirs, blockedDirs, ignoredPaths, [...readOnly]);
|
|
1452
|
+
if (verbose) {
|
|
1453
|
+
console.error("\n--- Generated sandbox profile ---");
|
|
1454
|
+
console.error(profile);
|
|
1455
|
+
console.error("--- End of profile ---\n");
|
|
1456
|
+
}
|
|
1457
|
+
if (dry) {
|
|
1458
|
+
printDryRunTree({
|
|
1459
|
+
home: HOME,
|
|
1460
|
+
blockedDirs,
|
|
1461
|
+
ignoredPaths,
|
|
1462
|
+
readOnlyDirs: readOnly,
|
|
1463
|
+
workDirs
|
|
1464
|
+
});
|
|
1465
|
+
process$1.exit(0);
|
|
1466
|
+
}
|
|
1467
|
+
const profilePath = join("/tmp", `bx-${process$1.pid}.sb`);
|
|
1468
|
+
writeFileSync(profilePath, profile);
|
|
1469
|
+
const cmd = buildCommand(mode, workDirs, HOME, profileSandbox, appArgs, apps);
|
|
1470
|
+
const nestedSandboxWarning = getNestedSandboxWarning(mode, apps);
|
|
1471
|
+
if (nestedSandboxWarning) console.error(fmt.detail(nestedSandboxWarning));
|
|
1472
|
+
if (verbose) printLaunchDetails(cmd, workDirs[0], getActivationCommand(mode, apps));
|
|
1473
|
+
console.error("");
|
|
1474
|
+
const child = spawn("sandbox-exec", [
|
|
1475
|
+
"-f",
|
|
1476
|
+
profilePath,
|
|
1477
|
+
"-D",
|
|
1478
|
+
`HOME=${HOME}`,
|
|
1479
|
+
"-D",
|
|
1480
|
+
`WORK=${workDirs[0]}`,
|
|
1481
|
+
cmd.bin,
|
|
1482
|
+
...cmd.args
|
|
1483
|
+
], {
|
|
1484
|
+
cwd: workDirs[0],
|
|
1485
|
+
stdio: "inherit",
|
|
1486
|
+
env: {
|
|
1487
|
+
...process$1.env,
|
|
1488
|
+
CODEBOX_SANDBOX: "1"
|
|
1489
|
+
}
|
|
1490
|
+
});
|
|
1491
|
+
bringAppToFront(mode, apps);
|
|
1492
|
+
child.on("close", (code) => {
|
|
1493
|
+
rmSync(profilePath, { force: true });
|
|
1494
|
+
process$1.exit(code ?? 0);
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
async function confirmLaunch(workDir, mode) {
|
|
1498
|
+
const rl = createInterface({
|
|
1499
|
+
input: process$1.stdin,
|
|
1500
|
+
output: process$1.stderr
|
|
1501
|
+
});
|
|
1502
|
+
const answer = await new Promise((res) => {
|
|
1503
|
+
rl.question(`${fmt.info(`open ${workDir} in ${mode}?`)} [Y/n] `, res);
|
|
1504
|
+
});
|
|
1505
|
+
rl.close();
|
|
1506
|
+
if (answer && !answer.match(/^y(es)?$/i)) process$1.exit(0);
|
|
1507
|
+
}
|
|
1508
|
+
function printPolicySummary(mode, workDirs, blockedDirs, ignoredPaths, readOnly) {
|
|
1509
|
+
const dirLabel = workDirs.length === 1 ? workDirs[0] : `${workDirs.length} directories`;
|
|
1510
|
+
console.error(`\n${fmt.info(`${mode} → ${dirLabel}`)}`);
|
|
1511
|
+
const parts = [`${blockedDirs.length} blocked`, `${ignoredPaths.length} hidden`];
|
|
1512
|
+
if (readOnly.size > 0) parts.push(`${readOnly.size} read-only`);
|
|
1513
|
+
const extraIgnored = ignoredPaths.length - PROTECTED_DOTDIRS.length;
|
|
1514
|
+
if (extraIgnored > 0) parts.push(`${extraIgnored} from .bxignore`);
|
|
1515
|
+
console.error(fmt.detail(parts.join(" · ")));
|
|
1516
|
+
}
|
|
1517
|
+
function printLaunchDetails(cmd, cwd, activationCmd) {
|
|
1518
|
+
const quote = (a) => JSON.stringify(a);
|
|
1519
|
+
console.error(fmt.detail(`bin: ${cmd.bin}`));
|
|
1520
|
+
console.error(fmt.detail(`args: ${cmd.args.map(quote).join(" ") || "(none)"}`));
|
|
1521
|
+
console.error(fmt.detail(`cwd: ${cwd}`));
|
|
1522
|
+
if (activationCmd) console.error(fmt.detail(`focus: ${activationCmd.bin} ${activationCmd.args.map(quote).join(" ")}`));
|
|
1523
|
+
}
|
|
1524
|
+
main();
|
|
380
1525
|
//#endregion
|
|
381
1526
|
export {};
|