@smthrs/std 1.0.0-rc.8 → 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.
Files changed (60) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +3 -1
  3. package/dist/cjs/PortableSearch.d.ts +13 -1
  4. package/dist/cjs/PortableSearch.d.ts.map +1 -1
  5. package/dist/cjs/PortableSearch.js +8 -1
  6. package/dist/cjs/PortableSearch.js.map +2 -2
  7. package/dist/cjs/RemoteSearch.d.ts +54 -0
  8. package/dist/cjs/RemoteSearch.d.ts.map +1 -0
  9. package/dist/cjs/RemoteSearch.js +134 -0
  10. package/dist/cjs/RemoteSearch.js.map +7 -0
  11. package/dist/cjs/index.d.ts +2 -0
  12. package/dist/cjs/index.d.ts.map +1 -1
  13. package/dist/cjs/index.js +2 -0
  14. package/dist/cjs/index.js.map +2 -2
  15. package/dist/cjs/internal/EnvelopePrecheck.d.ts.map +1 -1
  16. package/dist/cjs/internal/EnvelopePrecheck.js +21 -5
  17. package/dist/cjs/internal/EnvelopePrecheck.js.map +2 -2
  18. package/dist/cjs/internal/RemoteTree.d.ts +60 -0
  19. package/dist/cjs/internal/RemoteTree.d.ts.map +1 -0
  20. package/dist/cjs/internal/RemoteTree.js +123 -0
  21. package/dist/cjs/internal/RemoteTree.js.map +7 -0
  22. package/dist/cjs/internal/Tar.d.ts +23 -0
  23. package/dist/cjs/internal/Tar.d.ts.map +1 -0
  24. package/dist/cjs/internal/Tar.js +86 -0
  25. package/dist/cjs/internal/Tar.js.map +7 -0
  26. package/dist/esm/PortableSearch.d.ts +13 -1
  27. package/dist/esm/PortableSearch.d.ts.map +1 -1
  28. package/dist/esm/PortableSearch.js +16 -0
  29. package/dist/esm/PortableSearch.js.map +1 -1
  30. package/dist/esm/RemoteSearch.d.ts +54 -0
  31. package/dist/esm/RemoteSearch.d.ts.map +1 -0
  32. package/dist/esm/RemoteSearch.js +162 -0
  33. package/dist/esm/RemoteSearch.js.map +1 -0
  34. package/dist/esm/index.d.ts +2 -0
  35. package/dist/esm/index.d.ts.map +1 -1
  36. package/dist/esm/index.js +2 -0
  37. package/dist/esm/index.js.map +1 -1
  38. package/dist/esm/internal/EnvelopePrecheck.d.ts.map +1 -1
  39. package/dist/esm/internal/EnvelopePrecheck.js +29 -5
  40. package/dist/esm/internal/EnvelopePrecheck.js.map +1 -1
  41. package/dist/esm/internal/RemoteTree.d.ts +60 -0
  42. package/dist/esm/internal/RemoteTree.d.ts.map +1 -0
  43. package/dist/esm/internal/RemoteTree.js +132 -0
  44. package/dist/esm/internal/RemoteTree.js.map +1 -0
  45. package/dist/esm/internal/Tar.d.ts +23 -0
  46. package/dist/esm/internal/Tar.d.ts.map +1 -0
  47. package/dist/esm/internal/Tar.js +97 -0
  48. package/dist/esm/internal/Tar.js.map +1 -0
  49. package/docs/api.md +28 -4
  50. package/docs/concepts/effects-and-capabilities.md +3 -1
  51. package/docs/guides/bind-the-standard-flows.md +3 -1
  52. package/docs/guides/run-a-shell-command.md +4 -1
  53. package/docs/troubleshooting.md +8 -0
  54. package/package.json +16 -6
  55. package/src/PortableSearch.ts +20 -0
  56. package/src/RemoteSearch.ts +199 -0
  57. package/src/index.ts +3 -0
  58. package/src/internal/EnvelopePrecheck.ts +29 -5
  59. package/src/internal/RemoteTree.ts +159 -0
  60. package/src/internal/Tar.ts +95 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ### Added
6
+
7
+ - `RemoteSearch` answers `grep` and `glob` for a tree on another machine in two
8
+ round trips: the machine's `find` lists the root and `tar` returns its
9
+ `.gitignore` files, then `tar` returns the files the search reads, and
10
+ `PortableSearch` answers over that copy. Results, notices, and refusals are
11
+ the portable peer's. A sealed benchmark host searches its task container this
12
+ way rather than with one container exec per directory and per file.
13
+ - `PortableSearch.searched` names the files a `grep` reads for an input.
14
+
5
15
  ### Fixed
6
16
 
7
17
  - Container commands reject sparse argument arrays and build plans from the
