@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
@@ -1 +1 @@
1
- {"version":3,"file":"EnvelopePrecheck.js","sourceRoot":"","sources":["../../../src/internal/EnvelopePrecheck.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The hermetic pre-check: a lexical scan of shell text for the paths it names,\n * and the refusal when one of them falls outside the declared envelope.\n *\n * The scan is fail-closed over explicit path tokens and nothing more. It\n * cannot prove confinement — a shell can name a path no lexer sees, through a\n * variable, a substitution, or a program that opens a file of its own — so a\n * kernel or host sandbox must eventually enforce and report the complete read\n * and write sets.\n *\n * It lives beside the flow rather than inside it because it is its own\n * concept, with its own vocabulary (Access, PathReference) and its own\n * security history: three independent bypasses, each sufficient on its own to\n * defeat it, are closed here.\n *\n * @since 1.0.0\n */\n\nimport type * as Path from \"@smthrs/kernel/Path\"\nimport * as StdError from \"../StdError.ts\"\nimport { withinEnvelope } from \"./Paths.ts\"\n\n/**\n * The filesystem envelope one invocation declared.\n *\n * Structural rather than the hermetic `Bash.Input` itself: the scan needs the\n * three fields and none of the rest, and depending on the flow module would\n * make the flow and its pre-check import each other.\n *\n * @category models\n * @since 1.0.0\n */\nexport interface Envelope {\n readonly cwd?: string | undefined\n readonly reads: ReadonlyArray<string>\n readonly writes: ReadonlyArray<string>\n}\n\n/**\n * How a command touches a path it names.\n *\n * @category models\n * @since 1.0.0\n */\nexport type Access = \"read\" | \"write\"\n\n/**\n * One path a command names, and how it touches it.\n *\n * @category models\n * @since 1.0.0\n */\nexport interface PathReference {\n readonly access: Access\n readonly value: string\n}\n\nconst commandSeparators = new Set([\"&&\", \"||\", \";\", \";;\", \";&\", \";;&\", \"|\", \"|&\", \"&\", \"\\n\", \"(\", \")\"])\nconst writeRedirections = new Set([\">\", \">>\", \">|\", \"&>\", \"&>>\", \">&\", \"<>\"])\nconst readRedirections = new Set([\"<\", \"<&\"])\n// A here-document delimiter and a here-string are text, not file names.\nconst textRedirections = new Set([\"<<\", \"<<-\", \"<<<\"])\nconst isRedirection = (token: Token): boolean =>\n token.operator &&\n (writeRedirections.has(token.text) || readRedirections.has(token.text) || textRedirections.has(token.text))\n// Longest first, so `&>>` is not read as `&` then `>>`.\nconst operators = [\n \";;&\",\n \"&>>\",\n \"<<<\",\n \"<<-\",\n \";;\",\n \";&\",\n \"&&\",\n \"||\",\n \"|&\",\n \"&>\",\n \">>\",\n \">|\",\n \">&\",\n \"<>\",\n \"<<\",\n \"<&\",\n \"\\n\",\n \";\",\n \"|\",\n \"&\",\n \"(\",\n \")\",\n \">\",\n \"<\"\n]\n// Reserved words and grouping braces open or close a compound command; the\n// command they wrap stands after them.\nconst reservedWords = new Set([\n \"!\",\n \"{\",\n \"}\",\n \"case\",\n \"do\",\n \"done\",\n \"elif\",\n \"else\",\n \"esac\",\n \"fi\",\n \"for\",\n \"function\",\n \"if\",\n \"in\",\n \"select\",\n \"then\",\n \"until\",\n \"while\"\n])\nconst writeCommands = new Set([\"chmod\", \"chown\", \"mkdir\", \"rm\", \"rmdir\", \"tee\", \"touch\", \"truncate\", \"unlink\"])\nconst destinationCommands = new Set([\"cp\", \"install\", \"mv\"])\nconst prefixWrappers = new Set([\"env\", \"nice\", \"time\", \"sudo\", \"nohup\", \"xargs\", \"command\", \"exec\"])\n\n/**\n * One lexical unit of shell text: an operator, or a word with its quotes\n * removed. A command substitution inside a word stands as `$()` in the word;\n * its body is scanned as a command of its own.\n */\ninterface Token {\n readonly operator: boolean\n readonly text: string\n}\n\ninterface Lexed {\n readonly tokens: ReadonlyArray<Token>\n readonly substitutions: ReadonlyArray<string>\n}\n\n// The end of a `$(` body that starts at `from`: the index of its closing\n// parenthesis, skipping quoted spans and nested parentheses. An unclosed body\n// runs to the end of the text, which is how a fail-closed scan must read it.\n// Substitutions and quotes nest without limit in a shell, but each level\n// costs this scan a pass over the rest of the text and a stack frame. Fifty\n// thousand `$(` used to run for half a minute and then overflow the stack, so\n// past this depth the text is refused rather than scanned.\nconst maxNesting = 32\n\nconst tooDeep = (): StdError.StdError =>\n new StdError.StdError({\n code: \"invalid_input\",\n message: `Command nests substitutions or quotes deeper than ${maxNesting} levels`\n })\n\nconst substitutionEnd = (text: string, from: number, nesting: number): number => {\n if (nesting > maxNesting) throw tooDeep()\n let depth = 1\n let index = from\n while (index < text.length) {\n const char = text[index]!\n if (char === \"\\\\\") {\n index += 2\n } else if (char === \"'\") {\n const close = text.indexOf(\"'\", index + 1)\n index = close === -1 ? text.length : close + 1\n } else if (char === \"\\\"\") {\n index = doubleQuoteEnd(text, index + 1, nesting + 1) + 1\n } else if (char === \"`\") {\n index = backtickEnd(text, index + 1) + 1\n } else {\n if (char === \"(\") depth++\n if (char === \")\" && --depth === 0) return index\n index++\n }\n }\n return text.length\n}\n\nconst backtickEnd = (text: string, from: number): number => {\n let index = from\n while (index < text.length && text[index] !== \"`\") index += text[index] === \"\\\\\" ? 2 : 1\n return Math.min(index, text.length)\n}\n\nconst doubleQuoteEnd = (text: string, from: number, nesting: number): number => {\n if (nesting > maxNesting) throw tooDeep()\n let index = from\n while (index < text.length && text[index] !== \"\\\"\") {\n const char = text[index]!\n if (char === \"\\\\\") index += 2\n else if (char === \"`\") index = backtickEnd(text, index + 1) + 1\n else if (char === \"$\" && text[index + 1] === \"(\") index = substitutionEnd(text, index + 2, nesting + 1) + 1\n else index++\n }\n return Math.min(index, text.length)\n}\n\n// A hand-rolled lexer rather than one regular expression: a word is any run\n// of bare characters, quoted spans, and substitutions, and a substitution can\n// hold quotes and operators of its own. Splitting on operator characters\n// alone let `echo \"$(rm /work/target)\"` hide a delete inside one quoted word,\n// read `>|` as a pipe that turned the target into the next command's name,\n// and read `(rm /work/target)` as a command named `(rm`.\n//\n// Quotes are removed from a word's text, because `\"/outs\"ide/x` names\n// `/outside/x`; keeping the quotes made it a relative path under the\n// working directory.\n// Sticky, so the match starts at `lastIndex` without copying the rest of the\n// text for every word.\nconst descriptorPattern = /\\d+(?=[<>])/y\n\nconst lex = (command: string, nesting: number): Lexed => {\n const tokens: Array<Token> = []\n const substitutions: Array<string> = []\n let index = 0\n const substitution = (from: number, to: number): string => {\n substitutions.push(command.slice(from, to))\n return \"$()\"\n }\n while (index < command.length) {\n const char = command[index]!\n // Every blank the word loop stops at must be skipped here, or the lexer\n // would push an empty word without advancing.\n if (char !== \"\\n\" && /\\s/.test(char)) {\n index++\n continue\n }\n if (char === \"\\\\\" && command[index + 1] === \"\\n\") {\n index += 2\n continue\n }\n if (char === \"#\") {\n while (index < command.length && command[index] !== \"\\n\") index++\n continue\n }\n // A file-descriptor number belongs to the redirection after it: in\n // `2>/dev/null rm /work/target` the command is `rm`, not `2`.\n descriptorPattern.lastIndex = index\n const descriptor = descriptorPattern.exec(command)\n if (descriptor !== null) {\n index += descriptor[0].length\n continue\n }\n const operator = operators.find((candidate) => command.startsWith(candidate, index))\n if (operator !== undefined) {\n tokens.push({ operator: true, text: operator })\n index += operator.length\n continue\n }\n let text = \"\"\n while (index < command.length) {\n const next = command[index]!\n if (/[\\s<>;|&()]/.test(next)) break\n if (next === \"\\\\\") {\n // A backslash escapes shell syntax. Before any other character it is\n // kept, so `C:\\work\\input` stays the path it names on Windows rather\n // than collapsing to a word the scan cannot see as a path.\n const escaped = command[index + 1] ?? \"\"\n if (escaped !== \"\\n\") text += /[\\s<>;|&()'\"`$\\\\#]/.test(escaped) ? escaped : `\\\\${escaped}`\n index += 2\n } else if (next === \"'\") {\n const close = command.indexOf(\"'\", index + 1)\n const end = close === -1 ? command.length : close\n text += command.slice(index + 1, end)\n index = end + 1\n } else if (next === \"\\\"\") {\n const end = doubleQuoteEnd(command, index + 1, nesting + 1)\n let inner = index + 1\n while (inner < end) {\n const quoted = command[inner]!\n if (quoted === \"\\\\\" && /[$`\"\\\\\\n]/.test(command[inner + 1] ?? \"\")) {\n if (command[inner + 1] !== \"\\n\") text += command[inner + 1]\n inner += 2\n } else if (quoted === \"`\") {\n const close = backtickEnd(command, inner + 1)\n text += substitution(inner + 1, close)\n inner = close + 1\n } else if (quoted === \"$\" && command[inner + 1] === \"(\") {\n const close = substitutionEnd(command, inner + 2, nesting + 2)\n text += substitution(inner + 2, close)\n inner = close + 1\n } else {\n text += quoted\n inner++\n }\n }\n index = end + 1\n } else if (next === \"`\") {\n const close = backtickEnd(command, index + 1)\n text += substitution(index + 1, close)\n index = close + 1\n } else if (next === \"$\" && command[index + 1] === \"(\") {\n const close = substitutionEnd(command, index + 2, nesting + 1)\n text += substitution(index + 2, close)\n index = close + 1\n } else {\n text += next\n index++\n }\n }\n tokens.push({ operator: false, text })\n }\n return { tokens, substitutions }\n}\n\n// A parenthesis is an operator to the lexer, so one left in a word was quoted\n// and is part of the name; only a trailing comma is punctuation.\nconst pathValue = (token: string): string => token.replace(/,+$/, \"\")\n\nconst isPathToken = (path: Path.Path, token: string): boolean =>\n token === \".\" ||\n token === \"..\" ||\n token.startsWith(\"./\") ||\n token.startsWith(\"../\") ||\n path.isAbsolute(token) ||\n /^[A-Za-z]:[\\\\/]/.test(token) ||\n token.includes(\"/\")\n\nconst segmentReferences = (path: Path.Path, tokens: ReadonlyArray<Token>): ReadonlyArray<PathReference> => {\n // The word after a redirection operator is its target, never the command.\n const isTarget = (index: number): boolean => index > 0 && isRedirection(tokens[index - 1]!)\n const isWord = (index: number): boolean => !tokens[index]!.operator && !isTarget(index)\n const name = (index: number): string => path.basename(pathValue(tokens[index]!.text))\n const hasWrapper = tokens.some((_, index) => isWord(index) && prefixWrappers.has(name(index)))\n let commandIndex = tokens.findIndex((token, index) =>\n isWord(index) &&\n !token.text.includes(\"=\") &&\n !prefixWrappers.has(name(index)) &&\n !reservedWords.has(token.text) &&\n !token.text.startsWith(\"-\") &&\n token.text !== \"\"\n )\n if (hasWrapper) {\n // Wrapper options can carry their own values. Prefer a later known\n // mutating command so an option value cannot lend it a read classification.\n const mutatingIndex = tokens.findIndex((_, index) =>\n isWord(index) && (writeCommands.has(name(index)) || destinationCommands.has(name(index)))\n )\n if (mutatingIndex !== -1) commandIndex = mutatingIndex\n }\n\n const command = commandIndex === -1 ? \"\" : name(commandIndex)\n const candidates: Array<{ readonly index: number; readonly value: string }> = []\n for (let index = 0; index < tokens.length; index++) {\n const token = tokens[index]!\n if (index === commandIndex || token.operator || (!isTarget(index) && token.text.startsWith(\"-\"))) continue\n if (isTarget(index) && textRedirections.has(tokens[index - 1]!.text)) continue\n const value = pathValue(token.text)\n if (value !== \"\" && isPathToken(path, value)) candidates.push({ index, value })\n }\n\n const destinationIndex = destinationCommands.has(command)\n ? candidates.filter(({ index }) => !isTarget(index)).at(-1)?.index\n : undefined\n\n return candidates.map(({ index, value }) => {\n const access: Access = isTarget(index)\n ? writeRedirections.has(tokens[index - 1]!.text) ? \"write\" : \"read\"\n : writeCommands.has(command) || destinationIndex === index\n ? \"write\"\n : \"read\"\n return { access, value }\n })\n}\n\nconst references = (path: Path.Path, command: string, nesting: number): ReadonlyArray<PathReference> => {\n const { substitutions, tokens } = lex(command, nesting)\n const found: Array<PathReference> = []\n let segment: Array<Token> = []\n for (const token of tokens) {\n if (token.operator && commandSeparators.has(token.text)) {\n found.push(...segmentReferences(path, segment))\n segment = []\n } else {\n segment.push(token)\n }\n }\n found.push(...segmentReferences(path, segment))\n // A substitution runs its body as a command; its paths count like any other.\n for (const body of substitutions) found.push(...references(path, body, nesting + 1))\n return found\n}\n\n/**\n * Every path token the shell text names, one segment at a time.\n *\n * @throws {StdError.StdError} `invalid_input` when substitutions or quotes nest\n * deeper than the scan reads.\n * @category pre-check\n * @since 1.0.0\n */\nexport const commandReferences = (path: Path.Path, command: string): ReadonlyArray<PathReference> =>\n references(path, command, 0)\n\nconst resolvePath = (\n path: Path.Path,\n cwd: string,\n value: string\n): string => path.isAbsolute(value) ? path.normalize(value) : path.resolve(cwd, value)\n\nconst isDeclared = (\n path: Path.Path,\n cwd: string,\n declared: ReadonlyArray<string>,\n source: string\n): boolean =>\n withinEnvelope(\n declared.map((entry) => resolvePath(path, cwd, entry)),\n resolvePath(path, cwd, source)\n )\n\n/**\n * The refusal shell text earns by naming a path outside its declared\n * envelope, or `undefined` when every path it names is declared.\n *\n * @category pre-check\n * @since 1.0.0\n */\nexport const outsideEnvelope = (\n input: Envelope,\n text: string,\n path: Path.Path\n): StdError.StdError | undefined => {\n const base = path.resolve(\".\")\n const cwd = path.resolve(input.cwd ?? \".\")\n // Passing the default explicitly is not a declaration the caller owes. A\n // working directory is where the command runs, not a file it reads, and\n // `cwd: \".\"` names exactly what omitting `cwd` names — so refusing it asked\n // the caller to list the workspace root among its reads to say nothing at\n // all. Agents hit this constantly: one SWE-bench run spent ten of its\n // forty-eight tool calls re-issuing the same command after\n // \"Working directory is outside declared reads: <the workspace root>\". A\n // cwd that points somewhere else is still declared or refused.\n if (input.cwd !== undefined && cwd !== base && !isDeclared(path, cwd, input.reads, cwd)) {\n return new StdError.StdError({\n code: \"outside_declared_reads\",\n message: `Working directory is outside declared reads: ${cwd}`,\n path: cwd\n })\n }\n\n // The Shell contract currently has no sandbox or access-reporting surface.\n // This lexical scan is intentionally only a fail-closed pre-check for\n // explicit path tokens. It cannot prove confinement; a kernel/host sandbox\n // must eventually enforce and report the complete read and write sets.\n let named: ReadonlyArray<PathReference>\n try {\n named = commandReferences(path, text)\n } catch {\n // The depth bound is the one failure the scan raises, and a text the scan\n // cannot read is refused whatever the reason, never run unchecked.\n return tooDeep()\n }\n for (const reference of named) {\n const resolved = resolvePath(path, cwd, reference.value)\n // /dev/* is the process plumbing every shell one-liner leans on:\n // `2>/dev/null`, `cat /dev/stdin`. Normalize first so a dot-dot segment\n // cannot disguise an ordinary filesystem path as process plumbing.\n if (resolved.startsWith(\"/dev/\")) continue\n const declared = reference.access === \"write\" ? input.writes : input.reads\n if (!isDeclared(path, cwd, declared, resolved)) {\n const code = reference.access === \"write\" ? \"outside_declared_writes\" : \"outside_declared_reads\"\n return new StdError.StdError({\n code,\n message: `Command path is outside declared ${reference.access}s: ${resolved}`,\n path: resolved\n })\n }\n }\n return undefined\n}\n"]}
1
+ {"version":3,"file":"EnvelopePrecheck.js","sourceRoot":"","sources":["../../../src/internal/EnvelopePrecheck.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The hermetic pre-check: a lexical scan of shell text for the paths it names,\n * and the refusal when one of them falls outside the declared envelope.\n *\n * The scan is fail-closed over explicit path tokens and nothing more. It\n * cannot prove confinement — a shell can name a path no lexer sees, through a\n * variable, a substitution, or a program that opens a file of its own — so a\n * kernel or host sandbox must eventually enforce and report the complete read\n * and write sets.\n *\n * It lives beside the flow rather than inside it because it is its own\n * concept, with its own vocabulary (Access, PathReference) and its own\n * security history: three independent bypasses, each sufficient on its own to\n * defeat it, are closed here.\n *\n * @since 1.0.0\n */\n\nimport type * as Path from \"@smthrs/kernel/Path\"\nimport * as StdError from \"../StdError.ts\"\nimport { withinEnvelope } from \"./Paths.ts\"\n\n/**\n * The filesystem envelope one invocation declared.\n *\n * Structural rather than the hermetic `Bash.Input` itself: the scan needs the\n * three fields and none of the rest, and depending on the flow module would\n * make the flow and its pre-check import each other.\n *\n * @category models\n * @since 1.0.0\n */\nexport interface Envelope {\n readonly cwd?: string | undefined\n readonly reads: ReadonlyArray<string>\n readonly writes: ReadonlyArray<string>\n}\n\n/**\n * How a command touches a path it names.\n *\n * @category models\n * @since 1.0.0\n */\nexport type Access = \"read\" | \"write\"\n\n/**\n * One path a command names, and how it touches it.\n *\n * @category models\n * @since 1.0.0\n */\nexport interface PathReference {\n readonly access: Access\n readonly value: string\n}\n\nconst commandSeparators = new Set([\"&&\", \"||\", \";\", \";;\", \";&\", \";;&\", \"|\", \"|&\", \"&\", \"\\n\", \"(\", \")\"])\nconst writeRedirections = new Set([\">\", \">>\", \">|\", \"&>\", \"&>>\", \">&\", \"<>\"])\nconst readRedirections = new Set([\"<\", \"<&\"])\n// A here-document delimiter and a here-string are text, not file names.\nconst textRedirections = new Set([\"<<\", \"<<-\", \"<<<\"])\nconst isRedirection = (token: Token): boolean =>\n token.operator &&\n (writeRedirections.has(token.text) || readRedirections.has(token.text) || textRedirections.has(token.text))\n// Longest first, so `&>>` is not read as `&` then `>>`.\nconst operators = [\n \";;&\",\n \"&>>\",\n \"<<<\",\n \"<<-\",\n \";;\",\n \";&\",\n \"&&\",\n \"||\",\n \"|&\",\n \"&>\",\n \">>\",\n \">|\",\n \">&\",\n \"<>\",\n \"<<\",\n \"<&\",\n \"\\n\",\n \";\",\n \"|\",\n \"&\",\n \"(\",\n \")\",\n \">\",\n \"<\"\n]\n// Reserved words and grouping braces open or close a compound command; the\n// command they wrap stands after them.\nconst reservedWords = new Set([\n \"!\",\n \"{\",\n \"}\",\n \"case\",\n \"do\",\n \"done\",\n \"elif\",\n \"else\",\n \"esac\",\n \"fi\",\n \"for\",\n \"function\",\n \"if\",\n \"in\",\n \"select\",\n \"then\",\n \"until\",\n \"while\"\n])\nconst writeCommands = new Set([\"chmod\", \"chown\", \"mkdir\", \"rm\", \"rmdir\", \"tee\", \"touch\", \"truncate\", \"unlink\"])\nconst destinationCommands = new Set([\"cp\", \"install\", \"mv\"])\nconst prefixWrappers = new Set([\"env\", \"nice\", \"time\", \"sudo\", \"nohup\", \"xargs\", \"command\", \"exec\"])\n// A shell runs the argument of its `-c` flag as shell text of its own.\nconst shells = new Set([\"ash\", \"bash\", \"dash\", \"fish\", \"ksh\", \"mksh\", \"sh\", \"zsh\"])\nconst commandFlag = /^-[A-Za-z]*c[A-Za-z]*$/\n\n/**\n * One lexical unit of shell text: an operator, or a word with its quotes\n * removed. A command substitution inside a word stands as `$()` in the word;\n * its body is scanned as a command of its own.\n */\ninterface Token {\n readonly operator: boolean\n readonly text: string\n}\n\ninterface Lexed {\n readonly tokens: ReadonlyArray<Token>\n readonly substitutions: ReadonlyArray<string>\n}\n\n// The end of a `$(` body that starts at `from`: the index of its closing\n// parenthesis, skipping quoted spans and nested parentheses. An unclosed body\n// runs to the end of the text, which is how a fail-closed scan must read it.\n// Substitutions and quotes nest without limit in a shell, but each level\n// costs this scan a pass over the rest of the text and a stack frame. Fifty\n// thousand `$(` used to run for half a minute and then overflow the stack, so\n// past this depth the text is refused rather than scanned.\nconst maxNesting = 32\n\nconst tooDeep = (): StdError.StdError =>\n new StdError.StdError({\n code: \"invalid_input\",\n message: `Command nests substitutions or quotes deeper than ${maxNesting} levels`\n })\n\nconst substitutionEnd = (text: string, from: number, nesting: number): number => {\n if (nesting > maxNesting) throw tooDeep()\n let depth = 1\n let index = from\n while (index < text.length) {\n const char = text[index]!\n if (char === \"\\\\\") {\n index += 2\n } else if (char === \"'\") {\n const close = text.indexOf(\"'\", index + 1)\n index = close === -1 ? text.length : close + 1\n } else if (char === \"\\\"\") {\n index = doubleQuoteEnd(text, index + 1, nesting + 1) + 1\n } else if (char === \"`\") {\n index = backtickEnd(text, index + 1) + 1\n } else {\n if (char === \"(\") depth++\n if (char === \")\" && --depth === 0) return index\n index++\n }\n }\n return text.length\n}\n\nconst backtickEnd = (text: string, from: number): number => {\n let index = from\n while (index < text.length && text[index] !== \"`\") index += text[index] === \"\\\\\" ? 2 : 1\n return Math.min(index, text.length)\n}\n\nconst doubleQuoteEnd = (text: string, from: number, nesting: number): number => {\n if (nesting > maxNesting) throw tooDeep()\n let index = from\n while (index < text.length && text[index] !== \"\\\"\") {\n const char = text[index]!\n if (char === \"\\\\\") index += 2\n else if (char === \"`\") index = backtickEnd(text, index + 1) + 1\n else if (char === \"$\" && text[index + 1] === \"(\") index = substitutionEnd(text, index + 2, nesting + 1) + 1\n else index++\n }\n return Math.min(index, text.length)\n}\n\n// A hand-rolled lexer rather than one regular expression: a word is any run\n// of bare characters, quoted spans, and substitutions, and a substitution can\n// hold quotes and operators of its own. Splitting on operator characters\n// alone let `echo \"$(rm /work/target)\"` hide a delete inside one quoted word,\n// read `>|` as a pipe that turned the target into the next command's name,\n// and read `(rm /work/target)` as a command named `(rm`.\n//\n// Quotes are removed from a word's text, because `\"/outs\"ide/x` names\n// `/outside/x`; keeping the quotes made it a relative path under the\n// working directory.\n// Sticky, so the match starts at `lastIndex` without copying the rest of the\n// text for every word.\nconst descriptorPattern = /\\d+(?=[<>])/y\n\nconst lex = (command: string, nesting: number): Lexed => {\n const tokens: Array<Token> = []\n const substitutions: Array<string> = []\n let index = 0\n const substitution = (from: number, to: number): string => {\n substitutions.push(command.slice(from, to))\n return \"$()\"\n }\n while (index < command.length) {\n const char = command[index]!\n // Every blank the word loop stops at must be skipped here, or the lexer\n // would push an empty word without advancing.\n if (char !== \"\\n\" && /\\s/.test(char)) {\n index++\n continue\n }\n if (char === \"\\\\\" && command[index + 1] === \"\\n\") {\n index += 2\n continue\n }\n if (char === \"#\") {\n while (index < command.length && command[index] !== \"\\n\") index++\n continue\n }\n // A file-descriptor number belongs to the redirection after it: in\n // `2>/dev/null rm /work/target` the command is `rm`, not `2`.\n descriptorPattern.lastIndex = index\n const descriptor = descriptorPattern.exec(command)\n if (descriptor !== null) {\n index += descriptor[0].length\n continue\n }\n const operator = operators.find((candidate) => command.startsWith(candidate, index))\n if (operator !== undefined) {\n tokens.push({ operator: true, text: operator })\n index += operator.length\n continue\n }\n let text = \"\"\n while (index < command.length) {\n const next = command[index]!\n if (/[\\s<>;|&()]/.test(next)) break\n if (next === \"\\\\\") {\n // A backslash escapes shell syntax. Before any other character it is\n // kept, so `C:\\work\\input` stays the path it names on Windows rather\n // than collapsing to a word the scan cannot see as a path.\n const escaped = command[index + 1] ?? \"\"\n if (escaped !== \"\\n\") text += /[\\s<>;|&()'\"`$\\\\#]/.test(escaped) ? escaped : `\\\\${escaped}`\n index += 2\n } else if (next === \"'\") {\n const close = command.indexOf(\"'\", index + 1)\n const end = close === -1 ? command.length : close\n text += command.slice(index + 1, end)\n index = end + 1\n } else if (next === \"\\\"\") {\n const end = doubleQuoteEnd(command, index + 1, nesting + 1)\n let inner = index + 1\n while (inner < end) {\n const quoted = command[inner]!\n if (quoted === \"\\\\\" && /[$`\"\\\\\\n]/.test(command[inner + 1] ?? \"\")) {\n if (command[inner + 1] !== \"\\n\") text += command[inner + 1]\n inner += 2\n } else if (quoted === \"`\") {\n const close = backtickEnd(command, inner + 1)\n text += substitution(inner + 1, close)\n inner = close + 1\n } else if (quoted === \"$\" && command[inner + 1] === \"(\") {\n const close = substitutionEnd(command, inner + 2, nesting + 2)\n text += substitution(inner + 2, close)\n inner = close + 1\n } else {\n text += quoted\n inner++\n }\n }\n index = end + 1\n } else if (next === \"`\") {\n const close = backtickEnd(command, index + 1)\n text += substitution(index + 1, close)\n index = close + 1\n } else if (next === \"$\" && command[index + 1] === \"(\") {\n const close = substitutionEnd(command, index + 2, nesting + 1)\n text += substitution(index + 2, close)\n index = close + 1\n } else {\n text += next\n index++\n }\n }\n tokens.push({ operator: false, text })\n }\n return { tokens, substitutions }\n}\n\n// A parenthesis is an operator to the lexer, so one left in a word was quoted\n// and is part of the name; only a trailing comma is punctuation.\nconst pathValue = (token: string): string => token.replace(/,+$/, \"\")\n\nconst isPathToken = (path: Path.Path, token: string): boolean =>\n token === \".\" ||\n token === \"..\" ||\n token.startsWith(\"./\") ||\n token.startsWith(\"../\") ||\n path.isAbsolute(token) ||\n /^[A-Za-z]:[\\\\/]/.test(token) ||\n token.includes(\"/\")\n\nconst segmentReferences = (\n path: Path.Path,\n tokens: ReadonlyArray<Token>,\n nesting: number\n): ReadonlyArray<PathReference> => {\n // The word after a redirection operator is its target, never the command.\n const isTarget = (index: number): boolean => index > 0 && isRedirection(tokens[index - 1]!)\n const isWord = (index: number): boolean => !tokens[index]!.operator && !isTarget(index)\n const name = (index: number): string => path.basename(pathValue(tokens[index]!.text))\n const hasWrapper = tokens.some((_, index) => isWord(index) && prefixWrappers.has(name(index)))\n let commandIndex = tokens.findIndex((token, index) =>\n isWord(index) &&\n !token.text.includes(\"=\") &&\n !prefixWrappers.has(name(index)) &&\n !reservedWords.has(token.text) &&\n !token.text.startsWith(\"-\") &&\n token.text !== \"\"\n )\n if (hasWrapper) {\n // Wrapper options can carry their own values. Prefer a later known\n // mutating command so an option value cannot lend it a read classification.\n const mutatingIndex = tokens.findIndex((_, index) =>\n isWord(index) && (writeCommands.has(name(index)) || destinationCommands.has(name(index)))\n )\n if (mutatingIndex !== -1) commandIndex = mutatingIndex\n }\n\n const command = commandIndex === -1 ? \"\" : name(commandIndex)\n // Shell text handed to `eval` or to a shell's `-c` flag is a command, not a\n // path: reading `sh -c 'rm /work/target'` as one quoted word let a delete\n // pass as a read of a file named `rm /work/target`.\n const scripts = new Set<number>()\n if (command === \"eval\") {\n for (let index = commandIndex + 1; index < tokens.length; index++) if (isWord(index)) scripts.add(index)\n } else if (shells.has(command)) {\n const flag = tokens.findIndex((token, index) =>\n index > commandIndex && isWord(index) && commandFlag.test(token.text)\n )\n if (flag !== -1 && flag + 1 < tokens.length && isWord(flag + 1)) scripts.add(flag + 1)\n }\n const candidates: Array<{ readonly index: number; readonly value: string }> = []\n for (let index = 0; index < tokens.length; index++) {\n const token = tokens[index]!\n if (index === commandIndex || scripts.has(index) || token.operator) continue\n if (!isTarget(index) && token.text.startsWith(\"-\")) continue\n if (isTarget(index) && textRedirections.has(tokens[index - 1]!.text)) continue\n const value = pathValue(token.text)\n if (value !== \"\" && isPathToken(path, value)) candidates.push({ index, value })\n }\n\n const destinationIndex = destinationCommands.has(command)\n ? candidates.filter(({ index }) => !isTarget(index)).at(-1)?.index\n : undefined\n\n const script = [...scripts].map((index) => tokens[index]!.text).join(\" \")\n const nested = script === \"\" ? [] : references(path, script, nesting + 1)\n const named = candidates.map(({ index, value }): PathReference => {\n const access: Access = isTarget(index)\n ? writeRedirections.has(tokens[index - 1]!.text) ? \"write\" : \"read\"\n : writeCommands.has(command) || destinationIndex === index\n ? \"write\"\n : \"read\"\n return { access, value }\n })\n return [...named, ...nested]\n}\n\nconst references = (path: Path.Path, command: string, nesting: number): ReadonlyArray<PathReference> => {\n if (nesting > maxNesting) throw tooDeep()\n const { substitutions, tokens } = lex(command, nesting)\n const found: Array<PathReference> = []\n let segment: Array<Token> = []\n for (const token of tokens) {\n if (token.operator && commandSeparators.has(token.text)) {\n found.push(...segmentReferences(path, segment, nesting))\n segment = []\n } else {\n segment.push(token)\n }\n }\n found.push(...segmentReferences(path, segment, nesting))\n // A substitution runs its body as a command; its paths count like any other.\n for (const body of substitutions) found.push(...references(path, body, nesting + 1))\n return found\n}\n\n/**\n * Every path token the shell text names, one segment at a time.\n *\n * @throws {StdError.StdError} `invalid_input` when substitutions or quotes nest\n * deeper than the scan reads.\n * @category pre-check\n * @since 1.0.0\n */\nexport const commandReferences = (path: Path.Path, command: string): ReadonlyArray<PathReference> =>\n references(path, command, 0)\n\nconst resolvePath = (\n path: Path.Path,\n cwd: string,\n value: string\n): string => path.isAbsolute(value) ? path.normalize(value) : path.resolve(cwd, value)\n\nconst isDeclared = (\n path: Path.Path,\n cwd: string,\n declared: ReadonlyArray<string>,\n source: string\n): boolean =>\n withinEnvelope(\n declared.map((entry) => resolvePath(path, cwd, entry)),\n resolvePath(path, cwd, source)\n )\n\n/**\n * The refusal shell text earns by naming a path outside its declared\n * envelope, or `undefined` when every path it names is declared.\n *\n * @category pre-check\n * @since 1.0.0\n */\nexport const outsideEnvelope = (\n input: Envelope,\n text: string,\n path: Path.Path\n): StdError.StdError | undefined => {\n const base = path.resolve(\".\")\n const cwd = path.resolve(input.cwd ?? \".\")\n // Passing the default explicitly is not a declaration the caller owes. A\n // working directory is where the command runs, not a file it reads, and\n // `cwd: \".\"` names exactly what omitting `cwd` names — so refusing it asked\n // the caller to list the workspace root among its reads to say nothing at\n // all. Agents hit this constantly: one SWE-bench run spent ten of its\n // forty-eight tool calls re-issuing the same command after\n // \"Working directory is outside declared reads: <the workspace root>\". A\n // cwd that points somewhere else is still declared or refused.\n if (input.cwd !== undefined && cwd !== base && !isDeclared(path, cwd, input.reads, cwd)) {\n return new StdError.StdError({\n code: \"outside_declared_reads\",\n message: `Working directory is outside declared reads: ${cwd}`,\n path: cwd\n })\n }\n\n // The Shell contract currently has no sandbox or access-reporting surface.\n // This lexical scan is intentionally only a fail-closed pre-check for\n // explicit path tokens. It cannot prove confinement; a kernel/host sandbox\n // must eventually enforce and report the complete read and write sets.\n let named: ReadonlyArray<PathReference>\n try {\n named = commandReferences(path, text)\n } catch {\n // The depth bound is the one failure the scan raises, and a text the scan\n // cannot read is refused whatever the reason, never run unchecked.\n return tooDeep()\n }\n for (const reference of named) {\n const resolved = resolvePath(path, cwd, reference.value)\n // /dev/* is the process plumbing every shell one-liner leans on:\n // `2>/dev/null`, `cat /dev/stdin`. Normalize first so a dot-dot segment\n // cannot disguise an ordinary filesystem path as process plumbing.\n if (resolved.startsWith(\"/dev/\")) continue\n const declared = reference.access === \"write\" ? input.writes : input.reads\n if (!isDeclared(path, cwd, declared, resolved)) {\n const code = reference.access === \"write\" ? \"outside_declared_writes\" : \"outside_declared_reads\"\n return new StdError.StdError({\n code,\n message: `Command path is outside declared ${reference.access}s: ${resolved}`,\n path: resolved\n })\n }\n }\n return undefined\n}\n"]}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * A tree another machine listed, served as a read-only `FileSystem`.
3
+ *
4
+ * `RemoteSearch` lists a root on the machine with `find` and brings back the
5
+ * bytes the search reads with `tar`; this module holds what came back and
6
+ * answers what the walk, the globs, and the matcher ask of a filesystem, so
7
+ * `PortableSearch` can search the copy as it searches a host.
8
+ *
9
+ * @since 1.0.0-rc.8
10
+ */
11
+ import type * as Path from "@smthrs/kernel/Path";
12
+ import * as FileSystem from "effect/FileSystem";
13
+ /**
14
+ * What one listed path is: `d` a directory, `u` a directory the machine's
15
+ * user may not list, `f` a file, `o` anything else, and a symbolic link to a
16
+ * directory (`D`), to a file (`F`), or to neither (`L`).
17
+ *
18
+ * @private
19
+ * @since 1.0.0-rc.8
20
+ */
21
+ export type Kind = "d" | "u" | "f" | "o" | "D" | "F" | "L";
22
+ /**
23
+ * One machine's listing of a root, and the bytes it brought back, by
24
+ * absolute path.
25
+ *
26
+ * @private
27
+ * @since 1.0.0-rc.8
28
+ */
29
+ export interface Tree {
30
+ readonly kinds: ReadonlyMap<string, Kind>;
31
+ readonly children: ReadonlyMap<string, ReadonlyArray<string>>;
32
+ readonly contents: ReadonlyMap<string, Uint8Array>;
33
+ }
34
+ /**
35
+ * The member names `tar` wrote for absolute paths, which it strips of their
36
+ * leading `/`, as those paths again.
37
+ *
38
+ * @private
39
+ * @since 1.0.0-rc.8
40
+ */
41
+ export declare const absolute: (members: ReadonlyMap<string, Uint8Array>) => ReadonlyMap<string, Uint8Array>;
42
+ /**
43
+ * Reads NUL-terminated `<kind><path>` records up to an `E` record, then the
44
+ * tar stream of `.gitignore` files after it. `root` is the listed root, which
45
+ * is nobody's child. `undefined` when the bytes are not that shape.
46
+ *
47
+ * @private
48
+ * @since 1.0.0-rc.8
49
+ */
50
+ export declare const parse: (bytes: Uint8Array, root: string, path: Path.Path) => Tree | undefined;
51
+ /**
52
+ * The tree as a read-only `FileSystem`, resolving paths with `path`. A file
53
+ * whose bytes did not come back is one the machine's user could not read,
54
+ * which the in-process peer skips exactly as it skips such a file on a host.
55
+ *
56
+ * @private
57
+ * @since 1.0.0-rc.8
58
+ */
59
+ export declare const fileSystem: (tree: Tree, path: Path.Path) => FileSystem.FileSystem;
60
+ //# sourceMappingURL=RemoteTree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RemoteTree.d.ts","sourceRoot":"","sources":["../../../src/internal/RemoteTree.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,KAAK,IAAI,MAAM,qBAAqB,CAAA;AAGhD,OAAO,KAAK,UAAU,MAAM,mBAAmB,CAAA;AAI/C;;;;;;;GAOG;AACH,MAAM,MAAM,IAAI,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAA;AAE1D;;;;;;GAMG;AACH,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;IACzC,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC,CAAA;IAC7D,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;CACnD;AAID;;;;;;GAMG;AACH,eAAO,MAAM,QAAQ,GAAI,SAAS,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,KAAG,WAAW,CAAC,MAAM,EAAE,UAAU,CAC/B,CAAA;AAEnE;;;;;;;GAOG;AACH,eAAO,MAAM,KAAK,GAAI,OAAO,UAAU,EAAE,MAAM,MAAM,EAAE,MAAM,IAAI,CAAC,IAAI,KAAG,IAAI,GAAG,SAuB/E,CAAA;AAiCD;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,GAAI,MAAM,IAAI,EAAE,MAAM,IAAI,CAAC,IAAI,KAAG,UAAU,CAAC,UAiCnE,CAAA"}
@@ -0,0 +1,132 @@
1
+ /**
2
+ * A tree another machine listed, served as a read-only `FileSystem`.
3
+ *
4
+ * `RemoteSearch` lists a root on the machine with `find` and brings back the
5
+ * bytes the search reads with `tar`; this module holds what came back and
6
+ * answers what the walk, the globs, and the matcher ask of a filesystem, so
7
+ * `PortableSearch` can search the copy as it searches a host.
8
+ *
9
+ * @since 1.0.0-rc.8
10
+ */
11
+ import { Effect, Option, Stream } from "effect";
12
+ import * as ByteSize from "effect/ByteSize";
13
+ import * as FileSystem from "effect/FileSystem";
14
+ import * as PlatformError from "effect/PlatformError";
15
+ import * as Tar from "./Tar.js";
16
+ const kinds = new Set(["d", "u", "f", "o", "D", "F", "L"]);
17
+ /**
18
+ * The member names `tar` wrote for absolute paths, which it strips of their
19
+ * leading `/`, as those paths again.
20
+ *
21
+ * @private
22
+ * @since 1.0.0-rc.8
23
+ */
24
+ export const absolute = (members) => new Map([...members].map(([name, bytes]) => [`/${name}`, bytes]));
25
+ /**
26
+ * Reads NUL-terminated `<kind><path>` records up to an `E` record, then the
27
+ * tar stream of `.gitignore` files after it. `root` is the listed root, which
28
+ * is nobody's child. `undefined` when the bytes are not that shape.
29
+ *
30
+ * @private
31
+ * @since 1.0.0-rc.8
32
+ */
33
+ export const parse = (bytes, root, path) => {
34
+ const decoder = new TextDecoder();
35
+ const listed = new Map();
36
+ const children = new Map();
37
+ let offset = 0;
38
+ for (;;) {
39
+ const end = bytes.indexOf(0, offset);
40
+ if (end === -1)
41
+ return undefined;
42
+ const record = decoder.decode(bytes.subarray(offset, end));
43
+ offset = end + 1;
44
+ if (record === "E")
45
+ break;
46
+ const kind = record.slice(0, 1);
47
+ if (!kinds.has(kind))
48
+ return undefined;
49
+ const entry = record.slice(1);
50
+ listed.set(entry, kind);
51
+ if (entry === root)
52
+ continue;
53
+ const parent = path.dirname(entry);
54
+ const siblings = children.get(parent);
55
+ if (siblings === undefined)
56
+ children.set(parent, [path.basename(entry)]);
57
+ else
58
+ siblings.push(path.basename(entry));
59
+ }
60
+ const ignores = Tar.files(bytes.subarray(offset));
61
+ return ignores === undefined ? undefined : { kinds: listed, children, contents: absolute(ignores) };
62
+ };
63
+ const refusal = (tag, method, path) => PlatformError.systemError({ _tag: tag, module: "FileSystem", method, pathOrDescriptor: path });
64
+ /** What `stat` says each kind is: links resolved, a dangling one absent. */
65
+ const types = {
66
+ d: "Directory",
67
+ u: "Directory",
68
+ D: "Directory",
69
+ f: "File",
70
+ F: "File",
71
+ o: "Unknown",
72
+ L: undefined
73
+ };
74
+ const info = (type, size) => ({
75
+ type,
76
+ size: ByteSize.bytes(BigInt(size)),
77
+ mtime: Option.none(),
78
+ atime: Option.none(),
79
+ birthtime: Option.none(),
80
+ dev: 0,
81
+ ino: Option.none(),
82
+ mode: 0,
83
+ nlink: Option.none(),
84
+ uid: Option.none(),
85
+ gid: Option.none(),
86
+ rdev: Option.none(),
87
+ blksize: Option.none(),
88
+ blocks: Option.none()
89
+ });
90
+ /**
91
+ * The tree as a read-only `FileSystem`, resolving paths with `path`. A file
92
+ * whose bytes did not come back is one the machine's user could not read,
93
+ * which the in-process peer skips exactly as it skips such a file on a host.
94
+ *
95
+ * @private
96
+ * @since 1.0.0-rc.8
97
+ */
98
+ export const fileSystem = (tree, path) => {
99
+ const kindOf = (value) => tree.kinds.get(path.resolve(value));
100
+ const readFile = (value) => {
101
+ const bytes = tree.contents.get(path.resolve(value));
102
+ if (bytes !== undefined)
103
+ return Effect.succeed(bytes);
104
+ const type = types[kindOf(value) ?? "L"];
105
+ return Effect.fail(refusal(type === "File" ? "PermissionDenied" : type === undefined ? "NotFound" : "BadResource", "readFile", value));
106
+ };
107
+ return FileSystem.makeNoop({
108
+ stat: (value) => {
109
+ const type = types[kindOf(value) ?? "L"];
110
+ return type === undefined
111
+ ? Effect.fail(refusal("NotFound", "stat", value))
112
+ : Effect.succeed(info(type, tree.contents.get(path.resolve(value))?.length ?? 0));
113
+ },
114
+ readDirectory: (value) => {
115
+ const kind = kindOf(value);
116
+ if (kind === "d")
117
+ return Effect.succeed([...tree.children.get(path.resolve(value)) ?? []]);
118
+ const tag = kind === "u" ? "PermissionDenied" : kind === undefined || kind === "L" ? "NotFound" : "BadResource";
119
+ return Effect.fail(refusal(tag, "readDirectory", value));
120
+ },
121
+ readLink: (value) => {
122
+ const kind = kindOf(value);
123
+ return kind === "D" || kind === "F" || kind === "L"
124
+ ? Effect.succeed("")
125
+ : Effect.fail(refusal(kind === undefined ? "NotFound" : "BadResource", "readLink", value));
126
+ },
127
+ readFile,
128
+ readFileString: (value) => Effect.map(readFile(value), (bytes) => new TextDecoder().decode(bytes)),
129
+ stream: (value) => Stream.fromEffect(readFile(value))
130
+ });
131
+ };
132
+ //# sourceMappingURL=RemoteTree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RemoteTree.js","sourceRoot":"","sources":["../../../src/internal/RemoteTree.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC/C,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAA;AAC3C,OAAO,KAAK,UAAU,MAAM,mBAAmB,CAAA;AAC/C,OAAO,KAAK,aAAa,MAAM,sBAAsB,CAAA;AACrD,OAAO,KAAK,GAAG,MAAM,UAAU,CAAA;AAyB/B,MAAM,KAAK,GAAwB,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAA;AAE/E;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,OAAwC,EAAmC,EAAE,CACpG,IAAI,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,IAAI,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,CAAA;AAEnE;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,KAAiB,EAAE,IAAY,EAAE,IAAe,EAAoB,EAAE;IAC1F,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAA;IACjC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAgB,CAAA;IACtC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAyB,CAAA;IACjD,IAAI,MAAM,GAAG,CAAC,CAAA;IACd,SAAS,CAAC;QACR,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,EAAE,MAAM,CAAC,CAAA;QACpC,IAAI,GAAG,KAAK,CAAC,CAAC;YAAE,OAAO,SAAS,CAAA;QAChC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAA;QAC1D,MAAM,GAAG,GAAG,GAAG,CAAC,CAAA;QAChB,IAAI,MAAM,KAAK,GAAG;YAAE,MAAK;QACzB,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;QAC/B,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO,SAAS,CAAA;QACtC,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QAC7B,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,IAAY,CAAC,CAAA;QAC/B,IAAI,KAAK,KAAK,IAAI;YAAE,SAAQ;QAC5B,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;QAClC,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;QACrC,IAAI,QAAQ,KAAK,SAAS;YAAE,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;;YACnE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAA;IAC1C,CAAC;IACD,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAA;IACjD,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAA;AACrG,CAAC,CAAA;AAED,MAAM,OAAO,GAAG,CAAC,GAAiC,EAAE,MAAc,EAAE,IAAY,EAA+B,EAAE,CAC/G,aAAa,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,CAAA;AAEhG,4EAA4E;AAC5E,MAAM,KAAK,GAA6D;IACtE,CAAC,EAAE,WAAW;IACd,CAAC,EAAE,WAAW;IACd,CAAC,EAAE,WAAW;IACd,CAAC,EAAE,MAAM;IACT,CAAC,EAAE,MAAM;IACT,CAAC,EAAE,SAAS;IACZ,CAAC,EAAE,SAAS;CACb,CAAA;AAED,MAAM,IAAI,GAAG,CAAC,IAA0B,EAAE,IAAY,EAAwB,EAAE,CAAC,CAAC;IAChF,IAAI;IACJ,IAAI,EAAE,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAClC,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE;IACpB,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE;IACpB,SAAS,EAAE,MAAM,CAAC,IAAI,EAAE;IACxB,GAAG,EAAE,CAAC;IACN,GAAG,EAAE,MAAM,CAAC,IAAI,EAAE;IAClB,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE;IACpB,GAAG,EAAE,MAAM,CAAC,IAAI,EAAE;IAClB,GAAG,EAAE,MAAM,CAAC,IAAI,EAAE;IAClB,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE;IACnB,OAAO,EAAE,MAAM,CAAC,IAAI,EAAE;IACtB,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE;CACtB,CAAC,CAAA;AAEF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,IAAU,EAAE,IAAe,EAAyB,EAAE;IAC/E,MAAM,MAAM,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAA;IACrE,MAAM,QAAQ,GAAG,CAAC,KAAa,EAAE,EAAE;QACjC,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAA;QACpD,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;QACrD,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAA;QACxC,OAAO,MAAM,CAAC,IAAI,CAChB,OAAO,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,aAAa,EAAE,UAAU,EAAE,KAAK,CAAC,CACnH,CAAA;IACH,CAAC,CAAA;IACD,OAAO,UAAU,CAAC,QAAQ,CAAC;QACzB,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE;YACd,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAA;YACxC,OAAO,IAAI,KAAK,SAAS;gBACvB,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;gBACjD,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,IAAI,CAAC,CAAC,CAAC,CAAA;QACrF,CAAC;QACD,aAAa,EAAE,CAAC,KAAK,EAAE,EAAE;YACvB,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;YAC1B,IAAI,IAAI,KAAK,GAAG;gBAAE,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;YAC1F,MAAM,GAAG,GAAG,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,aAAa,CAAA;YAC/G,OAAO,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,eAAe,EAAE,KAAK,CAAC,CAAC,CAAA;QAC1D,CAAC;QACD,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE;YAClB,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;YAC1B,OAAO,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG;gBACjD,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;gBACpB,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,aAAa,EAAE,UAAU,EAAE,KAAK,CAAC,CAAC,CAAA;QAC9F,CAAC;QACD,QAAQ;QACR,cAAc,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAClG,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;KACtD,CAAC,CAAA;AACJ,CAAC,CAAA","sourcesContent":["/**\n * A tree another machine listed, served as a read-only `FileSystem`.\n *\n * `RemoteSearch` lists a root on the machine with `find` and brings back the\n * bytes the search reads with `tar`; this module holds what came back and\n * answers what the walk, the globs, and the matcher ask of a filesystem, so\n * `PortableSearch` can search the copy as it searches a host.\n *\n * @since 1.0.0-rc.8\n */\n\nimport type * as Path from \"@smthrs/kernel/Path\"\nimport { Effect, Option, Stream } from \"effect\"\nimport * as ByteSize from \"effect/ByteSize\"\nimport * as FileSystem from \"effect/FileSystem\"\nimport * as PlatformError from \"effect/PlatformError\"\nimport * as Tar from \"./Tar.ts\"\n\n/**\n * What one listed path is: `d` a directory, `u` a directory the machine's\n * user may not list, `f` a file, `o` anything else, and a symbolic link to a\n * directory (`D`), to a file (`F`), or to neither (`L`).\n *\n * @private\n * @since 1.0.0-rc.8\n */\nexport type Kind = \"d\" | \"u\" | \"f\" | \"o\" | \"D\" | \"F\" | \"L\"\n\n/**\n * One machine's listing of a root, and the bytes it brought back, by\n * absolute path.\n *\n * @private\n * @since 1.0.0-rc.8\n */\nexport interface Tree {\n readonly kinds: ReadonlyMap<string, Kind>\n readonly children: ReadonlyMap<string, ReadonlyArray<string>>\n readonly contents: ReadonlyMap<string, Uint8Array>\n}\n\nconst kinds: ReadonlySet<string> = new Set([\"d\", \"u\", \"f\", \"o\", \"D\", \"F\", \"L\"])\n\n/**\n * The member names `tar` wrote for absolute paths, which it strips of their\n * leading `/`, as those paths again.\n *\n * @private\n * @since 1.0.0-rc.8\n */\nexport const absolute = (members: ReadonlyMap<string, Uint8Array>): ReadonlyMap<string, Uint8Array> =>\n new Map([...members].map(([name, bytes]) => [`/${name}`, bytes]))\n\n/**\n * Reads NUL-terminated `<kind><path>` records up to an `E` record, then the\n * tar stream of `.gitignore` files after it. `root` is the listed root, which\n * is nobody's child. `undefined` when the bytes are not that shape.\n *\n * @private\n * @since 1.0.0-rc.8\n */\nexport const parse = (bytes: Uint8Array, root: string, path: Path.Path): Tree | undefined => {\n const decoder = new TextDecoder()\n const listed = new Map<string, Kind>()\n const children = new Map<string, Array<string>>()\n let offset = 0\n for (;;) {\n const end = bytes.indexOf(0, offset)\n if (end === -1) return undefined\n const record = decoder.decode(bytes.subarray(offset, end))\n offset = end + 1\n if (record === \"E\") break\n const kind = record.slice(0, 1)\n if (!kinds.has(kind)) return undefined\n const entry = record.slice(1)\n listed.set(entry, kind as Kind)\n if (entry === root) continue\n const parent = path.dirname(entry)\n const siblings = children.get(parent)\n if (siblings === undefined) children.set(parent, [path.basename(entry)])\n else siblings.push(path.basename(entry))\n }\n const ignores = Tar.files(bytes.subarray(offset))\n return ignores === undefined ? undefined : { kinds: listed, children, contents: absolute(ignores) }\n}\n\nconst refusal = (tag: PlatformError.SystemErrorTag, method: string, path: string): PlatformError.PlatformError =>\n PlatformError.systemError({ _tag: tag, module: \"FileSystem\", method, pathOrDescriptor: path })\n\n/** What `stat` says each kind is: links resolved, a dangling one absent. */\nconst types: Readonly<Record<Kind, FileSystem.File.Type | undefined>> = {\n d: \"Directory\",\n u: \"Directory\",\n D: \"Directory\",\n f: \"File\",\n F: \"File\",\n o: \"Unknown\",\n L: undefined\n}\n\nconst info = (type: FileSystem.File.Type, size: number): FileSystem.File.Info => ({\n type,\n size: ByteSize.bytes(BigInt(size)),\n mtime: Option.none(),\n atime: Option.none(),\n birthtime: Option.none(),\n dev: 0,\n ino: Option.none(),\n mode: 0,\n nlink: Option.none(),\n uid: Option.none(),\n gid: Option.none(),\n rdev: Option.none(),\n blksize: Option.none(),\n blocks: Option.none()\n})\n\n/**\n * The tree as a read-only `FileSystem`, resolving paths with `path`. A file\n * whose bytes did not come back is one the machine's user could not read,\n * which the in-process peer skips exactly as it skips such a file on a host.\n *\n * @private\n * @since 1.0.0-rc.8\n */\nexport const fileSystem = (tree: Tree, path: Path.Path): FileSystem.FileSystem => {\n const kindOf = (value: string) => tree.kinds.get(path.resolve(value))\n const readFile = (value: string) => {\n const bytes = tree.contents.get(path.resolve(value))\n if (bytes !== undefined) return Effect.succeed(bytes)\n const type = types[kindOf(value) ?? \"L\"]\n return Effect.fail(\n refusal(type === \"File\" ? \"PermissionDenied\" : type === undefined ? \"NotFound\" : \"BadResource\", \"readFile\", value)\n )\n }\n return FileSystem.makeNoop({\n stat: (value) => {\n const type = types[kindOf(value) ?? \"L\"]\n return type === undefined\n ? Effect.fail(refusal(\"NotFound\", \"stat\", value))\n : Effect.succeed(info(type, tree.contents.get(path.resolve(value))?.length ?? 0))\n },\n readDirectory: (value) => {\n const kind = kindOf(value)\n if (kind === \"d\") return Effect.succeed([...tree.children.get(path.resolve(value)) ?? []])\n const tag = kind === \"u\" ? \"PermissionDenied\" : kind === undefined || kind === \"L\" ? \"NotFound\" : \"BadResource\"\n return Effect.fail(refusal(tag, \"readDirectory\", value))\n },\n readLink: (value) => {\n const kind = kindOf(value)\n return kind === \"D\" || kind === \"F\" || kind === \"L\"\n ? Effect.succeed(\"\")\n : Effect.fail(refusal(kind === undefined ? \"NotFound\" : \"BadResource\", \"readLink\", value))\n },\n readFile,\n readFileString: (value) => Effect.map(readFile(value), (bytes) => new TextDecoder().decode(bytes)),\n stream: (value) => Stream.fromEffect(readFile(value))\n })\n}\n"]}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The regular files of a tar stream.
3
+ *
4
+ * `RemoteSearch` brings a machine's files back with its own `tar`, because
5
+ * every image ships one and it frames each file with its name and size. GNU
6
+ * and BusyBox `tar` write GNU long names, `bsdtar` writes POSIX extended
7
+ * headers, and `xargs` may start `tar` more than once, so the stream can hold
8
+ * several archives one after another. This reads all of them.
9
+ *
10
+ * @since 1.0.0-rc.8
11
+ */
12
+ /**
13
+ * Every regular file the stream holds, by member name, or `undefined` when
14
+ * the stream is not whole tar archives: a header whose checksum does not
15
+ * hold, a member cut short, or a last archive with no end. A `tar` that
16
+ * stopped between two members wrote no end, so its missing files are not
17
+ * mistaken for files it could not read.
18
+ *
19
+ * @private
20
+ * @since 1.0.0-rc.8
21
+ */
22
+ export declare const files: (stream: Uint8Array) => ReadonlyMap<string, Uint8Array> | undefined;
23
+ //# sourceMappingURL=Tar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Tar.d.ts","sourceRoot":"","sources":["../../../src/internal/Tar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAsCH;;;;;;;;;GASG;AACH,eAAO,MAAM,KAAK,GAAI,QAAQ,UAAU,KAAG,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,SAoC5E,CAAA"}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * The regular files of a tar stream.
3
+ *
4
+ * `RemoteSearch` brings a machine's files back with its own `tar`, because
5
+ * every image ships one and it frames each file with its name and size. GNU
6
+ * and BusyBox `tar` write GNU long names, `bsdtar` writes POSIX extended
7
+ * headers, and `xargs` may start `tar` more than once, so the stream can hold
8
+ * several archives one after another. This reads all of them.
9
+ *
10
+ * @since 1.0.0-rc.8
11
+ */
12
+ const decoder = new TextDecoder();
13
+ /** Bytes up to the first NUL, as text. */
14
+ const text = (bytes) => {
15
+ const end = bytes.indexOf(0);
16
+ return decoder.decode(end === -1 ? bytes : bytes.subarray(0, end));
17
+ };
18
+ /** An octal header field, or `undefined` when it holds anything else. */
19
+ const octal = (bytes) => {
20
+ const digits = text(bytes).trim();
21
+ return /^[0-7]+$/.test(digits) ? Number.parseInt(digits, 8) : undefined;
22
+ };
23
+ /** The header's checksum, computed with the checksum field read as spaces. */
24
+ const checksum = (header) => {
25
+ let sum = 0;
26
+ for (let index = 0; index < 512; index++)
27
+ sum += index >= 148 && index < 156 ? 32 : header[index];
28
+ return sum;
29
+ };
30
+ /** The `path` record of a POSIX extended header: `<length> <key>=<value>\n` records. */
31
+ const paxPath = (data) => {
32
+ let offset = 0;
33
+ let path;
34
+ while (offset < data.length) {
35
+ const space = data.indexOf(32, offset);
36
+ const length = space === -1 ? Number.NaN : Number(decoder.decode(data.subarray(offset, space)));
37
+ if (!Number.isSafeInteger(length) || length <= space - offset || offset + length > data.length)
38
+ return path;
39
+ const record = decoder.decode(data.subarray(space + 1, offset + length - 1));
40
+ if (record.startsWith("path="))
41
+ path = record.slice("path=".length);
42
+ offset += length;
43
+ }
44
+ return path;
45
+ };
46
+ /**
47
+ * Every regular file the stream holds, by member name, or `undefined` when
48
+ * the stream is not whole tar archives: a header whose checksum does not
49
+ * hold, a member cut short, or a last archive with no end. A `tar` that
50
+ * stopped between two members wrote no end, so its missing files are not
51
+ * mistaken for files it could not read.
52
+ *
53
+ * @private
54
+ * @since 1.0.0-rc.8
55
+ */
56
+ export const files = (stream) => {
57
+ const found = new Map();
58
+ let longName;
59
+ let extended;
60
+ let ended = true;
61
+ for (let offset = 0; offset + 512 <= stream.length;) {
62
+ const header = stream.subarray(offset, offset + 512);
63
+ offset += 512;
64
+ // Zero blocks end one archive; `xargs` may have started another.
65
+ ended = header.every((byte) => byte === 0);
66
+ if (ended)
67
+ continue;
68
+ const size = octal(header.subarray(124, 136));
69
+ if (size === undefined || octal(header.subarray(148, 156)) !== checksum(header))
70
+ return undefined;
71
+ if (offset + size > stream.length)
72
+ return undefined;
73
+ const data = stream.subarray(offset, offset + size);
74
+ offset += Math.ceil(size / 512) * 512;
75
+ const type = String.fromCharCode(header[156]);
76
+ if (type === "L") {
77
+ longName = text(data);
78
+ continue;
79
+ }
80
+ if (type === "x") {
81
+ extended = paxPath(data) ?? extended;
82
+ continue;
83
+ }
84
+ // A long link name and a global header describe no member of their own.
85
+ if (type === "K" || type === "g")
86
+ continue;
87
+ if (type === "0" || type === "\0" || type === "7") {
88
+ const prefix = decoder.decode(header.subarray(257, 263)) === "ustar\0" ? text(header.subarray(345, 500)) : "";
89
+ const short = text(header.subarray(0, 100));
90
+ found.set(extended ?? longName ?? (prefix === "" ? short : `${prefix}/${short}`), data.slice());
91
+ }
92
+ longName = undefined;
93
+ extended = undefined;
94
+ }
95
+ return ended ? found : undefined;
96
+ };
97
+ //# sourceMappingURL=Tar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Tar.js","sourceRoot":"","sources":["../../../src/internal/Tar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAA;AAEjC,0CAA0C;AAC1C,MAAM,IAAI,GAAG,CAAC,KAAiB,EAAU,EAAE;IACzC,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;IAC5B,OAAO,OAAO,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAA;AACpE,CAAC,CAAA;AAED,yEAAyE;AACzE,MAAM,KAAK,GAAG,CAAC,KAAiB,EAAsB,EAAE;IACtD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAA;IACjC,OAAO,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;AACzE,CAAC,CAAA;AAED,8EAA8E;AAC9E,MAAM,QAAQ,GAAG,CAAC,MAAkB,EAAU,EAAE;IAC9C,IAAI,GAAG,GAAG,CAAC,CAAA;IACX,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,GAAG,EAAE,KAAK,EAAE;QAAE,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAE,CAAA;IAClG,OAAO,GAAG,CAAA;AACZ,CAAC,CAAA;AAED,wFAAwF;AACxF,MAAM,OAAO,GAAG,CAAC,IAAgB,EAAsB,EAAE;IACvD,IAAI,MAAM,GAAG,CAAC,CAAA;IACd,IAAI,IAAwB,CAAA;IAC5B,OAAO,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QAC5B,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,MAAM,CAAC,CAAA;QACtC,MAAM,MAAM,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,CAAA;QAC/F,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,KAAK,GAAG,MAAM,IAAI,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAA;QAC3G,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,CAAA;QAC5E,IAAI,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC;YAAE,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;QACnE,MAAM,IAAI,MAAM,CAAA;IAClB,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC,CAAA;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,MAAkB,EAA+C,EAAE;IACvF,MAAM,KAAK,GAAG,IAAI,GAAG,EAAsB,CAAA;IAC3C,IAAI,QAA4B,CAAA;IAChC,IAAI,QAA4B,CAAA;IAChC,IAAI,KAAK,GAAG,IAAI,CAAA;IAChB,KAAK,IAAI,MAAM,GAAG,CAAC,EAAE,MAAM,GAAG,GAAG,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;QACpD,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,CAAA;QACpD,MAAM,IAAI,GAAG,CAAA;QACb,iEAAiE;QACjE,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,CAAC,CAAC,CAAA;QAC1C,IAAI,KAAK;YAAE,SAAQ;QACnB,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAA;QAC7C,IAAI,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,KAAK,QAAQ,CAAC,MAAM,CAAC;YAAE,OAAO,SAAS,CAAA;QACjG,IAAI,MAAM,GAAG,IAAI,GAAG,MAAM,CAAC,MAAM;YAAE,OAAO,SAAS,CAAA;QACnD,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAA;QACnD,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,GAAG,GAAG,CAAA;QACrC,MAAM,IAAI,GAAG,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,CAAE,CAAC,CAAA;QAC9C,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACjB,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,CAAA;YACrB,SAAQ;QACV,CAAC;QACD,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACjB,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAA;YACpC,SAAQ;QACV,CAAC;QACD,wEAAwE;QACxE,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG;YAAE,SAAQ;QAC1C,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YAClD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;YAC7G,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAA;YAC3C,KAAK,CAAC,GAAG,CAAC,QAAQ,IAAI,QAAQ,IAAI,CAAC,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,CAAC,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAA;QACjG,CAAC;QACD,QAAQ,GAAG,SAAS,CAAA;QACpB,QAAQ,GAAG,SAAS,CAAA;IACtB,CAAC;IACD,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAA;AAClC,CAAC,CAAA","sourcesContent":["/**\n * The regular files of a tar stream.\n *\n * `RemoteSearch` brings a machine's files back with its own `tar`, because\n * every image ships one and it frames each file with its name and size. GNU\n * and BusyBox `tar` write GNU long names, `bsdtar` writes POSIX extended\n * headers, and `xargs` may start `tar` more than once, so the stream can hold\n * several archives one after another. This reads all of them.\n *\n * @since 1.0.0-rc.8\n */\n\nconst decoder = new TextDecoder()\n\n/** Bytes up to the first NUL, as text. */\nconst text = (bytes: Uint8Array): string => {\n const end = bytes.indexOf(0)\n return decoder.decode(end === -1 ? bytes : bytes.subarray(0, end))\n}\n\n/** An octal header field, or `undefined` when it holds anything else. */\nconst octal = (bytes: Uint8Array): number | undefined => {\n const digits = text(bytes).trim()\n return /^[0-7]+$/.test(digits) ? Number.parseInt(digits, 8) : undefined\n}\n\n/** The header's checksum, computed with the checksum field read as spaces. */\nconst checksum = (header: Uint8Array): number => {\n let sum = 0\n for (let index = 0; index < 512; index++) sum += index >= 148 && index < 156 ? 32 : header[index]!\n return sum\n}\n\n/** The `path` record of a POSIX extended header: `<length> <key>=<value>\\n` records. */\nconst paxPath = (data: Uint8Array): string | undefined => {\n let offset = 0\n let path: string | undefined\n while (offset < data.length) {\n const space = data.indexOf(32, offset)\n const length = space === -1 ? Number.NaN : Number(decoder.decode(data.subarray(offset, space)))\n if (!Number.isSafeInteger(length) || length <= space - offset || offset + length > data.length) return path\n const record = decoder.decode(data.subarray(space + 1, offset + length - 1))\n if (record.startsWith(\"path=\")) path = record.slice(\"path=\".length)\n offset += length\n }\n return path\n}\n\n/**\n * Every regular file the stream holds, by member name, or `undefined` when\n * the stream is not whole tar archives: a header whose checksum does not\n * hold, a member cut short, or a last archive with no end. A `tar` that\n * stopped between two members wrote no end, so its missing files are not\n * mistaken for files it could not read.\n *\n * @private\n * @since 1.0.0-rc.8\n */\nexport const files = (stream: Uint8Array): ReadonlyMap<string, Uint8Array> | undefined => {\n const found = new Map<string, Uint8Array>()\n let longName: string | undefined\n let extended: string | undefined\n let ended = true\n for (let offset = 0; offset + 512 <= stream.length;) {\n const header = stream.subarray(offset, offset + 512)\n offset += 512\n // Zero blocks end one archive; `xargs` may have started another.\n ended = header.every((byte) => byte === 0)\n if (ended) continue\n const size = octal(header.subarray(124, 136))\n if (size === undefined || octal(header.subarray(148, 156)) !== checksum(header)) return undefined\n if (offset + size > stream.length) return undefined\n const data = stream.subarray(offset, offset + size)\n offset += Math.ceil(size / 512) * 512\n const type = String.fromCharCode(header[156]!)\n if (type === \"L\") {\n longName = text(data)\n continue\n }\n if (type === \"x\") {\n extended = paxPath(data) ?? extended\n continue\n }\n // A long link name and a global header describe no member of their own.\n if (type === \"K\" || type === \"g\") continue\n if (type === \"0\" || type === \"\\0\" || type === \"7\") {\n const prefix = decoder.decode(header.subarray(257, 263)) === \"ustar\\0\" ? text(header.subarray(345, 500)) : \"\"\n const short = text(header.subarray(0, 100))\n found.set(extended ?? longName ?? (prefix === \"\" ? short : `${prefix}/${short}`), data.slice())\n }\n longName = undefined\n extended = undefined\n }\n return ended ? found : undefined\n}\n"]}
package/docs/api.md CHANGED
@@ -180,10 +180,14 @@ resolves its own services when it is built.
180
180
  The in-process peer. Walks the injected `FileSystem`, so it needs no external
