@ariobarin/glossa 0.1.1 → 0.2.0-beta.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/dist/app.js +905 -319
- package/package.json +1 -1
package/dist/app.js
CHANGED
|
@@ -39921,6 +39921,9 @@ var MAX_TEXT_BYTES = 1024 * 1024;
|
|
|
39921
39921
|
var MAX_EDIT_DIFF_BYTES = 128 * 1024;
|
|
39922
39922
|
var MAX_EDIT_OPERATIONS = 100;
|
|
39923
39923
|
var MAX_COMMAND_OUTPUT_BYTES = 12 * 1024;
|
|
39924
|
+
var MAX_COMMAND_RETAINED_STREAM_BYTES = 1024 * 1024;
|
|
39925
|
+
var DEFAULT_COMMAND_OUTPUT_RANGE_BYTES = 32 * 1024;
|
|
39926
|
+
var MAX_COMMAND_OUTPUT_RANGE_BYTES = 64 * 1024;
|
|
39924
39927
|
var DEFAULT_COMMAND_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
39925
39928
|
var MAX_COMMAND_TIMEOUT_MS = 60 * 60 * 1e3;
|
|
39926
39929
|
var DEFAULT_COMMAND_FAST_WAIT_MS = 750;
|
|
@@ -39976,12 +39979,16 @@ var listFilesJobSchema = listFilesRequestSchema.extend({
|
|
|
39976
39979
|
requestId: external_exports.string().uuid(),
|
|
39977
39980
|
timeoutMs: structuredReadTimeoutSchema
|
|
39978
39981
|
});
|
|
39982
|
+
var searchGlobSchema = external_exports.string().min(1).max(256).refine((value) => !/[\r\n\u0000]/.test(value), "Search glob must fit on one line").describe("Root-relative glob pattern using forward slashes, for example src/**/*.ts.");
|
|
39979
39983
|
var searchTextRequestSchema = external_exports.object({
|
|
39980
|
-
query: external_exports.string().min(1).max(256).refine((value) => !/[\r\n\u0000]/.test(value), "Search text must fit on one line").describe("
|
|
39984
|
+
query: external_exports.string().min(1).max(256).refine((value) => !/[\r\n\u0000]/.test(value), "Search text must fit on one line").describe("Single-line UTF-8 search expression. Interpreted literally by default or as a JavaScript regular expression when matchMode is regex."),
|
|
39981
39985
|
path: relativePathSchema.optional().describe("File or directory relative to the exposed root. Defaults to the root."),
|
|
39986
|
+
matchMode: external_exports.enum(["literal", "regex"]).optional().describe("How to interpret query. Defaults to literal; regex uses JavaScript regular-expression syntax."),
|
|
39982
39987
|
caseSensitive: external_exports.boolean().optional().describe("Whether matching is case-sensitive. Defaults to false."),
|
|
39983
39988
|
maxResults: external_exports.number().int().min(1).max(MAX_SEARCH_TEXT_RESULTS).optional().describe("Maximum matching lines to return, from 1 through 100. Defaults to 50."),
|
|
39984
|
-
extensions: external_exports.array(external_exports.string().regex(/^\.[A-Za-z0-9][A-Za-z0-9._-]{0,19}$/).describe("Filename suffix including the leading dot, such as .ts or .d.ts.")).min(1).max(20).optional().describe("Optional filename extensions to search.")
|
|
39989
|
+
extensions: external_exports.array(external_exports.string().regex(/^\.[A-Za-z0-9][A-Za-z0-9._-]{0,19}$/).describe("Filename suffix including the leading dot, such as .ts or .d.ts.")).min(1).max(20).optional().describe("Optional filename extensions to search."),
|
|
39990
|
+
includeGlobs: external_exports.array(searchGlobSchema).min(1).max(20).optional().describe("Optional root-relative glob patterns. A file must match at least one include pattern before its contents are scanned."),
|
|
39991
|
+
excludeGlobs: external_exports.array(searchGlobSchema).min(1).max(20).optional().describe("Optional root-relative glob patterns. Matching files are skipped before their contents are scanned.")
|
|
39985
39992
|
}).strict();
|
|
39986
39993
|
var searchTextJobSchema = searchTextRequestSchema.extend({
|
|
39987
39994
|
type: external_exports.literal("search_text"),
|
|
@@ -40000,8 +40007,8 @@ var readFileRangeJobSchema = readFileRangeRequestSchema.extend({
|
|
|
40000
40007
|
});
|
|
40001
40008
|
var writeFileRequestSchema = external_exports.object({
|
|
40002
40009
|
path: relativePathSchema,
|
|
40003
|
-
content: boundedTextSchema.describe("Complete UTF-8 text content
|
|
40004
|
-
expectedSha256: external_exports.string().regex(/^[a-f0-9]{64}$/).optional().describe("Full-file SHA-256 returned by read_file or read_file_range.
|
|
40010
|
+
content: boundedTextSchema.describe("Complete UTF-8 text content for the new file or replacement revision."),
|
|
40011
|
+
expectedSha256: external_exports.string().regex(/^[a-f0-9]{64}$/).optional().describe("Full-file SHA-256 returned by read_file or read_file_range. Omit only when creating a new path; when provided, write_file replaces exactly that existing revision and fails if it is missing or changed.")
|
|
40005
40012
|
}).strict();
|
|
40006
40013
|
var writeFileJobSchema = writeFileRequestSchema.extend({
|
|
40007
40014
|
type: external_exports.literal("write_file"),
|
|
@@ -40029,6 +40036,30 @@ var editFileJobSchema = editFileRequestSchema.safeExtend({
|
|
|
40029
40036
|
type: external_exports.literal("edit_file"),
|
|
40030
40037
|
requestId: external_exports.string().uuid()
|
|
40031
40038
|
});
|
|
40039
|
+
var makeDirectoryRequestSchema = external_exports.object({
|
|
40040
|
+
path: relativePathSchema,
|
|
40041
|
+
recursive: external_exports.boolean().optional().describe("Whether to create missing parent directories. Defaults to false.")
|
|
40042
|
+
}).strict();
|
|
40043
|
+
var makeDirectoryJobSchema = makeDirectoryRequestSchema.extend({
|
|
40044
|
+
type: external_exports.literal("make_directory"),
|
|
40045
|
+
requestId: external_exports.string().uuid()
|
|
40046
|
+
});
|
|
40047
|
+
var deletePathRequestSchema = external_exports.object({
|
|
40048
|
+
path: relativePathSchema,
|
|
40049
|
+
recursive: external_exports.boolean().optional().describe("Whether to delete a non-empty directory tree. Defaults to false.")
|
|
40050
|
+
}).strict();
|
|
40051
|
+
var deletePathJobSchema = deletePathRequestSchema.extend({
|
|
40052
|
+
type: external_exports.literal("delete_path"),
|
|
40053
|
+
requestId: external_exports.string().uuid()
|
|
40054
|
+
});
|
|
40055
|
+
var movePathRequestSchema = external_exports.object({
|
|
40056
|
+
source: relativePathSchema.describe("Existing file or directory to move."),
|
|
40057
|
+
destination: relativePathSchema.describe("New path inside the exposed root. The destination must not already exist.")
|
|
40058
|
+
}).strict();
|
|
40059
|
+
var movePathJobSchema = movePathRequestSchema.extend({
|
|
40060
|
+
type: external_exports.literal("move_path"),
|
|
40061
|
+
requestId: external_exports.string().uuid()
|
|
40062
|
+
});
|
|
40032
40063
|
function requireOneCommand(value, context) {
|
|
40033
40064
|
if ((value.argv ? 1 : 0) + (value.shellCommand ? 1 : 0) !== 1) {
|
|
40034
40065
|
context.addIssue({
|
|
@@ -40058,6 +40089,16 @@ var getCommandJobSchema = getCommandRequestSchema.extend({
|
|
|
40058
40089
|
type: external_exports.literal("get_command"),
|
|
40059
40090
|
requestId: external_exports.string().uuid()
|
|
40060
40091
|
});
|
|
40092
|
+
var readCommandOutputRequestSchema = external_exports.object({
|
|
40093
|
+
commandId: external_exports.string().uuid().describe("Command identifier returned by run_command."),
|
|
40094
|
+
stream: external_exports.enum(["stdout", "stderr"]).describe("Command output stream to read independently."),
|
|
40095
|
+
offset: external_exports.number().int().min(0).optional().describe("Zero-based retained byte offset. Defaults to 0."),
|
|
40096
|
+
maxBytes: external_exports.number().int().min(4).max(MAX_COMMAND_OUTPUT_RANGE_BYTES).optional().describe("Maximum retained source bytes to inspect, from 4 through 65536. Defaults to 32768.")
|
|
40097
|
+
}).strict();
|
|
40098
|
+
var readCommandOutputJobSchema = readCommandOutputRequestSchema.extend({
|
|
40099
|
+
type: external_exports.literal("read_command_output"),
|
|
40100
|
+
requestId: external_exports.string().uuid()
|
|
40101
|
+
});
|
|
40061
40102
|
var cancelCommandRequestSchema = external_exports.object({
|
|
40062
40103
|
commandId: external_exports.string().uuid().describe("Command identifier returned by run_command.")
|
|
40063
40104
|
}).strict();
|
|
@@ -40072,8 +40113,12 @@ var workerJobSchema = external_exports.discriminatedUnion("type", [
|
|
|
40072
40113
|
readFileRangeJobSchema,
|
|
40073
40114
|
writeFileJobSchema,
|
|
40074
40115
|
editFileJobSchema,
|
|
40116
|
+
makeDirectoryJobSchema,
|
|
40117
|
+
deletePathJobSchema,
|
|
40118
|
+
movePathJobSchema,
|
|
40075
40119
|
runCommandJobSchema,
|
|
40076
40120
|
getCommandJobSchema,
|
|
40121
|
+
readCommandOutputJobSchema,
|
|
40077
40122
|
cancelCommandJobSchema
|
|
40078
40123
|
]);
|
|
40079
40124
|
var workerResultSchema = external_exports.object({
|
|
@@ -49820,6 +49865,8 @@ var WorkerError = class extends Error {
|
|
|
49820
49865
|
var STREAM_HEAD_BYTES = Math.floor(MAX_COMMAND_OUTPUT_BYTES / 3);
|
|
49821
49866
|
var STREAM_TAIL_BYTES = MAX_COMMAND_OUTPUT_BYTES - STREAM_HEAD_BYTES;
|
|
49822
49867
|
var RESTRICTED_SCAN_TAIL_BYTES = 1024;
|
|
49868
|
+
var COMMAND_RECORD_RETENTION_MS = 5 * 60 * 1e3;
|
|
49869
|
+
var MAX_RETAINED_COMMAND_RECORDS = 8;
|
|
49823
49870
|
function restrictedDataError() {
|
|
49824
49871
|
return new WorkerError(
|
|
49825
49872
|
RESTRICTED_DATA_ERROR_CODE,
|
|
@@ -49836,20 +49883,26 @@ function scanOutputChunk(previousTail, chunk) {
|
|
|
49836
49883
|
tail
|
|
49837
49884
|
};
|
|
49838
49885
|
}
|
|
49886
|
+
function markRestrictedData(record2) {
|
|
49887
|
+
if (record2.restrictedDataDetected) return;
|
|
49888
|
+
record2.restrictedDataDetected = true;
|
|
49889
|
+
record2.stdout = emptyCapture();
|
|
49890
|
+
record2.stderr = emptyCapture();
|
|
49891
|
+
record2.stdoutScanTail = Buffer.alloc(0);
|
|
49892
|
+
record2.stderrScanTail = Buffer.alloc(0);
|
|
49893
|
+
if (record2.status === "running") {
|
|
49894
|
+
record2.requestedTerminal = "canceled";
|
|
49895
|
+
void terminateProcessTree(record2.child).catch(() => void 0);
|
|
49896
|
+
}
|
|
49897
|
+
markChanged(record2);
|
|
49898
|
+
}
|
|
49839
49899
|
function recordCommandOutput(record2, streamName, chunk) {
|
|
49840
49900
|
if (record2.restrictedDataDetected || chunk.byteLength === 0) return;
|
|
49841
49901
|
const tailName = streamName === "stdout" ? "stdoutScanTail" : "stderrScanTail";
|
|
49842
49902
|
const scan = scanOutputChunk(record2[tailName], chunk);
|
|
49843
49903
|
record2[tailName] = scan.tail;
|
|
49844
49904
|
if (scan.detected) {
|
|
49845
|
-
record2
|
|
49846
|
-
record2.stdout = emptyCapture();
|
|
49847
|
-
record2.stderr = emptyCapture();
|
|
49848
|
-
if (record2.status === "running") {
|
|
49849
|
-
record2.requestedTerminal = "canceled";
|
|
49850
|
-
void terminateProcessTree(record2.child).catch(() => void 0);
|
|
49851
|
-
}
|
|
49852
|
-
markChanged(record2);
|
|
49905
|
+
markRestrictedData(record2);
|
|
49853
49906
|
return;
|
|
49854
49907
|
}
|
|
49855
49908
|
if (capture(record2, record2[streamName], chunk)) markChanged(record2);
|
|
@@ -49864,6 +49917,17 @@ function appendTail(existing, chunk) {
|
|
|
49864
49917
|
function capture(_record2, stream, chunk) {
|
|
49865
49918
|
if (chunk.byteLength === 0) return false;
|
|
49866
49919
|
stream.totalBytes += chunk.byteLength;
|
|
49920
|
+
const retentionBudget = MAX_COMMAND_RETAINED_STREAM_BYTES - stream.retainedBytes;
|
|
49921
|
+
if (retentionBudget > 0) {
|
|
49922
|
+
const retained = chunk.subarray(0, Math.min(chunk.byteLength, retentionBudget));
|
|
49923
|
+
if (retained.byteLength > 0) {
|
|
49924
|
+
stream.retained.push(Buffer.from(retained));
|
|
49925
|
+
stream.retainedBytes += retained.byteLength;
|
|
49926
|
+
}
|
|
49927
|
+
}
|
|
49928
|
+
if (chunk.byteLength > Math.max(0, retentionBudget)) {
|
|
49929
|
+
stream.retentionTruncated = true;
|
|
49930
|
+
}
|
|
49867
49931
|
let offset = 0;
|
|
49868
49932
|
if (stream.headBytes < STREAM_HEAD_BYTES) {
|
|
49869
49933
|
const accepted = chunk.subarray(
|
|
@@ -49912,7 +49976,10 @@ function emptyCapture() {
|
|
|
49912
49976
|
head: [],
|
|
49913
49977
|
headBytes: 0,
|
|
49914
49978
|
tail: Buffer.alloc(0),
|
|
49915
|
-
|
|
49979
|
+
retained: [],
|
|
49980
|
+
retainedBytes: 0,
|
|
49981
|
+
totalBytes: 0,
|
|
49982
|
+
retentionTruncated: false
|
|
49916
49983
|
};
|
|
49917
49984
|
}
|
|
49918
49985
|
function retainedBytes(stream, complete) {
|
|
@@ -49954,6 +50021,44 @@ function utf8SuffixWithinBudget(value, budget) {
|
|
|
49954
50021
|
}
|
|
49955
50022
|
return characters.slice(start).join("");
|
|
49956
50023
|
}
|
|
50024
|
+
function isUtf8Continuation(byte) {
|
|
50025
|
+
return byte !== void 0 && (byte & 192) === 128;
|
|
50026
|
+
}
|
|
50027
|
+
function utf8SequenceBytes(byte) {
|
|
50028
|
+
if ((byte & 128) === 0) return 1;
|
|
50029
|
+
if ((byte & 224) === 192) return 2;
|
|
50030
|
+
if ((byte & 240) === 224) return 3;
|
|
50031
|
+
if ((byte & 248) === 240) return 4;
|
|
50032
|
+
return 1;
|
|
50033
|
+
}
|
|
50034
|
+
function retainedRange(stream, requestedOffset, maxBytes) {
|
|
50035
|
+
const retained = Buffer.concat(stream.retained, stream.retainedBytes);
|
|
50036
|
+
let offset = requestedOffset;
|
|
50037
|
+
while (offset < retained.byteLength && isUtf8Continuation(retained[offset])) {
|
|
50038
|
+
offset += 1;
|
|
50039
|
+
}
|
|
50040
|
+
if (offset >= retained.byteLength) {
|
|
50041
|
+
return { offset, content: "" };
|
|
50042
|
+
}
|
|
50043
|
+
let end = Math.min(retained.byteLength, offset + maxBytes);
|
|
50044
|
+
if (end < retained.byteLength) {
|
|
50045
|
+
while (end > offset && isUtf8Continuation(retained[end])) end -= 1;
|
|
50046
|
+
}
|
|
50047
|
+
if (end > offset) {
|
|
50048
|
+
let lead = end - 1;
|
|
50049
|
+
while (lead > offset && isUtf8Continuation(retained[lead])) lead -= 1;
|
|
50050
|
+
const expected = utf8SequenceBytes(retained[lead]);
|
|
50051
|
+
if (expected > 1 && end - lead < expected) end = lead;
|
|
50052
|
+
}
|
|
50053
|
+
if (end <= offset) {
|
|
50054
|
+
end = Math.min(retained.byteLength, offset + maxBytes);
|
|
50055
|
+
}
|
|
50056
|
+
return {
|
|
50057
|
+
offset,
|
|
50058
|
+
content: retained.subarray(offset, end).toString("utf8"),
|
|
50059
|
+
...end < retained.byteLength ? { nextOffset: end } : {}
|
|
50060
|
+
};
|
|
50061
|
+
}
|
|
49957
50062
|
function renderStream(stream, budget, complete) {
|
|
49958
50063
|
if (budget <= 0 || stream.totalBytes === 0) {
|
|
49959
50064
|
return { content: "", truncated: stream.totalBytes > 0 };
|
|
@@ -50046,6 +50151,21 @@ var CommandService = class {
|
|
|
50046
50151
|
policy;
|
|
50047
50152
|
#commands = /* @__PURE__ */ new Map();
|
|
50048
50153
|
#activeCommandId = null;
|
|
50154
|
+
#pruneRetainedCommands() {
|
|
50155
|
+
while (this.#commands.size >= MAX_RETAINED_COMMAND_RECORDS) {
|
|
50156
|
+
const oldestTerminal = [...this.#commands].find(
|
|
50157
|
+
([, record2]) => record2.status !== "running"
|
|
50158
|
+
);
|
|
50159
|
+
if (!oldestTerminal) return;
|
|
50160
|
+
this.#commands.delete(oldestTerminal[0]);
|
|
50161
|
+
}
|
|
50162
|
+
}
|
|
50163
|
+
#scheduleDeletion(commandId) {
|
|
50164
|
+
setTimeout(
|
|
50165
|
+
() => this.#commands.delete(commandId),
|
|
50166
|
+
COMMAND_RECORD_RETENTION_MS
|
|
50167
|
+
).unref();
|
|
50168
|
+
}
|
|
50049
50169
|
async start(options) {
|
|
50050
50170
|
if (this.#activeCommandId) {
|
|
50051
50171
|
const active = this.#commands.get(this.#activeCommandId);
|
|
@@ -50130,6 +50250,7 @@ var CommandService = class {
|
|
|
50130
50250
|
void terminateProcessTree(child);
|
|
50131
50251
|
}, timeoutMs);
|
|
50132
50252
|
record2.timeout.unref();
|
|
50253
|
+
this.#pruneRetainedCommands();
|
|
50133
50254
|
this.#commands.set(id, record2);
|
|
50134
50255
|
this.#activeCommandId = id;
|
|
50135
50256
|
child.stdout.on("data", (chunk) => {
|
|
@@ -50147,6 +50268,7 @@ var CommandService = class {
|
|
|
50147
50268
|
this.#activeCommandId = null;
|
|
50148
50269
|
markChanged(record2);
|
|
50149
50270
|
record2.complete();
|
|
50271
|
+
this.#scheduleDeletion(id);
|
|
50150
50272
|
});
|
|
50151
50273
|
child.once("close", (exitCode, signal) => {
|
|
50152
50274
|
if (record2.status !== "running") return;
|
|
@@ -50158,7 +50280,7 @@ var CommandService = class {
|
|
|
50158
50280
|
this.#activeCommandId = null;
|
|
50159
50281
|
markChanged(record2);
|
|
50160
50282
|
record2.complete();
|
|
50161
|
-
|
|
50283
|
+
this.#scheduleDeletion(id);
|
|
50162
50284
|
});
|
|
50163
50285
|
if (options.stdin !== void 0) child.stdin.end(options.stdin);
|
|
50164
50286
|
else child.stdin.end();
|
|
@@ -50215,6 +50337,53 @@ var CommandService = class {
|
|
|
50215
50337
|
}
|
|
50216
50338
|
return this.snapshot(record2);
|
|
50217
50339
|
}
|
|
50340
|
+
async readOutput(commandId, streamName, offset = 0, maxBytes = DEFAULT_COMMAND_OUTPUT_RANGE_BYTES) {
|
|
50341
|
+
const record2 = this.#commands.get(commandId);
|
|
50342
|
+
if (!record2) throw new WorkerError("command_not_found", "The command was not found.");
|
|
50343
|
+
if (streamName !== "stdout" && streamName !== "stderr") {
|
|
50344
|
+
throw new WorkerError(
|
|
50345
|
+
"invalid_output_stream",
|
|
50346
|
+
"Command output stream must be stdout or stderr."
|
|
50347
|
+
);
|
|
50348
|
+
}
|
|
50349
|
+
if (!Number.isInteger(offset) || offset < 0) {
|
|
50350
|
+
throw new WorkerError(
|
|
50351
|
+
"invalid_output_offset",
|
|
50352
|
+
"Command output offset must be a non-negative integer."
|
|
50353
|
+
);
|
|
50354
|
+
}
|
|
50355
|
+
if (!Number.isInteger(maxBytes) || maxBytes < 4 || maxBytes > MAX_COMMAND_OUTPUT_RANGE_BYTES) {
|
|
50356
|
+
throw new WorkerError(
|
|
50357
|
+
"invalid_output_range",
|
|
50358
|
+
"Command output range must be between 4 and 65536 source bytes."
|
|
50359
|
+
);
|
|
50360
|
+
}
|
|
50361
|
+
if (record2.restrictedDataDetected) throw restrictedDataError();
|
|
50362
|
+
const stream = record2[streamName];
|
|
50363
|
+
if (offset > stream.retainedBytes) {
|
|
50364
|
+
throw new WorkerError(
|
|
50365
|
+
"output_offset_out_of_range",
|
|
50366
|
+
"The command output offset exceeds the retained stream length."
|
|
50367
|
+
);
|
|
50368
|
+
}
|
|
50369
|
+
const range = retainedRange(stream, offset, maxBytes);
|
|
50370
|
+
if (containsRestrictedAuthenticationData(range.content)) {
|
|
50371
|
+
markRestrictedData(record2);
|
|
50372
|
+
throw restrictedDataError();
|
|
50373
|
+
}
|
|
50374
|
+
return {
|
|
50375
|
+
commandId,
|
|
50376
|
+
stream: streamName,
|
|
50377
|
+
status: record2.status,
|
|
50378
|
+
offset: range.offset,
|
|
50379
|
+
content: range.content,
|
|
50380
|
+
...range.nextOffset === void 0 ? {} : { nextOffset: range.nextOffset },
|
|
50381
|
+
retainedBytes: stream.retainedBytes,
|
|
50382
|
+
totalBytes: stream.totalBytes,
|
|
50383
|
+
retentionTruncated: stream.retentionTruncated,
|
|
50384
|
+
complete: record2.status !== "running"
|
|
50385
|
+
};
|
|
50386
|
+
}
|
|
50218
50387
|
async cancel(commandId) {
|
|
50219
50388
|
const record2 = this.#commands.get(commandId);
|
|
50220
50389
|
if (!record2) throw new WorkerError("command_not_found", "The command was not found.");
|
|
@@ -50262,124 +50431,414 @@ var CommandService = class {
|
|
|
50262
50431
|
import { createHash, randomUUID as randomUUID2 } from "node:crypto";
|
|
50263
50432
|
import {
|
|
50264
50433
|
chmod as chmod2,
|
|
50265
|
-
|
|
50434
|
+
link as link2,
|
|
50435
|
+
lstat as lstat2,
|
|
50436
|
+
mkdir as mkdir3,
|
|
50266
50437
|
opendir,
|
|
50267
50438
|
open,
|
|
50268
50439
|
readFile as readFile3,
|
|
50269
50440
|
rename,
|
|
50270
50441
|
rm as rm2,
|
|
50271
|
-
|
|
50442
|
+
rmdir,
|
|
50443
|
+
stat as stat2,
|
|
50272
50444
|
writeFile as writeFile3
|
|
50273
50445
|
} from "node:fs/promises";
|
|
50274
|
-
import
|
|
50446
|
+
import path6 from "node:path";
|
|
50275
50447
|
import { performance as performance2 } from "node:perf_hooks";
|
|
50276
50448
|
import { StringDecoder as StringDecoder2 } from "node:string_decoder";
|
|
50277
|
-
|
|
50278
|
-
|
|
50449
|
+
|
|
50450
|
+
// src/worker/path-policy.ts
|
|
50451
|
+
import { lstat, realpath, stat } from "node:fs/promises";
|
|
50452
|
+
import os5 from "node:os";
|
|
50453
|
+
import path5 from "node:path";
|
|
50454
|
+
function samePath(left, right) {
|
|
50455
|
+
return process.platform === "win32" ? left.toLowerCase() === right.toLowerCase() : left === right;
|
|
50279
50456
|
}
|
|
50280
|
-
|
|
50281
|
-
async function withFileWriteLock(target, operation) {
|
|
50282
|
-
const normalized = path5.normalize(target);
|
|
50283
|
-
const key = process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
50284
|
-
const predecessor = fileWriteTails.get(key);
|
|
50285
|
-
let release;
|
|
50286
|
-
const tail = new Promise((resolve) => {
|
|
50287
|
-
release = resolve;
|
|
50288
|
-
});
|
|
50289
|
-
fileWriteTails.set(key, tail);
|
|
50290
|
-
if (predecessor) await predecessor;
|
|
50457
|
+
function accountHomeDirectory() {
|
|
50291
50458
|
try {
|
|
50292
|
-
return
|
|
50293
|
-
}
|
|
50294
|
-
|
|
50295
|
-
if (fileWriteTails.get(key) === tail) fileWriteTails.delete(key);
|
|
50459
|
+
return os5.userInfo().homedir;
|
|
50460
|
+
} catch {
|
|
50461
|
+
return os5.homedir();
|
|
50296
50462
|
}
|
|
50297
50463
|
}
|
|
50298
|
-
|
|
50299
|
-
|
|
50300
|
-
|
|
50301
|
-
|
|
50302
|
-
|
|
50303
|
-
|
|
50464
|
+
function isWithin(root, candidate) {
|
|
50465
|
+
const relative = path5.relative(root, candidate);
|
|
50466
|
+
return relative === "" || !relative.startsWith(`..${path5.sep}`) && relative !== ".." && !path5.isAbsolute(relative);
|
|
50467
|
+
}
|
|
50468
|
+
function validateRelativePath(value) {
|
|
50469
|
+
if (value.includes("\0")) {
|
|
50470
|
+
throw new WorkerError("invalid_path", "Paths cannot contain null bytes.");
|
|
50304
50471
|
}
|
|
50305
|
-
|
|
50306
|
-
|
|
50472
|
+
const explicitNativePosixPath = process.platform !== "win32" && value.startsWith("./");
|
|
50473
|
+
if (path5.isAbsolute(value) || path5.posix.isAbsolute(value) || !explicitNativePosixPath && path5.win32.isAbsolute(value)) {
|
|
50474
|
+
throw new WorkerError("absolute_path", "Absolute paths are not allowed.");
|
|
50307
50475
|
}
|
|
50476
|
+
const segments = explicitNativePosixPath ? value.split(/\/+/).filter(Boolean) : value.split(/[\\/]+/);
|
|
50477
|
+
if (segments.includes("..")) {
|
|
50478
|
+
throw new WorkerError("path_traversal", "Parent path traversal is not allowed.");
|
|
50479
|
+
}
|
|
50480
|
+
return value === "" ? "." : value;
|
|
50308
50481
|
}
|
|
50309
|
-
|
|
50310
|
-
|
|
50311
|
-
|
|
50312
|
-
|
|
50313
|
-
|
|
50314
|
-
|
|
50315
|
-
|
|
50316
|
-
|
|
50317
|
-
|
|
50318
|
-
|
|
50319
|
-
|
|
50320
|
-
|
|
50321
|
-
|
|
50322
|
-
|
|
50323
|
-
|
|
50324
|
-
|
|
50325
|
-
|
|
50326
|
-
|
|
50327
|
-
|
|
50328
|
-
|
|
50329
|
-
|
|
50330
|
-
|
|
50331
|
-
|
|
50332
|
-
|
|
50333
|
-
}
|
|
50334
|
-
|
|
50335
|
-
const code = error46?.code;
|
|
50336
|
-
return code === "ENOENT" || code === "EACCES" || code === "EPERM" || code === "ENOTDIR";
|
|
50337
|
-
}
|
|
50338
|
-
function isUnavailableDiscoveredPathError(error46) {
|
|
50339
|
-
return isUnavailableFileError(error46) || error46 instanceof WorkerError && error46.code === "path_not_found";
|
|
50340
|
-
}
|
|
50341
|
-
function isLinkedPathError(error46) {
|
|
50342
|
-
return error46 instanceof WorkerError && error46.code === "linked_path";
|
|
50482
|
+
async function canonicalizeRoot(candidate) {
|
|
50483
|
+
const root = await realpath(path5.resolve(candidate)).catch((error46) => {
|
|
50484
|
+
if (error46.code === "ENOENT") {
|
|
50485
|
+
throw new WorkerError("root_not_found", "The workspace directory does not exist.");
|
|
50486
|
+
}
|
|
50487
|
+
throw error46;
|
|
50488
|
+
});
|
|
50489
|
+
const rootStat = await stat(root);
|
|
50490
|
+
if (!rootStat.isDirectory()) {
|
|
50491
|
+
throw new WorkerError("root_not_directory", "The exposed root must be a directory.");
|
|
50492
|
+
}
|
|
50493
|
+
const filesystemRoot = path5.parse(root).root;
|
|
50494
|
+
const homes = await Promise.all(
|
|
50495
|
+
[os5.homedir(), accountHomeDirectory()].map(
|
|
50496
|
+
async (home) => await realpath(home).catch(() => path5.resolve(home))
|
|
50497
|
+
)
|
|
50498
|
+
);
|
|
50499
|
+
const isHomeDirectory = homes.some((home) => samePath(root, home));
|
|
50500
|
+
if (samePath(root, filesystemRoot) || isHomeDirectory) {
|
|
50501
|
+
const kind = isHomeDirectory ? "your home directory" : "a filesystem root";
|
|
50502
|
+
throw new WorkerError(
|
|
50503
|
+
"broad_root_refused",
|
|
50504
|
+
`The selected root is ${kind}, which Glossa will not expose. Choose a project directory instead.`
|
|
50505
|
+
);
|
|
50506
|
+
}
|
|
50507
|
+
return root;
|
|
50343
50508
|
}
|
|
50344
|
-
|
|
50345
|
-
|
|
50346
|
-
|
|
50347
|
-
|
|
50348
|
-
|
|
50349
|
-
|
|
50509
|
+
var PathPolicy = class _PathPolicy {
|
|
50510
|
+
constructor(root) {
|
|
50511
|
+
this.root = root;
|
|
50512
|
+
}
|
|
50513
|
+
root;
|
|
50514
|
+
static async create(candidate) {
|
|
50515
|
+
return new _PathPolicy(await canonicalizeRoot(candidate));
|
|
50516
|
+
}
|
|
50517
|
+
async resolveExisting(relativePath) {
|
|
50518
|
+
const lexical = this.resolveLexical(relativePath);
|
|
50519
|
+
await this.rejectLinkedComponents(lexical);
|
|
50520
|
+
const canonical = await realpath(lexical).catch((error46) => {
|
|
50521
|
+
if (error46.code === "ENOENT") {
|
|
50522
|
+
throw new WorkerError("path_not_found", "The requested path does not exist.");
|
|
50523
|
+
}
|
|
50524
|
+
throw error46;
|
|
50525
|
+
});
|
|
50526
|
+
if (!isWithin(this.root, canonical)) {
|
|
50527
|
+
throw new WorkerError("path_escape", "The requested path escapes the exposed root.");
|
|
50350
50528
|
}
|
|
50351
|
-
|
|
50352
|
-
|
|
50529
|
+
return canonical;
|
|
50530
|
+
}
|
|
50531
|
+
async resolveDiscoveredExisting(candidate) {
|
|
50532
|
+
const lexical = path5.resolve(candidate);
|
|
50533
|
+
if (!isWithin(this.root, lexical)) {
|
|
50534
|
+
throw new WorkerError("path_escape", "The requested path escapes the exposed root.");
|
|
50353
50535
|
}
|
|
50354
|
-
|
|
50355
|
-
|
|
50536
|
+
await this.rejectLinkedComponents(lexical);
|
|
50537
|
+
const canonical = await realpath(lexical).catch((error46) => {
|
|
50538
|
+
if (error46.code === "ENOENT") {
|
|
50539
|
+
throw new WorkerError("path_not_found", "The requested path does not exist.");
|
|
50540
|
+
}
|
|
50541
|
+
throw error46;
|
|
50542
|
+
});
|
|
50543
|
+
if (!isWithin(this.root, canonical)) {
|
|
50544
|
+
throw new WorkerError("path_escape", "The requested path escapes the exposed root.");
|
|
50356
50545
|
}
|
|
50357
|
-
|
|
50358
|
-
|
|
50359
|
-
|
|
50360
|
-
|
|
50361
|
-
|
|
50362
|
-
|
|
50363
|
-
|
|
50364
|
-
|
|
50365
|
-
|
|
50366
|
-
|
|
50367
|
-
|
|
50546
|
+
return canonical;
|
|
50547
|
+
}
|
|
50548
|
+
async resolveWritableFile(relativePath) {
|
|
50549
|
+
const lexical = this.resolveLexical(relativePath);
|
|
50550
|
+
const parent = path5.dirname(lexical);
|
|
50551
|
+
await this.rejectLinkedComponents(parent);
|
|
50552
|
+
const canonicalParent = await realpath(parent).catch((error46) => {
|
|
50553
|
+
if (error46.code === "ENOENT") {
|
|
50554
|
+
throw new WorkerError("parent_not_found", "The destination directory does not exist.");
|
|
50555
|
+
}
|
|
50556
|
+
throw error46;
|
|
50557
|
+
});
|
|
50558
|
+
if (!isWithin(this.root, canonicalParent)) {
|
|
50559
|
+
throw new WorkerError("path_escape", "The destination escapes the exposed root.");
|
|
50368
50560
|
}
|
|
50369
|
-
|
|
50370
|
-
|
|
50371
|
-
throw new WorkerError("file_changed", "The file changed while it was being read.");
|
|
50561
|
+
if (!(await stat(canonicalParent)).isDirectory()) {
|
|
50562
|
+
throw new WorkerError("not_directory", "The destination parent is not a directory.");
|
|
50372
50563
|
}
|
|
50373
|
-
|
|
50374
|
-
|
|
50375
|
-
|
|
50564
|
+
try {
|
|
50565
|
+
const targetStat = await lstat(lexical);
|
|
50566
|
+
if (targetStat.isSymbolicLink()) {
|
|
50567
|
+
throw new WorkerError("linked_path", "Writes through links are not allowed.");
|
|
50568
|
+
}
|
|
50569
|
+
if (targetStat.isDirectory()) {
|
|
50570
|
+
throw new WorkerError("not_file", "The destination is a directory.");
|
|
50571
|
+
}
|
|
50572
|
+
const canonicalTarget = await realpath(lexical);
|
|
50573
|
+
if (!isWithin(this.root, canonicalTarget)) {
|
|
50574
|
+
throw new WorkerError("path_escape", "The destination escapes the exposed root.");
|
|
50575
|
+
}
|
|
50576
|
+
return canonicalTarget;
|
|
50577
|
+
} catch (error46) {
|
|
50578
|
+
if (error46 instanceof WorkerError) throw error46;
|
|
50579
|
+
if (error46.code !== "ENOENT") throw error46;
|
|
50580
|
+
}
|
|
50581
|
+
return path5.join(canonicalParent, path5.basename(lexical));
|
|
50376
50582
|
}
|
|
50377
|
-
|
|
50378
|
-
|
|
50379
|
-
|
|
50380
|
-
|
|
50381
|
-
|
|
50382
|
-
|
|
50583
|
+
async resolveWritableDirectory(relativePath, recursive) {
|
|
50584
|
+
const lexical = this.resolveLexical(relativePath);
|
|
50585
|
+
await this.rejectLinkedComponents(lexical);
|
|
50586
|
+
try {
|
|
50587
|
+
const targetStat = await lstat(lexical);
|
|
50588
|
+
if (targetStat.isSymbolicLink()) {
|
|
50589
|
+
throw new WorkerError("linked_path", "Directory creation through links is not allowed.");
|
|
50590
|
+
}
|
|
50591
|
+
if (!targetStat.isDirectory()) {
|
|
50592
|
+
throw new WorkerError("not_directory", "The destination is not a directory.");
|
|
50593
|
+
}
|
|
50594
|
+
const canonicalTarget = await realpath(lexical);
|
|
50595
|
+
if (!isWithin(this.root, canonicalTarget)) {
|
|
50596
|
+
throw new WorkerError("path_escape", "The destination escapes the exposed root.");
|
|
50597
|
+
}
|
|
50598
|
+
return { target: canonicalTarget, exists: true };
|
|
50599
|
+
} catch (error46) {
|
|
50600
|
+
if (error46 instanceof WorkerError) throw error46;
|
|
50601
|
+
if (error46.code !== "ENOENT") throw error46;
|
|
50602
|
+
}
|
|
50603
|
+
if (!recursive) {
|
|
50604
|
+
const parent = path5.dirname(lexical);
|
|
50605
|
+
await this.rejectLinkedComponents(parent);
|
|
50606
|
+
const canonicalParent = await realpath(parent).catch(
|
|
50607
|
+
(error46) => {
|
|
50608
|
+
if (error46.code === "ENOENT") {
|
|
50609
|
+
throw new WorkerError(
|
|
50610
|
+
"parent_not_found",
|
|
50611
|
+
"The destination directory does not exist."
|
|
50612
|
+
);
|
|
50613
|
+
}
|
|
50614
|
+
throw error46;
|
|
50615
|
+
}
|
|
50616
|
+
);
|
|
50617
|
+
if (!isWithin(this.root, canonicalParent)) {
|
|
50618
|
+
throw new WorkerError("path_escape", "The destination escapes the exposed root.");
|
|
50619
|
+
}
|
|
50620
|
+
if (!(await stat(canonicalParent)).isDirectory()) {
|
|
50621
|
+
throw new WorkerError("not_directory", "The destination parent is not a directory.");
|
|
50622
|
+
}
|
|
50623
|
+
return {
|
|
50624
|
+
target: path5.join(canonicalParent, path5.basename(lexical)),
|
|
50625
|
+
exists: false
|
|
50626
|
+
};
|
|
50627
|
+
}
|
|
50628
|
+
let existingAncestor = path5.dirname(lexical);
|
|
50629
|
+
while (!samePath(existingAncestor, this.root)) {
|
|
50630
|
+
try {
|
|
50631
|
+
await this.rejectLinkedComponents(existingAncestor);
|
|
50632
|
+
const canonicalAncestor = await realpath(existingAncestor);
|
|
50633
|
+
if (!isWithin(this.root, canonicalAncestor)) {
|
|
50634
|
+
throw new WorkerError("path_escape", "The destination escapes the exposed root.");
|
|
50635
|
+
}
|
|
50636
|
+
if (!(await stat(canonicalAncestor)).isDirectory()) {
|
|
50637
|
+
throw new WorkerError("not_directory", "The destination parent is not a directory.");
|
|
50638
|
+
}
|
|
50639
|
+
return {
|
|
50640
|
+
target: path5.join(
|
|
50641
|
+
canonicalAncestor,
|
|
50642
|
+
path5.relative(existingAncestor, lexical)
|
|
50643
|
+
),
|
|
50644
|
+
exists: false
|
|
50645
|
+
};
|
|
50646
|
+
} catch (error46) {
|
|
50647
|
+
if (error46 instanceof WorkerError) throw error46;
|
|
50648
|
+
if (error46.code !== "ENOENT") throw error46;
|
|
50649
|
+
}
|
|
50650
|
+
existingAncestor = path5.dirname(existingAncestor);
|
|
50651
|
+
}
|
|
50652
|
+
return { target: lexical, exists: false };
|
|
50653
|
+
}
|
|
50654
|
+
async resolveVacantPath(relativePath) {
|
|
50655
|
+
const lexical = this.resolveLexical(relativePath);
|
|
50656
|
+
const parent = path5.dirname(lexical);
|
|
50657
|
+
await this.rejectLinkedComponents(parent);
|
|
50658
|
+
const canonicalParent = await realpath(parent).catch(
|
|
50659
|
+
(error46) => {
|
|
50660
|
+
if (error46.code === "ENOENT") {
|
|
50661
|
+
throw new WorkerError(
|
|
50662
|
+
"parent_not_found",
|
|
50663
|
+
"The destination directory does not exist."
|
|
50664
|
+
);
|
|
50665
|
+
}
|
|
50666
|
+
throw error46;
|
|
50667
|
+
}
|
|
50668
|
+
);
|
|
50669
|
+
if (!isWithin(this.root, canonicalParent)) {
|
|
50670
|
+
throw new WorkerError("path_escape", "The destination escapes the exposed root.");
|
|
50671
|
+
}
|
|
50672
|
+
if (!(await stat(canonicalParent)).isDirectory()) {
|
|
50673
|
+
throw new WorkerError("not_directory", "The destination parent is not a directory.");
|
|
50674
|
+
}
|
|
50675
|
+
try {
|
|
50676
|
+
await lstat(lexical);
|
|
50677
|
+
throw new WorkerError(
|
|
50678
|
+
"destination_exists",
|
|
50679
|
+
"The destination already exists."
|
|
50680
|
+
);
|
|
50681
|
+
} catch (error46) {
|
|
50682
|
+
if (error46 instanceof WorkerError) throw error46;
|
|
50683
|
+
if (error46.code !== "ENOENT") throw error46;
|
|
50684
|
+
}
|
|
50685
|
+
return path5.join(canonicalParent, path5.basename(lexical));
|
|
50686
|
+
}
|
|
50687
|
+
resolveLexical(relativePath) {
|
|
50688
|
+
const validated = validateRelativePath(relativePath);
|
|
50689
|
+
const candidate = path5.resolve(this.root, validated);
|
|
50690
|
+
if (!isWithin(this.root, candidate)) {
|
|
50691
|
+
throw new WorkerError("path_escape", "The requested path escapes the exposed root.");
|
|
50692
|
+
}
|
|
50693
|
+
return candidate;
|
|
50694
|
+
}
|
|
50695
|
+
async rejectLinkedComponents(candidate) {
|
|
50696
|
+
if (!isWithin(this.root, candidate)) {
|
|
50697
|
+
throw new WorkerError("path_escape", "The requested path escapes the exposed root.");
|
|
50698
|
+
}
|
|
50699
|
+
const relative = path5.relative(this.root, candidate);
|
|
50700
|
+
if (!relative) return;
|
|
50701
|
+
let current = this.root;
|
|
50702
|
+
for (const segment of relative.split(path5.sep)) {
|
|
50703
|
+
current = path5.join(current, segment);
|
|
50704
|
+
try {
|
|
50705
|
+
const currentStat = await lstat(current);
|
|
50706
|
+
if (currentStat.isSymbolicLink()) {
|
|
50707
|
+
throw new WorkerError(
|
|
50708
|
+
"linked_path",
|
|
50709
|
+
"Symlink and junction paths are not allowed."
|
|
50710
|
+
);
|
|
50711
|
+
}
|
|
50712
|
+
} catch (error46) {
|
|
50713
|
+
if (error46 instanceof WorkerError) throw error46;
|
|
50714
|
+
if (error46.code === "ENOENT") return;
|
|
50715
|
+
throw error46;
|
|
50716
|
+
}
|
|
50717
|
+
}
|
|
50718
|
+
}
|
|
50719
|
+
};
|
|
50720
|
+
|
|
50721
|
+
// src/worker/file-service.ts
|
|
50722
|
+
function sha256(content) {
|
|
50723
|
+
return createHash("sha256").update(content).digest("hex");
|
|
50724
|
+
}
|
|
50725
|
+
function isPathWithin(root, candidate) {
|
|
50726
|
+
const relative = path6.relative(root, candidate);
|
|
50727
|
+
return relative === "" || !relative.startsWith(`..${path6.sep}`) && relative !== ".." && !path6.isAbsolute(relative);
|
|
50728
|
+
}
|
|
50729
|
+
var fileWriteTails = /* @__PURE__ */ new Map();
|
|
50730
|
+
async function withFileWriteLock(target, operation) {
|
|
50731
|
+
const normalized = path6.normalize(target);
|
|
50732
|
+
const key = process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
50733
|
+
const predecessor = fileWriteTails.get(key);
|
|
50734
|
+
let release;
|
|
50735
|
+
const tail = new Promise((resolve) => {
|
|
50736
|
+
release = resolve;
|
|
50737
|
+
});
|
|
50738
|
+
fileWriteTails.set(key, tail);
|
|
50739
|
+
if (predecessor) await predecessor;
|
|
50740
|
+
try {
|
|
50741
|
+
return await operation();
|
|
50742
|
+
} finally {
|
|
50743
|
+
release();
|
|
50744
|
+
if (fileWriteTails.get(key) === tail) fileWriteTails.delete(key);
|
|
50745
|
+
}
|
|
50746
|
+
}
|
|
50747
|
+
async function withFileWriteLocks(targets, operation) {
|
|
50748
|
+
const unique = [...new Set(targets.map((target) => path6.normalize(target)))].sort(
|
|
50749
|
+
(left, right) => left.localeCompare(right)
|
|
50750
|
+
);
|
|
50751
|
+
const run = async (index) => {
|
|
50752
|
+
const target = unique[index];
|
|
50753
|
+
return target === void 0 ? await operation() : await withFileWriteLock(target, async () => await run(index + 1));
|
|
50754
|
+
};
|
|
50755
|
+
return await run(0);
|
|
50756
|
+
}
|
|
50757
|
+
async function requireRevision(target, expectedSha256) {
|
|
50758
|
+
let actual = null;
|
|
50759
|
+
try {
|
|
50760
|
+
actual = sha256(await readFile3(target));
|
|
50761
|
+
} catch (error46) {
|
|
50762
|
+
if (error46.code !== "ENOENT") throw error46;
|
|
50763
|
+
}
|
|
50764
|
+
if (actual !== expectedSha256) {
|
|
50765
|
+
throw new WorkerError("stale_revision", "The file revision has changed.");
|
|
50766
|
+
}
|
|
50767
|
+
}
|
|
50768
|
+
var DEFAULT_LIST_FILES_LIMIT = 100;
|
|
50769
|
+
var DEFAULT_SEARCH_TEXT_RESULTS = 50;
|
|
50770
|
+
var DEFAULT_READ_RANGE_LINES = 200;
|
|
50771
|
+
var MAX_REPOSITORY_SCAN_ENTRIES = 2e4;
|
|
50772
|
+
var MAX_SEARCH_FILES = 5e3;
|
|
50773
|
+
var MAX_SEARCH_BYTES = 32 * 1024 * 1024;
|
|
50774
|
+
var SKIPPED_RECURSIVE_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
50775
|
+
".git",
|
|
50776
|
+
".hg",
|
|
50777
|
+
".svn",
|
|
50778
|
+
"node_modules"
|
|
50779
|
+
]);
|
|
50780
|
+
function compareNames(left, right) {
|
|
50781
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
50782
|
+
}
|
|
50783
|
+
function relativeSortKey(root, target) {
|
|
50784
|
+
const relative = path6.relative(root, target);
|
|
50785
|
+
if (!relative) return ".";
|
|
50786
|
+
return process.platform === "win32" ? relative.replaceAll("\\", "/") : relative;
|
|
50787
|
+
}
|
|
50788
|
+
function displayPath(root, target) {
|
|
50789
|
+
const relative = relativeSortKey(root, target);
|
|
50790
|
+
if (process.platform === "win32") return relative;
|
|
50791
|
+
return relative.includes("\\") ? "./" + relative : relative;
|
|
50792
|
+
}
|
|
50793
|
+
function isUnavailableFileError(error46) {
|
|
50794
|
+
const code = error46?.code;
|
|
50795
|
+
return code === "ENOENT" || code === "EACCES" || code === "EPERM" || code === "ENOTDIR";
|
|
50796
|
+
}
|
|
50797
|
+
function isUnavailableDiscoveredPathError(error46) {
|
|
50798
|
+
return isUnavailableFileError(error46) || error46 instanceof WorkerError && error46.code === "path_not_found";
|
|
50799
|
+
}
|
|
50800
|
+
function isLinkedPathError(error46) {
|
|
50801
|
+
return error46 instanceof WorkerError && error46.code === "linked_path";
|
|
50802
|
+
}
|
|
50803
|
+
async function readBoundedFile(target, maximumBytes, expectedBytes) {
|
|
50804
|
+
const handle = await open(target, "r");
|
|
50805
|
+
try {
|
|
50806
|
+
const openedStat = await handle.stat();
|
|
50807
|
+
if (!openedStat.isFile()) {
|
|
50808
|
+
throw new WorkerError("not_file", "The requested path is not a file.");
|
|
50809
|
+
}
|
|
50810
|
+
if (openedStat.size !== expectedBytes) {
|
|
50811
|
+
throw new WorkerError("file_changed", "The file changed while it was being read.");
|
|
50812
|
+
}
|
|
50813
|
+
if (openedStat.size > maximumBytes) {
|
|
50814
|
+
throw new WorkerError("search_byte_limit", "The search byte limit was reached.");
|
|
50815
|
+
}
|
|
50816
|
+
const buffer = Buffer.allocUnsafe(openedStat.size);
|
|
50817
|
+
let offset = 0;
|
|
50818
|
+
while (offset < buffer.byteLength) {
|
|
50819
|
+
const { bytesRead } = await handle.read(
|
|
50820
|
+
buffer,
|
|
50821
|
+
offset,
|
|
50822
|
+
buffer.byteLength - offset,
|
|
50823
|
+
offset
|
|
50824
|
+
);
|
|
50825
|
+
if (bytesRead === 0) break;
|
|
50826
|
+
offset += bytesRead;
|
|
50827
|
+
}
|
|
50828
|
+
const completedStat = await handle.stat();
|
|
50829
|
+
if (completedStat.size !== openedStat.size || offset !== openedStat.size) {
|
|
50830
|
+
throw new WorkerError("file_changed", "The file changed while it was being read.");
|
|
50831
|
+
}
|
|
50832
|
+
return Buffer.from(buffer);
|
|
50833
|
+
} finally {
|
|
50834
|
+
await handle.close();
|
|
50835
|
+
}
|
|
50836
|
+
}
|
|
50837
|
+
function normalizedLines(content) {
|
|
50838
|
+
if (content.length === 0) return [];
|
|
50839
|
+
const normalized = content.replace(/\r\n?/g, "\n");
|
|
50840
|
+
const lines = normalized.split("\n");
|
|
50841
|
+
if (normalized.endsWith("\n")) lines.pop();
|
|
50383
50842
|
return lines;
|
|
50384
50843
|
}
|
|
50385
50844
|
function escapeRegExp(value) {
|
|
@@ -50563,7 +51022,7 @@ var FileService = class {
|
|
|
50563
51022
|
this.#beforeDeadline = dependencies.beforeDeadline ?? defaultBeforeDeadline;
|
|
50564
51023
|
this.#openDirectory = dependencies.openDirectory ?? ((directory) => opendir(directory));
|
|
50565
51024
|
this.#readFileBytes = dependencies.readFileBytes ?? readBoundedFile;
|
|
50566
|
-
this.#lstatPath = dependencies.lstatPath ?? ((target) =>
|
|
51025
|
+
this.#lstatPath = dependencies.lstatPath ?? ((target) => lstat2(target));
|
|
50567
51026
|
this.#trustDirectoryEntryTypes = dependencies.trustDirectoryEntryTypes ?? dependencies.lstatPath === void 0;
|
|
50568
51027
|
this.#maxRepositoryScanEntries = dependencies.maxRepositoryScanEntries ?? MAX_REPOSITORY_SCAN_ENTRIES;
|
|
50569
51028
|
this.#maxSearchBytes = dependencies.maxSearchBytes ?? MAX_SEARCH_BYTES;
|
|
@@ -50634,7 +51093,7 @@ var FileService = class {
|
|
|
50634
51093
|
}
|
|
50635
51094
|
async #readResolvedText(target, maximumBytes = MAX_TEXT_BYTES) {
|
|
50636
51095
|
const boundedMaximum = Math.min(maximumBytes, MAX_TEXT_BYTES);
|
|
50637
|
-
const targetStat = await
|
|
51096
|
+
const targetStat = await stat2(target);
|
|
50638
51097
|
if (!targetStat.isFile()) {
|
|
50639
51098
|
throw new WorkerError("not_file", "The requested path is not a file.");
|
|
50640
51099
|
}
|
|
@@ -50677,7 +51136,7 @@ var FileService = class {
|
|
|
50677
51136
|
this.policy.resolveExisting(startPath),
|
|
50678
51137
|
deadlineAt
|
|
50679
51138
|
);
|
|
50680
|
-
const startStat = await this.#withinDeadline(
|
|
51139
|
+
const startStat = await this.#withinDeadline(stat2(start), deadlineAt);
|
|
50681
51140
|
if (!startStat.isDirectory()) {
|
|
50682
51141
|
throw new WorkerError("not_directory", "The requested path is not a directory.");
|
|
50683
51142
|
}
|
|
@@ -50706,7 +51165,7 @@ var FileService = class {
|
|
|
50706
51165
|
if (batch.overflow) throw scanLimitError();
|
|
50707
51166
|
scannedEntries += batch.children.length;
|
|
50708
51167
|
for (const child of batch.children) {
|
|
50709
|
-
const target =
|
|
51168
|
+
const target = path6.join(directory, child.name);
|
|
50710
51169
|
heapPush(frontier, {
|
|
50711
51170
|
target,
|
|
50712
51171
|
sortKey: relativeSortKey(this.policy.root, target),
|
|
@@ -50730,7 +51189,7 @@ var FileService = class {
|
|
|
50730
51189
|
throw scanLimitError();
|
|
50731
51190
|
}
|
|
50732
51191
|
scannedEntries += 1;
|
|
50733
|
-
const target =
|
|
51192
|
+
const target = path6.join(directory, child.name);
|
|
50734
51193
|
let childStat;
|
|
50735
51194
|
try {
|
|
50736
51195
|
childStat = await this.#withinDeadline(
|
|
@@ -50876,15 +51335,31 @@ var FileService = class {
|
|
|
50876
51335
|
);
|
|
50877
51336
|
const matchLimit = maxResults + 1;
|
|
50878
51337
|
const extensions = options.extensions?.map((extension) => extension.toLowerCase());
|
|
50879
|
-
const
|
|
50880
|
-
|
|
50881
|
-
|
|
50882
|
-
|
|
51338
|
+
const includeGlobs = options.includeGlobs;
|
|
51339
|
+
const excludeGlobs = options.excludeGlobs;
|
|
51340
|
+
const globMatches = (relativePath, patterns) => {
|
|
51341
|
+
if (!patterns) return false;
|
|
51342
|
+
const normalized = relativePath.replaceAll("\\", "/");
|
|
51343
|
+
try {
|
|
51344
|
+
return patterns.some((pattern) => path6.posix.matchesGlob(normalized, pattern));
|
|
51345
|
+
} catch {
|
|
51346
|
+
throw new WorkerError("invalid_search", "Search glob pattern is invalid.");
|
|
51347
|
+
}
|
|
51348
|
+
};
|
|
51349
|
+
let matcher;
|
|
51350
|
+
try {
|
|
51351
|
+
matcher = new RegExp(
|
|
51352
|
+
options.matchMode === "regex" ? options.query : escapeRegExp(options.query),
|
|
51353
|
+
options.caseSensitive === true ? "u" : "iu"
|
|
51354
|
+
);
|
|
51355
|
+
} catch {
|
|
51356
|
+
throw new WorkerError("invalid_search", "Search regular expression is invalid.");
|
|
51357
|
+
}
|
|
50883
51358
|
const start = await this.#withinDeadline(
|
|
50884
51359
|
this.policy.resolveExisting(options.path ?? "."),
|
|
50885
51360
|
deadlineAt
|
|
50886
51361
|
);
|
|
50887
|
-
const startStat = await this.#withinDeadline(
|
|
51362
|
+
const startStat = await this.#withinDeadline(stat2(start), deadlineAt);
|
|
50888
51363
|
const matches = [];
|
|
50889
51364
|
let scannedEntries = 0;
|
|
50890
51365
|
let scannedFiles = 0;
|
|
@@ -50895,10 +51370,12 @@ var FileService = class {
|
|
|
50895
51370
|
const searchFile = async (target) => {
|
|
50896
51371
|
const relative = displayPath(this.policy.root, target);
|
|
50897
51372
|
if (extensions && !extensions.some(
|
|
50898
|
-
(extension) =>
|
|
51373
|
+
(extension) => path6.basename(target).toLowerCase().endsWith(extension)
|
|
50899
51374
|
)) {
|
|
50900
51375
|
return false;
|
|
50901
51376
|
}
|
|
51377
|
+
if (includeGlobs && !globMatches(relative, includeGlobs)) return false;
|
|
51378
|
+
if (globMatches(relative, excludeGlobs)) return false;
|
|
50902
51379
|
if (scannedFiles >= MAX_SEARCH_FILES) {
|
|
50903
51380
|
scanTruncated = true;
|
|
50904
51381
|
return true;
|
|
@@ -50988,7 +51465,7 @@ var FileService = class {
|
|
|
50988
51465
|
scannedEntries += batch.children.length;
|
|
50989
51466
|
for (const child of batch.children) {
|
|
50990
51467
|
this.#assertBeforeDeadline(deadlineAt);
|
|
50991
|
-
const target =
|
|
51468
|
+
const target = path6.join(directory, child.name);
|
|
50992
51469
|
let targetType;
|
|
50993
51470
|
if (this.#trustDirectoryEntryTypes) {
|
|
50994
51471
|
if (child.isSymbolicLink()) {
|
|
@@ -51123,6 +51600,118 @@ var FileService = class {
|
|
|
51123
51600
|
...endLine < lines.length ? { nextLine: endLine + 1 } : {}
|
|
51124
51601
|
};
|
|
51125
51602
|
}
|
|
51603
|
+
async makeDirectory(relativePath, recursive = false) {
|
|
51604
|
+
const initial = await this.policy.resolveWritableDirectory(
|
|
51605
|
+
relativePath,
|
|
51606
|
+
recursive
|
|
51607
|
+
);
|
|
51608
|
+
if (initial.exists) return { created: false };
|
|
51609
|
+
return await withFileWriteLock(initial.target, async () => {
|
|
51610
|
+
const current = await this.policy.resolveWritableDirectory(
|
|
51611
|
+
relativePath,
|
|
51612
|
+
recursive
|
|
51613
|
+
);
|
|
51614
|
+
if (current.exists) return { created: false };
|
|
51615
|
+
try {
|
|
51616
|
+
await mkdir3(current.target, { recursive });
|
|
51617
|
+
} catch (error46) {
|
|
51618
|
+
if (error46.code !== "EEXIST") throw error46;
|
|
51619
|
+
}
|
|
51620
|
+
const resolved = await this.policy.resolveExisting(relativePath);
|
|
51621
|
+
if (!(await stat2(resolved)).isDirectory()) {
|
|
51622
|
+
throw new WorkerError("not_directory", "The destination is not a directory.");
|
|
51623
|
+
}
|
|
51624
|
+
return { created: true };
|
|
51625
|
+
});
|
|
51626
|
+
}
|
|
51627
|
+
async deletePath(relativePath, recursive = false) {
|
|
51628
|
+
const initial = await this.policy.resolveExisting(relativePath);
|
|
51629
|
+
if (samePath(initial, this.policy.root)) {
|
|
51630
|
+
throw new WorkerError(
|
|
51631
|
+
"root_operation_refused",
|
|
51632
|
+
"The exposed workspace root cannot be deleted."
|
|
51633
|
+
);
|
|
51634
|
+
}
|
|
51635
|
+
return await withFileWriteLock(initial, async () => {
|
|
51636
|
+
const target = await this.policy.resolveExisting(relativePath);
|
|
51637
|
+
if (samePath(target, this.policy.root)) {
|
|
51638
|
+
throw new WorkerError(
|
|
51639
|
+
"root_operation_refused",
|
|
51640
|
+
"The exposed workspace root cannot be deleted."
|
|
51641
|
+
);
|
|
51642
|
+
}
|
|
51643
|
+
const targetStat = await lstat2(target);
|
|
51644
|
+
if (!targetStat.isFile() && !targetStat.isDirectory()) {
|
|
51645
|
+
throw new WorkerError(
|
|
51646
|
+
"unsupported_path_type",
|
|
51647
|
+
"Only regular files and directories can be deleted."
|
|
51648
|
+
);
|
|
51649
|
+
}
|
|
51650
|
+
try {
|
|
51651
|
+
if (targetStat.isDirectory()) {
|
|
51652
|
+
if (recursive) {
|
|
51653
|
+
await rm2(target, { recursive: true, force: false });
|
|
51654
|
+
} else {
|
|
51655
|
+
await rmdir(target);
|
|
51656
|
+
}
|
|
51657
|
+
} else {
|
|
51658
|
+
await rm2(target, { force: false });
|
|
51659
|
+
}
|
|
51660
|
+
} catch (error46) {
|
|
51661
|
+
const code = error46.code;
|
|
51662
|
+
if (targetStat.isDirectory() && !recursive && (code === "ENOTEMPTY" || code === "EEXIST" || code === "EPERM")) {
|
|
51663
|
+
throw new WorkerError(
|
|
51664
|
+
"directory_not_empty",
|
|
51665
|
+
"The directory is not empty. Set recursive to true to delete its contents."
|
|
51666
|
+
);
|
|
51667
|
+
}
|
|
51668
|
+
throw error46;
|
|
51669
|
+
}
|
|
51670
|
+
return {
|
|
51671
|
+
deletedType: targetStat.isDirectory() ? "directory" : "file"
|
|
51672
|
+
};
|
|
51673
|
+
});
|
|
51674
|
+
}
|
|
51675
|
+
async movePath(sourcePath, destinationPath) {
|
|
51676
|
+
const initialSource = await this.policy.resolveExisting(sourcePath);
|
|
51677
|
+
if (samePath(initialSource, this.policy.root)) {
|
|
51678
|
+
throw new WorkerError(
|
|
51679
|
+
"root_operation_refused",
|
|
51680
|
+
"The exposed workspace root cannot be moved."
|
|
51681
|
+
);
|
|
51682
|
+
}
|
|
51683
|
+
const initialDestination = await this.policy.resolveVacantPath(destinationPath);
|
|
51684
|
+
return await withFileWriteLocks(
|
|
51685
|
+
[initialSource, initialDestination],
|
|
51686
|
+
async () => {
|
|
51687
|
+
const source = await this.policy.resolveExisting(sourcePath);
|
|
51688
|
+
if (samePath(source, this.policy.root)) {
|
|
51689
|
+
throw new WorkerError(
|
|
51690
|
+
"root_operation_refused",
|
|
51691
|
+
"The exposed workspace root cannot be moved."
|
|
51692
|
+
);
|
|
51693
|
+
}
|
|
51694
|
+
const destination = await this.policy.resolveVacantPath(destinationPath);
|
|
51695
|
+
const sourceStat = await lstat2(source);
|
|
51696
|
+
if (!sourceStat.isFile() && !sourceStat.isDirectory()) {
|
|
51697
|
+
throw new WorkerError(
|
|
51698
|
+
"unsupported_path_type",
|
|
51699
|
+
"Only regular files and directories can be moved."
|
|
51700
|
+
);
|
|
51701
|
+
}
|
|
51702
|
+
if (sourceStat.isDirectory() && isPathWithin(source, destination)) {
|
|
51703
|
+
throw new WorkerError(
|
|
51704
|
+
"invalid_destination",
|
|
51705
|
+
"A directory cannot be moved inside itself."
|
|
51706
|
+
);
|
|
51707
|
+
}
|
|
51708
|
+
await rename(source, destination);
|
|
51709
|
+
return {
|
|
51710
|
+
movedType: sourceStat.isDirectory() ? "directory" : "file"
|
|
51711
|
+
};
|
|
51712
|
+
}
|
|
51713
|
+
);
|
|
51714
|
+
}
|
|
51126
51715
|
async editText(relativePath, edits, expectedSha256) {
|
|
51127
51716
|
const original = await this.readText(relativePath);
|
|
51128
51717
|
if (expectedSha256 && original.sha256 !== expectedSha256) {
|
|
@@ -51174,25 +51763,45 @@ var FileService = class {
|
|
|
51174
51763
|
return await withFileWriteLock(target, async () => {
|
|
51175
51764
|
let existingMode;
|
|
51176
51765
|
try {
|
|
51177
|
-
const existingStat = await
|
|
51766
|
+
const existingStat = await stat2(target);
|
|
51178
51767
|
if (existingStat.isFile()) existingMode = existingStat.mode & 4095;
|
|
51179
51768
|
} catch (error46) {
|
|
51180
51769
|
if (error46.code !== "ENOENT") throw error46;
|
|
51181
51770
|
}
|
|
51771
|
+
if (existingMode !== void 0 && expectedSha256 === void 0) {
|
|
51772
|
+
throw new WorkerError(
|
|
51773
|
+
"path_exists",
|
|
51774
|
+
"The file already exists. Read it first and pass expectedSha256 to replace that revision."
|
|
51775
|
+
);
|
|
51776
|
+
}
|
|
51182
51777
|
if (expectedSha256) await requireRevision(target, expectedSha256);
|
|
51183
|
-
const temporary =
|
|
51778
|
+
const temporary = path6.join(path6.dirname(target), `.glossa-${randomUUID2()}.tmp`);
|
|
51184
51779
|
try {
|
|
51185
51780
|
await writeFile3(temporary, bytes, { flag: "wx", mode: 384 });
|
|
51186
51781
|
target = await this.policy.resolveWritableFile(relativePath);
|
|
51187
|
-
const tempStat = await
|
|
51782
|
+
const tempStat = await lstat2(temporary);
|
|
51188
51783
|
if (!tempStat.isFile() || tempStat.isSymbolicLink()) {
|
|
51189
51784
|
throw new WorkerError("unsafe_temporary_file", "The atomic write temporary file changed.");
|
|
51190
51785
|
}
|
|
51191
51786
|
if (existingMode !== void 0 && process.platform !== "win32") {
|
|
51192
51787
|
await chmod2(temporary, existingMode);
|
|
51193
51788
|
}
|
|
51194
|
-
if (expectedSha256)
|
|
51195
|
-
|
|
51789
|
+
if (expectedSha256) {
|
|
51790
|
+
await requireRevision(target, expectedSha256);
|
|
51791
|
+
await rename(temporary, target);
|
|
51792
|
+
} else {
|
|
51793
|
+
try {
|
|
51794
|
+
await link2(temporary, target);
|
|
51795
|
+
} catch (error46) {
|
|
51796
|
+
if (error46.code === "EEXIST") {
|
|
51797
|
+
throw new WorkerError(
|
|
51798
|
+
"path_exists",
|
|
51799
|
+
"The file already exists. Read it first and pass expectedSha256 to replace that revision."
|
|
51800
|
+
);
|
|
51801
|
+
}
|
|
51802
|
+
throw error46;
|
|
51803
|
+
}
|
|
51804
|
+
}
|
|
51196
51805
|
} finally {
|
|
51197
51806
|
await rm2(temporary, { force: true });
|
|
51198
51807
|
}
|
|
@@ -51201,173 +51810,6 @@ var FileService = class {
|
|
|
51201
51810
|
}
|
|
51202
51811
|
};
|
|
51203
51812
|
|
|
51204
|
-
// src/worker/path-policy.ts
|
|
51205
|
-
import { lstat as lstat2, realpath, stat as stat2 } from "node:fs/promises";
|
|
51206
|
-
import os5 from "node:os";
|
|
51207
|
-
import path6 from "node:path";
|
|
51208
|
-
function samePath(left, right) {
|
|
51209
|
-
return process.platform === "win32" ? left.toLowerCase() === right.toLowerCase() : left === right;
|
|
51210
|
-
}
|
|
51211
|
-
function accountHomeDirectory() {
|
|
51212
|
-
try {
|
|
51213
|
-
return os5.userInfo().homedir;
|
|
51214
|
-
} catch {
|
|
51215
|
-
return os5.homedir();
|
|
51216
|
-
}
|
|
51217
|
-
}
|
|
51218
|
-
function isWithin(root, candidate) {
|
|
51219
|
-
const relative = path6.relative(root, candidate);
|
|
51220
|
-
return relative === "" || !relative.startsWith(`..${path6.sep}`) && relative !== ".." && !path6.isAbsolute(relative);
|
|
51221
|
-
}
|
|
51222
|
-
function validateRelativePath(value) {
|
|
51223
|
-
if (value.includes("\0")) {
|
|
51224
|
-
throw new WorkerError("invalid_path", "Paths cannot contain null bytes.");
|
|
51225
|
-
}
|
|
51226
|
-
const explicitNativePosixPath = process.platform !== "win32" && value.startsWith("./");
|
|
51227
|
-
if (path6.isAbsolute(value) || path6.posix.isAbsolute(value) || !explicitNativePosixPath && path6.win32.isAbsolute(value)) {
|
|
51228
|
-
throw new WorkerError("absolute_path", "Absolute paths are not allowed.");
|
|
51229
|
-
}
|
|
51230
|
-
const segments = explicitNativePosixPath ? value.split(/\/+/).filter(Boolean) : value.split(/[\\/]+/);
|
|
51231
|
-
if (segments.includes("..")) {
|
|
51232
|
-
throw new WorkerError("path_traversal", "Parent path traversal is not allowed.");
|
|
51233
|
-
}
|
|
51234
|
-
return value === "" ? "." : value;
|
|
51235
|
-
}
|
|
51236
|
-
async function canonicalizeRoot(candidate) {
|
|
51237
|
-
const root = await realpath(path6.resolve(candidate)).catch((error46) => {
|
|
51238
|
-
if (error46.code === "ENOENT") {
|
|
51239
|
-
throw new WorkerError("root_not_found", "The workspace directory does not exist.");
|
|
51240
|
-
}
|
|
51241
|
-
throw error46;
|
|
51242
|
-
});
|
|
51243
|
-
const rootStat = await stat2(root);
|
|
51244
|
-
if (!rootStat.isDirectory()) {
|
|
51245
|
-
throw new WorkerError("root_not_directory", "The exposed root must be a directory.");
|
|
51246
|
-
}
|
|
51247
|
-
const filesystemRoot = path6.parse(root).root;
|
|
51248
|
-
const homes = await Promise.all(
|
|
51249
|
-
[os5.homedir(), accountHomeDirectory()].map(
|
|
51250
|
-
async (home) => await realpath(home).catch(() => path6.resolve(home))
|
|
51251
|
-
)
|
|
51252
|
-
);
|
|
51253
|
-
const isHomeDirectory = homes.some((home) => samePath(root, home));
|
|
51254
|
-
if (samePath(root, filesystemRoot) || isHomeDirectory) {
|
|
51255
|
-
const kind = isHomeDirectory ? "your home directory" : "a filesystem root";
|
|
51256
|
-
throw new WorkerError(
|
|
51257
|
-
"broad_root_refused",
|
|
51258
|
-
`The selected root is ${kind}, which Glossa will not expose. Choose a project directory instead.`
|
|
51259
|
-
);
|
|
51260
|
-
}
|
|
51261
|
-
return root;
|
|
51262
|
-
}
|
|
51263
|
-
var PathPolicy = class _PathPolicy {
|
|
51264
|
-
constructor(root) {
|
|
51265
|
-
this.root = root;
|
|
51266
|
-
}
|
|
51267
|
-
root;
|
|
51268
|
-
static async create(candidate) {
|
|
51269
|
-
return new _PathPolicy(await canonicalizeRoot(candidate));
|
|
51270
|
-
}
|
|
51271
|
-
async resolveExisting(relativePath) {
|
|
51272
|
-
const lexical = this.resolveLexical(relativePath);
|
|
51273
|
-
await this.rejectLinkedComponents(lexical);
|
|
51274
|
-
const canonical = await realpath(lexical).catch((error46) => {
|
|
51275
|
-
if (error46.code === "ENOENT") {
|
|
51276
|
-
throw new WorkerError("path_not_found", "The requested path does not exist.");
|
|
51277
|
-
}
|
|
51278
|
-
throw error46;
|
|
51279
|
-
});
|
|
51280
|
-
if (!isWithin(this.root, canonical)) {
|
|
51281
|
-
throw new WorkerError("path_escape", "The requested path escapes the exposed root.");
|
|
51282
|
-
}
|
|
51283
|
-
return canonical;
|
|
51284
|
-
}
|
|
51285
|
-
async resolveDiscoveredExisting(candidate) {
|
|
51286
|
-
const lexical = path6.resolve(candidate);
|
|
51287
|
-
if (!isWithin(this.root, lexical)) {
|
|
51288
|
-
throw new WorkerError("path_escape", "The requested path escapes the exposed root.");
|
|
51289
|
-
}
|
|
51290
|
-
await this.rejectLinkedComponents(lexical);
|
|
51291
|
-
const canonical = await realpath(lexical).catch((error46) => {
|
|
51292
|
-
if (error46.code === "ENOENT") {
|
|
51293
|
-
throw new WorkerError("path_not_found", "The requested path does not exist.");
|
|
51294
|
-
}
|
|
51295
|
-
throw error46;
|
|
51296
|
-
});
|
|
51297
|
-
if (!isWithin(this.root, canonical)) {
|
|
51298
|
-
throw new WorkerError("path_escape", "The requested path escapes the exposed root.");
|
|
51299
|
-
}
|
|
51300
|
-
return canonical;
|
|
51301
|
-
}
|
|
51302
|
-
async resolveWritableFile(relativePath) {
|
|
51303
|
-
const lexical = this.resolveLexical(relativePath);
|
|
51304
|
-
const parent = path6.dirname(lexical);
|
|
51305
|
-
await this.rejectLinkedComponents(parent);
|
|
51306
|
-
const canonicalParent = await realpath(parent).catch((error46) => {
|
|
51307
|
-
if (error46.code === "ENOENT") {
|
|
51308
|
-
throw new WorkerError("parent_not_found", "The destination directory does not exist.");
|
|
51309
|
-
}
|
|
51310
|
-
throw error46;
|
|
51311
|
-
});
|
|
51312
|
-
if (!isWithin(this.root, canonicalParent)) {
|
|
51313
|
-
throw new WorkerError("path_escape", "The destination escapes the exposed root.");
|
|
51314
|
-
}
|
|
51315
|
-
if (!(await stat2(canonicalParent)).isDirectory()) {
|
|
51316
|
-
throw new WorkerError("not_directory", "The destination parent is not a directory.");
|
|
51317
|
-
}
|
|
51318
|
-
try {
|
|
51319
|
-
const targetStat = await lstat2(lexical);
|
|
51320
|
-
if (targetStat.isSymbolicLink()) {
|
|
51321
|
-
throw new WorkerError("linked_path", "Writes through links are not allowed.");
|
|
51322
|
-
}
|
|
51323
|
-
if (targetStat.isDirectory()) {
|
|
51324
|
-
throw new WorkerError("not_file", "The destination is a directory.");
|
|
51325
|
-
}
|
|
51326
|
-
const canonicalTarget = await realpath(lexical);
|
|
51327
|
-
if (!isWithin(this.root, canonicalTarget)) {
|
|
51328
|
-
throw new WorkerError("path_escape", "The destination escapes the exposed root.");
|
|
51329
|
-
}
|
|
51330
|
-
return canonicalTarget;
|
|
51331
|
-
} catch (error46) {
|
|
51332
|
-
if (error46 instanceof WorkerError) throw error46;
|
|
51333
|
-
if (error46.code !== "ENOENT") throw error46;
|
|
51334
|
-
}
|
|
51335
|
-
return path6.join(canonicalParent, path6.basename(lexical));
|
|
51336
|
-
}
|
|
51337
|
-
resolveLexical(relativePath) {
|
|
51338
|
-
const validated = validateRelativePath(relativePath);
|
|
51339
|
-
const candidate = path6.resolve(this.root, validated);
|
|
51340
|
-
if (!isWithin(this.root, candidate)) {
|
|
51341
|
-
throw new WorkerError("path_escape", "The requested path escapes the exposed root.");
|
|
51342
|
-
}
|
|
51343
|
-
return candidate;
|
|
51344
|
-
}
|
|
51345
|
-
async rejectLinkedComponents(candidate) {
|
|
51346
|
-
if (!isWithin(this.root, candidate)) {
|
|
51347
|
-
throw new WorkerError("path_escape", "The requested path escapes the exposed root.");
|
|
51348
|
-
}
|
|
51349
|
-
const relative = path6.relative(this.root, candidate);
|
|
51350
|
-
if (!relative) return;
|
|
51351
|
-
let current = this.root;
|
|
51352
|
-
for (const segment of relative.split(path6.sep)) {
|
|
51353
|
-
current = path6.join(current, segment);
|
|
51354
|
-
try {
|
|
51355
|
-
const currentStat = await lstat2(current);
|
|
51356
|
-
if (currentStat.isSymbolicLink()) {
|
|
51357
|
-
throw new WorkerError(
|
|
51358
|
-
"linked_path",
|
|
51359
|
-
"Symlink and junction paths are not allowed."
|
|
51360
|
-
);
|
|
51361
|
-
}
|
|
51362
|
-
} catch (error46) {
|
|
51363
|
-
if (error46 instanceof WorkerError) throw error46;
|
|
51364
|
-
if (error46.code === "ENOENT") return;
|
|
51365
|
-
throw error46;
|
|
51366
|
-
}
|
|
51367
|
-
}
|
|
51368
|
-
}
|
|
51369
|
-
};
|
|
51370
|
-
|
|
51371
51813
|
// src/worker/local-worker.ts
|
|
51372
51814
|
function restrictedDataError2() {
|
|
51373
51815
|
return new WorkerError(
|
|
@@ -51402,6 +51844,14 @@ function jobInputContainsRestrictedData(job) {
|
|
|
51402
51844
|
path: job.path,
|
|
51403
51845
|
edits: job.edits
|
|
51404
51846
|
});
|
|
51847
|
+
case "make_directory":
|
|
51848
|
+
case "delete_path":
|
|
51849
|
+
return containsRestrictedAuthenticationData(job.path);
|
|
51850
|
+
case "move_path":
|
|
51851
|
+
return containsRestrictedAuthenticationData({
|
|
51852
|
+
source: job.source,
|
|
51853
|
+
destination: job.destination
|
|
51854
|
+
});
|
|
51405
51855
|
case "run_command":
|
|
51406
51856
|
return containsRestrictedAuthenticationData({
|
|
51407
51857
|
argv: job.argv,
|
|
@@ -51409,12 +51859,13 @@ function jobInputContainsRestrictedData(job) {
|
|
|
51409
51859
|
stdin: job.stdin
|
|
51410
51860
|
});
|
|
51411
51861
|
case "get_command":
|
|
51862
|
+
case "read_command_output":
|
|
51412
51863
|
case "cancel_command":
|
|
51413
51864
|
return false;
|
|
51414
51865
|
}
|
|
51415
51866
|
}
|
|
51416
51867
|
function resultMayContainRestrictedData(job) {
|
|
51417
|
-
return job.type === "read_file" || job.type === "list_files" || job.type === "search_text" || job.type === "read_file_range" || job.type === "edit_file" || job.type === "run_command" || job.type === "get_command" || job.type === "cancel_command";
|
|
51868
|
+
return job.type === "read_file" || job.type === "list_files" || job.type === "search_text" || job.type === "read_file_range" || job.type === "edit_file" || job.type === "run_command" || job.type === "get_command" || job.type === "read_command_output" || job.type === "cancel_command";
|
|
51418
51869
|
}
|
|
51419
51870
|
var LocalWorker = class _LocalWorker {
|
|
51420
51871
|
constructor(accessProfile, policy, files, commands) {
|
|
@@ -51439,13 +51890,13 @@ var LocalWorker = class _LocalWorker {
|
|
|
51439
51890
|
async handle(job) {
|
|
51440
51891
|
try {
|
|
51441
51892
|
const permissions = workerPermissions(this.accessProfile);
|
|
51442
|
-
if ((job.type === "write_file" || job.type === "edit_file") && !permissions.writeFiles) {
|
|
51893
|
+
if ((job.type === "write_file" || job.type === "edit_file" || job.type === "make_directory" || job.type === "delete_path" || job.type === "move_path") && !permissions.writeFiles) {
|
|
51443
51894
|
throw new WorkerError(
|
|
51444
51895
|
"write_access_disabled",
|
|
51445
51896
|
"This worker was started without file-write access."
|
|
51446
51897
|
);
|
|
51447
51898
|
}
|
|
51448
|
-
if ((job.type === "run_command" || job.type === "get_command" || job.type === "cancel_command") && !permissions.runCommands) {
|
|
51899
|
+
if ((job.type === "run_command" || job.type === "get_command" || job.type === "read_command_output" || job.type === "cancel_command") && !permissions.runCommands) {
|
|
51449
51900
|
throw new WorkerError(
|
|
51450
51901
|
"command_access_disabled",
|
|
51451
51902
|
"This worker was started without system-command access."
|
|
@@ -51470,9 +51921,12 @@ var LocalWorker = class _LocalWorker {
|
|
|
51470
51921
|
value = await this.files.searchText({
|
|
51471
51922
|
query: job.query,
|
|
51472
51923
|
...job.path ? { path: job.path } : {},
|
|
51924
|
+
...job.matchMode === void 0 ? {} : { matchMode: job.matchMode },
|
|
51473
51925
|
...job.caseSensitive === void 0 ? {} : { caseSensitive: job.caseSensitive },
|
|
51474
51926
|
...job.maxResults === void 0 ? {} : { maxResults: job.maxResults },
|
|
51475
51927
|
...job.extensions ? { extensions: job.extensions } : {},
|
|
51928
|
+
...job.includeGlobs ? { includeGlobs: job.includeGlobs } : {},
|
|
51929
|
+
...job.excludeGlobs ? { excludeGlobs: job.excludeGlobs } : {},
|
|
51476
51930
|
timeoutMs: job.timeoutMs
|
|
51477
51931
|
});
|
|
51478
51932
|
break;
|
|
@@ -51503,6 +51957,15 @@ var LocalWorker = class _LocalWorker {
|
|
|
51503
51957
|
);
|
|
51504
51958
|
break;
|
|
51505
51959
|
}
|
|
51960
|
+
case "make_directory":
|
|
51961
|
+
value = await this.files.makeDirectory(job.path, job.recursive);
|
|
51962
|
+
break;
|
|
51963
|
+
case "delete_path":
|
|
51964
|
+
value = await this.files.deletePath(job.path, job.recursive);
|
|
51965
|
+
break;
|
|
51966
|
+
case "move_path":
|
|
51967
|
+
value = await this.files.movePath(job.source, job.destination);
|
|
51968
|
+
break;
|
|
51506
51969
|
case "run_command":
|
|
51507
51970
|
value = await this.commands.start({
|
|
51508
51971
|
...job.argv ? { argv: job.argv } : {},
|
|
@@ -51519,6 +51982,14 @@ var LocalWorker = class _LocalWorker {
|
|
|
51519
51982
|
job.afterSequence
|
|
51520
51983
|
);
|
|
51521
51984
|
break;
|
|
51985
|
+
case "read_command_output":
|
|
51986
|
+
value = await this.commands.readOutput(
|
|
51987
|
+
job.commandId,
|
|
51988
|
+
job.stream,
|
|
51989
|
+
job.offset,
|
|
51990
|
+
job.maxBytes
|
|
51991
|
+
);
|
|
51992
|
+
break;
|
|
51522
51993
|
case "cancel_command":
|
|
51523
51994
|
value = await this.commands.cancel(job.commandId);
|
|
51524
51995
|
break;
|
|
@@ -51581,6 +52052,7 @@ function supportsCapability(value, capability) {
|
|
|
51581
52052
|
function jobLane(job) {
|
|
51582
52053
|
switch (job.type) {
|
|
51583
52054
|
case "get_command":
|
|
52055
|
+
case "read_command_output":
|
|
51584
52056
|
return "status";
|
|
51585
52057
|
case "cancel_command":
|
|
51586
52058
|
return "cancel";
|
|
@@ -51591,14 +52063,20 @@ function jobLane(job) {
|
|
|
51591
52063
|
return "read";
|
|
51592
52064
|
case "write_file":
|
|
51593
52065
|
case "edit_file":
|
|
52066
|
+
case "make_directory":
|
|
52067
|
+
case "delete_path":
|
|
52068
|
+
case "move_path":
|
|
51594
52069
|
case "run_command":
|
|
51595
52070
|
return "mutation";
|
|
51596
52071
|
}
|
|
51597
52072
|
}
|
|
51598
|
-
function acceptedJobTypes(counts, total, structuredReads) {
|
|
52073
|
+
function acceptedJobTypes(counts, total, structuredReads, structuredMutations, commandOutputRanges) {
|
|
51599
52074
|
if (total >= MAX_CONCURRENT_JOBS) return [];
|
|
51600
52075
|
const accepted = [];
|
|
51601
|
-
if (counts.status < 1)
|
|
52076
|
+
if (counts.status < 1) {
|
|
52077
|
+
accepted.push("get_command");
|
|
52078
|
+
if (commandOutputRanges) accepted.push("read_command_output");
|
|
52079
|
+
}
|
|
51602
52080
|
if (counts.cancel < 1) accepted.push("cancel_command");
|
|
51603
52081
|
if (counts.read < 2) {
|
|
51604
52082
|
accepted.push("read_file");
|
|
@@ -51608,6 +52086,9 @@ function acceptedJobTypes(counts, total, structuredReads) {
|
|
|
51608
52086
|
}
|
|
51609
52087
|
if (counts.mutation < 1) {
|
|
51610
52088
|
accepted.push("write_file", "edit_file", "run_command");
|
|
52089
|
+
if (structuredMutations) {
|
|
52090
|
+
accepted.push("make_directory", "delete_path", "move_path");
|
|
52091
|
+
}
|
|
51611
52092
|
}
|
|
51612
52093
|
return accepted;
|
|
51613
52094
|
}
|
|
@@ -51712,6 +52193,25 @@ var RemoteWorker = class {
|
|
|
51712
52193
|
}
|
|
51713
52194
|
}
|
|
51714
52195
|
async #register() {
|
|
52196
|
+
const currentBody = {
|
|
52197
|
+
workerId: this.#workerId,
|
|
52198
|
+
capabilities: {
|
|
52199
|
+
commandProgress: true,
|
|
52200
|
+
concurrentJobs: true,
|
|
52201
|
+
structuredReads: true,
|
|
52202
|
+
structuredMutations: true,
|
|
52203
|
+
commandOutputRanges: true
|
|
52204
|
+
}
|
|
52205
|
+
};
|
|
52206
|
+
const mutationBody = {
|
|
52207
|
+
workerId: this.#workerId,
|
|
52208
|
+
capabilities: {
|
|
52209
|
+
commandProgress: true,
|
|
52210
|
+
concurrentJobs: true,
|
|
52211
|
+
structuredReads: true,
|
|
52212
|
+
structuredMutations: true
|
|
52213
|
+
}
|
|
52214
|
+
};
|
|
51715
52215
|
const structuredBody = {
|
|
51716
52216
|
workerId: this.#workerId,
|
|
51717
52217
|
capabilities: {
|
|
@@ -51724,14 +52224,54 @@ var RemoteWorker = class {
|
|
|
51724
52224
|
workerId: this.#workerId,
|
|
51725
52225
|
capabilities: { commandProgress: true, concurrentJobs: true }
|
|
51726
52226
|
};
|
|
52227
|
+
const versionedCurrentBody = this.#workerVersion ? { ...currentBody, workerVersion: this.#workerVersion } : void 0;
|
|
52228
|
+
const versionedMutationBody = this.#workerVersion ? { ...mutationBody, workerVersion: this.#workerVersion } : void 0;
|
|
51727
52229
|
const versionedStructuredBody = this.#workerVersion ? { ...structuredBody, workerVersion: this.#workerVersion } : void 0;
|
|
51728
52230
|
const preferredProfileBody = this.#accessProfile ? {
|
|
51729
|
-
...
|
|
52231
|
+
...versionedCurrentBody ?? currentBody,
|
|
51730
52232
|
accessProfile: this.#accessProfile,
|
|
51731
52233
|
...this.#workspaceLabel ? { workspaceLabel: this.#workspaceLabel } : {}
|
|
51732
52234
|
} : void 0;
|
|
51733
52235
|
const attempts = [
|
|
51734
52236
|
...preferredProfileBody ? [{ body: preferredProfileBody, legacyRelay: false }] : [],
|
|
52237
|
+
...versionedCurrentBody && this.#workspaceLabel ? [{
|
|
52238
|
+
body: {
|
|
52239
|
+
...versionedCurrentBody,
|
|
52240
|
+
workspaceLabel: this.#workspaceLabel
|
|
52241
|
+
},
|
|
52242
|
+
legacyRelay: false
|
|
52243
|
+
}] : [],
|
|
52244
|
+
...this.#workspaceLabel ? [{
|
|
52245
|
+
body: {
|
|
52246
|
+
...currentBody,
|
|
52247
|
+
workspaceLabel: this.#workspaceLabel
|
|
52248
|
+
},
|
|
52249
|
+
legacyRelay: false
|
|
52250
|
+
}] : [],
|
|
52251
|
+
...versionedCurrentBody ? [{ body: versionedCurrentBody, legacyRelay: false }] : [],
|
|
52252
|
+
{
|
|
52253
|
+
body: currentBody,
|
|
52254
|
+
legacyRelay: false
|
|
52255
|
+
},
|
|
52256
|
+
...versionedMutationBody && this.#workspaceLabel ? [{
|
|
52257
|
+
body: {
|
|
52258
|
+
...versionedMutationBody,
|
|
52259
|
+
workspaceLabel: this.#workspaceLabel
|
|
52260
|
+
},
|
|
52261
|
+
legacyRelay: false
|
|
52262
|
+
}] : [],
|
|
52263
|
+
...this.#workspaceLabel ? [{
|
|
52264
|
+
body: {
|
|
52265
|
+
...mutationBody,
|
|
52266
|
+
workspaceLabel: this.#workspaceLabel
|
|
52267
|
+
},
|
|
52268
|
+
legacyRelay: false
|
|
52269
|
+
}] : [],
|
|
52270
|
+
...versionedMutationBody ? [{ body: versionedMutationBody, legacyRelay: false }] : [],
|
|
52271
|
+
{
|
|
52272
|
+
body: mutationBody,
|
|
52273
|
+
legacyRelay: false
|
|
52274
|
+
},
|
|
51735
52275
|
...versionedStructuredBody && this.#workspaceLabel ? [{
|
|
51736
52276
|
body: {
|
|
51737
52277
|
...versionedStructuredBody,
|
|
@@ -51795,6 +52335,8 @@ var RemoteWorker = class {
|
|
|
51795
52335
|
legacyRelay: attempt.legacyRelay,
|
|
51796
52336
|
concurrentJobs: !attempt.legacyRelay && supportsCapability(value, "concurrentJobs"),
|
|
51797
52337
|
structuredReads: !attempt.legacyRelay && supportsCapability(value, "structuredReads"),
|
|
52338
|
+
structuredMutations: !attempt.legacyRelay && supportsCapability(value, "structuredMutations"),
|
|
52339
|
+
commandOutputRanges: !attempt.legacyRelay && supportsCapability(value, "commandOutputRanges"),
|
|
51798
52340
|
accessProfileAccepted: this.#accessProfile === void 0 || "accessProfile" in value && value.accessProfile === this.#accessProfile,
|
|
51799
52341
|
workspaceLabelAccepted: this.#workspaceLabel === void 0 || "workspaceLabel" in value && value.workspaceLabel === this.#workspaceLabel,
|
|
51800
52342
|
...workerToken ? { workerToken } : {}
|
|
@@ -51889,7 +52431,9 @@ var RemoteWorker = class {
|
|
|
51889
52431
|
const acceptedTypes = acceptedJobTypes(
|
|
51890
52432
|
counts,
|
|
51891
52433
|
inFlight.size,
|
|
51892
|
-
session.structuredReads
|
|
52434
|
+
session.structuredReads,
|
|
52435
|
+
session.structuredMutations,
|
|
52436
|
+
session.commandOutputRanges
|
|
51893
52437
|
);
|
|
51894
52438
|
if (acceptedTypes.length === 0) {
|
|
51895
52439
|
await Promise.race(inFlight);
|
|
@@ -51919,7 +52463,9 @@ var RemoteWorker = class {
|
|
|
51919
52463
|
const refreshedTypes = acceptedJobTypes(
|
|
51920
52464
|
counts,
|
|
51921
52465
|
inFlight.size,
|
|
51922
|
-
session.structuredReads
|
|
52466
|
+
session.structuredReads,
|
|
52467
|
+
session.structuredMutations,
|
|
52468
|
+
session.commandOutputRanges
|
|
51923
52469
|
);
|
|
51924
52470
|
const newlyAcceptedTypes = refreshedTypes.filter(
|
|
51925
52471
|
(type) => !acceptedTypes.includes(type)
|
|
@@ -52099,6 +52645,15 @@ function activitySafeJob(job) {
|
|
|
52099
52645
|
newText: ""
|
|
52100
52646
|
}]
|
|
52101
52647
|
};
|
|
52648
|
+
case "make_directory":
|
|
52649
|
+
case "delete_path":
|
|
52650
|
+
return { ...job, path: "[restricted input blocked]" };
|
|
52651
|
+
case "move_path":
|
|
52652
|
+
return {
|
|
52653
|
+
...job,
|
|
52654
|
+
source: "[restricted input blocked]",
|
|
52655
|
+
destination: "[restricted input blocked]"
|
|
52656
|
+
};
|
|
52102
52657
|
case "run_command":
|
|
52103
52658
|
return {
|
|
52104
52659
|
type: "run_command",
|
|
@@ -52108,6 +52663,7 @@ function activitySafeJob(job) {
|
|
|
52108
52663
|
...job.waitMs === void 0 ? {} : { waitMs: job.waitMs }
|
|
52109
52664
|
};
|
|
52110
52665
|
case "get_command":
|
|
52666
|
+
case "read_command_output":
|
|
52111
52667
|
case "cancel_command":
|
|
52112
52668
|
return job;
|
|
52113
52669
|
}
|
|
@@ -52541,6 +53097,16 @@ function summarizeJob(job) {
|
|
|
52541
53097
|
`${job.edits.length} ${job.edits.length === 1 ? "edit" : "edits"}`,
|
|
52542
53098
|
...job.expectedSha256 ? ["guarded"] : []
|
|
52543
53099
|
]);
|
|
53100
|
+
case "make_directory":
|
|
53101
|
+
return pathSummary(job.path, job.recursive ? ["recursive"] : []);
|
|
53102
|
+
case "delete_path":
|
|
53103
|
+
return pathSummary(job.path, job.recursive ? ["recursive"] : []);
|
|
53104
|
+
case "move_path":
|
|
53105
|
+
return {
|
|
53106
|
+
target: `${quoteActivityInput(job.source)} \u2192 ${quoteActivityInput(job.destination)}`,
|
|
53107
|
+
details: [],
|
|
53108
|
+
truncation: "middle"
|
|
53109
|
+
};
|
|
52544
53110
|
case "run_command":
|
|
52545
53111
|
return {
|
|
52546
53112
|
target: job.argv ? `argv [${job.argv.map(quoteActivityInput).join(", ")}]` : `shell ${quoteActivityInput(job.shellCommand ?? "")}`,
|
|
@@ -52560,6 +53126,15 @@ function summarizeJob(job) {
|
|
|
52560
53126
|
],
|
|
52561
53127
|
truncation: "middle"
|
|
52562
53128
|
};
|
|
53129
|
+
case "read_command_output":
|
|
53130
|
+
return {
|
|
53131
|
+
target: `command ${job.commandId} ${job.stream}`,
|
|
53132
|
+
details: [
|
|
53133
|
+
...job.offset === void 0 ? [] : [`offset ${job.offset}`],
|
|
53134
|
+
...job.maxBytes === void 0 ? [] : [`max ${job.maxBytes} bytes`]
|
|
53135
|
+
],
|
|
53136
|
+
truncation: "middle"
|
|
53137
|
+
};
|
|
52563
53138
|
case "cancel_command":
|
|
52564
53139
|
return {
|
|
52565
53140
|
target: `command ${job.commandId}`,
|
|
@@ -53504,7 +54079,7 @@ import path8 from "node:path";
|
|
|
53504
54079
|
|
|
53505
54080
|
// src/update-lock.ts
|
|
53506
54081
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
53507
|
-
import { mkdir as
|
|
54082
|
+
import { mkdir as mkdir4, readdir, rm as rm3, writeFile as writeFile4 } from "node:fs/promises";
|
|
53508
54083
|
import path7 from "node:path";
|
|
53509
54084
|
var UPDATE_SUFFIX = ".update";
|
|
53510
54085
|
var SESSION_SUFFIX = ".session";
|
|
@@ -53552,7 +54127,7 @@ async function activeSessionFiles(directory) {
|
|
|
53552
54127
|
return await activeLeaseFiles(directory, SESSION_SUFFIX);
|
|
53553
54128
|
}
|
|
53554
54129
|
async function createUpdateLock(directory) {
|
|
53555
|
-
await
|
|
54130
|
+
await mkdir4(directory, { recursive: true, mode: 448 });
|
|
53556
54131
|
const lockFile = path7.join(
|
|
53557
54132
|
directory,
|
|
53558
54133
|
`${process.pid}-${randomUUID4()}${UPDATE_SUFFIX}`
|
|
@@ -53587,7 +54162,7 @@ async function withUpdateLease(action, directory = updateRuntimeDirectory()) {
|
|
|
53587
54162
|
}
|
|
53588
54163
|
}
|
|
53589
54164
|
async function withWorkspaceLease(action, directory = updateRuntimeDirectory()) {
|
|
53590
|
-
await
|
|
54165
|
+
await mkdir4(directory, { recursive: true, mode: 448 });
|
|
53591
54166
|
if (await updateLockIsActive(directory)) {
|
|
53592
54167
|
throw new Error("Glossa is updating. Run this workspace again after the update finishes.");
|
|
53593
54168
|
}
|
|
@@ -53725,14 +54300,25 @@ async function fetchText(url2, fetchImpl, version2) {
|
|
|
53725
54300
|
if (!response.ok) throw new Error(`Glossa could not download ${url2} (HTTP ${response.status}).`);
|
|
53726
54301
|
return await response.text();
|
|
53727
54302
|
}
|
|
54303
|
+
function npmInstallInvocation(version2, platform2 = process.platform, commandShell = process.env.ComSpec ?? "cmd.exe") {
|
|
54304
|
+
const normalizedVersion = import_semver.default.valid(version2);
|
|
54305
|
+
if (!normalizedVersion) throw new Error(`Glossa version ${version2} is invalid.`);
|
|
54306
|
+
const packageSpec = `${PACKAGE_NAME}@${normalizedVersion}`;
|
|
54307
|
+
if (platform2 === "win32") {
|
|
54308
|
+
return {
|
|
54309
|
+
command: commandShell,
|
|
54310
|
+
args: ["/d", "/s", "/c", `npm.cmd install --global ${packageSpec}`]
|
|
54311
|
+
};
|
|
54312
|
+
}
|
|
54313
|
+
return {
|
|
54314
|
+
command: "npm",
|
|
54315
|
+
args: ["install", "--global", packageSpec]
|
|
54316
|
+
};
|
|
54317
|
+
}
|
|
53728
54318
|
async function runNpmInstall(version2) {
|
|
53729
|
-
const command
|
|
54319
|
+
const { command, args } = npmInstallInvocation(version2);
|
|
53730
54320
|
await new Promise((resolve, reject) => {
|
|
53731
|
-
const child = spawn3(
|
|
53732
|
-
command,
|
|
53733
|
-
["install", "--global", `${PACKAGE_NAME}@${version2}`],
|
|
53734
|
-
{ stdio: "inherit" }
|
|
53735
|
-
);
|
|
54321
|
+
const child = spawn3(command, args, { stdio: "inherit" });
|
|
53736
54322
|
child.once("error", reject);
|
|
53737
54323
|
child.once("close", (status) => {
|
|
53738
54324
|
if (status === 0) resolve();
|
|
@@ -53795,7 +54381,7 @@ async function installUpdate(info, distribution, options = {}) {
|
|
|
53795
54381
|
}
|
|
53796
54382
|
|
|
53797
54383
|
// src/update-state.ts
|
|
53798
|
-
import { chmod as chmod4, mkdir as
|
|
54384
|
+
import { chmod as chmod4, mkdir as mkdir5, readFile as readFile4, rename as rename3, rm as rm5, writeFile as writeFile6 } from "node:fs/promises";
|
|
53799
54385
|
import path9 from "node:path";
|
|
53800
54386
|
var UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
53801
54387
|
function defaultUpdateChannel(version2) {
|
|
@@ -53838,7 +54424,7 @@ async function loadUpdateState(currentVersion, file2 = updateStateFile()) {
|
|
|
53838
54424
|
async function saveUpdateState(state, file2 = updateStateFile()) {
|
|
53839
54425
|
const directory = path9.dirname(file2);
|
|
53840
54426
|
const temporary = `${file2}.${process.pid}.tmp`;
|
|
53841
|
-
await
|
|
54427
|
+
await mkdir5(directory, { recursive: true, mode: 448 });
|
|
53842
54428
|
try {
|
|
53843
54429
|
await writeFile6(temporary, `${JSON.stringify(state, null, 2)}
|
|
53844
54430
|
`, {
|
|
@@ -53876,14 +54462,14 @@ function isUpdateCheckDue(lastCheckedAt, now = Date.now()) {
|
|
|
53876
54462
|
}
|
|
53877
54463
|
|
|
53878
54464
|
// src/usage-store.ts
|
|
53879
|
-
import { appendFile, chmod as chmod5, mkdir as
|
|
54465
|
+
import { appendFile, chmod as chmod5, mkdir as mkdir6, readFile as readFile5 } from "node:fs/promises";
|
|
53880
54466
|
import path10 from "node:path";
|
|
53881
54467
|
function usageFile() {
|
|
53882
54468
|
return path10.join(configDirectory(), "usage.jsonl");
|
|
53883
54469
|
}
|
|
53884
54470
|
async function recordUsageEvent(event, file2 = usageFile()) {
|
|
53885
54471
|
const directory = path10.dirname(file2);
|
|
53886
|
-
await
|
|
54472
|
+
await mkdir6(directory, { recursive: true, mode: 448 });
|
|
53887
54473
|
await appendFile(file2, `${JSON.stringify(event)}
|
|
53888
54474
|
`, {
|
|
53889
54475
|
encoding: "utf8",
|
|
@@ -53927,7 +54513,7 @@ async function selectExposureRoot(explicitPath, cwd2 = process.cwd()) {
|
|
|
53927
54513
|
|
|
53928
54514
|
// src/worker/workspace-lease.ts
|
|
53929
54515
|
import { createHash as createHash3 } from "node:crypto";
|
|
53930
|
-
import { lstat as lstat3, mkdir as
|
|
54516
|
+
import { lstat as lstat3, mkdir as mkdir7, rm as rm6, stat as stat3 } from "node:fs/promises";
|
|
53931
54517
|
import net from "node:net";
|
|
53932
54518
|
import path12 from "node:path";
|
|
53933
54519
|
import { setTimeout as delay3 } from "node:timers/promises";
|
|
@@ -53958,7 +54544,7 @@ function defaultLeaseDirectory() {
|
|
|
53958
54544
|
return path12.join(accountHomeDirectory(), ".glossa-workspace-leases");
|
|
53959
54545
|
}
|
|
53960
54546
|
async function ensureLeaseDirectory(directory) {
|
|
53961
|
-
await
|
|
54547
|
+
await mkdir7(directory, { recursive: true, mode: 448 });
|
|
53962
54548
|
const directoryStat = await lstat3(directory);
|
|
53963
54549
|
if (directoryStat.isSymbolicLink() || !directoryStat.isDirectory()) {
|
|
53964
54550
|
throw new Error("The Glossa workspace lease location is not a private directory.");
|
|
@@ -53982,7 +54568,7 @@ async function acquireGuard(directory, identity) {
|
|
|
53982
54568
|
const deadline = Date.now() + GUARD_TIMEOUT_MS;
|
|
53983
54569
|
while (true) {
|
|
53984
54570
|
try {
|
|
53985
|
-
await
|
|
54571
|
+
await mkdir7(guard, { mode: 448 });
|
|
53986
54572
|
return async () => await rm6(guard, { recursive: true, force: true });
|
|
53987
54573
|
} catch (error46) {
|
|
53988
54574
|
if (error46.code !== "EEXIST") throw error46;
|
|
@@ -54077,10 +54663,10 @@ async function acquireWorkspaceLease(root, options = {}) {
|
|
|
54077
54663
|
};
|
|
54078
54664
|
} catch (error46) {
|
|
54079
54665
|
await closeServer(server);
|
|
54080
|
-
if (error46.code !== "EADDRINUSE") throw error46;
|
|
54081
54666
|
if (await endpointIsActive(endpoint3)) {
|
|
54082
54667
|
throw new WorkspaceAlreadyActiveError();
|
|
54083
54668
|
}
|
|
54669
|
+
if (error46.code !== "EADDRINUSE") throw error46;
|
|
54084
54670
|
if (process.platform !== "win32") await rm6(endpoint3, { force: true });
|
|
54085
54671
|
}
|
|
54086
54672
|
}
|
|
@@ -54091,7 +54677,7 @@ async function acquireWorkspaceLease(root, options = {}) {
|
|
|
54091
54677
|
}
|
|
54092
54678
|
|
|
54093
54679
|
// src/main.ts
|
|
54094
|
-
var VERSION = "0.
|
|
54680
|
+
var VERSION = "0.2.0-beta.1";
|
|
54095
54681
|
var DISTRIBUTION = "npm";
|
|
54096
54682
|
var HELP = `Glossa ${VERSION}
|
|
54097
54683
|
|