package/README.md CHANGED
@@ -37,10 +37,11 @@ The root entry point exports these namespaces; each is also importable from `@sm
37
37
  | `Manifest` | `flows`, `handlers`, `effectsFor`, `names`, `readOnly` | Exposes frozen flow, handler, and narrowing registries plus the read-only set. |
38
38
  | `NativeSearch` | `MAX_CAPTURE_BYTES`, `make`, `layer` | Implements Search through the ripgrep binary. |
39
39
  | `NodeLanguageServer` | `Config`, `MAX_QUEUED_FRAMES`, `MAX_PENDING_REQUESTS`, `make`, `makeLazy`, `layer`, `layerLazy` | Implements LanguageServer with Node child processes, routed by extension. |
40
- | `PortableSearch` | `make`, `layer` | Implements Search by walking the kernel filesystem in process. |
40
+ | `PortableSearch` | `make`, `layer`, `searched` | Implements Search by walking the kernel filesystem in process. |
41
41
  | `Probe` | `key`, `Reason`, `InvalidProbe`, `Attribution`, `CONFIDENCE_FLOOR`, `MAX_OUTPUT_BYTES`, `posix`, `probeAttribution`, `classify`, `unjudged` | Attributes a non-zero exit to the command or to the tree, with Jev. |
42
42
  | `Read` | `name`, `description`, `Input`, `Output`, `effects`, `effectsFor`, `capabilities`, `flow`, `run`, `activity`, `presentation`, `MAX_READ_FILE_BYTES`, `Preconditions`, `ReadSession`, `VersionedFileSystem` | Declares and runs bounded file reads. |
43
43
  | `Relocate` | `Relocation`, `relocate` | Rewrites a call's input so the call runs against a materialized checkpoint. |
44
+ | `RemoteSearch` | `maxBytes`, `make` | Implements Search for another machine's tree in two shell round trips. |
44
45
  | `Search` | `GrepInput`, `GrepLine`, `ContextLine`, `Symbol`, `GrepMatch`, `GrepOutput`, `GlobInput`, `GlobOutput`, `Search`, `make`, `makeNoop`, `layerNoop` | Defines the search service both peers implement. |
45
46
  | `SearchConformance` | `GeneratedFile`, `Plan`, `Divergence`, `plan`, `materialize`, `compare`, `report` | Generates a tree and calls, then reports where two Search peers disagree. |
46
47
  | `SearchContract` | `validatePattern`, `validateGlob`, `canonicalGlob`, `matchesGlob`, `includedByGlobs`, `expression`, `unsatisfiableNotice`, `isContractRejection` | The shared matcher an external Search peer must build on. |
@@ -98,6 +99,7 @@ Every limit is a display budget disclosed to the caller, never a silent cut. A c
98
99
  | `ShellCommand.DEFAULT_TIMEOUT_MS` | 10,000 | one `shell_command` call with no `timeout_ms` |
99
100
  | `ShellCommand.MAX_CAPTURE_BYTES` | 8,000,000 | the command output one `shell_command` call holds in memory |
100
101
  | `NativeSearch.MAX_CAPTURE_BYTES` | 64 MiB | one `rg` invocation's captured output, refused past the cap |
102
+ | `RemoteSearch.maxBytes` | 256 MiB | the files one search fetches from a machine, refused past it |
101
103
  | HTTP response bytes | 5 MiB | `fetch`, `http-post` and `webfetch`, refused past the cap |
102
104
  | `fetch` / `http-post` timeout | 30 s | total request and body budget; `timeout` seconds, capped at 120 |
103
105
  | `webfetch` request timeout | 120 s cap | the request and the body read |
@@ -4,9 +4,21 @@
4
4
  * @since 1.0.0
5
5
  */
6
6
  import * as Path from "@smthrs/kernel/Path";
7
- import { type Context, Layer } from "effect";
7
+ import { type Context, Effect, Layer } from "effect";
8
8
  import * as FileSystem from "effect/FileSystem";
9
9
  import * as Search from "./Search.ts";
10
+ import * as StdError from "./StdError.ts";
11
+ /**
12
+ * The files this peer's `grep` reads for `input`, in the order it reads
13
+ * them: the walk under `input.root` with its `.gitignore` rules and
14
+ * `input.globs` applied and symbolic links left out, or the root itself when
15
+ * it names one file. A peer that has to bring a tree's bytes to this process
16
+ * before it can search them (`RemoteSearch`) fetches exactly these.
17
+ *
18
+ * @category queries
19
+ * @since 1.0.0-rc.8
20
+ */
21
+ export declare const searched: (input: Pick<Search.GrepInput, "root" | "hidden" | "noIgnore" | "globs">) => Effect.Effect<ReadonlyArray<string>, StdError.StdError, FileSystem.FileSystem | Path.Path>;
10
22
  /**
11
23
  * Captures filesystem and path services in the portable peer.
12
24
  *
@@ -1 +1 @@
1
- {"version":3,"file":"PortableSearch.d.ts","sourceRoot":"","sources":["../../src/PortableSearch.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,IAAI,MAAM,qBAAqB,CAAA;AAC3C,OAAO,EAAE,KAAK,OAAO,EAAU,KAAK,EAAU,MAAM,QAAQ,CAAA;AAC5D,OAAO,KAAK,UAAU,MAAM,mBAAmB,CAAA;AAM/C,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AAoOrC;;;;;GAKG;AACH,eAAO,MAAM,IAAI,GAAI,UAAU,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,KAAG,MAAM,CAAC,MAIvF,CAAA;AAEJ;;;;;GAKG;AACH,eAAO,MAAM,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,UAAU,CAAC,UAAU,GAAG,IAAI,CAAC,IAAI,CAGtF,CAAA"}
1
+ {"version":3,"file":"PortableSearch.d.ts","sourceRoot":"","sources":["../../src/PortableSearch.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,IAAI,MAAM,qBAAqB,CAAA;AAC3C,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,EAAE,KAAK,EAAU,MAAM,QAAQ,CAAA;AAC5D,OAAO,KAAK,UAAU,MAAM,mBAAmB,CAAA;AAM/C,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AAErC,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AAkOzC;;;;;;;;;GASG;AACH,eAAO,MAAM,QAAQ,GACnB,OAAO,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC,KACtE,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC,UAAU,GAAG,IAAI,CAAC,IAAI,CAMxF,CAAA;AAEJ;;;;;GAKG;AACH,eAAO,MAAM,IAAI,GAAI,UAAU,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,KAAG,MAAM,CAAC,MAIvF,CAAA;AAEJ;;;;;GAKG;AACH,eAAO,MAAM,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,UAAU,CAAC,UAAU,GAAG,IAAI,CAAC,IAAI,CAGtF,CAAA"}
@@ -29,7 +29,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
29
29
  var PortableSearch_exports = {};
30
30
  __export(PortableSearch_exports, {
31
31
  layer: () => layer,
32
- make: () => make
32
+ make: () => make,
33
+ searched: () => searched
33
34
  });
34
35
  module.exports = __toCommonJS(PortableSearch_exports);
35
36
  var Path = __toESM(require("@smthrs/kernel/Path"), 1);
@@ -222,6 +223,12 @@ const glob = (input) => import_effect.Effect.gen(function* () {
222
223
  ...unsatisfiable === void 0 ? {} : { notice: unsatisfiable }
223
224
  };
224
225
  });
226
+ const searched = (input) => import_effect.Effect.gen(function* () {
227
+ const fileSystem = yield* FileSystem.FileSystem;
228
+ const path = yield* Path.Path;
229
+ const walked = yield* Walk.files(fileSystem, path, input.root, input.hidden, input.noIgnore);
230
+ return yield* candidates(fileSystem, path, walked, input.root, input.globs);
231
+ });
225
232
  const make = (services) => Search.make({
226
233
  grep: (input) => import_effect.Effect.provide(grep(input), services),
227
234
  glob: (input) => import_effect.Effect.provide(glob(input), services)
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/PortableSearch.ts"],
4
- "sourcesContent": ["/**\n * The in-process implementation of the ripgrep search contract.\n *\n * @since 1.0.0\n */\n\nimport * as Path from \"@smthrs/kernel/Path\"\nimport { type Context, Effect, Layer, Stream } from \"effect\"\nimport * as FileSystem from \"effect/FileSystem\"\nimport * as Grouping from \"./internal/Grouping.ts\"\nimport * as LinearRegex from \"./internal/LinearRegex.ts\"\nimport { escapeRegex } from \"./internal/SearchContract.ts\"\nimport { notice, truncateBytes } from \"./internal/Text.ts\"\nimport * as Walk from \"./internal/Walk.ts\"\nimport * as Search from \"./Search.ts\"\nimport * as Contract from \"./SearchContract.ts\"\nimport * as StdError from \"./StdError.ts\"\n\n/**\n * Narrows a walk to the files a search reports.\n *\n * A root that names one file is that file: `rg` searches a path given on the\n * command line whatever `-g` says, and follows it even when it is a symlink.\n * Everything a walk found is filtered by the globs first and probed for\n * symlinks second, so the probe runs over the far smaller included set.\n */\nconst candidates = (\n fileSystem: FileSystem.FileSystem,\n path: Path.Path,\n walked: Walk.Walked,\n root: string,\n globs: ReadonlyArray<string>\n): Effect.Effect<ReadonlyArray<string>> =>\n Effect.gen(function*() {\n if (walked.explicitFile) return walked.files\n const included = walked.files.filter((file) =>\n Contract.includedByGlobs(globs, Walk.relativeForPattern(path, root, file), path.basename(file))\n )\n const links = yield* Walk.symbolicLinks(fileSystem, included)\n return included.filter((_, index) => links[index] !== true)\n })\n\nconst preview = (line: string): string => {\n let head = \"\"\n let length = 0\n for (const character of line) {\n if (length++ === 500) break\n head += character\n }\n return truncateBytes(head, 500, { keep: \"head\" }).text\n}\n\nconst grep = (\n input: Search.GrepInput\n): Effect.Effect<Search.GrepOutput, StdError.StdError, FileSystem.FileSystem | Path.Path> =>\n Effect.gen(function*() {\n const fileSystem = yield* FileSystem.FileSystem\n const path = yield* Path.Path\n const includedByGlobs = (relative: string, basename: string) =>\n Contract.includedByGlobs(input.globs, relative, basename)\n const walked = yield* Walk.files(fileSystem, path, input.root, input.hidden, input.noIgnore, includedByGlobs)\n const insensitive = input.ignoreCase || (input.smartCase && !/[A-Z]/.test(input.pattern))\n const regex = LinearRegex.compile(\n input.fixedStrings ? escapeRegex(input.pattern) : input.pattern,\n insensitive\n )\n const shownMatches: Array<Search.GrepMatch> = []\n const shownFiles: Array<string> = []\n let total = 0\n let filesSearched = 0\n let skippedBinary = 0\n\n const included = yield* candidates(fileSystem, path, walked, input.root, input.globs)\n for (const file of included) {\n // Count every admitted file, including unreadable files and binaries.\n filesSearched++\n const remaining = input.filesWithMatches ? 0 : input.limit - shownMatches.length\n const matched: Array<number> = []\n const selected = new Map<number, Search.GrepLine>()\n const recent = new Map<number, Search.GrepLine>()\n let count = 0\n let lineNumber = 0\n let through = 0\n const scanLine = (text: string) =>\n Effect.gen(function*() {\n lineNumber++\n let matches = false\n if (input.maxCount === undefined || count < input.maxCount) {\n const evaluation = regex.test(text)\n let step = evaluation.next()\n while (!step.done) {\n yield* Effect.yieldNow\n step = evaluation.next()\n }\n matches = step.value\n }\n if (matches) count++\n if (remaining <= 0) return\n // The first overflow hit is a boundary: context nearest to it must\n // not migrate to the last retained hit when the limit is applied.\n const retain = matches && matched.length < remaining + 1\n const awaiting = matched.length < remaining + 1\n if (retain || lineNumber <= through || (awaiting && input.beforeContext > 0)) {\n const row: Search.GrepLine = {\n file,\n line: lineNumber,\n text: preview(text),\n kind: retain ? \"match\" : \"context\"\n }\n if (retain) {\n matched.push(lineNumber)\n for (const [number, context] of recent) selected.set(number, context)\n through = lineNumber + input.afterContext\n }\n if (retain || lineNumber <= through) selected.set(lineNumber, row)\n if (awaiting && input.beforeContext > 0) recent.set(lineNumber, row)\n }\n recent.delete(lineNumber - input.beforeContext)\n if (matched.length >= remaining + 1) recent.clear()\n })\n // Split only on LF (stripping a preceding CR), just like sourceLines.\n // Stream.splitLines also treats a standalone CR as a delimiter.\n const decoder = new TextDecoder()\n let fragments: Array<string> = []\n const consume = (chunk: string) =>\n Effect.gen(function*() {\n let from = 0\n let newline = chunk.indexOf(\"\\n\")\n while (newline >= 0) {\n fragments.push(chunk.slice(from, newline))\n const line = fragments.join(\"\")\n fragments = []\n yield* scanLine(line.endsWith(\"\\r\") ? line.slice(0, -1) : line)\n from = newline + 1\n newline = chunk.indexOf(\"\\n\", from)\n }\n if (from < chunk.length) fragments.push(chunk.slice(from))\n })\n const scanned = yield* Effect.gen(function*() {\n let started = false\n // A guarded disk host can safely expose atomic reads while refusing\n // open streaming handles. Retry through that same filesystem service\n // only before any bytes arrive, so a failed partial stream is never\n // combined with a second reading of a file that may have changed.\n yield* fileSystem.stream(file).pipe(\n Stream.catch((error) => started ? Stream.fail(error) : Stream.fromEffect(fileSystem.readFile(file))),\n Stream.runForEach((bytes) => {\n started = true\n if (bytes.includes(0)) {\n return Effect.fail(\n new StdError.StdError({\n code: \"binary_file\",\n message: `Cannot search binary file: ${file}`,\n path: file\n })\n )\n }\n return consume(decoder.decode(bytes, { stream: true }))\n })\n )\n yield* consume(decoder.decode())\n if (fragments.length > 0) yield* scanLine(fragments.join(\"\"))\n return true\n }).pipe(Effect.catch((error) => {\n if (error instanceof StdError.StdError) {\n if (walked.explicitFile) return Effect.fail(error)\n skippedBinary++\n }\n return Effect.succeed(false)\n }))\n if (!scanned || count === 0) continue\n total += input.filesWithMatches ? 1 : count\n if (input.filesWithMatches) {\n if (shownFiles.length < input.limit) shownFiles.push(file)\n continue\n }\n if (matched.length === 0) continue\n const grouped = Grouping.group([...selected.values()].sort((left, right) => left.line - right.line))\n .slice(0, remaining)\n // Source is needed only for returned symbols, and is released before\n // moving to the next file. Overflow files never load symbol source.\n const contents = new Map<string, ReadonlyArray<string>>()\n if (input.symbols) {\n const source = yield* fileSystem.readFileString(file).pipe(Effect.orElseSucceed(() => undefined))\n if (source !== undefined) contents.set(file, Grouping.sourceLines(source))\n }\n shownMatches.push(...Grouping.annotate(grouped, contents))\n }\n\n const truncated = total > input.limit\n const unsatisfiable = total > 0 ? undefined : yield* Contract.unsatisfiableNotice({\n fileSystem,\n path,\n root: input.root,\n globs: input.globs,\n hidden: input.hidden,\n noIgnore: input.noIgnore,\n ignored: walked.ignored\n })\n return {\n matches: shownMatches,\n files: shownFiles,\n filesSearched,\n skippedBinary,\n truncated,\n ...(truncated\n ? { notice: notice(input.filesWithMatches ? \"files\" : \"matches\", input.limit, total) }\n : {}),\n ...(unsatisfiable === undefined ? {} : { notice: unsatisfiable })\n }\n })\n\nconst glob = (\n input: Search.GlobInput\n): Effect.Effect<Search.GlobOutput, StdError.StdError, FileSystem.FileSystem | Path.Path> =>\n Effect.gen(function*() {\n const fileSystem = yield* FileSystem.FileSystem\n const path = yield* Path.Path\n const includedByGlobs = (relative: string, basename: string) =>\n Contract.includedByGlobs([input.pattern], relative, basename)\n const walked = yield* Walk.files(fileSystem, path, input.root, input.hidden, input.noIgnore, includedByGlobs)\n const included = yield* candidates(fileSystem, path, walked, input.root, [input.pattern])\n const matching = [...included].sort()\n const paths = matching.slice(0, input.limit)\n const unsatisfiable = matching.length > 0 ? undefined : yield* Contract.unsatisfiableNotice({\n fileSystem,\n path,\n root: input.root,\n globs: [input.pattern],\n hidden: input.hidden,\n noIgnore: input.noIgnore,\n ignored: walked.ignored\n })\n return {\n paths,\n total: matching.length,\n truncated: matching.length > input.limit,\n ...(matching.length > input.limit ? { notice: notice(\"entries\", paths.length, matching.length) } : {}),\n ...(unsatisfiable === undefined ? {} : { notice: unsatisfiable })\n }\n })\n\n/**\n * Captures filesystem and path services in the portable peer.\n *\n * @category constructors\n * @since 1.0.0\n */\nexport const make = (services: Context.Context<FileSystem.FileSystem | Path.Path>): Search.Search =>\n Search.make({\n grep: (input) => Effect.provide(grep(input), services),\n glob: (input) => Effect.provide(glob(input), services)\n })\n\n/**\n * Provides the in-process peer.\n *\n * @category layers\n * @since 1.0.0\n */\nexport const layer: Layer.Layer<Search.Search, never, FileSystem.FileSystem | Path.Path> = Layer.effect(\n Search.Search,\n Effect.map(Effect.context<FileSystem.FileSystem | Path.Path>(), make)\n)\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * The in-process implementation of the ripgrep search contract.\n *\n * @since 1.0.0\n */\n\nimport * as Path from \"@smthrs/kernel/Path\"\nimport { type Context, Effect, Layer, Stream } from \"effect\"\nimport * as FileSystem from \"effect/FileSystem\"\nimport * as Grouping from \"./internal/Grouping.ts\"\nimport * as LinearRegex from \"./internal/LinearRegex.ts\"\nimport { escapeRegex } from \"./internal/SearchContract.ts\"\nimport { notice, truncateBytes } from \"./internal/Text.ts\"\nimport * as Walk from \"./internal/Walk.ts\"\nimport * as Search from \"./Search.ts\"\nimport * as Contract from \"./SearchContract.ts\"\nimport * as StdError from \"./StdError.ts\"\n\n/**\n * Narrows a walk to the files a search reports.\n *\n * A root that names one file is that file: `rg` searches a path given on the\n * command line whatever `-g` says, and follows it even when it is a symlink.\n * Everything a walk found is filtered by the globs first and probed for\n * symlinks second, so the probe runs over the far smaller included set.\n */\nconst candidates = (\n fileSystem: FileSystem.FileSystem,\n path: Path.Path,\n walked: Walk.Walked,\n root: string,\n globs: ReadonlyArray<string>\n): Effect.Effect<ReadonlyArray<string>> =>\n Effect.gen(function*() {\n if (walked.explicitFile) return walked.files\n const included = walked.files.filter((file) =>\n Contract.includedByGlobs(globs, Walk.relativeForPattern(path, root, file), path.basename(file))\n )\n const links = yield* Walk.symbolicLinks(fileSystem, included)\n return included.filter((_, index) => links[index] !== true)\n })\n\nconst preview = (line: string): string => {\n let head = \"\"\n let length = 0\n for (const character of line) {\n if (length++ === 500) break\n head += character\n }\n return truncateBytes(head, 500, { keep: \"head\" }).text\n}\n\nconst grep = (\n input: Search.GrepInput\n): Effect.Effect<Search.GrepOutput, StdError.StdError, FileSystem.FileSystem | Path.Path> =>\n Effect.gen(function*() {\n const fileSystem = yield* FileSystem.FileSystem\n const path = yield* Path.Path\n const includedByGlobs = (relative: string, basename: string) =>\n Contract.includedByGlobs(input.globs, relative, basename)\n const walked = yield* Walk.files(fileSystem, path, input.root, input.hidden, input.noIgnore, includedByGlobs)\n const insensitive = input.ignoreCase || (input.smartCase && !/[A-Z]/.test(input.pattern))\n const regex = LinearRegex.compile(\n input.fixedStrings ? escapeRegex(input.pattern) : input.pattern,\n insensitive\n )\n const shownMatches: Array<Search.GrepMatch> = []\n const shownFiles: Array<string> = []\n let total = 0\n let filesSearched = 0\n let skippedBinary = 0\n\n const included = yield* candidates(fileSystem, path, walked, input.root, input.globs)\n for (const file of included) {\n // Count every admitted file, including unreadable files and binaries.\n filesSearched++\n const remaining = input.filesWithMatches ? 0 : input.limit - shownMatches.length\n const matched: Array<number> = []\n const selected = new Map<number, Search.GrepLine>()\n const recent = new Map<number, Search.GrepLine>()\n let count = 0\n let lineNumber = 0\n let through = 0\n const scanLine = (text: string) =>\n Effect.gen(function*() {\n lineNumber++\n let matches = false\n if (input.maxCount === undefined || count < input.maxCount) {\n const evaluation = regex.test(text)\n let step = evaluation.next()\n while (!step.done) {\n yield* Effect.yieldNow\n step = evaluation.next()\n }\n matches = step.value\n }\n if (matches) count++\n if (remaining <= 0) return\n // The first overflow hit is a boundary: context nearest to it must\n // not migrate to the last retained hit when the limit is applied.\n const retain = matches && matched.length < remaining + 1\n const awaiting = matched.length < remaining + 1\n if (retain || lineNumber <= through || (awaiting && input.beforeContext > 0)) {\n const row: Search.GrepLine = {\n file,\n line: lineNumber,\n text: preview(text),\n kind: retain ? \"match\" : \"context\"\n }\n if (retain) {\n matched.push(lineNumber)\n for (const [number, context] of recent) selected.set(number, context)\n through = lineNumber + input.afterContext\n }\n if (retain || lineNumber <= through) selected.set(lineNumber, row)\n if (awaiting && input.beforeContext > 0) recent.set(lineNumber, row)\n }\n recent.delete(lineNumber - input.beforeContext)\n if (matched.length >= remaining + 1) recent.clear()\n })\n // Split only on LF (stripping a preceding CR), just like sourceLines.\n // Stream.splitLines also treats a standalone CR as a delimiter.\n const decoder = new TextDecoder()\n let fragments: Array<string> = []\n const consume = (chunk: string) =>\n Effect.gen(function*() {\n let from = 0\n let newline = chunk.indexOf(\"\\n\")\n while (newline >= 0) {\n fragments.push(chunk.slice(from, newline))\n const line = fragments.join(\"\")\n fragments = []\n yield* scanLine(line.endsWith(\"\\r\") ? line.slice(0, -1) : line)\n from = newline + 1\n newline = chunk.indexOf(\"\\n\", from)\n }\n if (from < chunk.length) fragments.push(chunk.slice(from))\n })\n const scanned = yield* Effect.gen(function*() {\n let started = false\n // A guarded disk host can safely expose atomic reads while refusing\n // open streaming handles. Retry through that same filesystem service\n // only before any bytes arrive, so a failed partial stream is never\n // combined with a second reading of a file that may have changed.\n yield* fileSystem.stream(file).pipe(\n Stream.catch((error) => started ? Stream.fail(error) : Stream.fromEffect(fileSystem.readFile(file))),\n Stream.runForEach((bytes) => {\n started = true\n if (bytes.includes(0)) {\n return Effect.fail(\n new StdError.StdError({\n code: \"binary_file\",\n message: `Cannot search binary file: ${file}`,\n path: file\n })\n )\n }\n return consume(decoder.decode(bytes, { stream: true }))\n })\n )\n yield* consume(decoder.decode())\n if (fragments.length > 0) yield* scanLine(fragments.join(\"\"))\n return true\n }).pipe(Effect.catch((error) => {\n if (error instanceof StdError.StdError) {\n if (walked.explicitFile) return Effect.fail(error)\n skippedBinary++\n }\n return Effect.succeed(false)\n }))\n if (!scanned || count === 0) continue\n total += input.filesWithMatches ? 1 : count\n if (input.filesWithMatches) {\n if (shownFiles.length < input.limit) shownFiles.push(file)\n continue\n }\n if (matched.length === 0) continue\n const grouped = Grouping.group([...selected.values()].sort((left, right) => left.line - right.line))\n .slice(0, remaining)\n // Source is needed only for returned symbols, and is released before\n // moving to the next file. Overflow files never load symbol source.\n const contents = new Map<string, ReadonlyArray<string>>()\n if (input.symbols) {\n const source = yield* fileSystem.readFileString(file).pipe(Effect.orElseSucceed(() => undefined))\n if (source !== undefined) contents.set(file, Grouping.sourceLines(source))\n }\n shownMatches.push(...Grouping.annotate(grouped, contents))\n }\n\n const truncated = total > input.limit\n const unsatisfiable = total > 0 ? undefined : yield* Contract.unsatisfiableNotice({\n fileSystem,\n path,\n root: input.root,\n globs: input.globs,\n hidden: input.hidden,\n noIgnore: input.noIgnore,\n ignored: walked.ignored\n })\n return {\n matches: shownMatches,\n files: shownFiles,\n filesSearched,\n skippedBinary,\n truncated,\n ...(truncated\n ? { notice: notice(input.filesWithMatches ? \"files\" : \"matches\", input.limit, total) }\n : {}),\n ...(unsatisfiable === undefined ? {} : { notice: unsatisfiable })\n }\n })\n\nconst glob = (\n input: Search.GlobInput\n): Effect.Effect<Search.GlobOutput, StdError.StdError, FileSystem.FileSystem | Path.Path> =>\n Effect.gen(function*() {\n const fileSystem = yield* FileSystem.FileSystem\n const path = yield* Path.Path\n const includedByGlobs = (relative: string, basename: string) =>\n Contract.includedByGlobs([input.pattern], relative, basename)\n const walked = yield* Walk.files(fileSystem, path, input.root, input.hidden, input.noIgnore, includedByGlobs)\n const included = yield* candidates(fileSystem, path, walked, input.root, [input.pattern])\n const matching = [...included].sort()\n const paths = matching.slice(0, input.limit)\n const unsatisfiable = matching.length > 0 ? undefined : yield* Contract.unsatisfiableNotice({\n fileSystem,\n path,\n root: input.root,\n globs: [input.pattern],\n hidden: input.hidden,\n noIgnore: input.noIgnore,\n ignored: walked.ignored\n })\n return {\n paths,\n total: matching.length,\n truncated: matching.length > input.limit,\n ...(matching.length > input.limit ? { notice: notice(\"entries\", paths.length, matching.length) } : {}),\n ...(unsatisfiable === undefined ? {} : { notice: unsatisfiable })\n }\n })\n\n/**\n * The files this peer's `grep` reads for `input`, in the order it reads\n * them: the walk under `input.root` with its `.gitignore` rules and\n * `input.globs` applied and symbolic links left out, or the root itself when\n * it names one file. A peer that has to bring a tree's bytes to this process\n * before it can search them (`RemoteSearch`) fetches exactly these.\n *\n * @category queries\n * @since 1.0.0-rc.8\n */\nexport const searched = (\n input: Pick<Search.GrepInput, \"root\" | \"hidden\" | \"noIgnore\" | \"globs\">\n): Effect.Effect<ReadonlyArray<string>, StdError.StdError, FileSystem.FileSystem | Path.Path> =>\n Effect.gen(function*() {\n const fileSystem = yield* FileSystem.FileSystem\n const path = yield* Path.Path\n const walked = yield* Walk.files(fileSystem, path, input.root, input.hidden, input.noIgnore)\n return yield* candidates(fileSystem, path, walked, input.root, input.globs)\n })\n\n/**\n * Captures filesystem and path services in the portable peer.\n *\n * @category constructors\n * @since 1.0.0\n */\nexport const make = (services: Context.Context<FileSystem.FileSystem | Path.Path>): Search.Search =>\n Search.make({\n grep: (input) => Effect.provide(grep(input), services),\n glob: (input) => Effect.provide(glob(input), services)\n })\n\n/**\n * Provides the in-process peer.\n *\n * @category layers\n * @since 1.0.0\n */\nexport const layer: Layer.Layer<Search.Search, never, FileSystem.FileSystem | Path.Path> = Layer.effect(\n Search.Search,\n Effect.map(Effect.context<FileSystem.FileSystem | Path.Path>(), make)\n)\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -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": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA2BA,WAAsB;AACtB,oBAAgC;AAChC,aAAwB;AACxB,iBAA4B;AAC5B,aAAwB;AACxB,WAAsB;AACtB,YAAuB;AACvB,iBAA4B;AAC5B,UAAqB;AACrB,qBAAgC;AAChC,aAAwB;AACxB,eAA0B;AAanB,MAAM,WAAW,MAAM,OAAO;AAGrC,MAAM,kBAAkB,KAAK,OAAO;AAEpC,MAAM,SAAS,CAAC,UAA0B,IAAI,MAAM,WAAW,KAAK,OAAO,CAAC;AAE5E,MAAM,SAAS,OAAO,MAAM,sBAAsB,IAAI,CAAC,SAAS,SAAS,OAAO,IAAI,CAAC,EAAE,EAAE,KAAK,MAAM,CAAC;AAGrG,MAAM,UAAU,CAAC,SAAiB,gCAAgC,IAAI;AAEtE,MAAM,eAAe,0EACV,QAAQ,IAAI,CAAC;AAOxB,MAAM,UAAU;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA,8BAA8B,MAAM;AAAA,EAIpC;AAAA,EACA,oDAAoD,MAAM;AAAA,EAE1D;AACF,EAAE,KAAK,IAAI;AAGX,MAAM,WAAW;AAAA,EACf;AAAA,EACA;AACF,EAAE,KAAK,IAAI;AAEX,MAAM,SAAS,CAAC,MAAc,WAC5B,IAAI,SAAS,SAAS;AAAA,EACpB,MAAM;AAAA,EACN,SAAS,oBAAoB,IAAI,qBAAqB,MAAM;AAAA,EAC5D,MAAM;AACR,CAAC;AAEH,MAAM,WAAW,CAAC,MAAc,UAC9B,IAAI,SAAS,SAAS;AAAA,EACpB,MAAM;AAAA,EACN,SAAS,mBAAmB,IAAI,sBAAsB,KAAK;AAAA,EAC3D,MAAM;AACR,CAAC;AASH,MAAM,YAAY,2CAA2C,QAAQ,QAAQ,CAAC;AAO9E,MAAM,MAAM,CACV,MACA,QACA,MACA,OACA,QAAQ,OAER,qBAAO,IAAI,aAAY;AACrB,QAAM,SAAS,OAAO,KAAK,KAAK,MAAM;AAAA,IACpC,MAAM,CAAC,MAAM,WAAW,MAAM,GAAG,IAAI;AAAA,IACrC,OAAO,GAAG,OAAO,OAAO,IAAI,YAAY,EAAE,OAAO,MAAM,CAAC,CAAC;AAAA,EAAK,KAAK;AAAA,EACrE,CAAC,EAAE,KAAK,qBAAO,SAAS,CAAC,UAAU,OAAO,MAAM,MAAM,OAAO,CAAC,CAAC;AAC/D,MAAI,OAAO,aAAa,GAAG;AACzB,WAAO,OAAO,qBAAO,KAAK,OAAO,MAAM,OAAO,OAAO,KAAK,KAAK,QAAQ,OAAO,QAAQ,EAAE,CAAC;AAAA,EAC3F;AACA,QAAM,UAAU,OAAO,OAAO,OAAO,OAAO,WAAW,OAAO,EAAE,CAAC;AACjE,MAAI,OAAO,UAAU,OAAO,EAAG,QAAO,OAAO,qBAAO,KAAK,OAAO,MAAM,0CAA0C,CAAC;AACjH,MAAI,QAAQ,QAAQ,UAAU,MAAO,QAAO,OAAO,qBAAO,KAAK,SAAS,MAAM,QAAQ,CAAC,CAAC;AACxF,SAAO,QAAQ;AACjB,CAAC;AAWI,MAAM,OAAO,CAClB,UACA,YACkB;AAClB,QAAM,OAAO,sBAAQ,IAAI,UAAU,KAAK,IAAI;AAC5C,QAAM,gBAAgB,SAAS,YAAY,YAAY;AACvD,QAAM,OAAO,CAAC,SACZ,sBAAQ,KAAK,WAAW,YAAY,WAAW,WAAW,MAAM,IAAI,CAAC,EAAE,KAAK,sBAAQ,IAAI,KAAK,MAAM,IAAI,CAAC;AAC1G,QAAM,SAAS,CAAC,MAAc,aAAkC;AAC9D,UAAM,WAAW,KAAK,QAAQ,IAAI;AAClC,UAAM,QAAQ,kBAAkB;AAChC,WAAO,qBAAO;AAAA,MACZ,IAAI,MAAM,SAAS,CAAC,UAAU,OAAO,KAAK,GAAG,aAAa,OAAO,MAAM,GAAG,GAAG,KAAK;AAAA,MAClF,CAAC,UAAU;AACT,cAAM,OAAO,WAAW,MAAM,OAAO,UAAU,IAAI;AACnD,eAAO,SAAS,SAAY,qBAAO,KAAK,OAAO,MAAM,iCAAiC,CAAC,IAAI,qBAAO,QAAQ,IAAI;AAAA,MAChH;AAAA,IACF;AAAA,EACF;AACA,SAAO,OAAO,KAAK;AAAA,IACjB,MAAM,CAAC,UACL,qBAAO,IAAI,aAAY;AACrB,YAAM,OAAO,OAAO,OAAO,MAAM,MAAM,MAAM,QAAQ;AACrD,YAAM,OAAO,OAAO,eAAe,SAAS,KAAK,EAAE,KAAK,qBAAO,QAAQ,KAAK,IAAI,CAAC,CAAC;AAClF,YAAM,QAAQ,CAAC,GAAG,IAAI,IAAI,KAAK,IAAI,CAAC,SAAS,KAAK,QAAQ,IAAI,CAAC,CAAC,CAAC;AACjE,YAAM,QAAQ,MAAM,WAAW,IAAI,IAAI,WAAW,IAAI,OAAO;AAAA,QAC3D,MAAM;AAAA,QACN;AAAA,QACA,CAAC,OAAO,YAAY,CAAC;AAAA,QACrB;AAAA,QACA,MAAM,IAAI,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,KAAK,EAAE;AAAA,MAC1C;AAEA,YAAM,UAAU,MAAM,WAAW,KAAK,MAAM,SAAS,IAAI,IAAI,MAAM,KAAK,IAAI;AAC5E,UAAI,YAAY,OAAW,QAAO,OAAO,qBAAO,KAAK,OAAO,MAAM,MAAM,+BAA+B,CAAC;AACxG,YAAM,WAAW,IAAI,IAAI,CAAC,GAAG,KAAK,UAAU,GAAG,WAAW,SAAS,OAAO,CAAC,CAAC;AAC5E,aAAO,OAAO,eAAe,KAAK,KAAK,EAAE,GAAG,MAAM,SAAS,CAAC,CAAC,EAAE,KAAK,KAAK;AAAA,IAC3E,CAAC,EAAE,KAAK,qBAAO,QAAQ,QAAQ,CAAC;AAAA,IAClC,MAAM,CAAC,UACL,qBAAO,QAAQ,OAAO,MAAM,MAAM,MAAM,QAAQ,GAAG,CAAC,SAAS,eAAe,KAAK,KAAK,IAAI,CAAC,EAAE,KAAK,KAAK,CAAC,EAAE;AAAA,MACxG,qBAAO,QAAQ,QAAQ;AAAA,IACzB;AAAA,EACJ,CAAC;AACH;",
6
+ "names": []
7
+ }
@@ -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
@@ -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);
@@ -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
  }
@@ -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;AAkUD;;;;;;;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"}
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"}
@@ -88,6 +88,8 @@ const reservedWords = /* @__PURE__ */ new Set([
88
88
  const writeCommands = /* @__PURE__ */ new Set(["chmod", "chown", "mkdir", "rm", "rmdir", "tee", "touch", "truncate", "unlink"]);
89
89
  const destinationCommands = /* @__PURE__ */ new Set(["cp", "install", "mv"]);
90
90
  const prefixWrappers = /* @__PURE__ */ new Set(["env", "nice", "time", "sudo", "nohup", "xargs", "command", "exec"]);
91
+ const shells = /* @__PURE__ */ new Set(["ash", "bash", "dash", "fish", "ksh", "mksh", "sh", "zsh"]);
92
+ const commandFlag = /^-[A-Za-z]*c[A-Za-z]*$/;
91
93
  const maxNesting = 32;
92
94
  const tooDeep = () => new StdError.StdError({
93
95
  code: "invalid_input",
@@ -222,7 +224,7 @@ const lex = (command, nesting) => {
222
224
  };
223
225
  const pathValue = (token) => token.replace(/,+$/, "");
224
226
  const isPathToken = (path, token) => token === "." || token === ".." || token.startsWith("./") || token.startsWith("../") || path.isAbsolute(token) || /^[A-Za-z]:[\\/]/.test(token) || token.includes("/");
225
- const segmentReferences = (path, tokens) => {
227
+ const segmentReferences = (path, tokens, nesting) => {
226
228
  const isTarget = (index) => index > 0 && isRedirection(tokens[index - 1]);
227
229
  const isWord = (index) => !tokens[index].operator && !isTarget(index);
228
230
  const name = (index) => path.basename(pathValue(tokens[index].text));
@@ -237,33 +239,47 @@ const segmentReferences = (path, tokens) => {
237
239
  if (mutatingIndex !== -1) commandIndex = mutatingIndex;
238
240
  }
239
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
+ }
240
251
  const candidates = [];
241
252
  for (let index = 0; index < tokens.length; index++) {
242
253
  const token = tokens[index];
243
- if (index === commandIndex || token.operator || !isTarget(index) && token.text.startsWith("-")) continue;
254
+ if (index === commandIndex || scripts.has(index) || token.operator) continue;
255
+ if (!isTarget(index) && token.text.startsWith("-")) continue;
244
256
  if (isTarget(index) && textRedirections.has(tokens[index - 1].text)) continue;
245
257
  const value = pathValue(token.text);
246
258
  if (value !== "" && isPathToken(path, value)) candidates.push({ index, value });
247
259
  }
248
260
  const destinationIndex = destinationCommands.has(command) ? candidates.filter(({ index }) => !isTarget(index)).at(-1)?.index : void 0;
249
- return candidates.map(({ index, value }) => {
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 }) => {
250
264
  const access = isTarget(index) ? writeRedirections.has(tokens[index - 1].text) ? "write" : "read" : writeCommands.has(command) || destinationIndex === index ? "write" : "read";
251
265
  return { access, value };
252
266
  });
267
+ return [...named, ...nested];
253
268
  };
254
269
  const references = (path, command, nesting) => {
270
+ if (nesting > maxNesting) throw tooDeep();
255
271
  const { substitutions, tokens } = lex(command, nesting);
256
272
  const found = [];
257
273
  let segment = [];
258
274
  for (const token of tokens) {
259
275
  if (token.operator && commandSeparators.has(token.text)) {
260
- found.push(...segmentReferences(path, segment));
276
+ found.push(...segmentReferences(path, segment, nesting));
261
277
  segment = [];
262
278
  } else {
263
279
  segment.push(token);
264
280
  }
265
281
  }
266
- found.push(...segmentReferences(path, segment));
282
+ found.push(...segmentReferences(path, segment, nesting));
267
283
  for (const body of substitutions) found.push(...references(path, body, nesting + 1));
268
284
  return found;
269
285
  };