181
181
  binary and is browser-safe.
182
182
 
183
- | Export | Type |
184
- | ------- | ---------------------------------------------------------- |
185
- | `make` | `(services: Context<FileSystem \| Path>) => Search.Search` |
186
- | `layer` | `Layer<Search.Search, never, FileSystem \| Path>` |
183
+ | Export | Type |
184
+ | ---------- | -------------------------------------------------------------------------------------------------------------------------------------- |
185
+ | `make` | `(services: Context<FileSystem \| Path>) => Search.Search` |
186
+ | `layer` | `Layer<Search.Search, never, FileSystem \| Path>` |
187
+ | `searched` | `(input: Pick<GrepInput, "root" \| "hidden" \| "noIgnore" \| "globs">) => Effect<ReadonlyArray<string>, StdError, FileSystem \| Path>` |
188
+
189
+ `searched` names the files `grep` reads for an input, in the order it reads
190
+ them; `RemoteSearch` fetches exactly those.
187
191
 
188
192
  ## NativeSearch
189
193
 
@@ -201,6 +205,26 @@ start `rg` is `provider_unavailable`. The default layer uses the host environmen
201
205
  allowlist. `make` accepts explicit environment declarations as its second
202
206
  argument; those names override the allowlist.
203
207
 
208
+ ## RemoteSearch
209
+
210
+ The peer for a tree on another machine, such as a container or a sandbox
211
+ machine. It lists the root with the machine's `find`, brings back its
212
+ `.gitignore` files and then the files the search reads with `tar`, and lets
213
+ `PortableSearch` answer over that copy, so every result, notice, and refusal is
214
+ the in-process peer's. Each search is two round trips through the machine's
215
+ spawner, whatever the tree's size.
216
+
217
+ | Export | Type | Meaning |
218
+ | ---------- | ---------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
219
+ | `maxBytes` | `268_435_456` | The most file content one search brings back; past it, `response_too_large`. |
220
+ | `make` | `(services: Context<ChildProcessSpawner \| Path>, options?: { maxBytes?: number }) => Search.Search` | The spawner runs on the machine; `Path` resolves a relative root there. |
221
+
222
+ The machine needs `sh`, `find`, `xargs`, `tar`, `head`, and `base64`; GNU,
223
+ BusyBox, and BSD userlands all have them, and a machine without one refuses
224
+ with `command_failed` naming it. The program goes in on standard input and
225
+ the answer comes back as base64, so a transport whose argv is bounded or whose
226
+ output channel carries only text answers intact.
227
+
204
228
  ## SearchContract
