@smthrs/std 1.0.0-rc.7 → 1.0.0-rc.9
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/CHANGELOG.md +14 -0
- package/README.md +40 -36
- package/dist/cjs/Checkpoints.d.ts +37 -0
- package/dist/cjs/Checkpoints.d.ts.map +1 -1
- package/dist/cjs/Checkpoints.js +75 -0
- package/dist/cjs/Checkpoints.js.map +2 -2
- package/dist/cjs/PortableSearch.d.ts +13 -1
- package/dist/cjs/PortableSearch.d.ts.map +1 -1
- package/dist/cjs/PortableSearch.js +8 -1
- package/dist/cjs/PortableSearch.js.map +2 -2
- package/dist/cjs/RemoteSearch.d.ts +54 -0
- package/dist/cjs/RemoteSearch.d.ts.map +1 -0
- package/dist/cjs/RemoteSearch.js +134 -0
- package/dist/cjs/RemoteSearch.js.map +7 -0
- package/dist/cjs/index.d.ts +2 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +2 -0
- package/dist/cjs/index.js.map +2 -2
- package/dist/cjs/internal/EnvelopePrecheck.d.ts +2 -0
- package/dist/cjs/internal/EnvelopePrecheck.d.ts.map +1 -1
- package/dist/cjs/internal/EnvelopePrecheck.js +235 -36
- package/dist/cjs/internal/EnvelopePrecheck.js.map +3 -3
- package/dist/cjs/internal/Exec.d.ts +12 -7
- package/dist/cjs/internal/Exec.d.ts.map +1 -1
- package/dist/cjs/internal/Exec.js +8 -4
- package/dist/cjs/internal/Exec.js.map +2 -2
- package/dist/cjs/internal/RemoteTree.d.ts +60 -0
- package/dist/cjs/internal/RemoteTree.d.ts.map +1 -0
- package/dist/cjs/internal/RemoteTree.js +123 -0
- package/dist/cjs/internal/RemoteTree.js.map +7 -0
- package/dist/cjs/internal/Tar.d.ts +23 -0
- package/dist/cjs/internal/Tar.d.ts.map +1 -0
- package/dist/cjs/internal/Tar.js +86 -0
- package/dist/cjs/internal/Tar.js.map +7 -0
- package/dist/esm/Checkpoints.d.ts +37 -0
- package/dist/esm/Checkpoints.d.ts.map +1 -1
- package/dist/esm/Checkpoints.js +85 -0
- package/dist/esm/Checkpoints.js.map +1 -1
- package/dist/esm/PortableSearch.d.ts +13 -1
- package/dist/esm/PortableSearch.d.ts.map +1 -1
- package/dist/esm/PortableSearch.js +16 -0
- package/dist/esm/PortableSearch.js.map +1 -1
- package/dist/esm/RemoteSearch.d.ts +54 -0
- package/dist/esm/RemoteSearch.d.ts.map +1 -0
- package/dist/esm/RemoteSearch.js +162 -0
- package/dist/esm/RemoteSearch.js.map +1 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internal/EnvelopePrecheck.d.ts +2 -0
- package/dist/esm/internal/EnvelopePrecheck.d.ts.map +1 -1
- package/dist/esm/internal/EnvelopePrecheck.js +321 -62
- package/dist/esm/internal/EnvelopePrecheck.js.map +1 -1
- package/dist/esm/internal/Exec.d.ts +12 -7
- package/dist/esm/internal/Exec.d.ts.map +1 -1
- package/dist/esm/internal/Exec.js +30 -9
- package/dist/esm/internal/Exec.js.map +1 -1
- package/dist/esm/internal/RemoteTree.d.ts +60 -0
- package/dist/esm/internal/RemoteTree.d.ts.map +1 -0
- package/dist/esm/internal/RemoteTree.js +132 -0
- package/dist/esm/internal/RemoteTree.js.map +1 -0
- package/dist/esm/internal/Tar.d.ts +23 -0
- package/dist/esm/internal/Tar.d.ts.map +1 -0
- package/dist/esm/internal/Tar.js +97 -0
- package/dist/esm/internal/Tar.js.map +1 -0
- package/docs/README.md +3 -1
- package/docs/api.md +48 -21
- package/docs/concepts/effects-and-capabilities.md +3 -1
- package/docs/guides/bind-the-standard-flows.md +3 -1
- package/docs/guides/pin-a-checkpoint.md +20 -0
- package/docs/guides/run-a-shell-command.md +4 -1
- package/docs/installation.md +13 -9
- package/docs/quickstart.md +1 -1
- package/docs/troubleshooting.md +20 -0
- package/package.json +16 -6
- package/src/Checkpoints.ts +137 -0
- package/src/PortableSearch.ts +20 -0
- package/src/RemoteSearch.ts +199 -0
- package/src/index.ts +3 -0
- package/src/internal/EnvelopePrecheck.ts +320 -64
- package/src/internal/Exec.ts +32 -8
- package/src/internal/RemoteTree.ts +159 -0
- package/src/internal/Tar.ts +95 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The search contract for a tree on another machine, in two round trips.
|
|
3
|
+
*
|
|
4
|
+
* `PortableSearch` asks `FileSystem` one question per directory, per entry
|
|
5
|
+
* and per file. On a machine reached through a transport, such as a
|
|
6
|
+
* benchmark's task container behind `docker exec`, every question is a
|
|
7
|
+
* process start, so one `grep` over a 6,000-file repository is some 20,000 of
|
|
8
|
+
* them. This peer asks the machine's own shell twice instead. `find` lists
|
|
9
|
+
* the tree under the root, leaving out the directories every walk skips, and
|
|
10
|
+
* `tar` returns its `.gitignore` files with the listing. The in-process peer
|
|
11
|
+
* then decides which files the search reads, a second `tar` returns exactly
|
|
12
|
+
* those, and `PortableSearch` answers over that copy. Every result, notice,
|
|
13
|
+
* and refusal is therefore the in-process peer's own: this module decides
|
|
14
|
+
* only where the bytes come from.
|
|
15
|
+
*
|
|
16
|
+
* It runs through the machine's own `ChildProcessSpawner`, the one
|
|
17
|
+
* `Sandbox.hostContext` serves beside the machine's `FileSystem`. The machine
|
|
18
|
+
* needs `sh`, `find`, `xargs`, `tar`, `head`, and `base64`; GNU, BusyBox, and
|
|
19
|
+
* BSD userlands all have them. Output crosses as base64, so a transport whose
|
|
20
|
+
* channel carries only text returns it intact. A search reads at most
|
|
21
|
+
* {@link maxBytes} of file content and refuses beyond that, naming the root
|
|
22
|
+
* to narrow.
|
|
23
|
+
*
|
|
24
|
+
* @since 1.0.0-rc.8
|
|
25
|
+
*/
|
|
26
|
+
import type * as ChildProcessSpawner from "@smthrs/kernel/ChildProcessSpawner";
|
|
27
|
+
import * as Path from "@smthrs/kernel/Path";
|
|
28
|
+
import { Context } from "effect";
|
|
29
|
+
import * as Search from "./Search.ts";
|
|
30
|
+
/**
|
|
31
|
+
* The most file content one search brings back from the machine.
|
|
32
|
+
*
|
|
33
|
+
* The copy is held in memory while the in-process peer reads it, so the
|
|
34
|
+
* bound is on this process's memory rather than on the search: a root that
|
|
35
|
+
* holds more is refused with `response_too_large` and a narrower root or
|
|
36
|
+
* globs answer it.
|
|
37
|
+
*
|
|
38
|
+
* @category constants
|
|
39
|
+
* @since 1.0.0-rc.8
|
|
40
|
+
*/
|
|
41
|
+
export declare const maxBytes: number;
|
|
42
|
+
/**
|
|
43
|
+
* Builds the peer over a machine's spawner and its `Path`, which resolves a
|
|
44
|
+
* relative root where the machine's file operations do. `maxBytes` lowers
|
|
45
|
+
* the bound on the file content one search brings back; the default is
|
|
46
|
+
* {@link maxBytes}.
|
|
47
|
+
*
|
|
48
|
+
* @category constructors
|
|
49
|
+
* @since 1.0.0-rc.8
|
|
50
|
+
*/
|
|
51
|
+
export declare const make: (services: Context.Context<ChildProcessSpawner.ChildProcessSpawner | Path.Path>, options?: {
|
|
52
|
+
readonly maxBytes?: number | undefined;
|
|
53
|
+
}) => Search.Search;
|
|
54
|
+
//# sourceMappingURL=RemoteSearch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RemoteSearch.d.ts","sourceRoot":"","sources":["../../src/RemoteSearch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,KAAK,mBAAmB,MAAM,oCAAoC,CAAA;AAC9E,OAAO,KAAK,IAAI,MAAM,qBAAqB,CAAA;AAC3C,OAAO,EAAE,OAAO,EAAU,MAAM,QAAQ,CAAA;AASxC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AAGrC;;;;;;;;;;GAUG;AACH,eAAO,MAAM,QAAQ,QAAoB,CAAA;AA+FzC;;;;;;;;GAQG;AACH,eAAO,MAAM,IAAI,GACf,UAAU,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,mBAAmB,GAAG,IAAI,CAAC,IAAI,CAAC,EAC9E,UAAU;IAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,KACnD,MAAM,CAAC,MAwCT,CAAA"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var RemoteSearch_exports = {};
|
|
30
|
+
__export(RemoteSearch_exports, {
|
|
31
|
+
make: () => make,
|
|
32
|
+
maxBytes: () => maxBytes
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(RemoteSearch_exports);
|
|
35
|
+
var Path = __toESM(require("@smthrs/kernel/Path"), 1);
|
|
36
|
+
var import_effect = require("effect");
|
|
37
|
+
var Base64 = __toESM(require("effect/encoding/Base64"), 1);
|
|
38
|
+
var FileSystem = __toESM(require("effect/FileSystem"), 1);
|
|
39
|
+
var Result = __toESM(require("effect/Result"), 1);
|
|
40
|
+
var Exec = __toESM(require("./internal/Exec.js"), 1);
|
|
41
|
+
var Prune = __toESM(require("./internal/Prune.js"), 1);
|
|
42
|
+
var RemoteTree = __toESM(require("./internal/RemoteTree.js"), 1);
|
|
43
|
+
var Tar = __toESM(require("./internal/Tar.js"), 1);
|
|
44
|
+
var PortableSearch = __toESM(require("./PortableSearch.js"), 1);
|
|
45
|
+
var Search = __toESM(require("./Search.js"), 1);
|
|
46
|
+
var StdError = __toESM(require("./StdError.js"), 1);
|
|
47
|
+
const maxBytes = 256 * 1024 * 1024;
|
|
48
|
+
const maxListingBytes = 64 * 1024 * 1024;
|
|
49
|
+
const quoted = (value) => `'${value.replaceAll("'", `'\\''`)}'`;
|
|
50
|
+
const pruned = `\\( ${Prune.repositoryDirectories.map((name) => `-name ${quoted(name)}`).join(" -o ")} \\) -prune`;
|
|
51
|
+
const missing = (tool) => `searching this machine needs ${tool}, which it does not have`;
|
|
52
|
+
const requireTools = `for t in find xargs tar head; do command -v "$t" >/dev/null || { echo "${missing("$t")}" >&2; exit 3; }; done`;
|
|
53
|
+
const listing = [
|
|
54
|
+
"set -f",
|
|
55
|
+
requireTools,
|
|
56
|
+
"r=$1",
|
|
57
|
+
"{",
|
|
58
|
+
` if [ -d "$r" ]; then if [ -r "$r" ] && [ -x "$r" ]; then printf 'd%s\\0' "$r"; else printf 'u%s\\0' "$r"; fi`,
|
|
59
|
+
` elif [ -f "$r" ]; then printf 'f%s\\0' "$r"`,
|
|
60
|
+
` elif [ -e "$r" ]; then printf 'o%s\\0' "$r"`,
|
|
61
|
+
" fi",
|
|
62
|
+
// No `-exec … {} +` inside parentheses: BusyBox `find` never runs one there.
|
|
63
|
+
` find -H "$r" -mindepth 1 ${pruned} -o -type d -exec printf 'd%s\\0' {} + -o -type f -exec printf 'f%s\\0' {} + -o -type l -exec sh -c 'for p do if [ -d "$p" ]; then k=D; elif [ -f "$p" ]; then k=F; else k=L; fi; printf "%s%s\\0" "$k" "$p"; done' sh {} + -o -exec printf 'o%s\\0' {} + 2>/dev/null`,
|
|
64
|
+
" printf 'E\\0'",
|
|
65
|
+
` if [ "$3" = 1 ]; then find -H "$r" -mindepth 1 ${pruned} -o -type f -name .gitignore -print0 2>/dev/null | (cd / && COPYFILE_DISABLE=1 xargs -0 -r tar -cf - -- 2>/dev/null); fi`,
|
|
66
|
+
'} | head -c "$2" | base64'
|
|
67
|
+
].join("\n");
|
|
68
|
+
const fetching = [
|
|
69
|
+
requireTools,
|
|
70
|
+
'cd / && COPYFILE_DISABLE=1 xargs -0 -r tar -chf - -- 2>/dev/null | head -c "$1" | base64'
|
|
71
|
+
].join("\n");
|
|
72
|
+
const failed = (root, reason) => new StdError.StdError({
|
|
73
|
+
code: "command_failed",
|
|
74
|
+
message: `Could not search ${root} on this machine: ${reason}`,
|
|
75
|
+
path: root
|
|
76
|
+
});
|
|
77
|
+
const tooLarge = (root, bound) => new StdError.StdError({
|
|
78
|
+
code: "response_too_large",
|
|
79
|
+
message: `The files under ${root} come to more than ${bound} bytes; narrow the root or the globs`,
|
|
80
|
+
path: root
|
|
81
|
+
});
|
|
82
|
+
const bootstrap = `command -v base64 >/dev/null || { echo "${missing("base64")}" >&2; exit 3; }; IFS= read -r p && p=$(printf %s "$p" | base64 -d) && exec sh -c "$p" sh "$@"`;
|
|
83
|
+
const run = (root, script, args, bound, stdin = "") => import_effect.Effect.gen(function* () {
|
|
84
|
+
const result = yield* Exec.exec("sh", {
|
|
85
|
+
args: ["-c", bootstrap, "sh", ...args],
|
|
86
|
+
stdin: `${Base64.encode(new TextEncoder().encode(script))}
|
|
87
|
+
${stdin}`
|
|
88
|
+
}).pipe(import_effect.Effect.mapError((error) => failed(root, error.message)));
|
|
89
|
+
if (result.exitCode !== 0) {
|
|
90
|
+
return yield* import_effect.Effect.fail(failed(root, result.stderr.trim() || `exit ${result.exitCode}`));
|
|
91
|
+
}
|
|
92
|
+
const decoded = Base64.decode(result.stdout.replaceAll(/\s/g, ""));
|
|
93
|
+
if (Result.isFailure(decoded)) return yield* import_effect.Effect.fail(failed(root, "the machine answered with invalid base64"));
|
|
94
|
+
if (decoded.success.length >= bound) return yield* import_effect.Effect.fail(tooLarge(root, bound - 1));
|
|
95
|
+
return decoded.success;
|
|
96
|
+
});
|
|
97
|
+
const make = (services, options) => {
|
|
98
|
+
const path = import_effect.Context.get(services, Path.Path);
|
|
99
|
+
const contentBound = (options?.maxBytes ?? maxBytes) + 1;
|
|
100
|
+
const copy = (tree) => import_effect.Context.make(FileSystem.FileSystem, RemoteTree.fileSystem(tree, path)).pipe(import_effect.Context.add(Path.Path, path));
|
|
101
|
+
const listed = (root, noIgnore) => {
|
|
102
|
+
const resolved = path.resolve(root);
|
|
103
|
+
const bound = maxListingBytes + 1;
|
|
104
|
+
return import_effect.Effect.flatMap(
|
|
105
|
+
run(root, listing, [resolved, String(bound), noIgnore === true ? "0" : "1"], bound),
|
|
106
|
+
(bytes) => {
|
|
107
|
+
const tree = RemoteTree.parse(bytes, resolved, path);
|
|
108
|
+
return tree === void 0 ? import_effect.Effect.fail(failed(root, "the listing came back malformed")) : import_effect.Effect.succeed(tree);
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
};
|
|
112
|
+
return Search.make({
|
|
113
|
+
grep: (input) => import_effect.Effect.gen(function* () {
|
|
114
|
+
const tree = yield* listed(input.root, input.noIgnore);
|
|
115
|
+
const read = yield* PortableSearch.searched(input).pipe(import_effect.Effect.provide(copy(tree)));
|
|
116
|
+
const files = [...new Set(read.map((file) => path.resolve(file)))];
|
|
117
|
+
const bytes = files.length === 0 ? new Uint8Array() : yield* run(
|
|
118
|
+
input.root,
|
|
119
|
+
fetching,
|
|
120
|
+
[String(contentBound)],
|
|
121
|
+
contentBound,
|
|
122
|
+
files.map((file) => `${file}\0`).join("")
|
|
123
|
+
);
|
|
124
|
+
const members = files.length === 0 || bytes.length > 0 ? Tar.files(bytes) : void 0;
|
|
125
|
+
if (members === void 0) return yield* import_effect.Effect.fail(failed(input.root, "the files came back malformed"));
|
|
126
|
+
const contents = new Map([...tree.contents, ...RemoteTree.absolute(members)]);
|
|
127
|
+
return yield* PortableSearch.make(copy({ ...tree, contents })).grep(input);
|
|
128
|
+
}).pipe(import_effect.Effect.provide(services)),
|
|
129
|
+
glob: (input) => import_effect.Effect.flatMap(listed(input.root, input.noIgnore), (tree) => PortableSearch.make(copy(tree)).glob(input)).pipe(
|
|
130
|
+
import_effect.Effect.provide(services)
|
|
131
|
+
)
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
//# sourceMappingURL=RemoteSearch.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/RemoteSearch.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * The search contract for a tree on another machine, in two round trips.\n *\n * `PortableSearch` asks `FileSystem` one question per directory, per entry\n * and per file. On a machine reached through a transport, such as a\n * benchmark's task container behind `docker exec`, every question is a\n * process start, so one `grep` over a 6,000-file repository is some 20,000 of\n * them. This peer asks the machine's own shell twice instead. `find` lists\n * the tree under the root, leaving out the directories every walk skips, and\n * `tar` returns its `.gitignore` files with the listing. The in-process peer\n * then decides which files the search reads, a second `tar` returns exactly\n * those, and `PortableSearch` answers over that copy. Every result, notice,\n * and refusal is therefore the in-process peer's own: this module decides\n * only where the bytes come from.\n *\n * It runs through the machine's own `ChildProcessSpawner`, the one\n * `Sandbox.hostContext` serves beside the machine's `FileSystem`. The machine\n * needs `sh`, `find`, `xargs`, `tar`, `head`, and `base64`; GNU, BusyBox, and\n * BSD userlands all have them. Output crosses as base64, so a transport whose\n * channel carries only text returns it intact. A search reads at most\n * {@link maxBytes} of file content and refuses beyond that, naming the root\n * to narrow.\n *\n * @since 1.0.0-rc.8\n */\n\nimport type * as ChildProcessSpawner from \"@smthrs/kernel/ChildProcessSpawner\"\nimport * as Path from \"@smthrs/kernel/Path\"\nimport { Context, Effect } from \"effect\"\nimport * as Base64 from \"effect/encoding/Base64\"\nimport * as FileSystem from \"effect/FileSystem\"\nimport * as Result from \"effect/Result\"\nimport * as Exec from \"./internal/Exec.ts\"\nimport * as Prune from \"./internal/Prune.ts\"\nimport * as RemoteTree from \"./internal/RemoteTree.ts\"\nimport * as Tar from \"./internal/Tar.ts\"\nimport * as PortableSearch from \"./PortableSearch.ts\"\nimport * as Search from \"./Search.ts\"\nimport * as StdError from \"./StdError.ts\"\n\n/**\n * The most file content one search brings back from the machine.\n *\n * The copy is held in memory while the in-process peer reads it, so the\n * bound is on this process's memory rather than on the search: a root that\n * holds more is refused with `response_too_large` and a narrower root or\n * globs answer it.\n *\n * @category constants\n * @since 1.0.0-rc.8\n */\nexport const maxBytes = 256 * 1024 * 1024\n\n/** The most a listing, with its `.gitignore` files, may take. */\nconst maxListingBytes = 64 * 1024 * 1024\n\nconst quoted = (value: string): string => `'${value.replaceAll(\"'\", `'\\\\''`)}'`\n\nconst pruned = `\\\\( ${Prune.repositoryDirectories.map((name) => `-name ${quoted(name)}`).join(\" -o \")} \\\\) -prune`\n\n/** The refusal for a machine without one of the tools a search runs. */\nconst missing = (tool: string) => `searching this machine needs ${tool}, which it does not have`\n\nconst requireTools = `for t in find xargs tar head; do command -v \"$t\" >/dev/null || { ` +\n `echo \"${missing(\"$t\")}\" >&2; exit 3; }; done`\n\n/**\n * `$1` is the root, `$2` the byte bound, and `$3` is `1` to send the\n * `.gitignore` files after the listing. Records are NUL-terminated `<kind><path>`;\n * an `E` record ends the listing, and a tar stream of the ignore files follows.\n */\nconst listing = [\n \"set -f\",\n requireTools,\n \"r=$1\",\n \"{\",\n \" if [ -d \\\"$r\\\" ]; then if [ -r \\\"$r\\\" ] && [ -x \\\"$r\\\" ]; then printf 'd%s\\\\0' \\\"$r\\\"; else printf 'u%s\\\\0' \\\"$r\\\"; fi\",\n \" elif [ -f \\\"$r\\\" ]; then printf 'f%s\\\\0' \\\"$r\\\"\",\n \" elif [ -e \\\"$r\\\" ]; then printf 'o%s\\\\0' \\\"$r\\\"\",\n \" fi\",\n // No `-exec \u2026 {} +` inside parentheses: BusyBox `find` never runs one there.\n ` find -H \"$r\" -mindepth 1 ${pruned} -o -type d -exec printf 'd%s\\\\0' {} + ` +\n `-o -type f -exec printf 'f%s\\\\0' {} + ` +\n `-o -type l -exec sh -c 'for p do if [ -d \"$p\" ]; then k=D; elif [ -f \"$p\" ]; then k=F; else k=L; fi; ` +\n `printf \"%s%s\\\\0\" \"$k\" \"$p\"; done' sh {} + -o -exec printf 'o%s\\\\0' {} + 2>/dev/null`,\n \" printf 'E\\\\0'\",\n ` if [ \"$3\" = 1 ]; then find -H \"$r\" -mindepth 1 ${pruned} -o -type f -name .gitignore -print0 2>/dev/null | ` +\n \"(cd / && COPYFILE_DISABLE=1 xargs -0 -r tar -cf - -- 2>/dev/null); fi\",\n \"} | head -c \\\"$2\\\" | base64\"\n].join(\"\\n\")\n\n/** `$1` is the byte bound; standard input is the NUL-terminated absolute paths to send. */\nconst fetching = [\n requireTools,\n \"cd / && COPYFILE_DISABLE=1 xargs -0 -r tar -chf - -- 2>/dev/null | head -c \\\"$1\\\" | base64\"\n].join(\"\\n\")\n\nconst failed = (root: string, reason: string): StdError.StdError =>\n new StdError.StdError({\n code: \"command_failed\",\n message: `Could not search ${root} on this machine: ${reason}`,\n path: root\n })\n\nconst tooLarge = (root: string, bound: number): StdError.StdError =>\n new StdError.StdError({\n code: \"response_too_large\",\n message: `The files under ${root} come to more than ${bound} bytes; narrow the root or the globs`,\n path: root\n })\n\n/**\n * Reads the program from the first line of standard input, as base64, and\n * runs it with the arguments and the rest of standard input. A program in\n * the argv would be quoted once by the spawner and again by the transport,\n * and a sealed host refuses to start a command line past 4,096 characters;\n * this line stays short whatever the program says.\n */\nconst bootstrap = `command -v base64 >/dev/null || { echo \"${missing(\"base64\")}\" >&2; exit 3; }; ` +\n \"IFS= read -r p && p=$(printf %s \\\"$p\\\" | base64 -d) && exec sh -c \\\"$p\\\" sh \\\"$@\\\"\"\n\n/**\n * Runs one script on the machine and decodes the base64 it prints, refusing\n * output that reached `bound`: `head` cut it, so it is not the whole answer.\n */\nconst run = (\n root: string,\n script: string,\n args: ReadonlyArray<string>,\n bound: number,\n stdin = \"\"\n): Effect.Effect<Uint8Array, StdError.StdError, ChildProcessSpawner.ChildProcessSpawner> =>\n Effect.gen(function*() {\n const result = yield* Exec.exec(\"sh\", {\n args: [\"-c\", bootstrap, \"sh\", ...args],\n stdin: `${Base64.encode(new TextEncoder().encode(script))}\\n${stdin}`\n }).pipe(Effect.mapError((error) => failed(root, error.message)))\n if (result.exitCode !== 0) {\n return yield* Effect.fail(failed(root, result.stderr.trim() || `exit ${result.exitCode}`))\n }\n const decoded = Base64.decode(result.stdout.replaceAll(/\\s/g, \"\"))\n if (Result.isFailure(decoded)) return yield* Effect.fail(failed(root, \"the machine answered with invalid base64\"))\n if (decoded.success.length >= bound) return yield* Effect.fail(tooLarge(root, bound - 1))\n return decoded.success\n })\n\n/**\n * Builds the peer over a machine's spawner and its `Path`, which resolves a\n * relative root where the machine's file operations do. `maxBytes` lowers\n * the bound on the file content one search brings back; the default is\n * {@link maxBytes}.\n *\n * @category constructors\n * @since 1.0.0-rc.8\n */\nexport const make = (\n services: Context.Context<ChildProcessSpawner.ChildProcessSpawner | Path.Path>,\n options?: { readonly maxBytes?: number | undefined }\n): Search.Search => {\n const path = Context.get(services, Path.Path)\n const contentBound = (options?.maxBytes ?? maxBytes) + 1\n const copy = (tree: RemoteTree.Tree) =>\n Context.make(FileSystem.FileSystem, RemoteTree.fileSystem(tree, path)).pipe(Context.add(Path.Path, path))\n const listed = (root: string, noIgnore: boolean | undefined) => {\n const resolved = path.resolve(root)\n const bound = maxListingBytes + 1\n return Effect.flatMap(\n run(root, listing, [resolved, String(bound), noIgnore === true ? \"0\" : \"1\"], bound),\n (bytes) => {\n const tree = RemoteTree.parse(bytes, resolved, path)\n return tree === undefined ? Effect.fail(failed(root, \"the listing came back malformed\")) : Effect.succeed(tree)\n }\n )\n }\n return Search.make({\n grep: (input) =>\n Effect.gen(function*() {\n const tree = yield* listed(input.root, input.noIgnore)\n const read = yield* PortableSearch.searched(input).pipe(Effect.provide(copy(tree)))\n const files = [...new Set(read.map((file) => path.resolve(file)))]\n const bytes = files.length === 0 ? new Uint8Array() : yield* run(\n input.root,\n fetching,\n [String(contentBound)],\n contentBound,\n files.map((file) => `${file}\\0`).join(\"\")\n )\n // `tar` ends every archive it writes, even one whose files it could not read.\n const members = files.length === 0 || bytes.length > 0 ? Tar.files(bytes) : undefined\n if (members === undefined) return yield* Effect.fail(failed(input.root, \"the files came back malformed\"))\n const contents = new Map([...tree.contents, ...RemoteTree.absolute(members)])\n return yield* PortableSearch.make(copy({ ...tree, contents })).grep(input)\n }).pipe(Effect.provide(services)),\n glob: (input) =>\n Effect.flatMap(listed(input.root, input.noIgnore), (tree) => PortableSearch.make(copy(tree)).glob(input)).pipe(\n Effect.provide(services)\n )\n })\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -44,6 +44,8 @@ export * as SearchConformance from "./SearchConformance.ts";
|
|
|
44
44
|
export * as PortableSearch from "./PortableSearch.ts";
|
|
45
45
|
/** @category layers @since 1.0.0 */
|
|
46
46
|
export * as NativeSearch from "./NativeSearch.ts";
|
|
47
|
+
/** @category layers @since 1.0.0-rc.8 */
|
|
48
|
+
export * as RemoteSearch from "./RemoteSearch.ts";
|
|
47
49
|
/**
|
|
48
50
|
* @category flows
|
|
49
51
|
* @since 1.0.0
|
package/dist/cjs/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;GAGG;AACH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AAEjC;;;GAGG;AACH,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AAEnC;;;GAGG;AACH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AAEjC;;;GAGG;AACH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAA;AAE7B;;;GAGG;AACH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AAEjC;;;GAGG;AACH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AAEjC,sCAAsC;AACtC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AAErC,sCAAsC;AACtC,OAAO,KAAK,cAAc,MAAM,qBAAqB,CAAA;AAErD,yCAAyC;AACzC,OAAO,KAAK,iBAAiB,MAAM,wBAAwB,CAAA;AAE3D,oCAAoC;AACpC,OAAO,KAAK,cAAc,MAAM,qBAAqB,CAAA;AAErD,oCAAoC;AACpC,OAAO,KAAK,YAAY,MAAM,mBAAmB,CAAA;AAEjD;;;GAGG;AACH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AAEjC,sCAAsC;AACtC,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAA;AAE3C;;;GAGG;AACH,OAAO,KAAK,eAAe,MAAM,sBAAsB,CAAA;AAEvD,sCAAsC;AACtC,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAA;AAE/C,yCAAyC;AACzC,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AAEzC;;;GAGG;AACH,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AAEvC,sCAAsC;AACtC,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;AAE7C;;;GAGG;AACH,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AAEnC;;;GAGG;AACH,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;AAE7C;;;GAGG;AACH,OAAO,KAAK,YAAY,MAAM,mBAAmB,CAAA;AAEjD;;;GAGG;AACH,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;AAE7C;;;GAGG;AACH,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AAEnC;;;GAGG;AACH,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AAEzC;;;GAGG;AACH,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AAEvC,mCAAmC;AACnC,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AACzC,mCAAmC;AACnC,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAA;AAC3C,sCAAsC;AACtC,OAAO,KAAK,YAAY,MAAM,mBAAmB,CAAA;AACjD,sCAAsC;AACtC,OAAO,KAAK,cAAc,MAAM,qBAAqB,CAAA;AACrD,oCAAoC;AACpC,OAAO,KAAK,kBAAkB,MAAM,yBAAyB,CAAA;AAC7D,mCAAmC;AACnC,OAAO,KAAK,GAAG,MAAM,UAAU,CAAA;AAE/B;;;GAGG;AACH,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AAEzC;;;GAGG;AACH,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;GAGG;AACH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AAEjC;;;GAGG;AACH,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AAEnC;;;GAGG;AACH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AAEjC;;;GAGG;AACH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAA;AAE7B;;;GAGG;AACH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AAEjC;;;GAGG;AACH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AAEjC,sCAAsC;AACtC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AAErC,sCAAsC;AACtC,OAAO,KAAK,cAAc,MAAM,qBAAqB,CAAA;AAErD,yCAAyC;AACzC,OAAO,KAAK,iBAAiB,MAAM,wBAAwB,CAAA;AAE3D,oCAAoC;AACpC,OAAO,KAAK,cAAc,MAAM,qBAAqB,CAAA;AAErD,oCAAoC;AACpC,OAAO,KAAK,YAAY,MAAM,mBAAmB,CAAA;AAEjD,yCAAyC;AACzC,OAAO,KAAK,YAAY,MAAM,mBAAmB,CAAA;AAEjD;;;GAGG;AACH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AAEjC,sCAAsC;AACtC,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAA;AAE3C;;;GAGG;AACH,OAAO,KAAK,eAAe,MAAM,sBAAsB,CAAA;AAEvD,sCAAsC;AACtC,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAA;AAE/C,yCAAyC;AACzC,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AAEzC;;;GAGG;AACH,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AAEvC,sCAAsC;AACtC,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;AAE7C;;;GAGG;AACH,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AAEnC;;;GAGG;AACH,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;AAE7C;;;GAGG;AACH,OAAO,KAAK,YAAY,MAAM,mBAAmB,CAAA;AAEjD;;;GAGG;AACH,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;AAE7C;;;GAGG;AACH,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AAEnC;;;GAGG;AACH,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AAEzC;;;GAGG;AACH,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AAEvC,mCAAmC;AACnC,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AACzC,mCAAmC;AACnC,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAA;AAC3C,sCAAsC;AACtC,OAAO,KAAK,YAAY,MAAM,mBAAmB,CAAA;AACjD,sCAAsC;AACtC,OAAO,KAAK,cAAc,MAAM,qBAAqB,CAAA;AACrD,oCAAoC;AACpC,OAAO,KAAK,kBAAkB,MAAM,yBAAyB,CAAA;AAC7D,mCAAmC;AACnC,OAAO,KAAK,GAAG,MAAM,UAAU,CAAA;AAE/B;;;GAGG;AACH,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AAEzC;;;GAGG;AACH,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA"}
|
package/dist/cjs/index.js
CHANGED
|
@@ -49,6 +49,7 @@ __export(index_exports, {
|
|
|
49
49
|
Probe: () => Probe,
|
|
50
50
|
Read: () => Read,
|
|
51
51
|
Relocate: () => Relocate,
|
|
52
|
+
RemoteSearch: () => RemoteSearch,
|
|
52
53
|
Search: () => Search,
|
|
53
54
|
SearchConformance: () => SearchConformance,
|
|
54
55
|
SearchContract: () => SearchContract,
|
|
@@ -74,6 +75,7 @@ var SearchContract = __toESM(require("./SearchContract.js"), 1);
|
|
|
74
75
|
var SearchConformance = __toESM(require("./SearchConformance.js"), 1);
|
|
75
76
|
var PortableSearch = __toESM(require("./PortableSearch.js"), 1);
|
|
76
77
|
var NativeSearch = __toESM(require("./NativeSearch.js"), 1);
|
|
78
|
+
var RemoteSearch = __toESM(require("./RemoteSearch.js"), 1);
|
|
77
79
|
var Bash = __toESM(require("./Bash.js"), 1);
|
|
78
80
|
var Container = __toESM(require("./Container.js"), 1);
|
|
79
81
|
var TreeFingerprint = __toESM(require("./TreeFingerprint.js"), 1);
|
package/dist/cjs/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * `@smthrs/std`: the standard coding tools expressed as flows, the host\n * services they bind, and the one error they share.\n *\n * @since 1.0.0\n */\n\n/**\n * @category flows\n * @since 1.0.0\n */\nexport * as Read from \"./Read.ts\"\n\n/**\n * @category flows\n * @since 1.0.0\n */\nexport * as Write from \"./Write.ts\"\n\n/**\n * @category flows\n * @since 1.0.0\n */\nexport * as Edit from \"./Edit.ts\"\n\n/**\n * @category flows\n * @since 1.0.0\n */\nexport * as Ls from \"./Ls.ts\"\n\n/**\n * @category flows\n * @since 1.0.0\n */\nexport * as Glob from \"./Glob.ts\"\n\n/**\n * @category flows\n * @since 1.0.0\n */\nexport * as Grep from \"./Grep.ts\"\n\n/** @category services @since 1.0.0 */\nexport * as Search from \"./Search.ts\"\n\n/** @category services @since 1.0.0 */\nexport * as SearchContract from \"./SearchContract.ts\"\n\n/** @category conformance @since 1.0.0 */\nexport * as SearchConformance from \"./SearchConformance.ts\"\n\n/** @category layers @since 1.0.0 */\nexport * as PortableSearch from \"./PortableSearch.ts\"\n\n/** @category layers @since 1.0.0 */\nexport * as NativeSearch from \"./NativeSearch.ts\"\n\n/**\n * @category flows\n * @since 1.0.0\n */\nexport * as Bash from \"./Bash.ts\"\n\n/** @category services @since 1.0.0 */\nexport * as Container from \"./Container.ts\"\n\n/**\n * @category measurement\n * @since 1.0.0\n */\nexport * as TreeFingerprint from \"./TreeFingerprint.ts\"\n\n/** @category services @since 1.0.0 */\nexport * as Checkpoints from \"./Checkpoints.ts\"\n\n/** @category conversions @since 1.0.0 */\nexport * as Relocate from \"./Relocate.ts\"\n\n/**\n * @category flows\n * @since 1.0.0\n */\nexport * as TestRun from \"./TestRun.ts\"\n\n/** @category services @since 1.0.0 */\nexport * as TestRunner from \"./TestRunner.ts\"\n\n/**\n * @category classification\n * @since 1.0.0\n */\nexport * as Probe from \"./Probe.ts\"\n\n/**\n * @category flows\n * @since 1.0.0\n */\nexport * as ApplyPatch from \"./ApplyPatch.ts\"\n\n/**\n * @category flows\n * @since 1.0.0\n */\nexport * as ShellCommand from \"./ShellCommand.ts\"\n\n/**\n * @category flows\n * @since 1.0.0\n */\nexport * as UpdatePlan from \"./UpdatePlan.ts\"\n\n/**\n * @category flows\n * @since 1.0.0\n */\nexport * as Fetch from \"./Fetch.ts\"\n\n/**\n * @category flows\n * @since 1.0.0\n */\nexport * as HttpPost from \"./HttpPost.ts\"\n\n/**\n * @category flows\n * @since 1.0.0\n */\nexport * as Explore from \"./Explore.ts\"\n\n/** @category flows @since 1.0.0 */\nexport * as WebFetch from \"./WebFetch.ts\"\n/** @category flows @since 1.0.0 */\nexport * as WebSearch from \"./WebSearch.ts\"\n/** @category services @since 1.0.0 */\nexport * as ExaWebSearch from \"./ExaWebSearch.ts\"\n/** @category services @since 1.0.0 */\nexport * as LanguageServer from \"./LanguageServer.ts\"\n/** @category layers @since 1.0.0 */\nexport * as NodeLanguageServer from \"./NodeLanguageServer.ts\"\n/** @category flows @since 1.0.0 */\nexport * as Lsp from \"./Lsp.ts\"\n\n/**\n * @category registries\n * @since 1.0.0\n */\nexport * as Manifest from \"./Manifest.ts\"\n\n/**\n * @category errors\n * @since 1.0.0\n */\nexport * as StdError from \"./StdError.ts\"\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWA,WAAsB;AAMtB,YAAuB;AAMvB,WAAsB;AAMtB,SAAoB;AAMpB,WAAsB;AAMtB,WAAsB;AAGtB,aAAwB;AAGxB,qBAAgC;AAGhC,wBAAmC;AAGnC,qBAAgC;AAGhC,mBAA8B;AAM9B,WAAsB;AAGtB,gBAA2B;AAM3B,sBAAiC;AAGjC,kBAA6B;AAG7B,eAA0B;AAM1B,cAAyB;AAGzB,iBAA4B;AAM5B,YAAuB;AAMvB,iBAA4B;AAM5B,mBAA8B;AAM9B,iBAA4B;AAM5B,YAAuB;AAMvB,eAA0B;AAM1B,cAAyB;AAGzB,eAA0B;AAE1B,gBAA2B;AAE3B,mBAA8B;AAE9B,qBAAgC;AAEhC,yBAAoC;AAEpC,UAAqB;AAMrB,eAA0B;AAM1B,eAA0B;",
|
|
4
|
+
"sourcesContent": ["/**\n * `@smthrs/std`: the standard coding tools expressed as flows, the host\n * services they bind, and the one error they share.\n *\n * @since 1.0.0\n */\n\n/**\n * @category flows\n * @since 1.0.0\n */\nexport * as Read from \"./Read.ts\"\n\n/**\n * @category flows\n * @since 1.0.0\n */\nexport * as Write from \"./Write.ts\"\n\n/**\n * @category flows\n * @since 1.0.0\n */\nexport * as Edit from \"./Edit.ts\"\n\n/**\n * @category flows\n * @since 1.0.0\n */\nexport * as Ls from \"./Ls.ts\"\n\n/**\n * @category flows\n * @since 1.0.0\n */\nexport * as Glob from \"./Glob.ts\"\n\n/**\n * @category flows\n * @since 1.0.0\n */\nexport * as Grep from \"./Grep.ts\"\n\n/** @category services @since 1.0.0 */\nexport * as Search from \"./Search.ts\"\n\n/** @category services @since 1.0.0 */\nexport * as SearchContract from \"./SearchContract.ts\"\n\n/** @category conformance @since 1.0.0 */\nexport * as SearchConformance from \"./SearchConformance.ts\"\n\n/** @category layers @since 1.0.0 */\nexport * as PortableSearch from \"./PortableSearch.ts\"\n\n/** @category layers @since 1.0.0 */\nexport * as NativeSearch from \"./NativeSearch.ts\"\n\n/** @category layers @since 1.0.0-rc.8 */\nexport * as RemoteSearch from \"./RemoteSearch.ts\"\n\n/**\n * @category flows\n * @since 1.0.0\n */\nexport * as Bash from \"./Bash.ts\"\n\n/** @category services @since 1.0.0 */\nexport * as Container from \"./Container.ts\"\n\n/**\n * @category measurement\n * @since 1.0.0\n */\nexport * as TreeFingerprint from \"./TreeFingerprint.ts\"\n\n/** @category services @since 1.0.0 */\nexport * as Checkpoints from \"./Checkpoints.ts\"\n\n/** @category conversions @since 1.0.0 */\nexport * as Relocate from \"./Relocate.ts\"\n\n/**\n * @category flows\n * @since 1.0.0\n */\nexport * as TestRun from \"./TestRun.ts\"\n\n/** @category services @since 1.0.0 */\nexport * as TestRunner from \"./TestRunner.ts\"\n\n/**\n * @category classification\n * @since 1.0.0\n */\nexport * as Probe from \"./Probe.ts\"\n\n/**\n * @category flows\n * @since 1.0.0\n */\nexport * as ApplyPatch from \"./ApplyPatch.ts\"\n\n/**\n * @category flows\n * @since 1.0.0\n */\nexport * as ShellCommand from \"./ShellCommand.ts\"\n\n/**\n * @category flows\n * @since 1.0.0\n */\nexport * as UpdatePlan from \"./UpdatePlan.ts\"\n\n/**\n * @category flows\n * @since 1.0.0\n */\nexport * as Fetch from \"./Fetch.ts\"\n\n/**\n * @category flows\n * @since 1.0.0\n */\nexport * as HttpPost from \"./HttpPost.ts\"\n\n/**\n * @category flows\n * @since 1.0.0\n */\nexport * as Explore from \"./Explore.ts\"\n\n/** @category flows @since 1.0.0 */\nexport * as WebFetch from \"./WebFetch.ts\"\n/** @category flows @since 1.0.0 */\nexport * as WebSearch from \"./WebSearch.ts\"\n/** @category services @since 1.0.0 */\nexport * as ExaWebSearch from \"./ExaWebSearch.ts\"\n/** @category services @since 1.0.0 */\nexport * as LanguageServer from \"./LanguageServer.ts\"\n/** @category layers @since 1.0.0 */\nexport * as NodeLanguageServer from \"./NodeLanguageServer.ts\"\n/** @category flows @since 1.0.0 */\nexport * as Lsp from \"./Lsp.ts\"\n\n/**\n * @category registries\n * @since 1.0.0\n */\nexport * as Manifest from \"./Manifest.ts\"\n\n/**\n * @category errors\n * @since 1.0.0\n */\nexport * as StdError from \"./StdError.ts\"\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWA,WAAsB;AAMtB,YAAuB;AAMvB,WAAsB;AAMtB,SAAoB;AAMpB,WAAsB;AAMtB,WAAsB;AAGtB,aAAwB;AAGxB,qBAAgC;AAGhC,wBAAmC;AAGnC,qBAAgC;AAGhC,mBAA8B;AAG9B,mBAA8B;AAM9B,WAAsB;AAGtB,gBAA2B;AAM3B,sBAAiC;AAGjC,kBAA6B;AAG7B,eAA0B;AAM1B,cAAyB;AAGzB,iBAA4B;AAM5B,YAAuB;AAMvB,iBAA4B;AAM5B,mBAA8B;AAM9B,iBAA4B;AAM5B,YAAuB;AAMvB,eAA0B;AAM1B,cAAyB;AAGzB,eAA0B;AAE1B,gBAA2B;AAE3B,mBAA8B;AAE9B,qBAAgC;AAEhC,yBAAoC;AAEpC,UAAqB;AAMrB,eAA0B;AAM1B,eAA0B;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -52,6 +52,8 @@ export interface PathReference {
|
|
|
52
52
|
/**
|
|
53
53
|
* Every path token the shell text names, one segment at a time.
|
|
54
54
|
*
|
|
55
|
+
* @throws {StdError.StdError} `invalid_input` when substitutions or quotes nest
|
|
56
|
+
* deeper than the scan reads.
|
|
55
57
|
* @category pre-check
|
|
56
58
|
* @since 1.0.0
|
|
57
59
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EnvelopePrecheck.d.ts","sourceRoot":"","sources":["../../../src/internal/EnvelopePrecheck.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,KAAK,IAAI,MAAM,qBAAqB,CAAA;AAChD,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAA;AAG1C;;;;;;;;;GASG;AACH,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;IACrC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CACvC;AAED;;;;;GAKG;AACH,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;AAErC;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB;
|
|
1
|
+
{"version":3,"file":"EnvelopePrecheck.d.ts","sourceRoot":"","sources":["../../../src/internal/EnvelopePrecheck.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,KAAK,IAAI,MAAM,qBAAqB,CAAA;AAChD,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAA;AAG1C;;;;;;;;;GASG;AACH,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;IACrC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CACvC;AAED;;;;;GAKG;AACH,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;AAErC;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB;AA0VD;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,GAAI,MAAM,IAAI,CAAC,IAAI,EAAE,SAAS,MAAM,KAAG,aAAa,CAAC,aAAa,CAClE,CAAA;AAmB9B;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAC1B,OAAO,QAAQ,EACf,MAAM,MAAM,EACZ,MAAM,IAAI,CAAC,IAAI,KACd,QAAQ,CAAC,QAAQ,GAAG,SAgDtB,CAAA"}
|
|
@@ -34,63 +34,256 @@ __export(EnvelopePrecheck_exports, {
|
|
|
34
34
|
module.exports = __toCommonJS(EnvelopePrecheck_exports);
|
|
35
35
|
var StdError = __toESM(require("../StdError.js"), 1);
|
|
36
36
|
var import_Paths = require("./Paths.js");
|
|
37
|
-
const commandSeparators = /* @__PURE__ */ new Set(["&&", "||", ";", "|", "&", "\n"]);
|
|
38
|
-
const
|
|
37
|
+
const commandSeparators = /* @__PURE__ */ new Set(["&&", "||", ";", ";;", ";&", ";;&", "|", "|&", "&", "\n", "(", ")"]);
|
|
38
|
+
const writeRedirections = /* @__PURE__ */ new Set([">", ">>", ">|", "&>", "&>>", ">&", "<>"]);
|
|
39
|
+
const readRedirections = /* @__PURE__ */ new Set(["<", "<&"]);
|
|
40
|
+
const textRedirections = /* @__PURE__ */ new Set(["<<", "<<-", "<<<"]);
|
|
41
|
+
const isRedirection = (token) => token.operator && (writeRedirections.has(token.text) || readRedirections.has(token.text) || textRedirections.has(token.text));
|
|
42
|
+
const operators = [
|
|
43
|
+
";;&",
|
|
44
|
+
"&>>",
|
|
45
|
+
"<<<",
|
|
46
|
+
"<<-",
|
|
47
|
+
";;",
|
|
48
|
+
";&",
|
|
49
|
+
"&&",
|
|
50
|
+
"||",
|
|
51
|
+
"|&",
|
|
52
|
+
"&>",
|
|
53
|
+
">>",
|
|
54
|
+
">|",
|
|
55
|
+
">&",
|
|
56
|
+
"<>",
|
|
57
|
+
"<<",
|
|
58
|
+
"<&",
|
|
59
|
+
"\n",
|
|
60
|
+
";",
|
|
61
|
+
"|",
|
|
62
|
+
"&",
|
|
63
|
+
"(",
|
|
64
|
+
")",
|
|
65
|
+
">",
|
|
66
|
+
"<"
|
|
67
|
+
];
|
|
68
|
+
const reservedWords = /* @__PURE__ */ new Set([
|
|
69
|
+
"!",
|
|
70
|
+
"{",
|
|
71
|
+
"}",
|
|
72
|
+
"case",
|
|
73
|
+
"do",
|
|
74
|
+
"done",
|
|
75
|
+
"elif",
|
|
76
|
+
"else",
|
|
77
|
+
"esac",
|
|
78
|
+
"fi",
|
|
79
|
+
"for",
|
|
80
|
+
"function",
|
|
81
|
+
"if",
|
|
82
|
+
"in",
|
|
83
|
+
"select",
|
|
84
|
+
"then",
|
|
85
|
+
"until",
|
|
86
|
+
"while"
|
|
87
|
+
]);
|
|
39
88
|
const writeCommands = /* @__PURE__ */ new Set(["chmod", "chown", "mkdir", "rm", "rmdir", "tee", "touch", "truncate", "unlink"]);
|
|
40
89
|
const destinationCommands = /* @__PURE__ */ new Set(["cp", "install", "mv"]);
|
|
41
90
|
const prefixWrappers = /* @__PURE__ */ new Set(["env", "nice", "time", "sudo", "nohup", "xargs", "command", "exec"]);
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
91
|
+
const shells = /* @__PURE__ */ new Set(["ash", "bash", "dash", "fish", "ksh", "mksh", "sh", "zsh"]);
|
|
92
|
+
const commandFlag = /^-[A-Za-z]*c[A-Za-z]*$/;
|
|
93
|
+
const maxNesting = 32;
|
|
94
|
+
const tooDeep = () => new StdError.StdError({
|
|
95
|
+
code: "invalid_input",
|
|
96
|
+
message: `Command nests substitutions or quotes deeper than ${maxNesting} levels`
|
|
97
|
+
});
|
|
98
|
+
const substitutionEnd = (text, from, nesting) => {
|
|
99
|
+
if (nesting > maxNesting) throw tooDeep();
|
|
100
|
+
let depth = 1;
|
|
101
|
+
let index = from;
|
|
102
|
+
while (index < text.length) {
|
|
103
|
+
const char = text[index];
|
|
104
|
+
if (char === "\\") {
|
|
105
|
+
index += 2;
|
|
106
|
+
} else if (char === "'") {
|
|
107
|
+
const close = text.indexOf("'", index + 1);
|
|
108
|
+
index = close === -1 ? text.length : close + 1;
|
|
109
|
+
} else if (char === '"') {
|
|
110
|
+
index = doubleQuoteEnd(text, index + 1, nesting + 1) + 1;
|
|
111
|
+
} else if (char === "`") {
|
|
112
|
+
index = backtickEnd(text, index + 1) + 1;
|
|
113
|
+
} else {
|
|
114
|
+
if (char === "(") depth++;
|
|
115
|
+
if (char === ")" && --depth === 0) return index;
|
|
116
|
+
index++;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return text.length;
|
|
120
|
+
};
|
|
121
|
+
const backtickEnd = (text, from) => {
|
|
122
|
+
let index = from;
|
|
123
|
+
while (index < text.length && text[index] !== "`") index += text[index] === "\\" ? 2 : 1;
|
|
124
|
+
return Math.min(index, text.length);
|
|
125
|
+
};
|
|
126
|
+
const doubleQuoteEnd = (text, from, nesting) => {
|
|
127
|
+
if (nesting > maxNesting) throw tooDeep();
|
|
128
|
+
let index = from;
|
|
129
|
+
while (index < text.length && text[index] !== '"') {
|
|
130
|
+
const char = text[index];
|
|
131
|
+
if (char === "\\") index += 2;
|
|
132
|
+
else if (char === "`") index = backtickEnd(text, index + 1) + 1;
|
|
133
|
+
else if (char === "$" && text[index + 1] === "(") index = substitutionEnd(text, index + 2, nesting + 1) + 1;
|
|
134
|
+
else index++;
|
|
135
|
+
}
|
|
136
|
+
return Math.min(index, text.length);
|
|
137
|
+
};
|
|
138
|
+
const descriptorPattern = /\d+(?=[<>])/y;
|
|
139
|
+
const lex = (command, nesting) => {
|
|
140
|
+
const tokens = [];
|
|
141
|
+
const substitutions = [];
|
|
142
|
+
let index = 0;
|
|
143
|
+
const substitution = (from, to) => {
|
|
144
|
+
substitutions.push(command.slice(from, to));
|
|
145
|
+
return "$()";
|
|
146
|
+
};
|
|
147
|
+
while (index < command.length) {
|
|
148
|
+
const char = command[index];
|
|
149
|
+
if (char !== "\n" && /\s/.test(char)) {
|
|
150
|
+
index++;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (char === "\\" && command[index + 1] === "\n") {
|
|
154
|
+
index += 2;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (char === "#") {
|
|
158
|
+
while (index < command.length && command[index] !== "\n") index++;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
descriptorPattern.lastIndex = index;
|
|
162
|
+
const descriptor = descriptorPattern.exec(command);
|
|
163
|
+
if (descriptor !== null) {
|
|
164
|
+
index += descriptor[0].length;
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const operator = operators.find((candidate) => command.startsWith(candidate, index));
|
|
168
|
+
if (operator !== void 0) {
|
|
169
|
+
tokens.push({ operator: true, text: operator });
|
|
170
|
+
index += operator.length;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
let text = "";
|
|
174
|
+
while (index < command.length) {
|
|
175
|
+
const next = command[index];
|
|
176
|
+
if (/[\s<>;|&()]/.test(next)) break;
|
|
177
|
+
if (next === "\\") {
|
|
178
|
+
const escaped = command[index + 1] ?? "";
|
|
179
|
+
if (escaped !== "\n") text += /[\s<>;|&()'"`$\\#]/.test(escaped) ? escaped : `\\${escaped}`;
|
|
180
|
+
index += 2;
|
|
181
|
+
} else if (next === "'") {
|
|
182
|
+
const close = command.indexOf("'", index + 1);
|
|
183
|
+
const end = close === -1 ? command.length : close;
|
|
184
|
+
text += command.slice(index + 1, end);
|
|
185
|
+
index = end + 1;
|
|
186
|
+
} else if (next === '"') {
|
|
187
|
+
const end = doubleQuoteEnd(command, index + 1, nesting + 1);
|
|
188
|
+
let inner = index + 1;
|
|
189
|
+
while (inner < end) {
|
|
190
|
+
const quoted = command[inner];
|
|
191
|
+
if (quoted === "\\" && /[$`"\\\n]/.test(command[inner + 1] ?? "")) {
|
|
192
|
+
if (command[inner + 1] !== "\n") text += command[inner + 1];
|
|
193
|
+
inner += 2;
|
|
194
|
+
} else if (quoted === "`") {
|
|
195
|
+
const close = backtickEnd(command, inner + 1);
|
|
196
|
+
text += substitution(inner + 1, close);
|
|
197
|
+
inner = close + 1;
|
|
198
|
+
} else if (quoted === "$" && command[inner + 1] === "(") {
|
|
199
|
+
const close = substitutionEnd(command, inner + 2, nesting + 2);
|
|
200
|
+
text += substitution(inner + 2, close);
|
|
201
|
+
inner = close + 1;
|
|
202
|
+
} else {
|
|
203
|
+
text += quoted;
|
|
204
|
+
inner++;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
index = end + 1;
|
|
208
|
+
} else if (next === "`") {
|
|
209
|
+
const close = backtickEnd(command, index + 1);
|
|
210
|
+
text += substitution(index + 1, close);
|
|
211
|
+
index = close + 1;
|
|
212
|
+
} else if (next === "$" && command[index + 1] === "(") {
|
|
213
|
+
const close = substitutionEnd(command, index + 2, nesting + 1);
|
|
214
|
+
text += substitution(index + 2, close);
|
|
215
|
+
index = close + 1;
|
|
216
|
+
} else {
|
|
217
|
+
text += next;
|
|
218
|
+
index++;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
tokens.push({ operator: false, text });
|
|
222
|
+
}
|
|
223
|
+
return { tokens, substitutions };
|
|
47
224
|
};
|
|
48
|
-
const pathValue = (token) =>
|
|
225
|
+
const pathValue = (token) => token.replace(/,+$/, "");
|
|
49
226
|
const isPathToken = (path, token) => token === "." || token === ".." || token.startsWith("./") || token.startsWith("../") || path.isAbsolute(token) || /^[A-Za-z]:[\\/]/.test(token) || token.includes("/");
|
|
50
|
-
const segmentReferences = (path, tokens) => {
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
227
|
+
const segmentReferences = (path, tokens, nesting) => {
|
|
228
|
+
const isTarget = (index) => index > 0 && isRedirection(tokens[index - 1]);
|
|
229
|
+
const isWord = (index) => !tokens[index].operator && !isTarget(index);
|
|
230
|
+
const name = (index) => path.basename(pathValue(tokens[index].text));
|
|
231
|
+
const hasWrapper = tokens.some((_, index) => isWord(index) && prefixWrappers.has(name(index)));
|
|
232
|
+
let commandIndex = tokens.findIndex(
|
|
233
|
+
(token, index) => isWord(index) && !token.text.includes("=") && !prefixWrappers.has(name(index)) && !reservedWords.has(token.text) && !token.text.startsWith("-") && token.text !== ""
|
|
56
234
|
);
|
|
57
235
|
if (hasWrapper) {
|
|
58
|
-
const mutatingIndex =
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
});
|
|
236
|
+
const mutatingIndex = tokens.findIndex(
|
|
237
|
+
(_, index) => isWord(index) && (writeCommands.has(name(index)) || destinationCommands.has(name(index)))
|
|
238
|
+
);
|
|
62
239
|
if (mutatingIndex !== -1) commandIndex = mutatingIndex;
|
|
63
240
|
}
|
|
64
|
-
|
|
65
|
-
const
|
|
241
|
+
const command = commandIndex === -1 ? "" : name(commandIndex);
|
|
242
|
+
const scripts = /* @__PURE__ */ new Set();
|
|
243
|
+
if (command === "eval") {
|
|
244
|
+
for (let index = commandIndex + 1; index < tokens.length; index++) if (isWord(index)) scripts.add(index);
|
|
245
|
+
} else if (shells.has(command)) {
|
|
246
|
+
const flag = tokens.findIndex(
|
|
247
|
+
(token, index) => index > commandIndex && isWord(index) && commandFlag.test(token.text)
|
|
248
|
+
);
|
|
249
|
+
if (flag !== -1 && flag + 1 < tokens.length && isWord(flag + 1)) scripts.add(flag + 1);
|
|
250
|
+
}
|
|
66
251
|
const candidates = [];
|
|
67
|
-
for (let index = 0; index <
|
|
68
|
-
const token =
|
|
69
|
-
if (index === commandIndex ||
|
|
70
|
-
|
|
252
|
+
for (let index = 0; index < tokens.length; index++) {
|
|
253
|
+
const token = tokens[index];
|
|
254
|
+
if (index === commandIndex || scripts.has(index) || token.operator) continue;
|
|
255
|
+
if (!isTarget(index) && token.text.startsWith("-")) continue;
|
|
256
|
+
if (isTarget(index) && textRedirections.has(tokens[index - 1].text)) continue;
|
|
257
|
+
const value = pathValue(token.text);
|
|
71
258
|
if (value !== "" && isPathToken(path, value)) candidates.push({ index, value });
|
|
72
259
|
}
|
|
73
|
-
const destinationIndex = destinationCommands.has(command) ? candidates
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
260
|
+
const destinationIndex = destinationCommands.has(command) ? candidates.filter(({ index }) => !isTarget(index)).at(-1)?.index : void 0;
|
|
261
|
+
const script = [...scripts].map((index) => tokens[index].text).join(" ");
|
|
262
|
+
const nested = script === "" ? [] : references(path, script, nesting + 1);
|
|
263
|
+
const named = candidates.map(({ index, value }) => {
|
|
264
|
+
const access = isTarget(index) ? writeRedirections.has(tokens[index - 1].text) ? "write" : "read" : writeCommands.has(command) || destinationIndex === index ? "write" : "read";
|
|
77
265
|
return { access, value };
|
|
78
266
|
});
|
|
267
|
+
return [...named, ...nested];
|
|
79
268
|
};
|
|
80
|
-
const
|
|
81
|
-
|
|
269
|
+
const references = (path, command, nesting) => {
|
|
270
|
+
if (nesting > maxNesting) throw tooDeep();
|
|
271
|
+
const { substitutions, tokens } = lex(command, nesting);
|
|
272
|
+
const found = [];
|
|
82
273
|
let segment = [];
|
|
83
|
-
for (const token of
|
|
84
|
-
if (commandSeparators.has(token)) {
|
|
85
|
-
|
|
274
|
+
for (const token of tokens) {
|
|
275
|
+
if (token.operator && commandSeparators.has(token.text)) {
|
|
276
|
+
found.push(...segmentReferences(path, segment, nesting));
|
|
86
277
|
segment = [];
|
|
87
278
|
} else {
|
|
88
279
|
segment.push(token);
|
|
89
280
|
}
|
|
90
281
|
}
|
|
91
|
-
|
|
92
|
-
|
|
282
|
+
found.push(...segmentReferences(path, segment, nesting));
|
|
283
|
+
for (const body of substitutions) found.push(...references(path, body, nesting + 1));
|
|
284
|
+
return found;
|
|
93
285
|
};
|
|
286
|
+
const commandReferences = (path, command) => references(path, command, 0);
|
|
94
287
|
const resolvePath = (path, cwd, value) => path.isAbsolute(value) ? path.normalize(value) : path.resolve(cwd, value);
|
|
95
288
|
const isDeclared = (path, cwd, declared, source) => (0, import_Paths.withinEnvelope)(
|
|
96
289
|
declared.map((entry) => resolvePath(path, cwd, entry)),
|
|
@@ -106,7 +299,13 @@ const outsideEnvelope = (input, text, path) => {
|
|
|
106
299
|
path: cwd
|
|
107
300
|
});
|
|
108
301
|
}
|
|
109
|
-
|
|
302
|
+
let named;
|
|
303
|
+
try {
|
|
304
|
+
named = commandReferences(path, text);
|
|
305
|
+
} catch {
|
|
306
|
+
return tooDeep();
|
|
307
|
+
}
|
|
308
|
+
for (const reference of named) {
|
|
110
309
|
const resolved = resolvePath(path, cwd, reference.value);
|
|
111
310
|
if (resolved.startsWith("/dev/")) continue;
|
|
112
311
|
const declared = reference.access === "write" ? input.writes : input.reads;
|