205
229
 
206
230
  The validation and matching rules both peers build on, exported so a third peer
@@ -112,7 +112,9 @@ handler checks the command against them before spawning anything. Read what the
112
112
  check does, because the name promises more than it delivers.
113
113
 
114
114
  The check is **lexical**. It tokenizes the command line, keeping quoted spans
115
- whole, treats each physical line as its own command, classifies each path token
115
+ whole, treats each physical line as its own command, reads the body of a
116
+ command substitution, the argument of a shell's `-c` flag, and the arguments of
117
+ `eval` as commands of their own, classifies each path token
116
118
  as a read or a write from the command that governs it, resolves both the token
117
119
  and every declaration to a canonical absolute path, and refuses the call with
118
120
  `outside_declared_reads` or `outside_declared_writes` when one falls outside.
@@ -36,7 +36,9 @@ const host = Layer.mergeAll(
36
36
 
37
37
  Swap `NativeSearch.layer` for `PortableSearch.layer` to search in process with
38
38
  no external binary. Both satisfy the same `Search` service, and nothing above
39
- the layer changes.
39
+ the layer changes. When the files live on another machine, such as a container
40
+ or a sandbox machine, `RemoteSearch.make` over that machine's spawner and `Path`
41
+ answers in two round trips where the portable peer would ask once per file.
40
42
 
41
43
  A host that cannot serve a service binds its refusal layer instead of leaving it
42
44
  out, so the failure names the missing service rather than a missing context:
@@ -91,7 +91,10 @@ tracked in [#1790](https://github.com/smithersai/smithers/issues/1790).
91
91
  A host sealed to one container is the exception. `SMITHERS_BASH_CONTAINER` names
92
92
  the container, and `bash` and `test` then run in it through the `docker` CLI.
93
93
  Every other container, and a call that names none, fails with
94
- `outside_container`. The transport process runs outside the OS sandbox, which
94
+ `outside_container`. The file flows (`read`, `edit`, `apply_patch`, `grep` and
95
+ the rest) act on the container's files through the same transport, resolving a
96
+ relative path under `SMITHERS_TEST_CWD`, and `grep` and `glob` answer through
97
+ `RemoteSearch`. The transport process runs outside the OS sandbox, which
95
98
  denies the daemon's socket, and receives the host's `DOCKER_HOST`,
96
99
  `DOCKER_CONTEXT`, `DOCKER_CONFIG`, `DOCKER_TLS_VERIFY`, and `DOCKER_CERT_PATH`,
97
100
  so it reaches a rootless daemon.